diff --git a/src/http/body.rs b/src/http/body.rs index 4f50471..7e3ea39 100644 --- a/src/http/body.rs +++ b/src/http/body.rs @@ -1,8 +1,35 @@ //! HTTP body types -use crate::io::{AsyncRead, Cursor, Empty}; +use crate::io::{AsyncInputStream, AsyncRead, Cursor, Empty}; +use core::fmt; +use wasi::http::types::IncomingBody as WasiIncomingBody; -pub use super::response::IncomingBody; +pub use super::{ + error::{Error, ErrorVariant}, + HeaderMap, +}; + +#[derive(Debug)] +pub(crate) enum BodyKind { + Fixed(u64), + Chunked, +} + +impl BodyKind { + pub(crate) fn from_headers(headers: &HeaderMap) -> Result { + if let Some(value) = headers.get("content-length") { + let content_length = std::str::from_utf8(value.as_ref()) + .unwrap() + .parse::() + .map_err(|_| InvalidContentLength)?; + Ok(BodyKind::Fixed(content_length)) + } else if headers.contains_key("transfer-encoding") { + Ok(BodyKind::Chunked) + } else { + Ok(BodyKind::Chunked) + } + } +} /// A trait representing an HTTP body. #[doc(hidden)] @@ -82,3 +109,66 @@ impl Body for Empty { Some(0) } } + +/// An incoming HTTP body +#[derive(Debug)] +pub struct IncomingBody { + kind: BodyKind, + // IMPORTANT: the order of these fields here matters. `body_stream` must + // be dropped before `_incoming_body`. + body_stream: AsyncInputStream, + _incoming_body: WasiIncomingBody, +} + +impl IncomingBody { + pub(crate) fn new( + kind: BodyKind, + body_stream: AsyncInputStream, + incoming_body: WasiIncomingBody, + ) -> Self { + Self { + kind, + body_stream, + _incoming_body: incoming_body, + } + } +} + +impl AsyncRead for IncomingBody { + async fn read(&mut self, out_buf: &mut [u8]) -> crate::io::Result { + self.body_stream.read(out_buf).await + } +} + +impl Body for IncomingBody { + fn len(&self) -> Option { + match self.kind { + BodyKind::Fixed(l) => { + if l > (usize::MAX as u64) { + None + } else { + Some(l as usize) + } + } + BodyKind::Chunked => None, + } + } +} + +#[derive(Debug)] +pub struct InvalidContentLength; + +impl fmt::Display for InvalidContentLength { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + "incoming content-length should be a u64; violates HTTP/1.1".fmt(f) + } +} + +impl std::error::Error for InvalidContentLength {} + +impl From for Error { + fn from(e: InvalidContentLength) -> Self { + // TODO: What's the right error code here? + ErrorVariant::Other(e.to_string()).into() + } +} diff --git a/src/http/response.rs b/src/http/response.rs index 681b3bc..1544f8b 100644 --- a/src/http/response.rs +++ b/src/http/response.rs @@ -1,35 +1,15 @@ -use wasi::http::types::{IncomingBody as WasiIncomingBody, IncomingResponse}; - -use super::{fields::header_map_from_wasi, Body, Error, HeaderMap, Result}; -use crate::io::{AsyncInputStream, AsyncRead}; +use wasi::http::types::IncomingResponse; + +use super::{ + body::{BodyKind, IncomingBody}, + fields::header_map_from_wasi, + Error, HeaderMap, Result, +}; +use crate::io::AsyncInputStream; use http::StatusCode; pub use http::Response; -#[derive(Debug)] -enum BodyKind { - Fixed(u64), - Chunked, -} - -impl BodyKind { - fn from_headers(headers: &HeaderMap) -> Result { - if let Some(value) = headers.get("content-length") { - let content_length = std::str::from_utf8(value.as_ref()) - .unwrap() - .parse::() - .map_err(|_| { - Error::other("incoming content-length should be a u64; violates HTTP/1.1") - })?; - Ok(BodyKind::Fixed(content_length)) - } else if headers.contains_key("transfer-encoding") { - Ok(BodyKind::Chunked) - } else { - Ok(BodyKind::Chunked) - } - } -} - pub(crate) fn try_from_incoming_response( incoming: IncomingResponse, ) -> Result> { @@ -48,11 +28,7 @@ pub(crate) fn try_from_incoming_response( .stream() .expect("cannot call `stream` twice on an incoming body"); - let body = IncomingBody { - kind, - body_stream: AsyncInputStream::new(body_stream), - _incoming_body: incoming_body, - }; + let body = IncomingBody::new(kind, AsyncInputStream::new(body_stream), incoming_body); let mut builder = Response::builder().status(status); @@ -64,34 +40,3 @@ pub(crate) fn try_from_incoming_response( .body(body) .map_err(|err| Error::other(err.to_string())) } - -/// An incoming HTTP body -#[derive(Debug)] -pub struct IncomingBody { - kind: BodyKind, - // IMPORTANT: the order of these fields here matters. `body_stream` must - // be dropped before `_incoming_body`. - body_stream: AsyncInputStream, - _incoming_body: WasiIncomingBody, -} - -impl AsyncRead for IncomingBody { - async fn read(&mut self, out_buf: &mut [u8]) -> crate::io::Result { - self.body_stream.read(out_buf).await - } -} - -impl Body for IncomingBody { - fn len(&self) -> Option { - match self.kind { - BodyKind::Fixed(l) => { - if l > (usize::MAX as u64) { - None - } else { - Some(l as usize) - } - } - BodyKind::Chunked => None, - } - } -}