Skip to main content

gproxy_channel_api/
transport.rs

1//! Host-provided upstream transport capability available to channel adapters.
2
3use bytes::Bytes;
4
5/// Per-request transport behavior carried through [`http::Request::extensions`].
6///
7/// All request entrypoints consume these options, including WebSocket ones. The
8/// built-in native wreq transport enforces redirect/version policy on the
9/// WebSocket handshake; a WebSocket `total_timeout` remains in force for the
10/// socket's sends and receives. A native WebSocket handshake never sends the
11/// request body, independently of `omit_body`.
12///
13/// The built-in Fetch transport enforces `total_timeout`, `omit_body`, and
14/// `max_redirects = Some(0)`. For its terminal WebSocket round trip, the timeout
15/// covers both Fetch and socket use, while `omit_body` suppresses the initial
16/// application frame. Fetch cannot select an HTTP version or guarantee an exact
17/// positive redirect bound, so it returns [`ClientError::Config`] when either
18/// unsupported constraint is set; callers may leave those fields as `None` to
19/// use the host Fetch policy.
20#[derive(Debug, Clone, Copy, Default)]
21pub struct TransportOptions {
22    pub total_timeout: Option<std::time::Duration>,
23    pub max_redirects: Option<usize>,
24    pub http_version: Option<http::Version>,
25    pub omit_body: bool,
26}
27
28/// Synchronous decoder for a chunked upstream byte stream.
29pub trait ByteStreamDecoder: Send {
30    /// Feed one raw upstream chunk and return any decoded bytes.
31    fn push(&mut self, chunk: &[u8]) -> Vec<u8>;
32
33    /// Flush trailing buffered state at end of stream.
34    fn finish(&mut self) -> Vec<u8>;
35}
36
37/// Transport-level error from the upstream client.
38#[derive(Debug, thiserror::Error)]
39pub enum ClientError {
40    #[error("upstream transport error: {0}")]
41    Transport(String),
42    /// Per-target client configuration is unusable. The host fails the attempt
43    /// instead of silently downgrading proxy or TLS policy.
44    #[error("upstream client config error: {0}")]
45    Config(String),
46}
47
48/// Streaming response body. Native streams are `Send`; wasm streams stay local
49/// because Fetch `ReadableStream` handles are JS-bound.
50#[cfg(not(target_arch = "wasm32"))]
51pub type RespStream =
52    std::pin::Pin<Box<dyn futures_core::Stream<Item = Result<Bytes, ClientError>> + Send>>;
53#[cfg(target_arch = "wasm32")]
54pub type RespStream =
55    std::pin::Pin<Box<dyn futures_core::Stream<Item = Result<Bytes, ClientError>>>>;
56
57/// An open upstream WebSocket (native only), kept minimal and object-safe.
58#[cfg(not(target_arch = "wasm32"))]
59#[async_trait::async_trait]
60pub trait ConduitSocket: Send {
61    async fn send_text(&mut self, text: String) -> Result<(), ClientError>;
62    async fn recv_text(&mut self) -> Option<Result<String, ClientError>>;
63}
64
65/// Host-owned upstream capability. Implementations apply the resolved proxy,
66/// TLS profile, capture policy, and platform transport; channels only construct
67/// requests and interpret responses.
68#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
69#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
70pub trait UpstreamClient: Send + Sync {
71    async fn send(&self, req: http::Request<Bytes>) -> Result<http::Response<Bytes>, ClientError>;
72
73    async fn send_websocket(
74        &self,
75        _req: http::Request<Bytes>,
76    ) -> Result<http::Response<Bytes>, ClientError> {
77        Err(ClientError::Config(
78            "upstream websocket not supported by this client".into(),
79        ))
80    }
81
82    async fn send_streaming(
83        &self,
84        req: http::Request<Bytes>,
85    ) -> Result<(http::StatusCode, http::HeaderMap, RespStream), ClientError> {
86        let resp = self.send(req).await?;
87        let (parts, body) = resp.into_parts();
88        let once = futures_util::stream::once(async move { Ok::<Bytes, ClientError>(body) });
89        Ok((parts.status, parts.headers, Box::pin(once)))
90    }
91
92    #[cfg(not(target_arch = "wasm32"))]
93    async fn open_websocket(
94        &self,
95        _req: http::Request<Bytes>,
96    ) -> Result<Box<dyn ConduitSocket>, ClientError> {
97        Err(ClientError::Config(
98            "upstream websocket not supported by this client".into(),
99        ))
100    }
101}