Tower für Rust: Vollständige Anleitung mit Beispielen

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Was ist Tower und wozu braucht man es?

Tower ist eine fundamentale Bibliothek im Rust-Ökosystem, die Abstraktionen für den Bau modularer, komponierbarer und asynchroner Netzwerkdienste und Middleware bereitstellt. Es ist kein fertiges HTTP-Framework (wie Actix-web oder Axum), sondern eher ein "Baustein" für diese. Axum zum Beispiel baut direkt auf Tower auf.

Die Kernidee von Tower ist das Service-Trait, das eine asynchrone Funktion repräsentiert, die eine Anfrage (Request) in eine Antwort (Response) umwandelt. Middleware (oder "Layer") in Tower ist eine Möglichkeit, einen Service mit einem anderen zu umschließen und ihm zusätzliches Verhalten hinzuzufügen (Logging, Authentifizierung, Timeouts, Lastverteilung usw.), ohne den ursprünglichen Service-Code zu ändern.

Wozu braucht man das?

  • Modularität: Sie können komplexe Anfrageverarbeitungslogik in kleine, unabhängige, wiederverwendbare Komponenten (Services und Middleware) aufteilen.
  • Komponierbarkeit: Middleware lässt sich einfach kombinieren, indem man sie wie Schichten (Layers) übereinander legt.
  • Asynchronität: Tower ist für asynchrones Rust (tokio, async-std) konzipiert und unterstützt Futures.
  • Testbarkeit: Jede Komponente kann isoliert getestet werden.

Installation von Tower

Tower ist eine Rust-Bibliothek und wird über den Paketmanager Cargo installiert. Fügen Sie die Abhängigkeit in Ihre Cargo.toml-Datei ein:

[dependencies]
tower = { version = "0.4", features = ["full"] } # "full" aktiviert alle Features
tokio = { version = "1", features = ["full"] } # Für die asynchrone Laufzeitumgebung

Führen Sie anschließend folgenden Befehl im Terminal aus:

cargo build

Die Bibliothek wird heruntergeladen und zusammen mit Ihrem Projekt kompiliert.

Schnellstart: Ihr erster Service und Ihre erste Middleware

Lassen Sie uns einen einfachen Service erstellen, der einen String entgegennimmt und in Großbuchstaben zurückgibt. Dann umschließen wir ihn mit einer Middleware, die jede Anfrage protokolliert.

use std::convert::Infallible;
use std::task::{Context, Poll};
use tower::{Service, Layer, ServiceBuilder};
use tower::layer::util::Identity;
use std::future::{Ready, ready};

// 1. Unseren Service definieren
#[derive(Clone)]
struct MyService;

impl<Request> Service<Request> for MyService {
    type Response = String;
    type Error = Infallible;
    type Future = Ready<Result<Self::Response, Self::Error>>;

    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        Poll::Ready(Ok(()))
    }

    fn call(&mut self, req: Request) -> Self::Future {
        // Einfach einen String zurückgeben
        ready(Ok(format!("Hallo vom Service! Anfrage: {:?}", std::any::type_name::<Request>())))
    }
}

// 2. Middleware für Logging definieren
#[derive(Clone)]
struct LogMiddleware;

impl<S> Layer<S> for LogMiddleware {
    type Service = LogService<S>;

    fn layer(&self, inner: S) -> Self::Service {
        LogService { inner }
    }
}

#[derive(Clone)]
struct LogService<S> {
    inner: S,
}

impl<S, Request> Service<Request> for LogService<S>
where
    S: Service<Request>,
    S::Future: std::marker::Send,
    Request: std::fmt::Debug,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = S::Future;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, req: Request) -> Self::Future {
        println!("[LOG] Anfrage erhalten: {:?}", req);
        self.inner.call(req)
    }
}

#[tokio::m                    

Empfehlungen