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

news2024/11/25 13:23:20

本教程笔记来自 杨旭老师的 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 />
        <input
          type="text"
          name="imageurl"
          id="imageurl"
          value="{{current_imageurl}}"
        /><br />
        <label for="profile">简介</label><br />
        <input
          type="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.coloradmin.cn/o/392258.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用免费负载生成器swingbench对oracle数据库进行压力测试(测试Oracle的功能或评估性能)

1.Swingbench 简介 Swingbench 是一个免费负载生成器&#xff08;和基准测试&#xff09;&#xff0c;旨在对 Oracle 数据库 进行压力测试。目前最新版本 Swingbench 2.6。 SwingBench 由负载生成器&#xff0c;协调器和集群概述组成。该软件可以生成负载 并绘制交易/响应时间…

Vector - CAPL - Write窗口常用函数

在CAPL自动化开发中,特别是通过Vector CAPL Browser开发中,最终都是通过仿真节点来进行最终的测试,然而这种测试方式没有自动化执行过程报告,只能通过最后的html报告来查看执行过程,那我们是否有更好的调试方法呢?当然是有的,那就是write窗口,通过该窗口我们不但可以打…

【SpringBoot高级篇】SpringBoot集成jasypt 配置脱敏和数据脱敏

【SpringBoot高级篇】SpringBoot集成jasypt数据脱敏配置脱敏使用场景配置脱敏实践数据脱敏pomymlEncryptMethodEncryptFieldEncryptConstantEncryptHandlerPersonJasyptApplication配置脱敏 使用场景 数据库密码直接明文写在application.yml配置中&#xff0c;对安全来说&…

神垕古镇景区5A级十年都没有实现的三大主因

钧 瓷 内 参 第40期&#xff08;总第371期&#xff09; 2023年3月5日 神垕古镇景区5A级十年都没有实现的三大主因 这是2013年&#xff0c;禹州市市政府第一次提出创建5A级景区到今年三月份整整十年啊&#xff01; 目前神垕古镇景区是4A级景区&#xff0c;5A级一直进行中&a…

使用去中心化存储构建网站

今天的大多数网站都遵循后端服务器到前端代码的架构。但在 Web3 应用程序中&#xff0c;前端代码不具有与受智能合约保护的后端代码相同的去中心化性和弹性。那么如何使网站像智能合约一样具有弹性呢&#xff1f; 该体系结构似乎很简单&#xff1a; 创建一个没有服务器的静态…

CorelDRAWX4的VBA插件开发(四十四)建立类(1)汇总相似功能简化重复代码:一键建立设计外框加出血线和等分折页线

这次主要来浅讲一下"类"这个功能,先上一下要实现的功能,建立设计外框加出血线和等分折页线,先上图 那什么是类呢?类其实就是CLASS,用来封装成员参数和函数的,拆开来里面就是这些东西,那写起来其实也没有什么区别,那既然都是参数和函数,那类的出现有什么意义呢.那我…

聚观早报 | 京东百亿补贴今日上线;微软推出全能型人工智能模型

今日要闻&#xff1a;京东“百亿补贴”今日全面上线&#xff1b;小鹏回应人脸识别需对车头半跪&#xff1b;微软推出全能型人工智能模型&#xff1b;雷军建议构建完善汽车数据安全管理体系&#xff1b;苹果、Meta已向国内Micro LED企业下单京东“百亿补贴”今日全面上线 3 月 6…

创建springboot项目文件报红

目录 一、遇到问题 二、出现这个问题的原因 三、解决办法 三种方法 四、操作步骤 一、遇到问题 创建springboot项目的时候&#xff0c;会发现一些重要文件都变成红色了&#xff0c;但是不影响程序的运行。只是看起来会有点不舒服。 二、出现这个问题的原因 因为这个spr…

初识虚拟DOM渲染器

初识虚拟DOM渲染器什么是虚拟DOM什么是渲染器渲染器的实现组件是什么什么是虚拟DOM 首先简单说一下什么是虚拟DOM&#xff0c;虚拟DOM就是一个描述真实DOM的JS对象 例如&#xff1a; 真实的DOM元素 <div onClick"alert(click me)">click me</div>可以…

12 结构:如何系统设计框架的整体目录?

到现在&#xff0c;我们已经将 Gin 集成到框架 hade 中&#xff0c;同时又引入了服务容器和服务提供者&#xff0c;明确框架的核心思想是面向服务编程&#xff0c;一切皆服务&#xff0c;所有服务都是基于协议。后续也会以服务的形式&#xff0c;封装一个个的服务&#xff0c;让…

ESP-C2系列模组开发板简介

C2是一个芯片采用4毫米x 4毫米封装&#xff0c;与272 kB内存。它运行框架&#xff0c;例如ESP-Jumpstart和ESP造雨者&#xff0c;同时它也运行ESP-IDF。ESP-IDF是Espressif面向嵌入式物联网设备的开源实时操作系统&#xff0c;受到了全球用户的信赖。它由支持Espressif以及所有…

空间复杂度与顺序表的具体实现操作(1)

最近更新的少&#xff0c;主要是因为参加了ACM竞赛空间复杂度空间复杂度也是一个数学表达式&#xff0c;是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间&#xff0c;因为这个也没太大意义&#xff0c;所以空间复杂度算的是变量…

项目使用windows-root证书

项目使用windows-root证书 将证书导入到本地计算机 方式1&#xff1a;使用windows-root证书配置流程(计算机本地) 输入命令(mmc)&#xff0c;进入控制台管理窗口 点击“文件”》“添加或删除管理单元”&#xff0c;进入如下界面 双击证书&#xff0c;选择“计算机账户”…

Swagger生成接口在线文档

OpenAPI规范&#xff08;OpenAPI Specification 简称OAS&#xff09;是Linux基金会的一个项目&#xff0c;试图通过定义一种用来描述API格式或API定义的语言&#xff0c;来规范RESTful服务开发过程&#xff0c;目前版本是V3.0&#xff0c;并且已经发布并开源在github上。&#…

C++核心编程<类和对象>(4)

C核心编程<类和对象>4.类和对象4.1封装4.1.1封装的意义封装的意义1封装的意义24.1.2struct和class区别4.1.3成员属性设置为私有4.2对象的初始化和清理4.2.1构造函数和析构函数1.1构造函数语法&#xff1a;类名(){}1.2析构函数语法&#xff1a; ~类名(){}4.2.2构造函数的分…

【JUC2022】第七章 AQS、ReentrantReadWriteLock 和 StampedLock

【JUC2022】第七章 AQS 文章目录【JUC2022】第七章 AQS一、AQS1.概述2.同步器3.抽象的4.队列式二、ReentrantReadWriteLock1.概述2.案例3.存在的问题三、StampedLock1.概述2.案例3.存在的问题一、AQS 1.概述 AQS(AbstractQueueSynchronizer&#xff0c;抽象的队列式同步器)&am…

tesseract -图像识别

下载链接&#xff1a;https://digi.bib.uni-mannheim.de/tesseract/如下选择最新的版本&#xff0c;这里我选择tesseract-ocr-w64-setup-5.3.0.20221222.exe有如下python模块操作tesseractpyocr 国内源&#xff1a;pip install -i https://pypi.mirrors.ustc.edu.cn/simple/ py…

ThreadLocal 学习常见问题

ThreadLocal 这个此类提供线程局部变量。这些变量不同于通常的对应变量&#xff0c;因为每个访问一个变量的线程(通过 get 或 set 方法)都有自己独立初始化的变量副本。ThreadLocal 实例通常是希望将状态与线程(例如&#xff0c;用户 ID 或事务 ID)关联的类中的私有静态字段。使…

vue router elementui template CDN模式实现多个页面跳转

文章目录前言一、elementui Tabs标签页和NavMenu 导航菜单是什么&#xff1f;二、使用方式1.代码如下2.页面效果总结前言 写上一篇bloghttps://blog.csdn.net/jianyuwuyi/article/details/128959803的时候因为整个前端都写在一个index.html页面里&#xff0c;为了写更少的代码…

CENTO OS上的网络安全工具(十九)ClickHouse集群部署

一、VMware上集群部署ClickHouse &#xff08;一&#xff09;网络设置 1. 通过修改文件设置网络参数 &#xff08;1&#xff09;CentOS 在CENTOS上的网络安全工具&#xff08;十六&#xff09;容器特色的Linux操作_lhyzws的博客-CSDN博客中我们提到过可以使用更改配置文件的方式…