Skip to content

Commit

Permalink
mendes: upgrade to hyper/http 1
Browse files Browse the repository at this point in the history
  • Loading branch information
djc committed Jul 12, 2024
1 parent 5afff7d commit 2587ae7
Show file tree
Hide file tree
Showing 7 changed files with 708 additions and 270 deletions.
23 changes: 13 additions & 10 deletions mendes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,22 @@ readme = "../README.md"

[features]
default = ["application"]
application = ["http", "dep:async-trait", "dep:mendes-macros", "dep:percent-encoding", "dep:serde", "dep:serde_urlencoded"]
application = ["http", "dep:async-trait", "dep:bytes", "dep:http-body", "dep:mendes-macros", "dep:percent-encoding", "dep:pin-project", "dep:serde", "dep:serde_urlencoded"]
brotli = ["compression", "async-compression?/brotli"]
chrono = ["dep:chrono"]
compression = ["dep:async-compression", "dep:futures-util", "dep:tokio-util"]
compression = ["dep:async-compression", "dep:tokio", "dep:tokio-util"]
cookies = ["http", "key", "dep:chrono", "dep:data-encoding", "dep:mendes-macros", "dep:postcard", "serde?/derive"]
deflate = ["compression", "async-compression?/deflate"]
forms = ["dep:mendes-macros", "dep:serde_urlencoded", "serde?/derive"]
gzip = ["compression", "async-compression?/gzip"]
http = ["dep:http"]
http-body = ["dep:bytes", "dep:http-body", "dep:pin-utils"]
hyper = ["application", "http", "dep:async-trait", "dep:bytes", "dep:futures-util", "futures-util?/std", "dep:hyper"]
hyper = ["application", "http", "dep:async-trait", "dep:bytes", "dep:futures-util", "futures-util?/std", "dep:hyper", "dep:hyper-util", "dep:tokio", "tokio?/macros", "tracing"]
key = ["dep:data-encoding", "dep:ring"]
json = ["dep:serde_json"]
uploads = ["http", "dep:httparse", "dep:memchr"]
body = ["dep:http-body"]
body-util = ["dep:http-body-util", "dep:bytes", "dep:http-body"]
static = ["application", "http", "dep:mime_guess", "dep:tokio", "tokio?/fs"]
tracing = ["dep:tracing"]

[dependencies]
async-compression = { version = "0.4.0", features = ["tokio"], optional = true }
Expand All @@ -37,15 +38,17 @@ bytes = { version = "1", optional = true }
chrono = { version = "0.4.23", optional = true, features = ["serde"] }
data-encoding = { version = "2.1.2", optional = true }
futures-util = { version = "0.3.7", optional = true, default-features = false }
http = { version = "0.2", optional = true }
http-body = { version = "0.4", optional = true }
http = { version = "1", optional = true }
http-body = { version = "1", optional = true }
http-body-util = { version = "0.1", optional = true }
httparse = { version = "1.3.4", optional = true }
hyper = { version = "0.14.1", optional = true, features = ["http1", "http2", "runtime", "server", "stream"] }
hyper = { version = "1", optional = true, features = ["http1", "http2", "server"] }
hyper-util = { version = "0.1.3", features = ["http1", "http2", "server", "tokio"], optional = true }
memchr = { version = "2.5", optional = true }
mendes-macros = { version = "0.4", path = "../mendes-macros", optional = true }
mime_guess = { version = "2.0.3", default-features = false, optional = true }
percent-encoding = { version = "2.1.0", default-features = false, optional = true }
pin-utils = { version = "0.1.0", optional = true }
pin-project = { version = "1.1.5", optional = true }
postcard = { version = "1.0.6", default-features = false, features = ["use-std"], optional = true }
ring = { version = "0.17.0", optional = true }
serde = { version = "1.0.104", optional = true }
Expand All @@ -57,7 +60,7 @@ tokio-util = { version = "0.7", optional = true, features = ["codec", "compat",
tracing = { version = "0.1.26", optional = true }

[dev-dependencies]
reqwest = { version = "0.11.11", default-features = false }
reqwest = { version = "0.12", default-features = false }
tokio = { version = "1", features = ["macros", "rt"] }

[package.metadata.docs.rs]
Expand Down
100 changes: 27 additions & 73 deletions mendes/src/application.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
use std::borrow::Cow;
#[cfg(feature = "http-body")]
#[cfg(feature = "body-util")]
use std::error::Error as StdError;
use std::str;
use std::str::FromStr;
use std::sync::Arc;

use async_trait::async_trait;
#[cfg(feature = "http-body")]
use bytes::{Buf, BufMut, Bytes};
#[cfg(feature = "body-util")]
use bytes::Bytes;
use http::header::LOCATION;
use http::request::Parts;
use http::Request;
use http::{Response, StatusCode};
#[cfg(feature = "http-body")]
use http_body::Body as HttpBody;
use percent_encoding::percent_decode_str;
use thiserror::Error;

pub use mendes_macros::{handler, route, scope};

#[cfg(feature = "hyper")]
use crate::hyper::ApplicationService;

/// Main interface for an application or service
///
/// The `Application` holds state and routes request to the proper handlers. A handler gets
Expand All @@ -34,7 +28,7 @@ use crate::hyper::ApplicationService;
#[async_trait]
pub trait Application: Send + Sized {
type RequestBody: Send;
type ResponseBody: Send;
type ResponseBody: http_body::Body;
type Error: IntoResponse<Self> + WithStatus + From<Error> + Send;

async fn handle(cx: Context<Self>) -> Response<Self::ResponseBody>;
Expand All @@ -53,17 +47,17 @@ pub trait Application: Send + Sized {
from_bytes::<T>(req, bytes)
}

#[cfg(feature = "http-body")]
#[cfg_attr(docsrs, doc(cfg(feature = "http-body")))]
#[cfg(feature = "http-body-util")]
#[cfg_attr(docsrs, doc(cfg(feature = "http-body-util")))]
async fn from_body<T: serde::de::DeserializeOwned>(
req: &Parts,
body: Self::RequestBody,
max_len: usize,
) -> Result<T, Error>
where
Self::RequestBody: HttpBody + Send,
<Self::RequestBody as HttpBody>::Data: Send,
<Self::RequestBody as HttpBody>::Error: Into<Box<dyn StdError + Sync + Send>>,
Self::RequestBody: Body + Send,
<Self::RequestBody as Body>::Data: Send,
<Self::RequestBody as Body>::Error: Into<Box<dyn StdError + Sync + Send>>,
{
// Check if the Content-Length header suggests the body is larger than our max len
// to avoid allocation if we drop the request in any case.
Expand All @@ -78,12 +72,11 @@ pub trait Application: Send + Sized {
from_body::<Self::RequestBody, T>(req, body, max_len).await
}

#[cfg(feature = "http-body")]
#[cfg_attr(docsrs, doc(cfg(feature = "http-body")))]
async fn body_bytes<B>(body: B, max_len: usize) -> Result<Bytes, Error>
#[cfg(feature = "body-util")]
#[cfg_attr(docsrs, doc(cfg(feature = "body-util")))]
async fn body_bytes<B: http_body::Body + Send>(body: B, max_len: usize) -> Result<Bytes, Error>
where
B: HttpBody + Send,
<B as HttpBody>::Data: Send,
B::Data: Send,
B::Error: Into<Box<dyn StdError + Sync + Send + 'static>>,
{
// Check if the Content-Length header suggests the body is larger than our max len
Expand All @@ -92,6 +85,7 @@ pub trait Application: Send + Sized {
Some(length) => length,
None => body.size_hint().lower(),
};

if expected_len > max_len as u64 {
return Err(Error::BodyTooLarge);
}
Expand All @@ -109,11 +103,6 @@ pub trait Application: Send + Sized {
.body(Self::ResponseBody::default())
.unwrap()
}

#[cfg(feature = "hyper")]
fn into_service(self) -> ApplicationService<Self> {
ApplicationService(Arc::new(self))
}
}

pub trait WithStatus {}
Expand Down Expand Up @@ -513,56 +502,21 @@ fn from_bytes<'de, T: serde::de::Deserialize<'de>>(
deserialize_body!(req, bytes)
}

#[cfg(feature = "http-body")]
#[cfg_attr(docsrs, doc(cfg(feature = "http-body")))]
#[cfg(feature = "body-util")]
#[cfg_attr(docsrs, doc(cfg(feature = "body-util")))]
#[cfg_attr(feature = "tracing", tracing::instrument(skip(body)))]
async fn to_bytes<B>(body: B, max_len: usize) -> Result<Bytes, Error>
async fn to_bytes<B: http_body::Body>(body: B, max_len: usize) -> Result<Bytes, Error>
where
B: HttpBody,
B::Error: Into<Box<dyn StdError + Send + Sync + 'static>>,
{
pin_utils::pin_mut!(body);

// If there's only 1 chunk, we can just return Buf::to_bytes()
let mut first = if let Some(buf) = body.data().await {
buf.map_err(|err| Error::BodyReceive(err.into()))?
} else {
return Ok(Bytes::new());
};
#[cfg(feature = "body-util")]
use http_body_util::BodyExt;

let mut received = first.remaining();
if received > max_len {
return Err(Error::BodyTooLarge);
let limited = http_body_util::Limited::new(body, max_len);
match limited.collect().await {
Ok(collected) => Ok(collected.to_bytes()),
Err(err) => Err(Error::BodyReceive(err)),
}

let second = if let Some(buf) = body.data().await {
buf.map_err(|err| Error::BodyReceive(err.into()))?
} else {
return Ok(first.copy_to_bytes(first.remaining()));
};

received += second.remaining();
if received > max_len {
return Err(Error::BodyTooLarge);
}

// With more than 1 buf, we gotta flatten into a Vec first.
let cap = first.remaining() + second.remaining() + body.size_hint().lower() as usize;
let mut vec = Vec::with_capacity(cap);
vec.put(first);
vec.put(second);

while let Some(buf) = body.data().await {
let buf = buf.map_err(|err| Error::BodyReceive(err.into()))?;
received += buf.remaining();
if received > max_len {
return Err(Error::BodyTooLarge);
}

vec.put(buf);
}

Ok(vec.into())
}

// This should only be used by procedural routing macros.
Expand Down Expand Up @@ -645,10 +599,10 @@ pub enum Error {
QueryMissing,
#[error("unable to decode request URI query: {0}")]
QueryDecode(serde_urlencoded::de::Error),
#[cfg(feature = "http-body")]
#[cfg(feature = "body-util")]
#[error("unable to receive request body: {0}")]
BodyReceive(Box<dyn StdError + Send + Sync + 'static>),
#[cfg(feature = "http-body")]
#[cfg(feature = "body-util")]
#[error("request body too large")]
BodyTooLarge,
#[cfg(feature = "json")]
Expand Down Expand Up @@ -676,9 +630,9 @@ impl From<&Error> for StatusCode {
QueryMissing | QueryDecode(_) | BodyNoType => StatusCode::BAD_REQUEST,
BodyUnknownType(_) => StatusCode::UNSUPPORTED_MEDIA_TYPE,
PathNotFound | PathComponentMissing | PathParse | PathDecode => StatusCode::NOT_FOUND,
#[cfg(feature = "http-body")]
#[cfg(feature = "body-util")]
BodyReceive(_) => StatusCode::INTERNAL_SERVER_ERROR,
#[cfg(feature = "http-body")]
#[cfg(feature = "body-util")]
BodyTooLarge => StatusCode::BAD_REQUEST,
BodyDecodeForm(_) => StatusCode::UNPROCESSABLE_ENTITY,
#[cfg(feature = "json")]
Expand Down
Loading

0 comments on commit 2587ae7

Please sign in to comment.