budshome (blog: 芽之家) shared the aphorism --
问题的复杂性不在于问题本身,而是找到问题后,你如何去处理。 -- 佚名

[Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(2)- 获取并解析 GraphQL 数据

💥 内容涉及著作权,均归属作者本人。若非作者注明,默认欢迎转载:请注明出处,及相关链接。

Summary: 《使用 tide、handlebars、rhai、graphql 开发 Rust web 前端》第二部分:使用 surf、graphql_client 发起 GraphQL 请求,并解析 GraphQL 响应应答数据,渲染到 handlebars 模板中。

Topics: rust tide rust-web graphql-client surf

上一篇文章《crate 选择及环境搭建》中,我们对 HTTP 服务器端框架、模板引擎库、GraphQL 客户端等 crate 进行了选型,以及对开发环境进行了搭建和测试。另外,还完成了最基本的 handlebars 模板开发,这是 Rust web 开发的骨架工作。本篇文章中,我们请求 GraphQL 服务器后端提供的 API,获取 GraphQL 数据并进行解析,然后将其通过 handlebars 模板展示。

本次实践中,我们使用 surf 库做为 HTTP 客户端,用来发送 GraphQL 请求,以及接收响应数据。对于 GraphQL 客户端,目前成熟的 crate,并没有太多选择,可在生产环境中应用的,唯有 graphql_client。让我们直接将它们添加到依赖项,不需要做额外的特征启用方面的设定:

cargo add surf graphql_client

如果你想使用 reqwest 做为 HTTP 客户端,替换仅为一行代码(将发送 GraphQL 请求时的 surf 函数,修改为 reqwest 函数即可)。

现在,我们的 Cargo.toml 文件内容如下:

[package]
name = "frontend-handlebars"
version = "0.1.0"
authors = ["我是谁?"]
edition = "2018"

[dependencies]
async-std = { version = "1.9.0", features = ["attributes"] }
tide = "0.16.0"

serde = { version = "1.0.126", features = ["derive"] }
serde_json = "1.0.64"

surf = "2.2.0"
graphql_client = "0.9.0"
handlebars = "4.0.0"

编写 GraphQL 数据查询描述

首先,我们需要从 GraphQL 服务后端下载 schema.graphql,放置到 frontend-handlebars/graphql 文件夹中。schema 是我们要描述的 GraphQL 查询的类型系统,包括可用字段,以及返回对象等。

然后,在 frontend-handlebars/graphql 文件夹中创建一个新的文件 all_projects.graphql,描述我们要查询的项目数据。项目数据查询很简单,我们查询所有项目,不需要传递参数:

query AllProjects {
  allProjects {
    id
    userId
    subject
    website
  }
}

最后,在 frontend-handlebars/graphql 文件夹中创建一个新的文件 all_users.graphql,描述我们要查询的用户数据。用户的查询,需要权限。也就是说,我们需要先进行用户认证,用户获取到自己在系统的令牌(token)后,才可以查看系统用户数据。每次查询及其它操作,用户都要将令牌(token)作为参数,传递给服务后端,以作验证。

query AllUsers($token: String!) {
  allUsers(
    token: $token
  ) {
    id
    email
    username
  }
}

用户需要签入系统,才能获取个人令牌(token)。此部分我们不做详述,请参阅文章《基于 tide + async-graphql + mongodb 构建异步 Rust GraphQL 服务》、《基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务》,以及项目 zzy/tide-async-graphql-mongodb 进行了解。

使用 graphql_client 构建查询体(QueryBody)

在此,我们需要使用到上一节定义的 GraphQL 查询描述,通过 GraphQLQuery 派生属性注解,可以实现与查询描述文件(如 all_users.graphql)中查询同名的结构体。当然,Rust 文件中,结构体仍然需要我们定义,注意与查询描述文件中的查询同名。如,与 all_users.graphql 查询描述文件对应的代码为:

type ObjectId = String;

#[derive(GraphQLQuery)]
#[graphql(
    schema_path = "./graphql/schema.graphql",
    query_path = "./graphql/all_users.graphql",
    response_derives = "Debug"
)]
struct AllUsers;

type ObjectId = String; 表示我们直接从 MongoDB 的 ObjectId 中提取其 id 字符串。

接下来,我们构建 graphql_client 查询体(QueryBody),我们要将其转换为 Value 类型。项目列表查询没有参数,构造简单。我们以用户列表查询为例,传递我们使用 PBKDF2 对密码进行加密(salt)和散列(hash)运算后的令牌(token)。

本文实例中,为了演示,我们将令牌(token)获取后,作为字符串传送。实际应用代码中,当然是作为 cookie/session 参数来获取的,不会进行明文编码。

    // make data and render it
    let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJlbWFpbCI6ImlvazJAYnVkc2hvbWUuY29tIiwidXNlcm5hbWUiOiLmiJHmmK9vazIiLCJleHAiOjEwMDAwMDAwMDAwfQ.Gk98TjaFPpyW2Vdunn-pVqSPizP_zzTr89psBTE6zzfLQStUnBEXA2k0yVrS0CHBt9bHLLcFgmo4zYiioRBzBg";
    let build_query = AllUsers::build_query(all_users::Variables {
        token: token.to_string(),
    });
    let query = serde_json::json!(build_query);

使用 surf 发送 GraphQL 请求,并获取响应数据

相比于 frontend-yew 系列文章,本次 frontend-handlebars 实践中的 GraphQL 数据请求和响应,是比较简单易用的。

  • surf 库非常强大而易用,其提供的 post 函数,可以直接请求体,并返回泛型数据。
  • 因为在 hanlebars 模板中,可以直接接受并使用 json 数据,所以我们使用 recv_json() 方法接收响应数据,并指定其类型为 serde_json::Value
  • 在返回的数据响应体中,可以直接调用 Response<Data> 结构体中的 data 字段,这是 GraphQL 后端的完整应答数据。
    let gql_uri = "http://127.0.0.1:8000/graphql";
    let gql_post = surf::post(gql_uri).body(query);

    let resp_body: Response<serde_json::Value> = gql_post.recv_json().await.unwrap();
    let resp_data = resp_body.data.expect("missing response data");

let gql_uri = "http://127.0.0.1:8000/graphql"; 一行,实际项目中,通过配置环境变量来读取,是较好的体验。

数据的渲染

我们实现了数据获取、转换,以及部分解析。我们接收到的应答数据指定为 serde_json::Value 格式,我们可以直接将其发送给 handlebars 模板使用。因此,下述处理,直接转移到 handlebars 模板 —— html 文件中。是故,需要先创建 templates/users/index.html 以及 templates/projects/index.html 两个文件。

我们的数据内容为用户列表或者项目列表,很显然是一个迭代体,我们需要通过要给循环控制体来获取数据——handlebars 的模板语法我们不做详述(请参阅 handlebars 中文文档)。如,获取用户列表,使用 handlebars 模板的 #each 语法:

    <h1>all users</h1>

    <ul>
      {{#each allUsers as |u|}}
        <li><b>{{u.username}}</b></li>
        <ul>
          <li>{{ u.id }}</li>
          <li>{{ u.email }}</li>
        </ul>
      {{/each}}
    </ul>

基本上,技术点就是如上部分。现在,让我们看看,在上次实践《crate 选择及环境搭建》基础上新增、迭代的完整代码。

数据处理的完整代码

main.rs 文件,无需迭代。

routes/mod.rs 路由开发

增加用户列表、项目列表路由的设定。

use tide::{self, Server, Request};
use serde_json::json;

pub mod users;
pub mod projects;

use crate::{State, util::common::Tpl};
use crate::routes::{users::user_index, projects::project_index};

pub async fn push_res(app: &mut Server<State>) {
    app.at("/").get(index);
    app.at("users").get(user_index);
    app.at("projects").get(project_index);
}

async fn index(_req: Request<State>) -> tide::Result {
    let index: Tpl = Tpl::new("index").await;

    // make data and render it
    let data = json!({"app_name": "frontend-handlebars / tide-async-graphql-mongodb", "author": "我是谁?"});

    index.render(&data).await
}

routes/users.rs 用户列表处理函数

获取所有用户信息,需要传递令牌(token)参数。注意:为了演示,我们将令牌(token)获取后,作为字符串传送。实际应用代码中,是通过 cookie/session 参数来获取的,不会进行明文编码。

use graphql_client::{GraphQLQuery, Response};
use tide::Request;

use crate::{util::common::Tpl, State};

type ObjectId = String;

#[derive(GraphQLQuery)]
#[graphql(
    schema_path = "./graphql/schema.graphql",
    query_path = "./graphql/all_users.graphql",
    response_derives = "Debug"
)]
struct AllUsers;

pub async fn user_index(_req: Request<State>) -> tide::Result {
    let user_index: Tpl = Tpl::new("users/index").await;

    // make data and render it
    let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJlbWFpbCI6ImlvazJAYnVkc2hvbWUuY29tIiwidXNlcm5hbWUiOiLmiJHmmK9vazIiLCJleHAiOjEwMDAwMDAwMDAwfQ.Gk98TjaFPpyW2Vdunn-pVqSPizP_zzTr89psBTE6zzfLQStUnBEXA2k0yVrS0CHBt9bHLLcFgmo4zYiioRBzBg";
    let build_query = AllUsers::build_query(all_users::Variables {
        token: token.to_string(),
    });
    let query = serde_json::json!(build_query);

    let gql_uri = "http://127.0.0.1:8000/graphql";
    let gql_post = surf::post(gql_uri).body(query);

    let resp_body: Response<serde_json::Value> = gql_post.recv_json().await.unwrap();
    let resp_data = resp_body.data.expect("missing response data");

    user_index.render(&resp_data).await
}

routes/projects.rs 项目列表处理函数

项目列表的处理中,无需传递参数。

use graphql_client::{GraphQLQuery, Response};
use tide::Request;

use crate::{util::common::Tpl, State};

type ObjectId = String;

#[derive(GraphQLQuery)]
#[graphql(
    schema_path = "./graphql/schema.graphql",
    query_path = "./graphql/all_projects.graphql",
    response_derives = "Debug"
)]
struct AllProjects;

pub async fn project_index(_req: Request<State>) -> tide::Result {
    let project_index: Tpl = Tpl::new("projects/index").await;

    // make data and render it
    let build_query = AllProjects::build_query(all_projects::Variables {});
    let query = serde_json::json!(build_query);

    let gql_uri = "http://127.0.0.1:8000/graphql";
    let gql_post = surf::post(gql_uri).body(query);

    let resp_body: Response<serde_json::Value> = gql_post.recv_json().await.unwrap();
    let resp_data = resp_body.data.expect("missing response data");

    project_index.render(&resp_data).await
}

前端渲染的完整源码

templates/index.html 文件,无需迭代。

对于这部分代码,或许你会认为 headbody 部分,每次都要写,有些啰嗦。

实际上,这是模板引擎的一种思路。handlebars 模板认为:模板的继承或者包含,不足以实现模板重用。好的方法应该是使用组合的概念,如将模板分为 headheaderfooter,以及其它各自内容的部分,然后在父级页面中嵌入组合。

所以,实际应用中,这些不会显得啰嗦,反而会很简洁。本博客的 handlebars 前端源码 surfer/tree/main/frontend-handlebars 或许可以给你一点启发;至于具体使用方法,请参阅 handlebars 中文文档

templates/users/index.html 用户列表数据渲染

<!DOCTYPE html>
<html>

  <head>
    <title>all users</title>

    <link rel="icon" href="/static/favicon.ico">
    <link rel="shortcut icon" href="/static/favicon.ico">
  </head>

  <body>
    <a href="/"><strong>frontend-handlebars / tide-async-graphql-mongodb</strong></a>
    <h1>all users</h1>

    <ul>
      {{#each allUsers as |u|}}
        <li><b>{{u.username}}</b></li>
        <ul>
          <li>{{ u.id }}</li>
          <li>{{ u.email }}</li>
        </ul>
      {{/each}}
    </ul>

  </body>

</html>

templates/projects/index.html 项目列表数据渲染

<!DOCTYPE html>
<html>

  <head>
    <title>all projects</title>

    <link rel="icon" href="favicon.ico">
    <link rel="shortcut icon" href="favicon.ico">
  </head>

  <body>
    <a href="/"><strong>frontend-handlebars / tide-async-graphql-mongodb</strong></a>
    <h1>all projects</h1>

    <ul>
      {{#each allProjects as |p|}}
        <li><b>{{p.subject}}</b></li>
        <ul>
          <li>{{p.id}}</li>
          <li>{{p.userId}}</li>
          <li><a href="{{p.website}}" target="_blank">{{p.website}}</a></li>
        </ul>
      {{/each}}
    </ul>

  </body>

</html>

编译和运行

执行 cargo buildcargo run 后,如果你未自定义端口,请在浏览器中打开 http://127.0.0.1:3000 。

列表数据

至此,获取并解析 GraphQL 数据已经成功。

谢谢您的阅读!


Rust 生态与实践

Related Articles

  1. [Rust] Rust 官方周报 399 期(2021-07-14)
  2. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(5)- 构建 HTTP 请求、与外部服务器通信的两种方法
  3. [Rust] Rust 官方周报 398 期(2021-07-07)
  4. [Rust] Rust 官方周报 397 期(2021-06-30)
  5. [Rust] Rust 官方周报 396 期(2021-06-23)
  6. [Rust] Rust 官方周报 395 期(2021-06-16)
  7. [Rust] Rust 1.53.0 明日发布,关键新特性一瞥
  8. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(3)- rhai 脚本、静态/资源文件、环境变量等
  9. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(2)- 获取并解析 GraphQL 数据
  10. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(1)- crate 选择及环境搭建
  11. [Rust] Rust 官方周报 394 期(2021-06-09)
  12. [Rust] Rust web 前端库/框架评测,以及和 js 前端库/框架的比较
  13. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(4)- 获取 GraphQL 数据并解析
  14. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(3)- 资源文件及小重构
  15. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 标准的 web 前端(2)- 组件和路由
  16. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 标准的 web 前端(1)- 起步及 crate 选择
  17. [Rust] Rust 官方周报 393 期(2021-06-02)
  18. [Rust] Rust 官方周报 392 期(2021-05-26)
  19. [Rust] Rust 中,对网址进行异步快照,并添加水印效果的实践
  20. [Rust] Rust 官方周报 391 期(2021-05-19)
  21. [Rust] Rust,风雨六载,砥砺奋进
  22. [Rust] 为什么我们应当将 Rust 用于嵌入式开发?
  23. [Rust] Rust 官方周报 390 期(2021-05-12)
  24. [Rust] Rust + Android 的集成开发设计
  25. [Rust] Rust 1.52.1 已正式发布,及其新特性详述
  26. [Rust] 让我们用 Rust 重写那些伟大的软件吧
  27. [Rust] Rust 1.52.0 已正式发布,及其新特性详述
  28. [Rust] Rust 官方周报 389 期(2021-05-05)
  29. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(4) - 变更服务,以及小重构
  30. [Rust] Rust 1.52.0 稳定版预发布测试中,关键新特性一瞥
  31. [Rust] Rust 生态中,最不知名的贡献者和轶事
  32. [Rust] Rust 基金会迎来新的白金会员:Facebook
  33. [Rust] Rustup 1.24.1 已官宣发布,及其新特性详述
  34. [Rust] Rust 官方周报 388 期(2021-04-28)
  35. [Rust] Rust 官方周报 387 期(2021-04-21)
  36. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(4)- 变更服务,以及第二次重构
  37. [Rust] Rustup 1.24.0 已官宣发布,及其新特性详述
  38. [Rust] basedrop:Rust 生态中,适用于实时音频的垃圾收集器
  39. [Rust] Rust 编译器团队对成员 Aaron Hill 的祝贺
  40. [Rust] Jacob Hoffman-Andrews 加入 Rustdoc 团队
  41. [机器人] 为什么应将 Rust 引入机器人平台?以及机器人平台的 Rust 资源推荐
  42. [Rust] rust-lang.org、crates.io,以及 docs.rs 的管理,已由 Mozilla 转移到 Rust 基金会
  43. [Rust] Rust 官方周报 386 期(2021-04-14)
  44. [Rust] Rust 编译器(Compiler)团队 4 月份计划 - Rust Compiler April Steering Cycle
  45. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(3) - 重构
  46. [Rust] 头脑风暴进行中:Async Rust 的未来熠熠生辉
  47. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(2) - 查询服务
  48. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务 - 起步及 crate 选择
  49. [Rust] Rust 2021 版本特性预览,以及工作计划
  50. [Rust] Rust 用在生产环境的 42 家公司
  51. [Rust] 构建最精简的 Rust Docker 镜像
  52. [Rust] Rust 官方周报 385 期(2021-04-07)
  53. [Rust] 使用 Rust 做异步数据采集的实践
  54. [Rust] Android 支持 Rust 编程语言,以避免内存缺陷
  55. [Rust] Android 平台基础支持转向 Rust
  56. [Rust] Android 团队宣布 Android 开源项目(AOSP),已支持 Rust 语言来开发 Android 系统本身
  57. [Rust] RustyHermit——基于 Rust 实现的下一代容器 Unikernel
  58. [Rust] Rustic:完善的纯粹 Rust 技术栈实现的国际象棋引擎,多平台支持(甚至包括嵌入式设备树莓派 Raspberry Pi、Buster)
  59. [Rust] Rust 迭代器(Iterator trait )的要诀和技巧
  60. [Rust] 使用 Rust 极致提升 Python 性能:图表和绘图提升 24 倍,数据计算提升 10 倍
  61. [Rust] 【2021-04-03】Rust 核心团队人员变动
  62. [Rust] Rust web 框架现状【2021 年 1 季度】
  63. [Rust] Rust 官方周报 384 期(2021-03-31)
  64. [Rust] Rust 中的解析器组合因子(parser combinators)
  65. [生活] 毕马威(KPMG)调查报告:人工智能的实际采用,在新冠疫情(COVID-19)期间大幅提升
  66. [Python] HPy - 为 Python 扩展提供更优秀的 C API
  67. [Rust] 2021 年,学习 Rust 的网络资源推荐(2)
  68. [Rust] 2021 年,学习 Rust 的网络资源推荐
  69. [生活] 况属高风晚,山山黄叶飞——彭州葛仙山露营随笔
  70. [Rust] Rust 1.51.0 已正式发布,及其新特性详述
  71. [Rust] 为 Async Rust 构建共享的愿景文档—— Rust 社区的讲“故事”,可获奖
  72. [Rust] Rust 纪元第 382 周最佳 crate:ibig 的实践,以及和 num crate 的比较
  73. [Rust] Rust 1.51.0 稳定版本改进介绍
  74. [Rust] Rust 中将 markdown 渲染为 html
  75. [生活] 国民应用 App 的用户隐私数据窥探
  76. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(3)- 重构
  77. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(2)- 查询服务
  78. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(1)- 起步及 crate 选择
  79. [Rust] Rust 操控大疆可编程 tello 无人机

Topics

rust(79)

graphql(17)

rust-官方周报(17)

webassembly(14)

async-graphql(8)

wasm(8)

rust-官方博客(8)

tide(7)

rust-web(7)

yew(7)

this-week-in-rust(6)

mysql(5)

rbatis(5)

android(4)

actix-web(4)

mongodb(3)

json-web-token(3)

jwt(3)

cargo(3)

技术延伸(3)

rust-wasm(3)

trunk(3)

handlebars(3)

rhai(3)

用户隐私(2)

学习资料(2)

python(2)

ai(2)

人工智能(2)

postgresql(2)

rust-compiler(2)

rust-基金会(2)

rust-foundation(2)

rustup(2)

rust-toolchain(2)

rust-工具链(2)

rust-游戏开发(2)

rust-区块链(2)

graphql-client(2)

rust-game(2)

tello(1)

drone(1)

无人机(1)

隐私数据(1)

markdown(1)

html(1)

crate(1)

async(1)

异步(1)

旅游(1)

不忘生活(1)

葛仙山(1)

hpy(1)

python-扩展(1)

正则表达式(1)

解析器组合因子(1)

组合器(1)

regular-expression(1)

parser-combinator(1)

regex(1)

官方更新(1)

rust-工作招聘(1)

rust-技术资料(1)

rust-周最佳-crate(1)

rust-web-框架(1)

rust-web-framework(1)

rust-核心团队(1)

rust-core-team(1)

rust-language-team(1)

pyo3(1)

rust-python-集成(1)

python-性能改进(1)

迭代器(1)

iterator-trait(1)

国际象棋(1)

chess(1)

游戏引擎(1)

game-engine(1)

虚拟化(1)

unikernel(1)

rustyhermit(1)

linux(1)

virtualization(1)

sandboxing(1)

沙箱技术(1)

数据采集(1)

异步数据采集(1)

docker(1)

镜像(1)

生产环境(1)

rust-评价(1)

rust-2021-edition(1)

rust-2021-版本(1)

graphql-查询(1)

vision-doc(1)

愿景文档(1)

代码重构(1)

steering-cycle(1)

方向周期(1)

隐私声明(1)

机器人(1)

robotics(1)

rustdoc(1)

rust-编译器(1)

实时音频(1)

real-time-audio(1)

变更服务(1)

mutation(1)

查询服务(1)

query(1)

rust-贡献者(1)

rust-轶事(1)

rust-稳定版(1)

rust-预发布(1)

rust-测试(1)

安全编程(1)

可信计算(1)

安全代码(1)

secure-code(1)

rust-android-integrate(1)

rust-embedded(1)

rust-嵌入式(1)

rust-生产环境(1)

rust-2021(1)

rust-production(1)

网页快照(1)

网页截图(1)

水印效果(1)

图片水印(1)

yew-router(1)

css(1)

web-前端(1)

wasm-bindgen(1)

区块链(1)

blockchain(1)

surf(1)

dotenv(1)

标识符(1)

rust-1.53.0(1)

Elsewhere

- Open Source
  1. github/zzy
  2. github/sansx
- Awesome Blog
  1. Sansx's Studio
  2. 曙光磁铁的博客
- Learning & Studying
  1. Rust 学习资料 - 芽之家