spin_sdk/
http.rs

1/// Traits for converting between the various types
2pub mod conversions;
3
4use std::collections::HashMap;
5
6#[doc(inline)]
7pub use conversions::IntoResponse;
8#[doc(inline)]
9pub use types::{
10    ErrorCode, Headers, IncomingResponse, Method, OutgoingBody, OutgoingRequest, Scheme,
11    StatusCode, Trailers,
12};
13
14use self::conversions::{TryFromIncomingResponse, TryIntoOutgoingRequest};
15use super::wit::wasi::http0_2_0::types;
16use futures::SinkExt;
17use spin_executor::bindings::wasi::io::streams::{self, StreamError};
18
19/// Represents an incoming HTTP request.
20///
21/// If you don't need streaming access to the request body, you may find it
22/// easier to work with [Request] instead. To make outgoing requests, use
23/// [Request] (non-streaming) or [OutgoingRequest].
24///
25/// # Examples
26///
27/// Access the request body as a Rust stream:
28///
29/// ```no_run
30/// # use spin_sdk::http::{IncomingRequest, ResponseOutparam};
31/// async fn handle_request(req: IncomingRequest, response_outparam: ResponseOutparam) {
32///     use futures::stream::StreamExt;
33///
34///     let mut stream = req.into_body_stream();
35///     loop {
36///         let chunk = stream.next().await;
37///         match chunk {
38///             None => {
39///                 println!("end of request body");
40///                 break;
41///             }
42///             Some(Ok(chunk)) => {
43///                 // process the data from the stream in a very realistic way
44///                 println!("read {} bytes", chunk.len());
45///             }
46///             Some(Err(e)) => {
47///                 println!("error reading body: {e:?}");
48///                 break;
49///             }
50///         }
51///     }
52/// }
53/// ```
54///
55/// Access the body in a non-streaming way. This can be useful where your component
56/// must take IncomingRequest because some scenarios need streaming, but you
57/// have other scenarios that do not.
58///
59/// ```no_run
60/// # use spin_sdk::http::{IncomingRequest, ResponseOutparam};
61/// async fn handle_request(req: IncomingRequest, response_outparam: ResponseOutparam) {
62///     let body = req.into_body().await.unwrap();
63/// }
64/// ```
65#[doc(inline)]
66pub use types::IncomingRequest;
67
68/// Represents an outgoing HTTP response.
69///
70/// OutgoingResponse is used in conjunction with [ResponseOutparam] in cases where
71/// you want to stream the response body. In cases where you don't need to stream,
72/// it is often simpler to use [Response].
73///
74/// # Examples
75///
76/// Send a streaming response to an incoming request:
77///
78/// ```no_run
79/// # use spin_sdk::http::{Fields, IncomingRequest, OutgoingResponse, ResponseOutparam};
80/// async fn handle_request(req: IncomingRequest, response_outparam: ResponseOutparam) {
81///     use futures::SinkExt;
82///     use std::io::Read;
83///
84///     let response_headers = Fields::from_list(&[
85///         ("content-type".to_owned(), "text/plain".into())
86///     ]).unwrap();
87///
88///     let response = OutgoingResponse::new(response_headers);
89///     response.set_status_code(200).unwrap();
90///     let mut response_body = response.take_body();
91///
92///     response_outparam.set(response);
93///
94///     let mut file = std::fs::File::open("war-and-peace.txt").unwrap();
95///
96///     loop {
97///         let mut buf = vec![0; 1024];
98///         let count = file.read(&mut buf).unwrap();
99///
100///         if count == 0 {
101///             break;  // end of file
102///         }
103///
104///         buf.truncate(count);
105///         response_body.send(buf).await.unwrap();
106///     }
107/// }
108/// ```
109///
110/// Send a response then continue processing:
111///
112/// ```no_run
113/// # use spin_sdk::http::{Fields, IncomingRequest, OutgoingResponse, ResponseOutparam};
114/// async fn handle_request(req: IncomingRequest, response_outparam: ResponseOutparam) {
115///     use futures::SinkExt;
116///
117///     let response_headers = Fields::from_list(&[
118///         ("content-type".to_owned(), "text/plain".into())
119///     ]).unwrap();
120///
121///     let response = OutgoingResponse::new(response_headers);
122///     response.set_status_code(200).unwrap();
123///     let mut response_body = response.take_body();
124///
125///     response_outparam.set(response);
126///
127///     response_body
128///         .send("Request accepted".as_bytes().to_vec())
129///         .await
130///         .unwrap();
131///
132///     // End the HTTP response so the client deems it complete.
133///     response_body.flush().await.unwrap();
134///     response_body.close().await.unwrap();
135///     drop(response_body);
136///
137///     // Perform any additional processing
138///     println!("While the cat's away, the mice will play");
139/// }
140/// ```
141#[doc(inline)]
142pub use types::OutgoingResponse;
143
144/// A common representation for headers and trailers.
145///
146/// # Examples
147///
148/// Initialise response headers from a list:
149///
150/// ```no_run
151/// # use spin_sdk::http::{Fields, IncomingRequest, OutgoingResponse};
152/// # fn handle_request(req: IncomingRequest) {
153/// let response_headers = Fields::from_list(&[
154///     ("content-type".to_owned(), "text/plain".into())
155/// ]).unwrap();
156///
157/// let response = OutgoingResponse::new(response_headers);
158/// # }
159/// ```
160///
161/// Build response headers up dynamically:
162///
163/// ```no_run
164/// # use spin_sdk::http::{Fields, IncomingRequest, OutgoingResponse};
165/// # fn handle_request(req: IncomingRequest) {
166/// let accepts_json = req.headers()
167///     .get(&"accept".to_owned())
168///     .iter()
169///     .flat_map(|v| String::from_utf8(v.clone()).ok())
170///     .any(|s| s == "application/json");
171///
172/// let response_headers = Fields::new();
173/// if accepts_json {
174///     response_headers.set(&"content-type".to_owned(), &["application/json".into()]).unwrap();
175/// };
176/// # }
177/// ```
178///
179/// # WASI resource documentation
180///
181#[doc(inline)]
182pub use types::Fields;
183
184/// A unified request object that can represent both incoming and outgoing requests.
185///
186/// This should be used in favor of [IncomingRequest] and [OutgoingRequest] when there
187/// is no need for streaming bodies.
188///
189/// # Examples
190///
191/// Read the method, a header, and the body an incoming HTTP request, without streaming:
192///
193/// ```no_run
194/// # use spin_sdk::http::{Method, Request, Response};
195///
196/// fn handle_request(req: Request) -> anyhow::Result<Response> {
197///     let method = req.method();
198///     let content_type = req.header("content-type");
199///     if *method == Method::Post {
200///         let body = String::from_utf8_lossy(req.body());
201///     }
202///     todo!()
203/// }
204/// ```
205///
206/// Send an outgoing GET request (no body) to `example.com`:
207///
208/// ```no_run
209/// use spin_sdk::http::{Request, Response};
210///
211/// # #[tokio::main]
212/// # async fn main() -> anyhow::Result<()> {
213/// let request = Request::get("https://example.com");
214/// let response: Response = spin_sdk::http::send(request).await?;
215/// # Ok(())
216/// # }
217/// ```
218///
219/// Send an outgoing POST request with a non-streaming body to `example.com`.
220///
221/// ```no_run
222/// use spin_sdk::http::{Request, Response};
223///
224/// # #[tokio::main]
225/// # async fn main() -> anyhow::Result<()> {
226/// let request = Request::post("https://example.com", "it's a-me, Spin")
227///     .header("content-type", "text/plain")
228///     .build();
229/// let response: Response = spin_sdk::http::send(request).await?;
230/// # Ok(())
231/// # }
232/// ```
233///
234/// Build and send an outgoing request without using the helper shortcut.
235///
236/// ```no_run
237/// use spin_sdk::http::{Method, Request, Response};
238///
239/// # #[tokio::main]
240/// # async fn main() -> anyhow::Result<()> {
241/// let mut request = Request::new(Method::Put, "https://example.com/message/safety");
242/// request.set_header("content-type", "text/plain");
243/// *request.body_mut() = "beware the crocodile".as_bytes().to_vec();
244/// let response: Response = spin_sdk::http::send(request).await?;
245/// # Ok(())
246/// # }
247/// ```
248///
249/// Build and send an outgoing request using the fluent builder.
250///
251/// ```no_run
252/// use spin_sdk::http::{Method, Request, Response};
253///
254/// # #[tokio::main]
255/// # async fn main() -> anyhow::Result<()> {
256/// let request = Request::builder()
257///     .uri("https://example.com/message/motivational")
258///     .method(Method::Put)
259///     .header("content-type", "text/plain")
260///     .body("the capybaras of creativity fly higher than the bluebirds of banality")
261///     .build();
262/// let response: Response = spin_sdk::http::send(request).await?;
263/// # Ok(())
264/// # }
265/// ```
266pub struct Request {
267    /// The method of the request
268    method: Method,
269    /// The uri for the request
270    ///
271    /// The first item is set to `None` if the supplied uri is malformed
272    uri: (Option<hyperium::Uri>, String),
273    /// The request headers
274    headers: HashMap<String, HeaderValue>,
275    /// The request body as bytes
276    body: Vec<u8>,
277}
278
279impl Request {
280    /// Creates a new request from a method and uri
281    pub fn new(method: Method, uri: impl Into<String>) -> Self {
282        Self {
283            method,
284            uri: Self::parse_uri(uri.into()),
285            headers: HashMap::new(),
286            body: Vec::new(),
287        }
288    }
289
290    /// Creates a [`RequestBuilder`]
291    pub fn builder() -> RequestBuilder {
292        RequestBuilder::new(Method::Get, "/")
293    }
294
295    /// Creates a [`RequestBuilder`] to GET the given `uri`
296    pub fn get(uri: impl Into<String>) -> RequestBuilder {
297        RequestBuilder::new(Method::Get, uri)
298    }
299
300    /// Creates a [`RequestBuilder`] to POST the given `body` to `uri`
301    pub fn post(uri: impl Into<String>, body: impl conversions::IntoBody) -> RequestBuilder {
302        let mut builder = RequestBuilder::new(Method::Post, uri);
303        builder.body(body);
304        builder
305    }
306
307    /// Creates a [`RequestBuilder`] to PUT the given `body` to `uri`
308    pub fn put(uri: impl Into<String>, body: impl conversions::IntoBody) -> RequestBuilder {
309        let mut builder = RequestBuilder::new(Method::Put, uri);
310        builder.body(body);
311        builder
312    }
313
314    /// Creates a [`RequestBuilder`] to PATCH the resource specified by `uri`
315    pub fn patch(uri: impl Into<String>, body: impl conversions::IntoBody) -> RequestBuilder {
316        let mut builder = RequestBuilder::new(Method::Patch, uri);
317        builder.body(body);
318        builder
319    }
320
321    /// Creates a [`RequestBuilder`] to DELETE the resource specified by `uri`
322    pub fn delete(uri: impl Into<String>) -> RequestBuilder {
323        RequestBuilder::new(Method::Delete, uri)
324    }
325
326    /// The request method
327    pub fn method(&self) -> &Method {
328        &self.method
329    }
330
331    /// The request uri
332    pub fn uri(&self) -> &str {
333        &self.uri.1
334    }
335
336    /// The request uri path
337    pub fn path(&self) -> &str {
338        self.uri.0.as_ref().map(|u| u.path()).unwrap_or_default()
339    }
340
341    /// The request uri query
342    pub fn query(&self) -> &str {
343        self.uri
344            .0
345            .as_ref()
346            .and_then(|u| u.query())
347            .unwrap_or_default()
348    }
349
350    /// The request headers
351    pub fn headers(&self) -> impl Iterator<Item = (&str, &HeaderValue)> {
352        self.headers.iter().map(|(k, v)| (k.as_str(), v))
353    }
354
355    /// Return a header value
356    ///
357    /// Will return `None` if the header does not exist.
358    pub fn header(&self, name: &str) -> Option<&HeaderValue> {
359        self.headers.get(&name.to_lowercase())
360    }
361
362    /// Set a header
363    pub fn set_header(&mut self, name: impl Into<String>, value: impl Into<String>) {
364        self.headers.insert(
365            name.into(),
366            HeaderValue {
367                inner: HeaderValueRep::String(value.into()),
368            },
369        );
370    }
371
372    /// The request body
373    pub fn body(&self) -> &[u8] {
374        &self.body
375    }
376
377    /// The request body
378    pub fn body_mut(&mut self) -> &mut Vec<u8> {
379        &mut self.body
380    }
381
382    /// Consume this type and return its body
383    pub fn into_body(self) -> Vec<u8> {
384        self.body
385    }
386
387    fn parse_uri(uri: String) -> (Option<hyperium::Uri>, String) {
388        (
389            hyperium::Uri::try_from(&uri)
390                .or_else(|_| hyperium::Uri::try_from(&format!("http://{uri}")))
391                .ok(),
392            uri,
393        )
394    }
395
396    /// Whether the request is an HTTPS request
397    fn is_https(&self) -> bool {
398        self.uri
399            .0
400            .as_ref()
401            .and_then(|u| u.scheme())
402            .map(|s| s == &hyperium::uri::Scheme::HTTPS)
403            .unwrap_or(true)
404    }
405
406    /// The URI's authority
407    fn authority(&self) -> Option<&str> {
408        self.uri
409            .0
410            .as_ref()
411            .and_then(|u| u.authority())
412            .map(|a| a.as_str())
413    }
414
415    /// The request path and query combined
416    pub fn path_and_query(&self) -> Option<&str> {
417        self.uri
418            .0
419            .as_ref()
420            .and_then(|u| u.path_and_query())
421            .map(|s| s.as_str())
422    }
423}
424
425/// A builder for non-streaming outgoing HTTP requests. You can obtain
426/// a RequestBuilder from the [Request::builder()] method, or from
427/// method-specific helpers such as [Request::get()] or [Request::post()].
428///
429/// # Examples
430///
431/// Use a method helper to build an outgoing POST request:
432///
433/// ```no_run
434/// use spin_sdk::http::{Request, Response};
435///
436/// # #[tokio::main]
437/// # async fn main() -> anyhow::Result<()> {
438/// let request = Request::post("https://example.com", "it's a-me, Spin")
439///     .header("content-type", "text/plain")
440///     .build();
441/// let response: Response = spin_sdk::http::send(request).await?;
442/// # Ok(())
443/// # }
444/// ```
445///
446/// Build and send an outgoing request using the RequestBuilder.
447///
448/// ```no_run
449/// use spin_sdk::http::{Method, Request, Response};
450///
451/// # #[tokio::main]
452/// # async fn main() -> anyhow::Result<()> {
453/// let request = Request::builder()
454///     .uri("https://example.com/message/motivational")
455///     .method(Method::Put)
456///     .header("content-type", "text/plain")
457///     .body("the capybaras of creativity fly higher than the bluebirds of banality")
458///     .build();
459/// let response: Response = spin_sdk::http::send(request).await?;
460/// # Ok(())
461/// # }
462/// ```
463pub struct RequestBuilder {
464    request: Request,
465}
466
467impl RequestBuilder {
468    /// Create a new `RequestBuilder`
469    pub fn new(method: Method, uri: impl Into<String>) -> Self {
470        Self {
471            request: Request::new(method, uri.into()),
472        }
473    }
474
475    /// Set the method
476    pub fn method(&mut self, method: Method) -> &mut Self {
477        self.request.method = method;
478        self
479    }
480
481    /// Set the uri
482    pub fn uri(&mut self, uri: impl Into<String>) -> &mut Self {
483        self.request.uri = Request::parse_uri(uri.into());
484        self
485    }
486
487    /// Set the headers
488    pub fn headers(&mut self, headers: impl conversions::IntoHeaders) -> &mut Self {
489        self.request.headers = into_header_rep(headers);
490        self
491    }
492
493    /// Set a header
494    pub fn header(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
495        self.request
496            .headers
497            .insert(key.into().to_lowercase(), HeaderValue::string(value.into()));
498        self
499    }
500
501    /// Set the body
502    pub fn body(&mut self, body: impl conversions::IntoBody) -> &mut Self {
503        self.request.body = body.into_body();
504        self
505    }
506
507    /// Build the `Request`
508    pub fn build(&mut self) -> Request {
509        std::mem::replace(&mut self.request, Request::new(Method::Get, "/"))
510    }
511}
512
513/// A unified response object that can represent both outgoing and incoming responses.
514///
515/// This should be used in favor of `OutgoingResponse` and `IncomingResponse` when there
516/// is no need for streaming bodies.
517///
518/// # Examples
519///
520/// Send a response to an incoming HTTP request:
521///
522/// ```no_run
523/// use spin_sdk::http::{Request, Response};
524///
525/// fn handle_request(req: Request) -> anyhow::Result<Response> {
526///     Ok(Response::builder()
527///         .status(200)
528///         .header("content-type", "text/plain")
529///         .body("Hello, world")
530///         .build())
531/// }
532/// ```
533///
534/// Parse a response from an outgoing HTTP request:
535///
536/// ```no_run
537/// # use spin_sdk::http::{Request, Response};
538/// #[derive(serde::Deserialize)]
539/// struct User {
540///     name: String,
541/// }
542///
543/// # #[tokio::main]
544/// # async fn main() -> anyhow::Result<()> {
545/// let request = Request::get("https://example.com");
546/// let response: Response = spin_sdk::http::send(request).await?;
547/// if *response.status() == 200 {
548///     let body = response.body();
549///     let user: User = serde_json::from_slice(body)?;
550/// }
551/// # Ok(())
552/// # }
553/// ```
554pub struct Response {
555    /// The status of the response
556    status: StatusCode,
557    /// The response headers
558    headers: HashMap<String, HeaderValue>,
559    /// The body of the response as bytes
560    body: Vec<u8>,
561}
562
563impl Response {
564    /// Create a new response from a status and body
565    pub fn new(status: impl conversions::IntoStatusCode, body: impl conversions::IntoBody) -> Self {
566        Self {
567            status: status.into_status_code(),
568            headers: HashMap::new(),
569            body: body.into_body(),
570        }
571    }
572
573    /// The response status
574    pub fn status(&self) -> &StatusCode {
575        &self.status
576    }
577
578    /// The request headers
579    pub fn headers(&self) -> impl Iterator<Item = (&str, &HeaderValue)> {
580        self.headers.iter().map(|(k, v)| (k.as_str(), v))
581    }
582
583    /// Return a header value
584    ///
585    /// Will return `None` if the header does not exist.
586    pub fn header(&self, name: &str) -> Option<&HeaderValue> {
587        self.headers.get(&name.to_lowercase())
588    }
589
590    /// Set a response header
591    pub fn set_header(&mut self, name: impl Into<String>, value: impl Into<String>) {
592        self.headers.insert(
593            name.into(),
594            HeaderValue {
595                inner: HeaderValueRep::String(value.into()),
596            },
597        );
598    }
599
600    /// The response body
601    pub fn body(&self) -> &[u8] {
602        &self.body
603    }
604
605    /// The response body
606    pub fn body_mut(&mut self) -> &mut Vec<u8> {
607        &mut self.body
608    }
609
610    /// Consume this type and return its body
611    pub fn into_body(self) -> Vec<u8> {
612        self.body
613    }
614
615    /// Converts this response into a [`ResponseBuilder`]. This can be used to
616    /// update a response before passing it on.
617    pub fn into_builder(self) -> ResponseBuilder {
618        ResponseBuilder { response: self }
619    }
620
621    /// Creates a [`ResponseBuilder`]
622    pub fn builder() -> ResponseBuilder {
623        ResponseBuilder::new(200)
624    }
625}
626
627impl std::fmt::Debug for Response {
628    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
629        f.debug_struct("Response")
630            .field("status", &self.status)
631            .field("headers", &self.headers)
632            .field("body.len()", &self.body.len())
633            .finish()
634    }
635}
636
637/// A builder for `Response``
638pub struct ResponseBuilder {
639    response: Response,
640}
641
642impl ResponseBuilder {
643    /// Create a new `ResponseBuilder`
644    pub fn new(status: impl conversions::IntoStatusCode) -> Self {
645        ResponseBuilder {
646            response: Response::new(status, Vec::new()),
647        }
648    }
649
650    /// Set the status
651    pub fn status(&mut self, status: impl conversions::IntoStatusCode) -> &mut Self {
652        self.response.status = status.into_status_code();
653        self
654    }
655
656    /// Set the headers
657    pub fn headers(&mut self, headers: impl conversions::IntoHeaders) -> &mut Self {
658        self.response.headers = into_header_rep(headers.into_headers());
659        self
660    }
661
662    /// Set a header
663    pub fn header(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
664        self.response
665            .headers
666            .insert(key.into().to_lowercase(), HeaderValue::string(value.into()));
667        self
668    }
669
670    /// Set the body
671    pub fn body(&mut self, body: impl conversions::IntoBody) -> &mut Self {
672        self.response.body = body.into_body();
673        self
674    }
675
676    /// Build the `Response`
677    pub fn build(&mut self) -> Response {
678        std::mem::replace(&mut self.response, Response::new(200, Vec::new()))
679    }
680}
681
682/// A header value.
683///
684/// Since header values do not have to be valid utf8, this allows for
685/// both utf8 strings and bags of bytes.
686#[derive(Debug, PartialEq, Eq, Clone)]
687pub struct HeaderValue {
688    inner: HeaderValueRep,
689}
690
691#[derive(Debug, PartialEq, Eq, Clone)]
692enum HeaderValueRep {
693    /// Header value encoded as a utf8 string
694    String(String),
695    /// Header value as a bag of bytes
696    Bytes(Vec<u8>),
697}
698
699impl HeaderValue {
700    /// Construct a `HeaderValue` from a string
701    pub fn string(str: String) -> HeaderValue {
702        HeaderValue {
703            inner: HeaderValueRep::String(str),
704        }
705    }
706
707    /// Construct a `HeaderValue` from a bag of bytes
708    pub fn bytes(bytes: Vec<u8>) -> HeaderValue {
709        HeaderValue {
710            inner: String::from_utf8(bytes)
711                .map(HeaderValueRep::String)
712                .unwrap_or_else(|e| HeaderValueRep::Bytes(e.into_bytes())),
713        }
714    }
715
716    /// Get the `HeaderValue` as a utf8 encoded string
717    ///
718    /// Returns `None` if the value is a non utf8 encoded header value
719    pub fn as_str(&self) -> Option<&str> {
720        match &self.inner {
721            HeaderValueRep::String(s) => Some(s),
722            HeaderValueRep::Bytes(b) => std::str::from_utf8(b).ok(),
723        }
724    }
725
726    /// Get the `HeaderValue` as bytes
727    pub fn as_bytes(&self) -> &[u8] {
728        self.as_ref()
729    }
730
731    /// Turn the `HeaderValue` into a String (in a lossy way if the `HeaderValue` is a bag of bytes)
732    pub fn into_utf8_lossy(self) -> String {
733        match self.inner {
734            HeaderValueRep::String(s) => s,
735            HeaderValueRep::Bytes(b) => String::from_utf8_lossy(&b).into_owned(),
736        }
737    }
738
739    /// Turn the `HeaderValue` into bytes
740    pub fn into_bytes(self) -> Vec<u8> {
741        match self.inner {
742            HeaderValueRep::String(s) => s.into_bytes(),
743            HeaderValueRep::Bytes(b) => b,
744        }
745    }
746}
747
748impl AsRef<[u8]> for HeaderValue {
749    fn as_ref(&self) -> &[u8] {
750        match &self.inner {
751            HeaderValueRep::String(s) => s.as_bytes(),
752            HeaderValueRep::Bytes(b) => b,
753        }
754    }
755}
756
757fn into_header_rep(headers: impl conversions::IntoHeaders) -> HashMap<String, HeaderValue> {
758    headers
759        .into_headers()
760        .into_iter()
761        .map(|(k, v)| {
762            let v = String::from_utf8(v)
763                .map(HeaderValueRep::String)
764                .unwrap_or_else(|e| HeaderValueRep::Bytes(e.into_bytes()));
765            (k.to_lowercase(), HeaderValue { inner: v })
766        })
767        .collect()
768}
769
770impl std::hash::Hash for Method {
771    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
772        core::mem::discriminant(self).hash(state);
773    }
774}
775
776impl Eq for Method {}
777
778impl PartialEq for Method {
779    fn eq(&self, other: &Self) -> bool {
780        match (self, other) {
781            (Self::Other(l), Self::Other(r)) => l == r,
782            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
783        }
784    }
785}
786
787impl std::fmt::Display for Method {
788    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
789        f.write_str(match self {
790            Method::Get => "GET",
791            Method::Post => "POST",
792            Method::Put => "PUT",
793            Method::Delete => "DELETE",
794            Method::Patch => "PATCH",
795            Method::Head => "HEAD",
796            Method::Options => "OPTIONS",
797            Method::Connect => "CONNECT",
798            Method::Trace => "TRACE",
799            Method::Other(o) => o,
800        })
801    }
802}
803
804impl IncomingRequest {
805    /// The incoming request Uri
806    pub fn uri(&self) -> String {
807        let scheme_and_authority =
808            if let (Some(scheme), Some(authority)) = (self.scheme(), self.authority()) {
809                let scheme = match &scheme {
810                    Scheme::Http => "http://",
811                    Scheme::Https => "https://",
812                    Scheme::Other(s) => s.as_str(),
813                };
814                format!("{scheme}{authority}")
815            } else {
816                String::new()
817            };
818        let path_and_query = self.path_with_query().unwrap_or_default();
819        format!("{scheme_and_authority}{path_and_query}")
820    }
821
822    /// Return a `Stream` from which the body of the specified request may be read.
823    ///
824    /// # Panics
825    ///
826    /// Panics if the body was already consumed.
827    pub fn into_body_stream(self) -> impl futures::Stream<Item = Result<Vec<u8>, streams::Error>> {
828        executor::incoming_body(self.consume().expect("request body was already consumed"))
829    }
830
831    /// Return a `Vec<u8>` of the body or fails
832    pub async fn into_body(self) -> Result<Vec<u8>, streams::Error> {
833        use futures::TryStreamExt;
834        let mut stream = self.into_body_stream();
835        let mut body = Vec::new();
836        while let Some(chunk) = stream.try_next().await? {
837            body.extend(chunk);
838        }
839        Ok(body)
840    }
841}
842
843impl IncomingResponse {
844    /// Return a `Stream` from which the body of the specified response may be read.
845    ///
846    /// # Panics
847    ///
848    /// Panics if the body was already consumed.
849    // TODO: This should ideally take ownership of `self` and be called `into_body_stream` (i.e. symmetric with
850    // `IncomingRequest::into_body_stream`).  However, as of this writing, `wasmtime-wasi-http` is implemented in
851    // such a way that dropping an `IncomingResponse` will cause the request to be cancelled, meaning the caller
852    // won't necessarily have a chance to send the request body if they haven't started doing so yet (or, if they
853    // have started, they might not be able to finish before the connection is closed).  See
854    // https://github.com/bytecodealliance/wasmtime/issues/7413 for details.
855    pub fn take_body_stream(&self) -> impl futures::Stream<Item = Result<Vec<u8>, streams::Error>> {
856        executor::incoming_body(self.consume().expect("response body was already consumed"))
857    }
858
859    /// Return a `Vec<u8>` of the body or fails
860    ///
861    /// # Panics
862    ///
863    /// Panics if the body was already consumed.
864    pub async fn into_body(self) -> Result<Vec<u8>, streams::Error> {
865        use futures::TryStreamExt;
866        let mut stream = self.take_body_stream();
867        let mut body = Vec::new();
868        while let Some(chunk) = stream.try_next().await? {
869            body.extend(chunk);
870        }
871        Ok(body)
872    }
873}
874
875impl OutgoingResponse {
876    /// Construct a `Sink` which writes chunks to the body of the specified response.
877    ///
878    /// # Panics
879    ///
880    /// Panics if the body was already taken.
881    pub fn take_body(&self) -> impl futures::Sink<Vec<u8>, Error = StreamError> {
882        executor::outgoing_body(self.body().expect("response body was already taken"))
883    }
884}
885
886impl OutgoingRequest {
887    /// Construct a `Sink` which writes chunks to the body of the specified response.
888    ///
889    /// # Panics
890    ///
891    /// Panics if the body was already taken.
892    pub fn take_body(&self) -> impl futures::Sink<Vec<u8>, Error = StreamError> {
893        executor::outgoing_body(self.body().expect("request body was already taken"))
894    }
895}
896
897/// A parameter provided by Spin for setting a streaming [OutgoingResponse].
898///
899/// # Examples
900///
901/// Send a streaming response to an incoming request:
902///
903/// ```no_run
904/// # use spin_sdk::http::{Fields, IncomingRequest, OutgoingResponse, ResponseOutparam};
905/// async fn handle_request(req: IncomingRequest, response_outparam: ResponseOutparam) {
906///     use futures::SinkExt;
907///     use std::io::Read;
908///
909///     let response_headers = Fields::from_list(&[
910///         ("content-type".to_owned(), "text/plain".into())
911///     ]).unwrap();
912///
913///     let response = OutgoingResponse::new(response_headers);
914///     response.set_status_code(200).unwrap();
915///     let mut response_body = response.take_body();
916///
917///     response_outparam.set(response);
918///
919///     let mut file = std::fs::File::open("war-and-peace.txt").unwrap();
920///
921///     loop {
922///         let mut buf = vec![0; 1024];
923///         let count = file.read(&mut buf).unwrap();
924///
925///         if count == 0 {
926///             break;  // end of file
927///         }
928///
929///         buf.truncate(count);
930///         response_body.send(buf).await.unwrap();
931///     }
932/// }
933/// ```
934pub struct ResponseOutparam(types::ResponseOutparam);
935
936impl ResponseOutparam {
937    #[doc(hidden)]
938    // This is needed for the macro so we can transfrom the macro's
939    // `ResponseOutparam` to this `ResponseOutparam`
940    pub unsafe fn from_handle(handle: u32) -> Self {
941        Self(types::ResponseOutparam::from_handle(handle))
942    }
943
944    /// Set the outgoing response
945    pub fn set(self, response: OutgoingResponse) {
946        types::ResponseOutparam::set(self.0, Ok(response));
947    }
948
949    /// Set with the outgoing response and the supplied buffer
950    ///
951    /// Will panic if response body has already been taken
952    pub async fn set_with_body(
953        self,
954        response: OutgoingResponse,
955        buffer: Vec<u8>,
956    ) -> Result<(), StreamError> {
957        let mut body = response.take_body();
958        self.set(response);
959        body.send(buffer).await
960    }
961
962    /// Return the inner, `wit-bindgen`-generated instance
963    pub fn into_inner(self) -> types::ResponseOutparam {
964        self.0
965    }
966}
967
968/// Send an outgoing request
969///
970/// # Examples
971///
972/// Get the example.com home page:
973///
974/// ```no_run
975/// use spin_sdk::http::{Request, Response};
976///
977/// # #[tokio::main]
978/// # async fn main() -> anyhow::Result<()> {
979/// let request = Request::get("example.com").build();
980/// let response: Response = spin_sdk::http::send(request).await?;
981/// println!("{}", response.body().len());
982/// # Ok(())
983/// # }
984/// ```
985///
986/// Use the `http` crate Request type to send a data transfer value:
987///
988/// ```no_run
989/// use hyperium::Request;
990///
991/// #[derive(serde::Serialize)]
992/// struct User {
993///     name: String,
994/// }
995///
996/// impl spin_sdk::http::conversions::TryIntoBody for User {
997///     type Error = serde_json::Error;
998///
999///     fn try_into_body(self) -> Result<Vec<u8>, Self::Error> {
1000///         serde_json::to_vec(&self)
1001///     }
1002/// }
1003///
1004/// # #[tokio::main]
1005/// # async fn main() -> anyhow::Result<()> {
1006/// let user = User {
1007///     name: "Alice".to_owned(),
1008/// };
1009///
1010/// let request = hyperium::Request::builder()
1011///     .method("POST")
1012///     .uri("https://example.com/users")
1013///     .header("content-type", "application/json")
1014///     .body(user)?;
1015///
1016/// let response: hyperium::Response<()> = spin_sdk::http::send(request).await?;
1017///
1018/// println!("{}", response.status().is_success());
1019/// # Ok(())
1020/// # }
1021/// ```
1022pub async fn send<I, O>(request: I) -> Result<O, SendError>
1023where
1024    I: TryIntoOutgoingRequest,
1025    I::Error: Into<Box<dyn std::error::Error + Send + Sync>> + 'static,
1026    O: TryFromIncomingResponse,
1027    O::Error: Into<Box<dyn std::error::Error + Send + Sync>> + 'static,
1028{
1029    let (request, body_buffer) = I::try_into_outgoing_request(request)
1030        .map_err(|e| SendError::RequestConversion(e.into()))?;
1031    let response = if let Some(body_buffer) = body_buffer {
1032        // It is part of the contract of the trait that implementors of `TryIntoOutgoingRequest`
1033        // do not call `OutgoingRequest::write`` if they return a buffered body.
1034        let mut body_sink = request.take_body();
1035        let response = executor::outgoing_request_send(request);
1036        body_sink.send(body_buffer).await.map_err(SendError::Io)?;
1037        drop(body_sink);
1038        response.await.map_err(SendError::Http)?
1039    } else {
1040        executor::outgoing_request_send(request)
1041            .await
1042            .map_err(SendError::Http)?
1043    };
1044
1045    TryFromIncomingResponse::try_from_incoming_response(response)
1046        .await
1047        .map_err(|e: O::Error| SendError::ResponseConversion(e.into()))
1048}
1049
1050/// An error encountered when performing an HTTP request
1051#[derive(thiserror::Error, Debug)]
1052pub enum SendError {
1053    /// Error converting to a request
1054    #[error(transparent)]
1055    RequestConversion(Box<dyn std::error::Error + Send + Sync>),
1056    /// Error converting from a response
1057    #[error(transparent)]
1058    ResponseConversion(Box<dyn std::error::Error + Send + Sync>),
1059    /// An I/O error
1060    #[error(transparent)]
1061    Io(StreamError),
1062    /// An HTTP error
1063    #[error(transparent)]
1064    Http(ErrorCode),
1065}
1066
1067#[doc(hidden)]
1068/// The executor for driving wasi-http futures to completion
1069mod executor;
1070#[doc(hidden)]
1071pub use executor::run;
1072
1073/// An error parsing a JSON body
1074#[cfg(feature = "json")]
1075#[derive(Debug)]
1076pub struct JsonBodyError(serde_json::Error);
1077
1078#[cfg(feature = "json")]
1079impl std::error::Error for JsonBodyError {}
1080
1081#[cfg(feature = "json")]
1082impl std::fmt::Display for JsonBodyError {
1083    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1084        f.write_str("could not convert body to json")
1085    }
1086}
1087
1088/// An error when the body is not UTF-8
1089#[derive(Debug)]
1090pub struct NonUtf8BodyError;
1091
1092impl std::error::Error for NonUtf8BodyError {}
1093
1094impl std::fmt::Display for NonUtf8BodyError {
1095    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1096        f.write_str("body was expected to be utf8 but was not")
1097    }
1098}
1099
1100mod router;
1101#[doc(inline)]
1102pub use router::*;
1103
1104/// A Body extractor
1105#[derive(Debug)]
1106pub struct Body<T>(pub T);
1107
1108impl<T> std::ops::Deref for Body<T> {
1109    type Target = T;
1110
1111    fn deref(&self) -> &Self::Target {
1112        &self.0
1113    }
1114}
1115
1116/// A Json extractor
1117#[derive(Debug)]
1118pub struct Json<T>(pub T);
1119
1120impl<T> std::ops::Deref for Json<T> {
1121    type Target = T;
1122
1123    fn deref(&self) -> &Self::Target {
1124        &self.0
1125    }
1126}
1127
1128/// Helper functions for creating responses
1129pub mod responses {
1130    use super::Response;
1131
1132    /// Helper function to return a 404 Not Found response.
1133    pub fn not_found() -> Response {
1134        Response::new(404, "Not Found")
1135    }
1136
1137    /// Helper function to return a 500 Internal Server Error response.
1138    pub fn internal_server_error() -> Response {
1139        Response::new(500, "Internal Server Error")
1140    }
1141
1142    /// Helper function to return a 405 Method Not Allowed response.
1143    pub fn method_not_allowed() -> Response {
1144        Response::new(405, "Method Not Allowed")
1145    }
1146
1147    pub(crate) fn bad_request(msg: Option<String>) -> Response {
1148        Response::new(400, msg.map(|m| m.into_bytes()))
1149    }
1150}
1151
1152#[cfg(test)]
1153mod tests {
1154    use super::*;
1155
1156    #[test]
1157    fn request_uri_parses() {
1158        let uri = "/hello?world=1";
1159        let req = Request::new(Method::Get, uri);
1160        assert_eq!(req.uri(), uri);
1161        assert_eq!(req.path(), "/hello");
1162        assert_eq!(req.query(), "world=1");
1163
1164        let uri = "http://localhost:3000/hello?world=1";
1165        let req = Request::new(Method::Get, uri);
1166        assert_eq!(req.uri(), uri);
1167        assert_eq!(req.path(), "/hello");
1168        assert_eq!(req.query(), "world=1");
1169
1170        let uri = "localhost:3000/hello?world=1";
1171        let req = Request::new(Method::Get, uri);
1172        assert_eq!(req.uri(), uri);
1173        assert_eq!(req.path(), "/hello");
1174        assert_eq!(req.query(), "world=1");
1175    }
1176}