Rust Web 全栈开发(十):编写服务器端 Web 应用

Rust Web 全栈开发(十):编写服务器端 Web 应用

  • Rust Web 全栈开发(十):编写服务器端 Web 应用
    • 创建成员库:webapp
    • models
    • handlers
    • routers
    • errors
    • mod
    • svr
    • static
      • teachers.html
      • register.html
      • bootstrap.min.css
      • bootstrap.min.js
      • jquery.min.js
    • 测试

Rust Web 全栈开发(十):编写服务器端 Web 应用

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

继续之前的 Actix 项目。

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

在这里插入图片描述

主要的技术是模板引擎,它的作用是以静态页面作为模板,把动态的数据渲染进页面,最后一同返回给用户。

创建成员库:webapp

回到顶层的工作空间,在终端执行命令 cargo new webapp,创建一个新的成员库 webapp。

工作空间的 Cargo.toml 会自动添加这个新成员:

在这里插入图片描述

修改 webapp 中的 Cargo.toml,添加如下依赖:

[package]
name = "webapp"
version = "0.1.0"
edition = "2021"[dependencies]
actix-files = "0.6.0-beta.16"
actix-web = "4.0.0-rc.2"
awc = "3.0.0-beta.21"
dotenv = "0.15.0"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
tera = "1.15.0"

在 webapp 目录下新建一个 .env 文件,内容如下:

HOST_PORT=127.0.0.1:8080

webapp 目录一览:

├── webapp
│   ├── src
│   │   ├── bin
│   │       └── svr.rs
│   │   └── mod.rs
│   │   └── errors.rs
│   │   └── handlers.rs
│   │   └── models.rs
│   │   └── routers.rs
│   ├── static
│   │   ├── css
│   │       └── bootstrap.min.css
│   │   ├── javascript
│   │       └── bootstrap.min.js
│   │       └── jquery.min.js
│   │   └── register.html
│   │   └── teachers.html
│   ├── .env
│   └── Cargo.toml

models

编辑 models.rs:

use serde::{Deserialize, Serialize};/// 教师信息,用于应用注册
#[derive(Serialize, Deserialize, Debug)]
pub struct TeacherRegisterForm {pub name: String,pub picture_url: String,pub profile: String,
}/// 教师信息,用于数据库查询
#[derive(Serialize, Deserialize, Debug)]
pub struct TeacherResponse {pub id: i32,pub name: String,pub picture_url: String,pub profile: String,
}

handlers

编辑 handlers.rs:

use crate::errors::MyError;
use crate::models::{TeacherRegisterForm, TeacherResponse};
use actix_web::{web, Error, HttpResponse, Result};
use serde_json::json;pub async fn get_all_teachers(tmpl: web::Data<tera::Tera>
) -> Result<HttpResponse, Error> {// 创建 HTTP 客户端let awc_client = awc::Client::default();let res = awc_client.get("http://localhost:3000/teachers/").send().await.unwrap().json::<Vec<TeacherResponse>>().await.unwrap();// 创建一个上下文,可以向 HTML 模板里添加数据let mut ctx = tera::Context::new();// 向上下文中插入数据ctx.insert("error", "");ctx.insert("teachers", &res);// s 是渲染的模板,静态部分是 teachers.html,动态数据是 ctxlet s = tmpl.render("teachers.html", &ctx).map_err(|_| MyError::TeraError("Template error".to_string()))?;Ok(HttpResponse::Ok().content_type("text/html").body(s))
}pub async fn show_register_form(tmpl: web::Data<tera::Tera>
) -> Result<HttpResponse, Error> {let mut ctx = tera::Context::new();ctx.insert("error", "");ctx.insert("current_name", "");ctx.insert("current_picture_url", "");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))
}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", "Dave already exists!");ctx.insert("current_name", &params.name);ctx.insert("current_picture_url", &params.picture_url);ctx.insert("current_profile", &params.profile);s = tmpl.render("register.html", &ctx).map_err(|err| MyError::TeraError(err.to_string()))?;} else {let new_teacher = json!({"name": &params.name,"picture_url": &params.picture_url,"profile": &params.profile,});let awc_client = awc::Client::default();let res = awc_client.post("http://localhost:3000/teachers/").send_json(&new_teacher).await.unwrap().body().await?;let teacher_response: String =serde_json::from_str(&std::str::from_utf8(&res)?)?;s = format!("Message from Web Server: {}", teacher_response);}Ok(HttpResponse::Ok().content_type("text/html").body(s))
}

get_all_teachers 函数向 http://localhost:3000/teachers/ 发生 GET 请求,将得到的 Vec<TeacherResponse> 渲染到 teachers.html,最终返回一个包含渲染网页的 HTTP Response。

show_register_form 函数创建一个包含教师信息(name、picture_url、profile)的上下文 ctx,渲染到 register.html,最终返回一个包含渲染网页的 HTTP Response。

handle_register 函数判断传入的教师的 name 是否是 Dave 来做出不同的响应。如果是,则将 Dave already exists! 错误信息渲染到 register.html,作为 s。如果不是,则向 http://localhost:3000/teachers/ 发送 json 化的新教师信息,将服务器的响应作为 s。最终返回一个包含 s 的 HTTP Response。

routers

编辑 routers.rs:

use crate::handlers::{get_all_teachers, handle_register, show_register_form};
use actix_files;
use actix_web::web;pub fn app_config(config: &mut web::ServiceConfig) {config.service(web::scope("").service(actix_files::Files::new("/static", "./static/").show_files_listing()).service(web::resource("/").route(web::get().to(get_all_teachers))).service(web::resource("/register").route(web::get().to(show_register_form))).service(web::resource("/register-post").route(web::post().to(handle_register))));
}

errors

编辑 errors.rs:

use actix_web::{error, http::StatusCode, HttpResponse, Result};
use serde::Serialize;
use std::fmt;#[derive(Debug, Serialize)]
pub enum MyError {ActixError(String),#[allow(dead_code)]NotFound(String),TeraError(String),
}#[derive(Debug, Serialize)]
pub struct MyErrorResponse {error_message: String,
}impl std::error::Error for MyError {}impl MyError {fn error_response(&self) -> String {match self {MyError::ActixError(msg) => {println!("Server error occurred: {:?}", msg);"Internal server error".into()}MyError::TeraError(msg) => {println!("Error in rendering the template: {:?}", msg);msg.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::ActixError(_msg) | MyError::TeraError(_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())}
}

mod

mod.rs:

pub mod models;
pub mod handlers;
pub mod routers;
pub mod errors;

svr

bin/svr.rs 类似于 teacher_service.rs。

#[path = "../mod.rs"]
mod webapp;use actix_web::{web, App, HttpServer};
use dotenv::dotenv;
use routers::app_config;
use std::env;
use webapp::{errors, handlers, models, routers};
use tera::Tera;#[actix_web::main]
async fn main() -> std::io::Result<()> {// 检测并读取 .env 文件中的内容,若不存在也会跳过异常dotenv().ok();let host_port = env::var("HOST_PORT").expect("HOST_PORT is not set in .env file");println!("Listening on {}", &host_port);let app = move || {let tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/static/**/*")).unwrap();App::new().app_data(web::Data::new(tera)).configure(app_config)};HttpServer::new(app).bind(&host_port)?.run().await
}

CARGO_MANIFEST_DIR 就是 webapp 目录的地址,这一句代码将该地址与 /static 连接起来,告诉 app 这些静态文件的位置。

static

这个目录下都是网页相关的静态文件。

├── static
│   ├── css
│       └── bootstrap.min.css
│   ├── javascript
│       └── bootstrap.min.js
│       └── jquery.min.js
│   └── register.html
│   └── teachers.html

teachers.html

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>Teachers</title><link rel="stylesheet" href="/static/css/bootstrap.min.css">
</head><body><div class="container"><h1 class="mt-4 mb-4">Teacher List</h1><ul class="list-group">{% for t in teachers %}<li class="list-group-item"><div class="d-flex w-100 justify-content-between"><h5 class="mb-1">{{t.name}}</h5></div><p class="mb-1">{{t.profile}}</p></li>{% endfor %}</ul><div class="mt-4"><a href="/register" class="btn btn-primary">Register a Teacher</a></div></div><script src="/static/javascript/jquery.min.js"></script><script src="/static/javascript/bootstrap.min.js"></script>
</body></html>

register.html

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>Teacher registration</title><link rel="stylesheet" href="/static/css/bootstrap.min.css">
</head><body style="background-color: #f2f9fd;"><div class="container"><h2 class="header text-center mb-4 pt-4">Teacher Registration</h2><div class="row justify-content-center"><div class="col-md-6"><form action="/register-post" method="POST" class="p-4 bg-white rounded shadow-sm"><div class="form-group"><label for="name">Teacher Name</label><input type="text" name="name" id="name" value="{{current_name}}" class="form-control"></div><div class="form-group"><label for="picture_url">Teacher Picture URL</label><input type="text" name="picture_url" id="picture_url" value="{{current_picture_url}}"class="form-control"></div><div class="form-group"><label for="profile">Teacher Profile</label><input type="text" name="profile" id="profile" value="{{current_profile}}" class="form-control"></div><div><p style="color: red">{{error}}</p></div><br/><button type="submit" id="button1" class="btn btn-primary">Register</button></form></div></div></div><script src="/static/javascript/jquery.min.js"></script><script src="/static/javascript/bootstrap.min.js"></script>
</body></html>

bootstrap.min.css

文件太长,见于:UestcXiye/Actix-Workspace/webapp/static/css/bootstrap.min.css

bootstrap.min.js

文件太长,见于:UestcXiye/Actix-Workspace/webapp/static/javascript/bootstrap.min.js

jquery.min.js

文件太长,见于:UestcXiye/Actix-Workspace/webapp/static/javascript/jquery.min.js

测试

因为路由中配置了 /static,访问 http://localhost:8080/static 会给出项目 static 文件夹的目录:

在这里插入图片描述

数据库中 teacher 表:

在这里插入图片描述

在一个终端 cd 到 webservice,执行命令 cargo run。

在另一个终端 cd 到 webservice,执行命令 cargo run。

在浏览器访问 http://localhost:8080,页面如下:

在这里插入图片描述

webapp 终端输出 Listening on 127.0.0.1:8080。

点击 Register a Teacher 按钮,跳转到 http://localhost:8080/register,可以填写表单,注册新的教师信息。

在这里插入图片描述

我们先测试无法新增的情况,也就是 Teacher Name 是 Dave 的情况:

在这里插入图片描述

再测试可以插入新教师信息的情况:

在这里插入图片描述

插入成功,跳转到 http://localhost:8080/register-post 页面:

在这里插入图片描述

数据库中 teacher 表新增一条教师信息:

在这里插入图片描述

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

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

相关文章

每日面试题11:JVM

深入理解JVM&#xff1a;Java的“心脏”如何驱动程序运行&#xff1f;为什么需要JVM&#xff1f;你是否想过&#xff0c;为什么用Java写的程序&#xff0c;能在Windows、Linux、macOS上“无缝运行”&#xff1f;为什么开发者无需为不同操作系统重写代码&#xff1f;这背后的核心…

Linux网络信息(含ssh服务和rsync)

73.telnet&#xff1a;测试端口连通性用法&#xff1a;telnet 主机名或IP 端口号测试目标主机的指定端口是否开放&#xff0c;检查网络服务连通性。eg&#xff1a;telnet www.baidu.com 80# 说明&#xff1a;# - 如果连接成功&#xff0c;显示 "Connected to ..."。…

【PTA数据结构 | C语言版】我爱背单词

本专栏持续输出数据结构题目集&#xff0c;欢迎订阅。 文章目录题目代码题目 作为一个勤奋的学生&#xff0c;你在阅读一段英文文章时&#xff0c;是否希望有个程序能自动帮你把没有背过的生词列出来&#xff1f;本题就请你实现这个程序。 输入格式&#xff1a; 输入第 1 行给…

如何使用电脑连接小米耳机(红米 redmi耳机)

如何使用电脑连接小米&#xff08;红米 redmi&#xff09;耳机Redmi耳机连接电脑的具体步骤如下注意事项和常见问题解决方法&#xff1a;Redmi耳机连接电脑的具体步骤如下 打开耳机仓盖&#xff1a; 首先&#xff0c;打开Redmi耳机的充电仓盖&#xff0c;但不需要取出耳机。进…

排序算法—交换排序(冒泡、快速)(动图演示)

目录 十大排序算法分类​编辑 冒泡排序 算法步骤&#xff1a; 动图演示&#xff1a; 性能分析&#xff1a; 代码实现&#xff08;Java&#xff09;&#xff1a; 快速排序&#xff08;挖坑法&#xff09; 算法步骤&#xff1a; 动图演示&#xff1a; 性能分析&#xff1…

2023 年 5 月青少年软编等考 C 语言八级真题解析

目录 T1. 道路 思路分析 T2. Rainbow 的商店 思路分析 T3. 冰阔落 I 思路分析 T4. 青蛙的约会 思路分析 T1. 道路 题目链接:SOJ D1216 N N N 个以 1 ∼ N 1 \sim N 1∼N 标号的城市通过单向的道路相连,每条道路包含两个参数:道路的长度和需要为该路付的通行费(以金币的数…

【vue-4】深入理解 Vue 3 中的 v-for 指令

Vue.js 作为现代前端框架的代表之一&#xff0c;其模板指令系统提供了强大的数据绑定和渲染能力。其中&#xff0c;v-for 指令是 Vue 中最常用且最重要的指令之一&#xff0c;它允许我们基于数据源循环渲染元素或组件。在 Vue 3 中&#xff0c;v-for 保留了一贯的简洁语法&…

《R for Data Science (2e)》免费中文翻译 (第1章) --- Data visualization(1)

写在前面 本系列推文为《R for Data Science (2)》的中文翻译版本。所有内容都通过开源免费的方式上传至Github&#xff0c;欢迎大家参与贡献&#xff0c;详细信息见&#xff1a; Books-zh-cn 项目介绍&#xff1a; Books-zh-cn&#xff1a;开源免费的中文书籍社区 r4ds-zh-cn …

界面组件DevExpress WPF中文教程:Grid - 如何完成节点排序和移动?

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

【Prometheus+Grafana篇】监控通过Keepalived实现的MySQL HA高可用架构

&#x1f4ab;《博主主页》&#xff1a;    &#x1f50e; CSDN主页__奈斯DB    &#x1f50e; IF Club社区主页__奈斯、 &#x1f525;《擅长领域》&#xff1a;擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控&#xff1b;并对…

k8s:利用kubectl部署postgis:17-3.5

1.离线环境CPU:Hygon C86 7285 32-core Processor 操作系统&#xff1a;麒麟操作系统 containerd&#xff1a;1.7.27 Kubernetes:1.26.12 KubeSphere:4.1.2 kubekey&#xff1a;3.1.10 Harbor:2.13.1 Postgis:17-3.52.创建并执行postgresql-headless.yaml2.1创建apiVersion: v1…

Mysql(存储过程)

目录 介绍 特点 存储过程创建 系统变量(不重要) 用户变量 局部变量 if 判断 参数&#xff08;in, out, inout) case while repeat loop 游标和条件处理程序-handler 存储函数 为了防止以后忘记&#xff0c;反复去看视频浪费时间&#xff0c;特写一篇 介绍 存储过程…

Effective Python 第14条: 用sort方法的key参数来表示复杂的排序逻辑

一、引言&#xff1a;Python排序功能的重要性 在Python开发中&#xff0c;排序功能是一个常见的需求。无论是处理数据、优化算法&#xff0c;还是提升用户体验&#xff0c;排序都是不可或缺的一部分。Python的列表内置了sort方法&#xff0c;提供了灵活的排序功能。然而&#…

react+antd 可拖拽模态框组件

DraggableModal 可拖拽模态框组件使用说明 概述 DraggableModal 是一个基于 dnd-kit/core 实现的可拖拽模态框组件&#xff0c;允许用户通过拖拽标题栏来移动模态框位置。该组件具有智能边界检测功能&#xff0c;确保模态框始终保持在可视区域内。 功能特性 ✅ 可拖拽移动&…

MySQL的基本操作及相关python代码

下面为你介绍 MySQL 的基本操作,以及对应的 Python 代码实现。我会先介绍 SQL 基本操作,再展示如何用 Python 连接 MySQL 并执行这些操作。 一、MySQL 基本操作(SQL 语句) 1. 连接数据库 bash mysql -u root -p2. 创建数据库 sql CREATE DATABASE testdb;3. 使用数据…

Armbian(斐讯N1)安装xfce桌面以及远程环境

安装xfce桌面以及vncserver(远程连接) 安装xfce桌面 apt-get install xfce4 xfce4-goodies xorg dbus-x11 x11-xserver-utils ubuntu的安装gdm3&#xff0c; apt install gdm3 debian安装lightdm。 apt install lightdm 安装vnc server apt-get install tightvncserver 中文字体…

【Oracle】Oracle 11g打补丁时遇到opatch apply命令无法识别

⚙️ 1. 使用完整路径执行命令 问题原因&#xff1a;若未将$ORACLE_HOME/OPatch加入系统PATH环境变量&#xff0c;直接输入opatch apply会因系统无法定位命令而报错。 解决方案&#xff1a; 改用绝对路径执行&#xff1a; $ORACLE_HOME/OPatch/opatch apply例如&#xff1a; /u…

单例模式详细讲解

一.定义单例模式是一种创建型设计模式&#xff0c;确保一个类只有一个实例&#xff0c;并提供一个全局访问点特点&#xff1a;1.构造函数和析构函数私有化2.禁用拷贝构造函数和赋值运算符重载&#xff08;delete&#xff09;3.利用静态成员函数和静态成员变量来给外界提供访问二…

KORGym:评估大语言模型推理能力的动态游戏平台

KORGym&#xff1a;评估大语言模型推理能力的动态游戏平台 现有评估基准多受领域限制或 pretraining 数据影响&#xff0c;难以精准测LLMs内在推理能力。KORGym平台应运而生&#xff0c;含50余款游戏&#xff0c;多维度评估&#xff0c;本文将深入解析其设计、框架、实验及发现…

ISPDiffuser文章翻译理解

ISPDiffuser: Learning RAW-to-sRGB Mappings with Texture-Aware Diffusion Models and Histogram-Guided Color Consistency翻译 Type: Conference paper Author: Yang Ren1,4, Hai Jiang1,4, Menglong Yang1,2,†, Wei Li1,2, Shuaicheng Liu3,4,† Select: ⭐️⭐️⭐️⭐…