使用Rust和WebAssembly構建Web應用程式
無論是React、VueJS、Angular,還是Rust,現代網路應用都是由3種碎片組成的。
- 元件
- 頁面
- 服務
客戶端網路應用的架構
元件是可重複使用的部件和UI元素。例如,一個輸入欄位,或一個按鈕。
頁面是元件的集合體。它們與路由(URLs)相匹配。例如,登入頁面與/login路線相匹配。主頁與/路線相匹配。
最後,服務是輔助工具,用於包裝低階別的功能或外部服務,如HTTP客戶端、儲存......
我們的應用程式的目標很簡單。這是一個入口網站,受害者將在這裡輸入他們的證書(認為這是一個合法的表格),證書將被儲存在一個SQLite資料庫中,然後我們將受害者重定向到一個錯誤頁面,讓他們認為該服務暫時不可用,他們應該以後再嘗試。
安裝工具鏈
wasm-pack可以幫助你構建Rust生成的WebAssembly包,並在瀏覽器或Node.js中使用它。
$ cargo install -f wasm-pack
模型
請注意,在後端使用與前端相同的語言的一個好處是能夠重複使用模型。
ch_09/phishing/common/src/api.rs
pub mod model { use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub struct Login { pub email: String, pub password: String, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub struct LoginResponse { pub ok: bool, } } pub mod routes { pub const LOGIN: &str = "/api/login"; } |
元件
一開始,有元件。元件是可重用的功能或設計。
為了構建我們的元件,我們使用yew, crate ,在我寫這篇文章的時候,它是最先進和受支援的 Rust 前端框架。
Properties(或Props)可以看作是一個元件的引數。例如,函式fn factorial(x: u64) -> u64有一個引數x。對於元件,它是同樣的事情。如果我們想用特定資料渲染它們,我們使用Properties.
ch_09/phishing/webapp/src/components/error_alert.rs
use yew::{html, Component, ComponentLink, Html, Properties, ShouldRender}; pub struct ErrorAlert { props: Props, } #[derive(Properties, Clone)] pub struct Props { #[prop_or_default] pub error: Option<crate::Error>, } |
impl Component for ErrorAlert { type Message = (); type Properties = Props; fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self { ErrorAlert { props } } fn update(&mut self, _: Self::Message) -> ShouldRender { true } fn change(&mut self, props: Self::Properties) -> ShouldRender { self.props = props; true } fn view(&self) -> Html { if let Some(error) = &self.props.error { html! { <div class="alert alert-danger" role="alert"> {error} </div> } } else { html! {} } } } |
非常類似於(老式)React,不是嗎?
另一個元件是LoginForm包裝邏輯以捕獲和儲存憑據的元件。
ch_09/phishing/webapp/src/components/login_form.rs
最後是view函式(類似於render其他框架)。
fn view(&self) -> Html { let onsubmit = self.link.callback(|ev: FocusEvent| { ev.prevent_default(); /* Prevent event propagation */ Msg::Submit }); let oninput_email = self .link .callback(|ev: InputData| Msg::UpdateEmail(ev.value)); let oninput_password = self .link .callback(|ev: InputData| Msg::UpdatePassword(ev.value)); |
rorAlert您可以像任何其他 HTML 元素一樣嵌入其他元件(此處):
html! { <div> <components::ErrorAlert error=&self.error /> <form onsubmit=onsubmit> <div class="mb-3"> <input class="form-control form-control-lg" type="email" placeholder="Email" value=self.email.clone() oninput=oninput_email id="email-input" /> </div> <div class="mb-3"> <input class="form-control form-control-lg" type="password" placeholder="Password" value=self.password.clone() oninput=oninput_password /> </div> <button class="btn btn-lg btn-primary pull-xs-right" type="submit" disabled=false> { "Sign in" } </button> </form> </div> } } } |
頁面
頁面是元件的集合,並且是 yew 中的元件本身。
ch_09/phishing/webapp/src/pages/login.rs
pub struct Login {} impl Component for Login { type Message = (); type Properties = (); // ... fn view(&self) -> Html { html! { <div> <div class="container text-center mt-5"> <div class="row justify-content-md-center mb-5"> <div class="col col-md-8"> <h1>{ "My Awesome intranet" }</h1> </div> </div> <div class="row justify-content-md-center"> <div class="col col-md-8"> <LoginForm /> </div> </div> </div> </div> } } } |
路由
然後我們宣告應用程式的所有可能路由。
正如我們之前看到的,路由將 URL 對映到頁面。
ch_09/phishing/webapp/src/lib.rs
#[derive(Switch, Debug, Clone)] pub enum Route { #[to = "*"] Fallback, #[to = "/error"] Error, #[to = "/"] Login, } |
服務
發出 HTTP 請求
發出 HTTP 請求有點困難,因為我們需要回撥並反序列化響應。
ch_09/phishing/webapp/src/services/http_client.rs
#[derive(Default, Debug)] pub struct HttpClient {} impl HttpClient { pub fn new() -> Self { Self {} } pub fn post<B, T>( &mut self, url: String, body: B, callback: Callback<Result<T, Error>>, ) -> FetchTask where for<'de> T: Deserialize<'de> + 'static + std::fmt::Debug, B: Serialize, { let handler = move |response: Response<Text>| { if let (meta, Ok(data)) = response.into_parts() { if meta.status.is_success() { let data: Result<T, _> = serde_json::from_str(&data); if let Ok(data) = data { callback.emit(Ok(data)) } else { callback.emit(Err(Error::DeserializeError)) } } else { match meta.status.as_u16() { 401 => callback.emit(Err(Error::Unauthorized)), 403 => callback.emit(Err(Error::Forbidden)), 404 => callback.emit(Err(Error::NotFound)), 500 => callback.emit(Err(Error::InternalServerError)), _ => callback.emit(Err(Error::RequestError)), } } } else { callback.emit(Err(Error::RequestError)) } }; let body: Text = Json(&body).into(); let builder = Request::builder() .method("POST") .uri(url.as_str()) .header("Content-Type", "application/json"); let request = builder.body(body).unwrap(); FetchService::fetch(request, handler.into()).unwrap() } } |
話雖如此,它的優點是非常健壯,因為所有可能的錯誤都得到了處理。不再有您永遠不會知道的未捕獲的執行時錯誤。
應用程式
然後是App元件,它包裝了所有內容並呈現了路線。
ch_09/phishing/webapp/src/lib.rs
pub struct App {} impl Component for App { type Message = (); type Properties = (); // ... fn view(&self) -> Html { let render = Router::render(|switch: Route| match switch { Route::Login | Route::Fallback => html! {<pages::Login/>}, Route::Error => html! {<pages::Error/>}, }); html! { <Router<Route, ()> render=render/> } } } |
最後,掛載和啟動 webapp 的入口點:
#[wasm_bindgen(start)] pub fn run_app() { yew::App::<App>::new().mount_to_body(); } |
您可以透過執行以下命令來執行新構建的 Web 應用程式:
$ make webapp_debug $ make serve |
程式碼在 GitHub 上
像往常一樣,您可以在 GitHub 上找到程式碼:github.com/skerkour/black-hat-rust
相關文章
- 使用Java和Spring MVC構建Web應用JavaSpringMVCWeb
- 五、Spring Web應用程式構建SpringWeb
- 使用 Cloudflare 構建 Web3 應用CloudWeb
- 使用 Lambda Web Adapter 在 Lambda 上 構建 web 應用WebAPT
- 解決 Rust WebAssembly 啟動 Web 程式報錯RustWeb
- Let's Markdown:使用Rust、WebAssembly和React構建的實時協作Markdown編輯器!RustWebReact
- 快速學習丨使用Blazor構建Web應用BlazorWeb
- 使用 IBM Bluemix 構建,部署和管理自定義應用程式IBM
- 使用 Rust、OpenAI 和 Qdrant 構建 Agentic RAGRustOpenAI
- 使用JHipster構建Spring和React構建電子商務應用程式原始碼 -DEVSpringReact原始碼dev
- 使用 Docker 和 Elasticsearch 構建一個全文搜尋應用程式DockerElasticsearch
- 如何構建一個WEB同構應用Web
- [譯] Rust 開發完整的 Web 應用程式RustWeb
- 使用微服務構建現代應用程式微服務
- 使用Java和Reactive Streams構建流式應用JavaReact
- 使用 webpack 構建應用Web
- 使用Rust的Tauri和Yew建立桌面應用程式 - DEVRustdev
- 使用 Redis 和 Python 構建一個共享單車的應用程式RedisPython
- 使用 Micronaut和OpenFaaS 構建無伺服器Java 應用程式 - openvalue伺服器Java
- 使用React Native和Expo快速構建原生移動iOS和Android應用程式React NativeiOSAndroid
- 使用SvelteKit構建實時websocket應用程式 - IngestWeb
- 文盤rust--使用 Rust 構建RAGRust
- 使用汽車應用庫構建應用
- 001 Rust和WebAssembly初體驗RustWeb
- 構建現代Web應用的安全指南Web
- 六邊形架構教程:構建可維護的Web應用程式 - DEV架構Webdev
- Web3.0應用程式架構Web架構
- 使用 nuxi build 命令構建你的 Nuxt 應用程式UXUI
- Judo:使用無程式碼構建原生應用體驗
- Web程式效能優化——asm.js和WebAssemblyWeb優化ASMJS
- 【譯】使用 Webpack 和 Poi 構建更好的 JavaScript 應用WebJavaScript
- 使用Java和Dapr構建雲原生應用簡介Java
- 使用 Dash 庫構建可互動的資料展示 Web 應用Web
- 探索使用 Golang 和 Webassembly 構建一個多人遊戲伺服器GolangWeb遊戲伺服器
- JavaFX桌面應用-構建程式框架Java框架
- Blazor WebAssembly 漸進式 Web 應用程式 (PWA) 離線處理資料BlazorWeb
- 從零開始構建Web應用-PART 1Web
- Spring系列(六) Spring Web MVC 應用構建分析SpringWebMVC