Skip to main content

actioncable/
transport.rs

1use std::sync::Arc;
2
3use async_trait::async_trait;
4use http::HeaderMap;
5
6use crate::error::Error;
7
8/// Dials the network connection a client talks over. It is the seam where a network handler
9/// plugs in: the built-in `WebSocketTransport` speaks RFC 6455 over tokio-tungstenite, and
10/// wrapping another WebSocket crate, or an in-memory pipe for tests, means implementing
11/// this and [`Conn`] and nothing else.
12#[async_trait]
13pub trait Transport: Send + Sync {
14    /// Opens one connection. `options` carries the subprotocols the client's protocols
15    /// negotiate under and the headers that authorize the request.
16    async fn dial(&self, url: &str, options: DialOptions) -> Result<Box<dyn Conn>, Error>;
17}
18
19/// A shared transport dials like the one it shares, so an application can keep one and hand
20/// it to every client it builds.
21#[async_trait]
22impl<T: Transport + ?Sized> Transport for Arc<T> {
23    async fn dial(&self, url: &str, options: DialOptions) -> Result<Box<dyn Conn>, Error> {
24        (**self).dial(url, options).await
25    }
26}
27
28/// What the client needs the transport to negotiate: the subprotocols its protocols speak,
29/// most preferred first, and the headers that authenticate the request, a cookie or a
30/// token, since an Action Cable server authorizes the upgrade request itself.
31#[derive(Debug, Clone, Default)]
32pub struct DialOptions {
33    /// The subprotocols to offer, most preferred first, with
34    /// [`SUBPROTOCOL_UNSUPPORTED`](crate::SUBPROTOCOL_UNSUPPORTED) last.
35    pub subprotocols: Vec<String>,
36    /// The headers the upgrade request carries and nothing else ambient.
37    pub headers: HeaderMap,
38}
39
40/// One live connection. The client reads from one task and writes from another, one write
41/// at a time, and may call [`close`](Conn::close) concurrently with either. Closing has to
42/// interrupt a read in flight so the reader learns the connection is gone.
43#[async_trait]
44pub trait Conn: Send + Sync {
45    /// What the server negotiated, `None` if it named nothing.
46    fn subprotocol(&self) -> Option<&str>;
47
48    /// The next complete message. Fails once the connection is unusable, including after
49    /// `close`.
50    async fn read(&self) -> Result<Vec<u8>, Error>;
51
52    /// Sends one text message.
53    async fn write(&self, payload: &[u8]) -> Result<(), Error>;
54
55    /// Hangs up, with 1000 Normal Closure where the protocol has a code to send. Safe to
56    /// call twice.
57    async fn close(&self);
58
59    /// Hangs up with a code and reason of the caller's choosing, for a caller with
60    /// something to tell the server. Where the Go client asks whether a connection also
61    /// implements `StatusCloser`, a Rust caller can always call this: the default forwards
62    /// to [`close`](Conn::close), so a transport whose library can't say why hangs up all
63    /// the same. The built-in transport sends the code and as much of the reason as a
64    /// control frame has room for.
65    async fn close_with_status(&self, code: u16, reason: &str) {
66        let _ = (code, reason);
67        self.close().await;
68    }
69}