From e34964c8281da8657bd942cca7cdf45d95cca583 Mon Sep 17 00:00:00 2001 From: eastspire Date: Sun, 27 Sep 2026 00:12:33 +0800 Subject: [PATCH 1/5] Refactor code documentation and improve buffer management - Updated documentation comments across various modules to enhance clarity and consistency, particularly in trait and function signatures. - Introduced a thread-local buffer pool for efficient read buffer management in the stream module, reducing allocation overhead for keep-alive connections. - Implemented a `PooledReader` struct to manage buffered reads from TCP streams, optimizing performance by reusing buffers. - Enhanced the request and response handling in the stream module to utilize the new buffered reader, improving efficiency in parsing HTTP requests and responses. - Added a `reset` method to both `Request` and `Response` structs to allow for reusing existing allocations during keep-alive connections. --- cli/src/config/fn.rs | 2 +- cli/src/new/fn.rs | 14 +-- cli/src/new/impl.rs | 4 +- cli/src/template/fn.rs | 90 +++++++------- cli/src/template/impl.rs | 8 +- cli/src/watch/fn.rs | 4 +- constant/src/common/const.rs | 22 +++- core/src/context/impl.rs | 18 ++- core/src/lib.rs | 1 + core/src/route/impl.rs | 26 ++-- core/src/route/struct.rs | 6 +- core/src/server/impl.rs | 53 ++++---- macros/src/context/struct.rs | 2 +- macros/src/from_stream/impl.rs | 4 +- macros/src/lib.rs | 10 +- type/src/http_url/impl.rs | 4 +- type/src/lib.rs | 10 +- type/src/lifetime/trait.rs | 4 +- type/src/protocol/impl.rs | 12 +- type/src/request/impl.rs | 187 ++++++++++++++++------------ type/src/response/impl.rs | 96 +++++++++----- type/src/stream/fn.rs | 49 ++++++++ type/src/stream/impl.rs | 206 +++++++++++++++++++++++++------ type/src/stream/mod.rs | 3 + type/src/stream/struct.rs | 16 +++ type/src/task/impl.rs | 12 +- type/src/upgrade_type/impl.rs | 8 +- type/src/websocket_frame/impl.rs | 10 +- 28 files changed, 584 insertions(+), 297 deletions(-) create mode 100644 type/src/stream/fn.rs 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..926b6d09 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.route_params.clear(); + self + } + /// Attempts to retrieve a specific route parameter by its name. /// /// # Arguments 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/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..f8bbf4ad 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.method = Method::default(); + self.host.clear(); + self.version = HttpVersion::default(); + self.path.clear(); + self.querys.clear(); + self.headers.clear(); + self.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,41 @@ impl Request { Ok(length) } - /// Parses HTTP headers from a buffered reader. + /// Parses HTTP headers from a buffered reader into the given map. /// /// 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 target 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. + /// - `&mut RequestHeaders` - The map to insert parsed headers into. + /// - `&mut RequestHost` - The string to write the Host header value into. /// /// # 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( reader: &mut R, config: &RequestConfig, - ) -> Result<(RequestHeaders, RequestHost, usize), RequestError> + headers: &mut RequestHeaders, + host: &mut RequestHost, + ) -> Result where R: AsyncBufReadExt + Unpin, { - let buffer_size: usize = config.get_buffer_size(); 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 +579,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. diff --git a/type/src/response/impl.rs b/type/src/response/impl.rs index 80541bbd..cd60ad0b 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 @@ -536,6 +520,24 @@ impl Response { 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.status_code = http_status.code(); + self.reason_phrase.clear(); + let _: fmt::Result = write!(self.reason_phrase, "{}", http_status); + self.headers.clear(); + self.body.clear(); + self + } + /// Tries to parse cookies from the `Set-Cookie` header. /// /// This method retrieves the last `Set-Cookie` header value and parses it @@ -626,8 +628,6 @@ impl Response { if self.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..839f893b --- /dev/null +++ b/type/src/stream/fn.rs @@ -0,0 +1,49 @@ +use super::*; + +thread_local! { + static READ_BUFFER_POOL: RefCell>> = const { RefCell::new(Vec::new()) }; +} + +/// 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..16dc9eb0 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,115 @@ impl Lifetime for Stream { } } +/// Creates a new `PooledReader` wrapping the given stream and buffer. +impl<'a> PooledReader<'a> { + /// Creates a new `PooledReader` over the given stream. + /// + /// # Arguments + /// + /// - `&'a mut TcpStream` - The TCP stream to read from. + /// - `Vec` - The read buffer; its full length is used as capacity. + /// + /// # Returns + /// + /// - `Self` - A reader with an empty valid-data region. + pub(crate) fn new(stream: &'a mut TcpStream, buffer: Vec) -> Self { + Self { + stream, + buffer, + start: 0, + end: 0, + } + } +} + +/// Returns the read buffer to the thread-local pool when the reader is dropped. +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(&mut self.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.start < this.end { + let available: usize = this.end - this.start; + let amount: usize = available.min(buf.remaining()); + let end: usize = this.start + amount; + buf.put_slice(&this.buffer[this.start..end]); + this.start = end; + return Poll::Ready(Ok(())); + } + Pin::new(&mut *this.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(); + if this.start >= this.end { + this.start = 0; + this.end = 0; + let mut read_buf: ReadBuf<'_> = ReadBuf::new(&mut this.buffer); + match Pin::new(&mut *this.stream).poll_read(cx, &mut read_buf) { + Poll::Ready(Ok(())) => { + this.end = read_buf.filled().len(); + } + Poll::Ready(Err(error)) => return Poll::Ready(Err(error)), + Poll::Pending => return Poll::Pending, + } + } + Poll::Ready(Ok(&this.buffer[this.start..this.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(); + this.start = (this.start + amount).min(this.end); + } +} + impl Stream { /// Checks if the connection should be kept alive. /// @@ -159,61 +268,88 @@ 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, &mut request.querys); + let path_slice: &str = Request::get_http_path(path, query_index, hash_index); + request.path.push_str(path_slice); + let content_size: usize = Request::get_http_headers( + &mut reader, + &config, + &mut request.headers, + &mut request.host, + ) + .await?; + request.method = method; + request.version = version; + Request::fill_http_body(&mut reader, &mut request.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 +359,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 +422,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..534afdcc 100644 --- a/type/src/stream/mod.rs +++ b/type/src/stream/mod.rs @@ -1,7 +1,10 @@ +mod r#fn; mod r#impl; mod r#struct; mod r#type; pub use {r#struct::*, r#type::*}; +pub(crate) use r#fn::*; + use super::*; diff --git a/type/src/stream/struct.rs b/type/src/stream/struct.rs index 33c38ace..f0df83a4 100644 --- a/type/src/stream/struct.rs +++ b/type/src/stream/struct.rs @@ -12,3 +12,19 @@ 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. +pub(crate) struct PooledReader<'a> { + /// The underlying TCP stream being read. + pub(super) stream: &'a mut TcpStream, + /// The reusable read buffer holding unconsumed bytes in `start..end`. + pub(super) buffer: Vec, + /// The index of the first unconsumed byte in the buffer. + pub(super) start: usize, + /// The index one past the last valid byte in the buffer. + 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..2f6d7cb1 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]>, @@ -554,11 +554,11 @@ impl WebSocketFrame { /// /// # 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, From 736525e5da0184abb4e29f17d9bede2cc87a2e17 Mon Sep 17 00:00:00 2001 From: eastspire Date: Sun, 27 Sep 2026 08:13:10 +0800 Subject: [PATCH 2/5] chore: update version to 21.7.8 and refactor method calls for improved clarity --- core/src/context/impl.rs | 2 +- core/src/context/struct.rs | 1 - request/src/common/enum.rs | 13 +- request/src/request/http_request/impl.rs | 24 ++-- request/src/request/http_request/struct.rs | 13 +- request/src/request/proxy/impl.rs | 30 ++--- request/src/request/proxy/struct.rs | 90 +++++++++++++- request/src/request/request_builder/struct.rs | 67 ++++++---- request/src/request/tmp/struct.rs | 18 ++- request/src/response/struct.rs | 20 +-- type/src/cookie/impl.rs | 8 +- type/src/request/impl.rs | 18 +-- type/src/response/impl.rs | 26 ++-- type/src/stream/impl.rs | 116 ++++++++++++++---- type/src/websocket_frame/impl.rs | 14 +-- 15 files changed, 327 insertions(+), 133 deletions(-) diff --git a/core/src/context/impl.rs b/core/src/context/impl.rs index 926b6d09..06498a15 100644 --- a/core/src/context/impl.rs +++ b/core/src/context/impl.rs @@ -195,7 +195,7 @@ impl Context { /// - `&mut Self` - A mutable reference to self for chaining. #[inline(always)] pub(crate) fn clear_route_params(&mut self) -> &mut Self { - self.route_params.clear(); + self.get_mut_route_params().clear(); self } 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/request/src/common/enum.rs b/request/src/common/enum.rs index db51e953..0e5b6241 100644 --- a/request/src/common/enum.rs +++ b/request/src/common/enum.rs @@ -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_ref() + } + /// 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_ref()).ok() } } diff --git a/request/src/request/http_request/impl.rs b/request/src/request/http_request/impl.rs index e2aa5aa7..51363fc9 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 = self.get_tmp_ref().get_root_cert(); 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_ref().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_visit_url_mut().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 = self.get_tmp_ref().get_root_cert(); 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_ref().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_visit_url_mut().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 = self.get_tmp_ref().get_root_cert(); 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 = self.get_tmp_ref().get_root_cert(); 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 = self.get_tmp_ref().get_root_cert(); 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 = self.get_tmp_ref().get_root_cert(); 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 = self.get_tmp_ref().get_root_cert(); 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 = self.get_tmp_ref().get_root_cert(); 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..e8229b56 100644 --- a/request/src/request/http_request/struct.rs +++ b/request/src/request/http_request/struct.rs @@ -82,13 +82,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_headers_mut().remove(&normalized); self } /// Clear all headers. pub fn clear_headers(&mut self) -> &mut Self { - self.headers.clear(); + self.get_headers_mut().clear(); self } @@ -129,6 +129,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..26252241 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_ref().is_empty() { + let len: usize = std::cmp::min(self.get_pre_read_data_ref().len(), buf.remaining()); + buf.put_slice(&self.get_pre_read_data_ref()[..len]); + self.get_pre_read_data_mut().drain(..len); return Poll::Ready(Ok(())); } - Pin::new(&mut self.inner).poll_read(cx, buf) + Pin::new(self.get_inner_mut()).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_inner_mut()).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_inner_mut()).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_inner_mut()).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_ref().is_empty() { + let len: usize = std::cmp::min(self.get_pre_read_data_ref().len(), buf.len()); + buf[..len].copy_from_slice(&self.get_pre_read_data_ref()[..len]); + self.get_pre_read_data_mut().drain(..len); return Ok(len); } - self.inner.read(buf) + self.get_inner_mut().read(buf) } } impl Write for SyncProxyTunnelStream { fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.inner.write(buf) + self.get_inner_mut().write(buf) } fn flush(&mut self) -> std::io::Result<()> { - self.inner.flush() + self.get_inner_mut().flush() } } diff --git a/request/src/request/proxy/struct.rs b/request/src/request/proxy/struct.rs index 8450ee87..56f7f7bc 100644 --- a/request/src/request/proxy/struct.rs +++ b/request/src/request/proxy/struct.rs @@ -62,8 +62,36 @@ 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 + } + + /// Sets the proxy username. + /// + /// # Arguments + /// + /// - `Option` - The username, or None to clear. + /// + /// # Returns + /// + /// - `&mut Self` - A mutable reference to self for chaining. + pub fn set_username(&mut self, value: Option) -> &mut Self { + self.username = value; + self + } + + /// Sets the proxy password. + /// + /// # Arguments + /// + /// - `Option` - The password, or None to clear. + /// + /// # Returns + /// + /// - `&mut Self` - A mutable reference to self for chaining. + pub fn set_password(&mut self, value: Option) -> &mut Self { + self.password = value; self } @@ -91,3 +119,61 @@ pub struct SyncProxyTunnelStream { pub(super) inner: BoxReadWrite, pub(super) pre_read_data: Vec, } + +impl ProxyTunnelStream { + /// Returns the inner stream mutably. + /// + /// # Returns + /// + /// - `&mut BoxAsyncReadWrite` - The mutable inner stream. + pub(crate) fn get_inner_mut(&mut self) -> &mut BoxAsyncReadWrite { + &mut self.inner + } + + /// Returns the buffered pre-read bytes. + /// + /// # Returns + /// + /// - `&[u8]` - The pre-read bytes not yet consumed. + pub(crate) fn get_pre_read_data_ref(&self) -> &[u8] { + &self.pre_read_data + } + + /// Returns the buffered pre-read bytes mutably. + /// + /// # Returns + /// + /// - `&mut Vec` - The mutable pre-read buffer. + pub(crate) fn get_pre_read_data_mut(&mut self) -> &mut Vec { + &mut self.pre_read_data + } +} + +impl SyncProxyTunnelStream { + /// Returns the inner stream mutably. + /// + /// # Returns + /// + /// - `&mut BoxReadWrite` - The mutable inner stream. + pub(crate) fn get_inner_mut(&mut self) -> &mut BoxReadWrite { + &mut self.inner + } + + /// Returns the buffered pre-read bytes. + /// + /// # Returns + /// + /// - `&[u8]` - The pre-read bytes not yet consumed. + pub(crate) fn get_pre_read_data_ref(&self) -> &[u8] { + &self.pre_read_data + } + + /// Returns the buffered pre-read bytes mutably. + /// + /// # Returns + /// + /// - `&mut Vec` - The mutable pre-read buffer. + pub(crate) fn get_pre_read_data_mut(&mut self) -> &mut Vec { + &mut self.pre_read_data + } +} diff --git a/request/src/request/request_builder/struct.rs b/request/src/request/request_builder/struct.rs index 26ba2812..a28a4f12 100644 --- a/request/src/request/request_builder/struct.rs +++ b/request/src/request/request_builder/struct.rs @@ -34,6 +34,15 @@ pub struct RequestBuilder { } 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 +50,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_request_mut().set_method(Method::Get); + self.get_request_mut().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_request_mut().set_method(Method::Post); + self.get_request_mut().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_request_mut().set_method(method); self } /// Set URL. pub fn url(&mut self, url: impl Into) -> &mut Self { - self.request.set_url(url); + self.get_request_mut().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_request_mut().set_header(key, value); self } @@ -78,32 +87,32 @@ impl RequestBuilder { V: AsRef, { for (k, v) in headers { - self.request.set_header(k, v); + self.get_request_mut().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_request_mut().remove_header(key); self } /// Clear all headers. pub fn clear_headers(&mut self) -> &mut Self { - self.request.clear_headers(); + self.get_request_mut().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_request_mut().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_request_mut() .set_body(Body::from_bytes(text.into().into_bytes())); self } @@ -111,62 +120,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_request_mut().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_request_mut().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_request_mut().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_request_mut() + .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_request_mut() + .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_request_mut().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_request_mut().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_request_mut() + .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_request_mut().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_request_mut().get_config_mut().set_decode(false); self } @@ -176,18 +191,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_request_mut() + .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_request_mut().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_request_mut()) } } diff --git a/request/src/request/tmp/struct.rs b/request/src/request/tmp/struct.rs index b098cffa..24c94408 100644 --- a/request/src/request/tmp/struct.rs +++ b/request/src/request/tmp/struct.rs @@ -15,17 +15,29 @@ pub struct Tmp { impl Tmp { /// Borrow `visit_url` set. - pub(crate) fn visit_url_ref(&self) -> &HashSet { + /// + /// # Returns + /// + /// - `&HashSet` - The visited URL set. + pub(crate) fn get_visit_url_ref(&self) -> &HashSet { &self.visit_url } /// Mutable borrow of `visit_url` set. - pub(crate) fn visit_url_mut(&mut self) -> &mut HashSet { + /// + /// # Returns + /// + /// - `&mut HashSet` - The mutable visited URL set. + pub(crate) fn get_visit_url_mut(&mut self) -> &mut HashSet { &mut self.visit_url } /// Clone the `root_cert` store. - pub(crate) fn root_cert_clone(&self) -> RootCertStore { + /// + /// # Returns + /// + /// - `RootCertStore` - A cloned root certificate store. + pub(crate) fn get_root_cert(&self) -> RootCertStore { self.root_cert.clone() } } 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/request/impl.rs b/type/src/request/impl.rs index f8bbf4ad..3e21142d 100644 --- a/type/src/request/impl.rs +++ b/type/src/request/impl.rs @@ -283,13 +283,13 @@ impl Request { /// /// - `&mut Self` - A mutable reference to self for chaining. pub fn reset(&mut self) -> &mut Self { - self.method = Method::default(); - self.host.clear(); - self.version = HttpVersion::default(); - self.path.clear(); - self.querys.clear(); - self.headers.clear(); - self.body.clear(); + 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 } @@ -864,7 +864,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. @@ -883,7 +883,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 cd60ad0b..bb0320d4 100644 --- a/type/src/response/impl.rs +++ b/type/src/response/impl.rs @@ -219,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. @@ -238,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 @@ -450,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()); @@ -473,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 } @@ -498,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 @@ -516,7 +516,7 @@ 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 } @@ -530,11 +530,11 @@ impl Response { /// - `&mut Self` - A mutable reference to self for chaining. pub fn reset(&mut self) -> &mut Self { let http_status: HttpStatus = HttpStatus::default(); - self.status_code = http_status.code(); - self.reason_phrase.clear(); - let _: fmt::Result = write!(self.reason_phrase, "{}", http_status); - self.headers.clear(); - self.body.clear(); + 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 } @@ -625,7 +625,7 @@ 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 compress_type_opt: Option = self diff --git a/type/src/stream/impl.rs b/type/src/stream/impl.rs index 16dc9eb0..dda6648d 100644 --- a/type/src/stream/impl.rs +++ b/type/src/stream/impl.rs @@ -161,13 +161,72 @@ impl<'a> PooledReader<'a> { end: 0, } } + + /// Returns a mutable reborrow of the underlying TCP stream. + /// + /// # Returns + /// + /// - `&mut TcpStream` - A mutable reborrow of the wrapped stream. + pub(crate) fn get_stream_mut(&mut self) -> &mut TcpStream { + &mut *self.stream + } + + /// Returns the start offset of the valid-data region. + /// + /// # Returns + /// + /// - `usize` - The start offset. + pub(crate) fn get_start(&self) -> usize { + self.start + } + + /// Sets the start offset of the valid-data region. + /// + /// # Arguments + /// + /// - `usize` - The new start offset. + /// + /// # Returns + /// + /// - `&mut Self` - A mutable reference to self for chaining. + pub(crate) fn set_start(&mut self, value: usize) -> &mut Self { + self.start = value; + self + } + + /// Returns the end offset of the valid-data region. + /// + /// # Returns + /// + /// - `usize` - The end offset. + pub(crate) fn get_end(&self) -> usize { + self.end + } + + /// Returns the raw buffer as an immutable slice. + /// + /// # Returns + /// + /// - `&[u8]` - The whole buffer; valid data is the `start..end` range. + pub(crate) fn get_buffer_ref(&self) -> &[u8] { + &self.buffer + } + + /// Returns the raw buffer as a mutable vector. + /// + /// # Returns + /// + /// - `&mut Vec` - The whole buffer for refill operations. + pub(crate) fn get_buffer_mut(&mut self) -> &mut Vec { + &mut self.buffer + } } /// Returns the read buffer to the thread-local pool when the reader is dropped. 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(&mut self.buffer); + let buffer: Vec = mem::take(self.get_buffer_mut()); return_read_buffer(buffer); } } @@ -194,15 +253,15 @@ impl AsyncRead for PooledReader<'_> { buf: &mut ReadBuf<'_>, ) -> Poll> { let this: &mut Self = self.get_mut(); - if this.start < this.end { - let available: usize = this.end - this.start; + 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.start + amount; - buf.put_slice(&this.buffer[this.start..end]); - this.start = end; + let end: usize = this.get_start() + amount; + buf.put_slice(&this.get_buffer_ref()[this.get_start()..end]); + this.set_start(end); return Poll::Ready(Ok(())); } - Pin::new(&mut *this.stream).poll_read(cx, buf) + Pin::new(this.get_stream_mut()).poll_read(cx, buf) } } @@ -223,19 +282,25 @@ impl AsyncBufRead for PooledReader<'_> { /// - `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(); - if this.start >= this.end { - this.start = 0; - this.end = 0; - let mut read_buf: ReadBuf<'_> = ReadBuf::new(&mut this.buffer); - match Pin::new(&mut *this.stream).poll_read(cx, &mut read_buf) { + 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(())) => { - this.end = read_buf.filled().len(); + *end = read_buf.filled().len(); } Poll::Ready(Err(error)) => return Poll::Ready(Err(error)), Poll::Pending => return Poll::Pending, } } - Poll::Ready(Ok(&this.buffer[this.start..this.end])) + Poll::Ready(Ok(&buffer[*start..*end])) } /// Marks the given number of bytes as consumed. @@ -246,7 +311,8 @@ impl AsyncBufRead for PooledReader<'_> { /// - `usize` - The number of bytes to consume. fn consume(self: Pin<&mut Self>, amount: usize) { let this: &mut Self = self.get_mut(); - this.start = (this.start + amount).min(this.end); + let new_start: usize = (this.get_start() + amount).min(this.get_end()); + this.set_start(new_start); } } @@ -295,19 +361,15 @@ impl Stream { 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); - Request::fill_http_querys(query, &mut request.querys); + Request::fill_http_querys(query, request.get_mut_querys()); let path_slice: &str = Request::get_http_path(path, query_index, hash_index); - request.path.push_str(path_slice); - let content_size: usize = Request::get_http_headers( - &mut reader, - &config, - &mut request.headers, - &mut request.host, - ) - .await?; - request.method = method; - request.version = version; - Request::fill_http_body(&mut reader, &mut request.body, content_size).await?; + request.get_mut_path().push_str(path_slice); + let Request { headers, host, .. } = request; + let content_size: usize = + Request::get_http_headers(&mut reader, &config, headers, host).await?; + request.set_method(method); + request.set_version(version); + Request::fill_http_body(&mut reader, request.get_mut_body(), content_size).await?; Ok(()) } diff --git a/type/src/websocket_frame/impl.rs b/type/src/websocket_frame/impl.rs index 2f6d7cb1..369310b3 100644 --- a/type/src/websocket_frame/impl.rs +++ b/type/src/websocket_frame/impl.rs @@ -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,7 +547,7 @@ 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. From cc1eda1b1789b1f34b28a06f73090526ccb06719 Mon Sep 17 00:00:00 2001 From: eastspire Date: Sun, 27 Sep 2026 08:46:46 +0800 Subject: [PATCH 3/5] refactor(request): derive lombok accessors instead of hand-written sets Per project rule, field accessors come from the lombok Data/Getter/ GetterMut macros wherever the macro can generate them: - Proxy, ProxyTunnelStream, SyncProxyTunnelStream, Tmp, RequestBuilder: derive(Data), hand-written get_*/set_* removed, call sites renamed to lombok names (get_mut_*/get_*). - PooledReader: derive(Data) verified working on the lifetime struct with the &'a mut TcpStream field; hand-written accessor block removed. - HttpRequest: derive(GetterMut) with #[get_mut(skip)] on all fields except headers, generating only get_mut_headers; the existing hand-written accessor API (get_method -> Method, set_url(impl Into), etc.) is the published crates.io contract and is kept as-is since a full Data derive would collide with it. - Body: derive(Getter) for get_bytes(). --- request/src/common/enum.rs | 6 +- request/src/request/http_request/impl.rs | 24 ++-- request/src/request/http_request/struct.rs | 11 +- request/src/request/proxy/impl.rs | 30 ++--- request/src/request/proxy/struct.rs | 90 +-------------- request/src/request/request_builder/struct.rs | 52 ++++----- request/src/request/tmp/struct.rs | 31 +---- type/src/stream/impl.rs | 107 ++++-------------- type/src/stream/struct.rs | 1 + 9 files changed, 93 insertions(+), 259 deletions(-) diff --git a/request/src/common/enum.rs b/request/src/common/enum.rs index 0e5b6241..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, @@ -40,12 +40,12 @@ impl Body { /// View body as `&[u8]`. pub fn as_slice(&self) -> &[u8] { - self.get_bytes_ref() + self.get_bytes() } /// Try to view body as UTF-8 string. pub fn as_str(&self) -> Option<&str> { - std::str::from_utf8(self.get_bytes_ref()).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 51363fc9..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().get_root_cert(); + 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().get_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().get_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().get_root_cert(); + 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().get_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().get_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().get_root_cert(); + 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().get_root_cert(); + 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().get_root_cert(); + 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().get_root_cert(); + 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().get_root_cert(); + 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().get_root_cert(); + 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 e8229b56..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.get_headers_mut().remove(&normalized); + self.get_mut_headers().remove(&normalized); self } /// Clear all headers. pub fn clear_headers(&mut self) -> &mut Self { - self.get_headers_mut().clear(); + self.get_mut_headers().clear(); self } diff --git a/request/src/request/proxy/impl.rs b/request/src/request/proxy/impl.rs index 26252241..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.get_pre_read_data_ref().is_empty() { - let len: usize = std::cmp::min(self.get_pre_read_data_ref().len(), buf.remaining()); - buf.put_slice(&self.get_pre_read_data_ref()[..len]); - self.get_pre_read_data_mut().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(self.get_inner_mut()).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(self.get_inner_mut()).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(self.get_inner_mut()).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(self.get_inner_mut()).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.get_pre_read_data_ref().is_empty() { - let len: usize = std::cmp::min(self.get_pre_read_data_ref().len(), buf.len()); - buf[..len].copy_from_slice(&self.get_pre_read_data_ref()[..len]); - self.get_pre_read_data_mut().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.get_inner_mut().read(buf) + self.get_mut_inner().read(buf) } } impl Write for SyncProxyTunnelStream { fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.get_inner_mut().write(buf) + self.get_mut_inner().write(buf) } fn flush(&mut self) -> std::io::Result<()> { - self.get_inner_mut().flush() + self.get_mut_inner().flush() } } diff --git a/request/src/request/proxy/struct.rs b/request/src/request/proxy/struct.rs index 56f7f7bc..2a6b787e 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, @@ -67,34 +67,6 @@ impl Proxy { self } - /// Sets the proxy username. - /// - /// # Arguments - /// - /// - `Option` - The username, or None to clear. - /// - /// # Returns - /// - /// - `&mut Self` - A mutable reference to self for chaining. - pub fn set_username(&mut self, value: Option) -> &mut Self { - self.username = value; - self - } - - /// Sets the proxy password. - /// - /// # Arguments - /// - /// - `Option` - The password, or None to clear. - /// - /// # Returns - /// - /// - `&mut Self` - A mutable reference to self for chaining. - pub fn set_password(&mut self, value: Option) -> &mut Self { - self.password = value; - self - } - fn new>(proxy_type: ProxyType, host: H, port: u16) -> Self { Self { proxy_type, @@ -108,6 +80,7 @@ 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 { pub(super) inner: BoxAsyncReadWrite, pub(super) pre_read_data: Vec, @@ -115,65 +88,8 @@ pub struct ProxyTunnelStream { /// 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 { pub(super) inner: BoxReadWrite, pub(super) pre_read_data: Vec, } - -impl ProxyTunnelStream { - /// Returns the inner stream mutably. - /// - /// # Returns - /// - /// - `&mut BoxAsyncReadWrite` - The mutable inner stream. - pub(crate) fn get_inner_mut(&mut self) -> &mut BoxAsyncReadWrite { - &mut self.inner - } - - /// Returns the buffered pre-read bytes. - /// - /// # Returns - /// - /// - `&[u8]` - The pre-read bytes not yet consumed. - pub(crate) fn get_pre_read_data_ref(&self) -> &[u8] { - &self.pre_read_data - } - - /// Returns the buffered pre-read bytes mutably. - /// - /// # Returns - /// - /// - `&mut Vec` - The mutable pre-read buffer. - pub(crate) fn get_pre_read_data_mut(&mut self) -> &mut Vec { - &mut self.pre_read_data - } -} - -impl SyncProxyTunnelStream { - /// Returns the inner stream mutably. - /// - /// # Returns - /// - /// - `&mut BoxReadWrite` - The mutable inner stream. - pub(crate) fn get_inner_mut(&mut self) -> &mut BoxReadWrite { - &mut self.inner - } - - /// Returns the buffered pre-read bytes. - /// - /// # Returns - /// - /// - `&[u8]` - The pre-read bytes not yet consumed. - pub(crate) fn get_pre_read_data_ref(&self) -> &[u8] { - &self.pre_read_data - } - - /// Returns the buffered pre-read bytes mutably. - /// - /// # Returns - /// - /// - `&mut Vec` - The mutable pre-read buffer. - pub(crate) fn get_pre_read_data_mut(&mut self) -> &mut Vec { - &mut self.pre_read_data - } -} diff --git a/request/src/request/request_builder/struct.rs b/request/src/request/request_builder/struct.rs index a28a4f12..0c84acb0 100644 --- a/request/src/request/request_builder/struct.rs +++ b/request/src/request/request_builder/struct.rs @@ -28,7 +28,7 @@ use super::*; /// .build(); /// let _resp = req.send_async().await; /// ``` -#[derive(Clone, Debug, Default)] +#[derive(Clone, Data, Debug, Default)] pub struct RequestBuilder { request: HttpRequest, } @@ -50,33 +50,33 @@ impl RequestBuilder { /// Shortcut for `method(Method::Get)` + `url(url)`. pub fn get(&mut self, url: impl Into) -> &mut Self { - self.get_request_mut().set_method(Method::Get); - self.get_request_mut().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.get_request_mut().set_method(Method::Post); - self.get_request_mut().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.get_request_mut().set_method(method); + self.get_mut_request().set_method(method); self } /// Set URL. pub fn url(&mut self, url: impl Into) -> &mut Self { - self.get_request_mut().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.get_request_mut().set_header(key, value); + self.get_mut_request().set_header(key, value); self } @@ -87,32 +87,32 @@ impl RequestBuilder { V: AsRef, { for (k, v) in headers { - self.get_request_mut().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.get_request_mut().remove_header(key); + self.get_mut_request().remove_header(key); self } /// Clear all headers. pub fn clear_headers(&mut self) -> &mut Self { - self.get_request_mut().clear_headers(); + self.get_mut_request().clear_headers(); self } /// Set raw body bytes. pub fn body>>(&mut self, bytes: B) -> &mut Self { - self.get_request_mut().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.get_request_mut() + self.get_mut_request() .set_body(Body::from_bytes(text.into().into_bytes())); self } @@ -120,26 +120,26 @@ 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.get_request_mut().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.get_request_mut().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.get_request_mut().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.get_request_mut() + self.get_mut_request() .get_config_mut() .set_http_version(HttpVersion::Http1_1); self @@ -147,7 +147,7 @@ impl RequestBuilder { /// Force HTTP/2. pub fn http2_only(&mut self) -> &mut Self { - self.get_request_mut() + self.get_mut_request() .get_config_mut() .set_http_version(HttpVersion::Http2); self @@ -155,19 +155,19 @@ impl RequestBuilder { /// Enable auto-follow of 3xx redirects. pub fn redirect(&mut self) -> &mut Self { - self.get_request_mut().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.get_request_mut().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.get_request_mut() + self.get_mut_request() .get_config_mut() .set_max_redirect_times(n); self @@ -175,13 +175,13 @@ impl RequestBuilder { /// Enable automatic response body decompression (gzip / deflate / br). pub fn decode(&mut self) -> &mut Self { - self.get_request_mut().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.get_request_mut().get_config_mut().set_decode(false); + self.get_mut_request().get_config_mut().set_decode(false); self } @@ -191,7 +191,7 @@ 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.get_request_mut() + self.get_mut_request() .get_config_mut() .set_proxy(Some(proxy)); self @@ -199,12 +199,12 @@ impl RequestBuilder { /// Clear the proxy (use direct connection). pub fn no_proxy(&mut self) -> &mut Self { - self.get_request_mut().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(self.get_request_mut()) + std::mem::take(self.get_mut_request()) } } diff --git a/request/src/request/tmp/struct.rs b/request/src/request/tmp/struct.rs index 24c94408..7eafb261 100644 --- a/request/src/request/tmp/struct.rs +++ b/request/src/request/tmp/struct.rs @@ -7,41 +7,12 @@ 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 { pub(crate) visit_url: HashSet, pub(crate) root_cert: RootCertStore, } -impl Tmp { - /// Borrow `visit_url` set. - /// - /// # Returns - /// - /// - `&HashSet` - The visited URL set. - pub(crate) fn get_visit_url_ref(&self) -> &HashSet { - &self.visit_url - } - - /// Mutable borrow of `visit_url` set. - /// - /// # Returns - /// - /// - `&mut HashSet` - The mutable visited URL set. - pub(crate) fn get_visit_url_mut(&mut self) -> &mut HashSet { - &mut self.visit_url - } - - /// Clone the `root_cert` store. - /// - /// # Returns - /// - /// - `RootCertStore` - A cloned root certificate store. - pub(crate) fn get_root_cert(&self) -> RootCertStore { - self.root_cert.clone() - } -} - impl Default for Tmp { #[inline(always)] fn default() -> Self { diff --git a/type/src/stream/impl.rs b/type/src/stream/impl.rs index dda6648d..fbaadbb4 100644 --- a/type/src/stream/impl.rs +++ b/type/src/stream/impl.rs @@ -141,18 +141,31 @@ impl Lifetime for Stream { } } -/// Creates a new `PooledReader` wrapping the given stream and buffer. +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<'a> PooledReader<'a> { - /// Creates a new `PooledReader` over the given stream. + /// Creates a new pooled reader over `stream` with the given reusable + /// `buffer`. Field accessors come from the `Data` derive. /// /// # Arguments /// - /// - `&'a mut TcpStream` - The TCP stream to read from. - /// - `Vec` - The read buffer; its full length is used as capacity. + /// - `&'a mut TcpStream` - The stream to read from. + /// - `Vec` - The reusable read buffer (capacity preserved across + /// keep-alive requests). /// /// # Returns /// - /// - `Self` - A reader with an empty valid-data region. + /// - `Self`: The reader with an empty valid-data region. pub(crate) fn new(stream: &'a mut TcpStream, buffer: Vec) -> Self { Self { stream, @@ -161,80 +174,8 @@ impl<'a> PooledReader<'a> { end: 0, } } - - /// Returns a mutable reborrow of the underlying TCP stream. - /// - /// # Returns - /// - /// - `&mut TcpStream` - A mutable reborrow of the wrapped stream. - pub(crate) fn get_stream_mut(&mut self) -> &mut TcpStream { - &mut *self.stream - } - - /// Returns the start offset of the valid-data region. - /// - /// # Returns - /// - /// - `usize` - The start offset. - pub(crate) fn get_start(&self) -> usize { - self.start - } - - /// Sets the start offset of the valid-data region. - /// - /// # Arguments - /// - /// - `usize` - The new start offset. - /// - /// # Returns - /// - /// - `&mut Self` - A mutable reference to self for chaining. - pub(crate) fn set_start(&mut self, value: usize) -> &mut Self { - self.start = value; - self - } - - /// Returns the end offset of the valid-data region. - /// - /// # Returns - /// - /// - `usize` - The end offset. - pub(crate) fn get_end(&self) -> usize { - self.end - } - - /// Returns the raw buffer as an immutable slice. - /// - /// # Returns - /// - /// - `&[u8]` - The whole buffer; valid data is the `start..end` range. - pub(crate) fn get_buffer_ref(&self) -> &[u8] { - &self.buffer - } - - /// Returns the raw buffer as a mutable vector. - /// - /// # Returns - /// - /// - `&mut Vec` - The whole buffer for refill operations. - pub(crate) fn get_buffer_mut(&mut self) -> &mut Vec { - &mut self.buffer - } } -/// Returns the read buffer to the thread-local pool when the reader is dropped. -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_buffer_mut()); - 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. /// @@ -253,15 +194,15 @@ impl AsyncRead for PooledReader<'_> { 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(); + 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_ref()[this.get_start()..end]); + 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(this.get_stream_mut()).poll_read(cx, buf) + Pin::new(&mut **this.get_mut_stream()).poll_read(cx, buf) } } @@ -311,7 +252,7 @@ impl AsyncBufRead for PooledReader<'_> { /// - `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()); + let new_start: usize = (*this.get_start() + amount).min(*this.get_end()); this.set_start(new_start); } } diff --git a/type/src/stream/struct.rs b/type/src/stream/struct.rs index f0df83a4..5b38bffb 100644 --- a/type/src/stream/struct.rs +++ b/type/src/stream/struct.rs @@ -18,6 +18,7 @@ pub struct Stream { /// 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)] pub(crate) struct PooledReader<'a> { /// The underlying TCP stream being read. pub(super) stream: &'a mut TcpStream, From 258fbb775f7ef85d6f043dc394e46c2e23b8f990 Mon Sep 17 00:00:00 2001 From: eastspire Date: Sun, 27 Sep 2026 10:13:52 +0800 Subject: [PATCH 4/5] refactor(request): cap generated accessor visibility at pub(crate) lombok emits accessors as pub by default; fields that are crate internals (tunnel stream innards, Tmp redirect state, the builder's in-progress request, PooledReader buffers) now carry #[get/get_mut/set(pub(crate))] so the macro-generated surface matches the field exposure instead of widening it. Structs whose fields are already pub (Proxy, HttpRequest headers, Body) keep pub accessors. --- request/src/request/proxy/struct.rs | 12 ++++++++++++ request/src/request/request_builder/struct.rs | 3 +++ request/src/request/tmp/struct.rs | 6 ++++++ type/src/stream/struct.rs | 12 ++++++++++++ 4 files changed, 33 insertions(+) diff --git a/request/src/request/proxy/struct.rs b/request/src/request/proxy/struct.rs index 2a6b787e..5c569bff 100644 --- a/request/src/request/proxy/struct.rs +++ b/request/src/request/proxy/struct.rs @@ -82,7 +82,13 @@ impl Proxy { /// 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, } @@ -90,6 +96,12 @@ pub struct ProxyTunnelStream { /// 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 0c84acb0..731071d6 100644 --- a/request/src/request/request_builder/struct.rs +++ b/request/src/request/request_builder/struct.rs @@ -30,6 +30,9 @@ use super::*; /// ``` #[derive(Clone, Data, Debug, Default)] pub struct RequestBuilder { + #[get(pub(crate))] + #[get_mut(pub(crate))] + #[set(pub(crate))] request: HttpRequest, } diff --git a/request/src/request/tmp/struct.rs b/request/src/request/tmp/struct.rs index 7eafb261..b9e4b708 100644 --- a/request/src/request/tmp/struct.rs +++ b/request/src/request/tmp/struct.rs @@ -9,7 +9,13 @@ use super::*; /// Fields are private; access through the methods on [`HttpRequest`]. #[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, } diff --git a/type/src/stream/struct.rs b/type/src/stream/struct.rs index 5b38bffb..9d8fa73e 100644 --- a/type/src/stream/struct.rs +++ b/type/src/stream/struct.rs @@ -21,11 +21,23 @@ pub struct Stream { #[derive(Data)] 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))] 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))] pub(super) end: usize, } From 44e84fb3548402814e5480c9833fab95235bf039 Mon Sep 17 00:00:00 2001 From: eastspire Date: Sun, 27 Sep 2026 10:29:33 +0800 Subject: [PATCH 5/5] refactor(type): address review comments on stream module - Move the READ_BUFFER_POOL thread_local into the module's static.rs per the workspace file-layout convention. - Drop the hand-written PooledReader::new in favor of the lombok New derive (#[new(skip)] on start/end default-initializes them to 0). - Turn Request::get_http_headers into an &mut self method so the call site no longer destructures the request; the parser fills the headers map and host string through the struct's own fields (the two mutable borrows cannot coexist as two macro accessor calls in one argument list, so the access moved inside the method). --- type/src/request/impl.rs | 14 ++++++-------- type/src/stream/fn.rs | 4 ---- type/src/stream/impl.rs | 27 +-------------------------- type/src/stream/mod.rs | 3 ++- type/src/stream/static.rs | 6 ++++++ type/src/stream/struct.rs | 4 +++- 6 files changed, 18 insertions(+), 40 deletions(-) create mode 100644 type/src/stream/static.rs diff --git a/type/src/request/impl.rs b/type/src/request/impl.rs index 3e21142d..03c49139 100644 --- a/type/src/request/impl.rs +++ b/type/src/request/impl.rs @@ -525,34 +525,32 @@ impl Request { Ok(length) } - /// Parses HTTP headers from a buffered reader into the given map. + /// 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. The target map and host string - /// are expected to be empty; they are filled without reallocating when they already - /// have sufficient capacity. + /// 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 RequestHeaders` - The map to insert parsed headers into. - /// - `&mut RequestHost` - The string to write the Host header value into. /// /// # Returns /// /// - `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, - headers: &mut RequestHeaders, - host: &mut RequestHost, ) -> Result where R: AsyncBufReadExt + Unpin, { + 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(); diff --git a/type/src/stream/fn.rs b/type/src/stream/fn.rs index 839f893b..3b03e11f 100644 --- a/type/src/stream/fn.rs +++ b/type/src/stream/fn.rs @@ -1,9 +1,5 @@ use super::*; -thread_local! { - static READ_BUFFER_POOL: RefCell>> = const { RefCell::new(Vec::new()) }; -} - /// Takes a read buffer from the thread-local pool or allocates a new one. /// /// # Arguments diff --git a/type/src/stream/impl.rs b/type/src/stream/impl.rs index fbaadbb4..bf9b930a 100644 --- a/type/src/stream/impl.rs +++ b/type/src/stream/impl.rs @@ -153,29 +153,6 @@ impl Drop for PooledReader<'_> { /// /// Buffered bytes are served first; once drained, reads are delegated /// directly to the underlying stream. -impl<'a> PooledReader<'a> { - /// Creates a new pooled reader over `stream` with the given reusable - /// `buffer`. Field accessors come from the `Data` derive. - /// - /// # Arguments - /// - /// - `&'a mut TcpStream` - The stream to read from. - /// - `Vec` - The reusable read buffer (capacity preserved across - /// keep-alive requests). - /// - /// # Returns - /// - /// - `Self`: The reader with an empty valid-data region. - pub(crate) fn new(stream: &'a mut TcpStream, buffer: Vec) -> Self { - Self { - stream, - buffer, - start: 0, - end: 0, - } - } -} - impl AsyncRead for PooledReader<'_> { /// Polls to read data into the provided buffer. /// @@ -305,9 +282,7 @@ impl Stream { 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 Request { headers, host, .. } = request; - let content_size: usize = - Request::get_http_headers(&mut reader, &config, headers, host).await?; + 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?; diff --git a/type/src/stream/mod.rs b/type/src/stream/mod.rs index 534afdcc..c312841a 100644 --- a/type/src/stream/mod.rs +++ b/type/src/stream/mod.rs @@ -1,10 +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::*; +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 9d8fa73e..a9afd6a8 100644 --- a/type/src/stream/struct.rs +++ b/type/src/stream/struct.rs @@ -18,7 +18,7 @@ pub struct Stream { /// 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)] +#[derive(Data, New)] pub(crate) struct PooledReader<'a> { /// The underlying TCP stream being read. #[get(pub(crate))] @@ -34,10 +34,12 @@ pub(crate) struct PooledReader<'a> { #[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, }