Actix Web Data vs. State Extractors: Ein dualer Ansatz für Anwendungszustände
Daniel Hayes
Full-Stack Engineer · Leapcell

Einleitung
Das Erstellen robuster und skalierbarer Webanwendungen in Rust erfordert oft eine durchdachte Verwaltung des Anwendungszustands. Dieser Zustand kann von Datenbankverbindungen und Konfigurationseinstellungen bis hin zu Benutzersitzungen und Caching-Schichten reichen. Im Actix Web-Framework stechen zwei primäre Mechanismen hervor, um solche Zustände in Ihren Handlern zu injizieren und darauf zuzugreifen: die Data- und State-Extraktoren. Obwohl beide dem Zweck der Zustandsverwaltung dienen, sind sie für unterschiedliche Anwendungsfälle konzipiert und bieten unterschiedliche Vorteile. Das Verständnis ihrer Feinheiten ist entscheidend für das Schreiben effizienter, wartbarer und idiomatischer Actix Web-Anwendungen. Dieser Artikel wird sich mit den Spezifika von Data und State befassen und ihre Prinzipien, Implementierungen und idealen Anwendungsszenarien vergleichen.
Kernkonzepte
Bevor wir uns mit den Extraktoren selbst befassen, wollen wir ein klares Verständnis einiger grundlegender Konzepte erlangen, die bei der Zustandsverwaltung in Actix Web häufig anzutreffen sind.
- Anwendungszustand (Application State): Bezieht sich auf Daten oder Ressourcen, die über mehrere Anfragen hinweg gemeinsam genutzt oder während der gesamten Lebensdauer der Webanwendung global verfügbar sein müssen. Beispiele hierfür sind Datenbankverbindungspools, Redis-Clients, Konfigurationsstrukturen oder benutzerdefinierte Instanzen von Diensten.
 - Anfrageskopischer Zustand (Request-Scoped State): Daten oder Ressourcen, die nur für eine einzelne eingehende HTTP-Anfrage und deren Verarbeitung relevant sind. Dies könnten Benutzerauthentifizierungsinformationen sein, die aus Headern extrahiert wurden, anfragespezifische Tracing-IDs oder temporäre Daten, die während der Anfragenaushandlung generiert wurden.
 - Extractor: In Actix Web ist ein Extractor ein Mechanismus, der es Ihnen ermöglicht, auf einfache Weise bestimmte Daten aus der eingehenden HTTP-Anfrage abzurufen. Diese Daten können alles sein, von URI-Parametern und Query-Strings bis hin zu JSON-Anfragekörpern und, wie wir sehen werden, Anwendungszuständen. Extraktoren vereinfachen Handler-Signaturen und erzwingen Typsicherheit.
 - Arc (Atomic Reference Counted): Ein intelligenter Zeiger in Rust, der mehreren Besitzern eines Werts die gemeinsame Nutzung eines Werts ermöglicht. Der Wert wird gelöscht, wenn der letzte 
Arc, der ihn verwaltet, gelöscht wird.Arcist entscheidend für die sichere gemeinsame Nutzung von Daten über mehrere Threads hinweg, was in asynchronen Webservern üblich ist. - Mutex (Mutual Exclusion): Ein Synchronisationsprimitiv, das verwendet wird, um gemeinsam genutzte Daten vor gleichzeitigem Zugriff durch mehrere Threads zu schützen. Es stellt sicher, dass zu einem bestimmten Zeitpunkt nur ein Thread auf die geschützten Daten zugreifen kann, wodurch Datenbeschädigung verhindert wird. 
Arc<Mutex<T>>ist ein gängiges Muster für die gemeinsame Nutzung von veränderlichen Zuständen über Threads hinweg. 
Data Extractor: Anwendungsweiter gemeinsamer Zustand
Der Data-Extractor in Actix Web ist für die Injektion von anwendungsweitem, unveränderlichem oder sicher veränderbarem gemeinsamen Zustand in Ihre Handler konzipiert. Wenn Sie Zustand mit App::app_data registrieren, wickelt Actix Web ihn in einen Arc ein. Dieser Arc ist dann für alle Dienste (Routen) innerhalb dieser Anwendungsinstanz verfügbar.
Prinzip
Das Kernprinzip hinter Data ist die Bereitstellung einer Möglichkeit für Ihre Anwendung, wesentliche, langlebige Ressourcen effizient gemeinsam zu nutzen. Durch das Umwickeln des Zustands in Arc stellt Actix Web sicher, dass der Zustand sicher von mehreren Worker-Threads aus zugegriffen werden kann, ohne die Threadsicherheit zu beeinträchtigen. Wenn der Zustand veränderlich sein muss, kombinieren Sie in der Regel Arc mit einem internen Veränderbarkeitsprimitiv wie Mutex oder RwLock.
Implementierung
Lassen Sie uns dies mit einem Beispiel veranschaulichen, bei dem wir einen Datenbankverbindungspool in unserer gesamten Anwendung gemeinsam nutzen möchten.
use actix_web::{web, App, HttpResponse, HttpServer}; use sqlx::{PgPool, postgres::PgPoolOptions}; use std::sync::Arc; // Definieren Sie eine einfache Struktur, um unsere Konfiguration zu halten #[derive(Clone)] struct AppConfig { connection_string: String, // Andere Konfigurationsfelder } async fn index(pool: web::Data<PgPool>, config: web::Data<AppConfig>) -> HttpResponse { // Auf den Datenbankpool zugreifen let _result = sqlx::query!("SELECT 1").fetch_one(pool.as_ref()).await; // Auf Konfigurationen zugreifen println!("DB connection string: {}", config.connection_string); HttpResponse::Ok().body("Hello from Actix Web!") } #[actix_web::main] async fn main() -> std::io::Result<()> { let database_url = "postgres://user:password@localhost/database"; let pool = PgPoolOptions::new() .max_connections(5) .connect(&database_url) .await .expect("Failed to create PgPool."); let app_config = AppConfig { connection_string: database_url.to_string(), }; HttpServer::new(move || { App::new() .app_data(web::Data::new(pool.clone())) // Den PgPool registrieren .app_data(web::Data::new(app_config.clone())) // Die AppConfig registrieren .route("/", web::get().to(index)) }) .bind(("127.0.0.1", 8080))? // Die Serverbindung starten .run() .await }
In diesem Beispiel:
- Wir initialisieren einen 
PgPoolund eineAppConfigeinmal. - Wir verwenden 
App::app_data(web::Data::new(my_data)), um diese Ressourcen bei der Anwendung zu registrieren. Beachten Sie, dass bei größeren Typen wiePgPool.clone()notwendig ist, daapp_dataerwartet, dass der ZustandCloneist (da er oft in verschiedene Worker-Threads verschoben wird).web::Datawickelt ihn effektiv in einenArcein. - Im 
index-Handler können wir einfachweb::Data<PgPool>undweb::Data<AppConfig>als Argumente hinzufügen, und Actix Web extrahiert und injiziert automatisch die richtige Instanz. 
Anwendungsfälle
- Datenbankverbindungspools (
PgPool,SqlitePool,RedisPool) - Konfigurationseinstellungen (
AppConfig,EnvConfig) - Gemeinsam genutzte Service-Clients (z. B. S3-Client, Payment-Gateway-Client)
 - Globale Caches (z. B. 
Arc<RwLock<HashMap<K, V>>>) 
State Extractor: Anfrageskopischer kontextbezogener Zustand
Der State-Extractor (früher web::ReqData in älteren Actix Web-Versionen, heute typischerweise allgemeiner oder über benutzerdefinierte Extraktoren referenziert) ist für die Injektion von Daten konzipiert, die für die aktuelle Anfrage spezifisch sind und während des Anfragedurchlaufs von Middleware oder früheren Anfrageprozessoren hinzugefügt oder geändert werden könnten. Im Gegensatz zu Data, das einmal für die gesamte Anwendung konfiguriert wird, ermöglicht State Ihnen, Daten dynamisch pro Anfrage anzuhängen.
Prinzip
Das Prinzip von State ist die Anreicherung des Kontexts einer Anfrage, während diese die Anwendungs-Pipeline durchläuft. Middleware oder benutzerdefinierte Vorverarbeitungsschritte können Informationen einfügen, die nur für diese spezifische Anfrage relevant sind und dann bequem von nachfolgenden Handlern oder anderer Middleware abgerufen werden können. Diese Daten werden normalerweise gelöscht, sobald die Anfragenaushandlung abgeschlossen ist.
Implementierung
Während Actix Webs web::Data explizit für anwendungsweite Zustände sorgt, wird anfrageskopischer Zustand häufig über die Erweiterungen des HttpRequest-Objekts oder durch die Definition benutzerdefinierter Extraktoren, die auf dem HttpRequest arbeiten, verwaltet. Ein gängiges Muster zum Hinzufügen von anfrageskopischem Zustand beinhaltet Middleware.
Stellen Sie sich vor, wir haben eine Authentifizierungs-Middleware, die eine Benutzer-ID aus einem Token parst und diese UserId nachfolgenden Handlern zur Verfügung stellen möchte.
use actix_web::{middleware::NormalizePath, dev::{ServiceRequest, ServiceResponse, Transform}, web, App, Error, HttpRequest, HttpResponse, HttpServer}; use futures::future::{ok, Ready, LocalBoxFuture}; use std::{rc::Rc, future::{ready, Future}, collections::HashMap}; // Eine einfache UserId-Struktur, die in Anforderungserweiterungen gespeichert wird #[derive(Debug, Clone)] struct UserId(u32); // Unsere benutzerdefinierte Middleware pub struct AuthMiddleware; impl<S, B> Transform<S, ServiceRequest> for AuthMiddleware where S: actix_web::Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static, S::Future: 'static, B: 'static, { type Response = ServiceResponse<B>; type Error = Error; type InitError = (); type Transform = AuthMiddlewareService<S>; type Future = Ready<Result<Self::Transform, Self::InitError>>; fn new_transform(&self, service: S) -> Self::Future { ok(AuthMiddlewareService { service: Rc::new(service) }) } } pub struct AuthMiddlewareService<S> { service: Rc<S>, } impl<S, B> actix_web::Service<ServiceRequest> for AuthMiddlewareService<S> where S: actix_web::Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static, S::Future: 'static, B: 'static, { type Response = ServiceResponse<B>; type Error = Error; type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>; fn poll_ready(&self, cx: &mut core::task::Context<'_>) -> core::task::Poll<Result<(), Self::Error>> { self.service.poll_ready(cx) } fn call(&self, mut req: ServiceRequest) -> Self::Future { let svc = self.service.clone(); Box::pin(async move { // Authentifizierung und Benutzer-ID-Extraktion simulieren // In einer echten App würde dies das Parsen von Headern, das Überprüfen von Tokens usw. beinhalten. let user_id = UserId(123); // Für die Demo wird die Benutzer-ID hartcodiert // Speichern Sie die UserId in den Anforderungserweiterungen // Hier werden anfrageskopische Zustände hinzugefügt req.extensions_mut().insert(user_id.clone()); let res = svc.call(req).await?; Ok(res) }) } } // Handler, der die UserId extrahiert async fn user_profile(user_id: web::ReqData<UserId>) -> HttpResponse { println!("Zugriff durch Benutzer-ID: {:?}", user_id.0); HttpResponse::Ok().body(format!("Benutzerprofil für ID: {}", user_id.0)) } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .wrap(AuthMiddleware) // Wenden Sie unsere Authentifizierungs-Middleware an .route("/profile", web::get().to(user_profile)) }) .bind(("127.0.0.1", 8080))? // Die Serverbindung starten .run() .await }
In diesem Beispiel:
AuthMiddlewarewird zur Simulation der Authentifizierung eingeführt.- Innerhalb der 
call-Methode der Middleware wird nach einer hypothetischen Authentifizierung eineUserId-Instanz erstellt. - Entscheidend ist, dass 
req.extensions_mut().insert(user_id.clone());verwendet wird, um dieseUserIdin den lokalen Erweiterungen der Anfrage zu speichern. Dies macht dieUserIdfür die Lebensdauer dieser spezifischen Anfrage verfügbar. - Der 
user_profile-Handler verwendet dannweb::ReqData<UserId>als Argument. Actix Web sucht automatisch nach einemUserId-Typ in den Erweiterungen der Anfrage und extrahiert ihn. 
Anwendungsfälle
- Authentifizierte Benutzerdetails (
UserId,SessionToken) - Geparste anfragespezifische Parameter aus benutzerdefinierten Headern
 - Anfrage-Tracing-IDs (
X-Request-ID) - Temporäre Daten, die von einer Middleware generiert werden, um sie an nachfolgende Handler weiterzugeben
 
Data vs. State: Ein Vergleich
| Merkmal | web::Data<T> (Anwendungsweiter Zustand) | web::ReqData<T> (Anfrageskopischer Zustand/Erweiterungen) | 
|---|---|---|
| Geltungsbereich | Ganze Anwendungsinstanz | Einzelne HTTP-Anfrage | 
| Lebensdauer | Bis zur Beendigung der Anwendung | Bis zum Abschluss der Anfragenaushandlung | 
| Erstellung | Einmal während der App::new()-Einrichtung konfiguriert | Dynamisch von Middleware/Extraktoren pro Anfrage hinzugefügt | 
| Veränderbarkeit | Standardmäßig unveränderlich; Arc<Mutex<T>> für sichere Veränderung | Innerhalb des Anfrageskopfs veränderlich (z. B. HttpRequest::extensions_mut()) | 
| Basistyp | Arc<T> (oft) | Gespeichert in HttpRequest::extensions (intern Box<dyn Any>) | 
| Zugriffsmuster | Über App::app_data injiziert | Über req.extensions_mut().insert() in Middleware oder über benutzerdefinierte Extraktoren injiziert | 
| Typische Verwendung | Datenbankpools, Umgebungskonfiguration, gemeinsam genutzte Clients | Authentifizierter Benutzer, Anforderungs-ID, geparste Tokens | 
Fazit
Sowohl web::Data als auch das Konzept des anfrageskopischen State (oft über web::ReqData abgerufen) sind für die Verwaltung von Informationen innerhalb von Actix Web-Anwendungen unverzichtbar. web::Data ist Ihre bevorzugte Lösung für Ressourcen, die konsistent und über alle Anfragen und Threads hinweg gemeinsam genutzt werden, und bietet eine effiziente Möglichkeit, anwendungsweiten Kontext zu verteilen. Umgekehrt eignet sich der anfrageskopische State ideal für dynamische Informationen, die auf Anfragebasis generiert werden – perfekt, um den Anfragekontext mit Details wie Authentifizierung oder Tracing-Daten anzureichern. Durch die durchdachte Wahl zwischen diesen beiden Mechanismen können Entwickler gut strukturierte, performante und wartbare Actix Web-Dienste erstellen, die globale Anliegen effektiv von anfragespezifischen Kontexten trennen.