====== 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. Link alla [[https://rocket.rs/guide/|Documentazione ufficiale]] ===== 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("

Fabio

") } #[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 = "
")] fn ricevi(form: Form) -> RawHtml { let data = form.into_inner(); let nome = &data.nome; let email = &data.email; RawHtml( format!( "

Ricezione dati dal form:

{}, {}

", nome, email ) .to_string(), ) }
Possiamo mstrare il form con il seguente codice: #[get("/show_form")] fn show_form() -> RawHtml<&'static str> { RawHtml( r#" Form

"#, ) }
===== 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#" 404

404 — Pagina non trovata

"#, ) } ... #[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 ([[https://rocket.rs/guide/v0.5/configuration/#configuration|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> { let body = r#" 404

Forbidden

La risorsa richiesta non puo' essere mostrata.

"# .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 | Network authentication required. ===== 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/")] async fn files(file: PathBuf) -> Option { 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> { 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 Hello

{{name}}

Lista passata da Rust (vec[]) :

==== 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> { 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> { 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> { 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> { match cookies.get("val") { Some(c) => { let res = format!("

Valore del cookie: {}

", c.value()).to_string(); Custom(Status::Accepted, RawHtml(res)) } None => { let res = "

Cookie non impostato!

".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> { match cookies.get_private("val_private") { Some(c) => { let res = format!("

Valore del cookie: {}

", c.value()).to_string(); Custom(Status::Accepted, RawHtml(res)) } None => { let res = "

Cookie non impostato!

".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 ===== Work in progress. Non usare in produzione 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> fn set_session( cookies: &CookieJar<'_>, //cookie Rocket cookie_name: String, // valkey key val: String, // json key val_elem: String, // json key val ) -> Result> Le funzioni per intero sono disponibili a questo indirizzo, incluse in una appa base minimale: [[https://www.freemedialab.org/code/project.php?p=rocket_base_app|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> { 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!( "

Variabile di sessione letta correttamente: {}

", user_name ) .to_string(), ), ); } Err(e) => { return Custom( Status::Accepted, RawHtml( format!( "

Errore nel leggere la variabile di sessione: {}

", e ) .to_string(), ), ); } } } #[get("/page_set_session")] fn page_set_session(cookies: &CookieJar<'_>) -> Custom> { 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("

Sessione aggiornata con successo!

".to_string()), ), Err(e) => Custom( Status::InternalServerError, RawHtml( format!( "

Problemi nel settare la variabile di sessionei: {}

", 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> { 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!("

Variabile: {}

", 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> { 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!") }