首页
Preview

一款完全使用Rust编写的Web应用程序

我的最新软件架构实验是使用 Rust 写一个完整的真实世界 Web 应用程序,尽可能少地使用样板文件。在本文中,我想与你分享我的发现,回答关于 Rust 到底有多“网页”(how much web)的问题。

与本文相关的项目可以在 GitHub 上找到。我将客户端前端和服务器端后端放在同一个代码库中以便于维护。这意味着 Cargo 需要编译整个应用程序的前端和后端二进制文件,并使用不同的依赖关系。

请注意,该项目目前正在快速地进行架构演化,本文中所有相关源代码均可在 rev1 分支中找到。你可以在这里阅读本博客系列的第二部分。

该应用程序本身是一个简单的身份验证演示。它允许你使用所选择的用户名和密码(必须相同)进行登录,如果它们不相等则会失败。在成功身份验证后,JSON Web Token (JWT) 会存储在客户端和服务器端。通常不需要在服务器端存储令牌,但我这样做是为了演示目的。例如,它可以用于跟踪实际登录的用户数量。整个应用程序可以通过一个单一的 Config.toml 进行配置,例如设置数据库凭据或服务器主机和端口。

应用程序的默认 Config.toml

前端 —— 客户端

我决定在应用程序的客户端使用 yew。Yew 是一个现代的 Rust 框架,受 Elm、Angular 和 ReactJS 的启发,用于创建具有 WebAssembly(Wasm)的多线程前端应用程序。该项目正在积极开发中,尚没有那么多稳定版本。

工具 cargo-web 是 yew 的一个直接依赖项,它使得将代码交叉编译到 Wasm 变得简单直接。实际上,Rust 编译器有三个主要的 Wasm 目标:

  • asmjs-unknown-emscripten —— 通过 Emscripten 使用 asm.js
  • wasm32-unknown-emscripten —— 通过 Emscripten 使用 WebAssembly
  • wasm32-unknown-unknown —— 使用 Rust 的本地 WebAssembly 后端使用 WebAssembly

我决定使用最后一个目标,它需要一个 Rust 的夜间编译器,但最能展示 Rust 本机 Wasm 的可能性。

WebAssembly 目前是涉及 Rust 的最热门话题之一。在将 Rust 交叉编译为 Wasm 并将其集成到 nodejs(npm 打包)世界中方面有大量正在进行的工作。我决定直接进行,不依赖于任何 JavaScript。

在启动 Web 应用程序的前端时(在我的项目中通过 make frontend),cargo-web 会将应用程序交叉编译到 Wasm,并将其与一些静态内容打包在一起。然后 cargo-web 启动一个本地 Web 服务器,为开发目的提供服务。

Yew 有一些很棒的功能,比如可重用的组件架构,这使得将我的应用程序分成三个主要组件变得容易:

  • RootComponent:直接挂载在网站的 `` 标签上,并决定应该加载哪个子组件。如果在页面最初进入时发现 JWT,则尝试使用后端通信更新令牌。如果失败,则路由到 LoginComponent
  • LoginComponentRootComponent 的子组件,包含登录表单字段。它还与后端通信以进行基本的用户名和密码身份验证,并在成功身份验证后将 JWT 保存在 cookie 中。在成功身份验证后,路由到 ContentComponent

LoginComponent

  • ContentComponentRootComponent 的另一个子组件,包含主页面内容(现在只有标题和注销按钮)。它可以通过 RootComponent(如果已经存在有效的会话令牌)或通过 LoginComponent(在成功身份验证后)访问。当用户点击注销按钮时,该组件会与后端通信。

ContentComponent

  • RouterComponent:包含组件之间所有可能的路由,包含应用程序的初始“加载”状态和“错误”状态。直接附加到 RootComponent

服务是 yew 的下一个关键概念。它们允许在组件之间重用相同的逻辑,例如日志门面或 cookie 处理。服务在组件初始化时是无状态的,并将在组件初始化时创建。除了服务之外,yew 还包含代理的概念。它们可用于在组件之间共享数据,并提供整体应用程序状态,例如路由代理所需的状态。为了在所有组件之间实现演示应用程序的路由,我实现了自定义路由代理和服务。实际上,yew 没有独立的路由器,但它们的示例包含一个支持所有 URL 修改的参考实现。“奇妙的是,yew 使用Web Workers API在单独的线程中生成代理,并使用附加到线程的本地调度程序来处理并发任务。这使得使用Rust编写的高并发应用程序可以在浏览器中运行。”

每个组件都实现了自己的Renderable特质,这使我们可以通过html!{}宏直接在Rust源码中包含HTML,这非常棒,并且一定经过编译器内部的借用检查器检查!

LoginComponent的Renderable实现

前端与后端之间的通信是通过每个客户端的WebSocket连接实现的。WebSocket的好处在于它可用于二进制消息,并且服务器也能够在需要时向客户端推送通知。Yew已经提供了一个WebSocket服务,但我决定为演示应用程序创建一个自定义版本,主要原因是懒惰地在服务中直接初始化连接。如果在组件初始化期间创建WebSocket服务,我将不得不跟踪多个套接字连接。

我决定使用二进制协议Cap'n Proto作为应用程序数据通信层(而不是类似JSON、MessagePack或CBOR的东西)来提高速度和紧凑性。值得一提的是,我没有使用Cap'n Proto的接口RPC协议,因为Rust实现不能编译为WebAssembly(因为tokio-rs的Unix依赖项)。这使得在正确的请求和响应类型之间区分有点困难,但是一个清晰结构的API可以解决这个问题:

应用程序的Cap'n Proto协议定义

你可以看到这里有两个不同的登录请求变体:一个是针对_LoginComponent_的(带有用户名和密码的凭据请求),另一个是针对_RootComponent_的(已有令牌续订请求)。所有需要的协议相关实现都打包在一个协议服务中,这使得它可以在整个前端轻松地重用。

前端的用户界面由UIkit驱动,其中版本3.0.0将在不久的将来发布。一个自定义的build.rs脚本会自动下载所有需要的UIkit依赖项并编译总样式表。这意味着可以在单个style.scss文件中插入自定义样式,并应用于整个应用程序。很好!

前端测试

在我看来,测试是一个小问题:单独的服务可以很容易地进行测试,但是yew还没有提供一种方便的方法来测试单个组件或代理。目前,纯Rust无法进行前端的集成和端到端测试。可以使用类似CypressProtractor的项目,但这将包括太多的JavaScript/TypeScript样板代码,因此我跳过了这个选项。

但是,也许这是一个新项目的好起点:用Rust编写的端到端测试框架!你认为呢?

后端 - 服务器端

我选择的后端框架是actix-web:一个小型、实用且极快的Rustactor框架。它支持所有需要的技术,如WebSockets、TLS和HTTP/2.0。Actix-web支持不同的处理程序和资源,但在演示应用程序中仅使用了两个主要路由:

  • **/ws**:主要的WebSocket通信资源
  • **/**:主要的应用程序处理程序,路由到静态部署的前端应用程序

默认情况下,actix-web生成与本地计算机上可用的CPU内核数相同的工作线程。这意味着可能的应用程序状态必须在所有线程之间安全共享,但是这对于Rust无畏的并发模式来说真的不是问题。尽管如此,总体后端应该是无状态的,因为它可以在云基础架构(如Kubernetes)环境中并行部署多个副本。因此,应用程序状态应该在后端之外,例如在单独的Docker容器实例中。我决定使用 PostgreSQL 作为主要的数据存储。为什么?因为令人惊叹的 Diesel 项目 已经支持 PostgreSQL,并为其提供了安全、可扩展的对象关系映射 (ORM) 和查询构建器。这非常棒,因为 actix-web 已经支持 Diesel。因此,可以使用自定义的 Rust 特定领域语言来创建、读取、更新或删除 (CRUD) 数据库中的会话,如下所示:

使用 Diesel.rs 支持的 UpdateSession 处理程序

为了处理 actix-web 和 Diesel 之间的连接,使用了 r2d2 项目。这意味着我们有一个共享应用程序状态,其中包含多个数据库连接作为单个连接池。这使得整个后端非常容易进行大规模扩展和灵活性。整个服务器实例化过程可以在 此处 找到。

后端测试

后端的 集成测试 通过设置测试实例并连接到已运行的数据库来完成。然后可以使用标准的 WebSocket 客户端(我使用了 tungstenite)将与协议相关的 Cap'n Proto 数据发送到服务器并评估预期的结果。这非常有效!我没有使用 actix-web 的特定测试服务器,因为设置真实服务器并不需要更多的工作。其他部分的后端单元测试和预期一样简单,并且没有产生真正的陷阱。

部署

通过 Docker 镜像可以轻松部署应用程序。

Makefile 命令 make deploy 创建一个名为 webapp 的 Docker 镜像,其中包含静态链接的后端可执行文件、当前的 Config.toml、TLS 证书和前端的静态内容。在 Rust 中构建完全静态链接的可执行文件是通过修改 rust-musl-builder docker 镜像的变体来实现的。使用 make run 可以测试生成的 webapp,它将启动具有启用主机网络的容器。PostgreSQL 容器现在应该并行运行。总体而言,整个部署并不是一个大问题,并且应该足够灵活以适应未来的调整。

总结

作为总结,应用程序的基本依赖栈如下所示:

前端和后端之间唯一共享的组件是 Cap'n Proto 生成的 Rust 源代码,它需要一个本地安装的 Cap'n Proto 编译器。

所以,我们的 Web 应用程序已经准备好(投入生产)了吗?

这是个大问题,我的个人意见是:

对于后端,我倾向于说“是”,因为 Rust 除了 actix-web 还有一个非常成熟的 HTTP 栈 和各种不同的 框架,可以快速构建 API 和后端服务。

在前端方面,由于 WebAssembly 的炒作,也有很多工作正在进行中,但是项目需要与后端相同的成熟度,特别是当涉及到稳定的 API 和测试可能性时。因此,前端的答案是否定的,但我们已经走在了一个非常好的轨道上。

非常感谢你一直阅读到这里。 ❤

译自:https://medium.com/@saschagrunert/a-web-application-completely-in-rust-6f6bdb6c4471

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
一个人玩
先找到想要的,然后出发

评论(0)

添加评论