PV281: Programování v Rustu1/47 Obsah HTMX Askama rozšíření Actix Cookie Actix Session 2/47 AJAX (Asynchronous Javascript and XML) Pojďme se podívat 10 let zpátky: místo celé stránky posíláme pouze vykreslené části není třeba překreslit celou stránku 3/47 Kdy se mi to bude hodit? 4/47 Instalace přes npm npm install htmx.org následně přidat do stránky node_modules/htmx.org/dist/htmx.js 5/47 Instalace přes CDN (unpkg) 6/47 Load data
Response je vykreslená v elementu, který provedl request. 7/47 Requesty Atribut Typ requestu hx-get Provede GET na URL hx-post Provede POST na URL hx-put Provede PUT na URL hx-patch Provede PATCH na URL hx-delete Provede DELETE na URL 8/47 Předání parametrů
Get Some HTML, Including A Value in the Request
9/47 Target
Products
Pro změnu cíle pro vykreslení je nutné použít hx-target . 10/47 Extended selector Atributy (jako hx-target ), které očekávají CSS selector, ve větši případů podporují rozšířenou syntaxi. closest najde nejbližšího rodiče elementu next najde element níže v DOMu (následující) previous najde element výše v DOMu (předcházející) find najde nejbližšího potomka elementu 11/47 Swapping Hodnota Popis innerHTML výchozí nastavení - nahradí obsah elementu (potomky) outerHTML nahradí celý element včetně potomků none bez vypsání odpovědi 12/47 Loading indikátor Standardně se loading indikátor použije potomek s třídou htmx-indicator . Jeho opacity se nastaví na 1. Element jde vybrat pomocí hx-indicator="#indicator" . 13/47 View Transition Využítím View Transitions API lze animatovat změny v rámci DOMu.
Products
14/47 Vykreslení JSONu Vzhledem k potenciálním problémům s CORS je doporučené použít server jako proxy pro získání dat. Pokud ale k tak potřebujete udělat request na klientu, tak lze použít klientské šablony. 15/47 Klientské šablony 16/47 Mustache šablona
Loading...
17/47
Pozor na mixování s Askamou 18/47 Formulář
19/47 Přidání parametru mimo form
Enter email:
20/47 Odeslaní dat jako JSON Přidáním hx-ext='json-enc' je request odeslaný jako typ applicaton/json s převodem na JSON v těle.
21/47 Odeslání souboru Periodicky se vyvolává htmx:xhr:progress . Odpovídá standardní progress události během uploadu.
22/47 Zpracování v Rustu async fn save_files( MultipartForm(form): MultipartForm, ) -> Result { for f in form.files { let path = format!("./tmp/{}", f.file_name.unwrap()); log::info!("saving to {path}"); f.file.persist(path).unwrap(); } Ok(HttpResponse::Ok()) } 23/47 Zpracování v Rustu #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); log::info!("creating temporary upload directory"); std::fs::create_dir_all("./tmp")?; log::info!("starting HTTP server at http://localhost:8080"); HttpServer::new(|| { App::new() .wrap(middleware::Logger::default()) .app_data(TempFileConfig::default().directory("./tmp")) .service( web::resource("/") .route(web::get().to(index)) .route(web::post().to(save_files)), ) }) .bind(("127.0.0.1", 8080))? .workers(2) .run() .await } 24/47 Klientská validace Provádí se na formulářových prvcích. Jinde je třeba zapnout přes hx-validate='true' . Skriptování je přes hyperscript. Pro instalaci: přidat: 25/47 Klientská validace
26/47 CSS extensions
27/47 Multiswap
...
...
...
28/47 Práce s historii
Go to My Account
Pozor - pokud měníte URL musíte i umět překreslit celou stránku. 29/47 Práce s historií
Go to My Account
A nebo chytře rozdělit url komponenty (partial view) a URL stránky. 30/47 Websocket
...
31/47 Šablony pomocí Askamy Jeden ze šablonovacích enginů. Šablony jsou kompilované s typovou kontrolou. [dependencies] actix-web = "4" askama = "0.10" [build-dependencies] askama = "0.10" Alternativou může být např. crate Tera. 32/47 Rust kód šablony use std::collections::HashMap; use actix_web::{web, App, HttpResponse, HttpServer, Result}; use askama::Template; #[derive(Template)] #[template(path = "user.html")] struct UserTemplate<'a> { name: &'a str, text: &'a str, } #[derive(Template)] #[template(path = "index.html")] struct Index; async fn index( query: web::Query> ) -> Result { let s = if let Some(name) = query.get("name") { UserTemplate { name, text: "Welcome!", } .render() .unwrap() } else { Index.render().unwrap() }; Ok(HttpResponse::Ok().content_type("text/html").body(s)) } 33/47 Dědičnost šablon Definice bloků title , head a content pro použití v potomcích. {% block title %}{{ title }} - My Site{% endblock %} {% block head %}{% endblock %}
{% block content %}

Placeholder content

{% endblock %}
34/47 Child template {% extends "base.html" %} {% block title %}Index{% endblock %} {% block head %} {% endblock %} {% block content %}

Index

Hello, world!

{% call super() %} {% endblock %} 35/47 Include
{% include "item.html" %}
36/47 For

Users

    {% for user in users %}
  • {{ user.name|e }}
  • {% endfor %}
37/47 If {% if users.len() == 0 %} No users {% else if users.len() == 1 %} 1 user {% else %} {{ users.len() }} users {% endif %} 38/47 If {% match item %} {% when Some with ("foo") %} Found literal foo {% when Some with (val) %} Found {{ val }} {% when None %} {% endmatch %} 39/47 Co je cookie? 40/47 Actix Cookie use cookie::Cookie; let cookie = Cookie::build("name", "value") .domain("www.rust-lang.org") .path("/") .secure(true) .http_only(true) .finish(); 41/47 Actix Session umožňuje držet stav uživatele. session je postavená na cookie (Set-Cookie). je třeba řešit tak, aby řešení bylo škálovatelné (Redis vs vše v cookie) OWASP cheatsheat na práci se session: https://cheatsheetseries.owasp.org/cheatsheets/Session_Manageme 42/47 use actix_web::{App, cookie::{Key, time}, Error, HttpResponse, HttpServer, web}; use actix_session::{Session, SessionMiddleware, storage::RedisActorSessionStore}; use actix_session::config::PersistentSession; #[actix_web::main] async fn main() -> std::io::Result<()> { let secret_key = get_secret_key_from_config(); let redis_connection_string = "127.0.0.1:6379"; HttpServer::new(move || App::new() .wrap( SessionMiddleware::builder( RedisActorSessionStore::new(redis_connection_string), secret_key.clone() ) .session_lifecycle( PersistentSession::default() .session_ttl(time::Duration::days(5)) ) .build(), ) .default_service(web::to(|| HttpResponse::Ok()))) .bind(("127.0.0.1", 8080))? .run() .await } 43/47 Výběr backendu [dependencies] # ... actix-session = { version = "...", features = ["cookie-session"] } # pro Actix Redis actix-session = { version = "...", features = ["redis-actor-session"] } 44/47 Získání session use actix_session::Session; #[get("/")] async fn index(session: Session) -> Result { // access session data if let Some(count) = session.get::("counter")? { session.insert("counter", count + 1)?; } else { session.insert("counter", 1)?; } let count = session.get::("counter")?.unwrap(); Ok(format!("Counter: {}", count)) } 45/47 Dotazy? 46/47 Děkuji za pozornost 47/47