🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
Exemples de Fonctionnalités Web Web Rust
Exemples de fonctionnalités de framework web Web Rust incluant le routage, le middleware et les fichiers statiques
💻 Routage rust
🟡 intermediate
⭐⭐⭐⭐
Routage URL et extraction des paramètres de chemin
⏱️ 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, ¶ms);
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", ¶ms));
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", ¶ms));
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 ===");
}
💻 Fichiers Statiques rust
🟡 intermediate
⭐⭐⭐
Servir des fichiers statiques et des ressources depuis un répertoire
⏱️ 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 ===");
}
💻 Middleware rust
🔴 complex
⭐⭐⭐⭐
Pipeline de traitement des requêtes middleware
⏱️ 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(¶ms).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 ===");
}