[译]Rust 开发完整的 Web 应用程序

栏目: 编程语言 · Rust · 发布时间: 6年前

内容简介:我在软件架构方面最新的尝试,是在 Rust 中使用尽可能少的模板文件来搭建一个真实的 web 应用程序。在这篇文章中我将和大家分享我的发现,来回答实际上有多少网站在使用 Rust 这个问题。这篇文章提到的项目请注意,目前这个项目正在快速迭代中可以在

我在软件架构方面最新的尝试,是在 Rust 中使用尽可能少的模板文件来搭建一个真实的 web 应用程序。在这篇文章中我将和大家分享我的发现,来回答实际上有多少网站在使用 Rust 这个问题。

这篇文章提到的项目 都可以在 GitHub 上找到 。为了提高项目的可维护性,我将前端(客户端)和后端(服务端)放在了一个仓库中。这就需要 Cargo 为整个项目去分别编译有着不同依赖关系的前端和后端二进制文件。

请注意,目前这个项目正在快速迭代中可以在 rev1 这个分支上找到所有相关的代码。你可以点击此处阅读这个本系列博客的第二部分。

这个应用是一个简单的身份验证示范,它允许你选一个用户名和密码(必须相同)来登录,当它们不同就会失败。验证成功后,将一个JSON Web Token (JWT) 同时保存在客户端和服务端。通常服务端不需要存储 token,但是出于演示的目的,我们还是存储了。举个栗子,这个 token 可以被用来追踪实际登录的用户数量。整个项目可以通过一个 Config.toml 文件来配置,比如去设置数据库连接凭证,或者服务器的 host 和 port。

[server]
ip = "127.0.0.1"
port = "30080"
tls = false

[log]
actix_web = "debug"
webapp = "trace"

[postgres]
host = "127.0.0.1"
username = "username"
password = "password"
database = "database"
复制代码

webapp 默认的 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 开发完整的 Web 应用程序

我决定使用最后一个,需要一个 nightly Rust 编译器,事实上,演示 Rust 原生的 Wasm 可能是最好的。

WebAssembly 目前是 Rust 最热门 :fire: 的话题之一。关于编译 Rust 成为 Wasm 并将其集成到 nodejs(npm 打包),世界上有很多开发者为这项技术努力着。我决定采用直接的方式,不引入任何 JavaScript 依赖。

当启动 web 应用程序的前端部分的时候(在我的项目中用 make frontend ), cargo-web 将应用编译成 Wasm,并且将其与静态资源打包到一起。然后 cargo-web 启动一个本地 web 服务器,方便应用程序进行开发。

> make frontend
   Compiling webapp v0.3.0 (file:///home/sascha/webapp.rs)
    Finished release [optimized] target(s) in 11.86s
    Garbage collecting "app.wasm"...
    Processing "app.wasm"...
    Finished processing of "app.wasm"!

如果需要对任何其他文件启动服务,将其放入项目根目录下的 'static' 目录;然后它们将和你的应用程序一起提供给用户。
同样可以把静态资源目录放到 ‘src’ 目录中。
你的应用通过 '/app.js' 启动,如果有任何代码上的变动,都会触发自动重建。
你可以通过 `http://0.0.0.0:8000` 访问 web 服务器
复制代码

Yew 有些很好用的功能,就像可复用的组件架构,可以很轻松的将我的应用程序分为三个主要的组件:

  • 根组件 : 直接挂载在网页的 <body> 标签,决定接下来加载哪一个子组件。如果在进入页面的时候发现了 JWT,那么将尝试和后端通信来更新这个 token,如果更新失败,则路由到 登录组件
  • 登录组件 : 根组件 的一个子组件包含登录表单字段。它同样和后端进行基本的用户名和密码的身份验证,并在成功后将 JWT 保存到 cookie 中。成功验证身份后路由到 内容组件
[译]Rust 开发完整的 Web 应用程序
登录组件
  • 内容组件 : 根组件的 的另一个子组件,包括一个主页面内容(目前只有一个头部和一个登出按钮)。它可以通过 根组件 访问(如果有效的 session token 已经可用)或者通过 登录组件 (成功认证)访问。当用户按下登出按钮后,这个组件将会和后端进行通信。
[译]Rust 开发完整的 Web 应用程序
内容组件
  • 路由组件 : 保存包含内容的组件之间的所有可能路由。同样包含应用的一个初始的 “loading” 状态和一个 “error” 状态,并直接附加到 根组件 上。

服务是 yew 的下一个关键概念之一。它允许组件间重用相同的逻辑,比如日志记录或者 cookie 处理 。在组件的服务是无状态的,并且服务会在组件初始化的时候被创建。除了服务, yew 还包含了代理(Agent)的概念。代理可以用来在组件间共享数据,提供一个全局的应用状态,就像路由代理所需要的那样。为了在所有的组件之间完成示例程序的路由,实现了一套 自定义的路由代理和服务 。Yew 实际上没有独立的路由, 但他们的示例 提供了一个支持所有类型 URL 修改的参考实现。

太让人惊讶了,yew 使用Web Workers API 在独立的线程中生成代理,并使用附加到线程的本地的任务调度程序来执行并发任务。这使得使用 Rust 在浏览器中编写高并发应用成为可能。

每个组件都实现了 自己的 `Renderable` 特性 ,这让我们可以直接通过 [html!{}](https://github.com/DenisKolodin/yew#jsx-like-templates-with-html-macro) 宏在 rust 源码中包含 HTML。这非常棒,并且确保了使用编辑器内置的 borrow checker 进行检查!

impl Renderable<LoginComponent> for LoginComponent {
    fn view(&self) -> Html<Self> {
        html! {
            <div class="uk-card uk-card-default uk-card-body uk-width-1-3@s uk-position-center",>
                <form onsubmit="return false",>
                    <fieldset class="uk-fieldset",>
                        <legend class="uk-legend",>{"Authentication"}</legend>
                        <div class="uk-margin",>
                            <input class="uk-input",
                                   placeholder="Username",
                                   value=&self.username,
                                   oninput=|e| Message::UpdateUsername(e.value), />
                        </div>
                        <div class="uk-margin",>
                            <input class="uk-input",
                                   type="password",
                                   placeholder="Password",
                                   value=&self.password,
                                   oninput=|e| Message::UpdatePassword(e.value), />
                        </div>
                        <button class="uk-button uk-button-default",
                                type="submit",
                                disabled=self.button_disabled,
                                onclick=|_| Message::LoginRequest,>{"Login"}</button>
                        <span class="uk-margin-small-left uk-text-warning uk-text-right",>
                            {&self.error}
                        </span>
                    </fieldset>
                </form>
            </div>
        }
    }
}
复制代码

登录组件 Renderable 的实现

每个客户端从前端到后端的通信(反之亦然)通过WebSocket 连接来实现。WebSocket 的好处是可以使用二进制信息,并且如果需要的话,服务端同时可以向客户端推送通知。Yew 已经发行了一个 WebSocket 服务,但我还是要为示例程序 创建一个自定义的版本 ,主要是因为要在服务中的延迟初始化连接。如果在组件初始化的时候创建 WebSocket 服务,那么我们就得去追踪多个套接字连接。

[译]Rust 开发完整的 Web 应用程序

出于速度和紧凑的考量。我决定使用一个二进制协议 ——Cap’n Proto,作为应用数据通信层(而不是JSON、MessagePack 或者CBOR这些)。值得一提的是,我没有使用 Cap’n Proto 的RPC 接口协议,因为其 Rust 实现不能编译成 WebAssembly(由于 tokio-rs ’ unix 依赖项)。这使得正确区分请求和响应类型稍有困难,但是 结构清晰的 API 可以解决这个问题:

@0x998efb67a0d7453f;

struct Request {
    union {
        login :union {
            credentials :group {
                username @0 :Text;
                password @1 :Text;
            }
            token @2 :Text;
        }
        logout @3 :Text; # The session token
    }
}

struct Response {
    union {
        login :union {
            token @0 :Text;
            error @1 :Text;
        }
        logout: union {
            success @2 :Void;
            error @3 :Text;
        }
    }
}
复制代码

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

你可以看到我们这里有两个不同的登录请求变体:一个是 登录组件 (用户名和密码的凭证请求),另一个是 根组件 (已经存在的 token 刷新请求)。所有需要的协议实现都包含在 协议服务 中,这使得它在整个前端中可以被轻松复用。

[译]Rust 开发完整的 Web 应用程序

UIkit - 用于开发快速且功能强大的 Web 界面的轻量级模块化前端框架

前端的用户界面由UIkit 提供支持,其 3.0.0 版将在不久的将来发布。自定义的 build.rs 脚本会自动下载 UIkit 所需要的全部依赖项并编译整个样式表。这就意味着我们可以在 单独的一个 style.scss 文件 中插入自定义的样式,然后在应用程序中使用。安排!(PS: 原文是 Neat!

前端测试

在我的看来,测试可能会存在一些小问题。测试独立的服务很容易,但是 yew 还没有提供一个很优雅的方式去测试单个组件或者代理。目前在 Rust 内部也不可能对前端进行整合以及端到端测试。或许可以使用Cypress 或者Protractor 这类项目,但是这会引入太多的 JavaScript/TypeScript 样板文件,所以我跳过了这个选项。

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

后端 —— 服务端

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

**/ws**
**/**

默认情况下,actix-web 会生成与本地计算机逻辑 CPU 数量一样多的 works(译者注: 翻译参考了 Actix中文文档中服务器一节的多线程部分 )。这就意味着必须在线程之间安全的共享可能的应用程序状态,但这对于 Rust 无所畏惧的并发模式来说完全不是问题。尽管如此,整个后端应该是无状态的,因为可能会在云端(比如Kubernetes)上并行部署多个副本。所以应用程序状态应该在单个 Docker 容器实例中的后端服务之外。

[译]Rust 开发完整的 Web 应用程序

我决定使用PostgreSQL 作为主要的数据存储。为什么呢?因为令人敬畏的Diesel 项目 已经支持 PostgreSQL,并且为它提供了一个安全、可拓展的对象关系映射(ORM)和查询构建器(query builder)。这很棒,因为 actix-web 已经支持了 Diesel。这样的话,就可以自定义惯用的 Rust 域特定语言来创建、读取、更新或者删除(CRUD)数据库中的会话,如下所示:

impl Handler<UpdateSession> for DatabaseExecutor {
    type Result = Result<Session, Error>;

    fn handle(&mut self, msg: UpdateSession, _: &mut Self::Context) -> Self::Result {
        // Update the session
        debug!("Updating session: {}", msg.old_id);
        update(sessions.filter(id.eq(&msg.old_id)))
            .set(id.eq(&msg.new_id))
            .get_result::<Session>(&self.0.get()?)
            .map_err(|_| ServerError::UpdateToken.into())
    }
}
复制代码

由Diesel.rs 提供的 actix-web 的 UpdateSession 处理程序

至于 actix-web 和 Diesel 之间的连接的处理,使用 r2d2 项目。这就意味着我们(应用程序和它的 works)具有共享的应用程序状态,该状态将多个连接保存到数据库作为单个连接池。这使得整个后端非常灵活,很容易大规模拓展。 这里 可以找到整个服务器示例。

后端测试

后端的 集成测试 通过设置一个测试用例并连接到已经运行的数据库来完成。然后可以使用标准的 WebSocket 客户端(我使用 tungstenite )将与协议相关的 Cap'n Proto 数据发送到服务器并验证预期结果。这很好用!我没有用 actix-web 特定的测试服务器 ,因为设置一个真正的服务器并费不了多少事儿。后端其他部分的单元测试工作像预期一样简单,没有任何棘手的陷阱。

部署

使用 Docker 镜像可以很轻松地部署应用程序。

[译]Rust 开发完整的 Web 应用程序

Makefile 命令 make deploy 创建一个名为 webapp 的 Docker 镜像,其中包含静态链接(staticlly linked)的后端可执行文件、当前的 Config.toml 、TLS 证书和前端的静态资源。在 Rust 中构建一个完全的静态链接的可执行文件是通过修改的rust-musl-builder 镜像变体实现的。生成的 webapp 可以使用 make run 进行测试,这个命令可以启动容器和主机网络。PostgreSQL 容器现在应该并行运行。总的来说,整体部署不应该是这个工程的重要部分,应该足够灵活来适应将来的变动。

总结

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

[译]Rust 开发完整的 Web 应用程序

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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

认知与设计

认知与设计

Jeff Johnson / 张一宁、王军锋 / 人民邮电出版社 / 2014-8-1 / CNY 69.00

本书语言清晰明了,将设计准则与其核心的认知学和感知科学高度统一起来,使得设计准则更容易地在具体环境中得到应用。涵盖了交互计算机系统设计的方方面面,为交互系统设计提供了支持工程方法。不仅如此,这也是一本人类行为原理的入门书。一起来看看 《认知与设计》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具