Web Rust Web端功能示例

Web Rust Web框架功能示例,包括路由、中间件和静态文件

💻 路由处理 rust

🟡 intermediate ⭐⭐⭐⭐

URL路由和路径参数提取

⏱️ 30 min 🏷️ rust, web, web features
Prerequisites: Intermediate Rust, HTTP basics
// Web Rust Routing Examples
// URL routing and path parameter extraction
//
// For production web applications, consider using:
// - Actix Web: https://actix.rs/
// - Axum: https://github.com/tokio-rs/axum
// - Rocket: https://rocket.rs/
// - Warp: https://github.com/seanmonstar/warp

use std::collections::HashMap;
use serde::{Deserialize, Serialize};

// 1. Basic Route

/// HTTP Method
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HttpMethod {
    GET,
    POST,
    PUT,
    DELETE,
    PATCH,
    HEAD,
    OPTIONS,
}

/// HTTP Request (simplified)
#[derive(Debug, Clone)]
pub struct HttpRequest {
    pub method: HttpMethod,
    pub path: String,
    pub query_params: HashMap<String, String>,
    pub headers: HashMap<String, String>,
    pub body: Option<String>,
}

/// HTTP Response (simplified)
#[derive(Debug, Clone)]
pub struct HttpResponse {
    pub status_code: u16,
    pub headers: HashMap<String, String>,
    pub body: String,
}

impl HttpResponse {
    pub fn ok(body: &str) -> Self {
        HttpResponse {
            status_code: 200,
            headers: HashMap::new(),
            body: body.to_string(),
        }
    }

    pub fn not_found() -> Self {
        HttpResponse {
            status_code: 404,
            headers: HashMap::new(),
            body: "Not Found".to_string(),
        }
    }

    pub fn json<T: Serialize>(data: &T) -> Self {
        let mut response = HttpResponse::ok("");
        response.headers.insert("Content-Type".to_string(), "application/json".to_string());
        response.body = serde_json::to_string(data).unwrap_or_default();
        response
    }
}

// 2. Route Definition

/// Route handler type
pub type RouteHandler = fn(&HttpRequest) -> HttpResponse;

/// Route definition
#[derive(Debug, Clone)]
pub struct Route {
    pub method: HttpMethod,
    pub pattern: String,
    pub handler: RouteHandler,
    pub name: Option<String>,
}

impl Route {
    pub fn new(method: HttpMethod, pattern: &str, handler: RouteHandler) -> Self {
        Route {
            method,
            pattern: pattern.to_string(),
            handler,
            name: None,
        }
    }

    pub fn with_name(mut self, name: &str) -> Self {
        self.name = Some(name.to_string());
        self
    }
}

/// Create a GET route
pub fn get(pattern: &str, handler: RouteHandler) -> Route {
    Route::new(HttpMethod::GET, pattern, handler)
}

/// Create a POST route
pub fn post(pattern: &str, handler: RouteHandler) -> Route {
    Route::new(HttpMethod::POST, pattern, handler)
}

/// Create a PUT route
pub fn put(pattern: &str, handler: RouteHandler) -> Route {
    Route::new(HttpMethod::PUT, pattern, handler)
}

/// Create a DELETE route
pub fn delete(pattern: &str, handler: RouteHandler) -> Route {
    Route::new(HttpMethod::DELETE, pattern, handler)
}

// 3. Router

/// Simple router
pub struct Router {
    routes: Vec<Route>,
}

impl Router {
    pub fn new() -> Self {
        Router {
            routes: Vec::new(),
        }
    }

    /// Add a route
    pub fn add_route(mut self, route: Route) -> Self {
        self.routes.push(route);
        self
    }

    /// Match request to route
    pub fn match_route(&self, request: &HttpRequest) -> Option<&Route> {
        self.routes.iter()
            .find(|route| {
                route.method == request.method && self.match_pattern(&route.pattern, &request.path)
            })
    }

    /// Check if path matches pattern
    fn match_pattern(&self, pattern: &str, path: &str) -> bool {
        // Simple pattern matching
        // :id style parameters
        let pattern_parts: Vec<&str> = pattern.split('/').collect();
        let path_parts: Vec<&str> = path.split('/').collect();

        if pattern_parts.len() != path_parts.len() {
            return false;
        }

        pattern_parts.iter().zip(path_parts.iter())
            .all(|(p, actual)| {
                p.starts_with(':') || *p == *actual
            })
    }

    /// Extract path parameters
    pub fn extract_params(&self, pattern: &str, path: &str) -> HashMap<String, String> {
        let mut params = HashMap::new();
        let pattern_parts: Vec<&str> = pattern.split('/').collect();
        let path_parts: Vec<&str> = path.split('/').collect();

        for (p, actual) in pattern_parts.iter().zip(path_parts.iter()) {
            if p.starts_with(':') {
                let param_name = &p[1..];
                params.insert(param_name.to_string(), actual.to_string());
            }
        }

        params
    }

    /// Handle request
    pub fn handle(&self, request: &HttpRequest) -> HttpResponse {
        if let Some(route) = self.match_route(request) {
            (route.handler)(request)
        } else {
            HttpResponse::not_found()
        }
    }
}

impl Default for Router {
    fn default() -> Self {
        Self::new()
    }
}

// 4. URL Pattern Matching

/// Match pattern with wildcards
pub fn match_wildcard(pattern: &str, path: &str) -> bool {
    let pattern_parts: Vec<&str> = pattern.split('*').collect();

    if pattern_parts.len() == 1 {
        return pattern == path;
    }

    let mut current_pos = 0;
    for (i, part) in pattern_parts.iter().enumerate() {
        if part.is_empty() {
            continue;
        }

        if let Some(pos) = path[current_pos..].find(part) {
            current_pos += pos + part.len();
        } else {
            return false;
        }

        // Last part should match to end
        if i == pattern_parts.len() - 1 && !path.ends_with(part) {
            return false;
        }
    }

    true
}

/// Extract wildcard value
pub fn extract_wildcard(pattern: &str, path: &str) -> Option<String> {
    if !pattern.contains('*') {
        return None;
    }

    let prefix = &pattern[..pattern.find('*')?];
    let suffix = &pattern[pattern.find('*')? + 1..];

    if !path.starts_with(prefix) || !path.ends_with(suffix) {
        return None;
    }

    let start = prefix.len();
    let end = path.len() - suffix.len();

    if end <= start {
        return None;
    }

    Some(path[start..end].to_string())
}

// 5. Query Parameters

/// Parse query string
pub fn parse_query(query: &str) -> HashMap<String, String> {
    let mut params = HashMap::new();

    for pair in query.split('&') {
        let parts: Vec<&str> = pair.splitn(2, '=').collect();
        if parts.len() == 2 {
            params.insert(
                url_decode(parts[0]),
                url_decode(parts[1])
            );
        }
    }

    params
}

/// URL decode (simple implementation)
pub fn url_decode(s: &str) -> String {
    let mut result = String::new();
    let mut chars = s.chars().peekable();

    while let Some(c) = chars.next() {
        if c == '%' {
            let hex1 = chars.next().unwrap_or('0');
            let hex2 = chars.next().unwrap_or('0');
            let byte = u8::from_str_radix(&format!("{}{}", hex1, hex2), 16).unwrap_or(0);
            result.push(byte as char);
        } else if c == '+' {
            result.push(' ');
        } else {
            result.push(c);
        }
    }

    result
}

/// URL encode (simple implementation)
pub fn url_encode(s: &str) -> String {
    s.chars()
        .map(|c| match c {
            'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.' | '~' => c.to_string(),
            ' ' => "+".to_string(),
            _ => format!("%{:02X}", c as u8),
        })
        .collect()
}

// 6. Path Builders

/// Build URL with path parameters
pub fn build_path(template: &str, params: &HashMap<String, String>) -> String {
    let mut result = template.to_string();

    for (key, value) in params {
        let placeholder = format!(":{}", key);
        if result.contains(&placeholder) {
            result = result.replace(&placeholder, value);
        }
    }

    result
}

/// Build URL with query parameters
pub fn build_url(base: &str, params: &HashMap<String, String>) -> String {
    if params.is_empty() {
        return base.to_string();
    }

    let query: Vec<String> = params.iter()
        .map(|(k, v)| format!("{}={}", url_encode(k), url_encode(v)))
        .collect();

    format!("{}?{}", base, query.join("&"))
}

// 7. Route Groups

/// Route group with prefix
pub struct RouteGroup {
    prefix: String,
    routes: Vec<Route>,
}

impl RouteGroup {
    pub fn new(prefix: &str) -> Self {
        RouteGroup {
            prefix: prefix.to_string(),
            routes: Vec::new(),
        }
    }

    /// Add route to group
    pub fn add_route(mut self, mut route: Route) -> Self {
        route.pattern = format!("{}{}", self.prefix, route.pattern);
        self.routes.push(route);
        self
    }

    /// Get all routes with prefix applied
    pub fn routes(self) -> Vec<Route> {
        self.routes
    }
}

// 8. Named Routes

/// Route name registry
pub struct RouteRegistry {
    routes: HashMap<String, String>,
}

impl RouteRegistry {
    pub fn new() -> Self {
        RouteRegistry {
            routes: HashMap::new(),
        }
    }

    /// Register named route
    pub fn register(&mut self, name: &str, pattern: &str) {
        self.routes.insert(name.to_string(), pattern.to_string());
    }

    /// Generate URL from route name
    pub fn url(&self, name: &str, params: &HashMap<String, String>) -> Option<String> {
        let pattern = self.routes.get(name)?;
        Some(build_path(pattern, params))
    }
}

impl Default for RouteRegistry {
    fn default() -> Self {
        Self::new()
    }
}

// 9. Route Middleware

/// Route middleware type
pub type RouteMiddleware = Box<dyn Fn(&HttpRequest) -> Option<HttpResponse> + Send + Sync>;

/// Route with middleware
pub struct RouteWithMiddleware {
    route: Route,
    middleware: Vec<RouteMiddleware>,
}

impl RouteWithMiddleware {
    pub fn new(route: Route) -> Self {
        RouteWithMiddleware {
            route,
            middleware: Vec::new(),
        }
    }

    /// Add middleware
    pub fn add_middleware(mut self, middleware: RouteMiddleware) -> Self {
        self.middleware.push(middleware);
        self
    }

    /// Handle with middleware
    pub fn handle(&self, request: &HttpRequest) -> HttpResponse {
        // Run middleware
        for mw in &self.middleware {
            if let Some(response) = mw(request) {
                return response;
            }
        }

        // Run handler
        (self.route.handler)(request)
    }
}

// 10. Common Route Patterns

/// User routes
pub fn user_routes() -> Vec<Route> {
    vec![
        get("/users", |req| HttpResponse::ok("List users")),
        get("/users/:id", |req| HttpResponse::ok("Get user")),
        post("/users", |req| HttpResponse::ok("Create user")),
        put("/users/:id", |req| HttpResponse::ok("Update user")),
        delete("/users/:id", |req| HttpResponse::ok("Delete user")),
    ]
}

/// API routes
pub fn api_routes() -> Vec<Route> {
    vec![
        get("/api/status", |req| HttpResponse::ok("OK")),
        get("/api/health", |req| HttpResponse::ok("Healthy")),
        get("/api/version", |req| HttpResponse::ok("1.0.0")),
    ]
}

// Usage Examples
fn main() {
    println!("=== Web Rust Routing Examples ===\n");

    // 1. Basic routes
    println!("--- 1. Basic Routes ---");
    let home_route = get("/", |_| HttpResponse::ok("Home Page"));
    let about_route = get("/about", |_| HttpResponse::ok("About Page"));
    println!("Home route: {:?}", home_route);
    println!("About route: {:?}", about_route);

    // 2. Router setup
    println!("
--- 2. Router Setup ---");
    let router = Router::new()
        .add_route(get("/", |_| HttpResponse::ok("Home")))
        .add_route(get("/users", |_| HttpResponse::ok("Users")))
        .add_route(get("/users/:id", |_| HttpResponse::ok("User Details")));

    let request = HttpRequest {
        method: HttpMethod::GET,
        path: "/users/123".to_string(),
        query_params: HashMap::new(),
        headers: HashMap::new(),
        body: None,
    };

    let response = router.handle(&request);
    println!("Response: {}", response.status_code);

    // 3. Parameter extraction
    println!("
--- 3. Parameter Extraction ---");
    let params = router.extract_params("/users/:id", "/users/123");
    println!("Extracted params: {:?}", params);

    // 4. Query parameters
    println!("
--- 4. Query Parameters ---");
    let query = "name=John&age=30&city=NYC";
    let parsed = parse_query(query);
    println!("Parsed query: {:?}", parsed);

    // 5. URL encoding/decoding
    println!("
--- 5. URL Encoding/Decoding ---");
    let original = "hello world!";
    let encoded = url_encode(original);
    let decoded = url_decode(&encoded);
    println!("Original: {}", original);
    println!("Encoded: {}", encoded);
    println!("Decoded: {}", decoded);

    // 6. Build paths
    println!("
--- 6. Build Paths ---");
    let template = "/users/:id/posts/:post_id";
    let mut params = HashMap::new();
    params.insert("id".to_string(), "123".to_string());
    params.insert("post_id".to_string(), "456".to_string());
    let path = build_path(template, &params);
    println!("Built path: {}", path);

    // 7. Build URLs
    println!("
--- 7. Build URLs ---");
    let mut query_params = HashMap::new();
    query_params.insert("page".to_string(), "1".to_string());
    query_params.insert("limit".to_string(), "10".to_string());
    let url = build_url("/users", &query_params);
    println!("Built URL: {}", url);

    // 8. Wildcard matching
    println!("
--- 8. Wildcard Matching ---");
    let pattern = "/files/*";
    println!("Match '/files/doc.pdf': {}", match_wildcard(pattern, "/files/doc.pdf"));
    println!("Match '/files/images/photo.jpg': {}", match_wildcard(pattern, "/files/images/photo.jpg"));
    println!("Extract wildcard: {:?}", extract_wildcard(pattern, "/files/images/photo.jpg"));

    // 9. Route groups
    println!("
--- 9. Route Groups ---");
    let group = RouteGroup::new("/api/v1")
        .add_route(get("/users", |_| HttpResponse::ok("Users")))
        .add_route(get("/posts", |_| HttpResponse::ok("Posts")));

    let grouped_routes = group.routes();
    println!("Grouped routes:");
    for route in &grouped_routes {
        println!("  {}", route.pattern);
    }

    // 10. Named routes
    println!("
--- 10. Named Routes ---");
    let mut registry = RouteRegistry::new();
    registry.register("user_profile", "/users/:id");
    registry.register("post_detail", "/posts/:id/comments/:comment_id");

    let mut params = HashMap::new();
    params.insert("id".to_string(), "123".to_string());
    println!("User profile URL: {:?}", registry.url("user_profile", &params));

    let mut params = HashMap::new();
    params.insert("id".to_string(), "456".to_string());
    params.insert("comment_id".to_string(), "789".to_string());
    println!("Post detail URL: {:?}", registry.url("post_detail", &params));

    println!("
=== Note ===");
    println!("For production web applications, use:");
    println!("- Actix Web: https://actix.rs/");
    println!("- Axum: https://github.com/tokio-rs/axum");
    println!("- Rocket: https://rocket.rs/");
    println!("- Warp: https://github.com/seanmonstar/warp");

    println!("
=== All Routing Examples Completed ===");
}

💻 静态文件 rust

🟡 intermediate ⭐⭐⭐

从目录提供静态文件和资源

⏱️ 25 min 🏷️ rust, web, web features
Prerequisites: Intermediate Rust, File I/O
// Web Rust Static Files Examples
// Serve static files and assets from a directory
//
// For production web applications, consider using:
// - Actix Web: https://actix.rs/
// - Axum: https://github.com/tokio-rs/axum
// - Static file serving crates: rust-embed, mime_guess

use std::path::{Path, PathBuf};
use std::collections::HashMap;
use std::fs;

// 1. File Response Types

/// File response
#[derive(Debug, Clone)]
pub struct FileResponse {
    pub path: PathBuf,
    pub content_type: String,
    pub content: Vec<u8>,
    pub status_code: u16,
    pub headers: HashMap<String, String>,
}

impl FileResponse {
    pub fn new(path: PathBuf, content: Vec<u8>, content_type: &str) -> Self {
        FileResponse {
            path,
            content_type: content_type.to_string(),
            content,
            status_code: 200,
            headers: HashMap::new(),
        }
    }

    pub fn with_cache_control(mut self, value: &str) -> Self {
        self.headers.insert("Cache-Control".to_string(), value.to_string());
        self
    }

    pub fn with_etag(mut self, etag: &str) -> Self {
        self.headers.insert("ETag".to_string(), etag.to_string());
        self
    }
}

/// File not found response
pub fn file_not_found() -> FileResponse {
    FileResponse {
        path: PathBuf::new(),
        content_type: "text/plain".to_string(),
        content: "File Not Found".to_string().into_bytes(),
        status_code: 404,
        headers: HashMap::new(),
    }
}

// 2. MIME Type Detection

/// Get MIME type from file extension
pub fn get_mime_type(path: &Path) -> &'static str {
    path.extension()
        .and_then(|ext| ext.to_str())
        .map(|ext| match ext.to_lowercase().as_str() {
            "html" | "htm" => "text/html",
            "css" => "text/css",
            "js" => "application/javascript",
            "json" => "application/json",
            "xml" => "application/xml",
            "pdf" => "application/pdf",
            "zip" => "application/zip",
            "tar" => "application/x-tar",
            "gz" => "application/gzip",
            "txt" => "text/plain",
            "md" => "text/markdown",
            "png" => "image/png",
            "jpg" | "jpeg" => "image/jpeg",
            "gif" => "image/gif",
            "svg" => "image/svg+xml",
            "webp" => "image/webp",
            "ico" => "image/x-icon",
            "bmp" => "image/bmp",
            "woff" => "font/woff",
            "woff2" => "font/woff2",
            "ttf" => "font/ttf",
            "eot" => "application/vnd.ms-fontobject",
            "mp3" => "audio/mpeg",
            "wav" => "audio/wav",
            "ogg" => "audio/ogg",
            "mp4" => "video/mp4",
            "webm" => "video/webm",
            "avi" => "video/x-msvideo",
            "mov" => "video/quicktime",
            _ => "application/octet-stream",
        })
        .unwrap_or("application/octet-stream")
}

/// Check if file is binary
pub fn is_binary_file(path: &Path) -> bool {
    let ext = path.extension()
        .and_then(|e| e.to_str())
        .map(|e| e.to_lowercase())
        .unwrap_or_default();

    matches!(ext.as_str(),
        "png" | "jpg" | "jpeg" | "gif" | "webp" | "ico" | "bmp" |
        "woff" | "woff2" | "ttf" | "eot" |
        "mp3" | "wav" | "ogg" | "mp4" | "webm" | "avi" | "mov" |
        "pdf" | "zip" | "tar" | "gz"
    )
}

// 3. Static File Server

/// Static file server
pub struct StaticFileServer {
    root_dir: PathBuf,
    index_files: Vec<String>,
    spa_mode: bool,
}

impl StaticFileServer {
    pub fn new(root_dir: &str) -> Self {
        StaticFileServer {
            root_dir: PathBuf::from(root_dir),
            index_files: vec!["index.html".to_string()],
            spa_mode: false,
        }
    }

    /// Set index files
    pub fn index_files(mut self, files: Vec<&str>) -> Self {
        self.index_files = files.iter().map(|s| s.to_string()).collect();
        self
    }

    /// Enable SPA mode (serve index.html for all routes)
    pub fn spa_mode(mut self, enabled: bool) -> Self {
        self.spa_mode = enabled;
        self
    }

    /// Serve file
    pub fn serve_file(&self, request_path: &str) -> FileResponse {
        // Sanitize path
        let clean_path = self.sanitize_path(request_path);
        let full_path = self.root_dir.join(&clean_path);

        // Check if path exists and is within root
        if !full_path.starts_with(&self.root_dir) || !full_path.exists() {
            if self.spa_mode {
                // Try serving index.html
                let index_path = self.root_dir.join("index.html");
                if index_path.exists() {
                    return self.read_file(&index_path);
                }
            }
            return file_not_found();
        }

        // If directory, try index file
        if full_path.is_dir() {
            for index in &self.index_files {
                let index_path = full_path.join(index);
                if index_path.exists() {
                    return self.read_file(&index_path);
                }
            }
            return file_not_found();
        }

        self.read_file(&full_path)
    }

    /// Read file into response
    fn read_file(&self, path: &Path) -> FileResponse {
        match fs::read(path) {
            Ok(content) => {
                let mime_type = get_mime_type(path);
                FileResponse::new(path.to_path_buf(), content, mime_type)
                    .with_cache_control("public, max-age=3600")
            },
            Err(_) => file_not_found(),
        }
    }

    /// Sanitize path to prevent directory traversal
    fn sanitize_path(&self, path: &str) -> String {
        // Remove query string
        let path = path.split('?').next().unwrap_or(path);

        // Remove leading slash
        let path = path.trim_start_matches('/');

        // Remove any .. components
        path.split('/')
            .filter(|part| *part != ".." && *part != ".")
            .collect::<Vec<_>>()
            .join("/")
    }

    /// Generate ETag for file
    pub fn generate_etag(&self, path: &Path) -> Option<String> {
        let metadata = fs::metadata(path).ok()?;
        let modified = metadata.modified().ok()?;
        let timestamp = modified.duration_since(std::time::UNIX_EPOCH).ok()?.as_secs();
        Some(format!(r#""{}""#, timestamp))
    }
}

// 4. File Caching

/// Cached file entry
#[derive(Debug, Clone)]
pub struct CachedFile {
    pub content: Vec<u8>,
    pub content_type: String,
    pub etag: String,
    pub last_modified: u64,
}

/// File cache
pub struct FileCache {
    cache: HashMap<String, CachedFile>,
    max_size: usize,
    max_age: u64,
}

impl FileCache {
    pub fn new(max_size: usize, max_age_seconds: u64) -> Self {
        FileCache {
            cache: HashMap::new(),
            max_size,
            max_age: max_age_seconds,
        }
    }

    /// Get from cache
    pub fn get(&self, path: &str) -> Option<&CachedFile> {
        self.cache.get(path)
    }

    /// Insert into cache
    pub fn insert(&mut self, path: String, file: CachedFile) {
        // Remove oldest if at capacity
        if self.cache.len() >= self.max_size {
            // Simple FIFO - in production use LRU
            if let Some(key) = self.cache.keys().next() {
                self.cache.remove(key);
            }
        }
        self.cache.insert(path, file);
    }

    /// Clear cache
    pub fn clear(&mut self) {
        self.cache.clear();
    }

    /// Remove expired entries
    pub fn remove_expired(&mut self, current_time: u64) {
        self.cache.retain(|_, file| {
            current_time - file.last_modified < self.max_age
        });
    }
}

// 5. Range Request Support

/// Parse Range header
#[derive(Debug, Clone)]
pub struct Range {
    pub start: u64,
    pub end: Option<u64>,
}

pub fn parse_range_header(range_header: &str, file_size: u64) -> Option<Range> {
    if !range_header.starts_with("bytes=") {
        return None;
    }

    let range_spec = &range_header[6..];
    let parts: Vec<&str> = range_spec.split('-').collect();

    if parts.len() != 2 {
        return None;
    }

    let start = parts[0].parse::<u64>().ok()?;
    let end = if parts[1].is_empty() {
        None
    } else {
        Some(parts[1].parse::<u64>().ok()?)
    };

    // Validate range
    if start >= file_size {
        return None;
    }

    Some(Range {
        start,
        end: end.map(|e| e.min(file_size - 1)),
    })
}

/// Serve file with range support
pub fn serve_file_range(content: &[u8], range: &Range) -> (Vec<u8>, String) {
    let end = range.end.unwrap_or(content.len() as u64 - 1) as usize;
    let start = range.start as usize;

    let range_content = content[start..=end].to_vec();
    let content_range = format!("bytes {}-{}/{}", start, end, content.len());

    (range_content, content_range)
}

// 6. File Compression

/// Compress content (simplified)
pub fn compress_content(content: &[u8], encoding: &str) -> Option<Vec<u8>> {
    match encoding {
        "gzip" => {
            // In real implementation, use flate2 crate
            Some(content.to_vec())
        },
        "deflate" => {
            // In real implementation, use flate2 crate
            Some(content.to_vec())
        },
        "br" => {
            // In real implementation, use brotli crate
            Some(content.to_vec())
        },
        _ => None,
    }
}

// 7. Virtual File System

/// Virtual file (embedded)
pub struct VirtualFile {
    pub path: String,
    pub content: &'static [u8],
    pub content_type: &'static str,
}

/// Virtual file system
pub struct VirtualFileSystem {
    files: HashMap<String, VirtualFile>,
}

impl VirtualFileSystem {
    pub fn new() -> Self {
        VirtualFileSystem {
            files: HashMap::new(),
        }
    }

    /// Add virtual file
    pub fn add_file(&mut self, file: VirtualFile) {
        self.files.insert(file.path.clone(), file);
    }

    /// Get virtual file
    pub fn get_file(&self, path: &str) -> Option<&VirtualFile> {
        self.files.get(path)
    }

    /// Serve virtual file
    pub fn serve(&self, path: &str) -> Option<FileResponse> {
        let file = self.get_file(path)?;

        Some(FileResponse {
            path: PathBuf::from(path),
            content_type: file.content_type.to_string(),
            content: file.content.to_vec(),
            status_code: 200,
            headers: HashMap::new(),
        })
    }
}

impl Default for VirtualFileSystem {
    fn default() -> Self {
        Self::new()
    }
}

// 8. Static Assets Bundle

/// Predefined asset bundle
pub struct AssetBundle {
    assets: HashMap<String, &'static [u8]>,
    mime_types: HashMap<String, &'static str>,
}

impl AssetBundle {
    pub fn new() -> Self {
        AssetBundle {
            assets: HashMap::new(),
            mime_types: HashMap::new(),
        }
    }

    /// Add asset
    pub fn add_asset(mut self, path: &str, content: &'static [u8]) -> Self {
        let mime = get_mime_type(Path::new(path));
        self.mime_types.insert(path.to_string(), mime);
        self.assets.insert(path.to_string(), content);
        self
    }

    /// Serve asset
    pub fn serve(&self, path: &str) -> Option<FileResponse> {
        let content = self.assets.get(path)?;
        let mime_type = self.mime_types.get(path)?;

        Some(FileResponse {
            path: PathBuf::from(path),
            content_type: mime_type.to_string(),
            content: content.to_vec(),
            status_code: 200,
            headers: HashMap::new(),
        })
    }
}

impl Default for AssetBundle {
    fn default() -> Self {
        Self::new()
    }
}

// 9. Directory Listing

/// Directory entry
#[derive(Debug, Clone)]
pub struct DirectoryEntry {
    pub name: String,
    pub is_dir: bool,
    pub size: Option<u64>,
}

/// List directory contents
pub fn list_directory(path: &Path) -> Vec<DirectoryEntry> {
    let mut entries = Vec::new();

    if let Ok(read_dir) = fs::read_dir(path) {
        for entry in read_dir.filter_map(|e| e.ok()) {
            let metadata = entry.metadata().ok();
            let name = entry.file_name().to_string_lossy().to_string();

            entries.push(DirectoryEntry {
                name,
                is_dir: metadata.as_ref().map(|m| m.is_dir()).unwrap_or(false),
                size: metadata.and_then(|m| if m.is_file() { Some(m.len()) } else { None }),
            });
        }
    }

    entries.sort_by(|a, b| {
        if a.is_dir && !b.is_dir {
            std::cmp::Ordering::Less
        } else if !a.is_dir && b.is_dir {
            std::cmp::Ordering::Greater
        } else {
            a.name.cmp(&b.name)
        }
    });

    entries
}

// 10. Usage Examples

fn main() {
    println!("=== Web Rust Static Files Examples ===\n");

    // 1. MIME type detection
    println!("--- 1. MIME Type Detection ---");
    let files = [
        "index.html",
        "styles.css",
        "app.js",
        "image.png",
        "data.json",
        "document.pdf",
    ];

    for file in &files {
        let path = Path::new(file);
        println!("{} -> {}", file, get_mime_type(path));
    }

    // 2. Binary file detection
    println!("
--- 2. Binary File Detection ---");
    println!("index.html is binary: {}", is_binary_file(Path::new("index.html")));
    println!("image.png is binary: {}", is_binary_file(Path::new("image.png")));

    // 3. Path sanitization
    println!("
--- 3. Path Sanitization ---");
    let server = StaticFileServer::new("/var/www");
    let paths = [
        "/index.html",
        "/../etc/passwd",
        "/files/../../secret.txt",
        "/normal/path.js",
    ];

    for path in &paths {
        println!("Original: {}", path);
        println!("Sanitized: {}", server.sanitize_path(path));
    }

    // 4. Virtual file system
    println!("
--- 4. Virtual File System ---");
    let mut vfs = VirtualFileSystem::new();
    vfs.add_file(VirtualFile {
        path: "/index.html".to_string(),
        content: b"<html><body>Hello</body></html>",
        content_type: "text/html",
    });

    if let Some(response) = vfs.serve("/index.html") {
        println!("Virtual file served: {} bytes", response.content.len());
    }

    // 5. Asset bundle
    println!("
--- 5. Asset Bundle ---");
    let bundle = AssetBundle::new()
        .add_asset("/favicon.ico", b"icon_data")
        .add_asset("/robots.txt", b"User-agent: *");

    if let Some(response) = bundle.serve("/robots.txt") {
        println!("Asset served: {}", String::from_utf8_lossy(&response.content));
    }

    // 6. Range request parsing
    println!("
--- 6. Range Request ---");
    let range_header = "bytes=0-1023";
    let file_size = 5000;
    if let Some(range) = parse_range_header(range_header, file_size) {
        println!("Range: {}-{}", range.start, range.end.unwrap());
    }

    // 7. Directory listing
    println!("
--- 7. Directory Listing ---");
    let entries = list_directory(Path::new("."));
    println!("Current directory contents:");
    for entry in entries.iter().take(5) {
        let type_marker = if entry.is_dir { "[DIR]" } else { "[FILE]" };
        println!("  {} {} {:?}", type_marker, entry.name, entry.size);
    }

    // 8. File cache
    println!("
--- 8. File Cache ---");
    let mut cache = FileCache::new(100, 3600);
    cache.insert("test.html".to_string(), CachedFile {
        content: b"<html></html>".to_vec(),
        content_type: "text/html".to_string(),
        etag: r#""12345""#.to_string(),
        last_modified: 1234567890,
    });

    if let Some(cached) = cache.get("test.html") {
        println!("Cached file: {}, etag: {}", cached.content_type, cached.etag);
    }

    println!("
=== Note ===");
    println!("For production static file serving, use:");
    println!("- rust-embed: https://github.com/pyros2097/rust-embed");
    println!("- mime_guess: https://github.com/sfstewa/mime_guess");
    println!("- Actix static files: https://actix.rs/docs/static-files");
    println!("- Axum static files: https://docs.rs/axum/latest/axum/struct.Router.html#method.nest_service");

    println!("
=== All Static Files Examples Completed ===");
}

💻 中间件 rust

🔴 complex ⭐⭐⭐⭐

请求处理中间件管道

⏱️ 35 min 🏷️ rust, web, web features
Prerequisites: Advanced Rust, HTTP knowledge
// Web Rust Middleware Examples
// Request processing middleware pipeline
//
// For production web applications, consider using:
// - Actix Web: https://actix.rs/
// - Axum: https://github.com/tokio-rs/axum
// - Tower: https://github.com/tower-rs/tower

use std::time::{SystemTime, UNIX_EPOCH};
use std::collections::HashMap;

// 1. Request/Response Types

/// HTTP Request
#[derive(Debug, Clone)]
pub struct Request {
    pub method: String,
    pub path: String,
    pub headers: HashMap<String, String>,
    pub body: Option<String>,
    pub state: HashMap<String, String>,
}

/// HTTP Response
#[derive(Debug, Clone)]
pub struct Response {
    pub status_code: u16,
    pub headers: HashMap<String, String>,
    pub body: String,
}

impl Response {
    pub fn new(status_code: u16, body: &str) -> Self {
        Response {
            status_code,
            headers: HashMap::new(),
            body: body.to_string(),
        }
    }

    pub fn ok(body: &str) -> Self {
        Response::new(200, body)
    }

    pub fn not_found() -> Self {
        Response::new(404, "Not Found")
    }

    pub fn unauthorized() -> Self {
        Response::new(401, "Unauthorized")
    }

    pub fn forbidden() -> Self {
        Response::new(403, "Forbidden")
    }

    pub fn server_error() -> Self {
        Response::new(500, "Internal Server Error")
    }

    pub fn with_header(mut self, key: &str, value: &str) -> Self {
        self.headers.insert(key.to_string(), value.to_string());
        self
    }
}

// 2. Middleware Types

/// Middleware function type
pub type Middleware = Box<dyn Fn(Request) -> Result<Request, Response> + Send + Sync>;

/// Middleware result
pub type MiddlewareResult = Result<Request, Response>;

/// Next function in chain
pub type Next = Box<dyn Fn(Request) -> Response + Send + Sync>;

/// Async-style middleware (simplified)
pub type AsyncMiddleware = Box<dyn Fn(Request, Next) -> Response + Send + Sync>;

// 3. Common Middleware

/// Logging middleware
pub fn logging_middleware() -> Middleware {
    Box::new(|mut request: Request| -> MiddlewareResult {
        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();

        println!("[{}] {} {}", timestamp, request.method, request.path);

        // Add request timing
        request.state.insert("start_time".to_string(), timestamp.to_string());

        Ok(request)
    })
}

/// CORS middleware
pub fn cors_middleware(allowed_origins: Vec<String>) -> Middleware {
    Box::new(move |request: Request| -> MiddlewareResult {
        let origin = request.headers.get("origin").map(|s| s.as_str());

        if let Some(origin) = origin {
            if allowed_origins.contains(&origin.to_string()) || allowed_origins.contains(&"*".to_string()) {
                return Ok(request);
            }
        }

        Err(Response::new(403, "Origin not allowed")
            .with_header("Access-Control-Allow-Origin", "*"))
    })
}

/// Authentication middleware
pub fn auth_middleware() -> Middleware {
    Box::new(|request: Request| -> MiddlewareResult {
        let auth_header = request.headers.get("authorization");

        match auth_header {
            Some(token) if token.starts_with("Bearer ") => {
                // Validate token (simplified)
                let token_value = &token[7..];
                if token_value == "valid_token" {
                    Ok(request)
                } else {
                    Err(Response::unauthorized())
                }
            },
            Some(_) => Err(Response::unauthorized()
                .with_header("WWW-Authenticate", "Bearer")),
            None => Err(Response::unauthorized()
                .with_header("WWW-Authenticate", "Bearer")),
        }
    })
}

/// Content-Type middleware
pub fn content_type_middleware() -> Middleware {
    Box::new(|mut request: Request| -> MiddlewareResult {
        let content_type = request.headers.get("content-type").cloned();

        if let Some(ct) = content_type {
            request.state.insert("content_type".to_string(), ct);
        }

        Ok(request)
    })
}

/// Query parser middleware
pub fn query_parser_middleware() -> Middleware {
    Box::new(|mut request: Request| -> MiddlewareResult {
        if let Some(pos) = request.path.find('?') {
            let query_string = &request.path[pos + 1..];
            let params = parse_query_string(query_string);
            request.state.insert("query_params".to_string(), serde_json::to_string(&params).unwrap());
        }

        Ok(request)
    })
}

/// Parse query string
fn parse_query_string(query: &str) -> HashMap<String, String> {
    let mut params = HashMap::new();
    for pair in query.split('&') {
        let parts: Vec<&str> = pair.splitn(2, '=').collect();
        if parts.len() == 2 {
            params.insert(parts[0].to_string(), parts[1].to_string());
        }
    }
    params
}

// 4. Rate Limiting Middleware

/// Rate limiter
pub struct RateLimiter {
    requests: Vec<u64>,
    max_requests: usize,
    window_ms: u64,
}

impl RateLimiter {
    pub fn new(max_requests: usize, window_ms: u64) -> Self {
        RateLimiter {
            requests: Vec::new(),
            max_requests,
            window_ms,
        }
    }

    pub fn check(&mut self) -> bool {
        let now = current_timestamp();
        let window_start = now - self.window_ms;

        // Clean old requests
        self.requests.retain(|&t| t > window_start);

        if self.requests.len() < self.max_requests {
            self.requests.push(now);
            true
        } else {
            false
        }
    }
}

fn current_timestamp() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis() as u64
}

/// Rate limiting middleware
pub fn rate_limit_middleware(max_requests: usize, window_ms: u64) -> Middleware {
    Box::new(move |_| -> MiddlewareResult {
        use std::sync::{Arc, Mutex};
        thread_local! {
            static LIMITER: Arc<Mutex<RateLimiter>> = Arc::new(Mutex::new(RateLimiter::new(max_requests, window_ms)));
        }

        LIMITER.with(|limiter| {
            let mut limiter = limiter.lock().unwrap();
            if limiter.check() {
                Ok(Request {
                    method: "GET".to_string(),
                    path: "/".to_string(),
                    headers: HashMap::new(),
                    body: None,
                    state: HashMap::new(),
                })
            } else {
                Err(Response::new(429, "Too Many Requests")
                    .with_header("Retry-After", &format!("{}", window_ms / 1000)))
            }
        })
    })
}

// 5. Compression Middleware

/// Compression middleware
pub fn compression_middleware() -> AsyncMiddleware {
    Box::new(|request: Request, next: Next| -> Response {
        let accepts_encoding = request.headers.get("accept-encoding")
            .map(|s| s.contains("gzip"))
            .unwrap_or(false);

        let mut response = next(request);

        if accepts_encoding {
            response.headers.insert("Content-Encoding".to_string(), "gzip".to_string());
            // In real implementation, would compress body here
        }

        response
    })
}

// 6. Security Headers Middleware

/// Security headers middleware
pub fn security_headers_middleware() -> AsyncMiddleware {
    Box::new(|request: Request, next: Next| -> Response {
        let mut response = next(request);

        response.headers.insert("X-Content-Type-Options".to_string(), "nosniff".to_string());
        response.headers.insert("X-Frame-Options".to_string(), "DENY".to_string());
        response.headers.insert("X-XSS-Protection".to_string(), "1; mode=block".to_string());
        response.headers.insert("Strict-Transport-Security".to_string(), "max-age=31536000".to_string());

        response
    })
}

// 7. Request ID Middleware

/// Request ID middleware
pub fn request_id_middleware() -> Middleware {
    Box::new(|mut request: Request| -> MiddlewareResult {
        let request_id = generate_id();
        request.state.insert("request_id".to_string(), request_id.clone());
        request.headers.insert("X-Request-ID".to_string(), request_id);

        Ok(request)
    })
}

/// Generate unique ID
fn generate_id() -> String {
    format!("{:x}", current_timestamp())
}

// 8. Timeout Middleware

/// Timeout middleware (simplified)
pub fn timeout_middleware(timeout_ms: u64) -> AsyncMiddleware {
    Box::new(move |request: Request, next: Next| -> Response {
        let start = current_timestamp();

        let response = next(request);

        let elapsed = current_timestamp() - start;

        if elapsed > timeout_ms {
            Response::new(408, "Request Timeout")
                .with_header("X-Timeout", "true")
        } else {
            response
        }
    })
}

// 9. Body Size Limit Middleware

/// Body size limit middleware
pub fn body_size_limit_middleware(max_bytes: usize) -> Middleware {
    Box::new(move |request: Request| -> MiddlewareResult {
        if let Some(body) = &request.body {
            if body.len() > max_bytes {
                return Err(Response::new(413, "Payload Too Large")
                    .with_header("Content-Type", "text/plain"));
            }
        }
        Ok(request)
    })
}

// 10. Middleware Chain

/// Middleware chain
pub struct MiddlewareChain {
    middleware: Vec<Middleware>,
    handler: Box<dyn Fn(Request) -> Response + Send + Sync>,
}

impl MiddlewareChain {
    pub fn new() -> Self {
        MiddlewareChain {
            middleware: Vec::new(),
            handler: Box::new(|_| Response::not_found()),
        }
    }

    pub fn add_middleware(mut self, mw: Middleware) -> Self {
        self.middleware.push(mw);
        self
    }

    pub fn set_handler<H>(mut self, handler: H) -> Self
    where
        H: Fn(Request) -> Response + Send + Sync + 'static,
    {
        self.handler = Box::new(handler);
        self
    }

    pub fn process(&self, mut request: Request) -> Response {
        // Run middleware
        for mw in &self.middleware {
            match mw(request) {
                Ok(req) => request = req,
                Err(response) => return response,
            }
        }

        // Run handler
        (self.handler)(request)
    }
}

impl Default for MiddlewareChain {
    fn default() -> Self {
        Self::new()
    }
}

// 11. Middleware Combiner

/// Combine multiple middleware
pub fn combine_middleware(middleware: Vec<Middleware>) -> Middleware {
    Box::new(move |request: Request| -> MiddlewareResult {
        let mut request = request;
        for mw in &middleware {
            request = mw(request)?;
        }
        Ok(request)
    })
}

// Usage Examples
fn main() {
    println!("=== Web Rust Middleware Examples ===\n");

    // 1. Basic middleware
    println!("--- 1. Basic Middleware ---");
    let request = Request {
        method: "GET".to_string(),
        path: "/test".to_string(),
        headers: HashMap::new(),
        body: None,
        state: HashMap::new(),
    };

    let logged = logging_middleware()(request).unwrap();
    println!("Logged request: {}", logged.path);

    // 2. Authentication
    println!("
--- 2. Authentication Middleware ---");
    let mut auth_request = Request {
        method: "GET".to_string(),
        path: "/protected".to_string(),
        headers: {
            let mut map = HashMap::new();
            map.insert("authorization".to_string(), "Bearer valid_token".to_string());
            map
        },
        body: None,
        state: HashMap::new(),
    };

    match auth_middleware()(auth_request) {
        Ok(_) => println!("Request authenticated"),
        Err(response) => println!("Authentication failed: {}", response.status_code),
    }

    // 3. CORS
    println!("
--- 3. CORS Middleware ---");
    let mut cors_request = Request {
        method: "GET".to_string(),
        path: "/api/data".to_string(),
        headers: {
            let mut map = HashMap::new();
            map.insert("origin".to_string(), "https://example.com".to_string());
            map
        },
        body: None,
        state: HashMap::new(),
    };

    let allowed = vec!["https://example.com".to_string()];
    match cors_middleware(allowed)(cors_request) {
        Ok(_) => println!("CORS check passed"),
        Err(response) => println!("CORS blocked: {}", response.status_code),
    }

    // 4. Middleware chain
    println!("
--- 4. Middleware Chain ---");
    let chain = MiddlewareChain::new()
        .add_middleware(logging_middleware())
        .add_middleware(request_id_middleware())
        .add_middleware(content_type_middleware())
        .set_handler(|req| Response::ok(&format!("Handled: {}", req.path)));

    let response = chain.process(Request {
        method: "GET".to_string(),
        path: "/test".to_string(),
        headers: HashMap::new(),
        body: None,
        state: HashMap::new(),
    });

    println!("Chain response: {}", response.status_code);

    // 5. Combined middleware
    println!("
--- 5. Combined Middleware ---");
    let combined = combine_middleware(vec![
        logging_middleware(),
        content_type_middleware(),
        query_parser_middleware(),
    ]);

    let test_request = Request {
        method: "GET".to_string(),
        path: "/search?q=rust&lang=en".to_string(),
        headers: HashMap::new(),
        body: None,
        state: HashMap::new(),
    };

    match combined(test_request) {
        Ok(req) => println!("All middleware passed. Query params in state: {}",
            req.state.get("query_params").is_some()),
        Err(resp) => println!("Middleware chain failed: {}", resp.status_code),
    }

    // 6. Rate limiting
    println!("
--- 6. Rate Limiting ---");
    let mut limiter = RateLimiter::new(5, 1000);
    for i in 0..7 {
        let allowed = limiter.check();
        println!("Request {} allowed: {}", i + 1, allowed);
    }

    // 7. Security headers
    println!("
--- 7. Security Headers ---");
    let sec_chain = MiddlewareChain::new()
        .set_handler(|req| Response::ok("Secure content"));

    let response = sec_chain.process(Request {
        method: "GET".to_string(),
        path: "/secure".to_string(),
        headers: HashMap::new(),
        body: None,
        state: HashMap::new(),
    });

    println!("Security headers: {:?}", response.headers.keys().collect::<Vec<_>>());

    // 8. Body size limit
    println!("
--- 8. Body Size Limit ---");
    let large_body = "x".repeat(1_000_000);
    let large_request = Request {
        method: "POST".to_string(),
        path: "/upload".to_string(),
        headers: HashMap::new(),
        body: Some(large_body),
        state: HashMap::new(),
    };

    match body_size_limit_middleware(100_000)(large_request) {
        Ok(_) => println!("Body size acceptable"),
        Err(response) => println!("Body too large: {}", response.status_code),
    }

    println!("
=== Note ===");
    println!("For production middleware, use:");
    println!("- Actix Web: https://actix.rs/");
    println!("- Axum: https://github.com/tokio-rs/axum");
    println!("- Tower: https://github.com/tower-rs/tower");

    println!("
=== All Middleware Examples Completed ===");
}