百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

Rust 服务器、服务和应用程序:7 Rust 中的服务器端 Web 应用简介

xsobi 2024-12-23 14:20 2 浏览

本章涵盖

  • 使用 Actix 提供静态网页
  • 使用 Actix 和 Tera 渲染动态网页
  • 使用表单添加用户输入
  • 显示包含模板的列表
  • 编写和运行客户端测试
  • 连接到后端 Web 服务

在本书的第3-6章中,我们使用Rust和Actix Web框架从头开始构建了Tutors Web服务。在本节中,我们将重点学习在 Rust 中构建 Web 应用程序的基础知识。

使用系统编程语言来创建 Web 应用程序听起来可能很奇怪。但这就是 Rust 的力量。它可以轻松跨越系统和应用程序编程的世界。

在本章中,您将了解使用 Rust 构建 Web 应用程序的概念和工具。在这一点上,重要的是要记住,构建 Web 应用程序有两种广泛的技术 - 服务器端呈现 (SSR) 和单页应用程序 (SPA),每种技术都可能采用渐进式 Web 应用程序 (PWA) 的形式。在本节中,我们将重点介绍前者,在后面的章节中,我们将介绍后者。我们不会在本书中介绍PWA。

更具体地说,第7-9章的重点是学习如何开发一个简单的Web应用程序,用户可以使用该应用程序注册和登录到Web应用程序,查看列表和详细信息视图,以及使用基于Web的表单对数据执行标准的CRUD(创建-读取-更新-删除)操作。在此过程中,您将学习如何使用 Actix Web 框架和模板引擎渲染动态网页。虽然我们可以使用任何 Rust Web 框架(Actix web、RocketWarp 等)来实现相同的目标,但使用 Actix Web 有助于我们利用前几章的学习。

有了这个背景,我们就可以开始了。

服务器端呈现是一种 Web 开发技术,其中网页在服务器上呈现,然后发送到客户端(Web 浏览器)。在这种方法中,在服务器上运行的Web应用程序将静态HTML页面(例如,来自Web设计器)与数据(从数据库或其他Web服务获取)组合在一起,并将完全呈现的网页发送到浏览器以显示给用户。使用这种技术的 Web 应用程序称为服务器呈现或服务器端 Web 应用程序。使用这种方法,网站加载速度更快,网页内容反映最新数据,因为每个请求通常涉及获取用户数据的最新副本(例外是在服务器上采用缓存技术时)。作为旁注,为了保持特定于用户的数据,网站要么要求用户登录以验证/识别自己,要么使用 cookie 为用户个性化内容。

网页可以是静态的,也可以是动态的。

静态网页的一个例子是银行网站的主屏幕,它通常用作银行的营销工具,并为其客户提供有用的链接来使用银行的服务。对于访问银行主页 URL 的任何人来说,此页面都是相同的。从这个意义上说,它是一个静态网页。

动态网页是您使用授权凭据(例如用户名和密码)登录银行并查看帐户余额和对帐单时看到的内容。从某种意义上说,此页面是动态的,即每个客户查看自己的余额,但该网页也可能包含静态组件,例如银行徽标和网页的其他常见样式(例如颜色,字体,布局等),这些组件会向所有查看帐户余额的客户显示。

我们知道如何创建静态网页。网页设计师可以手动编写HTML和CSS脚本,也可以为此目的使用许多可用工具之一来做到这一点。但是如何将静态网页转换为动态网页呢?

这就是模板引擎的用武之地。


图 7.1 显示了呈现动态网页的各种组件。

模板引擎是将静态网页转换为动态网页的主要工具之一。它需要一个模板文件作为输入,并生成一个 HTML 文件作为输出。在此过程中,它将数据(由 Web 应用程序传递给它)嵌入到模板文件中以生成 HTML 文件。此过程在两个方面是动态的。首先,按需加载数据。其次,数据是为请求数据的个人用户量身定制的。

为了在 Rust 中开发服务器端 Web 应用程序,我们将使用以下工具/组件:

  1. Actix Web 服务器,它将托管在服务器上特定端口运行的 Web 应用程序,并将请求路由到 Web 应用程序提供的处理程序函数。
  2. 用 Rust 编写并部署在 Actix Web 服务器上的 Web 应用程序,它将提供内容以响应来自浏览器的请求。这将包含知道如何响应各种类型的 HTTP 请求的核心处理程序逻辑。
  3. Tera,一个在python世界中流行的模板引擎,已经移植到Rust上。
  4. 我们在上一节中开发了自己的后端 Tutor Web 服务,它将从数据库中获取数据并管理数据库交互。Web 应用程序将与导师 Web 服务通信以检索数据并执行事务,而不是处理数据库本身。
  5. 来自 Actix Web 框架的内置 HTTP 客户端,用于与导师 Web 服务通信。

如果服务器端渲染 (SSR) 的概念对你来说仍然有点清楚,我们为什么不通过实际编写一些示例代码来学习 Rust 的 SSR?如果一张图片胜过千言万语,那么即使是几行代码也值几倍。

请注意,本章是关于通过查看较小的代码片段来学习如何构建 Web 应用程序,并了解各个部分如何组合在一起以构建 Web 应用程序。但是,只有在下一章中,我们才会实际设计和构建导师 Web 应用程序。以下是您将在本章中构建的示例的快速思维导图。这些示例表示任何 Web 应用程序中最常见的任务,允许用户从基于浏览器的用户界面查看和维护数据。

  1. 第 7.1 节将展示如何使用 Actix Web 提供静态网页。
  2. 第 7.2 节将介绍如何使用 Tera 生成动态网页,Tera是 Web 开发世界中流行的模板引擎。
  3. 在第 7.3 节中,您将学习如何使用 HTML 表单捕获用户输入。
  4. 第 7.4 节是关于使用 Tera HTML 模板显示信息列表的
  5. 您之前学习了如何为 Web 服务(服务器端)编写自动测试,在第 7.5 节中,您将学习编写客户端测试。
  6. 我们将在第 7.6 节中结束这一章,使用 HTTP 客户端将前端 Web 应用程序后端 Web 服务连接起来。

有了这个背景,让我们进入第一部分。

7.1 使用 Actix 提供静态网页

在前面的章节中,我们使用Actix Web服务器来托管我们的导师Web服务。在本章的第一部分中,我们将使用 Actix 来提供静态网页。将此视为用于Web应用程序开发的“Hello World”程序。

让我们首先设置项目结构:

  1. 复制第 6 章中的 ezytutors 工作区存储库,以在本中工作。
  2. 创建一个新的 Rust 货运项目cargo new tutor-web-app-ssr
  3. ezytutors 工作区下的 tutor-db 文件夹重命名为 tutor-web-service。这样,工作区下的两个存储库可以明确地称为 Web 服务和 Web 应用
  4. 在工作区文件夹的 Cargo.toml 中,编辑工作区部分,如下所示:
[workspace]
members = ["tutor-web-service","tutor-web-app-ssr"]

我们现在在工作区中有两个项目,一个用于导师 Web 服务(我们之前开发),另一个用于服务器端呈现的导师 Web 应用(我们尚未开发)。

  1. CD tutor-web-app-ssr

切换到 tutor-web-app-ssr 文件夹。这就是我们将编写本节代码的地方。从今以后,让我们将此文件夹称为项目根文件夹。为避免混淆,请在您将为此项目使用的每个终端会话中将其设置为环境变量,如下所示:

export $PROJECT_ROOT=.
  1. 更新 Cargo.toml 以添加以下依赖项。
[dependencies]
actix-web = "4.2.1"
actix-files="0.6.2"

Actix-Web是核心的Actix Web框架,Actix-Files有助于从Web服务器提供静态文件。

  1. 在 $PROJECT_ROOT 下创建一个静态文件夹。使用以下 html 代码在 $PROJECT_ROOT/static 下创建一个文件 static-web-page.html
<!DOCTYPE html>
<html>
<head>
  <title>XYZ Bank Website</title>
</head>
<body>
  <h1>Welcome to XYZ bank home page!</h1>
  <p>This is an example of a static web page served from Actix Web server.</p>
</body>
</html>

这是一个简单的静态网页。我们将看到如何使用 Actix 服务器提供此页面。

  1. 在 $PROJECT_ROOT/src 下创建一个 bin 文件夹。在 $PROJECT_ROOT/src/bin 下创建一个新的源文件 static.rs,并添加以下代码:
use actix_files as fs;                      #1
use actix_web::{error, web, App, Error, HttpResponse, HttpServer, Result};

#[actix_web::main]
async fn main() -> std::io::Result<()> {    #2
    let addr = env::var("SERVER_ADDR").unwrap_or_else(|_| "127.0.0.1:8080".to_string());
    println!("Listening on: {}, open browser and visit have a try!",addr);
    HttpServer::new(|| {
        App::new().service(fs::Files::new("/static", "./static").show_files_listing())  #3
    })
    .bind(addr)?                        #4
    .run()                              #5
    .await                              #6
}

当以 /static 开头的路由上的 Web 服务器发出 GET 请求时,此程序创建一个新的 Web 应用程序,向 Web 应用程序注册服务以从文件系统(在磁盘上)提供文件。然后将 Web 应用程序部署到 Web 服务器上,并启动 Web 服务器。

  1. 使用 运行 Web 服务器。cargo run --bin static
  2. 从浏览器中,访问以下 URL:
http://localhost:8080/static/static-web-page.html

您应该会看到网页出现在浏览器中。

现在让我们试着理解我们刚刚做了什么。我们编写了一个程序来从Actix Web服务器提供静态网页。当我们请求特定的静态文件时,actix_files服务在 /static 文件夹中查找它并将其返回到浏览器,然后显示给用户。

这是静态页面的一个示例,因为此页面的内容不会根据请求此页面的用户而更改。在下一节中,我们将看到如何使用 Actix 构建动态网页的示例。

7.2 使用 Actix 和 Tera 渲染动态网页

如果我们想为每个用户显示自定义内容,该怎么办?您将如何编写动态呈现内容的HTML页面?请注意,显示动态网页并不意味着页面中的所有内容都会对每个用户进行更改,而是网页同时包含静态和动态部分。


我们之前在图 7.1 中看到了服务器端渲染的通用视图。图 7.2 显示了如何使用 Actix WebTera 模板引擎实现动态网页的服务器端呈现。请注意,在图中,本地数据库显示为动态网页的数据源,但也可以从外部 Web 服务检索数据。事实上,这就是我们将在本书中使用的设计方法。

为此,我们将以特定的模板格式定义 HTML 文件。Tera模板格式的详细信息可以在以下位置查看:https://tera.netlify.app/docs/。下面是一个非常简单的模板示例。将其添加到 $PROJECT_ROOT/static/iter1/index.html

<!DOCTYPE html>
<html>

<head>
    <title>XYZ Bank Website</title>
</head>

<body>
  <h1>Welcome {{ name }}, to XYZ bank home page!</h1>
  <p>This is an example of a dynamic web page served with Actix and Tera templates.</p>
</body>
</html>

请注意标记 {{name}} 的使用。当浏览器请求网页时,Tera在运行时将使用此标记替换为用户的实际名称。Tera可以从您想要的任何位置检索此值 - 从文件,数据库或简单的硬编码值中检索。

让我们修改之前编写的程序,以使用 Tera 来满足此类动态网页请求。

在 $PROJECT_ROOT/Cargo.toml 中添加以下依赖项:

tera = "1.17.0"
serde = { version = "1.0.144", features = ["derive"] }

我们正在添加用于模板支持的 tera crate 和用于在 Web 浏览器和 Web 服务器之间序列化/反序列化自定义数据结构的 serde crate。

$PROJECT_ROOT/src/bin 中,将我们之前编写 static.rs 文件的内容复制到新的文件 iter1.rs 中,并将以下代码修改为如下所示:

use tera::Tera;

#[actix_web::main]
async fn main() -> std::io::Result<()> {

    println!("Listening on: 127.0.0.1:8080, open browser and visit have a try!");
    HttpServer::new(|| {
        let tera = Tera::new(concat!(       #1
            env!("CARGO_MANIFEST_DIR"),
            "/static/iter1/**/*"
        ))
        .unwrap();

        App::new()
            .data(tera)                     #2
            .service(fs::Files::new("/static", "./static").show_files_listing()) #3
            .service(web::resource("/").route(web::get().to(index)))  #4
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

现在让我们编写索引处理程序:

async fn index(tmpl: web::Data<tera::Tera>) -> Result<HttpResponse, Error> {
    let mut ctx = tera::Context::new();     #1
    ctx.insert("name", "Bob");        #2
    let s = tmpl
        .render("index.html", &ctx)         #3
        .map_err(|_| error::ErrorInternalServerError("Template error"))?;

    Ok(HttpResponse::Ok().content_type("text/html").body(s))        #4
}

使用 运行服务器。然后从 Web 浏览器访问以下 URL:cargo run --bin iter1

http://localhost:8080/

您应该会在网页上看到以下消息:

Welcome Bob, to XYZ bank home page!

这是一个微不足道的例子,但用于说明如何使用 Actix 构建动态网页的概念。前面列出的Tera网站有很多功能可以用作模板的一部分,包括控制语句,如iffor循环,你可以悠闲地探索。

到目前为止,我们已经看到了如何呈现静态 Web(HTML) 页面和动态 HTML 页面。但是到目前为止的示例涉及向用户显示一些信息。Actix是否也支持编写接受用户输入的HTML页面?我们将在下一节中找到答案。

7.3 使用表单添加用户输入

在本节中,我们将创建一个通过表单接受用户输入的网页。这是一个尽可能简单的表格。创建一个文件夹 $PROJECT_ROOT/static/iter2 并将以下 html 放在新文件格式中.html在此文件夹下。此 html 代码包含一个表单,该表单接受导师名称,然后将包含导师姓名的 POST 请求提交到 Actix Web 服务器。

<!doctype html>
<html>

<head>
    <meta charset=utf-8>
    <title>Forms with Actix & Rust</title>
</head>

<body>
    <h3>Enter name of tutor</h3>
    <form action=/tutors method=POST>
        <label>
            Tutor name:
            <input name="name">
        </label>
        <button type=submit>Submit form</button>
    </form>

    <hr>
</html>

请注意 HTML 的 <input> 元素,该元素用于接受用户输入的教师名称。<按钮>标记用于将表单提交到 Web 服务器。此表单封装在发送到路由 /tutors 上的 Web 服务器的 HTTP POST 请求中,该请求在 <form action=“”> 属性中指定。

让我们在 $PROJECT_ROOT/static/iter2 文件夹下创建一个名为 user.html 的第二个 html 文件,该文件将显示用户在上一个表单中提交的名称。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>Actix web</title>
</head>

<body>
    <h1>Hi, {{ name }}!</h1>
    <p>
        {{ text }}
    </p>
</body>

</html>

此 HTML 文件有一个模板变量 {{name}}。向用户显示此页面时,模板变量 {{name}} 的值将替换为用户在上一个表单中输入的实际导师名称

现在让我们添加此路由,以及一个处理程序来处理此 POST 请求。

$PROJECT_ROOT/src/bin 中,创建一个新的文件 iter2.rs,并将以下代码添加到 iter2.rs

... // imports removed for concision; see full source code from GitHub

// store tera template in application state
async fn index(                                 #1
    tmpl: web::Data<tera::Tera>
) -> Result<HttpResponse, Error> {
    let s = tmpl
        .render("form.html", &tera::Context::new())        #2
        .map_err(|_| error::ErrorInternalServerError("Template error"))?;

    Ok(HttpResponse::Ok().content_type("text/html").body(s))
}

#[derive(Serialize, Deserialize)]                       #3
pub struct Tutor {
    name: String,
}

async fn handle_post_tutor(                             #4
    tmpl: web::Data<tera::Tera>,
    params: web::Form<Tutor>,
) -> Result<HttpResponse, Error> {
    let mut ctx = tera::Context::new();
    ctx.insert("name", ?ms.name);                   #5
    ctx.insert("text", "Welcome!");
    let s = tmpl
        .render("user.html", &ctx)
        .map_err(|_| error::ErrorInternalServerError("Template error"))?;

    Ok(HttpResponse::Ok().content_type("text/html").body(s))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {                    #6

    println!("Listening on: 127.0.0.1:8080");
    HttpServer::new(|| {
        let tera = Tera::new(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/static/iter2/**/*"
        ))
        .unwrap();

        App::new()
            .data(tera)                             #7
            .configure(app_config)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

fn app_config(config: &mut web::ServiceConfig) {        #8
  config.service(
    web::scope("")
      .service(web::resource("/").route(web::get().to(index)))
      .service(web::resource("/tutors").route(web::post().to(handle_post_tutor)))
  );
}

回顾一下,在显示的代码中,当用户访问“/”路由时,将显示包含表单的 form.html。当用户在表单中输入名称并按下提交按钮时,将在 route /tutors 上生成一个 POST 请求,该请求handle_post_tutor调用另一个处理程序函数。在此处理程序中,用户输入的名称可通过 web::Form 提取器访问。处理程序将此名称注入到新的 Tera 上下文对象中。然后与上下文对象一起调用 Tera 渲染函数,向用户显示用户.html页面。

使用以下命令运行 Web 服务器:

cargo run --bin iter2

从浏览器访问 URL:

http://localhost:8080/

您应该首先看到显示的窗体。输入名称,然后单击提交表单按钮。您应该会看到显示的第二个 html,其中包含您输入的名称。

本节演示如何接受用户输入并对其进行处理到此结束。在下一节中,我们将介绍模板引擎的另一个常见功能 - 显示列表的功能。

7.4 显示包含模板的列表

在本节中,我们将学习如何在网页上动态显示数据元素列表。在导师 Web 应用程序中,用户想要的一件事是查看导师或课程的列表。此列表是动态的,因为用户可能希望查看系统中所有导师的列表,或者基于某些条件的导师子集。同样,用户可能希望查看网站上可用的所有课程的列表或特定导师的课程。我们将如何使用Actix和Tera来显示这些信息?让我们来了解一下。

$PROJECT_ROOT/static 下创建一个文件夹 iter3。在此处创建一个新的文件列表.html并添加以下 html。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>Actix web</title>
</head>

<body>
    <h1>Tutors list</h1>
    <ol>                                        #1
        {% for tutor in tutors %}               #2
        <li>                                    #3
                <h5>{{tutor.name}}</h5>         #4
        </li>
        {% endfor %}                            #5
    </ol>
</body>

</html>

总而言之,我们编写了一个 HTML 文件,其中包含一个模板控制语句(使用 for 循环),该语句循环遍历列表中的每个导师并在网页上显示导师名称。

接下来,让我们编写处理程序函数来实现此逻辑,以及 Web 服务器的 main 函数。

$PROJECT_ROOT/src/bin 下创建一个新文件 iter3.rs,并添加以下代码:

use actix_files as fs;
use actix_web::{error, web, App, Error, HttpResponse, HttpServer, Result};
use serde::{Deserialize, Serialize};
use tera::Tera;

#[derive(Serialize, Deserialize)]
pub struct Tutor {                          #1
    name: String,
}

async fn handle_get_tutors(tmpl: web::Data<tera::Tera>) -> Result<HttpResponse, Error> {    #2
    let tutors: Vec<Tutor> = vec![          #3
        Tutor {
            name: String::from("Tutor 1"),
        },
        ...                                                     #4
    ];
    let mut ctx = tera::Context::new();                         #5
    ctx.insert("tutors", &tutors);                              #6
    let rendered_html = tmpl
        .render("list.html", &ctx)                              #7
        .map_err(|_| error::ErrorInternalServerError("Template error"))?;

    Ok(HttpResponse::Ok().content_type("text/html").body(rendered_html))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {

    println!("Listening on: 127.0.0.1:8080");
    HttpServer::new(|| {
        let tera = Tera::new(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/static/iter3/**/*"
        ))
        .unwrap();

        App::new()
            .data(tera)
            .service(fs::Files::new("/static", "./static").show_files_listing())
            .service(web::resource("/tutors").route(web::get().to(handle_get_tutors))) #8

    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

使用以下命令运行 Web 服务器:

cargo run --bin iter3

从 Web 浏览器中,访问以下 URL:

http://localhost:8080/tutors

您应该会看到显示的导师列表。

在看到显示的导师列表的最初兴奋消退后,您将开始注意到该网页并不是特别令人印象深刻或美观。您现在肯定希望向网页添加一些css。当然,这很容易完成。这是一个示例 css,仅用于说明目的。将此代码放在样式中.css 在 /static 文件夹下,我们已经在 main 函数中声明它是静态资产的源。

/* css */
ul {
    list-style: none;
    padding: 0;
  }
  li {
    padding: 5px 7px;
    background-color: #FFEBCD;
    border: 2px solid #DEB887;
  }

列表中.html在 $PROJECT_ROOT/iter3__ 下,将 css 文件添加到 html 的头块中,如下所示:

<head>
    <meta charset="utf-8" />
    <link rel="stylesheet" type="text/css" href="/static/styles.css" />
    <title>Actix web</title>
</head>

再次运行 Web 服务器,并从 Web 浏览器访问 /tutors 路由。您现在应该看到 css 样式反映在网页上。这可能仍然不是最漂亮的页面,但您现在了解如何将自己的样式添加到网页。

但是,如果您像我一样,并且不想编写自己的自定义 css,则可以像这样导入您喜欢的 css 框架之一。更改 list.html 文件的 HEAD 部分以导入 tailwind.css,一个流行的现代 css 库。您可以导入 BootstrapFoundationBulma 或任何其他选择的 css 框架。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>Actix web</title>
    <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
</head>

<body>
    <h1 class="text-2xl font-bold mt-8 mb-5">Tutors list</h1>
    <ul class="list-disc list-inside my-5 pl-2">
        {% for tutor in tutors %}
        <ol class="list-decimal list-inside my-5 pl-2">
            <h5 class="text-1xl font-bold mb-4 mt-0">{{tutor.name}}</h5>
        </ol>
        {% endfor %}
    </ul>
</body>

</html>

再次编译并运行服务器,这一次你应该看到一些更吸引你眼睛的东西。

在本书中,我们不会花太多时间讨论CSS样式,但是CSS是网页不可或缺的一部分,了解如何将其与Actix和模板一起使用对您来说很重要。

到目前为止,我们已经看到了使用 Actix 和 Tera 在网页中显示动态内容的不同方法。现在让我们换个角度,专注于开发前端 Web 应用程序的另一个重要方面:自动化单元和集成测试。就像我们能够为后端导师 Web 服务编写测试用例一样,是否也可以使用 Actix 和 tera 在 Rust 中为前端 Web 应用程序编写测试用例?让我们在下一节中找出答案。

7.5 编写和运行客户端测试

在本节中,我们不会编写任何新的应用程序代码,而是重用我们之前编写的处理程序函数之一,并学习如何为处理程序编写单元测试用例。

让我们使用我们在 iter2.rs 中编写的代码。具体来说,这是我们将重点介绍的处理程序函数:

async fn handle_post_tutor(
    tmpl: web::Data<tera::Tera>,
    params: web::Form<Tutor>,
) -> Result<HttpResponse, Error> {
    let mut ctx = tera::Context::new();
    ctx.insert("name", ?ms.name);
    ctx.insert("text", "Welcome!");
    let s = tmpl
        .render("user.html", &ctx)
        .map_err(|_| error::ErrorInternalServerError("Template error"))?;

    Ok(HttpResponse::Ok().content_type("text/html").body(s))
}

可以使用 curl POST 请求从命令行调用此处理程序,如下所示。

curl -X POST localhost:8080/tutors -d "name=Terry"

让我们为这个处理程序函数编写一个单元测试用例。

$PROJECT_ROOT/Cargo.toml 中,添加以下部分:

[dev-dependencies]
actix-rt = "2.2.0"

actix-rt 是 Actix 异步运行时,是执行异步测试函数所必需的。

$PROJECT_ROOT/src/bin/iter2.rs 中,将以下测试代码添加到文件末尾(按照惯例,Rust 单元测试用例位于源文件的末尾)。

#[cfg(test)]                            #1
mod tests {                             #2
    use super::*;
    use actix_web::http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
    use actix_web::web::Form;


    #[actix_rt::test]                               #3
    async fn handle_post_1_unit_test() {
        let params = Form(Tutor {                   #4
            name: "Terry".to_string(),
        });
        let tera = Tera::new(concat!(               #5
            env!("CARGO_MANIFEST_DIR"),
            "/static/iter2/**/*"
        ))
        .unwrap();
        let webdata_tera = web::Data::new(tera);        #6
        let resp = handle_post_tutor(webdata_tera, params).await.unwrap();  #7

        assert_eq!(resp.status(), StatusCode::OK);                  #8
        assert_eq!(
            resp.headers().get(CONTENT_TYPE).unwrap(),              #9
            HeaderValue::from_static("text/html")
        );
    }
}

使用以下命令从 $PROJECT_ROOT 运行测试:

cargo test --bin iter2

您应该看到测试通过。

我们刚刚通过直接调用处理程序函数编写了一个单元测试用例。我们能够做到这一点,因为我们知道处理程序函数签名。这对于单元测试用例来说是可以的,但是我们如何模拟Web客户端使用表单数据发布HTTP请求?

这就是集成测试的领域。让我们编写一个集成测试用例来模拟用户表单提交。

将以下内容添加到 $PROJECT_ROOT/src/bin/iter2.rs 中的测试模块。

    use actix_web::dev::{HttpResponseBuilder, Service, ServiceResponse};
    use actix_web::test::{self, TestRequest};

    // Integration test case
    #[actix_rt::test]
    async fn handle_post_1_integration_test() {
        let tera = Tera::new(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/static/iter2/**/*"
        ))
        .unwrap();
        let mut app = test::init_service(App::new().data(tera).configure(app_config)).await;    #1

        let req = test::TestRequest::post()             #2
            .uri("/tutors")
            .set_form(&Tutor {
                name: "Terry".to_string(),
            })
            .to_request();                              #3
        let resp: ServiceResponse = app.call(req).await.unwrap();   #4
        assert_eq!(resp.status(), StatusCode::OK);              #5
        assert_eq!(
            resp.headers().get(CONTENT_TYPE).unwrap(),          #6
            HeaderValue::from_static("text/html")
        );
    }

您会注意到,Actix 以内置服务、模块和函数的形式为测试提供了丰富的支持,我们可以使用它们来编写单元测试或集成测试。

使用以下命令从 $PROJECT_ROOT 运行测试:

cargo test --bin iter2

您应该看到单元测试和集成测试都通过。

至此,我们结束了关于学习为使用 Actix 和 tera 构建的前端 Web 应用程序编写单元和集成测试用例的部分。我们将使用我们在这里学到的知识,在开发导师 Web 应用程序的同时编写实际的测试用例。

7.6 连接到后端 Web 服务

在上一节中,我们使用模拟数据在网页上显示了导师列表。在本节中,我们将从后端导师 Web 服务获取数据以显示在网页上,而不是模拟数据。请注意,从技术上讲,我们可以直接从Actix Web应用程序与数据库通信,但这不是我们想要做的。主要原因是我们不想复制 Web 服务中已经存在的数据库访问逻辑。另一个原因是,我们不希望在 Web 服务和 Web 应用程序中同时公开数据库访问凭据,这可能会增加任何安全/黑客攻击的外围应用。

我们知道后端导师 Web 服务公开了各种 REST API。要从 Web 应用程序与 Web 服务通信,我们需要一个可以嵌入到 Web 应用程序中的 HTTP 客户端。虽然还有其他外部 crate 可用于此目的,但让我们使用 Actix-web 框架中的内置 HTTP 客户端。我们还需要一种方法来解析和解释从 Web 服务返回的 json 数据。为此,我们将使用serde_json箱。

将以下内容添加到 $PROJECT_ROOT/Cargo.toml

serde_json = "1.0.64"

现在让我们编写代码进行连接,以向导师 Web 服务发出 GET 请求并检索导师列表。

$PROJECT_ROOT/src/bin 下创建一个新文件 iter4.rs 并将 iter3.rs 的内容复制到其中,以获得良好的开端。

使用 serde_jsoncrate,我们可以将 HTTP 响应中传入的 json 有效负载反序列化为强类型数据结构。在我们的例子中,我们希望将导师 Web 服务发送的 json 转换为 Vec<Tutor> 类型。我们还想定义 Tutor 结构的结构以匹配传入的 json 数据。删除文件 $PROJECT_ROOT/src/bin/iter4.rsTutor 结构的旧定义,并将其替换为以下内容:

#[derive(Serialize, Deserialize, Debug)]
pub struct Tutor {
    pub tutor_id: i32,
    pub tutor_name: String,
    pub tutor_pic_url: String,
    pub tutor_profile: String,
}

在同一源文件中,在handle_get_tutors处理程序函数中,让我们连接到导师 Web 服务以检索导师列表。在这种情况下,我们可以删除硬编码值。导入actix_web客户端模块并修改handle_get_tutors处理程序函数的代码,如下所示:

use actix_web::client::Client;

async fn handle_get_tutors(tmpl: web::Data<tera::Tera>) -> Result<HttpResponse, Error> {
    let client = Client::default();                 #1

    // Create request builder and send request

    let response = client
        .get("http://localhost:3000/tutors/")       #2
        .send()                                     #3
        .await                                      #4
        .unwrap()                                   #5
        .body()                                     #6
        .await                                      #4
        .unwrap();                                  #5

    let str_list = std::str::from_utf8(&response.as_ref()).unwrap();    #7
    let tutor_list: Vec<Tutor> = serde_json::from_str(str_list).unwrap(); #8
    let mut ctx = tera::Context::new();

    ctx.insert("tutors", &tutor_list);                                 #9
    let rendered_html = tmpl
        .render("list.html", &ctx)
        .map_err(|_| error::ErrorInternalServerError("Template error"))?;

    Ok(HttpResponse::Ok().content_type("text/html").body(rendered_html))
}

与渲染 Tera 模板相关的其余代码类似于我们之前看到的。

接下来,创建一个新文件夹 $PROJECT_ROOT/static/iter4。在此文件夹下放置 $PROJECT_ROOT/static/iter3 中的列表.html文件的副本。更改 list.html 文件以将模板变量 {{tutor.name}} 更改为 {{tutor.tutor_name}},因为这是从导师 Web 服务发回的数据的结构。

这是更新的列表.html列表,在 iter4 文件夹下。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>Actix web</title>
    <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
</head>

<body>
    <h1 class="text-2xl font-bold mt-8 mb-5">Tutors list</h1>
    <ul class="list-disc list-inside my-5 pl-2">
        {% for tutor in tutors %}
        <ol class="list-decimal list-inside my-5 pl-2">
          <h5 class="text-1xl font-bold mb-4 mt-0">{{tutor.tutor_name}}</h5>
        </ol>
        {% endfor %}
    </ul>
</body>

</html>

还要更改 iter4.rs 中的 main() 函数,以在 $PROJCT_ROOT/static/iter4 文件夹中查找 Tera 模板。这是更新的 main() 函数。

#[actix_web::main]
async fn main() -> std::io::Result<()> {
  println!("Listening on: 127.0.0.1:8080!");
  HttpServer::new(|| {
    let tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"),"/static/iter4/**/*")).unwrap();

    App::new()
      .data(tera)
      .service(fs::Files::new("/static", "./static").show_files_listing())
      .service(web::resource("/tutors").route(web::get().to(handle_get_tutors)))
  })
  .bind("127.0.0.1:8080")?
  .run()
  .await
}

到目前为止,我们所做的是从导师 Web 服务中获取导师列表(而不是迭代 3 中使用的硬编码值),并使用它来在 list.html 文件中显示导师列表,该文件在 HTTP 请求从 route /tutors 的客户端到达时呈现。

要对此进行测试,请首先转到 ezytutors 工作区下的文件夹 tutor_web_service,然后在单独的终端中运行服务器。此服务器现在应该侦听本地主机:3000。使用以下命令测试服务器:

cargo run --bin iter6

Iter6 是我们为 Tutor Web service_构建的最后一个迭代。

然后从另一个终端,使用以下命令从 $PROJECT_ROOT 运行tutor_ssr_app Web 服务器:

cargo run --bin iter4

我们现在有在端口 3000 上运行的导师 Web 服务和在端口 8080 上运行的导师 Web 应用程序,两者都在本地主机上。应该发生以下情况:当用户访问端口 8080 上的 /tutors 路由时,请求将转到 Web 应用的 Web 处理程序,然后该处理程序将调用导师 Web 服务以检索导师列表。然后,导师 Web 应用处理程序会将此数据注入 tera 模板,并将网页显示回用户。

要从浏览器对此进行测试,请访问 URL:

localhost:8080/tutors

您应该会看到网页中填充的导师姓名列表,该列表是从我们的导师网络服务中检索到的。如果您已经走到了这一步,恭喜您!如果遇到任何错误,只需将代码回溯到工作时的最后一个点,然后按照本章中的相应说明再次按顺序重新应用更改。

有了这个,我们学习了使用 Actix 开发客户端应用程序的关键方面。在下一章中,我们将使用本章中获得的知识和技能,为导师Web应用程序编写代码。

7.7 小结

  • 在本章中,我们介绍了使用 Actix 开发服务器端 Web 应用程序的基础知识。
  • 我们首先学习了如何使用Actix提供静态网页。
  • 在第二部分中,我们使用Actix和Tera模板构建了一个简单的动态网页。我们学习了如何将Tera注入Web应用程序,并使其可供所有处理程序使用。我们还学习了如何创建 Tera 上下文对象,将数据插入其中,并通过传递 Tera 模板中定义的模板变量的值来呈现 Tera html 模板。
  • 在第三部分中,我们学习了如何通过表单接受用户输入,并在用户提交表单时在 Web 应用程序的特定路由上触发 HTTP 请求。
  • 在第四部分中,我们学习了如何使用Tera模板在网页中呈现导师姓名列表。
  • 然后,我们学习了如何为 Web 应用程序处理程序编写单元和集成测试用例。
  • 在最后一部分中,我们连接到后端导师 Web 服务 API,并检索了导师列表。向用户显示导师列表。
  • Rust 不仅可以用于构建后端 Web 服务,还可以用于构建前端 Web 应用程序
  • 服务器端呈现 (SSR) 是一种 Web 体系结构模式,它涉及在服务器上创建完全呈现的网页,并简单地将其发送到浏览器进行显示。SSR 通常涉及在网页上混合提供静态和动态内容。
  • Actix Web 和 Tera 模板引擎是在基于 Rust 的 Web 应用程序中实现服务器端渲染的强大工具。
  • Tera 模板引擎被实例化并注入到 main() 函数中的 Web 应用程序中。tera 实例由 Actix Web 框架提供给所有处理程序函数。反过来,路由处理程序函数可以使用 tera 模板来构造动态网页,这些网页作为 HTTP 响应正文的一部分发送回浏览器客户端。
  • HTML 表单用于捕获用户输入,并将这些输入发布到 Actix Web 应用程序上的路由。然后,相应的路由处理程序处理该 HTTP 请求,并发回包含动态网页的 HTTP 响应。
  • Tera模板的控制流功能可用于在网页上显示信息列表。可以从本地数据库或外部 Web 服务检索列表的内容,并将其注入到网页模板中。
  • Actix Web 客户端可用作 HTTP 客户端,用于在 Actix Web 应用程序前端和 Actix Web 服务后端之间进行通信。

在本书的下一部分,我们将直接开始编写导师 Web 应用程序,该应用程序可以充当导师 Web 服务的客户端前端。

下一章见。

相关推荐

好用的云函数!后端低代码接口开发,零基础编写API接口

前言在开发项目过程中,经常需要用到API接口,实现对数据库的CURD等操作。不管你是专业的PHP开发工程师,还是客户端开发工程师,或者是不懂编程但懂得数据库SQL查询,又或者是完全不太懂技术的人,通过...

快速上手:Windows 平台上 cURL 命令的使用方法

在工作流程中,为了快速验证API接口有效性,团队成员经常转向直接执行cURL命令的方法。这种做法不仅节省时间,而且促进了团队效率的提升。对于使用Windows系统的用户来说,这里有一套详细...

使用 Golang net/http 包:基础入门与实战

简介Go的net/http包是构建HTTP服务的核心库,功能强大且易于使用。它提供了基本的HTTP客户端和服务端支持,可以快速构建RESTAPI、Web应用等服务。本文将介绍ne...

#小白接口# 使用云函数,人人都能编写和发布自己的API接口

你只需编写简单的云函数,就可以实现自己的业务逻辑,发布后就可以生成自己的接口给客户端调用。果创云支持对云函数进行在线接口编程,进入开放平台我的接口-在线接口编程,设计一个新接口,设计和配置好接口参...

极度精神分裂:我家没有墙面开关,但我虚拟出来了一系列开关

本内容来源于@什么值得买APP,观点仅代表作者本人|作者:iN在之前和大家说过,在iN的家里是没有墙面开关的。...

window使用curl命令的注意事项 curl命令用法

cmd-使用curl命令的注意点前言最近在cmd中使用curl命令来测试restapi,发现有不少问题,这里记录一下。在cmd中使用curl命令的注意事项json不能由单引号包括起来json...

Linux 系统curl命令使用详解 linuxctrl

curl是一个强大的命令行工具,用于在Linux系统中进行数据传输。它支持多种协议,包括HTTP、HTTPS、FTP等,用于下载或上传数据,执行Web请求等。curl命令的常见用法和解...

Tornado 入门:初学者指南 tornados

Tornado是一个功能强大的PythonWeb框架和异步网络库。它最初是为了处理实时Web服务中的数千个同时连接而开发的。它独特的Web服务器和框架功能组合使其成为开发高性能Web...

PHP Curl的简单使用 php curl formdata

本文写给刚入PHP坑不久的新手们,作为工具文档,方便用时查阅。CURL是一个非常强大的开源库,它支持很多种协议,例如,HTTP、HTTPS、FTP、TELENT等。日常开发中,我们经常会需要用到cur...

Rust 服务器、服务和应用程序:7 Rust 中的服务器端 Web 应用简介

本章涵盖使用Actix提供静态网页...

我给 Apache 顶级项目提了个 Bug apache顶级项目有哪些

这篇文章记录了给Apache顶级项目-分库分表中间件ShardingSphere提交Bug的历程。说实话,这是一次比较曲折的Bug跟踪之旅。10月28日,我们在GitHub上提...

linux文件下载、服务器交互(curl)

基础环境curl命令描述...

curl简单使用 curl sh

1.curl--help#查看关键字2.curl-A“(添加user-agent<name>SendUser-Agent<name>toserver)”...

常用linux命令:curl 常用linux命令大全

//获取网页内容//不加任何选项使用curl时,默认会发送GET请求来获取内容到标准输出$curlhttp://www.baidu.com//输出<!DOCTYPEh...

三十七,Web渗透提高班之hack the box在线靶场注册及入门知识

一.注册hacktheboxHackTheBox是一个在线平台,允许测试您的渗透技能和代码,并与其他类似兴趣的成员交流想法和方法。它包含一些不断更新的挑战,并且模拟真实场景,其风格更倾向于CT...