sdf_http/
lib.rs

1pub mod http {
2    pub use http::*;
3}
4
5use anyhow::anyhow;
6pub type Result<T> = anyhow::Result<T>;
7pub type Error = anyhow::Error;
8
9mod bindings {
10    wit_bindgen::generate!({
11        path: "wit",
12        world: "http",
13        generate_all
14    });
15}
16
17/// re-export serde
18#[cfg(feature = "serde")]
19pub use serde;
20
21/// re-export serde_json
22#[cfg(feature = "serde_json")]
23pub use serde_json;
24
25pub use wrapper::*;
26
27// high level API similar to Reqwest
28mod wrapper {
29    use std::{
30        any::Any,
31        ops::{Deref, DerefMut},
32    };
33
34    use http::{request::Builder, Error, HeaderName, HeaderValue, Response, Uri, Version};
35
36    pub struct ByteResponse(Response<Vec<u8>>);
37
38    impl Deref for ByteResponse {
39        type Target = Response<Vec<u8>>;
40
41        fn deref(&self) -> &Self::Target {
42            &self.0
43        }
44    }
45
46    impl DerefMut for ByteResponse {
47        fn deref_mut(&mut self) -> &mut Self::Target {
48            &mut self.0
49        }
50    }
51
52    impl From<Response<Vec<u8>>> for ByteResponse {
53        fn from(response: Response<Vec<u8>>) -> Self {
54            ByteResponse(response)
55        }
56    }
57
58    impl ByteResponse {
59        pub fn text(self) -> anyhow::Result<String> {
60            Ok(String::from_utf8(self.0.into_body())?)
61        }
62
63        pub fn bytes(self) -> Vec<u8> {
64            self.0.into_body()
65        }
66
67        pub fn as_slice(&self) -> &[u8] {
68            self.0.body()
69        }
70
71        pub fn into_inner(self) -> Response<Vec<u8>> {
72            self.0
73        }
74    }
75
76    /// Shortcut method to quickly make a sync `GET` request using HTTP WASI call
77    /// and return the response with Wrapper around it.
78    pub fn get<T>(uri: T) -> anyhow::Result<ByteResponse>
79    where
80        T: TryInto<Uri>,
81        <T as TryInto<Uri>>::Error: Into<Error>,
82    {
83        let body_helper = BodyBuilder::empty();
84        body_helper.get(uri)
85    }
86
87    /// JSON request builder helper
88    pub fn json<T>(body: T) -> BodyBuilder<T> {
89        let request = crate::http::Request::builder().header("Content-Type", "application/json");
90        BodyBuilder {
91            body,
92            builder: request,
93        }
94    }
95
96    /// Body based builder, we subvert the Request builder oriented toward body
97    /// This make it easier to build request with body
98    pub struct BodyBuilder<Body> {
99        body: Body,
100        builder: Builder,
101    }
102
103    impl BodyBuilder<Vec<u8>> {
104        pub fn empty() -> Self {
105            Self {
106                body: Vec::new(),
107                builder: crate::http::Request::builder(),
108            }
109        }
110    }
111
112    impl<Body> BodyBuilder<Body>
113    where
114        Body: AsRef<[u8]>,
115    {
116        pub fn body(self) -> Body {
117            self.body
118        }
119
120        pub fn body_ref(&self) -> &Body {
121            &self.body
122        }
123
124        pub fn version(self, version: Version) -> Self {
125            Self {
126                builder: self.builder.version(version),
127                body: self.body,
128            }
129        }
130
131        /// set authorization header
132        pub fn auth<V>(self, token: V) -> Self
133        where
134            V: TryInto<HeaderValue>,
135            <V as TryInto<HeaderValue>>::Error: Into<Error>,
136        {
137            self.header("Authorization", token)
138        }
139
140        /// set bearer token, this is a shortcut for `Authorization: Bearer
141        pub fn bearer(self, token: &str) -> Self {
142            self.auth(format!("Bearer {}", token))
143        }
144
145        pub fn header<K, V>(self, key: K, value: V) -> Self
146        where
147            K: TryInto<HeaderName>,
148            <K as TryInto<HeaderName>>::Error: Into<Error>,
149            V: TryInto<HeaderValue>,
150            <V as TryInto<HeaderValue>>::Error: Into<Error>,
151        {
152            Self {
153                builder: self.builder.header(key, value),
154                body: self.body,
155            }
156        }
157
158        pub fn extension<T>(self, extension: T) -> Self
159        where
160            T: Clone + Any + Send + Sync + 'static,
161        {
162            Self {
163                builder: self.builder.extension(extension),
164                body: self.body,
165            }
166        }
167
168        /// invoke post call with the given URI and return the response
169        pub fn post<T>(self, uri: T) -> anyhow::Result<ByteResponse>
170        where
171            T: TryInto<Uri>,
172            <T as TryInto<Uri>>::Error: Into<Error>,
173        {
174            let request = self
175                .builder
176                .uri(uri)
177                .method(http::Method::POST)
178                .body(self.body)?;
179            let response = crate::blocking::send(request)?;
180            Ok(response.into())
181        }
182
183        pub fn get<T>(self, uri: T) -> anyhow::Result<ByteResponse>
184        where
185            T: TryInto<Uri>,
186            <T as TryInto<Uri>>::Error: Into<Error>,
187        {
188            let request = self
189                .builder
190                .uri(uri)
191                .method(http::Method::GET)
192                .body(self.body)?;
193            let response = crate::blocking::send(request)?;
194            Ok(response.into())
195        }
196    }
197
198    impl<Body> Deref for BodyBuilder<Body> {
199        type Target = Builder;
200
201        fn deref(&self) -> &Self::Target {
202            &self.builder
203        }
204    }
205
206    impl<Body> DerefMut for BodyBuilder<Body> {
207        fn deref_mut(&mut self) -> &mut Self::Target {
208            &mut self.builder
209        }
210    }
211}
212
213pub mod blocking {
214    use anyhow::anyhow;
215
216    use crate::bindings::wasi::http::outgoing_handler;
217    use crate::bindings::wasi::http::types::{OutgoingBody, OutgoingRequest};
218    use crate::bindings::wasi::io::streams::StreamError;
219
220    use super::http::{Request, Response};
221    use super::Result;
222
223    pub fn send<T: AsRef<[u8]>>(request: Request<T>) -> Result<Response<Vec<u8>>> {
224        let request_wasi = OutgoingRequest::try_from(&request)?;
225
226        let request_body = request_wasi
227            .body()
228            .map_err(|_| anyhow!("outgoing request write failed"))?;
229        let output_stream = request_body
230            .write()
231            .map_err(|_| anyhow!("request has no input stream"))?;
232        output_stream.write(request.body().as_ref())?;
233        drop(output_stream);
234
235        let response_fut = outgoing_handler::handle(request_wasi, None)?;
236        OutgoingBody::finish(request_body, None)?;
237
238        let response_wasi = match response_fut.get() {
239            Some(result) => result.map_err(|_| anyhow!("response already taken"))?,
240            None => {
241                let pollable = response_fut.subscribe();
242                pollable.block();
243                response_fut
244                    .get()
245                    .ok_or_else(|| anyhow!("response available"))?
246                    .map_err(|_| anyhow!("response already taken"))?
247            }
248        }?;
249
250        let mut response_builder = Response::builder();
251        response_builder =
252            response_builder.status(http::StatusCode::from_u16(response_wasi.status())?);
253
254        for (header, values) in response_wasi.headers().entries() {
255            response_builder = response_builder.header(header, values);
256        }
257
258        let body_wasi = response_wasi
259            .consume()
260            .map_err(|()| anyhow!("response has no body stream"))?;
261
262        let input_stream = body_wasi
263            .stream()
264            .map_err(|()| anyhow!("response body has no stream"))?;
265        let input_stream_pollable = input_stream.subscribe();
266
267        let mut body = Vec::new();
268        loop {
269            input_stream_pollable.block();
270            let mut body_chunk = match input_stream.read(1024 * 1024) {
271                Ok(c) => c,
272                Err(StreamError::Closed) => break,
273                Err(e) => Err(anyhow!("input stream read failed: {e:?}"))?,
274            };
275            if !body_chunk.is_empty() {
276                body.append(&mut body_chunk);
277            }
278        }
279        Ok(response_builder.body(body)?)
280    }
281}
282
283impl<T> TryFrom<&http::Request<T>> for bindings::wasi::http::types::OutgoingRequest {
284    type Error = Error;
285
286    fn try_from(request: &http::Request<T>) -> std::result::Result<Self, Self::Error> {
287        let headers = request.headers().try_into()?;
288        let request_wasi = Self::new(headers);
289
290        let method = request.method().into();
291        let scheme = request.uri().scheme().map(|s| s.into());
292        let authority = request.uri().authority().map(|a| a.as_str());
293        let path_and_query = request.uri().path_and_query().map(|a| a.as_str());
294
295        request_wasi
296            .set_method(&method)
297            .map_err(|_| anyhow!("invalid method"))?;
298        request_wasi
299            .set_scheme(scheme.as_ref())
300            .map_err(|_| anyhow!("invalid scheme"))?;
301        request_wasi
302            .set_authority(authority)
303            .map_err(|_| anyhow!("invalid authority"))?;
304        request_wasi
305            .set_path_with_query(path_and_query)
306            .map_err(|_| anyhow!("invalid path_and_query"))?;
307
308        Ok(request_wasi)
309    }
310}
311
312impl From<&http::Method> for bindings::wasi::http::types::Method {
313    fn from(value: &http::Method) -> Self {
314        match value.as_str() {
315            "OPTIONS" => Self::Options,
316            "GET" => Self::Get,
317            "POST" => Self::Post,
318            "PUT" => Self::Put,
319            "DELETE" => Self::Delete,
320            "HEAD" => Self::Head,
321            "TRACE" => Self::Trace,
322            "CONNECT" => Self::Connect,
323            "PATCH" => Self::Patch,
324            other => Self::Other(other.to_string()),
325        }
326    }
327}
328
329impl From<&http::uri::Scheme> for bindings::wasi::http::types::Scheme {
330    fn from(value: &http::uri::Scheme) -> Self {
331        match value.as_str() {
332            "https" | "HTTPS" => Self::Https,
333            _ => Self::Http,
334        }
335    }
336}
337
338impl TryFrom<&http::HeaderMap> for bindings::wasi::http::types::Headers {
339    type Error = bindings::wasi::http::types::HeaderError;
340
341    fn try_from(value: &http::HeaderMap) -> std::result::Result<Self, Self::Error> {
342        let headers = bindings::wasi::http::types::Headers::new();
343        for key in value.keys() {
344            let all: Vec<Vec<u8>> = value
345                .get_all(key)
346                .iter()
347                .flat_map(|v| v.to_str().ok())
348                .map(|v| v.as_bytes().to_vec())
349                .collect();
350            let key: String = key.to_string();
351            headers.set(&key, &all)?;
352        }
353        Ok(headers)
354    }
355}