当前位置: 首页 > news >正文

Rust Web 全栈开发(十二):构建 WebAssembly 应用

Rust Web 全栈开发(十二):构建 WebAssembly 应用

  • Rust Web 全栈开发(十二):构建 WebAssembly 应用
    • 项目配置
    • 构建 wasm-client
    • 构建项目
    • 生成网页
    • 构建网页
    • 添加并下载依赖
    • 测试

Rust Web 全栈开发(十二):构建 WebAssembly 应用

参考视频:https://www.bilibili.com/video/BV1RP4y1G7KF

继续之前的 Actix 项目。

我们已经实现了一个 Web App,在网页端查看并操作数据库中教师的数据。现在我们想创建一个 WebAssembly App,查看并操作数据库中课程的数据。

在这里插入图片描述

项目配置

打开 webservice 的 Cargo.toml,添加配置:

actix-cors = "0.6.0-beta.10"

因为客户端和服务器在不同的端口,需要跨域。

修改 webservice/src/bin/teacher_service.rs:

use actix_cors::Cors;...#[actix_rt::main]
async fn main() -> io::Result<()> {// 检测并读取 .env 文件中的内容,若不存在也会跳过异常dotenv().ok();let db_url = env::var("DATABASE_URL").expect("DATABASE_URL 没有在 .env 文件里设置");// 创建数据库连接池let db_pool = MySqlPoolOptions::new().connect(&db_url).await.unwrap();let shared_data = web::Data::new(AppState {health_check_response: "I'm OK.".to_string(),visit_count: Mutex::new(0),// courses: Mutex::new(vec![]),db: db_pool,});let app = move || {let cors = Cors::default().allowed_origin("http://localhost:8082/").allowed_origin_fn(|origin, _req_head| {origin.as_bytes().starts_with(b"http://localhost")}).allowed_methods(vec!["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]).allowed_headers(vec![http::header::AUTHORIZATION,http::header::ACCEPT,http::header::CONTENT_TYPE,]).allowed_header(http::header::CONTENT_TYPE).max_age(3600);App::new().app_data(shared_data.clone()).app_data(web::JsonConfig::default().error_handler(|_err, _req| {MyError::InvalidInput("Please provide valid json input".to_string()).into()})).configure(general_routes).configure(course_routes).wrap(cors).configure(teacher_routes)};HttpServer::new(app).bind("127.0.0.1:3000")?.run().await
}

构建 wasm-client

回到 Actix 项目的最顶层,在终端中用下面的命令克隆项目模板:

cargo generate --git https://github.com/rustwasm/wasm-pack-template

wasm 模块名称为:wasm-client。

Actix 项目的 members 会自动添加:

在这里插入图片描述

在 wasm-client/src 目录下新建文件,编写代码。

在这里插入图片描述

wasm-client/src/models/mod.rs:

pub mod course;

wasm-client/src/models/course.rs:

use super::super::errors::MyError;
use serde::{Deserialize, Serialize};
use chrono::NaiveDateTime;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response};#[derive(Debug, Serialize, Deserialize)]
pub struct Course {pub teacher_id: i32,pub id: i32,pub name: String,pub time: NaiveDateTime,pub description: Option<String>,pub format: Option<String>,pub structure: Option<String>,pub duration: Option<String>,pub price: Option<i32>,pub language: Option<String>,pub level: Option<String>,
}pub async fn get_courses_by_teacher(teacher_id: i32) -> Result<Vec<Course>, MyError> {let mut opts = RequestInit::new();opts.method("GET");opts.mode(RequestMode::Cors);let url = &format!("http://localhost:3000/courses/{}", teacher_id);let request = Request::new_with_str_and_init(&url, &opts)?;request.headers().set("Accept", "application/json")?;let window = web_sys::window().ok_or("no windows exists".to_string())?;let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;assert!(resp_value.is_instance_of::<Response>());let resp: Response = resp_value.dyn_into().unwrap();let json = JsFuture::from(resp.json()?).await?;let courses: Vec<Course> = json.into_serde().unwrap();Ok(courses)
}pub async fn delete_course(teacher_id: i32, course_id: i32) -> () {let mut opts = RequestInit::new();opts.method("DELETE");opts.mode(RequestMode::Cors);let url = format!("http://localhost:3000/courses/{}/{}", teacher_id, course_id);let request = Request::new_with_str_and_init(&url, &opts).unwrap();request.headers().set("Accept", "application/json").unwrap();let window = web_sys::window().unwrap();let resp_value = JsFuture::from(window.fetch_with_request(&request)).await.unwrap();assert!(resp_value.is_instance_of::<Response>());let resp: Response = resp_value.dyn_into().unwrap();let json = JsFuture::from(resp.json().unwrap()).await.unwrap();let _courses: Course = json.into_serde().unwrap();
}use js_sys::Promise;
use wasm_bindgen::prelude::*;#[wasm_bindgen]
pub async fn add_course(name: String, description: String) -> Result<Promise, JsValue> {let mut opts = RequestInit::new();opts.method("POST");opts.mode(RequestMode::Cors);let str_json = format!(r#"{{"teacher_id":1,"name": "{}","description": "{}"}}"#,name, description);opts.body(Some(&JsValue::from_str(str_json.as_str())));let url = "http://localhost:3000/courses/";let request = Request::new_with_str_and_init(&url, &opts)?;request.headers().set("Content-Type", "application/json")?;request.headers().set("Accept", "application/json")?;let window = web_sys::window().ok_or("no windows exists".to_string())?;let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;assert!(resp_value.is_instance_of::<Response>());let resp: Response = resp_value.dyn_into().unwrap();Ok(resp.json()?)
}

wasm-client/src/lib.rs:

mod utils;use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use web_sys::{HtmlButtonElement};// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;// "C" is the ABI the wasm target uses
#[wasm_bindgen]
extern "C" {fn alert(s: &str);fn confirm(s: &str) -> bool;#[wasm_bindgen(js_namespace = console)]fn log(s: &str);
}#[wasm_bindgen]
pub fn greet() {alert("Hello, wasm-client!");
}pub mod errors;
pub mod models;use models::course::Course;
use crate::models::course::delete_course;#[wasm_bindgen(start)]
pub async fn main() -> Result<(), JsValue> {let window = web_sys::window().expect("no global window exists");let document = window.document().expect("should have a document exists");let left_body = document.get_element_by_id("left-tbody").expect("left div not exists");let courses: Vec<Course> = models::course::get_courses_by_teacher(1).await.unwrap();for c in courses.iter() {let tr = document.create_element("tr")?;tr.set_attribute("id", format!("tr-{}", c.id).as_str())?;// course idlet td = document.create_element("td")?;td.set_text_content(Some(format!("{}", c.id).as_str()));tr.append_child(&td)?;// course namelet td = document.create_element("td")?;td.set_text_content(Some(c.name.as_str()));tr.append_child(&td)?;// course timelet td = document.create_element("td")?;td.set_text_content(Some(c.time.format("%Y-%m-%d").to_string().as_str()));tr.append_child(&td)?;// course descriptionlet td = document.create_element("td")?;if let Some(desc) = &c.description.clone() {td.set_text_content(Some(desc.as_str()));}tr.append_child(&td)?;// append buttonlet td = document.create_element("td")?;let btn: HtmlButtonElement = document.create_element("button").unwrap().dyn_into::<HtmlButtonElement>().unwrap();let cid = c.id;let click_closure = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {let r = confirm(format!("Are you sure to delete course {}?", cid).as_str());match r {true => {spawn_local(delete_course(1, cid));alert("deleted!");// reloadweb_sys::window().unwrap().location().reload().unwrap();}_ => {}}}) as Box<dyn Fn(_)>);// convert to `Function` and pass to `addEventListener`btn.add_event_listener_with_callback("click", click_closure.as_ref().unchecked_ref())?;// prevent memory leakclick_closure.forget();btn.set_attribute("class", "btn btn-danger btn-sm")?;btn.set_text_content(Some("Delete"));td.append_child(&btn)?;tr.append_child(&td)?;left_body.append_child(&tr)?;}Ok(())
}

wasm-client/src/errors.rs:

use serde::Serialize;#[derive(Debug, Serialize)]
pub enum MyError {SomeError(String),
}impl From<String> for MyError {fn from(error: String) -> Self {MyError::SomeError(error)}
}impl From<wasm_bindgen::JsValue> for MyError {fn from(js_value: wasm_bindgen::JsValue) -> Self {MyError::SomeError(js_value.as_string().unwrap())}
}

构建项目

cd 到 wasm-pack-template 目录下,执行命令:

wasm-pack build

构建成功:

在这里插入图片描述

当构建完成后,我们可以在 pkg 目录中找到它的工件:

在这里插入图片描述

生成网页

在 wasm-client 目录下运行这个命令:

npm init wasm-app www

等待构建成功。

构建网页

修改 wasm-client/www/index.html:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><title>Hello wasm-pack!</title><!--引入bootstrap 5--><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
<nav class="navbar navbar-dark bg-primary"><div class="container-fluid"><a class="navbar-brand" href="#">Wasm-client</a></div>
</nav>
<div class="m-3" style="height: 600px"><div class="row"><div class="col"><div class="card border-info mb-3"><div class="card-header">Course</div><div class="card-body"><button type="button" class="btn btn-primary">Add</button></div><table class="table table-hover table-bordered table-sm"><thead><tr><th scope="col">ID</th><th scope="col">Name</th><th scope="col">Time</th><th scope="col">Description</th><th scope="col">Option</th></tr></thead><tbody id="left-tbody"></tbody></table></div></div><div class="col"><div class="card border-info mb-3"><div class="card-header">Add Course</div><div class="card-body"><form class="row g-3 needs-validation" id="form"><div class="mb-3"><label for="name" class="form-label">Course Name</label><input type="text" class="form-control" id="name" required placeholder="Please fill in Course name"><div class="invalid-feedback">Please fill in course name</div></div><div class="mb-3"><label for="description" class="form-label">Description</label><textarea class="form-control" id="description" rows="3" placeholder="Please fill in description"></textarea></div><div class="col-12"><button type="submit" class="btn btn-primary">Submit</button></div></form></div></div></div></div>
</div>
<script src="./bootstrap.js"></script>
</body>
</html>

修改 wasm-client/www/index.js:

import * as wasm from "wasm-client";const myForm = document.getElementById("form");myForm.addEventListener("submit", (e) => {e.preventDefault();const name = document.getElementById("name").value;const desc = document.querySelector("#description").value;wasm.add_course(name, desc).then((json) => {alert('添加成功!')window.location.reload();});
});

添加并下载依赖

向 wasm-client/www/package.json 中添加依赖:

  "dependencies": {"wasm-client": "file:../pkg"},

通过在 wasm-client/www 子目录下运行 npm install,确保本地开发服务器及其依赖已经安装:

在这里插入图片描述

测试

先 cd 到 webservice,执行 cargo run 命令,把服务器运行起来

新建一个终端,在新终端中 cd 到 wasm-client/www 目录中,运行以下命令:

npm run start

构建成功:

在这里插入图片描述

将 Web 浏览器导航到 localhost:8082/:

在这里插入图片描述

http://www.lryc.cn/news/601403.html

相关文章:

  • day69—动态规划—爬楼梯(LeetCode-70)
  • LeetCode 923.多重三数之和
  • PMO如何赋能AI产品项目治理和价值交付︱商汤绝影PMO总监陈福龙
  • 0-1BFS(双端队列,洛谷P4667 [BalticOI 2011] Switch the Lamp On 电路维修 (Day1)题解)
  • 【C++】论如何封装红黑树模拟实现set和map
  • Java全栈面试实战:从JVM到AI的技术演进之路
  • JavaScript手录07-数组
  • LangChain实现RAG
  • JavaSE-String类
  • Rust赋能智能土木工程革新
  • 【奔跑吧!Linux 内核(第二版)】第5章:内核模块
  • 栈----4.每日温度
  • 2.qt调试日志输出
  • 多智能体系统设计:协作、竞争与涌现行为
  • Day4.AndroidAudio初始化
  • bash的特性-常用的通配符
  • bash的特性-命令和文件自动补全
  • C++ 多线程(一)
  • 第六章 JavaScript 互操(2).NET调用JS
  • ios UIAppearance 协议
  • 「iOS」————消息传递和消息转发
  • 携带参数的表单文件上传 axios, SpringBoot
  • 深度解读Go 变量指针
  • [每周一更]-(第152期):Go中的CAS(Compare-And-Swap)锁原理详解
  • iOS安全和逆向系列教程 第20篇:Objective-C运行时机制深度解析与Hook技术
  • 结合Golang语言说明对多线程编程以及 select/epoll等网络模型的使用
  • goland编写go语言导入自定义包出现: package xxx is not in GOROOT (/xxx/xxx) 的解决方案
  • 学习Python中Selenium模块的基本用法(1:简介)
  • Day06–哈希表–242. 有效的字母异位词,349. 两个数组的交集,202. 快乐数,1. 两数之和
  • 仓库管理系统-2-后端之基于继承基类的方式实现增删改查