序言: Rust 语言简介
参与过 C/C++ 大型项目的同学可能都经历过因为 Null Pointer 、 Memory Leak 等问题 “ 被 ” 加班了不知道多少个晚上。别沮丧,你不是一个人, Mozilla Firefox 的开发者们同样经历过这个问题。浏览器可以说是我们日常使用最为频繁的软件了,目前主流的浏览器主要 有 Google Chrome 、 Internet Explorer 、 Mozilla Firefox 。 为了提升用户体验, Mozilla 就已经启动了多线程渲染的计划。然而,面对大型的 C/C++ 工程, Mozilla 的开发者们也坚持不住了。此时, Rust 进入了开发者的眼中,与 C/C++ ABI 兼容、多编程范式支持、无 GC 及独特的所有权系统,使得 Mozilla 与 Rust 语言一拍即合,并迅速启动了 Mozilla 的下一代浏览器引擎项目: servo ,到目前为止( 2018 年 8 月), servo 已经成为了除 Rust 编译器自身外,社区中最大的 Rust 项目。 servo 目前已经部 分应用在 Firefox 57 之后的版本中。
Rust 语言的设计目标是安全、高效、并发以及实用性。 Rust 从一定程度上解决了 C++ 的以 下痛点:
1. 容器 / 数组越界访问;
2. 动态内存分配的泄露与 double free 问题;
3. 难以对依赖进行管理;
其中前两点在 C/C++ 项目中是最容易引发 Bug 以及安全问题的原因,依靠人来对这些问题进行检查往往不是最佳的解决方案。 Rust 通过其独特的所有权系统, 简化 这个所研究的对象,使得一些隐晦的问题在编译期间便暴露出来。任何事情都是有两面性的,由于严格的编译期检查以及工程实现上的取舍, Rust 在一定程度上牺牲了编译速度以及灵活性,对 “ 灵活性 ” 的舍弃并不代表 Rust 语言的表现力下降,只是我们在编写 Rust 程序时,可能需要 改变一下以往的思路。
在 Rust 圈子中,有一句调侃: “C++ 是调试的时候想撞墙,而 Rust 是编译的时候想撞墙 ” 。
接下来我们将通过一个简单的例子来建立 Rust 中所有权系统的一个基本印象。
核心概念:所有权系统
Rust 的所有权系统包括三个核心概念:所有权、借用以及生命周期。我们首先来通过一个 简单的例子来建立对所有权以及生命周期的直观概念。
#[derive(Debug)]
struct Foo;
fn main() {
let foo = Foo; // Note: Foo not implement Copy trait
let bar = foo;
println!("{:?}", bar);
// println!("{:?}", foo);
}
首先创建了一个 Foo 类型的变量 foo ,然后我们执行 let bar = foo; ,然后我们尝试 输出这两个变量的值,如果我们将第 9 行的注释去掉,程序将无法通过编译,这是因为在 Rust 中,对于没有实现 Copy trait 的类型,如果我们将一个绑定赋给另一个绑定,默认 使用的是 move 语义,也即对于任意给定的资源,当且仅当有一个变量绑定与之对应。
想要进一步学习 Rust 的小哥哥小姐姐,可以参考 Rust Learning
使用 Rust 进行 HTTP Web 后端应用开发
在 Rust 生态中进行 HTTP Web 后端应用开发目前主要依赖两个基础库: http 以及 hyper ,其中 http 提供 HTTP 标准相关的基础类型,如 Request<T> 、 Response<T> 以及 StatusCode 和常用的 Header 等; hyper 的定位是一个高效、准确的 HTTP 底层库,它封装了 HTTP 的报文解析、报文编码处理、连接控制等内容,对于用户而言 只需要实现一个类似于 Fn(Request) -> Response 的映射,就可以完 HTTP Web 服务端的开 发。
基于 http 以及 hyper ,社区中还有很多用于 Web 应用开发的框架,常用的有:
• rocket
• iron
值得一提的是上周刚发布的 tower-web ,因为这是官方 net 团队 2018 年工作计划的一部分, 这个库在未来会为 Rust 生态提供一个灵活、高效、易于使用的 Web 开发框架。那么事不宜迟, 我们通过实战演练来一睹为快。
在本月月底, tower-web 将会集成到 wrap 项目中,成为 wrap 框架的一部分,开发的重心将会转移到 wrap 上。
实战演练
登录公有云 ,并创建弹性云服务器作为我们的后端应用 服务器
实战中使用的系统版本为 Ubuntu 16.04 ,如果选择不同的系统需要根据情况调整命令。
安装相关的工具链
apt update && apt install build-essential
# 安装 Rust 工具链
curl https://sh.rustup.rs -sSf | sh
这一步结束后,我们就可以开始编写我们的应用服务了。
编写后端 Web 应用
这次分享我们来构建一个 RESTful 中文分词 API ,首先我们来创建一个 Rust 工程 cargo new --bin chinese_segmentation
接下来在 Cargo.toml 中添加相关依赖
[dependencies]
tower-web = "0.2"
# Jieba Chinese Work Segmentation
jieba-rs = "0.2"
# logging utils
log = "0.4.0"
env_logger = "0.5.12"
# Serializing responses, deserializing requests
serde = "1.0.70"
然后是我们的 main.rs ,与其他语言一样,在文件开始的部分引入外部依赖以及相关声明:
extern crate jieba_rs;
#[macro_use]
extern crate tower_web;
#[macro_use]
extern crate log;
extern crate env_logger;
use std::iter::FromIterator;
use std::collections::HashSet;
use jieba_rs::Jieba;
use tower_web::ServiceBuilder;
接下来我们定义我们的服务资源 ChineseTokenizer :
#[derive(Debug)]
struct ChineseTokenizer {
inner: Jieba,
}
impl ChineseTokenizer {
pub fn new() -> ChineseTokenizer {
ChineseTokenizer { inner: Jieba::new() }
}
// 对传入的字符串进行分词,并返回一个字符串向量
pub fn cut(&self, text: &String) -> Vec<String> {
let words = self.inner.cut(&text, true)
.into_iter()
.map(|word| word.to_owned())
.collect::<HashSet<String>>();
let mut words = Vec::from_iter(words.into_iter());
// 由于使用 HashSet 进行去重会引入不确定性,
// 因此对结果进行重排,使输出的结果有序。
words.sort();
words
}
}
定义了我们的服务资源后,我们来定义输入 Web API 的输入输出类型:
#[derive(Debug, Extract)]
struct TokenizeRequest {
text: String
}
#[derive(Debug, Response)]
#[web(status = "200")] // 当 handler 返回 Ok(xx) 时,返回 200 状态码
struct TokenizeResponse {
words: Vec<String>,
}
到目前为止,我们已经有了我们的服务资源,输入输出类型,接下来就到我们的重头戏了, Web 部分的实现,别担心,因为真的很简单。
impl_web! {
impl ChineseTokenizer {
#[post("/tokenize")]
#[content_type("application/json")]
fn tokenize(&self, body: TokenizeRequest) -> Reqult<TokenizeResponse, ()> {
Ok(TokenizeResponse {
words: self.cut(&body.text),
})
}
}
}
最后是我们的 main 函数:
fn main() {
// 初始化 Logger
env_logger::init();
let addr = "0.0.0.0:8081".parse().expect("invalid address");
info!("listening on http://{}", addr);
ServiceBuilder::new()
.resource(ChineseTokenizer::new()) // 注册我们的服务资源
.run(&addr) // 让我们的服务跑起来
.unwrap();
}
现在,我们通过命令 RUST_LOG=chinese_segmentation=info cargo run --release 来检验 一下我们的成果了。服务在本地跑起来之后,我们可以通过命令 curl -H "Content-Type: application/json" -X POST -d '{"text":" 中间件小哥 "}' <url> 来测试一下我们的接口。
本地测试通过之后,就需要着手开始部署了,我们检查一下弹性云服务器的安全组的入方向 是否放开 8081 端口。
API 部署
API 网关集成了监控、流控、负载均衡等一系列功能,为开发者提供高性能、高可用的 API 托管服务,在本次实践中,我们将我们的 API 部署在 API 网关中。
登录公有云API网关服务 ,选择 “ 新建 API”
1. 填写 API 的基本信息
在本次实验中,选择无认证。
1. 定义 API 请求
请求路径填为 /segment ,方法为 POST
1. 定义后端服务
请求方式设置为 POST ,在 VPC 通道这一项中,我们需要 新建 VPC 通道。端口设置为 8081 , 并将其与弹性云服务器关联。
创建完 VPC 通道后,回到 API 创建页面,填入相关信息:
网关创建完成后,我们需要回到我们的弹性云服务器,将我们的后端服务器先跑起来:
RUST_LOG=chinese_segmentation=info nohup ./target/release/chinese_segmentation 2>&1 ~/api.log &
作为示例,这里使用 nohup 命令来跑我们的服务。但在生产环境中,建议使用 systemd 等工具来跑服务。
服务在云服务器运行起来之后,将 API 发布至 RELEASE 环境中。
然后我们就可以和我们的 API 愉快地玩耍啦。
上述公有云以华为云为具体案例,有兴趣可以上去体验一下~ https://www.huaweicloud.com/product/apig.html
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/31543630/viewspace-2212716/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/31543630/viewspace-2212716/