Skip to main content

ic_bn_lib/http/
proxy.rs

1use std::sync::Arc;
2
3use axum::{body::Body, extract::Request, response::Response};
4use ic_bn_lib_common::{
5    traits::http::{Client, ClientHttp},
6    types::http::Error,
7};
8use url::Url;
9
10use super::{body::SyncBody, headers::strip_connection_headers};
11
12/// Proxies provided Axum request to a given URL using `Client` trait object and returns Axum response
13pub async fn proxy(
14    url: Url,
15    request: Request,
16    http_client: &Arc<dyn Client>,
17) -> Result<Response, Error> {
18    // Convert Axum request into Reqwest one
19    let (mut parts, body) = request.into_parts();
20
21    // Strip connection-related headers so that the request fits HTTP/2
22    strip_connection_headers(&mut parts.headers);
23
24    let mut request = reqwest::Request::new(parts.method.clone(), url);
25    *request.headers_mut() = parts.headers;
26
27    // Use SyncBody wrapper that is Sync (Axum body is !Sync)
28    *request.body_mut() = Some(reqwest::Body::wrap(SyncBody::new(body)));
29
30    // Execute the request
31    let response = http_client.execute(request).await?;
32
33    // Convert Reqwest response into Axum one
34    let response: http::Response<_> = response.into();
35    let (parts, body) = response.into_parts();
36
37    Ok(Response::from_parts(parts, Body::new(body)))
38}
39
40/// Proxies provided request to a given URL using `ClientHttp` trait object and returns Axum response
41pub async fn proxy_http<B>(
42    mut request: Request<B>,
43    http_client: &Arc<dyn ClientHttp<B>>,
44) -> Result<Response, Error> {
45    // Strip connection-related headers so that the request fits HTTP/2
46    strip_connection_headers(request.headers_mut());
47
48    // Execute the request
49    let response = http_client.execute(request).await?;
50
51    // Convert the response into Axum one
52    let (parts, body) = response.into_parts();
53    Ok(Response::from_parts(parts, Body::new(body)))
54}
55
56#[cfg(test)]
57mod test {
58    use std::sync::Arc;
59
60    use axum::{body::Body, extract::Request};
61    use http_body::Body as _;
62    use ic_bn_lib_common::traits::http::Client;
63
64    use crate::http::proxy::proxy;
65
66    #[derive(Debug)]
67    struct HttpClient;
68
69    #[async_trait::async_trait]
70    impl Client for HttpClient {
71        async fn execute(&self, _: reqwest::Request) -> Result<reqwest::Response, reqwest::Error> {
72            Ok(reqwest::Response::from(
73                http::response::Builder::new()
74                    .status(200)
75                    .body(reqwest::Body::from("foobarbaz"))
76                    .unwrap(),
77            ))
78        }
79    }
80
81    #[tokio::test]
82    async fn test_size_hint() {
83        let cli = Arc::new(HttpClient) as Arc<dyn Client>;
84        let url = url::Url::parse("http://foo.bar").unwrap();
85        let request = Request::new(Body::empty());
86        let resp = proxy(url, request, &cli).await.unwrap();
87
88        assert_eq!(resp.body().size_hint().exact(), Some(9));
89    }
90}