diff --git a/cli/src/config/fn.rs b/cli/src/config/fn.rs index 66e7d2b9..0bf5ddf6 100644 --- a/cli/src/config/fn.rs +++ b/cli/src/config/fn.rs @@ -4,7 +4,7 @@ use super::*; /// /// # Returns /// -/// - `Args`: Parsed arguments +/// - `Args` - Parsed arguments pub fn parse_args() -> Args { let raw_args: Vec = args().collect(); let mut command: CommandType = CommandType::Help; diff --git a/cli/src/new/fn.rs b/cli/src/new/fn.rs index 58e633dd..cba88156 100644 --- a/cli/src/new/fn.rs +++ b/cli/src/new/fn.rs @@ -4,11 +4,11 @@ use super::*; /// /// # Arguments /// -/// - `&str`: Project name to validate +/// - `&str` - Project name to validate /// /// # Returns /// -/// - `Result<(), NewError>`: Ok if valid, error otherwise +/// - `Result<(), NewError>` - Ok if valid, error otherwise fn validate_project_name(name: &str) -> Result<(), NewError> { if name.is_empty() { return Err(NewError::InvalidName( @@ -32,7 +32,7 @@ fn validate_project_name(name: &str) -> Result<(), NewError> { /// /// # Returns /// -/// - `Result<(), NewError>`: Ok if git is available, error otherwise +/// - `Result<(), NewError>` - Ok if git is available, error otherwise async fn check_git_available() -> Result<(), NewError> { let output: std::process::Output = Command::new("git") .arg("--version") @@ -52,11 +52,11 @@ async fn check_git_available() -> Result<(), NewError> { /// /// # Arguments /// -/// - `&NewProjectConfig`: Project configuration containing template URL and project name +/// - `&NewProjectConfig` - Project configuration containing template URL and project name /// /// # Returns /// -/// - `Result<(), NewError>`: Success or error +/// - `Result<(), NewError>` - Success or error async fn git_clone(config: &NewProjectConfig) -> Result<(), NewError> { let project_path: PathBuf = PathBuf::from(&config.project_name); if project_path.exists() { @@ -83,11 +83,11 @@ async fn git_clone(config: &NewProjectConfig) -> Result<(), NewError> { /// /// # Arguments /// -/// - `&str`: Name of the project to create +/// - `&str` - Name of the project to create /// /// # Returns /// -/// - `Result<(), NewError>`: Success or error +/// - `Result<(), NewError>` - Success or error pub async fn execute_new(project_name: &str) -> Result<(), NewError> { validate_project_name(project_name)?; check_git_available().await?; diff --git a/cli/src/new/impl.rs b/cli/src/new/impl.rs index 4eea8e4a..48d85740 100644 --- a/cli/src/new/impl.rs +++ b/cli/src/new/impl.rs @@ -5,11 +5,11 @@ impl NewProjectConfig { /// /// # Arguments /// - /// - `String`: Name of the project + /// - `String` - Name of the project /// /// # Returns /// - /// - `Self`: Configuration instance + /// - `Self` - Configuration instance pub fn new(project_name: String) -> Self { Self { project_name, diff --git a/cli/src/template/fn.rs b/cli/src/template/fn.rs index f4a42b07..f05491b9 100644 --- a/cli/src/template/fn.rs +++ b/cli/src/template/fn.rs @@ -4,11 +4,11 @@ use super::*; /// /// # Arguments /// -/// - `&TemplateType`: The template type +/// - `&TemplateType` - The template type /// /// # Returns /// -/// - `String`: Directory name +/// - `String` - Directory name fn get_directory_name(template_type: &TemplateType) -> String { match template_type { TemplateType::Controller => "controller".to_string(), @@ -27,11 +27,11 @@ fn get_directory_name(template_type: &TemplateType) -> String { /// /// # Arguments /// -/// - `&ModelSubType`: The model subtype +/// - `&ModelSubType` - The model subtype /// /// # Returns /// -/// - `String`: Directory name +/// - `String` - Directory name fn get_model_sub_type_name(sub_type: &ModelSubType) -> String { match sub_type { ModelSubType::Application => "application".to_string(), @@ -44,11 +44,11 @@ fn get_model_sub_type_name(sub_type: &ModelSubType) -> String { /// /// # Arguments /// -/// - `&Path`: Path to the directory +/// - `&Path` - Path to the directory /// /// # Returns /// -/// - `Result<(), TemplateError>`: Success or error +/// - `Result<(), TemplateError>` - Success or error async fn ensure_directory(path: &Path) -> Result<(), TemplateError> { if !path.exists() { create_dir_all(path).await?; @@ -60,12 +60,12 @@ async fn ensure_directory(path: &Path) -> Result<(), TemplateError> { /// /// # Arguments /// -/// - `&Path`: Path to mod.rs file -/// - `&[&str]`: List of modules to include +/// - `&Path` - Path to mod.rs file +/// - `&[&str]` - List of modules to include /// /// # Returns /// -/// - `Result<(), TemplateError>`: Success or error +/// - `Result<(), TemplateError>` - Success or error async fn write_mod_rs(path: &Path, modules: &[&str]) -> Result<(), TemplateError> { let mut content: String = String::new(); for module in modules { @@ -112,11 +112,11 @@ async fn write_mod_rs(path: &Path, modules: &[&str]) -> Result<(), TemplateError /// /// # Arguments /// -/// - `&Path`: Path to mod.rs file +/// - `&Path` - Path to mod.rs file /// /// # Returns /// -/// - `Result<(), TemplateError>`: Success or error +/// - `Result<(), TemplateError>` - Success or error async fn write_empty_mod_rs(path: &Path) -> Result<(), TemplateError> { write(path, "\n").await?; Ok(()) @@ -126,12 +126,12 @@ async fn write_empty_mod_rs(path: &Path) -> Result<(), TemplateError> { /// /// # Arguments /// -/// - `&Path`: Target directory path -/// - `&str`: Name of the component +/// - `&Path` - Target directory path +/// - `&str` - Name of the component /// /// # Returns /// -/// - `Result<(), TemplateError>`: Success or error +/// - `Result<(), TemplateError>` - Success or error async fn create_controller_template( target_dir: &Path, _component_name: &str, @@ -152,12 +152,12 @@ async fn create_controller_template( /// /// # Arguments /// -/// - `&Path`: Target directory path -/// - `&str`: Name of the component +/// - `&Path` - Target directory path +/// - `&str` - Name of the component /// /// # Returns /// -/// - `Result<(), TemplateError>`: Success or error +/// - `Result<(), TemplateError>` - Success or error async fn create_view_template( target_dir: &Path, _component_name: &str, @@ -178,12 +178,12 @@ async fn create_view_template( /// /// # Arguments /// -/// - `&Path`: Target directory path -/// - `&str`: Name of the component +/// - `&Path` - Target directory path +/// - `&str` - Name of the component /// /// # Returns /// -/// - `Result<(), TemplateError>`: Success or error +/// - `Result<(), TemplateError>` - Success or error async fn create_service_template( target_dir: &Path, _component_name: &str, @@ -202,12 +202,12 @@ async fn create_service_template( /// /// # Arguments /// -/// - `&Path`: Target directory path -/// - `&str`: Name of the component +/// - `&Path` - Target directory path +/// - `&str` - Name of the component /// /// # Returns /// -/// - `Result<(), TemplateError>`: Success or error +/// - `Result<(), TemplateError>` - Success or error async fn create_domain_template( target_dir: &Path, _component_name: &str, @@ -226,12 +226,12 @@ async fn create_domain_template( /// /// # Arguments /// -/// - `&Path`: Target directory path -/// - `&str`: Name of the component +/// - `&Path` - Target directory path +/// - `&str` - Name of the component /// /// # Returns /// -/// - `Result<(), TemplateError>`: Success or error +/// - `Result<(), TemplateError>` - Success or error async fn create_mapper_template( target_dir: &Path, _component_name: &str, @@ -262,12 +262,12 @@ async fn create_mapper_template( /// /// # Arguments /// -/// - `&Path`: Target directory path -/// - `&str`: Name of the component +/// - `&Path` - Target directory path +/// - `&str` - Name of the component /// /// # Returns /// -/// - `Result<(), TemplateError>`: Success or error +/// - `Result<(), TemplateError>` - Success or error async fn create_utils_template( target_dir: &Path, _component_name: &str, @@ -284,12 +284,12 @@ async fn create_utils_template( /// /// # Arguments /// -/// - `&Path`: Target directory path -/// - `&str`: Name of the component +/// - `&Path` - Target directory path +/// - `&str` - Name of the component /// /// # Returns /// -/// - `Result<(), TemplateError>`: Success or error +/// - `Result<(), TemplateError>` - Success or error async fn create_exception_template( target_dir: &Path, _component_name: &str, @@ -304,12 +304,12 @@ async fn create_exception_template( /// /// # Arguments /// -/// - `&Path`: Target directory path -/// - `&str`: Name of the component +/// - `&Path` - Target directory path +/// - `&str` - Name of the component /// /// # Returns /// -/// - `Result<(), TemplateError>`: Success or error +/// - `Result<(), TemplateError>` - Success or error async fn create_repository_template( target_dir: &Path, _component_name: &str, @@ -328,13 +328,13 @@ async fn create_repository_template( /// /// # Arguments /// -/// - `&Path`: Target directory path -/// - `&str`: Name of the component -/// - `&ModelSubType`: Model subtype +/// - `&Path` - Target directory path +/// - `&str` - Name of the component +/// - `&ModelSubType` - Model subtype /// /// # Returns /// -/// - `Result<(), TemplateError>`: Success or error +/// - `Result<(), TemplateError>` - Success or error async fn create_model_template( target_dir: &Path, _component_name: &str, @@ -354,13 +354,13 @@ async fn create_model_template( /// /// # Arguments /// -/// - `&TemplateType`: Type of template component -/// - `&str`: Name of the component -/// - `model_sub_type`: Optional model subtype +/// - `&TemplateType` - Type of template component +/// - `&str` - Name of the component +/// - `model_sub_type` - Optional model subtype /// /// # Returns /// -/// - `Result<(), TemplateError>`: Success or error +/// - `Result<(), TemplateError>` - Success or error pub async fn execute_template( template_type: TemplateType, component_name: &str, @@ -416,11 +416,11 @@ pub async fn execute_template( /// /// # Arguments /// -/// - `&Path`: Path to format +/// - `&Path` - Path to format /// /// # Returns /// -/// - `Result<(), io::Error>`: Success or error +/// - `Result<(), io::Error>` - Success or error async fn format_generated_path(path: &Path) -> Result<(), io::Error> { let mut cmd: Command = Command::new("cargo"); cmd.arg("fmt").arg("--").arg(path); diff --git a/cli/src/template/impl.rs b/cli/src/template/impl.rs index 1240ff88..419a175d 100644 --- a/cli/src/template/impl.rs +++ b/cli/src/template/impl.rs @@ -24,13 +24,13 @@ impl TemplateConfig { /// /// # Arguments /// - /// - `TemplateType`: Type of template component - /// - `String`: Name of the component - /// - `Option`: Optional model subtype for model components + /// - `TemplateType` - Type of template component + /// - `String` - Name of the component + /// - `Option` - Optional model subtype for model components /// /// # Returns /// - /// - `Self`: Configuration instance + /// - `Self` - Configuration instance pub fn new( template_type: TemplateType, component_name: String, diff --git a/cli/src/watch/fn.rs b/cli/src/watch/fn.rs index b1076ad9..1da6bff4 100644 --- a/cli/src/watch/fn.rs +++ b/cli/src/watch/fn.rs @@ -4,7 +4,7 @@ use super::*; /// /// # Returns /// -/// - `Result<(), io::Error>`: Success or error +/// - `Result<(), io::Error>` - Success or error async fn run_cargo_run() -> Result<(), io::Error> { let output: std::process::Output = Command::new("cargo") .arg("run") @@ -45,7 +45,7 @@ async fn run_cargo_run() -> Result<(), io::Error> { /// /// # Returns /// -/// - `Result<(), io::Error>`: Success or error +/// - `Result<(), io::Error>` - Success or error pub async fn execute_watch() -> Result<(), io::Error> { let src_path: PathBuf = PathBuf::from("src"); if !src_path.exists() { diff --git a/constant/src/common/const.rs b/constant/src/common/const.rs index 4ade866d..22f46547 100644 --- a/constant/src/common/const.rs +++ b/constant/src/common/const.rs @@ -45,7 +45,7 @@ pub const DOUBLE_BR: &str = "\n\n"; /// A const byte slice representation of the string `DOUBLE_BR`. pub const DOUBLE_BR_BYTES: &[u8] = DOUBLE_BR.as_bytes(); -/// A colon followed by a space (`: `). +/// A colon followed by a space (` - `). /// This constant is commonly used in formatted strings, such as /// headers or key-value pairs, where a colon and a space are needed. pub const COLON_SPACE: &str = ": "; @@ -224,6 +224,26 @@ pub const ZERO_STR_U8: u8 = ZERO_STR_BYTES[0]; /// operations, such as reading from a network stream. pub const DEFAULT_BUFFER_SIZE: usize = KB_4; +/// The initial capacity of the request-line buffer. +/// This constant defines the initial string capacity used when reading +/// the HTTP request line; the string grows automatically for longer lines. +pub const REQUEST_LINE_BUFFER_CAPACITY: usize = B_128; + +/// The initial capacity of the header-line buffer. +/// This constant defines the initial string capacity used when reading +/// a single HTTP header line; the string grows automatically for longer lines. +pub const HEADER_LINE_BUFFER_CAPACITY: usize = B_256; + +/// The maximum number of pooled read buffers retained per thread. +/// This constant caps the thread-local read-buffer pool size to bound +/// the memory retained for connection reuse. +pub const MAX_POOLED_READ_BUFFERS: usize = B_64; + +/// The maximum byte size of a read buffer eligible for pooling. +/// This constant prevents oversized buffers from being retained in +/// the thread-local read-buffer pool. +pub const MAX_POOLED_READ_BUFFER_SIZE: usize = KB_64; + /// The default maximum path size. /// This constant defines the maximum size of the path component /// in an HTTP request to prevent excessive memory usage. diff --git a/core/src/context/impl.rs b/core/src/context/impl.rs index b879e173..06498a15 100644 --- a/core/src/context/impl.rs +++ b/core/src/context/impl.rs @@ -154,7 +154,7 @@ impl Lifetime for Context { /// /// # Returns /// - /// - `&'static Self`: A reference to the context with a `'static` lifetime. + /// - `&'static Self` - A reference to the context with a `'static` lifetime. /// /// # Safety /// @@ -170,7 +170,7 @@ impl Lifetime for Context { /// /// # Returns /// - /// - `&'static mut Self`: A mutable reference to the context with a `'static` lifetime. + /// - `&'static mut Self` - A mutable reference to the context with a `'static` lifetime. /// /// # Safety /// @@ -185,6 +185,20 @@ impl Lifetime for Context { /// Implementation of methods for `Context` structure. impl Context { + /// Clears all route parameters while retaining the map's allocated capacity. + /// + /// Used between keep-alive requests on the same connection to avoid + /// reallocating the parameter map for every request. + /// + /// # Returns + /// + /// - `&mut Self` - A mutable reference to self for chaining. + #[inline(always)] + pub(crate) fn clear_route_params(&mut self) -> &mut Self { + self.get_mut_route_params().clear(); + self + } + /// Attempts to retrieve a specific route parameter by its name. /// /// # Arguments diff --git a/core/src/context/struct.rs b/core/src/context/struct.rs index 75562c5d..9995b1bb 100644 --- a/core/src/context/struct.rs +++ b/core/src/context/struct.rs @@ -11,7 +11,6 @@ pub struct Context { /// The outgoing HTTP response. pub(super) response: Response, /// Parameters extracted from the route path. - #[get_mut(skip)] pub(super) route_params: RouteParams, /// A collection of custom attributes for sharing data within the request lifecycle. pub(super) attributes: ThreadSafeAttributeStore, diff --git a/core/src/lib.rs b/core/src/lib.rs index 54e67646..7ce8a512 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -26,6 +26,7 @@ use std::{ future::Future, hash::{Hash, Hasher}, io::{self, Write, stderr, stdout}, + mem, pin::Pin, sync::Arc, }; diff --git a/core/src/route/impl.rs b/core/src/route/impl.rs index 6eddb589..4e619247 100644 --- a/core/src/route/impl.rs +++ b/core/src/route/impl.rs @@ -32,7 +32,7 @@ impl PartialEq for RoutePattern { /// /// # Returns /// - /// - `bool`- `true` if the instances are equal, `false` otherwise. + /// - `bool` - `true` if the instances are equal, `false` otherwise. #[inline(always)] fn eq(&self, other: &Self) -> bool { self.get_0() == other.get_0() @@ -67,11 +67,11 @@ impl PartialOrd for RoutePattern { /// /// # Arguments /// - /// - `&Self`- The other `RoutePattern` instance to compare against. + /// - `&Self` - The other `RoutePattern` instance to compare against. /// /// # Returns /// - /// - `Option`- The ordering of the two instances. + /// - `Option` - The ordering of the two instances. #[inline(always)] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -86,11 +86,11 @@ impl Ord for RoutePattern { /// /// # Arguments /// - /// - `&Self`- The other `RoutePattern` instance to compare against. + /// - `&Self` - The other `RoutePattern` instance to compare against. /// /// # Returns /// - /// - `Ordering`- The ordering of the two instances. + /// - `Ordering` - The ordering of the two instances. #[inline(always)] fn cmp(&self, other: &Self) -> Ordering { self.get_0().cmp(other.get_0()) @@ -105,11 +105,11 @@ impl PartialEq for RouteMatcher { /// /// # Arguments /// - /// - `&Self`- The other `RouteMatcher` instance to compare against. + /// - `&Self` - The other `RouteMatcher` instance to compare against. /// /// # Returns /// - /// - `bool`- `true` if the instances are equal, `false` otherwise. + /// - `bool` - `true` if the instances are equal, `false` otherwise. fn eq(&self, other: &Self) -> bool { if self.get_static_route().len() != other.get_static_route().len() { return false; @@ -177,11 +177,11 @@ impl PartialOrd for RouteSegment { /// /// # Arguments /// - /// - `&Self`- The other `RouteSegment` instance to compare against. + /// - `&Self` - The other `RouteSegment` instance to compare against. /// /// # Returns /// - /// - `Option`- The ordering of the two instances. + /// - `Option` - The ordering of the two instances. #[inline(always)] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -196,11 +196,11 @@ impl Ord for RouteSegment { /// /// # Arguments /// - /// - `&Self`- The other `RouteSegment` instance to compare against. + /// - `&Self` - The other `RouteSegment` instance to compare against. /// /// # Returns /// - /// - `Ordering`- The ordering of the two instances. + /// - `Ordering` - The ordering of the two instances. #[inline(always)] fn cmp(&self, other: &Self) -> Ordering { match (self, other) { @@ -229,11 +229,11 @@ impl PartialEq for RouteSegment { /// /// # Arguments /// - /// - `&Self`- The other `RouteSegment` instance to compare against. + /// - `&Self` - The other `RouteSegment` instance to compare against. /// /// # Returns /// - /// - `bool`- `true` if the instances are equal, `false` otherwise. + /// - `bool` - `true` if the instances are equal, `false` otherwise. #[inline(always)] fn eq(&self, other: &Self) -> bool { match (self, other) { diff --git a/core/src/route/struct.rs b/core/src/route/struct.rs index 5d48b3a2..d3c14a3e 100644 --- a/core/src/route/struct.rs +++ b/core/src/route/struct.rs @@ -15,9 +15,9 @@ pub struct RoutePattern( /// The core routing engine responsible for matching request paths to their corresponding handlers. /// /// The matcher categorizes route into three types for optimized performance: -/// 1. `static_route`- For exact path matches, offering the fastest lookups. -/// 2. `dynamic_route`- For paths with variable segments. -/// 3. `regex_route`- For complex matching based on regular expressions. +/// 1. `static_route` - For exact path matches, offering the fastest lookups. +/// 2. `dynamic_route` - For paths with variable segments. +/// 3. `regex_route` - For complex matching based on regular expressions. /// /// When a request comes in, the matcher checks these categories in order to find the appropriate hook. #[derive(Clone, CustomDebug, DisplayDebug, Getter, GetterMut, Setter)] diff --git a/core/src/server/impl.rs b/core/src/server/impl.rs index e2152412..1cf61a6e 100644 --- a/core/src/server/impl.rs +++ b/core/src/server/impl.rs @@ -29,11 +29,11 @@ impl PartialEq for Server { /// /// # Arguments /// - /// - `&Self`- The other `Server` instance to compare against. + /// - `&Self` - The other `Server` instance to compare against. /// /// # Returns /// - /// - `bool`- `true` if the instances are equal, `false` otherwise. + /// - `bool` - `true` if the instances are equal, `false` otherwise. #[inline] fn eq(&self, other: &Self) -> bool { self.get_server_config() == other.get_server_config() @@ -247,7 +247,7 @@ impl Lifetime for Server { /// /// # Returns /// - /// - `&'static Self`: A reference to the server with a `'static` lifetime. + /// - `&'static Self` - A reference to the server with a `'static` lifetime. /// /// # Safety /// @@ -263,7 +263,7 @@ impl Lifetime for Server { /// /// # Returns /// - /// - `&'static mut Self`: A mutable reference to the server with a `'static` lifetime. + /// - `&'static mut Self` - A mutable reference to the server with a `'static` lifetime. /// /// # Safety /// @@ -354,7 +354,7 @@ impl Server { /// /// # Arguments /// - /// - `RequestConfig`- The HTTP request config to set. + /// - `RequestConfig` - The HTTP request config to set. /// /// # Returns /// @@ -694,30 +694,24 @@ impl Server { /// /// - `&mut Stream` - The `Stream` for the current request. /// - `&mut Context` - The `Context` for the current request. - /// - `&Request` - The incoming request to be processed. + /// - `Request` - The incoming request to be processed. /// /// # Returns /// /// - `bool` - A boolean indicating whether the connection should be kept alive. - async fn request_hook( - &self, - stream: &mut Stream, - ctx: &mut Context, - request: &Request, - ) -> bool { - let mut response: Response = Response::default(); - response.set_version(request.get_version().clone()); - ctx.set_request(request.clone()); - ctx.set_response(response); - ctx.set_route_params(RouteParams::default()); + async fn request_hook(&self, stream: &mut Stream, ctx: &mut Context, request: Request) -> bool { + let keep_alive: bool = request.is_enable_keep_alive(); + let version: RequestVersion = request.get_version().clone(); + let route: RequestPath = request.get_path().clone(); + ctx.set_request(request); + ctx.get_mut_response().reset().set_version(version); + ctx.clear_route_params(); ctx.clear_attribute(); stream.set_closed(false); - let keep_alive: bool = request.is_enable_keep_alive(); if self.handle_request_middleware(stream, ctx).await { return stream.is_keep_alive(keep_alive); } - let route: &str = request.get_path(); - if self.handle_route_matcher(stream, ctx, route).await { + if self.handle_route_matcher(stream, ctx, &route).await { return stream.is_keep_alive(keep_alive); } if self.handle_response_middleware(stream, ctx).await { @@ -732,24 +726,21 @@ impl Server { /// /// - `&mut Stream` - The `Stream` for the current request. /// - `&mut Context` - The `Context` for the current request. - /// - `&Request` - The initial request that established the keep-alive connection. - async fn handle_http_requests( - &self, - stream: &mut Stream, - ctx: &mut Context, - request: &Request, - ) { + /// - `Request` - The initial request that established the keep-alive connection. + async fn handle_http_requests(&self, stream: &mut Stream, ctx: &mut Context, request: Request) { if !self.request_hook(stream, ctx, request).await { return; } loop { - match stream.try_get_http_request().await { - Ok(new_request) => { - if !self.request_hook(stream, ctx, &new_request).await { + let mut reused_request: Request = mem::take(ctx.get_mut_request()); + match stream.try_fill_http_request(&mut reused_request).await { + Ok(()) => { + if !self.request_hook(stream, ctx, reused_request).await { return; } } Err(error) => { + ctx.set_request(reused_request); self.handle_request_error(stream, ctx, &error).await; return; } @@ -773,7 +764,7 @@ impl Server { async fn handle_connection(&self, stream: &mut Stream, ctx: &mut Context) { match stream.try_get_http_request().await { Ok(request) => { - self.handle_http_requests(stream, ctx, &request).await; + self.handle_http_requests(stream, ctx, request).await; } Err(error) => { self.handle_request_error(stream, ctx, &error).await; diff --git a/macros/src/context/struct.rs b/macros/src/context/struct.rs index d9354374..c04309ed 100644 --- a/macros/src/context/struct.rs +++ b/macros/src/context/struct.rs @@ -8,6 +8,6 @@ use super::*; pub(crate) struct ContextInput { /// The source context variable identifier. pub(crate) source_ctx: Ident, - /// The optional type annotation (e.g., `: &mut Context` or `: &Context`). + /// The optional type annotation. pub(crate) ty: Option, } diff --git a/macros/src/from_stream/impl.rs b/macros/src/from_stream/impl.rs index 87e09919..fa4d9c19 100644 --- a/macros/src/from_stream/impl.rs +++ b/macros/src/from_stream/impl.rs @@ -6,7 +6,7 @@ use super::*; /// It supports an optional variable name parameter. /// /// # Arguments -/// - `input`: The parse stream containing the token stream to be parsed +/// - `input` - The parse stream containing the token stream to be parsed /// /// # Returns /// Returns a `syn::Result` containing the parsed FromStreamData on success, @@ -24,7 +24,7 @@ impl Parse for FromStreamData { /// 2. No parameters: variable_name will be None /// /// # Arguments - /// - `ParseStream`: The ParseStream containing the token stream to be parsed + /// - `ParseStream` - The ParseStream containing the token stream to be parsed /// /// # Returns /// Returns `syn::Result` where: diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 5bb8a282..795315eb 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -52,8 +52,8 @@ use { /// /// # Arguments /// -/// - `TokenStream`: Optional variable name to store the read request data. -/// - `TokenStream`: The function item to be modified +/// - `TokenStream` - Optional variable name to store the read request data. +/// - `TokenStream` - The function item to be modified /// /// # Returns /// @@ -127,8 +127,8 @@ pub fn try_get_websocket_request(attr: TokenStream, item: TokenStream) -> TokenS /// /// # Arguments /// -/// - `TokenStream`: Optional variable name to store the read request data. -/// - `TokenStream`: The function item to be modified +/// - `TokenStream` - Optional variable name to store the read request data. +/// - `TokenStream` - The function item to be modified /// /// # Returns /// @@ -3269,7 +3269,7 @@ pub fn hyperlane(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// # Parameters /// -/// - `path`: String literal defining the route path +/// - `path` - String literal defining the route path /// /// # Dependencies /// diff --git a/request/src/common/enum.rs b/request/src/common/enum.rs index db51e953..2849e67b 100644 --- a/request/src/common/enum.rs +++ b/request/src/common/enum.rs @@ -10,7 +10,7 @@ use super::*; /// `Request` / `Response` design in `hyperlane-core` — see /// `hyperlane-standards §8.1`. Higher-level framing (json vs text vs binary) /// lives in the builder, not in the data type. -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Eq, Getter, PartialEq, Serialize)] pub struct Body { /// Raw body bytes. pub bytes: Vec, @@ -30,13 +30,22 @@ impl Body { } /// View body as `&[u8]`. - pub fn as_slice(&self) -> &[u8] { + /// + /// # Returns + /// + /// - `&[u8]` - The raw body bytes. + pub fn get_bytes_ref(&self) -> &[u8] { &self.bytes } + /// View body as `&[u8]`. + pub fn as_slice(&self) -> &[u8] { + self.get_bytes() + } + /// Try to view body as UTF-8 string. pub fn as_str(&self) -> Option<&str> { - std::str::from_utf8(&self.bytes).ok() + std::str::from_utf8(self.get_bytes()).ok() } } diff --git a/request/src/request/http_request/impl.rs b/request/src/request/http_request/impl.rs index e2aa5aa7..d662c364 100644 --- a/request/src/request/http_request/impl.rs +++ b/request/src/request/http_request/impl.rs @@ -165,7 +165,7 @@ impl HttpRequest { tcp.set_write_timeout(Some(timeout)) .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; if self.is_https() { - let roots = self.get_tmp_ref().root_cert_clone(); + let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); let tls_cfg = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); @@ -293,10 +293,10 @@ impl HttpRequest { if !self.get_config_ref().redirect { return Err(RequestError::Request("Redirect Not Enabled".to_string())); } - if self.get_tmp_ref().visit_url_ref().contains(&url) { + if self.get_tmp_ref().get_visit_url().contains(&url) { return Err(RequestError::Request("Redirect URL Dead Loop".to_string())); } - self.get_tmp_mut().visit_url_mut().insert(url.clone()); + self.get_tmp_mut().get_mut_visit_url().insert(url.clone()); if self.get_config_ref().max_redirect_times == 0 { return Err(RequestError::Request( "Max Redirect Times Exceeded".to_string(), @@ -356,7 +356,7 @@ impl HttpRequest { .await .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; if self.is_https() { - let roots = self.get_tmp_ref().root_cert_clone(); + let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); let tls_cfg = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); @@ -496,10 +496,10 @@ impl HttpRequest { if !self.get_config_ref().redirect { return Err(RequestError::Request("Redirect Not Enabled".to_string())); } - if self.get_tmp_ref().visit_url_ref().contains(&url) { + if self.get_tmp_ref().get_visit_url().contains(&url) { return Err(RequestError::Request("Redirect URL Dead Loop".to_string())); } - self.get_tmp_mut().visit_url_mut().insert(url.clone()); + self.get_tmp_mut().get_mut_visit_url().insert(url.clone()); if self.get_config_ref().max_redirect_times == 0 { return Err(RequestError::Request( "Max Redirect Times Exceeded".to_string(), @@ -540,7 +540,7 @@ impl HttpRequest { tcp.set_write_timeout(Some(timeout)) .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; let mut proxy_stream: BoxReadWrite = if proxy.proxy_type == ProxyType::Https { - let roots = self.get_tmp_ref().root_cert_clone(); + let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); let tls_cfg = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); @@ -589,7 +589,7 @@ impl HttpRequest { Vec::new() }; if self.is_https() { - let roots = self.get_tmp_ref().root_cert_clone(); + let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); let tls_cfg = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); @@ -694,7 +694,7 @@ impl HttpRequest { _ => return Err(RequestError::Request("Internal Server Error".to_string())), } if self.is_https() { - let roots = self.get_tmp_ref().root_cert_clone(); + let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); let tls_cfg = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); @@ -739,7 +739,7 @@ impl HttpRequest { .await .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; let mut proxy_stream: BoxAsyncReadWrite = if proxy.proxy_type == ProxyType::Https { - let roots = self.get_tmp_ref().root_cert_clone(); + let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); let tls_cfg = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); @@ -794,7 +794,7 @@ impl HttpRequest { Vec::new() }; if self.is_https() { - let roots = self.get_tmp_ref().root_cert_clone(); + let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); let tls_cfg = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); @@ -909,7 +909,7 @@ impl HttpRequest { _ => return Err(RequestError::Request("Internal Server Error".to_string())), } if self.is_https() { - let roots = self.get_tmp_ref().root_cert_clone(); + let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); let tls_cfg = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); diff --git a/request/src/request/http_request/struct.rs b/request/src/request/http_request/struct.rs index 57851d0d..fb64f9a5 100644 --- a/request/src/request/http_request/struct.rs +++ b/request/src/request/http_request/struct.rs @@ -17,20 +17,25 @@ pub type RequestResult = Result; /// `HttpRequest` is constructed either via [`crate::RequestBuilder`] or /// directly from its public fields, then sent via [`HttpRequest::send`] /// (sync) or [`HttpRequest::send_async`]. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, GetterMut)] pub struct HttpRequest { /// HTTP method (`Method::Get` / `Method::Post` / ...). + #[get_mut(skip)] pub method: Method, /// Target URL (string form, parsed on send). + #[get_mut(skip)] pub url: String, /// Request headers (single-value). pub headers: HashMap, /// Request body. + #[get_mut(skip)] pub body: Body, /// Per-request config: timeout, redirects, proxy, decode, etc. + #[get_mut(skip)] pub config: RequestConfig, /// Internal scratch: redirect-loop tracking + TLS root store. /// Private; only `HttpRequest` methods touch it. + #[get_mut(skip)] pub(crate) tmp: Tmp, } @@ -82,13 +87,13 @@ impl HttpRequest { /// Remove a header by key. pub fn remove_header>(&mut self, key: K) -> &mut Self { let normalized = Self::normalize_header_key(key.as_ref()); - self.headers.remove(&normalized); + self.get_mut_headers().remove(&normalized); self } /// Clear all headers. pub fn clear_headers(&mut self) -> &mut Self { - self.headers.clear(); + self.get_mut_headers().clear(); self } @@ -129,6 +134,15 @@ impl HttpRequest { &self.headers } + /// Returns a mutable reference to the headers map. + /// + /// # Returns + /// + /// - `&mut HashMap` - The mutable headers map. + pub fn get_headers_mut(&mut self) -> &mut HashMap { + &mut self.headers + } + /// Get a clone of the body. pub fn get_body(&self) -> Body { self.body.clone() diff --git a/request/src/request/proxy/impl.rs b/request/src/request/proxy/impl.rs index ffe3cb93..54709950 100644 --- a/request/src/request/proxy/impl.rs +++ b/request/src/request/proxy/impl.rs @@ -15,13 +15,13 @@ impl AsyncRead for ProxyTunnelStream { cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { - if !self.pre_read_data.is_empty() { - let len: usize = std::cmp::min(self.pre_read_data.len(), buf.remaining()); - buf.put_slice(&self.pre_read_data[..len]); - self.pre_read_data.drain(..len); + if !self.get_pre_read_data().is_empty() { + let len: usize = std::cmp::min(self.get_pre_read_data().len(), buf.remaining()); + buf.put_slice(&self.get_pre_read_data()[..len]); + self.get_mut_pre_read_data().drain(..len); return Poll::Ready(Ok(())); } - Pin::new(&mut self.inner).poll_read(cx, buf) + Pin::new(self.get_mut_inner()).poll_read(cx, buf) } } @@ -31,21 +31,21 @@ impl AsyncWrite for ProxyTunnelStream { cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - Pin::new(&mut self.inner).poll_write(cx, buf) + Pin::new(self.get_mut_inner()).poll_write(cx, buf) } fn poll_flush( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - Pin::new(&mut self.inner).poll_flush(cx) + Pin::new(self.get_mut_inner()).poll_flush(cx) } fn poll_shutdown( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - Pin::new(&mut self.inner).poll_shutdown(cx) + Pin::new(self.get_mut_inner()).poll_shutdown(cx) } } @@ -62,22 +62,22 @@ impl SyncProxyTunnelStream { impl Read for SyncProxyTunnelStream { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - if !self.pre_read_data.is_empty() { - let len: usize = std::cmp::min(self.pre_read_data.len(), buf.len()); - buf[..len].copy_from_slice(&self.pre_read_data[..len]); - self.pre_read_data.drain(..len); + if !self.get_pre_read_data().is_empty() { + let len: usize = std::cmp::min(self.get_pre_read_data().len(), buf.len()); + buf[..len].copy_from_slice(&self.get_pre_read_data()[..len]); + self.get_mut_pre_read_data().drain(..len); return Ok(len); } - self.inner.read(buf) + self.get_mut_inner().read(buf) } } impl Write for SyncProxyTunnelStream { fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.inner.write(buf) + self.get_mut_inner().write(buf) } fn flush(&mut self) -> std::io::Result<()> { - self.inner.flush() + self.get_mut_inner().flush() } } diff --git a/request/src/request/proxy/struct.rs b/request/src/request/proxy/struct.rs index 8450ee87..5c569bff 100644 --- a/request/src/request/proxy/struct.rs +++ b/request/src/request/proxy/struct.rs @@ -30,7 +30,7 @@ pub enum ProxyType { /// let p = Proxy::https("proxy.example.com", 7890); /// let auth = Proxy::socks5("127.0.0.1", 1080).auth("user", "pass"); /// ``` -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Data, Debug, Eq, PartialEq)] pub struct Proxy { /// Proxy protocol family. pub proxy_type: ProxyType, @@ -62,8 +62,8 @@ impl Proxy { /// Attach username / password to this proxy. pub fn auth, P: AsRef>(mut self, username: U, password: P) -> Self { - self.username = Some(username.as_ref().to_owned()); - self.password = Some(password.as_ref().to_owned()); + self.set_username(Some(username.as_ref().to_owned())); + self.set_password(Some(password.as_ref().to_owned())); self } @@ -80,14 +80,28 @@ impl Proxy { /// Async tunnel stream wrapping another async stream, with a buffer of /// pre-read bytes that are returned before delegating to the inner stream. +#[derive(Data)] pub struct ProxyTunnelStream { + #[get(pub(crate))] + #[get_mut(pub(crate))] + #[set(pub(crate))] pub(super) inner: BoxAsyncReadWrite, + #[get(pub(crate))] + #[get_mut(pub(crate))] + #[set(pub(crate))] pub(super) pre_read_data: Vec, } /// Sync tunnel stream wrapping another sync stream, with a buffer of /// pre-read bytes that are returned before delegating to the inner stream. +#[derive(Data)] pub struct SyncProxyTunnelStream { + #[get(pub(crate))] + #[get_mut(pub(crate))] + #[set(pub(crate))] pub(super) inner: BoxReadWrite, + #[get(pub(crate))] + #[get_mut(pub(crate))] + #[set(pub(crate))] pub(super) pre_read_data: Vec, } diff --git a/request/src/request/request_builder/struct.rs b/request/src/request/request_builder/struct.rs index 26ba2812..731071d6 100644 --- a/request/src/request/request_builder/struct.rs +++ b/request/src/request/request_builder/struct.rs @@ -28,12 +28,24 @@ use super::*; /// .build(); /// let _resp = req.send_async().await; /// ``` -#[derive(Clone, Debug, Default)] +#[derive(Clone, Data, Debug, Default)] pub struct RequestBuilder { + #[get(pub(crate))] + #[get_mut(pub(crate))] + #[set(pub(crate))] request: HttpRequest, } impl RequestBuilder { + /// Returns the underlying request mutably. + /// + /// # Returns + /// + /// - `&mut HttpRequest` - The mutable request being built. + pub fn get_request_mut(&mut self) -> &mut HttpRequest { + &mut self.request + } + /// Create an empty builder (defaults: GET, no headers, default config). pub fn new() -> Self { Self::default() @@ -41,33 +53,33 @@ impl RequestBuilder { /// Shortcut for `method(Method::Get)` + `url(url)`. pub fn get(&mut self, url: impl Into) -> &mut Self { - self.request.set_method(Method::Get); - self.request.set_url(url); + self.get_mut_request().set_method(Method::Get); + self.get_mut_request().set_url(url); self } /// Shortcut for `method(Method::Post)` + `url(url)`. pub fn post(&mut self, url: impl Into) -> &mut Self { - self.request.set_method(Method::Post); - self.request.set_url(url); + self.get_mut_request().set_method(Method::Post); + self.get_mut_request().set_url(url); self } /// Set HTTP method explicitly (`Method::Get` / `Method::Post` / etc.). pub fn method(&mut self, method: Method) -> &mut Self { - self.request.set_method(method); + self.get_mut_request().set_method(method); self } /// Set URL. pub fn url(&mut self, url: impl Into) -> &mut Self { - self.request.set_url(url); + self.get_mut_request().set_url(url); self } /// Set a single header (last write wins on duplicate keys). pub fn header, V: AsRef>(&mut self, key: K, value: V) -> &mut Self { - self.request.set_header(key, value); + self.get_mut_request().set_header(key, value); self } @@ -78,32 +90,32 @@ impl RequestBuilder { V: AsRef, { for (k, v) in headers { - self.request.set_header(k, v); + self.get_mut_request().set_header(k, v); } self } /// Remove a header by key. pub fn remove_header>(&mut self, key: K) -> &mut Self { - self.request.remove_header(key); + self.get_mut_request().remove_header(key); self } /// Clear all headers. pub fn clear_headers(&mut self) -> &mut Self { - self.request.clear_headers(); + self.get_mut_request().clear_headers(); self } /// Set raw body bytes. pub fn body>>(&mut self, bytes: B) -> &mut Self { - self.request.set_body(Body::from_bytes(bytes)); + self.get_mut_request().set_body(Body::from_bytes(bytes)); self } /// Set UTF-8 text body (will be encoded per `Content-Type` on send). pub fn body_text>(&mut self, text: T) -> &mut Self { - self.request + self.get_mut_request() .set_body(Body::from_bytes(text.into().into_bytes())); self } @@ -111,62 +123,68 @@ impl RequestBuilder { /// Set JSON body (serialised via `serde_json`). pub fn body_json(&mut self, value: &V) -> &mut Self { if let Ok(bytes) = serde_json::to_vec(value) { - self.request.set_body(Body::from_bytes(bytes)); + self.get_mut_request().set_body(Body::from_bytes(bytes)); } self } /// Set request timeout in milliseconds. pub fn timeout(&mut self, ms: u64) -> &mut Self { - self.request.get_config_mut().set_timeout(ms); + self.get_mut_request().get_config_mut().set_timeout(ms); self } /// Set per-read buffer size. pub fn buffer_size(&mut self, n: usize) -> &mut Self { - self.request.get_config_mut().set_buffer_size(n); + self.get_mut_request().get_config_mut().set_buffer_size(n); self } /// Force HTTP/1.1. pub fn http1_1_only(&mut self) -> &mut Self { - self.request.get_config_mut().http_version = HttpVersion::Http1_1; + self.get_mut_request() + .get_config_mut() + .set_http_version(HttpVersion::Http1_1); self } /// Force HTTP/2. pub fn http2_only(&mut self) -> &mut Self { - self.request.get_config_mut().http_version = HttpVersion::Http2; + self.get_mut_request() + .get_config_mut() + .set_http_version(HttpVersion::Http2); self } /// Enable auto-follow of 3xx redirects. pub fn redirect(&mut self) -> &mut Self { - self.request.get_config_mut().set_redirect(true); + self.get_mut_request().get_config_mut().set_redirect(true); self } /// Disable auto-follow of 3xx redirects (default). pub fn no_redirect(&mut self) -> &mut Self { - self.request.get_config_mut().set_redirect(false); + self.get_mut_request().get_config_mut().set_redirect(false); self } /// Maximum number of redirects to follow (default `DEFAULT_MAX_REDIRECT_TIMES`). pub fn max_redirect_times(&mut self, n: usize) -> &mut Self { - self.request.get_config_mut().set_max_redirect_times(n); + self.get_mut_request() + .get_config_mut() + .set_max_redirect_times(n); self } /// Enable automatic response body decompression (gzip / deflate / br). pub fn decode(&mut self) -> &mut Self { - self.request.get_config_mut().set_decode(true); + self.get_mut_request().get_config_mut().set_decode(true); self } /// Disable automatic response body decompression. pub fn no_decode(&mut self) -> &mut Self { - self.request.get_config_mut().set_decode(false); + self.get_mut_request().get_config_mut().set_decode(false); self } @@ -176,18 +194,20 @@ impl RequestBuilder { /// Construct via [`Proxy::http`] / [`Proxy::https`] / [`Proxy::socks5`] /// and optionally chain `.auth(user, pass)`. pub fn proxy(&mut self, proxy: Proxy) -> &mut Self { - self.request.get_config_mut().set_proxy(Some(proxy)); + self.get_mut_request() + .get_config_mut() + .set_proxy(Some(proxy)); self } /// Clear the proxy (use direct connection). pub fn no_proxy(&mut self) -> &mut Self { - self.request.get_config_mut().set_proxy(None); + self.get_mut_request().get_config_mut().set_proxy(None); self } /// Finalise the builder and return the [`HttpRequest`]. pub fn build(&mut self) -> HttpRequest { - std::mem::take(&mut self.request) + std::mem::take(self.get_mut_request()) } } diff --git a/request/src/request/tmp/struct.rs b/request/src/request/tmp/struct.rs index b098cffa..b9e4b708 100644 --- a/request/src/request/tmp/struct.rs +++ b/request/src/request/tmp/struct.rs @@ -7,29 +7,18 @@ use super::*; /// implementation detail of the client state machine. /// /// Fields are private; access through the methods on [`HttpRequest`]. -#[derive(Clone, Debug)] +#[derive(Clone, Data, Debug)] pub struct Tmp { + #[get(pub(crate))] + #[get_mut(pub(crate))] + #[set(pub(crate))] pub(crate) visit_url: HashSet, + #[get(pub(crate))] + #[get_mut(pub(crate))] + #[set(pub(crate))] pub(crate) root_cert: RootCertStore, } -impl Tmp { - /// Borrow `visit_url` set. - pub(crate) fn visit_url_ref(&self) -> &HashSet { - &self.visit_url - } - - /// Mutable borrow of `visit_url` set. - pub(crate) fn visit_url_mut(&mut self) -> &mut HashSet { - &mut self.visit_url - } - - /// Clone the `root_cert` store. - pub(crate) fn root_cert_clone(&self) -> RootCertStore { - self.root_cert.clone() - } -} - impl Default for Tmp { #[inline(always)] fn default() -> Self { diff --git a/request/src/response/struct.rs b/request/src/response/struct.rs index e126e845..1df70009 100644 --- a/request/src/response/struct.rs +++ b/request/src/response/struct.rs @@ -145,12 +145,12 @@ impl HttpResponse { /// Was the response a 2xx success? pub fn is_success(&self) -> bool { - (200..300).contains(&self.status_code) + (200..300).contains(&self.get_status_code()) } /// Was the response a 3xx redirect? pub fn is_redirect(&self) -> bool { - (300..400).contains(&self.status_code) + (300..400).contains(&self.get_status_code()) } /// Look up a single header value (case-insensitive). @@ -161,27 +161,27 @@ impl HttpResponse { /// Get the response body as a UTF-8 string (lossy on invalid bytes). pub fn text(&self) -> String { - String::from_utf8_lossy(&self.body).into_owned() + String::from_utf8_lossy(self.get_body()).into_owned() } /// Get the response body as raw bytes. pub fn bytes(&self) -> &[u8] { - &self.body + self.get_body() } /// Decode the body using the headers' Content-Encoding (gzip / deflate / /// br). Returns a fresh response with the decoded body — the original is /// untouched. `buffer_size` controls the chunk size for streaming decoders. pub fn decode(&self, buffer_size: usize) -> HttpResponse { - let flat_headers: HttpResponseHeaders = self.headers.clone(); + let flat_headers: HttpResponseHeaders = self.get_headers().clone(); let decoded: ResponseBody = Compress::from(&flat_headers) - .decode(&self.body, buffer_size) + .decode(self.get_body(), buffer_size) .into_owned(); HttpResponse { - version: self.version.clone(), - status_code: self.status_code, - reason_phrase: self.reason_phrase.clone(), - headers: self.headers.clone(), + version: self.get_version().clone(), + status_code: self.get_status_code(), + reason_phrase: self.get_reason_phrase().clone(), + headers: self.get_headers().clone(), body: decoded, } } diff --git a/type/src/cookie/impl.rs b/type/src/cookie/impl.rs index 3486e66b..2d0adfc1 100644 --- a/type/src/cookie/impl.rs +++ b/type/src/cookie/impl.rs @@ -187,7 +187,7 @@ impl CookieBuilder { /// The `CookieBuilder` instance for method chaining. #[inline(always)] pub fn secure(&mut self) -> &mut Self { - self.secure = Some(true); + *self.get_mut_secure() = Some(true); self } @@ -200,7 +200,7 @@ impl CookieBuilder { /// The `CookieBuilder` instance for method chaining. #[inline(always)] pub fn http_only(&mut self) -> &mut Self { - self.http_only = Some(true); + *self.get_mut_http_only() = Some(true); self } @@ -214,7 +214,7 @@ impl CookieBuilder { /// The `CookieBuilder` instance for method chaining. #[inline(always)] pub fn disable_secure(&mut self) -> &mut Self { - self.secure = Some(false); + *self.get_mut_secure() = Some(false); self } @@ -228,7 +228,7 @@ impl CookieBuilder { /// The `CookieBuilder` instance for method chaining. #[inline(always)] pub fn disable_http_only(&mut self) -> &mut Self { - self.http_only = Some(false); + *self.get_mut_http_only() = Some(false); self } diff --git a/type/src/http_url/impl.rs b/type/src/http_url/impl.rs index 790bac75..5359db94 100644 --- a/type/src/http_url/impl.rs +++ b/type/src/http_url/impl.rs @@ -31,11 +31,11 @@ impl From for HttpUrlError { /// /// # Arguments /// - /// - `ParseError`: The URL parse error to convert. + /// - `ParseError` - The URL parse error to convert. /// /// # Returns /// - /// - `HttpUrlError`: The corresponding error as `InvalidUrl`. + /// - `HttpUrlError` - The corresponding error as `InvalidUrl`. #[inline(always)] fn from(_: ParseError) -> Self { HttpUrlError::InvalidUrl diff --git a/type/src/lib.rs b/type/src/lib.rs index 79b199c0..b326f854 100644 --- a/type/src/lib.rs +++ b/type/src/lib.rs @@ -40,10 +40,12 @@ pub use {http_compress::*, http_constant::*, serde_json, tokio}; use std::{ any::Any, + cell::{RefCell, RefMut}, collections::{HashMap, HashSet, VecDeque}, - fmt::{self, Debug, Display, Formatter}, + fmt::{self, Debug, Display, Formatter, Write}, hash::Hash, - io::ErrorKind, + io::{self, ErrorKind}, + mem, net::IpAddr, num::ParseIntError, pin::Pin, @@ -54,6 +56,8 @@ use std::{ Arc, atomic::{self, AtomicBool, AtomicUsize}, }, + task::{Context, Poll}, + thread_local, time::Duration, }; @@ -62,7 +66,7 @@ use { lombok_macros::*, serde::{Deserialize, Serialize, de::DeserializeOwned}, tokio::{ - io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}, + io::{AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWriteExt, ReadBuf}, net::TcpStream, runtime::Handle, sync::{ diff --git a/type/src/lifetime/trait.rs b/type/src/lifetime/trait.rs index 24818a9e..49cfab78 100644 --- a/type/src/lifetime/trait.rs +++ b/type/src/lifetime/trait.rs @@ -9,7 +9,7 @@ pub trait Lifetime { /// /// # Returns /// - /// - `&'static Self`: A reference to the instance with a `'static` lifetime. + /// - `&'static Self` - A reference to the instance with a `'static` lifetime. /// /// # Safety /// @@ -21,7 +21,7 @@ pub trait Lifetime { /// /// # Returns /// - /// - `&'static mut Self`: A mutable reference to the instance with a `'static` lifetime. + /// - `&'static mut Self` - A mutable reference to the instance with a `'static` lifetime. /// /// # Safety /// diff --git a/type/src/protocol/impl.rs b/type/src/protocol/impl.rs index 4fa13d5b..4d42ba9d 100644 --- a/type/src/protocol/impl.rs +++ b/type/src/protocol/impl.rs @@ -11,10 +11,10 @@ impl Protocol { /// Performs a case-insensitive comparison against the HTTP protocol identifier. /// /// # Arguments - /// - `&str`: A string slice representing the protocol to check. + /// - `&str` - A string slice representing the protocol to check. /// /// # Returns - /// - `bool`: Returns `true` if the protocol is HTTP (case-insensitive), `false` otherwise. + /// - `bool` - Returns `true` if the protocol is HTTP (case-insensitive), `false` otherwise. #[inline(always)] pub fn is_http(protocol: &str) -> bool { matches!(protocol.to_lowercase().as_str(), HTTP_LOWERCASE) @@ -25,10 +25,10 @@ impl Protocol { /// Performs a case-insensitive comparison against the HTTPS protocol identifier. /// /// # Arguments - /// - `&str`: A string slice representing the protocol to check. + /// - `&str` - A string slice representing the protocol to check. /// /// # Returns - /// - `bool`: Returns `true` if the protocol is HTTPS (case-insensitive), `false` otherwise. + /// - `bool` - Returns `true` if the protocol is HTTPS (case-insensitive), `false` otherwise. #[inline(always)] pub fn is_https(protocol: &str) -> bool { matches!(protocol.to_lowercase().as_str(), HTTPS_LOWERCASE) @@ -40,10 +40,10 @@ impl Protocol { /// and returns the corresponding standard port number. /// /// # Arguments - /// - `&str`: A string slice representing the protocol to lookup. + /// - `&str` - A string slice representing the protocol to lookup. /// /// # Returns - /// - `u16`: The default port number for the protocol. + /// - `u16` - The default port number for the protocol. #[inline(always)] pub fn get_port(protocol: &str) -> u16 { match protocol.to_lowercase().as_str() { diff --git a/type/src/request/impl.rs b/type/src/request/impl.rs index 062f6605..03c49139 100644 --- a/type/src/request/impl.rs +++ b/type/src/request/impl.rs @@ -23,11 +23,11 @@ impl From for RequestError { /// /// # Arguments /// - /// - `std::io::Error`: The I/O error to convert. + /// - `std::io::Error` - The I/O error to convert. /// /// # Returns /// - /// - `RequestError`: The corresponding request error. + /// - `RequestError` - The corresponding request error. #[inline(always)] fn from(error: std::io::Error) -> Self { let kind: ErrorKind = error.kind(); @@ -46,11 +46,11 @@ impl From for RequestError { /// /// # Arguments /// - /// - `Elapsed`: The elapsed error to convert. + /// - `Elapsed` - The elapsed error to convert. /// /// # Returns /// - /// - `RequestError`: The corresponding request error as `ReadTimeout`. + /// - `RequestError` - The corresponding request error as `ReadTimeout`. #[inline(always)] fn from(_: Elapsed) -> Self { RequestError::ReadTimeout(HttpStatus::RequestTimeout) @@ -65,11 +65,11 @@ impl From for RequestError { /// /// # Arguments /// - /// - `ParseIntError`: The parse error to convert. + /// - `ParseIntError` - The parse error to convert. /// /// # Returns /// - /// - `RequestError`: The corresponding request error as `InvalidContentLength`. + /// - `RequestError` - The corresponding request error as `InvalidContentLength`. #[inline(always)] fn from(_: ParseIntError) -> Self { RequestError::InvalidContentLength(HttpStatus::BadRequest) @@ -84,11 +84,11 @@ impl From for RequestError { /// /// # Arguments /// - /// - `ResponseError`: The response error to convert. + /// - `ResponseError` - The response error to convert. /// /// # Returns /// - /// - `RequestError`: The corresponding request error as `WriteTimeout`. + /// - `RequestError` - The corresponding request error as `WriteTimeout`. #[inline(always)] fn from(_: ResponseError) -> Self { RequestError::WriteTimeout(HttpStatus::InternalServerError) @@ -274,15 +274,34 @@ impl Default for Request { } impl Request { + /// Resets the request to its default state while retaining allocated capacity. + /// + /// This keeps the header map, query map, body, and string allocations so + /// persistent (keep-alive) connections avoid repeated allocation per request. + /// + /// # Returns + /// + /// - `&mut Self` - A mutable reference to self for chaining. + pub fn reset(&mut self) -> &mut Self { + self.set_method(Method::default()); + self.get_mut_host().clear(); + self.set_version(HttpVersion::default()); + self.get_mut_path().clear(); + self.get_mut_querys().clear(); + self.get_mut_headers().clear(); + self.get_mut_body().clear(); + self + } + /// Parses the first line of HTTP request into method, path, and version components. /// /// # Arguments /// - /// - `&str`: The first line string of HTTP request to parse. + /// - `&str` - The first line string of HTTP request to parse. /// /// # Returns /// - /// - `Result<(RequestMethod, &str, RequestVersion), RequestError>`: A tuple containing: + /// - `Result<(RequestMethod, &str, RequestVersion), RequestError>` - A tuple containing: /// - The parsed HTTP method /// - The full path string /// - The parsed HTTP version @@ -320,12 +339,12 @@ impl Request { /// /// # Arguments /// - /// - `&str`: The path string to check. - /// - `usize`: The maximum allowed path size. + /// - `&str` - The path string to check. + /// - `usize` - The maximum allowed path size. /// /// # Returns /// - /// - `Result<(), RequestError>`: Ok if valid, or an error if the path is too long. + /// - `Result<(), RequestError>` - Ok if valid, or an error if the path is too long. #[inline(always)] pub(crate) fn check_http_path_size(path: &str, max_size: usize) -> Result<(), RequestError> { if path.len() > max_size && max_size != DEFAULT_LOW_SECURITY_MAX_PATH_SIZE { @@ -341,13 +360,13 @@ impl Request { /// /// # Arguments /// - /// - `&str`: The full path string containing the query. - /// - `Option`: The index of the query separator (`?`), if present. - /// - `Option`: The index of the hash separator (`#`), if present. + /// - `&str` - The full path string containing the query. + /// - `Option` - The index of the query separator (`?`), if present. + /// - `Option` - The index of the hash separator (`#`), if present. /// /// # Returns /// - /// - `&str`: The parsed query string slice, or empty string if no query. + /// - `&str` - The parsed query string slice, or empty string if no query. #[inline(always)] pub(crate) fn get_http_query( path: &str, @@ -368,65 +387,61 @@ impl Request { /// /// # Arguments /// - /// - `&str`: The full path string. - /// - `Option`: The index of the query separator (`?`), if present. - /// - `Option`: The index of the hash separator (`#`), if present. + /// - `&str` - The full path string. + /// - `Option` - The index of the query separator (`?`), if present. + /// - `Option` - The index of the hash separator (`#`), if present. /// /// # Returns /// - /// - `RequestPath`: The request path without query or hash. + /// - `&str` - The request path slice without query or hash. #[inline(always)] pub(crate) fn get_http_path( path: &str, query_index: Option, hash_index: Option, - ) -> RequestPath { + ) -> &str { match query_index.or(hash_index) { - Some(separator_index) => path[..separator_index].to_owned(), - None => path.to_owned(), + Some(separator_index) => &path[..separator_index], + None => path, } } - /// Parses a query string as_ref key-value pairs. + /// Parses a query string as_ref key-value pairs into the given map. /// /// Expects format "key1=value1&key2=value2". Empty values are allowed. + /// The target map is expected to be empty; entries are inserted without + /// reallocating the map when it already has sufficient capacity. /// /// # Arguments /// /// - `&str` - The query string to parse. - /// - /// # Returns - /// - /// - `RequestQuerys` - The parsed query parameters. + /// - `&mut RequestQuerys` - The map to insert parsed parameters into. #[inline(always)] - pub(crate) fn get_http_querys(query: &str) -> RequestQuerys { - let estimated_capacity: usize = query.matches(AND).count() + 1; - let mut query_map: RequestQuerys = HashMapXxHash3_64::with_capacity_and_hasher( - estimated_capacity, - BuildHasherDefault::default(), - ); + pub(crate) fn fill_http_querys(query: &str, querys: &mut RequestQuerys) { + if query.is_empty() { + return; + } for pair in query.split(AND) { if let Some((key, value)) = pair.split_once(EQUAL) { if !key.is_empty() { - query_map.insert(key.to_string(), value.to_string()); + querys.insert(key.to_string(), value.to_string()); } } else if !pair.is_empty() { - query_map.insert(pair.to_string(), String::new()); + querys.insert(pair.to_string(), String::new()); } } - query_map } /// Checks if the header count exceeds the maximum allowed. /// /// # Arguments /// - /// - `usize`: The current number of headers parsed. - /// - `usize`: The maximum allowed number of headers. + /// - `usize` - The current number of headers parsed. + /// - `usize` - The maximum allowed number of headers. /// /// # Returns /// - /// - `Result<(), RequestError>`: Returns an error if the limit is exceeded and not in low security mode. + /// - `Result<(), RequestError>` - Returns an error if the limit is exceeded and not in low security mode. #[inline(always)] pub(crate) fn check_http_header_count( count: usize, @@ -444,12 +459,12 @@ impl Request { /// /// # Arguments /// - /// - `&str`: The header key to check. - /// - `usize`: The maximum allowed length for a header key. + /// - `&str` - The header key to check. + /// - `usize` - The maximum allowed length for a header key. /// /// # Returns /// - /// - `Result<(), RequestError>`: Returns an error if the limit is exceeded and not in low security mode. + /// - `Result<(), RequestError>` - Returns an error if the limit is exceeded and not in low security mode. #[inline(always)] pub(crate) fn check_http_header_key_size( key: &str, @@ -467,12 +482,12 @@ impl Request { /// /// # Arguments /// - /// - `&str`: The header value to check. - /// - `usize`: The maximum allowed length for a header value. + /// - `&str` - The header value to check. + /// - `usize` - The maximum allowed length for a header value. /// /// # Returns /// - /// - `Result<(), RequestError>`: Returns an error if the limit is exceeded and not in low security mode. + /// - `Result<(), RequestError>` - Returns an error if the limit is exceeded and not in low security mode. #[inline(always)] pub(crate) fn check_http_header_value_size( value: &str, @@ -490,12 +505,12 @@ impl Request { /// /// # Arguments /// - /// - `&str`: The Content-Length header value string. - /// - `usize`: The maximum allowed body size. + /// - `&str` - The Content-Length header value string. + /// - `usize` - The maximum allowed body size. /// /// # Returns /// - /// - `Result`: The parsed content length or an error. + /// - `Result` - The parsed content length or an error. #[inline(always)] pub(crate) fn check_http_body_size( value: &str, @@ -510,42 +525,39 @@ impl Request { Ok(length) } - /// Parses HTTP headers from a buffered reader. + /// Parses HTTP headers from a buffered reader into this request. /// /// This method reads header lines from the provided buffered reader until an empty line /// is encountered, which indicates the end of headers. It checks header count, length, - /// and content according to the provided configuration. + /// and content according to the provided configuration. The request's headers map and + /// host string are expected to be empty; they are filled without reallocating when they + /// already have sufficient capacity. /// /// # Arguments /// - /// - `&mut AsyncBufReadExt + Unpin`: A mutable reference to a buffered reader implementing `AsyncBufReadExt`. - /// - `&RequestConfig`: Configuration for security limits and buffer settings. + /// - `&mut AsyncBufReadExt + Unpin` - A mutable reference to a buffered reader implementing `AsyncBufReadExt`. + /// - `&RequestConfig` - Configuration for security limits and buffer settings. /// /// # Returns /// - /// - `Result<(RequestHeaders, RequestHost, usize), RequestError>`: A tuple containing: - /// - The parsed headers as a hash map - /// - The host value parsed from the Host header - /// - The content length parsed from the Content-Length header - /// - Or an error if parsing fails + /// - `Result` - The content length parsed from the + /// Content-Length header, or an error if parsing fails. pub(crate) async fn get_http_headers( + &mut self, reader: &mut R, config: &RequestConfig, - ) -> Result<(RequestHeaders, RequestHost, usize), RequestError> + ) -> Result where R: AsyncBufReadExt + Unpin, { - let buffer_size: usize = config.get_buffer_size(); + let Request { headers, host, .. } = self; let max_header_count: usize = config.get_max_header_count(); let max_header_key_size: usize = config.get_max_header_key_size(); let max_header_value_size: usize = config.get_max_header_value_size(); let max_body_size: usize = config.get_max_body_size(); - let mut headers: RequestHeaders = - HashMapXxHash3_64::with_capacity_and_hasher(B_16, BuildHasherDefault::default()); - let mut host: RequestHost = String::new(); let mut content_size: usize = 0; let mut header_count: usize = 0; - let mut header_line_buffer: String = String::with_capacity(buffer_size); + let mut header_line_buffer: String = String::with_capacity(HEADER_LINE_BUFFER_CAPACITY); loop { header_line_buffer.clear(); AsyncBufReadExt::read_line(reader, &mut header_line_buffer).await?; @@ -565,41 +577,52 @@ impl Request { } let key: String = key_trimmed.to_ascii_lowercase(); Self::check_http_header_key_size(&key, max_header_key_size)?; - let value: String = value_part.trim().to_string(); - Self::check_http_header_value_size(&value, max_header_value_size)?; + let value: &str = value_part.trim(); + Self::check_http_header_value_size(value, max_header_value_size)?; match key.as_str() { - HOST => host = value.clone(), + HOST => { + host.clear(); + host.push_str(value); + } CONTENT_LENGTH => { - content_size = Self::check_http_body_size(&value, max_body_size)?; + content_size = Self::check_http_body_size(value, max_body_size)?; } _ => {} } - headers.entry(key).or_default().push_back(value); + headers.entry(key).or_default().push_back(value.to_string()); } - Ok((headers, host, content_size)) + Ok(content_size) } - /// Reads the request body from the buffered reader. + /// Reads the request body from the buffered reader into the given buffer. + /// + /// The target buffer is cleared and resized to the expected content size, + /// retaining its allocation when it already has sufficient capacity. /// /// # Arguments /// - /// - `&mut BufReader<&mut TcpStream>`: The buffered reader to read from. - /// - `usize`: The expected content size. + /// - `&mut AsyncRead + Unpin` - The buffered reader to read from. + /// - `&mut RequestBody` - The buffer to read the body bytes into. + /// - `usize` - The expected content size. /// /// # Returns /// - /// - `Result`: The body bytes or an error. + /// - `Result<(), RequestError>` - Ok on success, or an error if reading fails. #[inline(always)] - pub(crate) async fn get_http_body( - reader: &mut BufReader<&mut TcpStream>, + pub(crate) async fn fill_http_body( + reader: &mut R, + body: &mut RequestBody, content_size: usize, - ) -> Result { - let mut body: RequestBody = Vec::with_capacity(content_size); + ) -> Result<(), RequestError> + where + R: AsyncRead + Unpin, + { + body.clear(); if content_size > 0 { body.resize(content_size, 0); - AsyncReadExt::read_exact(reader, &mut body).await?; + AsyncReadExt::read_exact(reader, body).await?; } - Ok(body) + Ok(()) } /// Tries to get a query parameter value by key. @@ -839,7 +862,7 @@ impl Request { where K: AsRef, { - self.headers.contains_key(key.as_ref()) + self.get_headers().contains_key(key.as_ref()) } /// Checks if a header contains a specific value. @@ -858,7 +881,7 @@ impl Request { K: AsRef, V: AsRef, { - if let Some(values) = self.headers.get(key.as_ref()) { + if let Some(values) = self.get_headers().get(key.as_ref()) { values.iter().any(|data: &String| data == value.as_ref()) } else { false diff --git a/type/src/response/impl.rs b/type/src/response/impl.rs index 80541bbd..bb0320d4 100644 --- a/type/src/response/impl.rs +++ b/type/src/response/impl.rs @@ -12,11 +12,11 @@ impl From for ResponseError { /// /// # Arguments /// - /// - `std::io::Error`: The I/O error to convert. + /// - `std::io::Error` - The I/O error to convert. /// /// # Returns /// - /// - `ResponseError`: The corresponding response error as `Send`. + /// - `ResponseError` - The corresponding response error as `Send`. #[inline(always)] fn from(error: std::io::Error) -> Self { ResponseError::Send(error.to_string()) @@ -30,7 +30,7 @@ impl Display for ResponseError { /// /// # Arguments /// - /// - `f`: A mutable reference to a `Formatter` used for writing the formatted string. + /// - `f` - A mutable reference to a `Formatter` used for writing the formatted string. /// /// # Returns /// @@ -76,9 +76,9 @@ impl Response { /// /// # Arguments /// - /// - `&mut String`: A mutable reference to the string where the header will be added. - /// - `&str`: The header key as a string slice (`&str`). - /// - `&str`: The header value as a string slice (`&str`). + /// - `&mut String` - A mutable reference to the string where the header will be added. + /// - `&str` - The header key as a string slice (`&str`). + /// - `&str` - The header value as a string slice (`&str`). #[inline(always)] fn push_header(response_string: &mut String, key: &str, value: &str) { response_string.push_str(key); @@ -87,22 +87,6 @@ impl Response { response_string.push_str(HTTP_BR); } - /// Pushes the first line of an HTTP response (version, status code, and reason phrase) as_ref the response string. - /// This corresponds to the status line of the HTTP response. - /// - /// # Arguments - /// - /// - `&mut String`: A mutable reference to the string where the first line will be added. - #[inline(always)] - fn push_http_first_line(&self, response_string: &mut String) { - response_string.push_str(&self.get_version().to_string()); - response_string.push_str(SPACE); - response_string.push_str(&self.get_status_code().to_string()); - response_string.push_str(SPACE); - response_string.push_str(self.get_reason_phrase()); - response_string.push_str(HTTP_BR); - } - /// Tries to retrieve the value of a response header by its key. /// /// # Arguments @@ -235,7 +219,7 @@ impl Response { where K: AsRef, { - self.headers.contains_key(key.as_ref()) + self.get_headers().contains_key(key.as_ref()) } /// Checks if a header contains a specific value. @@ -254,7 +238,7 @@ impl Response { K: AsRef, V: AsRef, { - if let Some(values) = self.headers.get(key.as_ref()) { + if let Some(values) = self.get_headers().get(key.as_ref()) { values.contains(&value.as_ref().to_owned()) } else { false @@ -466,7 +450,7 @@ impl Response { if self.should_skip_header(&key) { return self; } - self.headers + self.get_mut_headers() .entry(key) .or_default() .push_back(value.as_ref().to_owned()); @@ -489,7 +473,7 @@ impl Response { where K: AsRef, { - let _: bool = self.headers.remove(key.as_ref()).is_some(); + let _: bool = self.get_mut_headers().remove(key.as_ref()).is_some(); self } @@ -514,10 +498,10 @@ impl Response { V: AsRef, { let key: ResponseHeadersKey = key.as_ref().to_owned(); - if let Some(values) = self.headers.get_mut(&key) { + if let Some(values) = self.get_mut_headers().get_mut(&key) { values.retain(|data: &String| data != &value.as_ref().to_owned()); if values.is_empty() { - self.headers.remove(&key); + self.get_mut_headers().remove(&key); } } self @@ -532,7 +516,25 @@ impl Response { /// - `&mut Self` - A mutable reference to self for chaining. #[inline(always)] pub fn clear_headers(&mut self) -> &mut Self { - self.headers.clear(); + self.get_mut_headers().clear(); + self + } + + /// Resets the response to its default state while retaining allocated capacity. + /// + /// This keeps the header map and body allocations so persistent + /// (keep-alive) connections avoid repeated allocation per request. + /// + /// # Returns + /// + /// - `&mut Self` - A mutable reference to self for chaining. + pub fn reset(&mut self) -> &mut Self { + let http_status: HttpStatus = HttpStatus::default(); + self.set_status_code(http_status.code()); + self.get_mut_reason_phrase().clear(); + let _: fmt::Result = write!(self.get_mut_reason_phrase(), "{}", http_status); + self.get_mut_headers().clear(); + self.get_mut_body().clear(); self } @@ -623,11 +625,9 @@ impl Response { /// /// - `ResponseData` - The complete HTTP response bytes. pub fn build(&mut self) -> ResponseData { - if self.reason_phrase.is_empty() { + if self.get_reason_phrase().is_empty() { self.set_reason_phrase(HttpStatus::phrase(self.get_status_code())); } - let mut response_string: String = String::with_capacity(DEFAULT_BUFFER_SIZE); - self.push_http_first_line(&mut response_string); let compress_type_opt: Option = self .try_get_header_back(CONTENT_ENCODING) .map(|data: String| data.parse::().unwrap_or_default()); @@ -645,17 +645,42 @@ impl Response { self.set_header_without_check(CONTENT_TYPE, &content_type); content_type }); - let mut body: ResponseBody = self.get_body().clone(); - if let Some(compress_type) = compress_type_opt - && !compress_type.is_unknown() - { - body = compress_type - .encode(&body, DEFAULT_BUFFER_SIZE) - .into_owned(); - } + let compressed_body: Option> = match compress_type_opt { + Some(compress_type) if !compress_type.is_unknown() => Some( + compress_type + .encode(self.get_body(), DEFAULT_BUFFER_SIZE) + .into_owned(), + ), + _ => None, + }; + let body_len: usize = compressed_body + .as_ref() + .map_or_else(|| self.get_body().len(), Vec::len); if !content_type.eq_ignore_ascii_case(TEXT_EVENT_STREAM) { - self.set_header_without_check(CONTENT_LENGTH, body.len().to_string()); + self.set_header_without_check(CONTENT_LENGTH, body_len.to_string()); } + let mut head_size: usize = self.get_reason_phrase().len() + B_16 + HTTP_BR.len(); + head_size += self + .get_headers() + .iter() + .map(|header_entry: (&String, &VecDeque)| { + let (header_key, header_values): (&String, &VecDeque) = header_entry; + header_values + .iter() + .map(|header_value: &String| { + header_key.len() + COLON.len() + header_value.len() + HTTP_BR.len() + }) + .sum::() + }) + .sum::(); + let mut response_string: String = String::with_capacity(head_size + body_len); + let _: fmt::Result = write!( + response_string, + "{} {} {}{HTTP_BR}", + self.get_version(), + self.get_status_code(), + self.get_reason_phrase() + ); self.get_headers() .iter() .for_each(|header_entry: (&String, &VecDeque)| { @@ -666,7 +691,10 @@ impl Response { }); response_string.push_str(HTTP_BR); let mut response_bytes: Vec = response_string.into_bytes(); - response_bytes.extend_from_slice(&body); + match &compressed_body { + Some(body) => response_bytes.extend_from_slice(body), + None => response_bytes.extend_from_slice(self.get_body()), + } response_bytes } } diff --git a/type/src/stream/fn.rs b/type/src/stream/fn.rs new file mode 100644 index 00000000..3b03e11f --- /dev/null +++ b/type/src/stream/fn.rs @@ -0,0 +1,45 @@ +use super::*; + +/// Takes a read buffer from the thread-local pool or allocates a new one. +/// +/// # Arguments +/// +/// - `usize` - The required buffer length in bytes. +/// +/// # Returns +/// +/// - `Vec` - A buffer with at least the requested length. +pub(crate) fn take_read_buffer(capacity: usize) -> Vec { + READ_BUFFER_POOL.with(|pool: &RefCell>>| { + let mut pool: RefMut<'_, Vec>> = pool.borrow_mut(); + match pool.pop() { + Some(mut buffer) => { + if buffer.len() < capacity { + buffer.resize(capacity, 0); + } + buffer + } + None => vec![0; capacity], + } + }) +} + +/// Returns a read buffer to the thread-local pool for reuse. +/// +/// Buffers larger than `MAX_POOLED_READ_BUFFER_SIZE` are dropped instead +/// of being retained, and the pool is capped at `MAX_POOLED_READ_BUFFERS`. +/// +/// # Arguments +/// +/// - `Vec` - The buffer to recycle. +pub(crate) fn return_read_buffer(buffer: Vec) { + if buffer.len() > MAX_POOLED_READ_BUFFER_SIZE { + return; + } + READ_BUFFER_POOL.with(|pool: &RefCell>>| { + let mut pool: RefMut<'_, Vec>> = pool.borrow_mut(); + if pool.len() < MAX_POOLED_READ_BUFFERS { + pool.push(buffer); + } + }); +} diff --git a/type/src/stream/impl.rs b/type/src/stream/impl.rs index 211921ba..bf9b930a 100644 --- a/type/src/stream/impl.rs +++ b/type/src/stream/impl.rs @@ -112,7 +112,7 @@ impl Lifetime for Stream { /// /// # Returns /// - /// - `&'static Self`: A reference to the stream with a `'static` lifetime. + /// - `&'static Self` - A reference to the stream with a `'static` lifetime. /// /// # Safety /// @@ -128,7 +128,7 @@ impl Lifetime for Stream { /// /// # Returns /// - /// - `&'static mut Self`: A mutable reference to the stream with a `'static` lifetime. + /// - `&'static mut Self` - A mutable reference to the stream with a `'static` lifetime. /// /// # Safety /// @@ -141,6 +141,99 @@ impl Lifetime for Stream { } } +impl Drop for PooledReader<'_> { + /// Releases the buffer back to the pool for reuse by later requests. + fn drop(&mut self) { + let buffer: Vec = mem::take(self.get_mut_buffer()); + return_read_buffer(buffer); + } +} + +/// Implements non-blocking buffered reads for `PooledReader`. +/// +/// Buffered bytes are served first; once drained, reads are delegated +/// directly to the underlying stream. +impl AsyncRead for PooledReader<'_> { + /// Polls to read data into the provided buffer. + /// + /// # Arguments + /// + /// - `Pin<&mut Self>` - The pinned reader. + /// - `&mut Context<'_>` - The task context. + /// - `&mut ReadBuf<'_>` - The destination buffer. + /// + /// # Returns + /// + /// - `Poll>` - Ready when data was read or an error occurred. + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let this: &mut Self = self.get_mut(); + if *this.get_start() < *this.get_end() { + let available: usize = *this.get_end() - *this.get_start(); + let amount: usize = available.min(buf.remaining()); + let end: usize = *this.get_start() + amount; + buf.put_slice(&this.get_buffer()[*this.get_start()..end]); + this.set_start(end); + return Poll::Ready(Ok(())); + } + Pin::new(&mut **this.get_mut_stream()).poll_read(cx, buf) + } +} + +/// Implements buffered-read support for `PooledReader`. +/// +/// The internal buffer is refilled from the stream only when fully +/// consumed, so a single socket read serves multiple line parses. +impl AsyncBufRead for PooledReader<'_> { + /// Polls to fill the internal buffer and returns the available data. + /// + /// # Arguments + /// + /// - `Pin<&mut Self>` - The pinned reader. + /// - `&mut Context<'_>` - The task context. + /// + /// # Returns + /// + /// - `Poll>` - Ready with the unconsumed bytes, or an error. + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this: &mut Self = self.get_mut(); + let PooledReader { + stream, + buffer, + start, + end, + } = this; + if *start >= *end { + *start = 0; + *end = 0; + let mut read_buf: ReadBuf<'_> = ReadBuf::new(buffer); + match Pin::new(&mut **stream).poll_read(cx, &mut read_buf) { + Poll::Ready(Ok(())) => { + *end = read_buf.filled().len(); + } + Poll::Ready(Err(error)) => return Poll::Ready(Err(error)), + Poll::Pending => return Poll::Pending, + } + } + Poll::Ready(Ok(&buffer[*start..*end])) + } + + /// Marks the given number of bytes as consumed. + /// + /// # Arguments + /// + /// - `Pin<&mut Self>` - The pinned reader. + /// - `usize` - The number of bytes to consume. + fn consume(self: Pin<&mut Self>, amount: usize) { + let this: &mut Self = self.get_mut(); + let new_start: usize = (*this.get_start() + amount).min(*this.get_end()); + this.set_start(new_start); + } +} + impl Stream { /// Checks if the connection should be kept alive. /// @@ -159,61 +252,82 @@ impl Stream { !self.get_closed() && keep_alive } - /// Parses the HTTP request content from the stream. + /// Parses the HTTP request content from the stream into the given request. /// - /// This is an internal helper function that performs the actual parsing. + /// The request is reset first, then filled in place so its existing + /// allocations are reused across keep-alive requests. + /// + /// # Arguments + /// + /// - `&mut Request` - The request object to fill. /// /// # Returns /// - /// - `Result`: The parsed request or an error. - async fn get_http_from_stream(&mut self) -> Result { + /// - `Result<(), RequestError>` - Ok on success, or an error if parsing fails. + async fn fill_http_from_stream(&mut self, request: &mut Request) -> Result<(), RequestError> { + request.reset(); let config: RequestConfig = *self.get_request_config(); let buffer_size: usize = config.get_buffer_size(); let max_path_size: usize = config.get_max_path_size(); - let reader: &mut BufReader<&mut TcpStream> = - &mut BufReader::with_capacity(buffer_size, self.get_mut_stream()); - let mut line: String = String::with_capacity(buffer_size); - AsyncBufReadExt::read_line(reader, &mut line).await?; + let buffer: Vec = take_read_buffer(buffer_size); + let mut reader: PooledReader<'_> = PooledReader::new(self.get_mut_stream(), buffer); + let mut line: String = String::with_capacity(REQUEST_LINE_BUFFER_CAPACITY); + AsyncBufReadExt::read_line(&mut reader, &mut line).await?; let (method, path, version): (RequestMethod, &str, RequestVersion) = Request::get_http_first_line(&line)?; Request::check_http_path_size(path, max_path_size)?; let hash_index: Option = path.find(HASH); let query_index: Option = path.find(QUERY); let query: &str = Request::get_http_query(path, query_index, hash_index); - let querys: RequestQuerys = Request::get_http_querys(query); - let path: RequestPath = Request::get_http_path(path, query_index, hash_index); - let (headers, host, content_size): (RequestHeaders, RequestHost, usize) = - Request::get_http_headers(reader, &config).await?; - let body: RequestBody = Request::get_http_body(reader, content_size).await?; - Ok(Request { - method, - host, - version, - path, - querys, - headers, - body, - }) + Request::fill_http_querys(query, request.get_mut_querys()); + let path_slice: &str = Request::get_http_path(path, query_index, hash_index); + request.get_mut_path().push_str(path_slice); + let content_size: usize = request.get_http_headers(&mut reader, &config).await?; + request.set_method(method); + request.set_version(version); + Request::fill_http_body(&mut reader, request.get_mut_body(), content_size).await?; + Ok(()) } - /// Parses an HTTP request from a TCP stream. + /// Parses an HTTP request from a TCP stream into the given request. /// - /// Wraps the stream in a buffered reader and delegates to `http_from_reader`. + /// The request is reset and filled in place, reusing its allocations. /// If the timeout is DEFAULT_LOW_SECURITY_READ_TIMEOUT_MS, no timeout is applied. /// + /// # Arguments + /// + /// - `&mut Request` - The request object to reset and fill. + /// /// # Returns /// - /// - `Result` - The parsed request or an error. - pub async fn try_get_http_request(&mut self) -> Result { + /// - `Result<(), RequestError>` - Ok on success, or an error if parsing fails. + pub async fn try_fill_http_request( + &mut self, + request: &mut Request, + ) -> Result<(), RequestError> { if self.get_closed() { return Err(RequestError::ServerClosedConnection(HttpStatus::BadRequest)); } let timeout_ms: u64 = self.get_request_config().get_read_timeout_ms(); if timeout_ms == DEFAULT_LOW_SECURITY_READ_TIMEOUT_MS { - return self.get_http_from_stream().await; + return self.fill_http_from_stream(request).await; } let duration: Duration = Duration::from_millis(timeout_ms); - timeout(duration, self.get_http_from_stream()).await? + timeout(duration, self.fill_http_from_stream(request)).await? + } + + /// Parses an HTTP request from a TCP stream. + /// + /// Wraps the stream in a buffered reader and delegates to `http_from_reader`. + /// If the timeout is DEFAULT_LOW_SECURITY_READ_TIMEOUT_MS, no timeout is applied. + /// + /// # Returns + /// + /// - `Result` - The parsed request or an error. + pub async fn try_get_http_request(&mut self) -> Result { + let mut request: Request = Request::default(); + self.try_fill_http_request(&mut request).await?; + Ok(request) } /// Parses a WebSocket request from a TCP stream. @@ -223,7 +337,7 @@ impl Stream { /// /// # Returns /// - /// - `Result`: The parsed WebSocket request or an error. + /// - `Result` - The parsed WebSocket request or an error. pub async fn try_get_websocket_request(&mut self) -> Result { if self.get_closed() { return Err(RequestError::ServerClosedConnection(HttpStatus::BadRequest)); @@ -286,13 +400,13 @@ impl Stream { /// /// # Arguments /// - /// - `&mut [u8]`: The buffer to read data into. - /// - `Option`: The optional timeout duration. If Some, timeout is applied; if None, no timeout. - /// - `&mut bool`: Mutable reference to track if we got a client response. + /// - `&mut [u8]` - The buffer to read data into. + /// - `Option` - The optional timeout duration. If Some, timeout is applied; if None, no timeout. + /// - `&mut bool` - Mutable reference to track if we got a client response. /// /// # Returns /// - /// - `Result, RequestError>`: The number of bytes read, None for timeout/ping, or an error. + /// - `Result, RequestError>` - The number of bytes read, None for timeout/ping, or an error. pub(crate) async fn get_websocket_from_stream( &mut self, buffer: &mut [u8], diff --git a/type/src/stream/mod.rs b/type/src/stream/mod.rs index 7c96b2e5..c312841a 100644 --- a/type/src/stream/mod.rs +++ b/type/src/stream/mod.rs @@ -1,7 +1,11 @@ +mod r#fn; mod r#impl; +mod r#static; mod r#struct; mod r#type; pub use {r#struct::*, r#type::*}; +pub(crate) use {r#fn::*, r#static::*}; + use super::*; diff --git a/type/src/stream/static.rs b/type/src/stream/static.rs new file mode 100644 index 00000000..1587df71 --- /dev/null +++ b/type/src/stream/static.rs @@ -0,0 +1,6 @@ +use super::*; + +thread_local! { + /// Thread-local pool of reusable read buffers for `PooledReader`. + pub(crate) static READ_BUFFER_POOL: RefCell>> = const { RefCell::new(Vec::new()) }; +} diff --git a/type/src/stream/struct.rs b/type/src/stream/struct.rs index 33c38ace..a9afd6a8 100644 --- a/type/src/stream/struct.rs +++ b/type/src/stream/struct.rs @@ -12,3 +12,34 @@ pub struct Stream { #[get_mut(pub(super))] pub(super) closed: bool, } + +/// A buffered reader over a `TcpStream` with a reusable read buffer. +/// +/// This reader batches socket reads into an internal buffer to reduce +/// syscall count, and returns its buffer to a thread-local pool on drop +/// so keep-alive requests avoid repeated buffer allocation. +#[derive(Data, New)] +pub(crate) struct PooledReader<'a> { + /// The underlying TCP stream being read. + #[get(pub(crate))] + #[get_mut(pub(crate))] + #[set(pub(crate))] + pub(super) stream: &'a mut TcpStream, + /// The reusable read buffer holding unconsumed bytes in `start..end`. + #[get(pub(crate))] + #[get_mut(pub(crate))] + #[set(pub(crate))] + pub(super) buffer: Vec, + /// The index of the first unconsumed byte in the buffer. + #[get(pub(crate))] + #[get_mut(pub(crate))] + #[set(pub(crate))] + #[new(skip)] + pub(super) start: usize, + /// The index one past the last valid byte in the buffer. + #[get(pub(crate))] + #[get_mut(pub(crate))] + #[set(pub(crate))] + #[new(skip)] + pub(super) end: usize, +} diff --git a/type/src/task/impl.rs b/type/src/task/impl.rs index 306ce234..17e76385 100644 --- a/type/src/task/impl.rs +++ b/type/src/task/impl.rs @@ -6,7 +6,7 @@ impl Default for Task { /// /// # Returns /// - /// - `Self`: The default instance. + /// - `Self` - The default instance. #[inline(always)] fn default() -> Self { let worker_count: usize = Handle::try_current() @@ -31,11 +31,11 @@ impl Task { /// /// # Arguments /// - /// - `usize`: The number of worker threads to spawn. + /// - `usize` - The number of worker threads to spawn. /// /// # Returns /// - /// - `Self`: The new instance. + /// - `Self` - The new instance. pub fn new(worker_count: usize) -> Self { let mut pool: Vec> = Vec::with_capacity(worker_count); let counter: Arc = Arc::new(AtomicUsize::new(0)); @@ -83,13 +83,13 @@ impl Task { /// /// # Arguments /// - /// - `Option`: An optional index to force selection of a specific worker. + /// - `Option` - An optional index to force selection of a specific worker. /// If None, the worker is selected using round-robin distribution. - /// - `Future + Send + 'static`: The future to spawn on the task pool. + /// - `Future + Send + 'static` - The future to spawn on the task pool. /// /// # Returns /// - /// - `bool`: true if the task was successfully sent, false otherwise. + /// - `bool` - true if the task was successfully sent, false otherwise. pub fn try_spawn_local(&self, index_opt: Option, hook: F) -> bool where F: Future + Send + 'static, diff --git a/type/src/upgrade_type/impl.rs b/type/src/upgrade_type/impl.rs index d6da2448..e35dbbd8 100644 --- a/type/src/upgrade_type/impl.rs +++ b/type/src/upgrade_type/impl.rs @@ -22,7 +22,7 @@ impl Display for UpgradeType { /// /// # Arguments /// - /// - `&mut Formatter<'_>`: A mutable reference to a `Formatter` used for writing the formatted string. + /// - `&mut Formatter<'_>` - A mutable reference to a `Formatter` used for writing the formatted string. /// /// # Returns /// @@ -51,12 +51,12 @@ impl FromStr for UpgradeType { /// /// # Arguments /// - /// - `&str`: The string slice to parse. + /// - `&str` - The string slice to parse. /// /// # Returns /// - /// - `Ok(UpgradeType)`: The parsed `UpgradeType` variant. - /// - `Err(())`: If parsing fails (though this implementation always returns `Ok`). + /// - `Ok(UpgradeType)` - The parsed `UpgradeType` variant. + /// - `Err(())` - If parsing fails (though this implementation always returns `Ok`). #[inline(always)] fn from_str(from_str: &str) -> Result { match from_str.to_ascii_lowercase().as_str() { diff --git a/type/src/websocket_frame/impl.rs b/type/src/websocket_frame/impl.rs index 37318487..369310b3 100644 --- a/type/src/websocket_frame/impl.rs +++ b/type/src/websocket_frame/impl.rs @@ -26,7 +26,7 @@ impl WebSocketOpcode { /// /// # Arguments /// - /// - `u8`: The raw opcode value. + /// - `u8` - The raw opcode value. /// /// # Returns /// @@ -167,9 +167,9 @@ impl WebSocketFrame { /// # Returns /// /// - `Option<(WebSocketFrame, usize)>` - /// - `Some((WebSocketFrame, usize))`: If the frame is successfully decoded, returns the decoded frame + /// - `Some((WebSocketFrame, usize))` - If the frame is successfully decoded, returns the decoded frame /// and the number of bytes consumed from the input slice. - /// - `None`: If the frame is incomplete or malformed. + /// - `None` - If the frame is incomplete or malformed. pub fn decode_ws_frame(data: D) -> Option<(WebSocketFrame, usize)> where D: AsRef<[u8]>, @@ -487,7 +487,7 @@ impl WebSocketFrame { /// - `bool` - `true` if the opcode is `Continuation`, otherwise `false`. #[inline(always)] pub fn is_continuation_opcode(&self) -> bool { - self.opcode.is_continuation() + self.get_opcode().is_continuation() } /// Checks if the opcode is a text frame. @@ -497,7 +497,7 @@ impl WebSocketFrame { /// - `bool` - `true` if the opcode is `Text`, otherwise `false`. #[inline(always)] pub fn is_text_opcode(&self) -> bool { - self.opcode.is_text() + self.get_opcode().is_text() } /// Checks if the opcode is a binary frame. @@ -507,7 +507,7 @@ impl WebSocketFrame { /// - `bool` - `true` if the opcode is `Binary`, otherwise `false`. #[inline(always)] pub fn is_binary_opcode(&self) -> bool { - self.opcode.is_binary() + self.get_opcode().is_binary() } /// Checks if the opcode is a close frame. @@ -517,7 +517,7 @@ impl WebSocketFrame { /// - `bool` - `true` if the opcode is `Close`, otherwise `false`. #[inline(always)] pub fn is_close_opcode(&self) -> bool { - self.opcode.is_close() + self.get_opcode().is_close() } /// Checks if the opcode is a ping frame. @@ -527,7 +527,7 @@ impl WebSocketFrame { /// - `bool` - `true` if the opcode is `Ping`, otherwise `false`. #[inline(always)] pub fn is_ping_opcode(&self) -> bool { - self.opcode.is_ping() + self.get_opcode().is_ping() } /// Checks if the opcode is a pong frame. @@ -537,7 +537,7 @@ impl WebSocketFrame { /// - `bool` - `true` if the opcode is `Pong`, otherwise `false`. #[inline(always)] pub fn is_pong_opcode(&self) -> bool { - self.opcode.is_pong() + self.get_opcode().is_pong() } /// Checks if the opcode is a reserved frame. @@ -547,18 +547,18 @@ impl WebSocketFrame { /// - `bool` - `true` if the opcode is `Reserved(_)`, otherwise `false`. #[inline(always)] pub fn is_reserved_opcode(&self) -> bool { - self.opcode.is_reserved() + self.get_opcode().is_reserved() } /// Handles a decoded WebSocket Text or Binary frame and accumulates payload data. /// /// # Arguments /// - /// - `&mut Vec`: The accumulated frame data. + /// - `&mut Vec` - The accumulated frame data. /// /// # Returns /// - /// - `Result, RequestError>`: Some(request) if frame is complete, None to continue, or error. + /// - `Result, RequestError>` - Some(request) if frame is complete, None to continue, or error. #[inline(always)] pub(crate) fn build_full_frame( &self,