spin_sdk/
http.rs

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