利用Rust構建一個REST API服務

關注公衆號:香菜粉絲 瞭解更多精彩內容編程

Rust 是一個擁有不少忠實粉絲的編程語言,仍是很難找到一些用它構建的項目,並且掌握起來甚至有點難度。json

想要開始學習一門編程語言最好的方式就是利用它去作一些有趣的項目,或者天天都使用它,若是你的公司在構建或者已經在使用微服務了,能夠嘗試一下使用rust把它重寫一遍。api

第一次使用Rust的時候,和學習其餘語言同樣,須要瞭解基礎知識,在你瞭解基礎語法和一些經常使用的概念後,就能夠思考如何使用Rust進行異步編程了,不少編程語言都在運行時內置了異步的運行模式,好比在一些發送請求或者等待後臺運行的場景裏面會使用到異步編程。瀏覽器

補充一點,Tokio是一個事件驅動的非阻塞I / O平臺,用於使用Rust編程語言編寫異步應用程序,頗有可能你下一家公司就在用它,因爲你會選擇一些內置了Tokio的庫來編寫異步程序,所以接下來,咱們會使用wrap來實現一個異步API平臺。服務器

建立項目

跟着下面的教程,你須要裝下面列出的庫:數據結構

  • Wrap 用來建立API服務
  • Tokio 運行異步服務器
  • Serde 序列化輸入的JSON
  • parking_lot 爲存儲器提供讀寫鎖

首先,使用Cargo創一個新項目app

cargo new neat-api --bin框架

將上面須要安裝的依賴庫添加到Cargo.toml文件裏curl

…
[dependencies]
warp = "0.2"
parking_lot = "0.10.0"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "0.2", features = ["macros"] }

爲了第一次運行測試,在main.rs建立一個「Hello World」 例子異步

use wrap:: Filter;

#[tokio::main]
async fn main() {
  // GET /hello/warp => 200 OK with body "Hello, warp!"
  let hello = wrap::path!("hello" / String)
  		.map(|name| format!("Hello, {}", name));
  
  warp::serve(hello)
  		.run(([127,0,0,1], 3030))
  		.await;
}

Filters 是解析請求和匹配路由的方法,使用cargo run 來啓動服務,在瀏覽器中輸入http://localhost:3030/hello/WHATEVER, warp將經過Filiters發送請求,而後執行觸發請求。

let hello=...,咱們建立了一個新的路由, 意味着全部經過/hello的請求都由該方法處理,所以,它將返回Hello, WHATEVER。

若是咱們在瀏覽器訪問http://localhost:3030/hello/new/WHATEVER,程序將返回404,因咱們沒有定義/hello/new + String 相關的過濾器。

建立API

接下來,咱們建立一個真正的API來演示上面介紹的概念,用API實現一個購物車列表是很好的例子,咱們能夠在列表裏添加條目,更新或者刪除條目,還能夠看到整個列表,所以,咱們利用GET,DELETE,PUT,POSTHTTP方法實現四個不一樣的路由。

建立本地存儲

除了路由,還須要將狀態存儲在文件或局部變量中,在異步環境中,咱們須要確保在訪問存儲器的同一時間只有一個狀態,所以,線程之間不能存在不一致的狀況,在Rust中,利用Arc可讓編譯器知道什麼時候該刪除值,什麼時候控制讀寫鎖(RwLock),這樣,不會有兩個參數在不一樣的線程上操做同一塊內存。

以下是實現方法:

use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::Arc;

type Items = HashMap<String, i32>;

#[derive(Debug, Deserialize, Serialize, Clone)]
struct Item {
    name: String,
    quantity: i32,
}

#[derive(Clone)]
struct Store {
  grocery_list: Arc<RwLock<Items>>
}

impl Store {
    fn new() -> Self {
        Store {
            grocery_list: Arc::new(RwLock::new(HashMap::new())),
        }
    }
}

Posting一個條目到列表裏

如今咱們添加第一個路由,添加條目到列表裏,將POST參數請求添加進路由地址中,咱們的應用須要返回一個HTTPcode讓請求者知道他們是否請求成功,wrap經過HTTP庫提供了一個基礎形式,這些咱們也要添加進去。

use warp::{http, Filter};

POST方法的請求方法以下所示

async fn add_grocery_list_item(
		item: Item,
  	store: Store
  	) -> Result<impl warp::Reply, warp::Rejection> {
      store.grocery_list.write().insert(item.name, item.quantity);
      
      Ok(warp::reply::with_status(
        "Added item to the grocery list",
        http::StatusCode::CREATED,
      ))
}

warp框架提供了一個reply with status的選項,所以咱們能夠添加文本以及標準的HTTP狀態碼,以便讓請求者知道是否請求成功或者須要再次請求一次。

如今增長新的路由地址,並調用上面寫好的方法。因爲你能夠爲這個方法調用JSON,須要新建一個json_body功能函數將Item從請求的body中提取出來。

額外咱們須要將存儲方法經過克隆傳遞給每個參數,建立一個warp filter。

fn json_body() -> impl Filter<Extract = (Item,), Error = warp::Rejection> + Clone{
	    // When accepting a body, we want a JSON body
    	// (and to reject huge payloads)...
		warp::body::content_length_limit(1024*16).and(warp::body::json())
}

#[tokio::main]
async fn main() {
  let store = Store::new();
  let store_filter = warp::any().map(move || store.clone());
  let add_items = warp::post()
  		.and(warp::path("v1"))
  		.and(warp::path("groceries"))
  		.and(warp::path.end())
  		.and(json_body())
  		.and(store_filter.clone())
  		.and_then(add_grocery_list_item);
  
  warp::serve(add_items)
  		.run(([127,0,0,1], 3030))
  		.await;
}

接下來經過curl或者Postman經過POST請求測試服務,如今它將是一個能夠獨立處理HTTP請求的服務了,經過

cargo run啓動服務,而後在新的窗口中運行下面的命令。

curl --location --request POST 'localhost:3030/v1/groceries' \
--header 'Content-Type: application/json' \
--header 'Content-Type: text/plain' \
--data-raw '{
    "name": "apple",
    "quantity": 3
}'

獲取列表

如今咱們能夠經過post將條目添加到列表中,可是咱們不能檢索它們,咱們須要爲GET新建另外一個路由,咱們不須要爲這個路由解析Json。

#[tokio::main]
async fn main() {
		let store = Store::new();
  	let store_filter = warp::any().map(move || store.clone());
  	
  	let add_items = warp::post()
  			.and(warp::path("v1"))
  			.and(warp::path("groceries"))
  			.and(warp::path::end())
  			.and(json_body())
  			.and(store_filter.clone())
  			.and_then(add_grocery_list_item);
  	
  	let get_items = warp::get()
  			.and(warp::path("v1"))
  			.and(warp::path("groceries"))
  			.and(warp::path::end())
  			.and(store_filter.clone())
  			.and_then(get_grocery_list);
  	
  	let routes = add_items.or(get_items);
  	
  	warp::serve(routes)
  			.run(([127,0,0,1],3030))
  			.await;
}

當你研究Arc背後的數據結構時,你會體會到到異步的存在,你將須要先對RwLock中的數據先進行.read()而後進行.iter()操做,所以新建一個變量用來返回給請求者,因爲Rust全部權模型的性質,您不能簡單地閱讀並返回底層的雜貨清單。方法以下:

async fn get_grocery_list(
    store: Store
    ) -> Result<impl warp::Reply, warp::Rejection> {
        let mut result = HashMap::new();
        let r = store.grocery_list.read();


        for (key,value) in r.iter() {
            result.insert(key, value);
        }

        Ok(warp::reply::json(
            &result
        ))
}

最後缺乏的兩個方法是UPDATEDELETE。 對於DELETE,能夠直接複製add_grocery_list_item,可是要使用.remove()代替.insert()

一種特殊狀況是update。 Rust HashMap實現也使用.insert(),可是若是鍵不存在,它會更新值而不是建立新條目。

所以,只需重命名該方法,而後爲POST和PUT調用它便可。

對於DELETE方法,只須要傳遞項目的名稱,建立一個新結構,併爲該新類型添加另外一個parse_json()方法。

能夠簡單地重命名add_grocery_list_item方法以將其命名爲update_grocery_list,併爲warp :: post()warp :: put()對其進行調用。 您的完整代碼應以下所示:

use warp::{http, Filter};
use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::Arc;
use serde::{Serialize, Deserialize};

type Items = HashMap<String, i32>;

#[derive(Debug, Deserialize, Serialize, Clone)]
struct Id {
    name: String,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
struct Item {
    name: String,
    quantity: i32,
}

#[derive(Clone)]
struct Store {
  grocery_list: Arc<RwLock<Items>>
}

impl Store {
    fn new() -> Self {
        Store {
            grocery_list: Arc::new(RwLock::new(HashMap::new())),
        }
    }
}

async fn update_grocery_list(
    item: Item,
    store: Store
    ) -> Result<impl warp::Reply, warp::Rejection> {
        store.grocery_list.write().insert(item.name, item.quantity);


        Ok(warp::reply::with_status(
            "Added items to the grocery list",
            http::StatusCode::CREATED,
        ))
}

async fn delete_grocery_list_item(
    id: Id,
    store: Store
    ) -> Result<impl warp::Reply, warp::Rejection> {
        store.grocery_list.write().remove(&id.name);


        Ok(warp::reply::with_status(
            "Removed item from grocery list",
            http::StatusCode::OK,
        ))
}

async fn get_grocery_list(
    store: Store
    ) -> Result<impl warp::Reply, warp::Rejection> {
        let mut result = HashMap::new();
        let r = store.grocery_list.read();


        for (key,value) in r.iter() {
            result.insert(key, value);
        }

        Ok(warp::reply::json(
            &result
        ))
}

fn delete_json() -> impl Filter<Extract = (Id,), Error = warp::Rejection> + Clone {
    // When accepting a body, we want a JSON body
    // (and to reject huge payloads)...
    warp::body::content_length_limit(1024 * 16).and(warp::body::json())
}

fn post_json() -> impl Filter<Extract = (Item,), Error = warp::Rejection> + Clone {
    // When accepting a body, we want a JSON body
    // (and to reject huge payloads)...
    warp::body::content_length_limit(1024 * 16).and(warp::body::json())
}

測試腳本以下:

POST

curl --location --request POST 'localhost:3030/v1/groceries' \
--header 'Content-Type: application/json' \
--header 'Content-Type: text/plain' \
--data-raw '{
    "name": "apple",
    "quantity": 3
}'

UPDATE

curl --location --request PUT 'localhost:3030/v1/groceries' \
--header 'Content-Type: application/json' \
--header 'Content-Type: text/plain' \
--data-raw '{
    "name": "apple",
    "quantity": 5
}'

GET

curl --location --request GET 'localhost:3030/v1/groceries' \
--header 'Content-Type: application/json' \
--header 'Content-Type: text/plain'

DELETE

curl --location --request DELETE 'localhost:3030/v1/groceries' \
--header 'Content-Type: application/json' \
--header 'Content-Type: text/plain' \
--data-raw '{
    "name": "apple"
}'

翻譯自 Creating a REST API in Rust with warp,做者:Bastian Gruber

原文連接: https://blog.logrocket.com/creating-a-rest-api-in-rust-with-warp/

關注公衆號瞭解更多精彩內容 公衆號

相關文章
相關標籤/搜索