Indice
Appunti sul framework web Rocket (Rust)
Autore: Fabio Di Matteo
Ultima revisione: 13/05/2026 11:08
Alcuni pezzettini di codice che possono aiutare a iniziare un progetto.
Hello world
#[macro_use] extern crate rocket; use rocket::response::content::RawHtml; use rocket::http::Status; use rocket::response::Redirect; use rocket::response::status::Custom; //Restituisco stringa semplice #[get("/")] fn index() -> &'static str { "Hello, world!" } //restituisco html #[get("/fabio", format = "text/html")] fn fabio() -> RawHtml<&'static str> { RawHtml("<h2>Fabio</h2>") } #[launch] fn rocket() -> _ { rocket::build().mount("/", routes![index, fabio]) }
Ricezioni dati da un form con metodo POST
#[derive(FromForm)] struct MyForm { nome: String, email: String, } #[post("/invia", data = "<form>")] fn ricevi(form: Form<MyForm>) -> RawHtml<String> { let data = form.into_inner(); let nome = &data.nome; let email = &data.email; RawHtml( format!( "<h3>Ricezione dati dal form:</h3><p>{}, {} </p>", nome, email ) .to_string(), ) }
Possiamo mstrare il form con il seguente codice:
#[get("/show_form")] fn show_form() -> RawHtml<&'static str> { RawHtml( r#"<!DOCTYPE html> <html> <head><meta charset="utf-8"><title>Form</title></head> <body> <form action="/invia" method="post"> <label>Nome: <input name="nome" /></label><br/> <label>Email: <input name="email" /></label><br/> <button type="submit">Invia</button> </form> </body> </html>"#, ) }
Redirect
Un semplice redirect
use rocket::response::Redirect; #[get("/fml")] fn redirect() -> Redirect { Redirect::to("https://www.freemedialab.org/wiki") }
404 personalizzato
#[catch(404)] fn not_found_catcher() -> RawHtml<&'static str> { RawHtml( r#"<!doctype html> <html><head><meta charset="utf-8"><title>404</title></head> <body><h1>404 — Pagina non trovata</h1></body> </html>"#, ) } ... #[launch] fn rocket() -> _ { rocket::build() .mount("/", routes![index]) .register("/", catchers![not_found_catcher]) //il mio catcher per 404 }
Configurare il server
Il server puo' essere configurato in vari modi (Configuration). Io ho usato il file di configurazione “Rocket.toml” nella cartella corrente.
Rocket.toml
[default] address = "127.0.0.1" port = 8080
Restituire Status delle richieste
use rocket::http::Status; use rocket::response::Redirect; use rocket::response::status::Custom; #[get("/forbidden")] fn forbidden() -> Custom<RawHtml<String>> { let body = r#"<!doctype html> <html><head><meta charset="utf-8"><title>404</title></head> <body><h1>Forbidden</h1><p>La risorsa richiesta non puo' essere mostrata.</p></body> </html>"# .to_string(); Custom(Status::Forbidden, RawHtml(body)) }
Tabella dei codici di stato
Restituire come sopra con :
... Custom(Status::Forbidden, RawHtml(body))
| Code | Nome (enum) | Significato breve |
|---|---|---|
| 100 | Status::Continue | Il client può continuare con la richiesta. |
| 101 | Status::SwitchingProtocols | Il server cambia protocollo come richiesto. |
| 102 | Status::Processing | Elaborazione in corso (WebDAV). |
| 103 | Status::EarlyHints | Suggerimenti preliminari (Link header). |
| 200 | Status::Ok | Richiesta completata con successo. |
| 201 | Status::Created | Risorsa creata. |
| 202 | Status::Accepted | Richiesta accettata per elaborazione asincrona. |
| 203 | Status::NonAuthoritativeInformation | Informazioni non autorevoli. |
| 204 | Status::NoContent | Nessun contenuto nella risposta. |
| 205 | Status::ResetContent | Resetta l'input client. |
| 206 | Status::PartialContent | Contenuto parziale (range). |
| 207 | Status::MultiStatus | Multi-status (WebDAV). |
| 208 | Status::AlreadyReported | Già riportato (WebDAV). |
| 226 | Status::ImUsed | Instance-manipulations (HTTP Delta encoding). |
| 300 | Status::MultipleChoices | Scelte multiple. |
| 301 | Status::MovedPermanently | Risorsa spostata permanentemente. |
| 302 | Status::Found | Risorsa trovata (redirect temporaneo). |
| 303 | Status::SeeOther | Vedi altra risorsa (GET). |
| 304 | Status::NotModified | Non modificato (cache). |
| 305 | Status::UseProxy | Usare il proxy indicato (deprecato). |
| 307 | Status::TemporaryRedirect | Redirect temporaneo (mantiene metodo). |
| 308 | Status::PermanentRedirect | Redirect permanente (mantiene metodo). |
| 400 | Status::BadRequest | Richiesta malformata. |
| 401 | Status::Unauthorized | Richiesta non autenticata. |
| 402 | Status::PaymentRequired | Payment Required (riservato). |
| 403 | Status::Forbidden | Accesso negato. |
| 404 | Status::NotFound | Risorsa non trovata. |
| 405 | Status::MethodNotAllowed | Metodo HTTP non consentito. |
| 406 | Status::NotAcceptable | Non accettabile (Accept header). |
| 407 | Status::ProxyAuthenticationRequired | Autenticazione proxy richiesta. |
| 408 | Status::RequestTimeout | Timeout della richiesta. |
| 409 | Status::Conflict | Conflitto nello stato della risorsa. |
| 410 | Status::Gone | Risorsa rimossa definitivamente. |
| 411 | Status::LengthRequired | Content-Length richiesto. |
| 412 | Status::PreconditionFailed | Precondizione fallita. |
| 413 | Status::PayloadTooLarge | Payload troppo grande. |
| 414 | Status::UriTooLong | URI troppo lungo. |
| 415 | Status::UnsupportedMediaType | Media type non supportato. |
| 416 | Status::RangeNotSatisfiable | Range non soddisfacibile. |
| 417 | Status::ExpectationFailed | Expectation header non soddisfatta. |
| 418 | Status::ImATeapot | I'm a teapot (RFC 2324, easter egg). |
| 421 | Status::MisdirectedRequest | Richiesta mal indirizzata. |
| 422 | Status::UnprocessableEntity | Entità non processabile (WebDAV). |
| 423 | Status::Locked | Risorsa bloccata (WebDAV). |
| 424 | Status::FailedDependency | Dipendenza fallita (WebDAV). |
| 425 | Status::TooEarly | Too Early (riesecuzione non sicura). |
| 426 | Status::UpgradeRequired | Upgrade del protocollo richiesto. |
| 428 | Status::PreconditionRequired | Precondizione richiesta. |
| 429 | Status::TooManyRequests | Troppe richieste (rate limit). |
| 431 | Status::RequestHeaderFieldsTooLarge | Campi header troppo grandi. |
| 451 | Status::UnavailableForLegalReasons | Non disponibile per motivi legali. |
| 500 | Status::InternalServerError | Errore interno del server. |
| 501 | Status::NotImplemented | Non implementato. |
| 502 | Status::BadGateway | Bad gateway. |
| 503 | Status::ServiceUnavailable | Servizio non disponibile. |
| 504 | Status::GatewayTimeout | Gateway timeout. |
| 505 | Status::HttpVersionNotSupported | Versione HTTP non supportata. |
| 506 | Status::VariantAlsoNegotiates | Variant Also Negotiates. |
| 507 | Status::InsufficientStorage | Insufficient Storage (WebDAV). |
| 508 | Status::LoopDetected | Loop detected (WebDAV). |
| 510 | Status::NotExtended | Not Extended. |
| 511 | Status::NetworkAuthenticationRequired |
Servire file statici
Esistono almeno 2 modi pr servire file statici. Io ho usato il seguente:
use rocket::fs::NamedFile; use std::path::{Path, PathBuf}; #[get("/static/<file..>")] async fn files(file: PathBuf) -> Option<NamedFile> { let path = Path::new("static/").join(file); NamedFile::open(path).await.ok() }
Templates
Di seguito alcune prove con motori di templates.
Minijinjia
use minijinja::{Environment, context}; //Passo al template 2 variabili. Una str e un vec. #[get("/template")] fn template() -> Custom<RawHtml<String>> { let name = "Fabio Di Matteo"; let html = include_str!("../views/home.html"); //includo la stringa a cmpile time let list = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; //instruisco minijinja let mut env = Environment::new(); env.add_template("hello", html).unwrap(); let tmpl = env.get_template("hello").unwrap(); let res = tmpl.render(context!(name => name, list=>list)).unwrap(); Custom(Status::Accepted, RawHtml(res)) }
ed ecco il template html
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Hello</title> </head> <body> <h1>{{name}}</h1> <p>Lista passata da Rust (vec[]) :</p> <ul> {% for e in list %} {% if e == 5 %} <hr> {% endif %} <li>{{e}}</li> {% endfor %} </ul> </body> </html>
Rendiamo tutto piu' semplice con macro e funzioni
Per evitare di scrivere ogni volta tutto quel codice ecco una macro per inglobare un template dentro l'eseguibile:
use minijinja::{Environment, Value}; use std::error::Error; use std::fs; #[macro_export] macro_rules! template_embed { ($file_name:expr, $ctx:expr) => {{ let text = include_str!($file_name); let mut env = Environment::new(); env.add_template($file_name, text).unwrap(); let tmpl = env.get_template($file_name).unwrap(); let res = tmpl.render($ctx).unwrap(); res }}; } pub fn template_load(file_name: &str, ctx: Value) -> Result<String, Box<dyn Error>> { let text: String; text = fs::read_to_string(file_name)?; let text_clone = text.clone(); let mut env = Environment::new(); env.add_template(file_name, text_clone.as_str())?; let tmpl = env.get_template(file_name)?; let res = tmpl.render(ctx)?; Ok(res) }
Includere template nell'eseguibile:
#[get("/template")] fn template() -> Custom<RawHtml<String>> { let res = template_embed!("views/home.html", context!(title => "A beatifull title!")); Custom(Status::Accepted, RawHtml(res)) }
Caricarlo a caldo:
#[get("/template")] fn template() -> Custom<RawHtml<String>> { let res = templates::template_load("views/home.html", context!(title => "A caldo!")); Custom( Status::Accepted, RawHtml(res.unwrap_or_else(|_| "Error on load template file!".to_string())), ) }
Semplici cookie
use rocket::http::Cookie; use rocket::http::CookieJar; #[get("/get_cookie")] fn get_cookie(cookies: &CookieJar<'_>) -> Custom<RawHtml<String>> { match cookies.get("val") { Some(c) => { let res = format!("<h3>Valore del cookie: {}</h3>", c.value()).to_string(); Custom(Status::Accepted, RawHtml(res)) } None => { let res = "<h3>Cookie non impostato!</h3>".to_string(); Custom(Status::Accepted, RawHtml(res)) } } } #[get("/set_cookie")] fn set_cookie(cookies: &CookieJar<'_>) -> Redirect { cookies.add(Cookie::new("val", "mio valore")); Redirect::to(uri!(get_cookie)) } #[get("/del_cookie")] fn del_cookie(cookies: &CookieJar<'_>) -> Redirect { cookies.remove("val"); Redirect::to(uri!(get_cookie)) }
Cookie cifrati (private)
Aggiungere a Cargo.toml le dipendenze:
rocket = { version = "0.5.1", features = ["secrets"] }
Ecco il codice per usare i cookie private:
#[get("/get_cookie_private")] fn get_cookie_private(cookies: &CookieJar<'_>) -> Custom<RawHtml<String>> { match cookies.get_private("val_private") { Some(c) => { let res = format!("<h3>Valore del cookie: {}</h3>", c.value()).to_string(); Custom(Status::Accepted, RawHtml(res)) } None => { let res = "<h3>Cookie non impostato!</h3>".to_string(); Custom(Status::Accepted, RawHtml(res)) } } } #[get("/set_cookie_private")] fn set_cookie_private(cookies: &CookieJar<'_>) -> Redirect { cookies.add_private(Cookie::new("val_private", "mio valore")); Redirect::to(uri!(get_cookie_private)) } #[get("/del_cookie_private")] fn del_cookie_private(cookies: &CookieJar<'_>) -> Redirect { cookies.remove("val_private"); Redirect::to(uri!(get_cookie_private)) }
Implementare le sessioni
Sto provando ad implementare un meccanismo di sessioni con db in memory Valkey . In pratica nel db salvo una struttora del genere:
123456 '{"id":"1","user":"fabio"}'
Dove il nome della chiave è il valore del cookie (in questo caso : 123456) . E il valore della chiave è un json.
Il cookie potrebbe essere generato lato client in javascript e inviato in una richiesta. A livello di sicurezza non importa, in quanto il cookie memorizza soltanto id della sessione. Evitare cmq il furto del cookie sarebbe la cosa migliore da fare. Mitigando anche con la scadenza dello stesso.
Cargo.toml
...
redis = "1.2.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Le firme delle funzioni commentate
fn get_session( cookies: &CookieJar<'_>, // cookie Rocket cookie_name: String, //valkey key val: String, // json key ) -> Result<String, Box<dyn Error>>
fn set_session( cookies: &CookieJar<'_>, //cookie Rocket cookie_name: String, // valkey key val: String, // json key val_elem: String, // json key val ) -> Result<String, Box<dyn Error>>
Le funzioni per intero sono disponibili a questo indirizzo, incluse in una appa base minimale: rocket_base_app
Come usare le funzioni che gestiscono la sessione. Come contenuto del cookie uso “123456” ma potrei generare un cookie con uuid da javascript(lato client) e mandarlo nella successiva richiesta. Un esempio? my_session_app=1234dfs243dfs298984
#[get("/page_get_session")] fn page_get_session(cookies: &CookieJar<'_>) -> Custom<RawHtml<String>> { cookies.add(Cookie::new("session_id", "123456")); let user_name: String; //prendo la variabile user contenuta sottoforma di json nel db valkey match get_session(cookies, "session_id".to_string(), "user".to_string()) { Ok(u) => { user_name = u.to_string(); return Custom( Status::Accepted, RawHtml( format!( "<h3>Variabile di sessione letta correttamente: {}</h3>", user_name ) .to_string(), ), ); } Err(e) => { return Custom( Status::Accepted, RawHtml( format!( "<h3>Errore nel leggere la variabile di sessione: {}</h3>", e ) .to_string(), ), ); } } } #[get("/page_set_session")] fn page_set_session(cookies: &CookieJar<'_>) -> Custom<RawHtml<String>> { cookies.add(Cookie::new("session_id", "123456")); match set_session( cookies, "session_id".to_string(), "user".to_string(), "Chester Di Matteo".to_string(), ) { Ok(_) => Custom( Status::Accepted, RawHtml("<h3>Sessione aggiornata con successo!</h3>".to_string()), ), Err(e) => Custom( Status::InternalServerError, RawHtml( format!( "<h3>Problemi nel settare la variabile di sessionei: {}</h3>", e ) .to_string(), ), ), } }
Codice piu' compatto per l'utilizzo di get_session(), set_session()
#[get("/easy_get_session")] fn easy_get_session(cookies: &CookieJar<'_>) -> Custom<RawHtml<String>> { cookies.add(Cookie::new("session_id", "123456")); let val = get_session(cookies, "session_id".to_string(), "user".to_string()).unwrap_or_else(|e| { eprintln!("Problemi nel leggere la sessione: {}", e); "".to_string() }); Custom( Status::Accepted, RawHtml(format!("<h3>Variabile: {}</h3>", val).to_string()), ) }
Settare scadenza sessioni
Le sessioni fin qui create sono permanenti sul db valkey. Per settare la scadenza della sessione sul db (non sul cookie) possiamo usare la seguente funzione:
fn set_expire_session(session_name: &str, s: i64) -> Result<bool, Box<dyn Error>> { let client = redis::Client::open("redis://127.0.0.1/")?; let mut conn = client.get_connection()?; let ok: bool = conn.expire(session_name, s)?; Ok(ok) }
Dove session_name è il nome della chiave valkey(che nel nostro caso è anche il valore del cookie[valore, non nome cookie]).
Utilizzo funzione
if let Ok(_) = set_expire_session("123456", 10) { println!("Expire sessione settato correttamente.") } else { eprintln!("Problemi nel settare la scadenza della sessione!") }
