Ez a lecke nem tanít új fogalmat – az egész Async, Tokio és webszerverek útvonalat foglalja össze, a Future-ök és a poll modelltől kezdve, a Tokio taskokon és csatornákon át, az Axum routeren, extractorokon és state-en keresztül, a middleware/tracing és sqlx anyagon át egészen a JWT autentikációig és az előző leckében megismert Docker + Fly.io deploy-ig. Minden kérdés után lenyitható a megoldás – próbálj meg előbb önállóan válaszolni, csak utána nézz bele!
Ha egy kérdésnél elakadsz, ne csüggedj – a lecke végén útmutatót adunk, melyik korábbi anyagot érdemes átnézni.
1. kérdés – a Future és a poll
Mit ad vissza a Future trait poll metódusa?
A) Option<T>
B) Result<T, E>
C) Poll<T>, amely Ready(T) vagy Pending
D) mindig ()-t, a tényleges értéket a Waker küldi el
Megoldás
C) A poll egy Poll<T> enumot ad vissza, amelynek két varianse van: Ready(T), ha a Future már kész, és Pending, ha még várni kell. Pending esetén a Future regisztrálja a Waker-t, hogy az executor később újra próbálkozhasson.
2. kérdés – mit ír ki ez a kód?
async fn square(x: i32) -> i32 {
x * x
}
#[tokio::main]
async fn main() {
let result = square(6).await;
println!("Az eredmény: {result}");
}
Megoldás
A program kiírja: Az eredmény: 36. A square egy async fn, amely a hívás pillanatában csak egy Future-t ad vissza – a tényleges számítás a .await-nél fut le, ami itt azonnal Ready(36)-ot kap, mivel nincs benne semmi valódi várakozás (I/O, sleep).
3. kérdés – spawnolt task és JoinHandle
use tokio::task::JoinHandle;
#[tokio::main]
async fn main() {
let handle: JoinHandle<i32> = tokio::spawn(async { 1 + 1 });
println!("A main folytatódik, mielőtt megvárnánk a handle-t.");
// Itt nem awaitáljuk meg a handle-t.
}
Mi történik a spawnolt taskkal, ha a main a .await hívása előtt véget ér?
Megoldás
A tokio::spawn egy önálló taskot indít a runtime-on, amely a main függvénytől függetlenül fut. Ha a main (és vele a runtime) leáll, mielőtt a taskot megvárnánk, a task futása megszakadhat, mielőtt befejeződne – a JoinHandle awaitolása nélkül nincs garancia arra, hogy a task lefut és az eredményét felhasználhatjuk.
4. kérdés – select! és a versenyhelyzet
use tokio::sync::mpsc;
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
let (tx1, mut rx1) = mpsc::channel::<&str>(1);
let (tx2, mut rx2) = mpsc::channel::<&str>(1);
tokio::spawn(async move {
sleep(Duration::from_millis(50)).await;
let _ = tx1.send("gyors csatorna").await;
});
tokio::spawn(async move {
sleep(Duration::from_millis(200)).await;
let _ = tx2.send("lassú csatorna").await;
});
tokio::select! {
msg1 = rx1.recv() => println!("Érkezett: {msg1:?}"),
msg2 = rx2.recv() => println!("Érkezett: {msg2:?}"),
}
}
Mit ír ki ez a program?
Megoldás
Kiírja: Érkezett: Some("gyors csatorna"). A select! makró azt az ágat futtatja le, amelynek a Future-je elsőként lesz Ready – itt a 50 ms-os csatorna előbb küld üzenetet, mint a 200 ms-os, így az a branch nyer, a másik ág eldobásra kerül.
5. kérdés – mpsc csatorna és a Sender eldobása
Mi történik, ha egy mpsc::channel összes Sender klónja eldobásra kerül (drop), a receiver oldalon pedig még nem érkezett meg minden üzenet?
A) A recv() örökre blokkol
B) A recv() visszaadja a még a bufferben lévő üzeneteket, majd None-t ad, ha kiürült a csatorna
C) panic keletkezik
D) a program azonnal leáll
Megoldás
B) A csatorna a bufferben lévő üzeneteket még kiszolgálja, de amint az összes Sender megszűnt és a puffer kiürült, a recv() None-t ad vissza – ez a jelzés a receiver felé, hogy nincs több adó, a csatornát be lehet zárni.
6. kérdés – oneshot csatorna
Hányszor lehet üzenetet küldeni egy tokio::sync::oneshot::Sender-en keresztül?
Megoldás
Pontosan egyszer. A oneshot csatorna kifejezetten egyetlen érték egyszeri átadására van kitalálva (pl. egy válasz visszaküldésére egy háttértaskból) – a send metódus a Sender-t elfogyasztja (move), így másodszor nem hívható meg rajta.
7. kérdés – Axum router
use axum::{routing::get, Router};
async fn hello() -> &'static str {
"Szia, Rust!"
}
fn app() -> Router {
Router::new().route("/hello", get(hello))
}
#[tokio::main]
async fn main() {
let router = app();
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, router).await.unwrap();
}
Melyik HTTP metódussal érhető el a /hello végpont?
Megoldás
Csak GET-tel, mivel a route hívásban get(hello) szerepel. Ha POST-tal próbálnánk elérni, az Axum egy 405 Method Not Allowed választ adna, mert a routeren nincs POST handler regisztrálva ehhez az útra.
8. kérdés – extractor sorrend
Nézd meg ezt a handler-aláírást (részlet, nem önmagában fordítható):
async fn create_item(
Json(payload): Json<CreateItem>,
State(pool): State<PgPool>,
) -> impl IntoResponse {
// ...
}
Fordul-e ez a kód, és ha nem, miért?
Megoldás
Nem fordul. Az Axum extractor-szabálya szerint a body-t olvasó extractor (pl. Json<T>) mindig az utolsó paraméter kell legyen, mert a request body csak egyszer, egyfolytában olvasható – a nem-body extractoroknak (mint a State) meg kell előznie a body extractort. A helyes sorrend: State előbb, Json utoljára.
9. kérdés – tracing
Mire szolgál a tracing és a tracing_subscriber::fmt::init() páros egy Axum alkalmazásban?
A) Kizárólag a panic-ok elkapására B) Strukturált naplózásra és a kérések nyomon követésére (span-ek, eventek) a konzolra vagy máshova C) Az adatbázis-kapcsolatok pool-ozására D) A JWT token aláírására
Megoldás
B) A tracing crate span-eket és eventeket biztosít strukturált naplózáshoz, a tracing_subscriber::fmt::init() pedig beállítja azt a subscribert, amely ezeket tényleg kiírja (alapesetben a konzolra). Middleware-ként az Axumban is használható, hogy minden kéréshez lássunk logot.
10. kérdés – sqlx és a connection pool
Miért érdemes egy PgPool-t (connection pool) használni közvetlen, egyedi kapcsolatok helyett egy webszerverben?
Megoldás
Mert egy webszerver egyszerre sok kérést kezel, és minden kéréshez új adatbázis-kapcsolatot nyitni túl drága és lassú lenne. A pool előre nyitott, újrafelhasználható kapcsolatokat tart készen, amiket a handlerek State-en keresztül megosztva, párhuzamosan is biztonságosan kikölcsönözhetnek és visszaadhatnak.
11. kérdés – JWT felépítése
Melyik állítás hamis a JWT-re vonatkozóan?
A) Három, ponttal elválasztott, Base64-kódolt részből áll: header, payload, signature B) A payload (claims) tartalmazhat pl. felhasználói ID-t és lejárati időt C) A payload titkosított, ezért bátran tehetünk bele jelszót is D) A signature ellenőrzésével deríthető ki, hogy a tokent nem módosították
Megoldás
C) a hamis állítás. A JWT payloadja nincs titkosítva, csak Base64-kódolt – bárki dekódolhatja és elolvashatja, aki megkapja a tokent. Ezért soha nem szabad érzékeny adatot (jelszót, titkos kulcsot) tenni bele, csak olyan claim-eket, amiket nyugtán megismerhet mindenki, akihez a token eljut.
12. kérdés – Docker és Fly.io
Miért érdemes multi-stage Dockerfile-t használni egy Rust webszerver konténerizálásához, közvetlen egylépéses build helyett?
Megoldás
Azért, hogy a végső image kicsi és biztonságos legyen: az első stage-ben (pl. a hivatalos Rust image-dzsel) lefordítjuk a bináris futtatható fájlt, majd egy második, minimál (pl. debian-slim vagy distroless) image-be csak a lefordított binárist másoljuk át. Így a build-eszközök (compiler, cache, forráskód) nem kerülnek bele a futtatott konténerbe, ami gyorsabb deploy-t és kisebb attack surface-t jelent – ez az alapja a Fly.io-ra való gyors, olcsó deploy-nak is.
Hogyan tovább, ha akadtak hibák?
Ha a 1–4. kérdéseknél hibáztál, nézd át a Future/poll és az async/await, illetve a Tokio taskok és select! témájú leckéket. Ha az 5–6. kérdés okozott gondot, a csatornákról (mpsc, oneshot) szóló lecke a barátod. A 7–8. kérdésnél hibázva térj vissza az Axum router, handler és extractor leckéhez, a 9–10. kérdésnél a middleware/tracing és az sqlx-es adatbázis-lecke segít. A 11. kérdés a JWT-autentikációs leckéhez küld vissza, a 12. pedig egyenesen az előző, Deploy: Docker + Fly.io leckéhez, amelyre ez a záróvizsga is épült.
Ha 9 vagy több kérdésre helyesen válaszoltál, nyugodtan tekintheted magad felkészültnek arra, hogy éles async Rust webszervert tervezz és üzemeltess.
Gratulálunk!
Ha idáig eljutottál, végigvitted a teljes Async, Tokio és webszerverek útvonalat: a Future-ök és a poll modelltől a Tokio executorig, onnan egy teljes Axum REST API-ig adatbázissal, JWT-alapú autentikációval és Docker + Fly.io deploy-jal. Ez már egy éles, produkciós minőségű Rust backend alapja – csak rajtad áll, mit építesz rá tovább. Gratulálunk, és jó kódolást!