关注公众号:香菜粉丝 了解更多精彩内容编程
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
,POST
HTTP方法实现四个不一样的路由。
建立本地存储
除了路由,还须要将状态存储在文件或局部变量中,在异步环境中,咱们须要确保在访问存储器的同一时间只有一个状态,所以,线程之间不能存在不一致的状况,在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 )) }
最后缺乏的两个方法是UPDATE
和DELETE
。 对于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/
关注公众号了解更多精彩内容