Rust Web入门(六):服务器端web应用
本教程笔记来自 杨旭老师的 rust web 全栈教程,链接如下:
https://www.bilibili.com/video/BV1RP4y1G7KF?p=1&vd_source=8595fbbf160cc11a0cc07cadacf22951
学习 Rust Web 需要学习 rust 的前置知识可以学习杨旭老师的另一门教程
https://www.bilibili.com/video/BV1hp4y1k7SV/?spm_id_from=333.999.0.0&vd_source=8595fbbf160cc11a0cc07cadacf22951
项目的源代码可以查看 git:(注意作者使用的是 mysql 数据库而不是原教程的数据库)
https://github.com/aiai0603/rust_web_mysql
在之前的项目中,我们已经使用了 rust 编写了一些具有增删改查功能的接口并且进行测试,但是作为一款完整的应用,他还需要将这些数据展示到页面中的功能;在之前的学习中,我们已经尝试过将一个html 页面提供给用户,但是在实际开发中,我们肯定希望我们的数据可以绑定到页面中展示给用户,类似许多语言提供了模板引擎功能,如 jsp、asp 等,rust也有自己的模板引擎 Tera,找不到官网链接了,给一个资料链接:
http://www.xiyangw.com/post/17560.html
这节课我们使用 Tera 来完成一个简单的新增教师和查询教师的界面:
架构搭建
同样我们新建一个项目,在开始编写之前我们先添加我们的依赖
[dependencies]
actix-files = "0.6.0-beta.16"
actix-web = "4.0.0-rc.2"
awc = "3.0.0-beta.21"
chrono = { version = "0.4.19", features = ["serde"] }
dotenv = "0.15.0"
# openssl = { version = "0.10.38", features = ["vendored"] }
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
tera = "1.15.0"
之后我们依次新建 errors.rs , handlers.rs , models.rs 和 routers.rs 作为我们的各个功能模块,我们编写一个 mod.rs 将各个模块导出:
pub mod errors;
pub mod handlers;
pub mod routers;
pub mod models;
之后在根目录下创建一个 static 文件夹存放我们的模板 html 文件和样式文件等资源,
最后我们在 bin 目录下新建我们的 svr.rs 文件作为我们的启动文件,现在完整的架构搭建起来了
配置服务器
对于这个项目,我们将项目的启动路径编写在配置文件中,方便我们随时改变他,我们在根目录编写一个 .env 文件写上我们的端口:
HOST_PORT = 127.0.0.1:4396
之后我们在 svr.rs 文件编写我们的服务器启动逻辑,我们获取配置文件的服务器启动地址之后,之后我们将 static 文件夹绑定在 tera 中,这样 static 文件夹下的文件就会被 tera 识别到,之后我们将 tera 传入整个项目中,我们的项目就可以使用 tera 这个引擎的相关内容了,最后我们在配置文件的编写的地址启动我们的项目:
#[path = "../mod.rs"]
mod wa;use actix_web::{web, App, HttpServer};
use dotenv::dotenv;
use routers::app_config;
use std::env;
use wa::{errors,handlers,models,routers};use tera::Tera;#[actix_web::main]
async fn main() -> std::io::Result<()> {dotenv().ok();let host_port = env::var("HOST_PORT").expect("HOST_PORT 没有在 .env 文件里设置")HttpServer::new(move || {let tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"),"/static/**/*")).unwrap();App::new().app_data(web::Data::new(tera)).configure(app_config)}).bind(&host_port)?.run().await
}
业务逻辑
现在我们的服务器已经能启动起来了,我们来为他编写业务逻辑:
首先是 models ,我们编写两个数据结构方便我们查询和提交老师的数据:
use serde::{Deserialize, Serialize};#[derive(Deserialize, Serialize, Debug)]
pub struct TeacherRegisterForm {pub name: String,pub imageurl: String,pub profile: String,}#[derive(Deserialize, Serialize, Debug)]
pub struct TeacherResponse {pub id: i32,pub name: String,pub picture_url: String,pub profile: String,}
之后我们编写 routers 来配置我们的路由,我们直接访问页面的时候展示所有老师的信息,而 register 页面用于注册老师,注册信息通过register-post 发送,而对静态文件的请求可以返回正确的静态文件 fs::Files::new("/static","./static") 就是将 /static 文件夹下的内容作为我们的静态资源列表,当用户请求静态资源的时候,就从这个位置获取他们:
use actix_web::web;
use crate::handlers::*;
use actix_files as fs;pub fn app_config(cfg: &mut web::ServiceConfig) {cfg.service(web::scope("").service(fs::Files::new("/static","./static").show_files_listing()).service(web::resource("/").route(web::get().to(get_all_teacher))).service(web::resource("/register").route(web::get().to(show_register_from))).service(web::resource("/register-post").route(web::post().to(handle_register))));
}
模板引擎的使用
现在我们来依次编写 handlers 里的处理函数
首先是默认路由的展示教师数据,首先我们使用 awc_client 这个包来调用我们之前编写的接口,测试的时候,我们要将之前编写的完整的增删改查的接口 api 项目在 3077 端口启动起来:
pub async fn get_all_teacher(tmpl: web::Data<tera::Tera>) -> Result<HttpResponse, Error> {let awc_client = awc::Client::default();let res = awc_client.get("http://localhost:3077/teachers/").send().await.unwrap().json::<Vec<TeacherResponse>>().await.unwrap();
}
在获取数据之后我们把它添加到我们的模板里,我们开启一个 ctx 上下文,在其中插入 teachers 和 errors 两个数据,之后我们将 teachers.html 作为我们的模板,把上下文插入到这个模板中,现在这个模板就可以使用这两个变量了,通过模板引擎选然后会返回将模板的插值语句变为插入数据的网页代码,将它封装返回,用户就能看到完整的页面了:
pub async fn get_all_teacher(tmpl: web::Data<tera::Tera>) -> Result<HttpResponse, Error> {let awc_client = awc::Client::default();let res = awc_client.get("http://localhost:3077/teachers/").send().await.unwrap().json::<Vec<TeacherResponse>>().await.unwrap();let mut ctx = tera::Context::new();ctx.insert("error", "");ctx.insert("teachers", &res);let s = tmpl.render("teachers.html", &ctx).map_err(|_| MyError::TeraError("Template error".to_string()))?;Ok(HttpResponse::Ok().content_type("text/html").body(s))
}
如下是编写好的模板,因为这个内容不是本教程最关键介绍的编写页面的方案,所以这里就简单给出 demo,如果想要了解模板的更多编写方法可以自行查阅资料:
<!DOCTYPE html>
<html><head><meta charset="utf-8" /><title>Teachers</title></head><body><h1>教师列表</h1><ol>{% for t in teachers %}<li><h5>{{t.name}}</h5><div>{{t.profile}}</div></li>{% endfor %}</ol><div style="margin: 20px;"><a href="/register"> 注册老师 </a></div></body>
</html>
同样我们将我们的 register 界面也写好,因为初始值都是空的,所以我们给与的上下文信息都是空的:
pub async fn show_register_from(tmpl: web::Data<tera::Tera>) -> Result<HttpResponse, Error> {let mut ctx = tera::Context::new();ctx.insert("error", "");ctx.insert("current_name", "");ctx.insert("current_imageurl", "");ctx.insert("current_profile", "");let s = tmpl.render("register.html", &ctx).map_err(|_| MyError::TeraError("Template error".to_string()))?;Ok(HttpResponse::Ok().content_type("text/html").body(s))
}
这是 register .html 的页面:
<!DOCTYPE html>
<html><head><meta charset="utf-8" /><title>register</title><link rel="stylesheet" href="/static/css/register.css" /></head><body><h2 class="header">注册老师</h2><div class="center"><form action="/register-post" method="post"><label for="name">名字</label><br /><input type="text" name="name" id="name" value="{{current_name}}" /><br /><label for="imageurl">头像</label><br /><inputtype="text"name="imageurl"id="imageurl"value="{{current_imageurl}}"/><br /><label for="profile">简介</label><br /><inputtype="text"name="profile"id="profile"value="{{current_profile}}"/><br /><label for="error"><p style="color: red">{{error}}</p> </label><br /><button type="submit" id="button1"> 注册</button></form></div></body>
</html>
最后我们来写我们的提交数据的逻辑:我们在 post 数据到 /register-post 作为我们表单的提交方法,在这个方法的处理函数中,我们添加一个 web::Form<TeacherRegisterForm> 类型的参数传递我们的表单数据,要注意,如上的 html 模板,表单的每个字段的 name 属性必须和我们定义的数据结构一一对应:
我们做一个简单的判定,如果名字 Dave 那么将错误写到注册页面中,保持原来的数据不变;
否则我们调用接口将数据写到数据库中,返回回显新增数据的 id;
pub async fn handle_register(tmpl: web::Data<tera::Tera>,params: web::Form<TeacherRegisterForm>,
) -> Result<HttpResponse, Error> {let mut ctx = tera::Context::new();let s;if params.name == "Dave" {ctx.insert("error", "名字已经存在");ctx.insert("current_name", ¶ms.name);ctx.insert("current_imageurl", ¶ms.imageurl);ctx.insert("current_profile", ¶ms.profile);s = tmpl.render("register.html", &ctx).map_err(|_| MyError::TeraError("Template error".to_string()))?;} else {let new_teacher = json!({"name":¶ms.name,"picture_url":¶ms.imageurl,"profile":¶ms.profile});let awc_client = awc::Client::default();let res = awc_client.post("http://localhost:3077/teachers/").send_json(&new_teacher).await.unwrap().body().await?;let teacher_response: TeacherResponse = serde_json::from_str(&std::str::from_utf8(&res)?)?;s = format!("成功,id是:{}",teacher_response.id);}Ok(HttpResponse::Ok().content_type("text/html").body(s))
}
错误处理
最后我们补上我们的错误处理,与上一个项目的错误处理逻辑基本上一致,只是多了一个 TeraError 作为我们模板渲染中发生的错误的错误类型:
use actix_web::{error, http::StatusCode, HttpResponse, Result};
use serde::Serialize;
use std::fmt;#[derive(Debug, Serialize)]
pub enum MyError {TeraError(String),ActixError(String),#[allow(dead_code)]NotFound(String),
}#[derive(Debug, Serialize)]
pub struct MyErrorResponse {error_message: String,
}impl MyError {fn error_response(&self) -> String {match self {MyError::TeraError(msg) => {println!("Tera error occurred: {:?}", msg);"Database error".into()}MyError::ActixError(msg) => {println!("Server error occurred: {:?}", msg);"Internal server error".into()}MyError::NotFound(msg) => {println!("Not found error occurred: {:?}", msg);msg.into()}}}
}impl error::ResponseError for MyError {fn status_code(&self) -> StatusCode {match self {MyError::TeraError(_msg) | MyError::ActixError(_msg) => StatusCode::INTERNAL_SERVER_ERROR,MyError::NotFound(_msg) => StatusCode::NOT_FOUND,}}fn error_response(&self) -> HttpResponse {HttpResponse::build(self.status_code()).json(MyErrorResponse {error_message: self.error_response(),})}
}impl fmt::Display for MyError {fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {write!(f, "{}", self)}
}impl From<actix_web::error::Error> for MyError {fn from(err: actix_web::error::Error) -> Self {MyError::ActixError(err.to_string())}
}impl From<tera::Error> for MyError {fn from(err: tera::Error) -> Self {MyError::TeraError(err.to_string())}
}
效果演示
编写完毕后,我们将项目启动,然后将上一个编写的增删改查项目的 demo 在 3077 这个端口启动,现在我们在网页输入 127.0.0.1:4396:

之后我们点击注册老师页面:

我们添加一个新的老师:


我们在测试一下 Dave:

最后返回我们的首页,刚刚注册的老师显示出来了:

ok 我们的项目测试成功了,如果你没有运行成功可以查看这个 git 的 stage8:
https://github.com/aiai0603/rust_web_mysql
相关文章:
Rust Web入门(六):服务器端web应用
本教程笔记来自 杨旭老师的 rust web 全栈教程,链接如下: https://www.bilibili.com/video/BV1RP4y1G7KF?p1&vd_source8595fbbf160cc11a0cc07cadacf22951 学习 Rust Web 需要学习 rust 的前置知识可以学习杨旭老师的另一门教程 https://www.bili…...
1.特定领域知识图谱知识融合方案(实体对齐):金融产业产业知识图谱-基于内容匹配和图模型的品牌知识链指
1 引言 供应链金融是一种围绕经营关系,以核心企业为依托,针对中小企业的新型金融服务。如何精准地还原企业间的经营关系,是供应链金融的关键所在。知识图谱是描绘实体间关系的网络结构,对于挖掘企业关系有重要意义。在真实场景中,仅有企业与用户的微观知识对于还原经营关系…...
前端基础语法合集
JS语法基础1-注释//单行注释/*......*/多行注释2-分号;用作分割javascript语句,可以省略。3-变量定义定义变量使用varvar a;//声明变量 var a100;//声明变量并赋值 var b,c;//声明多个变量 var d20;bd1;cb1;//一行多条语句要用;分割4-数据类型判断该变量…...
百亿补贴,京东的自卫反击战
“百亿补贴”这个词大家有没有很熟悉?大部分人应该是在看拼多多投放广告的时候,知道这个词的吧。而京东APP也于近日在升级11.6.2版本时,在更新日志中明确提到:“京东3.8节,百亿补贴上线”。至此,发酵数日的…...
融云入选中国信通院《高质量数字化转型产品及服务全景图》
企业数字化转型正在进入“深水区”。 3 月 3 日,“中国信息通信研究院(以下简称中国信通院)高质量数字化转型创新发展大会暨中国信通院‘铸基计划’年度峰会”在京召开,深度展示了中国信通院在数字化转型领域的工作成果ÿ…...
开源消息代理组件mosquitto
# ll total 556 -rw-r----- 1 sk sk 148417 Mar 6 14:59 libuv-1.44.2-1.el7.x86_64.rpm -rw-r----- 1 sk sk 120717 Mar 6 14:59 libwebsockets-3.0.1-2.el7.x86_64.rpm -rw-r----- 1 sk sk 293429 Mar 6 14:59 mosquitto-1.6.10-1.el7.x86_64.rpm 将用到的依赖上传到主机…...
vuex的五个属性及使用方法示例
一,Vuex简介 Vuex是Vue.js的状态管理库,它通过中心化的状态管理使得组件间的数据共享更加容易。 Vuex包含五个核心属性:state、getters、mutations、actions和modules。 Vuex是Vue.js的状态管理库,它提供了一种集中式存储管理应…...
9.SpringSecurity核心过滤器-SecurityContextPersistenceFilter
SpringSecurity核心过滤器-SecurityContextPersistenceFilter 一、SpringSecurity中的核心组件 在SpringSecurity中的jar分为4个,作用分别为 jar作用spring-security-coreSpringSecurity的核心jar包,认证和授权的核心代码都在这里面spring-security-co…...
23种设计模式-桥接模式
概念 桥接模式是一种结构型设计模式,它通过将抽象与其实现分离来解耦。它使用接口(抽象类)作为桥梁,将一个抽象类与其实现类的代码分别独立开来,从而使它们可以各自独立地变化。桥接模式的核心思想是“组合优于继承”…...
TCP PMTU 静态路由
HTTP协议 --- 超文本传输协议TCP --- 80端口超文本 --- 包含有超链接link和多媒体元素标记的文本TCP协议是一种面向连接的可靠性传输协议面向连接:数据在传输前,收发双方建立一条逻辑通道。可靠性确认机制:传输确认,每接受一个数据…...
Android动画——属性动画
在属性动画中,常用到的API有ValueAnimator,ObjectAnimator。ValueAnimator:时间引擎,负责计算各个帧的属性值,基本上其他属性动画都会直接或间接继承它;ObjectAnimator: ValueAnimator 的子类&a…...
华为OD机试真题Python实现【寻找连续区间】真题+解题思路+代码(20222023)
寻找连续区间 题目 给定一个含有N个正整数的数组, 求出有多少个连续区间(包括单个正整数), 它们的和大于等于x。 🔥🔥🔥🔥🔥👉👉👉👉👉👉 华为OD机试(Python)真题目录汇总 ## 输入 第一行两个整数N x (0 < N <= 100000 ,0 <= x <=…...
15. 三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。 示例 …...
40-Golang中的文件
Golang中的文件基本介绍文件的打开和关闭读文件操作应用实例写文件操作实例判断文件是否存在基本介绍 文件在程序中是以流的形式存在的 流:数据在数据源(文件)和程序(内存)之间经历的路程 输入流:数据从数据源到程序之间的路径 输出流:数据…...
Springboot整合RabbitMQ并使用
1、Springboot整合RabbitMQ 1、引入场景启动器 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </dependency>引入AMQP场景启动器之后,RabbitAutoConfiguratio…...
Java中方法引用(引用静态方法、引用成员方法(引用其他类的成员方法、引用本类的成员方法、引用父类的成员方法)、引用构造方法、其他调用方式、小练习)
方法引用:把已经存在的方法拿过来用,当作函数式接口中抽象方法的方法体 我们前面学到Arrays工具类中的sort方法,当我们需要指定排序规则时,需要传递Comparator接口的实现类对象,我们之前使用匿名内部类类的形式作为参…...
整理了100道关于Python基础知识的练习题,记得收藏~
实例1.数字组合 题目: 有四个数字:1、2、3、4,能组成多少个互不相同且无重复数字的三位数?各是多少? 程序分析: 遍历全部可能,把有重复的剃掉。 total0 for i in range(1,5):for j in range(…...
OSG三维渲染引擎编程学习之七十七:“第七章:OSG场景图形交互” 之 “7.8 场景交互”
目录 第七章 OSG场景图形交互 7.8 场景交互 7.8.1 osgGA库 7.8.2 事件消息处理 7.8.3 程序抓图示例...
797.差分
输入一个长度为 n的整数序列。 接下来输入 m个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。 请你输出进行完所有操作后的序列。 输入格式 第一行包含两个整数 n和 m。 第二行包含 n个整数,表示整数序列。 …...
为什么说要慎用BeanUtils,因为性能真的拉跨
1 背景之前在专栏中讲过“不推荐使用属性拷贝工具”,推荐直接定义转换类和方法使用 IDEA 插件自动填充 get / set 函数。不推荐的主要理由是:有些属性拷贝工具性能有点差有些属性拷贝工具有“BUG”使用属性拷贝工具容易存在一些隐患(后面例子…...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...
