5分鐘APIG實戰: 使用Rust語言快速構建API能力開放


序言: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應用開發的框架,常用的有:

值得一提的是上週剛發佈的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愉快地玩耍啦。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章