Rust Web入门(六):服务器端web应用

news/2024/5/5 9:53:36/文章来源:https://blog.csdn.net/weixin_46463785/article/details/129371196

本教程笔记来自 杨旭老师的 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", &params.name);ctx.insert("current_imageurl", &params.imageurl);ctx.insert("current_profile", &params.profile);s = tmpl.render("register.html", &ctx).map_err(|_| MyError::TeraError("Template error".to_string()))?;} else {let new_teacher = json!({"name":&params.name,"picture_url":&params.imageurl,"profile":&params.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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_78349.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

List、Set、Map众多集合框架等你来学,让我们一起精进Java框架的知识点吧

文章目录一、各集合特性二、HashMap深入解析三、遍历集合元素的若干方式一、各集合特性 1. ArrayList特性 不唯一&#xff0c;有序&#xff1a;实现了List接口&#xff0c;该接口是序列&#xff0c;所以不唯一且按顺序保存不同步&#xff1a;因为ArrayList.add&#xff08;&am…

Android Service知识

一. 概览 Service 是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动&#xff0c;而且即使用户切换到其他应用&#xff0c;服务仍将在后台继续运行。此外&#xff0c;组件可通过绑定到服务与之进行交互&#xff0c;甚至是执行进程间通信 (IPC…

【数据结构】核心数据结构之二叉堆的原理及实现

1.大顶堆和小顶堆原理 什么是堆 堆&#xff08;Heap&#xff09;是计算机科学中一类特殊的数据结构&#xff0c;通常是一个可以被看作一颗完全二叉树的数组对象。 完全二叉树 只有最下面两层节点的度可以小于2&#xff0c;并且最下层的叶节点集中在靠左连续的边界 只允许最后…

中电金信源启小程序开发平台 赋能金融+业务生态共享共建

导语&#xff1a;源启小程序开发平台立足于“为金融业定制”&#xff0c;从小程序全生命周期的角度出发&#xff0c;助力银行、互联网金融、保险、证券客户实现一站式小程序开发、发布、运营与营销。企业可以通过源启小程序开发平台&#xff0c;低成本高效率开发一款定制化小程…

模电学习11 运算放大器学习入门

一、基本概念 运算放大器简称运放&#xff0c;是一种模拟电路实现的集成电路&#xff0c;可以对信号进行很高倍数的放大。一般有正相输入端、反相输入端、输出端口、正电源、负电源等接口。 运放可工作在饱和区、放大区&#xff0c;其中放大区极其陡峭&#xff0c;因为运放的放…

【C++】30h速成C++从入门到精通(stack、queuepriority_queue以及deque介绍)

stackstack的介绍https://cplusplus.com/reference/stack/stack/?kwstackstack是一种容器适配器&#xff0c;专门在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。stack是作为容器适配器被实现的&#xff0c;容器适配器即是对特…

Greenplum-MVCC与数据可见性判断

众所周知&#xff0c;Greenplum内部支持MVCC多版本并发控制&#xff0c;通过MVCC技术&#xff0c;可以支持同一行数据的读写并发问题&#xff0c;从而大大提升并发访问控制的能力。 GP中的MVCC实现 所谓多版本&#xff0c;其含义在于数据的更新和删除操作并不是直接在原数据上…

(小甲鱼python)函数笔记合集七 函数(IX)总结 python实现汉诺塔详解

一、基础复习 函数的基本用法 创建和调用函数 函数的形参与实参等等函数的几种参数 位置参数、关键字参数、默认参数等函数的收集参数*args **args 解包参数详解函数中参数的作用域 局部作用域 全局作用域 global语句 嵌套函数 nonlocal语句等详解函数的闭包&#xff08;工厂函…

产品新人如何培养产品思维?

什么是产品思维&#xff1f;其实很难定义&#xff0c;不同人有不同的定义。有的人定义为以用户为中心打磨一个完美体验的产品&#xff1b;有的定义为从需求调研到需求上线各个步骤需要思考的点&#xff0c;等等。本文想讨论的产品思维是&#xff1a;怎么去发现问题&#xff0c;…

【JavaSE】逻辑控制语句

文章目录一. 顺序结构二. 分支结构1. if 语句2. switch 语句3、循环结构3.1 while 循环3.2 do while 循环3.3 for 循环3.4 break 和 continue三. 输入输出1. 输出到控制台2. 从键盘输入一. 顺序结构 顺序结构比较简单&#xff0c;即程序按照代码书写的顺序一行一行执行下去。 …

BS系统中的安全方案(SSO和Oauth2认证,数据加密)

摘要用户用浏览器打开网站&#xff0c;DNS会根据域名找到相应的服务器IP给到浏览器&#xff0c;仅接着用户的浏览器会与服务器建立连接&#xff0c;通过网路上的各个设备(交换机、路由器、基站、光纤等)&#xff0c;将服务器上的数据发送到用户的电脑上&#xff0c;在浏览器里呈…

函数式编程:Lambda 表达式

函数式编程&#xff1a;Lambda 表达式 每博一文案 曾经读过的依然令我感动的句子&#xff0c;生活总是不如意&#xff0c;但往往是在无数痛苦中&#xff0c;但往往是在无数痛苦中&#xff0c;在重重矛盾 和艰难中才能成熟起来&#xff0c;坚强起来&#xff0c;爱情啊&#xf…

EXCEL里的各种奇怪计算问题:数字后面自动多了 0.0001, 数字后面位数变成000,以及一些取整,数学函数

1 公式计算后的数&#xff0c;用只粘贴数值后&#xff0c;后面自动多了 0.0001&#xff0c;导致不再是整数的问题 问题入戏 见第1个8400&#xff0c;计算时就出现了问题&#xff0c;按正常&#xff0c;这里8400应该是整数&#xff0c;而不应该带小数&#xff0c;但是确实就计…

vmware虚拟机与树莓派4B安装ubuntu1804 + ros遇到的问题

如题所示&#xff0c;本人在虚拟机上安装ubuntu1804&#xff0c;可以很容易安装&#xff0c;并且更换系统apt源和ros源&#xff0c;然后安装ros&#xff0c;非常顺利&#xff0c;但是在树莓派4B上安装raspiberry系统就遇到了好多问题。 树莓派我烧录的是这个镜像&#xff1a;ub…

k8s-Kubernetes集群部署

文章目录前言一、Kubernetes简介与架构1.Kubernetes简介2.kubernetes设计架构二、Kubernetes集群部署1.集群环境初始化2.所有节点安装kubeadm3.拉取集群所需镜像3.集群初始化4.安装flannel网络插件5.扩容节点6.设置kubectl命令补齐前言 一、Kubernetes简介与架构 1.Kubernetes…

L - Let‘s Swap(哈希 + 规律)

2023河南省赛组队训练赛&#xff08;四&#xff09; - Virtual Judge (vjudge.net) 约瑟夫最近开发了一款名为Pandote的编辑软件&#xff0c;现在他正在测试&#xff0c;以确保它能正常工作&#xff0c;否则&#xff0c;他可能会被解雇!Joseph通过实现对Pandote上字符串的复制和…

断点调试(debug)

目录 F8案例 ​编辑 debug过程中报错 ​编辑用debug查看方法源码 一层一层查看 Arrays.sort()方法 F9 DebugExercise 介绍&#xff1a;断点调试是指在程序的某一行设置一个断电&#xff0c;调试时&#xff0c;程序运行到这一行就会停住&#xff0c;然后可以一步步往下调试…

项目实战典型案例17——环境混用来带的影响

环境混用来带的影响一&#xff1a;背景介绍背景出现的事故二&#xff1a;思路&方案环境混用的危害如何彻底避免环境混用的问题四&#xff1a;总结五&#xff1a;升华一&#xff1a;背景介绍 本篇博客是对对项目开发中出现的环境混用来带的影响进行的总结并进行的改进。目的…

JAVA后端部署项目三步走

1. JAVA部署项目三步走 1.1 查看 运行的端口 lsof -i:8804 &#xff08;8804 为端口&#xff09; 发现端口25111被监听 1.2 杀死进程,终止程序 pid 为进程号 kill -9 pid 1.3 后台运行jar包 nohup java -jar -Xms128M -Xmx256M -XX:MetaspaceSize128M -XX:MaxM…

基于半车悬架的轴距预瞄与轴间预瞄仿真对比

目录 前言 1. 半车悬架模型 2.轴距预瞄(单点预瞄)和轴间预瞄(两点预瞄)原理与仿真分析 2.1轴距预瞄(单点预瞄) 2.1.1预瞄原理 2.2.轴间预瞄(两点预瞄) 2.2.1预瞄原理 2.3仿真分析 3.总结 前言 对于悬架而言&#xff0c;四个车轮实际的输入信息是受到前后延时以及左右相…