该材料的作者(我们今天将发表其翻译)说,他在软件项目体系结构领域的最新实验是仅使用Rust语言并以最少的模板代码使用量创建一个可运行的Web应用程序。 在本文中,他希望与读者分享他通过开发应用程序并回答Rust是否准备在Web开发的各个领域中使用它的
问题而发现的发现。

项目概况
可以在
GitHub上找到将在此处讨论的项目代码。 应用程序的客户端和服务器部分位于同一存储库中,这样做是为了简化项目的维护。 应该注意的是,Cargo将需要编译具有不同依赖关系的前端和后端应用程序。
在这里,您可以查看正在运行的应用程序。
我们的项目是认证机制的简单演示。 它允许您使用所选的用户名和密码登录(必须相同)。
如果用户名和密码不同,则身份验证将失败。 成功认证后,
JWT令牌(JSON Web令牌)既存储在客户端,又存储在服务器端。 通常不需要在此类应用程序中将令牌存储在服务器上,但是我只是出于演示目的。 例如,这可用于查找已登录的用户数。 可以使用单个
Config.toml文件配置整个应用程序,例如,指定用于访问数据库的凭据或服务器的地址和端口号。 这是我们应用程序中该文件的标准代码。
[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"
应用程序客户端开发
为了开发应用程序的客户端,我决定使用
yew 。 这是受Elm,Angular和React启发的现代Rust框架。 它旨在使用
WebAssembly (Wasm)创建多线程Web应用程序的客户端部分。 当前,该项目正在积极开发中,但没有太多稳定版本。
yew
框架依赖于
cargo-web工具,该工具旨在在Wasm中交叉编译代码。
cargo-web工具是直接的
yew
依赖,可简化Wasm中Rust代码的交叉编译。 通过此工具可以达到以下三个主要的编译Wasm目标:
asmjs-unknown-emscripten
通过Emscripten使用asmjs-unknown-emscripten
wasm32-unknown-emscripten
通过Emscripten使用WebAssemblywasm32-unknown-unknown
将WebAssembly与本机Rust后端一起用于WebAssembly
网络组装我决定使用最后一个选项,该选项要求使用Rust编译器的“夜间”程序集,但最大程度地展示了Rust的本机Wasm功能。
如果我们谈论WebAssembly,那么今天谈论Rust是最热门的话题。 正在Wasm中交叉编译Rust并将其集成到Node.js生态系统中(使用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"! If you need to serve any extra files put them in the 'static' directory in the root of your crate; they will be served alongside your application. You can also put a 'static' directory in your 'src' directory. Your application is being served at '/app.js'. It will be automatically rebuilt if you make any changes in your code. You can access the web server at `http://0.0.0.0:8000`.
yew
框架具有一些非常有趣的功能。 其中包括对可重用组件体系结构的支持。 此功能将我的应用程序细分为三个主要组件:
RootComponent 。 该组件直接安装在网站的
<body>
标签上。 他决定接下来应加载哪个子组件。 如果在页面的第一个入口找到了JWT令牌,它将尝试通过与应用程序的服务器部分联系来更新此令牌。 如果失败,则
LoginComponent
到
LoginComponent
组件的转换。
LoginComponent 。 此组件是
RootComponent
组件的后代;它包含带有用于输入凭据的字段的表单。 此外,它与应用程序的后端进行交互,以基于用户名和密码的验证来组织简单的身份验证方案,并且,如果身份验证成功,则将JWT保存在cookie中。 另外,如果用户能够进行身份验证,则他将过渡到
ContentComponent
组件。
LoginComponent组件的外观内容组件 该组件是
RootComponent
组件的另一个后代。 它包含在应用程序主页上显示的内容(目前只是标题和退出系统的按钮)。 可以通过
RootComponent
(如果应用程序在启动时设法找到了有效的会话令牌)来访问它,也可以通过
LoginComponent
(在成功通过身份验证的情况下)获得访问权限。 当用户单击注销按钮时,此组件与后端交换数据。
ContentComponent组件路由器组件 该组件存储包含内容的组件之间的所有可能路由。 此外,它还包含应用程序
loading
和
error
的初始状态。 它直接连接到
RootComponent
。
现在将讨论的以下
yew
关键概念之一是服务。 它们使您可以在不同的组件中重用相同的逻辑。 假设这些可以是记录界面或支持
cookie使用的工具。 服务不存储某些全局状态;它们是在组件初始化时创建的。 除了服务,
yew
支持代理的概念。 它们可用于组织各个组件之间的数据共享,以维护应用程序的一般状态,例如负责路由的代理所需的状态。 为了组织覆盖所有组件的应用程序的路由系统,我们在此处实现了
我们自己的代理和路由服务 。
yew
没有标准的路由器,但是在框架存储库中,您可以找到
一个支持各种URL操作的路由器实现
示例 。
我很高兴地注意到
yew
使用
Web Workers API在各种线程上运行代理,并使用附加到该线程的本地调度程序来解决并行任务。 这样就可以在Rust上开发具有高度多线程的浏览器应用程序。
每个组件都实现了自己的
Renderable特性 ,这使我们可以使用
html!{} Macro将HTML代码直接包含在Rust源代码中。
这是一个很棒的功能,当然,其正确使用由编译器控制。 这是
LoginComponent
组件中的
Renderable
实现
Renderable
。
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> } } }
前端和后端之间的连接基于每个客户端使用的
WebSocket连接。 WebSocket技术的优势在于它适合传输二进制消息,并且服务器可以在必要时将推送通知发送给客户端。
yew
有一个标准的WebSocket服务,但我决定创建
自己的版本用于演示,主要是因为直接在服务内部对连接进行了“惰性”初始化。 如果要在组件初始化期间创建WebSocket服务,则必须监视许多连接。
协议Cap'n协议我决定使用
Cap'n Proto协议(而不是诸如
JSON ,
MessagePack或
CBOR之类的协议)作为传输应用程序数据的层,这是出于速度和紧凑性的考虑。 值得注意的是,我没有使用Cap'n Proto中可用的
RPC协议接口,因为它的Rust实现不是为WebAssembly编译的(由于
toxio-rs Unix依赖性)。 正确类型的请求和响应的选择有些复杂,但是可以使用
结构良好的API解决此问题。 这是该应用程序的Cap'n Proto协议声明。
@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; } } }
您可以看到这里有两个不同版本的登录请求。
一个用于
LoginComponent
(在这里获取令牌,使用名称和密码),另一个用于
RootComponent
(用于更新现有令牌)。 协议工作所需的一切都打包在
协议服务中,这使得在前端的各个部分中重用相应的功能很方便。
UIkit-一个紧凑的模块化前端框架,用于开发快速而强大的Web界面该应用程序客户端部分的用户界面基于
UIkit框架,其3.0.0版将在不久的将来发布。 专门准备的
build.rs脚本会自动下载所有必需的UIkit依赖项并编译生成的样式表。 这意味着您可以将自己的样式添加到单个
style.scss文件中,该文件可以应用于整个应用程序。 非常方便。
front测试前端
我认为测试我们的解决方案存在一些问题。 事实是,测试单个服务非常简单,但是
yew
没有为开发人员提供测试组件和代理的便捷方法。 现在,在纯Rust框架中,前端的集成和端到端测试不可用。 在这里,您可以使用
Cypress或
Protractor之类的项目,但采用这种方法,您将不得不在项目中包含很多模板JavaScript / TypeScript代码,因此我决定放弃此类测试的实现。
顺便说一下,这是一个新项目的想法:一个用Rust编写的端到端测试框架。
应用服务器端开发
为了实现应用程序的服务器端,我选择了
actix-web框架。 这是一个紧凑,实用且快速的基于Rust的
actor模型框架。 它支持所有必要的技术,例如WebSockets,TLS和
HTTP / 2.0 。 该框架支持各种处理程序和资源,但是在我们的应用程序中,仅使用了两条主要路线:
/ws
是WebSocket通信的主要资源。/
-可以访问静态前端应用程序的主处理程序。
默认情况下,
actix-web
以与本地计算机上可用处理器核心数量相对应的数量启动工作流。 这意味着如果应用程序具有状态,则必须在所有线程之间安全地共享它,但是由于可靠的Rust并行计算模板,这不是问题。 尽管如此,后端应该是一个无状态系统,因为它的许多副本可以在云环境(如
Kubernetes )中并行部署。 结果,形成应用程序状态的数据必须与后端分开。 例如,它们可能在
Docker容器的单独实例中。
PostgreSQL DBMS和Diesel项目作为主要数据仓库,我决定使用
PostgreSQL DBMS。 怎么了 这一选择确定了一个出色的
Diesel项目的存在,该项目已经支持PostgreSQL,并为其提供了安全和可扩展的ORM系统以及查询构建工具。 所有这些完全符合我们项目的需求,因为
actix-web
已经支持Diesel。 结果,在这里,要使用有关数据库中会话的信息来执行CRUD操作,可以使用一种考虑了Rust的特殊性的特殊语言。 这是基于Diesel.rs的
actix-web
的示例
UpdateSession
处理程序。
impl Handler<UpdateSession> for DatabaseExecutor { type Result = Result<Session, Error>; fn handle(&mut self, msg: UpdateSession, _: &mut Self::Context) -> Self::Result {
r2d2项目用于在
actix-web
和Diesel之间建立连接。 这意味着我们(除了具有工作流程的应用程序之外)具有应用程序的共享状态,该状态将许多数据库连接作为一个连接池支持。 这大大简化了后端的扩展,使该解决方案变得灵活。
在这里,您可以找到负责创建服务器实例的代码。
▍测试后端
通过启动测试服务器实例并连接到已经运行的数据库,可以执行我们项目中的后端
集成测试 。 然后,您可以使用标准的WebSocket客户端(我使用
钨合金 )将使用Cap'n Proto协议生成的数据发送到服务器,并将结果与预期结果进行比较。 该测试设置已被证明是出色的。 我没有使用
actix-web专用
测试服务器 ,因为配置和运行真实服务器不需要做很多工作。 预期,后端单元测试是一项相当简单的任务;进行此类测试不会引起任何问题。
项目部署
该应用程序非常容易使用Docker映像进行部署。
码头工人使用
make deploy
您可以创建一个名为
webapp
的映像,其中包含静态链接的后端可执行文件,当前的
Config.toml
文件,TLS证书和静态前端内容。 使用
rust-musl-builder Docker映像的修改版本,实现Rust中完全静态链接的可执行程序的汇编。 可以使用
make run
命令测试完成的Web应用程序,该命令将启动启用网络的容器。 PostgreSQL容器必须与应用程序容器并行运行,以确保系统正常工作。 通常,部署我们的系统的过程非常简单,此外,由于这里使用的技术,我们可以谈论其足够的灵活性,简化其对开发中的应用程序的适应性。
项目开发中使用的技术
这是应用程序依赖关系图。
用于在Rust中开发Web应用程序的技术前端和后端使用的唯一组件是Cap'n Proto的Rust版本,该版本需要在本地安装的Cap'n Proto编译器才能创建。
结果。 Rust已准备好进行Web生产吗?
这是一个大问题。 这是我能回答的。 从服务器的角度来看,我倾向于回答“是”,因为Rust生态系统除了
actix-web
,还具有非常成熟的
HTTP堆栈以及用于快速开发服务器API和服务的许多不同
框架 。
如果我们谈论前端,那么,由于对WebAssembly的普遍关注,现在正在进行许多工作。 但是,在此区域中创建的项目必须达到与服务器项目相同的成熟度。 对于API稳定性和测试功能尤其如此。 因此,现在我对在前端使用Rust表示“不”,但我不得不指出它正在朝着正确的方向发展。
亲爱的读者们! 您是否在Web开发中使用Rust?