Hinweis: Diese Inhalte wurden mit Unterstützung von Künstlicher Intelligenz erstellt und redaktionell überprüft (Transparenzhinweis gemäß Art. 50 EU AI Act).
Axum und Actix-web
Hinweis: Diese Inhalte wurden mit Unterstützung von Künstlicher Intelligenz erstellt und redaktionell überprüft (Transparenzhinweis gemäß Art. 50 EU AI Act).
Axum und Actix-web sind die beiden verbreitetsten Web-Frameworks für die Programmiersprache Rust. Mit ihnen baut man sehr schnelle und speichersichere Webserver und REST-Schnittstellen. Das Ergebnis ist ein einzelnes, eigenständiges Programm ohne Laufzeitumgebung.
Vorbemerkungen
-
Die beiden Frameworks im Vergleich:
Axum Actix-web Lizenz MIT MIT oder Apache 2.0 (nach Wahl) Herkunft Vom Tokio-Projekt, das auch die Grundlage für asynchrones Rust liefert Eigenständiges Projekt, eines der ältesten Rust-Web-Frameworks Routen festlegen Zentral im Router, Funktionen bleiben gewöhnlicheasync fnÜber Makros wie #[get("/pfad")]direkt an der FunktionLaufzeit Tokio, gemeinsam für den ganzen Server Eigenes System auf Tokio-Basis, ein Arbeitsbereich pro Prozessorkern Besonderheit Passt nahtlos zu anderen Tokio- und Tower-Bausteinen Sehr ausgereift, eigenes Ökosystem an Erweiterungen Beide sind schnell genug für praktisch jeden Einsatz. Die Wahl ist vor allem eine Frage des Geschmacks.
-
Installation: Den Rust-Compiler
rustcund das Build-Werkzeugcargoliefert Ubuntu 26.04 in Version 1.93. Das genügt für beide Frameworks (Axum braucht mindestens 1.80, Actix-web mindestens 1.88). Die Frameworks selbst werden pro Projekt voncargoaus dem Paketverzeichnis crates.io geladen. -
Gleiches Beispiel: Beide Projekte bekommen dieselbe kleine Notiz-Schnittstelle wie in der ASP.NET-Core-Anleitung, damit man sie gut vergleichen kann.
-
Versionen: Axum 0.8, Actix-web 4.15, Tokio 1.53.
Installation
1. Paketlisten aktualisieren
Damit apt die aktuellen Versionen von Rust und Cargo aus den Ubuntu-Paketquellen kennt.
sudo apt update
2. Rust und Cargo installieren
Installiert den Compiler rustc und das Build-Werkzeug cargo, das Projekte anlegt, Abhängigkeiten lädt und übersetzt. Den C-Compiler gcc, den Rust zum Zusammenfügen (Linken) der Programme braucht, installiert apt automatisch mit.
sudo apt install rustc cargo
Prüfen: Die Ausgabe nennt die Version, z. B. cargo 1.93.1.
cargo --version
Hast du Rust zusätzlich über rustup installiert (Befehle im Ordner ~/.cargo/bin), wird dessen neuere Version angezeigt. Das ist in Ordnung, beide Wege funktionieren.
Axum
3. Projekt anlegen
cargo new legt ein neues Rust-Programm im Ordner ~/hallo-axum an: die Projektbeschreibung Cargo.toml und den Quellcode src/main.rs.
cargo new ~/hallo-axum
4. In den Projektordner wechseln
Die Befehle bis Schritt 10 beziehen sich auf diesen Ordner.
cd ~/hallo-axum
5. Abhängigkeiten hinzufügen
cargo add trägt Bibliotheken (in Rust Crates genannt) in Cargo.toml ein:
axum– das Web-Frameworktokiomit allen Funktionen (full) – die Laufzeitumgebung für asynchrone Programme, auf der Axum aufbautserdemitderive– wandelt eigene Datentypen in JSON um und zurückserde_json– für JSON-Werte ohne eigenen Datentyp
cargo add axum tokio serde serde_json --features tokio/full,serde/derive
Prüfen: Im Abschnitt [dependencies] stehen die vier Crates mit ihren Versionen.
cat Cargo.toml
6. Programmcode schreiben
Ersetzt src/main.rs. Die wichtigsten Bausteine:
#[derive(Serialize, Deserialize)]– erzeugt automatisch den Code zum Umwandeln in und aus JSON- Handler – gewöhnliche
async fn. Was sie brauchen, geben sie als Parameter an:Queryliest Werte aus der Adresse,Jsonaus dem mitgeschickten Inhalt,Stategreift auf gemeinsame Daten zu. Arc<Mutex<…>>– eine Liste, die sich alle gleichzeitig laufenden Anfragen sicher teilen könnenRouter– legt zentral fest, welcher Handler welche Adresse und Methode beantwortetTcpListener::bind("127.0.0.1:3000")– der Server ist nur vom eigenen Rechner aus erreichbar, auf Port 3000
nano src/main.rs
Die Datei hat schon Inhalt aus der Vorlage, der vollständig ersetzt wird. Lösche ihn zuerst: Alt+\ springt an den Anfang, Alt+A beginnt eine Markierung, Alt+/ springt ans Ende, Strg+K schneidet alles Markierte aus. Füge diesen Inhalt ein (im Terminal mit Strg+Umschalt+V), speichere mit Strg+O und Enter und beende nano mit Strg+X:
use std::sync::{Arc, Mutex};
use axum::{
extract::{Query, State},
http::StatusCode,
routing::get,
Json, Router,
};
use serde::{Deserialize, Serialize};
// Datentypen: Serialize/Deserialize wandeln automatisch in JSON um und zurück
#[derive(Clone, Serialize)]
struct Notiz {
id: usize,
titel: String,
}
#[derive(Deserialize)]
struct NeueNotiz {
titel: String,
}
#[derive(Deserialize)]
struct Name {
name: Option<String>,
}
// Gemeinsamer Zustand: die Notizliste, sicher für gleichzeitige Zugriffe
type Notizen = Arc<Mutex<Vec<Notiz>>>;
// GET /hallo?name=... liefert eine Begrüßung als JSON
async fn hallo(Query(abfrage): Query<Name>) -> Json<serde_json::Value> {
let name = abfrage.name.unwrap_or_else(|| "Welt".to_string());
Json(serde_json::json!({ "gruss": format!("Hallo {name}!") }))
}
// GET /notizen liefert alle Notizen
async fn liste(State(notizen): State<Notizen>) -> Json<Vec<Notiz>> {
Json(notizen.lock().unwrap().clone())
}
// POST /notizen legt eine Notiz an; der Inhalt kommt als JSON
async fn anlegen(
State(notizen): State<Notizen>,
Json(eingabe): Json<NeueNotiz>,
) -> (StatusCode, Json<Notiz>) {
let mut liste = notizen.lock().unwrap();
let notiz = Notiz { id: liste.len() + 1, titel: eingabe.titel };
liste.push(notiz.clone());
(StatusCode::CREATED, Json(notiz))
}
#[tokio::main]
async fn main() {
let notizen: Notizen = Arc::new(Mutex::new(Vec::new()));
// Router: welche Funktion beantwortet welche Adresse und Methode
let app = Router::new()
.route("/hallo", get(hallo))
.route("/notizen", get(liste).post(anlegen))
.with_state(notizen);
// Nur vom eigenen Rechner aus erreichbar, Port 3000
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
println!("Axum läuft auf http://127.0.0.1:3000");
axum::serve(listener, app).await.unwrap();
}
7. Übersetzen und starten
cargo run lädt beim ersten Mal alle Crates herunter (nach ~/.cargo/registry), übersetzt sie und startet das Programm. Das dauert beim ersten Mal etwa eine halbe Minute, danach nur noch Sekunden. Das Terminal bleibt belegt, solange der Server läuft.
cargo run
Prüfen: Die letzte Zeile lautet Axum läuft auf http://127.0.0.1:3000.
8. Schnittstelle testen
Öffne ein zweites Terminal und lege eine Notiz an.
curl -i -X POST http://localhost:3000/notizen -H "Content-Type: application/json" -d '{"titel": "Erste Notiz"}'
Prüfen: Die erste Zeile lautet HTTP/1.1 201 Created, die letzte {"id":1,"titel":"Erste Notiz"}. Außerdem liefern:
curl http://localhost:3000/notizen→[{"id":1,"titel":"Erste Notiz"}]curl "http://localhost:3000/hallo?name=Thorsten"→{"gruss":"Hallo Thorsten!"}
Fehlt beim Anlegen die Angabe Content-Type: application/json, antwortet Axum mit 415 Unsupported Media Type.
9. Server beenden
Wechsle in das erste Terminal und drücke Strg+C.
10. Fertiges Programm erstellen
--release übersetzt mit allen Optimierungen. Das Ergebnis ist ein einzelnes Programm von etwa 2 MB, das ohne Rust oder weitere Dateien auf jedem vergleichbaren Linux-System läuft.
cargo build --release
Prüfen: Das Programm liegt unter target/release/hallo-axum und lässt sich direkt starten:
./target/release/hallo-axum
Mit Strg+C beendest du es wieder.
Actix-web
11. Projekt anlegen
Legt das zweite Projekt im Ordner ~/hallo-actix an.
cargo new ~/hallo-actix
12. In den Projektordner wechseln
Die Befehle bis Schritt 18 beziehen sich auf diesen Ordner.
cd ~/hallo-actix
13. Abhängigkeiten hinzufügen
Actix-web bringt seine Laufzeitumgebung selbst mit, deshalb ist tokio hier nicht nötig.
cargo add actix-web serde serde_json --features serde/derive
14. Programmcode schreiben
Ersetzt src/main.rs. Die Unterschiede zu Axum:
#[get("/hallo")],#[post("/notizen")]– die Adresse steht als Makro direkt über der Funktionweb::Query,web::Json,web::Data– entsprechenQuery,JsonundStatebei Axumimpl Responder– die Funktion gibt „irgendetwas Beantwortbares“ zurück, hier eineHttpResponsemit Status und JSONHttpServer::new(move || App::new() …)– Actix-web startet mehrere Arbeitsbereiche (einen pro Prozessorkern) und baut für jeden eine eigeneApp. Die gemeinsame Notizliste reichtweb::Dataan alle weiter.bind(("127.0.0.1", 8081))– nur vom eigenen Rechner aus erreichbar, auf Port 8081
Hinweis: Das Makro #[get("/notizen")] macht aus der Funktion liste einen gleichnamigen Datentyp. Eine Variable darf deshalb im selben Programm nicht ebenfalls liste heißen. Im Code unten heißt sie darum alle.
nano src/main.rs
Die Datei hat schon Inhalt aus der Vorlage, der vollständig ersetzt wird. Lösche ihn zuerst: Alt+\ springt an den Anfang, Alt+A beginnt eine Markierung, Alt+/ springt ans Ende, Strg+K schneidet alles Markierte aus. Füge diesen Inhalt ein (im Terminal mit Strg+Umschalt+V), speichere mit Strg+O und Enter und beende nano mit Strg+X:
use std::sync::Mutex;
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
// Datentypen: Serialize/Deserialize wandeln automatisch in JSON um und zurück
#[derive(Clone, Serialize)]
struct Notiz {
id: usize,
titel: String,
}
#[derive(Deserialize)]
struct NeueNotiz {
titel: String,
}
#[derive(Deserialize)]
struct Name {
name: Option<String>,
}
// Gemeinsamer Zustand: die Notizliste, sicher für gleichzeitige Zugriffe
struct Notizen(Mutex<Vec<Notiz>>);
// GET /hallo?name=... liefert eine Begrüßung als JSON
#[get("/hallo")]
async fn hallo(abfrage: web::Query<Name>) -> impl Responder {
let name = abfrage.name.clone().unwrap_or_else(|| "Welt".to_string());
HttpResponse::Ok().json(serde_json::json!({ "gruss": format!("Hallo {name}!") }))
}
// GET /notizen liefert alle Notizen
#[get("/notizen")]
async fn liste(notizen: web::Data<Notizen>) -> impl Responder {
HttpResponse::Ok().json(notizen.0.lock().unwrap().clone())
}
// POST /notizen legt eine Notiz an; der Inhalt kommt als JSON
#[post("/notizen")]
async fn anlegen(notizen: web::Data<Notizen>, eingabe: web::Json<NeueNotiz>) -> impl Responder {
let mut alle = notizen.0.lock().unwrap();
let notiz = Notiz { id: alle.len() + 1, titel: eingabe.titel.clone() };
alle.push(notiz.clone());
HttpResponse::Created().json(notiz)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let notizen = web::Data::new(Notizen(Mutex::new(Vec::new())));
println!("Actix-web läuft auf http://127.0.0.1:8081");
// Für jeden Arbeits-Thread wird eine App mit denselben Routen gebaut;
// die Notizliste teilen sich alle über web::Data
HttpServer::new(move || {
App::new()
.app_data(notizen.clone())
.service(hallo)
.service(liste)
.service(anlegen)
})
// Nur vom eigenen Rechner aus erreichbar, Port 8081
.bind(("127.0.0.1", 8081))?
.run()
.await
}
15. Übersetzen und starten
Lädt die Crates, übersetzt und startet den Server. Crates, die schon für Axum geladen wurden, holt cargo aus dem Zwischenspeicher.
cargo run
Prüfen: Die letzte Zeile lautet Actix-web läuft auf http://127.0.0.1:8081.
16. Schnittstelle testen
Im zweiten Terminal dieselbe Anfrage wie bei Axum, nur mit Port 8081.
curl -i -X POST http://localhost:8081/notizen -H "Content-Type: application/json" -d '{"titel": "Erste Notiz"}'
Prüfen: Die Antworten sind identisch mit denen von Axum: HTTP/1.1 201 Created und {"id":1,"titel":"Erste Notiz"}. Auch /notizen und /hallo?name=… verhalten sich gleich.
17. Server beenden
Wechsle in das erste Terminal und drücke Strg+C.
18. Fertiges Programm erstellen
Übersetzt mit allen Optimierungen. Das Programm ist mit etwa 7 MB etwas größer als bei Axum, weil Actix-web mehr Funktionen fest einbaut (z. B. Komprimierung).
cargo build --release
Prüfen: Das Programm liegt unter target/release/hallo-actix.
ls -lh target/release/hallo-actix
Wie geht es weiter?
- Datenbank: Die Crate
sqlxgreift asynchron auf PostgreSQL oder SQLite zu und prüft SQL-Abfragen schon beim Übersetzen. - Betrieb: Das fertige Programm lässt sich als systemd-Dienst starten (siehe die Dienstdatei in der Qdrant-Anleitung als Vorlage). Davor setzt man nginx, der auch HTTPS übernimmt.
- Entwicklungsumgebung: VS Code mit der Erweiterung „rust-analyzer“, Neovim mit rust-analyzer oder JetBrains RustRover.
Deinstallieren
1. Projekte entfernen
Löscht beide Projektordner samt übersetzter Programme.
rm -rf ~/hallo-axum ~/hallo-actix
2. Heruntergeladene Crates entfernen
cargo hat die Crates in ~/.cargo/registry zwischengespeichert. Achtung: Hast du Rust über rustup installiert, liegt in ~/.cargo auch die Rust-Installation selbst. Lösche dann nur diesen Unterordner, nicht ganz ~/.cargo.
rm -rf ~/.cargo/registry
3. Optional: Rust und Cargo entfernen
Nur ausführen, wenn du Rust aus den Ubuntu-Paketen nicht mehr brauchst.
sudo apt purge rustc cargo
sudo apt autoremove
Prüfen: Die Projektordner existieren nicht mehr.
ls ~/hallo-axum ~/hallo-actix
Hinweis: Diese Inhalte wurden mit Unterstützung von Künstlicher Intelligenz erstellt und redaktionell überprüft (Transparenzhinweis gemäß Art. 50 EU AI Act).
Open Knowledge (Freies Wissen): Alle Texte stehen unter der freien Lizenz Creative Commons Namensnennung – Weitergabe unter gleichen Bedingungen 4.0 International (CC BY-SA 4.0) und können frei gelesen, geteilt und weiterverarbeitet werden.