利用Rust构建一个REST API服务

2023-05-16

利用Rust构建一个REST API服务

关注公众号:香菜粉丝
了解更多精彩内容

Rust 是一个拥有很多忠实粉丝的编程语言,还是很难找到一些用它构建的项目,而且掌握起来甚至有点难度。

想要开始学习一门编程语言最好的方式就是利用它去做一些有趣的项目,或者每天都使用它,如果你的公司在构建或者已经在使用微服务了,可以尝试一下使用rust把它重写一遍。

第一次使用Rust的时候,和学习其他语言一样,需要了解基础知识,在你了解基础语法和一些常用的概念后,就可以思考如何使用Rust进行异步编程了,很多编程语言都在运行时内置了异步的运行模式,比如在一些发送请求或者等待后台运行的场景里面会使用到异步编程。

补充一点,Tokio是一个事件驱动的非阻塞I / O平台,用于使用Rust编程语言编写异步应用程序,很有可能你下一家公司就在用它,由于你会选择一些内置了Tokio的库来编写异步程序,因此接下来,我们会使用wrap来实现一个异步API平台。

创建项目

跟着下面的教程,你需要装下面列出的库:

  • Wrap 用来创建API服务
  • Tokio 运行异步服务器
  • Serde 序列化输入的JSON
  • parking_lot 为存储器提供读写锁

首先,使用Cargo创一个新项目

cargo new neat-api --bin

将上面需要安装的依赖库添加到Cargo.toml文件里

…
[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/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

利用Rust构建一个REST API服务 的相关文章

随机推荐

  • 08-1 UIControl 及其子类 UISegmentedControl 、UISlider

    1 UIControl UIControl是所有控制控件 xff08 比如UIButton UISlider UISegmentedControl等 xff09 的基类 只要跟控制有关的控件都是继承于该类 UISlider是可以响应滑动事件
  • android Settings.Secure的使用

    在Android中 xff0c 许多的系统属性都在settings应用当中进行设置的 xff0c 比如wifi 蓝牙状态 xff0c 当前本机语言 xff0c 屏幕亮度等等一些相关的系统属性值 这些数据主要是存储在数据库中 xff0c 对应
  • 关于VM一启动虚拟机电脑就重启或蓝屏的几个解决方法

    最近在刚开始学习Linux在使用VMware创建新的虚拟机时只要一点启动虚拟机电脑就直接重启了 xff0c 最开始以为是vm版本或者是Linux镜像的原因来来回回换了好几个vm和Linux xff0c 电脑重启了二三十次都没成功启动虚拟机
  • Golang + Qt5 桌面开发终极解决方案

    Golang 43 Qt5 桌面开发终极解决方案 首先要安装Qt和Golang 一 安装前准备 1 下载Go1 4版本的压缩包版本 xff0c 解压至C盘User目录下 2 安装MinGW 并配置相关环境变量 参考链接 xff1a MinG
  • Oracle snapper ASH监控工具

    Oracle snapper ASH监控工具 snapper工具是由国外技术人员 xff0c 将基于Oracle ash技术原理用来监控数据库会话的负载情况 比较适合小范围时间监控 xff0c 可以生成多个快照 xff0c 例如1小时内 x
  • Matlab之数据筛选

    Matlab功能强大 xff0c 这里介绍一些数据筛选方法 xff0c 至少让其达到Excel的数据筛选程度 一 从多维数组中取某些行或列组合为新数组 示例如下 xff1a 取某些列组成新数组 newdata span class toke
  • kurento-room的搭建教程,绝对可行

    目前网上参考的kurento room的搭建教程 xff0c 比如https blog csdn net u010602143 article details 106670864 已经跑不起了 我估计原来也跑不起 原因很简单 xff0c k
  • Python 爬取携程所有机票

    打开携程网 xff0c 查询机票 xff0c 如广州到成都 这时网址为 xff1a http flights ctrip com booking CAN CTU day 1 html DDate1 61 2018 06 15 其中 xff0
  • Rust Web框架warp使用

    目录 简介快速开始Request和Response从path和body中获取参数从query中获取参数 设置状态码 静态文件 目录websocket重定向tls 简介 warp是一个超级便捷 可组合 速度极快的异步Web框架 目前最新版本为
  • CCNP路由实验之四 动态路由协议之EIGRP

    CCNP 路由实验之四 动态路由协议之 EIGRP 动态路由协议可以自动的发现远程网络 xff0c 只要网络拓扑结构发生了变化 xff0c 路由器就会相互交换路由信息 xff0c 不仅能够自动获知新增加的网络 xff0c 还可以在当前网络连
  • C++中typedef用法说明

    typedef声明提供了一种将标识符声明为类型别名的方法 xff0c 用于替换复杂的类型名 解释 在声明中使用typedef说明符时 xff0c 会指定这个声明是typedef声明 xff0c 而不是变量或函数声明 通常 xff0c typ
  • Ubuntu 服务配置(sysv-rc-conf)

    版权声明 xff1a 本文为博主原创文章 xff0c 未经博主允许不得转载 sudo apt get install sysv rc conf sudo sysv rc conf 运行级别说明 xff1a S表示开机后就会运行的服务0表示关
  • 安装vnc的各种悲剧解决

    系统 环境 VM 43 RHEL5 1 root 64 localhost vnc uname r 2 6 18 53 el5xen 本地XP系统安装 VNCVIEW去控制VM中的RHEL5 1 下面在LINUX上安装VNCSERVER 1
  • iOS基础 UITabBarController

    使用 创建子控制器继承自UITabBarController xff0c 在viewDidLoad阶段 xff0c 把各个分页上的控制器给创建好 xff0c 用UITabBarController的方法addChildControoler相
  • 插入内核模块失败提示"Invalid module format"

    产品需要编译自己的定制内核 43 内核模块 xff0c 下载内核源码定制修改后rpmbuild方式 点击打开链接 编译升级内核 xff0c 如下方式编译内核模块 make C kernel source SUBDIRS 61 96 pwd
  • microsoft visual c++ build tools

    因为visual studio的安装包太大 xff0c 所以在不需要开发的情况下 xff0c 可以选择使用microsoft visual c 43 43 build tools安装c 43 43 编译器 xff0c 这个工具会小很多 安装
  • C++ 应用程序 内存结构 --- BSS段,数据段,代码段,堆内存和栈

    转自 xff1a http hi baidu com C6 BF D6 D0 B5 C4 C5 AE CE D7 blog item 5043d08e741075f3503d922c html ld 时把所有的目标文件的代码段组合成一个代码
  • 4.1 简单题 - B 恭喜你

    当别人告诉你自己考了 x 分的时候 xff0c 你要回答说 xff1a 恭喜你考了 x 分 xff01 比如小明告诉你他考了90分 xff0c 你就用汉语拼音打出来 gong xi ni kao le 90 fen 输入格式 xff1a 输
  • <script>在页面代码上没有显示

    记录一下 导入js文件 xff0c 自己路径都没有问题 xff0c 为什么在浏览器查看页面代码没有自己写的那行js导入文件的代码呢 xff0c 原来 xff0c 是之前看着不舒服 xff0c 点了exclude xff0c exclude是
  • 利用Rust构建一个REST API服务

    利用Rust构建一个REST API服务 关注公众号 xff1a 香菜粉丝 了解更多精彩内容 Rust 是一个拥有很多忠实粉丝的编程语言 xff0c 还是很难找到一些用它构建的项目 xff0c 而且掌握起来甚至有点难度 想要开始学习一门编程