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!

Tipp

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.

Jó tudni

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!