🎯 Recommended Samples
Balanced sample collections from various categories for you to explore
Web Features Rust Samples
Web Rust web framework features examples including routing, middleware, and static files
💻 Routing rust
🟡 intermediate
⭐⭐⭐⭐
URL routing and path parameter extraction
⏱️ 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 ===");
}
💻 Static Files rust
🟡 intermediate
⭐⭐⭐
Serve static files and assets from a directory
⏱️ 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
⭐⭐⭐⭐
Request processing middleware pipeline
⏱️ 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 ===");
}