diff --git a/Cargo.lock b/Cargo.lock index c9072337..ea800f92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2990,8 +2990,9 @@ dependencies = [ [[package]] name = "volo-http" -version = "0.1.12" +version = "0.1.13" dependencies = [ + "ahash", "bytes", "cookie", "faststr", diff --git a/Cargo.toml b/Cargo.toml index 5132cc84..5a999ecf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ motore = "0.4" metainfo = "0.7" +ahash = "0.8" anyhow = "1" async-broadcast = "0.6" async-stream = "0.3" diff --git a/examples/src/http/simple.rs b/examples/src/http/simple.rs index 04f0ee89..33fc496f 100644 --- a/examples/src/http/simple.rs +++ b/examples/src/http/simple.rs @@ -11,8 +11,8 @@ use volo_http::{ middleware::{self, Next}, response::IntoResponse, route::{from_handler, get, post, service_fn, MethodRouter, Router}, - Address, BodyIncoming, Bytes, ConnectionInfo, CookieJar, HttpContext, Json, MaybeInvalid, - Method, Params, Response, Server, StatusCode, Uri, + Address, BodyIncoming, ConnectionInfo, CookieJar, HttpContext, Json, MaybeInvalid, Method, + Params, Response, Server, StatusCode, Uri, }; async fn hello() -> &'static str { @@ -103,9 +103,9 @@ async fn timeout_test() { tokio::time::sleep(Duration::from_secs(10)).await } -async fn echo(params: Params) -> Result { +async fn echo(params: Params) -> Result { if let Some(echo) = params.get("echo") { - return Ok(echo.clone()); + return Ok(echo.to_owned()); } Err(StatusCode::BAD_REQUEST) } diff --git a/volo-http/Cargo.toml b/volo-http/Cargo.toml index 6b2788aa..00f1906e 100644 --- a/volo-http/Cargo.toml +++ b/volo-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "volo-http" -version = "0.1.12" +version = "0.1.13" edition.workspace = true homepage.workspace = true repository.workspace = true @@ -24,6 +24,7 @@ http-body-util = "0.1" hyper = { version = "1", features = ["server", "http1", "http2"] } hyper-util = { version = "0.1", features = ["tokio"] } +ahash.workspace = true bytes.workspace = true cookie = { workspace = true, optional = true, features = ["percent-encode"] } faststr.workspace = true diff --git a/volo-http/src/context.rs b/volo-http/src/context.rs index dc61fe4e..7105f904 100644 --- a/volo-http/src/context.rs +++ b/volo-http/src/context.rs @@ -14,7 +14,7 @@ use crate::param::Params; static X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host"); static X_FORWARDED_PROTO: HeaderName = HeaderName::from_static("x-forwarded-proto"); -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct HttpContext { pub peer: Address, pub method: Method, diff --git a/volo-http/src/param.rs b/volo-http/src/param.rs index f83ddfd2..8ade850b 100644 --- a/volo-http/src/param.rs +++ b/volo-http/src/param.rs @@ -1,36 +1,34 @@ -use std::slice::Iter; +use std::collections::{hash_map::Iter, HashMap}; -use bytes::{BufMut, Bytes, BytesMut}; +use ahash::RandomState; +use bytes::{BufMut, BytesMut}; +use faststr::FastStr; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Params { - pub(crate) inner: Vec<(Bytes, Bytes)>, + inner: HashMap, } -impl From> for Params { - fn from(params: matchit::Params) -> Self { - let mut inner = Vec::with_capacity(params.len()); - let mut capacity = 0; - for (k, v) in params.iter() { - capacity += k.len(); - capacity += v.len(); - } +impl Params { + pub(crate) fn extend(&mut self, params: matchit::Params<'_, '_>) { + self.inner.reserve(params.len()); - let mut buf = BytesMut::with_capacity(capacity); + let cap = params.iter().map(|(k, v)| k.len() + v.len()).sum(); + let mut buf = BytesMut::with_capacity(cap); for (k, v) in params.iter() { buf.put(k.as_bytes()); - let k = buf.split().freeze(); + // SAFETY: The key is from a valid string as path of router + let k = unsafe { FastStr::from_bytes_unchecked(buf.split().freeze()) }; buf.put(v.as_bytes()); - let v = buf.split().freeze(); - inner.push((k, v)); + // SAFETY: The value is from a valid string as requested uri + let v = unsafe { FastStr::from_bytes_unchecked(buf.split().freeze()) }; + if self.inner.insert(k, v).is_some() { + tracing::info!("[VOLO-HTTP] Conflicting key in param"); + } } - - Self { inner } } -} -impl Params { pub fn len(&self) -> usize { self.inner.len() } @@ -39,14 +37,11 @@ impl Params { self.len() == 0 } - pub fn iter(&self) -> Iter<'_, (Bytes, Bytes)> { + pub fn iter(&self) -> Iter<'_, FastStr, FastStr> { self.inner.iter() } - pub fn get>(&self, k: K) -> Option<&Bytes> { - self.iter() - .filter(|(ik, _)| ik.as_ref() == k.as_ref()) - .map(|(_, v)| v) - .next() + pub fn get>(&self, k: K) -> Option<&FastStr> { + self.inner.get(&k.into()) } } diff --git a/volo-http/src/response.rs b/volo-http/src/response.rs index e4bf080c..d04cca8e 100644 --- a/volo-http/src/response.rs +++ b/volo-http/src/response.rs @@ -4,6 +4,7 @@ use std::{ task::{Context, Poll}, }; +use faststr::FastStr; use futures_util::ready; use http_body_util::Full; use hyper::{ @@ -57,6 +58,14 @@ impl From for RespBody { } } +impl From for RespBody { + fn from(value: FastStr) -> Self { + Self { + inner: Full::new(value.into()), + } + } +} + impl From for RespBody { fn from(value: String) -> Self { Self { diff --git a/volo-http/src/route.rs b/volo-http/src/route.rs index a843e15a..84ce50ae 100644 --- a/volo-http/src/route.rs +++ b/volo-http/src/route.rs @@ -173,7 +173,7 @@ impl Service for Router<()> { ) -> Result { if let Ok(matched) = self.matcher.at(cx.uri.path()) { if let Some(srv) = self.routes.get(matched.value) { - cx.params = matched.params.into(); + cx.params.extend(matched.params); return srv.call_with_state(cx, req, ()).await; } } diff --git a/volo-http/src/server.rs b/volo-http/src/server.rs index 21e905bd..1799ac0e 100644 --- a/volo-http/src/server.rs +++ b/volo-http/src/server.rs @@ -259,9 +259,7 @@ async fn handle_conn( version: parts.version, headers: parts.headers, extensions: parts.extensions, - params: Params { - inner: Vec::with_capacity(0), - }, + params: Params::default(), }; let resp = match service.call(&mut cx, req).await { Ok(resp) => resp,