wasmer_wasix/http/
client.rs

1use std::{collections::BTreeSet, ops::Deref, sync::Arc};
2
3use futures::future::BoxFuture;
4use http::{HeaderMap, Method, StatusCode};
5use url::Url;
6
7/// Defines http client permissions.
8#[derive(Clone, Debug, PartialEq, Eq, Hash)]
9pub struct HttpClientCapabilityV1 {
10    pub allow_all: bool,
11    pub allowed_hosts: BTreeSet<String>,
12}
13
14impl HttpClientCapabilityV1 {
15    pub fn new() -> Self {
16        Self {
17            allow_all: false,
18            allowed_hosts: Default::default(),
19        }
20    }
21
22    pub fn new_allow_all() -> Self {
23        Self {
24            allow_all: true,
25            allowed_hosts: Default::default(),
26        }
27    }
28
29    pub fn is_deny_all(&self) -> bool {
30        !self.allow_all && self.allowed_hosts.is_empty()
31    }
32
33    pub fn can_access_domain(&self, domain: &str) -> bool {
34        self.allow_all || self.allowed_hosts.contains(domain)
35    }
36
37    pub fn update(&mut self, other: HttpClientCapabilityV1) {
38        let HttpClientCapabilityV1 {
39            allow_all,
40            allowed_hosts,
41        } = other;
42        self.allow_all |= allow_all;
43        self.allowed_hosts.extend(allowed_hosts);
44    }
45}
46
47impl Default for HttpClientCapabilityV1 {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53#[derive(Debug, Default)]
54pub struct HttpRequestOptions {
55    pub gzip: bool,
56    pub cors_proxy: Option<String>,
57}
58
59// TODO: use types from http crate?
60pub struct HttpRequest {
61    pub url: Url,
62    pub method: Method,
63    pub headers: HeaderMap,
64    pub body: Option<Vec<u8>>,
65    pub options: HttpRequestOptions,
66}
67
68impl HttpRequest {
69    fn from_http_parts(parts: http::request::Parts, body: impl Into<Option<Vec<u8>>>) -> Self {
70        let http::request::Parts {
71            method,
72            uri,
73            headers,
74            ..
75        } = parts;
76
77        HttpRequest {
78            url: uri.to_string().parse().unwrap(),
79            method,
80            headers,
81            body: body.into(),
82            options: HttpRequestOptions::default(),
83        }
84    }
85}
86
87impl std::fmt::Debug for HttpRequest {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        let HttpRequest {
90            url,
91            method,
92            headers,
93            body,
94            options,
95        } = self;
96
97        f.debug_struct("HttpRequest")
98            .field("url", &format_args!("{url}"))
99            .field("method", method)
100            .field("headers", headers)
101            .field("body", &body.as_deref().map(String::from_utf8_lossy))
102            .field("options", &options)
103            .finish()
104    }
105}
106
107impl From<http::Request<Option<Vec<u8>>>> for HttpRequest {
108    fn from(value: http::Request<Option<Vec<u8>>>) -> Self {
109        let (parts, body) = value.into_parts();
110        HttpRequest::from_http_parts(parts, body)
111    }
112}
113
114impl From<http::Request<Vec<u8>>> for HttpRequest {
115    fn from(value: http::Request<Vec<u8>>) -> Self {
116        let (parts, body) = value.into_parts();
117        HttpRequest::from_http_parts(parts, body)
118    }
119}
120
121impl From<http::Request<&str>> for HttpRequest {
122    fn from(value: http::Request<&str>) -> Self {
123        value.map(|body| body.to_string()).into()
124    }
125}
126
127impl From<http::Request<String>> for HttpRequest {
128    fn from(value: http::Request<String>) -> Self {
129        let (parts, body) = value.into_parts();
130        HttpRequest::from_http_parts(parts, body.into_bytes())
131    }
132}
133
134impl From<http::Request<()>> for HttpRequest {
135    fn from(value: http::Request<()>) -> Self {
136        let (parts, _) = value.into_parts();
137        HttpRequest::from_http_parts(parts, None)
138    }
139}
140
141// TODO: use types from http crate?
142pub struct HttpResponse {
143    pub body: Option<Vec<u8>>,
144    pub redirected: bool,
145    pub status: StatusCode,
146    pub headers: HeaderMap,
147}
148
149impl HttpResponse {
150    pub fn is_ok(&self) -> bool {
151        !self.status.is_client_error() && !self.status.is_server_error()
152    }
153}
154
155impl std::fmt::Debug for HttpResponse {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        let HttpResponse {
158            body,
159            redirected,
160            status,
161            headers,
162        } = self;
163
164        f.debug_struct("HttpResponse")
165            .field("ok", &self.is_ok())
166            .field("redirected", &redirected)
167            .field("status", &status)
168            .field("headers", &headers)
169            .field("body", &body.as_deref().map(String::from_utf8_lossy))
170            .finish()
171    }
172}
173
174pub trait HttpClient: std::fmt::Debug {
175    // TODO: use custom error type!
176    fn request(&self, request: HttpRequest) -> BoxFuture<'_, Result<HttpResponse, anyhow::Error>>;
177}
178
179impl<D, C> HttpClient for D
180where
181    D: Deref<Target = C> + std::fmt::Debug,
182    C: HttpClient + ?Sized + 'static,
183{
184    fn request(&self, request: HttpRequest) -> BoxFuture<'_, Result<HttpResponse, anyhow::Error>> {
185        let client = &**self;
186        client.request(request)
187    }
188}
189
190pub type DynHttpClient = Arc<dyn HttpClient + Send + Sync + 'static>;