Architecture d'un projet Runique

Stack Middleware

Ordre des slots

Runique applique les middlewares dans un ordre optimal via le système de slots :

Requête entrante
    ↓
1.  Extensions (slot 0)        → Injection Tera, Config, Engine
2.  TrustedProxies (slot 2)    → IP cliente réelle
3.  Compression (slot 5)       → Compression externe
4.  CORS (slot 8)              → Avant ErrorHandler (preflight OPTIONS)
5.  ErrorHandler (slot 10)     → Capture et rendu des erreurs
6.  HostValidation (slot 15)   → Validation Allowed Hosts
7.  HttpsRedirect (slot 17)    → Redirection HTTPS derrière un proxy (ENFORCE_HTTPS, sans ACME)
8.  Custom (slot 20+)          → Middlewares personnalisés
9.  OpenRedirect (slot 25)     → Inspection des réponses 3xx
10. Security Headers (slot 30) → CSP (avec son nonce), HSTS, X-Frame-Options, etc.
11. Cache (slot 40)            → No-cache en développement
12. Session (slot 50)          → Gestion des sessions
13. SessionUpgrade (slot 55)   → Lecture/écriture en session
14. Auth (slot 57)             → Relit en base le compte de la session (CurrentUser)
15. CSRF (slot 60)             → Protection CSRF
16. AntiBot (slot 65)          → Honeypot
    ↓
Handler (votre code)
    ↓
Réponse sortante (middlewares en sens inverse)

Important : Avec Axum, le dernier .layer() appliqué est le premier exécuté. Le Builder Intelligent gère cet ordre automatiquement.

Middlewares personnalisés : ils prennent les slots 20, 21… dans leur ordre d'enregistrement, donc avant Session (50), Auth (57) et CSRF (60). Ils ne voient ni la session ni le compte connecté (CurrentUser). Au plus 5 : le 6ᵉ tomberait sur le slot 25 d'OpenRedirect, et le démarrage s'arrête sur une panique (collision de slots).


Injection de dépendances

Via les Extensions Axum, injectées automatiquement par le middleware Extensions :

// Enregistré automatiquement par le builder :
// Extension(engine)  → Arc<RuniqueEngine>
// Extension(tera)    → Arc<Tera>
// Extension(config)  → Arc<RuniqueConfig>

// Accessible dans les handlers via Request :
pub async fn handler(request: Request) -> AppResult<Response> {
    let db = request.engine.db.clone();
    let config = &request.engine.config;
    // ...
}