#[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 = "
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[]) :
{% for e in list %}
{% if e == 5 %}
{% endif %}
- {{e}}
{% endfor %}
==== 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 =====
...
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!")
}