diff --git a/crates/extra/src/affix_state.rs b/crates/extra/src/affix_state.rs index 24e836491..8daba6ebe 100644 --- a/crates/extra/src/affix_state.rs +++ b/crates/extra/src/affix_state.rs @@ -1,4 +1,8 @@ -//! Middleware for add any data to depot. +//! Middleware for adding shared application state to the request context. +//! +//! This middleware allows you to inject any data into the Depot, making it +//! available to all subsequent handlers in the request pipeline. This is useful +//! for sharing configuration, database connections, and other application state. //! //! # Example //! @@ -76,7 +80,9 @@ where } } -/// Inject a value into depot. +/// Inject a typed value into depot using the type's ID as the key. +/// +/// This is useful when you want to access the value by its type rather than by an explicit key. /// /// View [module level documentation](index.html) for more details. #[inline] @@ -84,7 +90,9 @@ pub fn inject(value: V) -> AffixList { insert(format!("{:?}", TypeId::of::()), value) } -/// Insert a key-value pair into depot. +/// Insert a key-value pair into depot with an explicit key. +/// +/// Use this when you need to access the value using a specific key string. /// /// View [module level documentation](index.html) for more details. #[inline] diff --git a/crates/extra/src/basic_auth.rs b/crates/extra/src/basic_auth.rs index a92f7b0d3..933ae833a 100644 --- a/crates/extra/src/basic_auth.rs +++ b/crates/extra/src/basic_auth.rs @@ -1,4 +1,7 @@ -//! Middleware for basic authentication. +//! Middleware for HTTP Basic Authentication. +//! +//! This middleware implements the standard HTTP Basic Authentication scheme as described in RFC 7617. +//! It extracts credentials from the Authorization header and validates them against your custom validator. //! //! # Example //! @@ -35,14 +38,18 @@ use salvo_core::{async_trait, Depot, Error, FlowCtrl, Handler}; /// key used when insert into depot. pub const USERNAME_KEY: &str = "::salvo::basic_auth::username"; -/// BasicAuthValidator +/// Validator for Basic Authentication credentials. pub trait BasicAuthValidator: Send + Sync { - /// Validate is that username and password is right. + /// Validates whether the provided username and password are correct. + /// + /// Implement this method to check credentials against your authentication system. + /// Return `true` if authentication succeeds, `false` otherwise. fn validate(&self, username: &str, password: &str, depot: &mut Depot) -> impl Future + Send; } -/// BasicAuthDepotExt + +/// Extension trait for retrieving the authenticated username from a Depot. pub trait BasicAuthDepotExt { - /// Get basic auth username reference. + /// Returns the authenticated username if authentication was successful. fn basic_auth_username(&self) -> Option<&str>; } diff --git a/crates/extra/src/caching_headers.rs b/crates/extra/src/caching_headers.rs index bf477c2ef..900ab9a32 100644 --- a/crates/extra/src/caching_headers.rs +++ b/crates/extra/src/caching_headers.rs @@ -1,8 +1,8 @@ -//! Middleware for etag and last-modified-since headers. +//! Middleware for handling ETag and Last-Modified headers. //! //! This crate provides three handlers: [`ETag`], [`Modified`], and [`CachingHeaders`]. -//! Unless you are sure that you _don't_ want either etag or last-modified -//! behavior, please use the combined [`CachingHeaders`] handler. +//! Unless you are sure that you _don't_ want either ETag or Last-Modified +//! behavior, use the combined [`CachingHeaders`] handler for better cache control. use etag::EntityTag; use salvo_core::http::header::{ETAG, IF_NONE_MATCH}; @@ -157,6 +157,10 @@ impl Handler for Modified { } /// A combined handler that provides both [`ETag`] and [`Modified`] behavior. +/// +/// This handler helps improve performance by preventing unnecessary data transfers +/// when a client already has the latest version of a resource, as determined by +/// either ETag or Last-Modified comparisons. #[derive(Clone, Debug, Copy, Default)] pub struct CachingHeaders(Modified, ETag); diff --git a/crates/extra/src/catch_panic.rs b/crates/extra/src/catch_panic.rs index 634f6a989..f5e35b416 100644 --- a/crates/extra/src/catch_panic.rs +++ b/crates/extra/src/catch_panic.rs @@ -30,7 +30,10 @@ use salvo_core::http::{Request, Response, StatusError}; use salvo_core::{async_trait, Depot, FlowCtrl, Error, Handler}; -/// Middleware for catch panic in handlers. +/// Middleware that catches panics in handlers and converts them to HTTP 500 responses. +/// +/// This middleware should be registered as the first middleware in your router chain +/// to ensure it catches panics from all subsequent handlers and middlewares. /// /// View [module level documentation](index.html) for more details. #[derive(Default, Debug)] diff --git a/crates/extra/src/concurrency_limiter.rs b/crates/extra/src/concurrency_limiter.rs index 6da820a47..0036cad0a 100644 --- a/crates/extra/src/concurrency_limiter.rs +++ b/crates/extra/src/concurrency_limiter.rs @@ -1,8 +1,9 @@ -//! Middleware for limit concurrency. +//! Middleware for limiting concurrency. //! -//! Limit the max number of requests being concurrently processed. +//! This middleware limits the maximum number of requests being processed concurrently, +//! which helps prevent server overload during traffic spikes. //! -//! Example: +//! # Example //! //! ```no_run //! use std::fs::create_dir_all; diff --git a/crates/flash/src/session_store.rs b/crates/flash/src/session_store.rs index 412bddd53..f2767cd38 100644 --- a/crates/flash/src/session_store.rs +++ b/crates/flash/src/session_store.rs @@ -7,7 +7,7 @@ use super::{Flash, FlashHandler, FlashStore}; #[derive(Debug)] #[non_exhaustive] pub struct SessionStore { - /// The cookie name for the flash messages. + /// The session key for the flash messages. pub name: String, } impl Default for SessionStore { @@ -24,13 +24,13 @@ impl SessionStore { } } - /// Sets cookie name. + /// Sets session key name. pub fn name(mut self, name: impl Into) -> Self { self.name = name.into(); self } - /// Into `FlashHandler`. + /// Converts into `FlashHandler`. pub fn into_handler(self) -> FlashHandler { FlashHandler::new(self) } @@ -43,13 +43,13 @@ impl FlashStore for SessionStore { async fn save_flash(&self, _req: &mut Request, depot: &mut Depot, _res: &mut Response, flash: Flash) { if let Err(e) = depot .session_mut() - .expect("session must be exist") + .expect("session must exist") .insert(&self.name, flash) { tracing::error!(error = ?e, "save flash to session failed"); } } async fn clear_flash(&self, depot: &mut Depot, _res: &mut Response) { - depot.session_mut().expect("session must be exist").remove(&self.name); + depot.session_mut().expect("session must exist").remove(&self.name); } } diff --git a/crates/jwt-auth/README.md b/crates/jwt-auth/README.md index 9bd278607..c9387bca5 100644 --- a/crates/jwt-auth/README.md +++ b/crates/jwt-auth/README.md @@ -37,9 +37,19 @@ Salvo is an extremely simple and powerful Rust web backend framework. Only basic # salvo-jwt-auth -## Jwt auth middleware for Salvo. +JWT (JSON Web Token) authentication middleware for the Salvo web framework. -This is an official crate, so you can enable it in `Cargo.toml` like this: +## Features + +- **Flexible token extraction**: Extract tokens from headers, query parameters, cookies, or form data +- **Multiple authentication strategies**: Use either static keys or OpenID Connect for token validation +- **Easy integration**: Works seamlessly within Salvo's middleware system +- **Type-safe claims**: Decode tokens into your own custom claims structs +- **Configurable validation**: Customize token validation rules + +## Installation + +This is an official crate, so you can enable it in `Cargo.toml`: ```toml salvo = { version = "*", features = ["jwt-auth"] } diff --git a/crates/jwt-auth/src/decoder.rs b/crates/jwt-auth/src/decoder.rs index e2a13d1c7..b2b257063 100644 --- a/crates/jwt-auth/src/decoder.rs +++ b/crates/jwt-auth/src/decoder.rs @@ -4,12 +4,33 @@ use serde::Deserialize; use salvo_core::Depot; -/// JwtAuthDecoder is used to decode a token into claims. +/// Trait for JWT token decoding and validation. +/// +/// Implementors of this trait are responsible for decoding JWT tokens into claims objects +/// and performing any necessary validation. The `JwtAuth` middleware uses the configured +/// decoder to validate tokens extracted from requests. +/// +/// The crate provides built-in implementations: +/// - `ConstDecoder`: Uses a static key for token validation +/// - `OidcDecoder`: Uses OpenID Connect for validation (requires the `oidc` feature) pub trait JwtAuthDecoder { - /// Error type. + /// The error type returned if decoding or validation fails. type Error: std::error::Error + Send + Sync + 'static; - ///Decode token. + /// Decodes and validates a JWT token. + /// + /// # Parameters + /// + /// * `token` - The JWT token string to decode + /// * `depot` - The current request's depot, which can be used to store/retrieve additional data + /// + /// # Type Parameters + /// + /// * `C` - The claims type to deserialize from the token payload + /// + /// # Returns + /// + /// Returns a `Result` containing either the decoded token data or an error. fn decode( &self, token: &str, @@ -19,21 +40,28 @@ pub trait JwtAuthDecoder { C: for<'de> Deserialize<'de>; } -/// ConstDecoder will decode token with a static secret. +/// A decoder that uses a constant key for JWT token validation. +/// +/// This is the simplest decoder implementation, suitable for applications using +/// symmetric key signing (HMAC) or a single asymmetric key pair (RSA, ECDSA). pub struct ConstDecoder { + /// Key used for validating JWT signatures decoding_key: DecodingKey, + + /// JWT validation parameters validation: Validation, } impl ConstDecoder { - /// Create a new `ConstDecoder`. + /// Creates a new decoder with the given decoding key and default validation. pub fn new(decoding_key: DecodingKey) -> Self { Self { decoding_key, validation: Validation::default(), } } - /// Create a new `ConstDecoder` with validation. + + /// Creates a new decoder with the given decoding key and custom validation parameters. pub fn with_validation(decoding_key: DecodingKey, validation: Validation) -> Self { Self { decoding_key, @@ -41,19 +69,22 @@ impl ConstDecoder { } } - /// If you're using HMAC, use this. + /// Creates a decoder from a raw secret byte array for HMAC verification. + /// + /// This is the most common method for symmetric key validation. pub fn from_secret(secret: &[u8]) -> Self { Self::with_validation(DecodingKey::from_secret(secret), Validation::default()) } - /// If you're using HMAC with a base64 encoded secret, use this. + /// Creates a decoder from a base64-encoded secret string for HMAC verification. pub fn from_base64_secret(secret: &str) -> Result { DecodingKey::from_base64_secret(secret) .map(|key| Self::with_validation(key, Validation::default())) } - /// If you are loading a public RSA key in a PEM format, use this. - /// Only exists if the feature `use_pem` is enabled. + /// Creates a decoder from an RSA public key in PEM format. + /// + /// Only available when the `use_pem` feature is enabled. pub fn from_rsa_pem(key: &[u8]) -> Result { DecodingKey::from_rsa_pem(key) .map(|key| Self::with_validation(key, Validation::new(Algorithm::RS256))) diff --git a/crates/jwt-auth/src/finder.rs b/crates/jwt-auth/src/finder.rs index b3f504791..6249041fa 100644 --- a/crates/jwt-auth/src/finder.rs +++ b/crates/jwt-auth/src/finder.rs @@ -6,22 +6,47 @@ use salvo_core::http::{Method, Request}; use super::ALL_METHODS; -/// `JwtTokenFinder` is to provide a way to find a JWT (JSON Web Token) from a request. +/// Trait for extracting JWT tokens from HTTP requests. +/// +/// Implementors of this trait provide different strategies for locating JWT tokens +/// in various parts of an HTTP request (headers, query string, cookies, etc.). +/// The `JwtAuth` middleware tries each configured finder in sequence until one +/// returns a token. #[async_trait] pub trait JwtTokenFinder: Send + Sync { - /// Get token from request. + /// Attempts to extract a JWT token from the request. /// - /// The token is returned as an `Option`, where Some contains the token if found, and `None` if not found. + /// Returns `Some(String)` containing the token if found, or `None` if no token + /// could be extracted using this finder's strategy. async fn find_token(&self, req: &mut Request) -> Option; } -/// `HeaderFinder` is to find a JWT from a request header. +/// Extracts JWT tokens from HTTP request headers. +/// +/// By default, this finder looks for Bearer tokens in the `Authorization` +/// and `Proxy-Authorization` headers for all HTTP methods. +/// +/// # Example +/// +/// ``` +/// use salvo::jwt_auth::HeaderFinder; +/// use salvo::http::Method; +/// +/// // Default configuration +/// let finder = HeaderFinder::new(); +/// +/// // Custom configuration for specific methods +/// let get_only = HeaderFinder::new() +/// .cared_methods(vec![Method::GET]); +/// ``` #[derive(Eq, PartialEq, Clone, Default)] #[non_exhaustive] pub struct HeaderFinder { - /// Cared methods list. + /// List of HTTP methods for which this finder should extract tokens. + /// If the request's method is not in this list, the finder will not attempt extraction. pub cared_methods: Vec, - /// Header names. + + /// List of headers names to check for Bearer tokens. pub header_names: Vec, } impl HeaderFinder { @@ -76,13 +101,30 @@ impl JwtTokenFinder for HeaderFinder { } } -/// `FormFinder` is to find a JWT from a request form. +/// Extracts JWT tokens from request form data. +/// +/// This finder looks for a token in the request's form data using a specified field name. +/// +/// # Example +/// +/// ``` +/// use salvo::jwt_auth::FormFinder; +/// use salvo::http::Method; +/// +/// // Create finder that looks for a form field named "access_token" +/// let finder = FormFinder::new("access_token"); +/// +/// // Limit to POST requests only +/// let post_only = FormFinder::new("access_token") +/// .cared_methods(vec![Method::POST]); +/// ``` #[derive(Eq, PartialEq, Clone, Default)] #[non_exhaustive] pub struct FormFinder { - /// Cared methods list. + /// List of HTTP methods for which this finder should extract tokens. pub cared_methods: Vec, - /// Form field name. + + /// Name of the form field containing the token. pub field_name: Cow<'static, str>, } impl FormFinder { @@ -118,13 +160,30 @@ impl JwtTokenFinder for FormFinder { } } -/// `QueryFinder` is to find a JWT from a request query string. +/// Extracts JWT tokens from URL query parameters. +/// +/// This finder looks for a token in the request's query string using a specified parameter name. +/// +/// # Example +/// +/// ``` +/// use salvo::jwt_auth::QueryFinder; +/// use salvo::http::Method; +/// +/// // Create finder that looks for query parameter "token" +/// let finder = QueryFinder::new("token"); +/// +/// // Limit to GET requests only +/// let get_only = QueryFinder::new("token") +/// .cared_methods(vec![Method::GET]); +/// ``` #[derive(Eq, PartialEq, Clone, Default)] #[non_exhaustive] pub struct QueryFinder { - /// Cared methods list. + /// List of HTTP methods for which this finder should extract tokens. pub cared_methods: Vec, - /// Query name. + + /// Name of the query parameter containing the token. pub query_name: Cow<'static, str>, } impl QueryFinder { @@ -161,13 +220,30 @@ impl JwtTokenFinder for QueryFinder { } } -/// CookieFinder +/// Extracts JWT tokens from cookies. +/// +/// This finder looks for a token in the request's cookies using a specified cookie name. +/// +/// # Example +/// +/// ``` +/// use salvo::jwt_auth::CookieFinder; +/// use salvo::http::Method; +/// +/// // Create finder that looks for cookie named "jwt" +/// let finder = CookieFinder::new("jwt"); +/// +/// // Limit to specific methods +/// let restricted = CookieFinder::new("jwt") +/// .cared_methods(vec![Method::GET, Method::POST]); +/// ``` #[derive(Eq, PartialEq, Clone, Default)] #[non_exhaustive] pub struct CookieFinder { - /// Cared methods list. + /// List of HTTP methods for which this finder should extract tokens. pub cared_methods: Vec, - /// Cookie name. + + /// Name of the cookie containing the token. pub cookie_name: Cow<'static, str>, } impl CookieFinder { diff --git a/crates/jwt-auth/src/lib.rs b/crates/jwt-auth/src/lib.rs index e4863a841..9ede4bf1a 100644 --- a/crates/jwt-auth/src/lib.rs +++ b/crates/jwt-auth/src/lib.rs @@ -1,6 +1,17 @@ -//! Provide JWT authentication support for Salvo web framework. +//! Provides JWT (JSON Web Token) authentication support for the Salvo web framework. //! -//! Example: +//! This crate helps you implement JWT-based authentication in your Salvo web applications. +//! It offers flexible token extraction from various sources (headers, query parameters, cookies, etc.) +//! and multiple decoding strategies. +//! +//! # Features +//! +//! - Extract JWT tokens from multiple sources (headers, query parameters, cookies, forms) +//! - Configurable token validation +//! - OpenID Connect support (behind the `oidc` feature flag) +//! - Seamless integration with Salvo's middleware system +//! +//! # Example: //! //! ```no_run //! use jsonwebtoken::{self, EncodingKey}; @@ -10,7 +21,7 @@ //! use serde::{Deserialize, Serialize}; //! use time::{Duration, OffsetDateTime}; //! -//! const SECRET_KEY: &str = "YOUR SECRET_KEY"; +//! const SECRET_KEY: &str = "YOUR_SECRET_KEY"; // In production, use a secure key management solution //! //! #[derive(Debug, Serialize, Deserialize)] //! pub struct JwtClaims { @@ -60,7 +71,7 @@ //! JwtAuthState::Authorized => { //! let data = depot.jwt_auth_data::().unwrap(); //! res.render(Text::Plain(format!( -//! "Hi {}, have logged in successfully!", +//! "Hi {}, you have logged in successfully!", //! data.claims.username //! ))); //! } @@ -76,6 +87,7 @@ //! } //! //! fn validate(username: &str, password: &str) -> bool { +//! // In a real application, use secure password verification //! username == "root" && password == "pwd" //! } //! @@ -199,27 +211,43 @@ pub enum JwtAuthError { MissingKid, } -/// JwtAuthState +/// Possible states of JWT authentication. +/// +/// The middleware sets this state in the depot after processing a request. +/// You can access it via `depot.jwt_auth_state()`. #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum JwtAuthState { - /// Authorized. Used if decoding jwt token successfully. + /// Authentication was successful and the token was valid. Authorized, - /// Unauthorized. Used if no jwt token is provided. + /// No token was provided in the request. + /// Usually results in a 401 Unauthorized response unless `force_passed` is true. Unauthorized, - /// Forbidden. Used if decoding jwt token failed. + /// A token was provided but it failed validation. + /// Usually results in a 403 Forbidden response unless `force_passed` is true. Forbidden, } -/// JwtAuthDepotExt + +/// Extension trait for accessing JWT authentication data from the depot. +/// +/// This trait provides convenient methods to retrieve JWT authentication information +/// that was previously stored in the depot by the `JwtAuth` middleware. pub trait JwtAuthDepotExt { - /// get jwt auth token reference from depot. + /// Gets the JWT token string from the depot. fn jwt_auth_token(&self) -> Option<&str>; - /// get jwt auth decoded data from depot. + + /// Gets the decoded JWT claims data from the depot. + /// + /// The generic parameter `C` should be the same type used when configuring the `JwtAuth` middleware. fn jwt_auth_data(&self) -> Option<&TokenData> where C: DeserializeOwned + Send + Sync + 'static; - /// get jwt auth state from depot. + + /// Gets the current JWT authentication state from the depot. + /// + /// Returns `JwtAuthState::Unauthorized` if no state is present in the depot. fn jwt_auth_state(&self) -> JwtAuthState; - /// get jwt auth error from depot. + + /// Gets the JWT error if authentication failed. fn jwt_auth_error(&self) -> Option<&JwtError>; } @@ -251,17 +279,28 @@ impl JwtAuthDepotExt for Depot { } } -/// JwtAuth, used as middleware. +/// JWT Authentication middleware for Salvo. +/// +/// `JwtAuth` extracts and validates JWT tokens from incoming requests based on the configured +/// token finders and decoder. If valid, it stores the decoded data in the depot for later use. +/// +/// # Type Parameters +/// +/// * `C` - The claims type that will be deserialized from the JWT payload. +/// * `D` - The decoder implementation used to validate and decode the JWT token. #[non_exhaustive] pub struct JwtAuth { - /// Only write auth state to depot when set to `true`. + /// When set to `true`, the middleware will allow the request to proceed even if + /// authentication fails, storing only the authentication state in the depot. /// - /// **Note**: If you set to `true`, you must handle auth state in next middlewares or handler. + /// When set to `false` (default), requests with invalid or missing tokens will be + /// immediately rejected with appropriate status codes. pub force_passed: bool, _claims: PhantomData, - /// The decoder. + /// The decoder used to validate and decode the JWT token. pub decoder: D, - /// The finders list. + /// A list of token finders that will be used to extract the token from the request. + /// Finders are tried in order until one returns a token. pub finders: Vec>, } diff --git a/crates/proxy/Cargo.toml b/crates/proxy/Cargo.toml index ac8f07b95..3465befc9 100644 --- a/crates/proxy/Cargo.toml +++ b/crates/proxy/Cargo.toml @@ -5,7 +5,8 @@ authors = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } description = """ -TBD: Proxy support for salvo web server framework. +HTTP proxy support for the Salvo web server framework. Provides flexible proxy middleware +for forwarding requests to upstream servers. """ homepage = { workspace = true } repository = { workspace = true } diff --git a/crates/proxy/README.md b/crates/proxy/README.md index 5a10dfdb6..bd9f915a5 100644 --- a/crates/proxy/README.md +++ b/crates/proxy/README.md @@ -37,7 +37,20 @@ Salvo is an extremely simple and powerful Rust web backend framework. Only basic # salvo-proxy -## Proxy middleware for Salvo. +## Proxy middleware for Salvo + +This crate provides proxy capabilities for the Salvo web framework, allowing you to forward requests to upstream servers. It's useful for creating API gateways, load balancers, and reverse proxies. + +### Features + +- Support for HTTP and HTTPS proxying +- Multiple upstream server selection strategies +- WebSocket connection support +- Header manipulation +- Path and query rewriting +- Multiple HTTP client backends (Hyper, Reqwest) + +### Usage This is an official crate, so you can enable it in `Cargo.toml` like this: diff --git a/crates/proxy/src/hyper_client.rs b/crates/proxy/src/hyper_client.rs index 03d26abea..91c064afc 100644 --- a/crates/proxy/src/hyper_client.rs +++ b/crates/proxy/src/hyper_client.rs @@ -10,6 +10,9 @@ use tokio::io::copy_bidirectional; use crate::{Client, HyperRequest, Proxy, BoxedError, Upstreams, HyperResponse}; /// A [`Client`] implementation based on [`hyper_util::client::legacy::Client`]. +/// +/// This client provides proxy capabilities using the Hyper HTTP client library. +/// It's lightweight and tightly integrated with the Tokio runtime. #[derive(Clone, Debug)] pub struct HyperClient { inner: HyperUtilClient, ReqBody>, @@ -34,7 +37,9 @@ where U: Upstreams, U::Error: Into, { - /// Create new `Proxy` which use default hyper util client. + /// Create a new `Proxy` using the default Hyper client. + /// + /// This is a convenient way to create a proxy with standard configuration. pub fn use_hyper_client(upstreams: U) -> Self { Proxy::new(upstreams, HyperClient::default()) } @@ -120,6 +125,7 @@ mod tests { .take_string() .await .unwrap(); + println!("{}", content); assert!(content.contains("Install Rust")); } diff --git a/crates/proxy/src/lib.rs b/crates/proxy/src/lib.rs index d231096cd..4732f2527 100644 --- a/crates/proxy/src/lib.rs +++ b/crates/proxy/src/lib.rs @@ -1,10 +1,14 @@ -//! Provide proxy support for Salvo web framework. +//! Provide HTTP proxy capabilities for the Salvo web framework. +//! +//! This crate allows you to easily forward requests to upstream servers, +//! supporting both HTTP and HTTPS protocols. It's useful for creating API gateways, +//! load balancers, and reverse proxies. //! //! # Example //! -//! In this example, if the requested URL begins with , the proxy goes to -//! ; if the requested URL begins with , the proxy -//! goes to . +//! In this example, requests to different hosts are proxied to different upstream servers: +//! - Requests to http://127.0.0.1:5800/ are proxied to https://www.rust-lang.org +//! - Requests to http://localhost:5800/ are proxied to https://crates.io //! //! ```no_run //! use salvo_core::prelude::*; @@ -70,11 +74,15 @@ pub(crate) fn encode_url_path(path: &str) -> String { .join("/") } -/// Client trait. +/// Client trait for implementing different HTTP clients for proxying. +/// +/// Implement this trait to create custom proxy clients with different +/// backends or configurations. pub trait Client: Send + Sync + 'static { - /// Error type. + /// Error type returned by the client. type Error: StdError + Send + Sync + 'static; - /// Elect a upstream to process current request. + + /// Execute a request through the proxy client. fn execute( &self, req: HyperRequest, @@ -82,11 +90,16 @@ pub trait Client: Send + Sync + 'static { ) -> impl Future> + Send; } -/// Upstreams trait. +/// Upstreams trait for selecting target servers. +/// +/// Implement this trait to customize how target servers are selected +/// for proxying requests. This can be used to implement load balancing, +/// failover, or other server selection strategies. pub trait Upstreams: Send + Sync + 'static { - /// Error type. + /// Error type returned when selecting a server fails. type Error: StdError + Send + Sync + 'static; - /// Elect a upstream to process current request. + + /// Elect a server to handle the current request. fn elect(&self) -> impl Future> + Send; } impl Upstreams for &'static str { diff --git a/crates/proxy/src/reqwest_client.rs b/crates/proxy/src/reqwest_client.rs index 4509830f8..b028032d6 100644 --- a/crates/proxy/src/reqwest_client.rs +++ b/crates/proxy/src/reqwest_client.rs @@ -9,6 +9,10 @@ use tokio::io::copy_bidirectional; use crate::{Client, HyperRequest, BoxedError, Proxy, Upstreams, HyperResponse}; /// A [`Client`] implementation based on [`reqwest::Client`]. +/// +/// This client provides proxy capabilities using the Reqwest HTTP client. +/// It supports all features of Reqwest including automatic redirect handling, +/// connection pooling, and other HTTP client features. #[derive(Default, Clone, Debug)] pub struct ReqwestClient { inner: InnerClient, @@ -19,7 +23,9 @@ where U: Upstreams, U::Error: Into, { - /// Create new `Proxy` which use default reqwest util client. + /// Create a new `Proxy` using the default Reqwest client. + /// + /// This is a convenient way to create a proxy with standard configuration. pub fn use_reqwest_client(upstreams: U) -> Self { Proxy::new(upstreams, ReqwestClient::default()) } diff --git a/crates/serve-static/src/README.md b/crates/serve-static/src/README.md index 9cb8d3332..7c67abe40 100644 --- a/crates/serve-static/src/README.md +++ b/crates/serve-static/src/README.md @@ -1,9 +1,15 @@ -# salvo-rate-limiter +# salvo-serve-static - -> Rate limit for Salvo. +> Serve static files and directories for Salvo web framework. ## Documentation & Resources -- [API Documentation](https://docs.rs/salvo-rate-limiter) -- [Example Projects](https://github.com/salvo-rs/salvo/examples/) \ No newline at end of file +- [API Documentation](https://docs.rs/salvo-serve-static) +- [Example Projects](https://github.com/salvo-rs/salvo/tree/main/examples/serve-static) + +## Features + +- Serve static files with automatic content type detection +- Directory listing with multiple format support (HTML, JSON, XML, Text) +- Support for compressed file variants (Brotli, Gzip, Deflate, Zstd) +- Embedded file serving with rust-embed integration \ No newline at end of file diff --git a/crates/serve-static/src/dir.rs b/crates/serve-static/src/dir.rs index c41185c46..6c35fa7d2 100644 --- a/crates/serve-static/src/dir.rs +++ b/crates/serve-static/src/dir.rs @@ -1,4 +1,4 @@ -//! serve static dir +//! Serve static directories with directory listing support use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; @@ -22,17 +22,17 @@ use super::{ decode_url_path_safely, encode_url_path, format_url_path_safely, join_path, redirect_to_dir_url, }; -/// CompressionAlgo +/// Supported compression algorithms for serving compressed file variants #[derive(Eq, PartialEq, Clone, Copy, Debug, Hash)] #[non_exhaustive] pub enum CompressionAlgo { - /// Brotli + /// Brotli compression Brotli, - /// Deflate + /// Deflate compression Deflate, - /// Gzip + /// Gzip compression Gzip, - /// Zstd + /// Zstandard compression Zstd, } impl FromStr for CompressionAlgo { @@ -122,32 +122,41 @@ where } } -/// Handler that serves a directory. +/// Handler that serves static files from directories. +/// +/// This handler can serve files from one or more directory paths, +/// with support for directory listing, compressed file variants, +/// and default files. +/// +/// # Examples +/// +/// ``` +/// use salvo_core::prelude::*; +/// use salvo_serve_static::StaticDir; +/// +/// let router = Router::new() +/// .push(Router::with_path("static/<**>") +/// .get(StaticDir::new(["assets", "static"]) +/// .defaults("index.html") +/// .auto_list(true))); +/// ``` #[non_exhaustive] pub struct StaticDir { - /// Static roots. + /// Static root directories to search for files pub roots: Vec, - /// During the file chunk read, the maximum read size at one time will affect the - /// access experience and the demand for server memory. - /// - /// Please set it according to your own situation. - /// - /// The default is 1M. + /// Chunk size for file reading (in bytes) pub chunk_size: Option, - /// List dot files. + /// Whether to include dot files (files/directories starting with .) pub include_dot_files: bool, #[allow(clippy::type_complexity)] exclude_filters: Vec bool + Send + Sync>>, - /// Auto list the directory if default file not found. + /// Whether to automatically list directories when default file isn't found pub auto_list: bool, - /// Compressed variations. - /// - /// The key is the compression algorithm, and the value is the file extension. - /// If the compression file exists, it will serve the compressed file instead of the original file. + /// Map of compression algorithms to file extensions for compressed variants pub compressed_variations: HashMap>, - /// Default file names list. + /// Default file names to look for in directories (e.g., "index.html") pub defaults: Vec, - /// Fallback file name. This is used when the requested file is not found. + /// Fallback file to serve when requested file isn't found pub fallback: Option, } impl StaticDir { diff --git a/crates/serve-static/src/embed.rs b/crates/serve-static/src/embed.rs index f7f89b595..6d5dc2096 100644 --- a/crates/serve-static/src/embed.rs +++ b/crates/serve-static/src/embed.rs @@ -9,18 +9,21 @@ use salvo_core::handler::{ Handler}; use super::{decode_url_path_safely, format_url_path_safely, join_path, redirect_to_dir_url}; -/// Handler that serves embed file. +/// Handler that serves embedded files using `rust-embed`. +/// +/// This handler allows serving files embedded in the application binary, +/// which is useful for distributing a self-contained executable. #[non_exhaustive] #[derive(Default)] pub struct StaticEmbed { _assets: PhantomData, - /// Default file names list. + /// Default file names list (e.g., "index.html") pub defaults: Vec, - /// Fallback file name. This is used when the requested file is not found. + /// Fallback file name used when the requested file isn't found pub fallback: Option, } -/// Create a new `StaticEmbed` middleware. +/// Create a new `StaticEmbed` handler for the given embedded asset type. #[inline] pub fn static_embed() -> StaticEmbed { StaticEmbed { @@ -30,7 +33,7 @@ pub fn static_embed() -> StaticEmbed { } } -/// Render [`EmbeddedFile`] to [`Response`]. +/// Render an [`EmbeddedFile`] to the [`Response`]. #[inline] pub fn render_embedded_file(file: EmbeddedFile, req: &Request, res: &mut Response, mime: Option) { let EmbeddedFile { data, metadata, .. } = file; diff --git a/crates/serve-static/src/file.rs b/crates/serve-static/src/file.rs index 1234e4a79..ab3c45346 100644 --- a/crates/serve-static/src/file.rs +++ b/crates/serve-static/src/file.rs @@ -4,23 +4,41 @@ use salvo_core::fs::{NamedFile, NamedFileBuilder}; use salvo_core::http::{Request, Response, StatusError}; use salvo_core::{Depot, FlowCtrl, Handler, Writer, async_trait}; -/// `StaticFile` is a handler that serves a single file. +/// `StaticFile` is a handler that serves a single static file. +/// +/// # Examples +/// +/// ``` +/// use salvo_core::prelude::*; +/// use salvo_serve_static::StaticFile; +/// +/// #[handler] +/// async fn hello() -> &'static str { +/// "Hello World" +/// } +/// +/// let router = Router::new() +/// .get(hello) +/// .push(Router::with_path("favicon.ico").get(StaticFile::new("assets/favicon.ico"))); +/// ``` #[derive(Clone)] pub struct StaticFile(NamedFileBuilder); impl StaticFile { - /// Create a new `StaticFile`. + /// Create a new `StaticFile` handler. #[inline] pub fn new(path: impl Into) -> Self { StaticFile(NamedFile::builder(path)) } - /// During the file chunk read, the maximum read size at one time will affect the - /// access experience and the demand for server memory. + /// Set the chunk size for file reading. /// - /// Please set it according to your own situation. + /// During file reading, the maximum read size at one time will affect the + /// access experience and memory usage of the server. /// - /// The default is 1M. + /// Please set it according to your specific requirements. + /// + /// The default is 1MB. #[inline] pub fn chunk_size(self, size: u64) -> Self { Self(self.0.buffer_size(size)) diff --git a/crates/serve-static/src/lib.rs b/crates/serve-static/src/lib.rs index 22d9434c6..e19596a39 100644 --- a/crates/serve-static/src/lib.rs +++ b/crates/serve-static/src/lib.rs @@ -1,4 +1,9 @@ -//! serve static dir and file middleware for Salvo web framework. +//! Serve static files and directories for Salvo web framework. +//! +//! This crate provides handlers for serving static content: +//! - `StaticDir` - Serve files from directory with options for directory listing +//! - `StaticFile` - Serve a single file +//! - `StaticEmbed` - Serve embedded files using rust-embed (when "embed" feature is enabled) //! //! Read more: #![doc(html_favicon_url = "https://salvo.rs/favicon-32x32.png")] diff --git a/crates/session/src/lib.rs b/crates/session/src/lib.rs index 5efe0032d..24ded1db6 100644 --- a/crates/session/src/lib.rs +++ b/crates/session/src/lib.rs @@ -1,67 +1,62 @@ /*! -# Salvo session support +# Salvo Session Support -Salvo session middleware is built on top of +Salvo's session middleware is built on top of [`async-session`](https://github.com/http-rs/async-session). -An example: [`session-login`](https://github.com/salvo-rs/salvo/tree/main/examples/session-login) +See a complete example: [`session-login`](https://github.com/salvo-rs/salvo/tree/main/examples/session-login) -Sessions allows salvo to securely attach data to a browser session -allowing for retrieval and modification of this data within salvo -on subsequent visits. Session data is generally only retained for the -duration of a browser session. +Sessions allow Salvo applications to securely attach data to browser sessions, +enabling retrieval and modification of this data on subsequent visits. +Session data is typically retained only for the duration of a browser session. ## Stores -It is highly recommended that salvo applications use an -external-datastore-backed session storage. For a list of currently -available session stores, see [the documentation for -async-session](https://github.com/http-rs/async-session). +It is highly recommended to use an external-datastore-backed session storage +for production Salvo applications. For a list of currently available session +stores, see [the documentation for async-session](https://github.com/http-rs/async-session). ## Security -Although each session store may have different security implications, -the general approach of salvo's session system is as follows: On -each request, salvo checks the cookie configurable as `cookie_name` -on the handler. +While each session store may have different security implications, +Salvo's session system works as follows: -### If no cookie is found: +On each request, Salvo checks for the cookie specified by `cookie_name` +in the handler configuration. -A cryptographically random cookie value is generated. A cookie is set -on the outbound response and signed with an HKDF key derived from the -`secret` provided on creation of the SessionHandler. The configurable -session store uses a SHA256 digest of the cookie value and stores the -session along with a potential expiry. +### When no cookie is found: -### If a cookie is found: +1. A cryptographically random cookie value is generated +2. A cookie is set on the outbound response and signed with an HKDF key + derived from the `secret` provided when creating the SessionHandler +3. The session store uses a SHA256 digest of the cookie value to store + the session along with an optional expiry time -The hkdf derived signing key is used to verify the cookie value's -signature. If it verifies, it is then passed to the session store to -retrieve a Session. For most session stores, this will involve taking -a SHA256 digest of the cookie value and retrieving a serialized -Session from an external datastore based on that digest. +### When a cookie is found: -### Expiry +1. The HKDF-derived signing key verifies the cookie value's signature +2. If verification succeeds, the value is passed to the session store to + retrieve the associated Session +3. For most session stores, this involves taking a SHA256 digest of the + cookie value and retrieving a serialized Session from an external datastore -In addition to setting an expiry on the session cookie, salvo -sessions include the same expiry in their serialization format. If an -adversary were able to tamper with the expiry of a cookie, salvo -sessions would still check the expiry on the contained session before -using it +### Expiry Handling -### If anything goes wrong with the above process +Sessions include expiry information in both the cookie and the serialization format. +Even if an adversary tampers with a cookie's expiry, Salvo validates +the expiry on the contained session before using it. -If there are any failures in the above session retrieval process, a -new empty session is generated for the request, which proceeds through -the application as normal. +### Error Handling -## Stale/expired session cleanup +If any failures occur during session retrieval, a new empty session +is generated for the request, which proceeds through the application normally. -Any session store other than the cookie store will accumulate stale -sessions. Although the salvo session handler ensures that they -will not be used as valid sessions, For most session stores, it is the -salvo application's responsibility to call cleanup on the session -store if it requires it. +## Stale/Expired Session Cleanup + +Any session store (except the cookie store) will accumulate stale sessions over time. +Although Salvo ensures expired sessions won't be used, it remains the +application's responsibility to periodically call cleanup on the session +store if required. Read more: */