use crate::{
HttpBody,
error::Error,
headers::{FromHeaders, Header},
};
use hyper::body::Incoming;
use query_args::{QueryArgsCache, QueryArgsIter};
use std::sync::OnceLock;
use crate::http::{
Extensions, Method, Parts, Request, Uri, Version, endpoints::args::FromRequestRef,
request::request_body_limit::RequestBodyLimit, request_scope::HttpRequestScope,
};
use crate::headers::HeaderMap;
#[cfg(feature = "middleware")]
pub use request_mut::{HttpRequestMut, IntoTapResult};
mod query_args;
pub mod request_body_limit;
#[cfg(feature = "middleware")]
mod request_mut;
pub struct HttpRequest {
inner: Request<HttpBody>,
query_args_cache: OnceLock<QueryArgsCache>,
}
impl std::fmt::Debug for HttpRequest {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("HttpRequest(..)")
}
}
impl HttpRequest {
pub(crate) fn new(request: Request<Incoming>) -> Self {
Self {
inner: request.map(HttpBody::incoming),
query_args_cache: OnceLock::new(),
}
}
#[inline]
pub fn uri(&self) -> &Uri {
self.inner.uri()
}
#[inline]
pub fn headers(&self) -> &HeaderMap {
self.inner.headers()
}
#[inline]
#[allow(unused)]
pub(crate) fn headers_mut(&mut self) -> &mut HeaderMap {
self.inner.headers_mut()
}
#[inline]
pub fn method(&self) -> &Method {
self.inner.method()
}
#[inline]
pub fn version(&self) -> Version {
self.inner.version()
}
#[inline]
pub(crate) fn extensions(&self) -> &Extensions {
self.inner.extensions()
}
#[inline]
#[allow(unused)]
pub(crate) fn extensions_mut(&mut self) -> &mut Extensions {
self.inner.extensions_mut()
}
pub fn body_limit(&self) -> Option<usize> {
match self
.inner
.extensions()
.get::<HttpRequestScope>()?
.body_limit
{
RequestBodyLimit::Enabled(size) => Some(size),
RequestBodyLimit::Disabled => None,
}
}
#[inline]
pub(crate) fn into_limited(self, body_limit: RequestBodyLimit) -> Self {
match body_limit {
RequestBodyLimit::Disabled => self,
RequestBodyLimit::Enabled(limit) => {
let (parts, body) = self.into_parts();
let body = HttpBody::limited(body, limit);
Self::from_parts(parts, body)
}
}
}
#[inline]
pub fn into_body(self) -> HttpBody {
self.inner.into_body()
}
pub(crate) fn into_parts(self) -> (Parts, HttpBody) {
self.inner.into_parts()
}
pub(crate) fn from_parts(parts: Parts, body: HttpBody) -> Self {
let request = Request::from_parts(parts, body);
Self {
inner: request,
query_args_cache: OnceLock::new(),
}
}
#[inline]
pub fn extract<T: FromRequestRef>(&self) -> Result<T, Error> {
T::from_request(self)
}
pub fn path_args(&self) -> impl Iterator<Item = (&str, &str)> {
self.inner
.extensions()
.get::<HttpRequestScope>()
.map(|s| &s.params)
.into_iter()
.flat_map(|args| {
args.iter()
.map(|arg| (arg.name.as_ref(), arg.value.as_ref()))
})
}
pub fn query_args(&self) -> impl Iterator<Item = (&str, &str)> {
let query = self.inner.uri().query().unwrap_or_default();
let cache = self
.query_args_cache
.get_or_init(|| QueryArgsCache::new(query));
QueryArgsIter::new(query, cache)
}
#[inline]
pub fn get_header<T: FromHeaders>(&self) -> Option<Header<T>> {
self.headers().get(T::NAME).map(Header::from_ref)
}
#[inline]
pub fn get_all_headers<T: FromHeaders>(&self) -> impl Iterator<Item = Header<T>> {
self.headers().get_all(T::NAME).iter().map(Header::from_ref)
}
}
#[cfg(test)]
#[allow(unreachable_pub)]
#[allow(unused)]
mod tests {
use super::*;
use crate::headers::{Header, Vary, headers};
use crate::http::endpoints::route::PathArg;
use http_body_util::BodyExt;
headers! {
(Foo, "x-foo")
}
#[test]
fn it_inserts_header() {
let req = Request::get("http://localhost")
.body(HttpBody::empty())
.unwrap();
let (parts, body) = req.into_parts();
let mut http_req = HttpRequest::from_parts(parts, body);
http_req
.headers_mut()
.insert("vary", "foo".parse().unwrap());
assert_eq!(http_req.headers().get("vary").unwrap(), "foo");
}
#[test]
fn it_extracts_from_request_ref() {
let req = Request::get("http://localhost/")
.header("vary", "foo")
.body(HttpBody::empty())
.unwrap();
let (parts, body) = req.into_parts();
let http_req = HttpRequest::from_parts(parts, body);
let header = http_req.extract::<Header<Vary>>().unwrap();
assert_eq!(header.value(), "foo");
}
#[tokio::test]
async fn it_unwraps_body() {
let req = Request::get("http://localhost/")
.body(HttpBody::full("foo"))
.unwrap();
let (parts, body) = req.into_parts();
let http_req = HttpRequest::from_parts(parts, body);
let body = http_req.into_body().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(&body), "foo");
}
#[tokio::test]
async fn it_unwraps_inner_req() {
let req = Request::get("http://localhost/")
.body(HttpBody::full("foo"))
.unwrap();
let (parts, body) = req.into_parts();
let http_req = HttpRequest::from_parts(parts, body);
let body = http_req.into_body().collect().await.unwrap().to_bytes();
assert_eq!(String::from_utf8_lossy(&body), "foo");
}
#[test]
fn it_debugs() {
let (parts, body) = Request::get("/")
.body(HttpBody::empty())
.unwrap()
.into_parts();
let req = HttpRequest::from_parts(parts, body);
assert_eq!(format!("{req:?}"), "HttpRequest(..)");
}
#[test]
fn it_splits_into_parts() {
let (parts, body) = Request::get("/test")
.body(HttpBody::empty())
.unwrap()
.into_parts();
let ctx = HttpRequest::from_parts(parts, body);
let (parts, _) = ctx.into_parts();
assert_eq!(parts.uri, "/test")
}
#[test]
fn it_returns_url_path() {
use crate::http::endpoints::route::{PathArg, PathArgs};
use crate::http::request_scope::HttpRequestScope;
let args: PathArgs = smallvec::smallvec![
PathArg {
name: "id".into(),
value: "123".into()
},
PathArg {
name: "name".into(),
value: "John".into()
}
]
.into();
let req = Request::get("/")
.extension(HttpRequestScope {
params: args,
..HttpRequestScope::default()
})
.body(HttpBody::empty())
.unwrap();
let (parts, body) = req.into_parts();
let req = HttpRequest::from_parts(parts, body);
let mut args = req.path_args();
let mut args_2 = req.path_args();
assert_eq!(Vec::from_iter(args), Vec::from_iter(args_2));
let mut args = req.path_args();
assert_eq!(args.next().unwrap(), ("id", "123"));
assert_eq!(args.next().unwrap(), ("name", "John"));
}
#[test]
fn it_returns_url_query() {
let req = Request::get("/test?id=123&name=John&age")
.body(HttpBody::empty())
.unwrap();
let (parts, body) = req.into_parts();
let req = HttpRequest::from_parts(parts, body);
let mut args = req.query_args();
assert_eq!(args.next().unwrap(), ("id", "123"));
assert_eq!(args.next().unwrap(), ("name", "John"));
assert!(args.next().is_none());
}
#[test]
fn it_returns_empty_iter_if_no_path_params() {
let req = Request::get("/").body(HttpBody::empty()).unwrap();
let (parts, body) = req.into_parts();
let req = HttpRequest::from_parts(parts, body);
let mut args = req.path_args();
assert!(args.next().is_none());
}
#[test]
fn it_returns_empty_iter_if_no_query_params() {
let req = Request::get("/").body(HttpBody::empty()).unwrap();
let (parts, body) = req.into_parts();
let req = HttpRequest::from_parts(parts, body);
let mut args = req.query_args();
assert!(args.next().is_none());
}
#[test]
fn it_gets_header() {
let (parts, body) = Request::get("/test")
.body(HttpBody::empty())
.unwrap()
.into_parts();
let mut req = HttpRequest::from_parts(parts, body);
req.headers_mut().insert("x-foo", "val".parse().unwrap());
assert_eq!(req.get_header::<Foo>().unwrap().value(), "val");
}
#[test]
fn it_gets_many_headers() {
let (parts, body) = Request::get("/test")
.body(HttpBody::empty())
.unwrap()
.into_parts();
let mut req = HttpRequest::from_parts(parts, body);
req.headers_mut().append("x-foo", "1".parse().unwrap());
req.headers_mut().append("x-foo", "2".parse().unwrap());
assert_eq!(
req.get_all_headers::<Foo>()
.map(|h| h.value().clone())
.collect::<Vec<_>>(),
["1", "2"]
);
}
#[test]
fn it_gets_body_limit() {
use crate::http::request_scope::HttpRequestScope;
let scope = HttpRequestScope {
body_limit: RequestBodyLimit::Enabled(100),
..HttpRequestScope::default()
};
let (parts, body) = Request::get("/test")
.extension(scope)
.body(HttpBody::full("Hello, World!"))
.unwrap()
.into_parts();
let req = HttpRequest::from_parts(parts, body);
assert_eq!(req.body_limit(), Some(100))
}
}