Rust HTTP API框架Dropshot的使用,Dropshot提供轻量级RESTful端点构建工具和OpenAPI集成

Rust HTTP API框架Dropshot的使用

Dropshot是一个通用但具有特定设计理念的Rust库,用于从Rust程序暴露REST API。它旨在保持简单和轻量级,并提供了对OpenAPI的一流支持,可以直接从代码生成精确的规范。它还支持一致的页面分页原语,包括在规范中表示它的OpenAPI扩展。

安装

在项目目录中运行以下Cargo命令:

cargo add dropshot

或者在Cargo.toml中添加:

dropshot = "0.16.2"

示例代码

下面是一个完整的Dropshot使用示例,展示了如何创建一个简单的REST API:

use dropshot::{endpoint, ApiDescription, HttpServer, HttpServerStarter, RequestContext};
use dropshot::endpoint::WebContext;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

// 定义一个简单的数据结构
#[derive(Serialize, Deserialize)]
struct Greeting {
    message: String,
}

// 定义一个端点处理函数
#[endpoint {
    method = GET,
    path = "/greet/{name}",
}]
async fn greet(
    rqctx: Arc<RequestContext<()>>,
    name: String,
) -> Result<Greeting, dropshot::HttpError> {
    Ok(Greeting {
        message: format!("Hello, {}!", name),
    })
}

#[tokio::main]
async fn main() -> Result<(), String> {
    // 创建API描述
    let mut api = ApiDescription::new();
    api.register(greet).unwrap();

    // 配置服务器
    let server = HttpServerStarter::new(
        &Default::default(),
        api,
        (),
        &std::net::SocketAddr::from(([127, 0, 0, 1], 3000)),
    )
    .map_err(|e| e.to_string())?
    .start();

    // 运行服务器
    server.await
}

这个示例展示了:

  1. 定义了一个简单的数据结构Greeting
  2. 创建了一个端点处理函数greet,它接受一个路径参数name
  3. 设置了API描述和服务器配置
  4. 启动服务器监听3000端口

OpenAPI集成

Dropshot的一个主要特点是它可以直接从你的Rust代码生成OpenAPI规范。当你运行服务器时,它会自动提供一个/openapi.json端点,其中包含你的API的完整OpenAPI规范。

分页支持

Dropshot提供了内置的分页支持。下面是使用分页的示例:

use dropshot::{endpoint, PaginationParams, ResultsPage};
use serde_json::Value;

#[endpoint {
    method = GET,
    path = "/items",
}]
async fn list_items(
    rqctx: Arc<RequestContext<()>>,
    pag_params: PaginationParams<(), ()>,
) -> Result<ResultsPage<Value>, dropshot::HttpError> {
    let items = vec![
        serde_json::json!({"id": 1, "name": "Item 1"}),
        serde_json::json!({"id": 2, "name": "Item 2"}),
    ];
    
    Ok(ResultsPage {
        items,
        next_page: None,
    })
}

完整示例

下面是一个更完整的Dropshot API示例,包含多个端点和状态共享:

use dropshot::{endpoint, ApiDescription, HttpServer, HttpServerStarter, RequestContext};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use std::collections::HashMap;

// 定义共享状态
#[derive(Default)]
struct AppState {
    db: Mutex<HashMap<u64, String>>,
}

// 定义数据结构
#[derive(Serialize, Deserialize)]
struct Item {
    id: u64,
    name: String,
}

#[derive(Serialize, Deserialize)]
struct ItemInput {
    name: String,
}

// 创建项目端点
#[endpoint {
    method = POST,
    path = "/items",
}]
async fn create_item(
    rqctx: Arc<RequestContext<AppState>>,
    body: ItemInput,
) -> Result<Item, dropshot::HttpError> {
    let state = rqctx.context();
    let mut db = state.db.lock().unwrap();
    let id = db.len() as u64 + 1;
    db.insert(id, body.name.clone());
    Ok(Item { id, name: body.name })
}

// 获取项目列表端点
#[endpoint {
    method = GET,
    path = "/items",
}]
async fn list_items(
    rqctx: Arc<RequestContext<AppState>>,
) -> Result<Vec<Item>, dropshot::HttpError> {
    let state = rqctx.context();
    let db = state.db.lock().unwrap();
    let items = db.iter()
        .map(|(&id, name)| Item { id, name: name.clone() })
        .collect();
    Ok(items)
}

// 获取单个项目端点
#[endpoint {
    method = GET,
    path = "/items/{id}",
}]
async fn get_item(
    rqctx: Arc<RequestContext<AppState>>,
    id: u64,
) -> Result<Item, dropshot::HttpError> {
    let state = rqctx.context();
    let db = state.db.lock().unwrap();
    match db.get(&id) {
        Some(name) => Ok(Item { id, name: name.clone() }),
        None => Err(dropshot::HttpError::for_not_found(
            None,
            "Item not found".to_string(),
        )),
    }
}

#[tokio::main]
async fn main() -> Result<(), String> {
    // 创建API描述
    let mut api = ApiDescription::new();
    api.register(create_item).unwrap();
    api.register(list_items).unwrap();
    api.register(get_item).unwrap();

    // 创建共享状态
    let shared_state = AppState::default();

    // 配置服务器
    let server = HttpServerStarter::new(
        &Default::default(),
        api,
        shared_state,
        &std::net::SocketAddr::from(([127, 0, 0, 1], 3000)),
    )
    .map_err(|e| e.to_string())?
    .start();

    // 运行服务器
    server.await
}

这个完整示例展示了:

  1. 定义共享状态AppState用于存储数据
  2. 创建了三个端点用于:
    • POST /items - 创建新项目
    • GET /items - 获取项目列表
    • GET /items/{id} - 获取单个项目
  3. 使用Mutex实现线程安全的状态共享
  4. 包含了错误处理逻辑
  5. 保持简单API设计的同时展示Dropshot的核心功能

1 回复

Rust HTTP API框架Dropshot的使用指南

Dropshot是一个轻量级的Rust HTTP API框架,专注于构建RESTful端点和提供OpenAPI集成。它特别适合需要自动生成OpenAPI文档的项目。

主要特性

  • 轻量级且易于使用的RESTful端点构建工具
  • 自动生成OpenAPI 3.0文档
  • 内置支持JSON请求/响应
  • 与hyper集成,提供异步支持
  • 类型安全的端点处理

基本使用方法

1. 添加依赖

首先在Cargo.toml中添加dropshot依赖:

[dependencies]
dropshot = "0.8"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }

2. 创建基本API服务器

use dropshot::{ApiDescription, HttpServer, ConfigDropshot};
use dropshot::endpoint;
use dropshot::RequestContext;
use dropshot::HttpError;
use http::{Response, StatusCode};
use serde::{Deserialize, Serialize};
use std::sync::Arc;

#[derive(Serialize, Deserialize)]
struct Greeting {
    message: String,
}

#[endpoint {
    method = GET,
    path = "/greet/{name}",
}]
async fn greet(
    ctx: Arc<RequestContext>,
    name: String,
) -> Result<Greeting, HttpError> {
    Ok(Greeting {
        message: format!("Hello, {}!", name),
    })
}

#[tokio::main]
async fn main() -> Result<(), String> {
    let mut api = ApiDescription::new();
    api.register(greet).unwrap();

    let server = HttpServer::new(
        &ConfigDropshot::default(),
        api,
        Arc::new(()), // 共享状态
    )
    .await
    .map_err(|e| e.to_string())?;

    server.await
}

3. 启动服务器并测试

运行程序后,可以访问:

  • 获取JSON响应
  • 查看自动生成的OpenAPI文档

高级功能

请求体和参数处理

#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
}

#[endpoint {
    method = POST,
    path = "/users",
}]
async fn create_user(
    ctx: Arc<RequestContext>,
    body: Json<CreateUser>,
) -> Result<Response<()>, HttpError> {
    println!("Creating user: {} <{}>", body.name, body.email);
    Ok(Response::builder()
        .status(StatusCode::CREATED)
        .body(())
        .unwrap())
}

共享状态

struct AppState {
    counter: Mutex<u32>,
}

#[endpoint {
    method = GET,
    path = "/counter",
}]
async fn get_counter(
    ctx: Arc<RequestContext<AppState>>,
) -> Result<Json<u32>, HttpError> {
    let counter = ctx.app_private.counter.lock().unwrap();
    Ok(Json(*counter))
}

自定义错误处理

#[derive(Debug, Serialize)]
struct ErrorMessage {
    code: u16,
    message: String,
}

impl IntoResponse for ErrorMessage {
    fn into_response(self) -> Response<BoxBody> {
        let body = Json(self).into_response().into_body();
        Response::builder()
            .status(StatusCode::from_u16(self.code).unwrap()
            .body(body)
            .unwrap()
    }
}

#[endpoint {
    method = GET,
    path = "/error",
}]
async fn error_endpoint() -> Result<String, ErrorMessage> {
    Err(ErrorMessage {
        code: 418,
        message: "I'm a teapot".to_string(),
    })
}

OpenAPI集成

Dropshot会自动为所有注册的端点生成OpenAPI文档。你还可以通过endpoint属性添加更多元数据:

#[endpoint {
    method = GET,
    path = "/user/{id}",
    tags = ["user"],
    summary = "Get user by ID",
    description = "Returns a single user",
}]
async fn get_user(
    ctx: Arc<RequestContext>,
    id: u64,
) -> Result<Json<User>, HttpError> {
    // 实现代码
}

完整示例代码

下面是一个完整的Dropshot API服务器示例,包含了基本路由、共享状态和错误处理:

use dropshot::{ApiDescription, HttpServer, ConfigDropshot, endpoint, HttpError, RequestContext};
use http::{Response, StatusCode};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use tokio::sync::Mutex as AsyncMutex;

// 共享应用状态
#[derive(Default)]
struct AppState {
    counter: AsyncMutex<u32>,  // 使用异步互斥锁
    users: Mutex<Vec<User>>,   // 使用同步互斥锁(因为本例中users操作不涉及await)
}

// 用户数据结构
#[derive(Serialize, Deserialize, Clone)]
struct User {
    id: u64,
    name: String,
    email: String,
}

// 错误响应
#[derive(Debug, Serialize)]
struct ApiError {
    code: u16,
    message: String,
}

// 获取计数器端点
#[endpoint {
    method = GET,
    path = "/counter",
}]
async fn get_counter(
    ctx: Arc<RequestContext<AppState>>,
) -> Result<Json<u32>, HttpError> {
    let counter = ctx.app_private.counter.lock().await;
    Ok(Json(*counter))
}

// 增加计数器端点
#[endpoint {
    method = POST,
    path = "/counter/increment",
}]
async fn increment_counter(
    ctx: Arc<RequestContext<AppState>>,
) -> Result<Json<u32>, HttpError> {
    let mut counter = ctx.app_private.counter.lock().await;
    *counter += 1;
    Ok(Json(*counter))
}

// 创建用户端点
#[endpoint {
    method = POST,
    path = "/users",
}]
async fn create_user(
    ctx: Arc<RequestContext<AppState>>,
    body: Json<User>,
) -> Result<Response<()>, HttpError> {
    let mut users = ctx.app_private.users.lock().unwrap();
    users.push(body.into_inner());
    Ok(Response::builder()
        .status(StatusCode::CREATED)
        .body(())
        .unwrap())
}

// 获取所有用户端点
#[endpoint {
    method = GET,
    path = "/users",
}]
async fn get_users(
    ctx: Arc<RequestContext<AppState>>,
) -> Result<Json<Vec<User>>, HttpError> {
    let users = ctx.app_private.users.lock().unwrap();
    Ok(Json(users.clone()))
}

// 自定义错误端点
#[endpoint {
    method = GET,
    path = "/error",
}]
async fn error_endpoint() -> Result<String, ApiError> {
    Err(ApiError {
        code: 418,
        message: "I'm a teapot".to_string(),
    })
}

#[tokio::main]
async fn main() -> Result<(), String> {
    // 创建API描述
    let mut api = ApiDescription::new();
    api.register(get_counter).unwrap();
    api.register(increment_counter).unwrap();
    api.register(create_user).unwrap();
    api.register(get_users).unwrap();
    api.register(error_endpoint).unwrap();

    // 初始化共享状态
    let shared_state = Arc::new(AppState {
        counter: AsyncMutex::new(0),
        users: Mutex::new(Vec::new()),
    });

    // 配置并启动服务器
    let server_config = ConfigDropshot {
        bind_address: "127.0.0.1:8080".parse().unwrap(),
        ..Default::default()
    };

    let server = HttpServer::new(&server_config, api, shared_state)
        .await
        .map_err(|e| e.to_string())?;

    println!("Server running on port 8080");
    server.await
}

总结

Dropshot是一个简单但功能强大的Rust HTTP框架,特别适合需要良好文档化的RESTful API项目。它的自动OpenAPI集成可以显著减少API文档维护的工作量,同时保持类型安全和良好的性能。

回到顶部