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

PaaS小魔仙發表於2018-08-24

序言: 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 的下一代瀏覽器引擎專案: ,到目前為止( 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 的小哥哥小姐姐,可以參考

進行 HTTP Web 後端應用開發

Rust 生態中進行 HTTP Web 後端應用開發目前主要依賴兩個基礎庫: 以及 ,其中 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 上。

登入公有云 ,並建立彈性雲伺服器作為我們的後端應用 伺服器

實戰中使用的系統版本為 Ubuntu 16.04 ,如果選擇不同的系統需要根據情況調整命令。

apt update && apt install build-essential

# 安裝 Rust 工具鏈
curl -sSf | sh

這一步結束後,我們就可以開始編寫我們的應用服務了。

應用

這次分享我們來構建一個 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”

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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章