Custom middleware and built-in middleware for the Ironclad framework
Middleware in Ironclad provides powerful request/response interception capabilities. The framework includes built-in middleware for common tasks and supports custom middleware development.
The maintenance mode middleware allows you to temporarily disable your application for updates or maintenance while optionally providing bypass access to specific users.
Maintenance mode is Laravel-inspired and includes features like secret bypass, custom HTML templates, and redirect capabilities.
Cookie-based BypassThe middleware checks for a maintenance_bypass cookie to allow authorized users to bypass maintenance mode:
src/middleware/maintenance.rs
if let Some(secret) = secret_opt { // Check if the user already has a valid bypass cookie if let Some(cookie) = req.cookie("maintenance_bypass") { if cookie.value() == secret { // Valid cookie, bypass maintenance mode completely let fut = self.service.call(req); return Box::pin(async move { let res = fut.await?; Ok(res.map_into_left_body()) }); } }}
URL-based Secret AuthenticationUsers can authenticate by appending the secret to any URL:
src/middleware/maintenance.rs
let path = req.path().to_string();let secret_suffix = format!("/{}", secret);if path.ends_with(&secret_suffix) { // Remove secret from path to determine where to redirect let new_path = path.trim_end_matches(&secret_suffix); let redirect_to = if new_path.is_empty() { "/" } else { new_path }.to_string(); // Create a session cookie for the bypass let mut cookie = Cookie::new("maintenance_bypass", secret); cookie.set_path("/"); cookie.set_http_only(true); let response = HttpResponse::TemporaryRedirect() .insert_header((header::LOCATION, redirect_to.as_str())) .cookie(cookie) .finish();}
Response Type DetectionThe middleware automatically detects whether to return HTML or JSON based on the Accept header:
src/middleware/maintenance.rs
fn is_browser(req: &ServiceRequest) -> bool { if let Some(accept) = req.headers().get("Accept") { if let Ok(accept_str) = accept.to_str() { return accept_str.contains("text/html"); } } false}
use actix_governor::Governor;use crate::middleware::api_rate_limiter;// Allow 10 requests immediately, then 1 request every 2 secondsApp::new().wrap(Governor::new(&api_rate_limiter(2, 10)))
Strict Rate Limiting for Sensitive Endpoints
// Allow 1 request every 12 seconds (5 requests per minute)let strict_limiter = api_rate_limiter(12, 1);web::resource("/api/admin/sensitive") .wrap(Governor::new(&strict_limiter)) .route(web::post().to(sensitive_handler))
For more granular control, use device-based rate limiting instead of IP-based:
src/middleware/rate_limit.rs
#[derive(Clone)]pub struct DeviceIdExtractor;impl KeyExtractor for DeviceIdExtractor { type Key = String; type KeyExtractionError = ApiError; fn extract(&self, req: &ServiceRequest) -> Result<Self::Key, Self::KeyExtractionError> { // Try to read the unique device identifier from the headers if let Some(device_id) = req.headers().get("x-device-id") { if let Ok(id_str) = device_id.to_str() { return Ok(id_str.to_string()); } } // Fallback: Use IP address if no device ID is provided let ip = req.connection_info().realip_remote_addr() .unwrap_or("unknown_ip") .to_string(); Ok(ip) }}
Usage with Device ID
use crate::middleware::api_rate_limiter_with_device_id;// Rate limit by device ID instead of IPlet device_limiter = api_rate_limiter_with_device_id(2, 10);App::new().wrap(Governor::new(&device_limiter))