Rust Web入门(六):服务器端web应用
创始人
2024-05-29 15:09:58
0

本教程笔记来自 杨旭老师的 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) -> Result {let awc_client = awc::Client::default();let res = awc_client.get("http://localhost:3077/teachers/").send().await.unwrap().json::>().await.unwrap();
}

在获取数据之后我们把它添加到我们的模板里,我们开启一个 ctx 上下文,在其中插入 teachers 和 errors 两个数据,之后我们将 teachers.html 作为我们的模板,把上下文插入到这个模板中,现在这个模板就可以使用这两个变量了,通过模板引擎选然后会返回将模板的插值语句变为插入数据的网页代码,将它封装返回,用户就能看到完整的页面了:

pub async fn get_all_teacher(tmpl: web::Data) -> Result {let awc_client = awc::Client::default();let res = awc_client.get("http://localhost:3077/teachers/").send().await.unwrap().json::>().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,如果想要了解模板的更多编写方法可以自行查阅资料:


Teachers

教师列表

    {% for t in teachers %}
  1. {{t.name}}
    {{t.profile}}
  2. {% endfor %}

同样我们将我们的 register 界面也写好,因为初始值都是空的,所以我们给与的上下文信息都是空的:

pub async fn show_register_from(tmpl: web::Data) -> Result {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 的页面:


register

注册老师


{current_name}}" />

{current_imageurl}}"/>

{current_profile}}"/>

最后我们来写我们的提交数据的逻辑:我们在 post 数据到 /register-post 作为我们表单的提交方法,在这个方法的处理函数中,我们添加一个 web::Form 类型的参数传递我们的表单数据,要注意,如上的 html 模板,表单的每个字段的 name 属性必须和我们定义的数据结构一一对应:

我们做一个简单的判定,如果名字 Dave 那么将错误写到注册页面中,保持原来的数据不变;

否则我们调用接口将数据写到数据库中,返回回显新增数据的 id;

pub async fn handle_register(tmpl: web::Data,params: web::Form,
) -> Result {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 for MyError {fn from(err: actix_web::error::Error) -> Self {MyError::ActixError(err.to_string())}
}impl From 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

相关内容

热门资讯

寒梅墨香初中作文500字(精... 寒梅墨香初中作文500字 篇一:寒梅墨香寒梅墨香,这是我最喜欢的一句诗句。每当我看到这句诗时,心中就...
仰望星空,脚踏实地作文650... 仰望星空,脚踏实地作文650字 篇一仰望星空,脚踏实地星空,是人类永远的向往和追求。每当夜幕降临,我...
我的忏悔初中作文【精简5篇】 我的忏悔初中作文 篇一我曾经做出了一件让我深感愧疚的事情,我想在这里向大家忏悔并寻求原谅。那是一个晴...
生活中的语言_初中记叙文【经... 生活中的语言_初中记叙文 篇一我和爸爸妈妈一样,是普通的工薪阶层,生活虽然平凡,但却充满了各种语言的...
初一我的奋斗目标作文500字... 初一我的奋斗目标作文500字 第一篇俗话说:“你脸上云淡风轻,谁也不知道你呼吸得有多紧;你走路带风,...
谢谢你让我遇见你作文(精彩5... 谢谢你让我遇见你作文 篇一遇见你,是我这一生最美好的偶遇。谢谢你,让我在茫茫人海中找到了属于我的那份...
那道靓丽的风景线作文700字... 那道靓丽的风景线作文700字 篇一那道靓丽的风景线在我家附近有一道靓丽的风景线,它是一条蜿蜒曲折的小...
烟花泪的作文【通用5篇】 烟花泪的作文 篇一烟花泪,是我对烟花的一种特殊感受。每当夜幕降临,烟花绽放的瞬间,我总能感受到一种深...
红色精神的作文600字【最新... 红色精神的作文600字 篇一:传承红色精神,弘扬革命精神红色精神是我们中华民族宝贵的精神财富,它是中...
我终于七年级作文通用8篇 我终于七年级作文 第一篇我默默地坐在床上,眼泪不争气地从眼眶里骨碌骨碌滚了出来。我抽了一张纸巾,把眼...
我好想见你_初中记叙文(通用... 我好想见你_初中记叙文 篇一初中时光,是我们成长的岁月。那时候的我们,充满了青春的活力和对未来的期待...
初中说明文作文(优质6篇) 初中说明文作文 篇一绿色出行的意义与方法随着城市化进程的加快,汽车的数量不断增加,交通拥堵和空气污染...
初一生活句子精选375句 初一生活句子 精选120句1. 除了你的母亲和你爱的女人,不要多余的给任何女人面子,男女不是平等的么...
生命的长河初中作文(通用3篇... 生命的长河初中作文 篇一生命的长河初中作文生命是一条绵延不绝的长河,每个人都在这条长河中行走,流淌着...
什么是幸福的作文【精彩6篇】 什么是幸福的作文 篇一幸福是什么?这是一个让人们思考已久的问题。对于每个人来说,幸福的定义可能是不同...
初一的幸福作文600字(最新... 初一的幸福作文600字 篇一初一的幸福初一,对于每个学生来说都是一个特殊的年级。对我来说,初一是一个...
初一写景作文800字大全【通... 初一写景作文800字大全 篇一初一写景作文800字大全 篇一:春天的花海春天到了,大自然万物复苏,到...
红旗下的蛋初三军训作文【通用... 红旗下的蛋初三军训作文 篇一初中生活即将进入尾声,作为一名初三学生,我们迎来了期待已久的军训。那是一...
我的好朋友七年级作文(精选6... 我的好朋友七年级作文 篇一我有一个好朋友,她的名字叫小芳。我们从小学一年级开始就是同班同学,从那时起...
秉烛夜游叹沧桑初一作文(推荐... 秉烛夜游叹沧桑初一作文 篇一秉烛夜游叹沧桑初一作文初一的晚上,月光下的街道显得异常寂静,我独自一人拿...