Skip to main content

ruma_client/
http_client.rs

1//! This module contains an abstraction for HTTP clients as well as friendly-named re-exports of
2//! client types that implement this trait.
3
4use std::{future::Future, pin::Pin};
5
6use bytes::BufMut;
7use ruma::api::{
8    AppserviceUserIdentity, OutgoingRequest,
9    auth_scheme::{AuthScheme, SendAccessToken},
10    path_builder::PathBuilder,
11};
12
13use crate::{ResponseError, ResponseResult};
14
15#[cfg(feature = "hyper")]
16mod hyper;
17#[cfg(feature = "reqwest")]
18mod reqwest;
19
20#[cfg(feature = "hyper")]
21pub use self::hyper::Hyper;
22#[cfg(feature = "hyper-native-tls")]
23pub use self::hyper::HyperNativeTls;
24#[cfg(feature = "hyper-rustls")]
25pub use self::hyper::HyperRustls;
26#[cfg(feature = "reqwest")]
27pub use self::reqwest::Reqwest;
28
29/// An HTTP client that can be used to send requests to a Matrix homeserver.
30pub trait HttpClient: Sync {
31    /// The type to use for `try_into_http_request`.
32    type RequestBody: AsRef<[u8]> + Default + BufMut + Send;
33
34    /// The type to use for `try_from_http_response`.
35    type ResponseBody: AsRef<[u8]>;
36
37    /// The error type for the `send_request` function.
38    type Error: Send + Unpin;
39
40    /// Send an `http::Request` to get back an `http::Response`.
41    fn send_http_request(
42        &self,
43        req: http::Request<Self::RequestBody>,
44    ) -> impl Future<Output = Result<http::Response<Self::ResponseBody>, Self::Error>> + Send;
45}
46
47/// An HTTP client that has a default configuration.
48pub trait DefaultConstructibleHttpClient: HttpClient {
49    /// Creates a new HTTP client with default configuration.
50    fn default() -> Self;
51}
52
53/// Convenience functionality on top of `HttpClient`.
54///
55/// If you want to build your own matrix client type instead of using `ruma_client::Client`, this
56/// trait should make that relatively easy.
57pub trait HttpClientExt: HttpClient {
58    /// Send a strongly-typed matrix request to get back a strongly-typed response.
59    // TODO: `R: 'a` bound should not be needed
60    fn send_matrix_request<'a, R>(
61        &'a self,
62        homeserver_url: &str,
63        access_token: SendAccessToken<'a>,
64        path_builder_input: <R::PathBuilder as PathBuilder>::Input<'_>,
65        request: R,
66    ) -> Pin<Box<dyn Future<Output = ResponseResult<Self, R>> + 'a + Send>>
67    where
68        R: OutgoingRequest,
69        R::Authentication: AuthScheme<Input<'a> = SendAccessToken<'a>>,
70    {
71        self.send_customized_matrix_request(
72            homeserver_url,
73            access_token,
74            path_builder_input,
75            request,
76            |_| Ok(()),
77        )
78    }
79
80    /// Turn a strongly-typed matrix request into an `http::Request`, customize it and send it to
81    /// get back a strongly-typed response.
82    // TODO: `R: 'a` and `F: 'a` should not be needed
83    fn send_customized_matrix_request<'a, R, F>(
84        &'a self,
85        homeserver_url: &str,
86        access_token: SendAccessToken<'a>,
87        path_builder_input: <R::PathBuilder as PathBuilder>::Input<'_>,
88        request: R,
89        customize: F,
90    ) -> Pin<Box<dyn Future<Output = ResponseResult<Self, R>> + 'a + Send>>
91    where
92        R: OutgoingRequest,
93        R::Authentication: AuthScheme<Input<'a> = SendAccessToken<'a>>,
94        F: FnOnce(&mut http::Request<Self::RequestBody>) -> Result<(), ResponseError<Self, R>>,
95    {
96        Box::pin(crate::send_customized_request(
97            self,
98            homeserver_url,
99            access_token,
100            path_builder_input,
101            request,
102            customize,
103        ))
104    }
105
106    /// Turn a strongly-typed matrix request into an `http::Request`, add `user_id` and/or
107    /// `device_id` query parameters to it and send it to get back a strongly-typed response.
108    ///
109    /// This method is meant to be used by application services when interacting with the
110    /// client-server API.
111    fn send_matrix_request_as<'a, R>(
112        &'a self,
113        homeserver_url: &str,
114        access_token: SendAccessToken<'a>,
115        path_builder_input: <R::PathBuilder as PathBuilder>::Input<'_>,
116        identity: AppserviceUserIdentity<'a>,
117        request: R,
118    ) -> Pin<Box<dyn Future<Output = ResponseResult<Self, R>> + 'a>>
119    where
120        R: OutgoingRequest,
121        R::Authentication: AuthScheme<Input<'a> = SendAccessToken<'a>>,
122    {
123        self.send_customized_matrix_request(
124            homeserver_url,
125            access_token,
126            path_builder_input,
127            request,
128            |request| Ok(identity.maybe_add_to_uri(request.uri_mut())?),
129        )
130    }
131}
132
133impl<T: HttpClient> HttpClientExt for T {}
134
135#[doc(hidden)]
136#[derive(Debug)]
137#[allow(clippy::exhaustive_structs)]
138pub struct Dummy;
139
140impl HttpClient for Dummy {
141    type RequestBody = Vec<u8>;
142    type ResponseBody = Vec<u8>;
143    type Error = ();
144
145    #[allow(clippy::diverging_sub_expression)]
146    async fn send_http_request(
147        &self,
148        _req: http::Request<Self::RequestBody>,
149    ) -> Result<http::Response<Self::ResponseBody>, Self::Error> {
150        unimplemented!("this client only exists to allow doctests to compile")
151    }
152}
153
154impl DefaultConstructibleHttpClient for Dummy {
155    fn default() -> Self {
156        Dummy
157    }
158}