Rust插件库Okapi的使用:高性能API开发与扩展工具库
Okapi
Okapi:
Rocket-Okapi:
为Rust/Rocket项目自动生成OpenAPI(又称Swagger)文档。
不再有过时的文档。Okapi会在设置服务器时为您生成文档。它结合使用Rust文档注释和编程逻辑来记录您的API。
生成的OpenAPI文件随后可以被各种程序用来可视化文档。Rocket-okapi目前包含RapiDoc和Swagger UI,但也可以使用其他工具。
支持的OpenAPI规范:3.0.0
支持的Rocket版本(对于rocket_okapi):0.5.0
使用Okapi生成的文档示例:
- DF Storyteller: RapiDoc, Swagger UI
- …
示例
- Json web API:简单示例展示Okapi的基础用法。
- UUID:简单示例展示基础用法,但使用UUID而不是普通的u32/u64 ID。
- Custom Schema:展示如何向OpenAPI文件添加更多/自定义信息,并将多个模块合并到一个OpenAPI文件中。
- Secure Request Guard:展示如何在OpenAPI文件中实现认证方法。展示:无认证、API密钥、HTTP认证、OAuth2、OpenID和Cookie。
- Special types:展示一些更晦涩类型的使用及其用法。(仍在进行中)
- 还有更多
常见问题
问:我可以从我的OpenAPI文件生成代码吗?
答:不行,这个crate只允许您从代码自动生成OpenAPI文件。有其他crate(尝试)做这件事。所以:
- (Rust代码(Rocket) --> OpenAPI) == Okapi
- (OpenAPI --> Rust代码) != Okapi
问:如何记录我的端点?
答:Okapi自动使用大多数地方的Rust文档注释,这包括:
- 端点函数。
- 端点函数参数,使用Schemars。除非在其他结构体中使用,否则无法为String和其他默认类型添加文档。
- 端点函数返回类型,使用Schemars。与参数相同的规则适用。如果是Result<T, E>,可以记录错误代码。
可以使用#[openapi(…)]派生宏提供更多信息。
Schemars也可以用于为实现了#[derive(JsonSchema)]的对象提供更多信息,使用#[schemars(…)]和#[serde(…)]语法。
大多数其他地方也可以增强文档,但可能需要自定义实现。
如果上述方法不够,您始终可以创建自定义的OpenAPI对象。然后可以将其合并到最终的OpenAPI文件中。仅在确实必要时使用此方法!(因为它可能会覆盖其他生成的对象。)
问:我的(diesel)数据库没有实现OpenApiFromRequest。
答:这是因为参数没有出现在路径、查询或正文中。所以这被认为是一个请求守卫。有一个派生宏,但这与#[database("…")]宏不兼容。您可以通过手动实现来解决这个问题,像这样:
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
use rocket_okapi::gen::OpenApiGenerator;
use rocket_sync_db_pools::{diesel, database};
#[database("sqlite_logs")]
pub struct MyDB(diesel::SqliteConnection);
impl<'r> OpenApiFromRequest<'r> for MyDB {
fn from_request_input(
_gen: &mut OpenApiGenerator,
_name: String,
_required: bool,
) -> rocket_okapi::Result<RequestHeaderInput> {
Ok(RequestHeaderInput::None)
}
}
问:…没有实现JsonSchema?
答:JsonSchema实现由Schemars处理,请确保您为其启用了正确的功能标志。如果仍未实现,请在Schemars仓库中提出问题。
问:我可以向我的OpenAPI规范添加自定义数据吗?
答:可以,请参见Custom Schema示例。如果您想手动合并OpenAPI对象,Okapi也有内置函数。
问:我可以将此与其他Web框架一起使用吗?
答:可以,但目前没有其他实现。但您可以独立使用Okapi crate,并使用Serde创建json或yaml文件。
功能标志
Okapi:
- impl_json_schema:为Schemars和Okapi类型本身实现JsonSchema。
- preserve_order:在Schema和OpenAPI文档的所有部分中保持结构体字段的顺序。
Rocket-Okapi:
- preserve_order:在Schema和OpenAPI文档的所有部分中保持结构体字段的顺序。
- swagger:启用Swagger UI以呈现文档。
- rapidoc:启用RapiDoc以呈现文档。
- uuid:在Rocket和Schemars中启用UUID支持。
- msgpack:为Rocket启用msgpack支持。(当使用相同的Rocket功能标志时。)
- secrets:为Rocket启用密钥支持。(当使用相同的Rocket功能标志时。)
- mtls:为Rocket启用相互TSL。(当使用相同的Rocket功能标志时。)
- rocket_dyn_templates:启用与rocket_dyn_templates的兼容性。
- rocket_db_pools:启用与rocket_db_pools的兼容性。
- rocket_sync_db_pools:启用与rocket_sync_db_pools的兼容性。
- rocket_ws:启用与rocket_ws的兼容性。
请注意,并非所有Schemars的功能标志都被重新导出或启用。因此,如果您有未实现JsonSchema特征的对象,您可能需要在Schemars中启用功能标志。(确保crate版本匹配)
工作原理
这个crate在Rocket服务器启动时自动生成一个OpenAPI文件。这里简要描述了完成此操作的方式。
Schemars crate为我们提供了所有不同结构和枚举的模式。Okapi不直接实现任何模式,这全部由Schemars处理。
Okapi crate只包含创建OpenAPI文件所需的所有结构。这个crate不包含任何创建它们的代码,只有结构以及将两个OpenAPI结构合并在一起的代码。这个crate可以重用以在其他Web框架中创建OpenAPI支持。
Rocket-Okapi crate包含生成OpenAPI文件并在创建后提供服务的所有代码。此代码通常使用宏执行,例如:mount_endpoints_and_merged_docs!{…}, openapi_get_routes![…], openapi_get_routes_spec![…]和openapi_get_spec![…]。
当Rocket服务器启动(或放置宏的地方)时,OpenAPI文件生成一次。然后这个文件/结构存储在内存中,并在请求时提供。
Rocket-Okapi-codegen crate包含派生宏的代码。#[openapi], rocket_okapi::openapi_spec![…], rocket_okapi::openapi_routes![…]和#[derive(OpenApiFromRequest)]在我们的例子中。由于Rust的限制,这需要在一个单独的crate中。注意:与其他crate相比,derive或codegen crate通常有点难以使用。因此,在更改这里的任何内容之前,建议先获得一些关于派生宏如何工作的经验。
待办事项
- 测试
- 文档
- 基准测试/优化内存使用和分配
- 自注:看起来很有用
- 为更多rocket/rocket-contrib类型实现OpenApiFrom___/OpenApiResponder
- 允许自定义openapi生成设置,例如
- 自定义json模式生成设置
- 更改托管文档的路径
许可证
此项目根据MIT许可证授权。
对此项目的所有贡献都将同样获得许可。
完整示例demo
use rocket::get;
use rocket::routes;
use rocket_okapi::openapi_get_routes;
use rocket_okapi::swagger_ui::make_swagger_ui;
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
}
#[rocket::main]
async fn main() {
rocket::build()
.mount("/", openapi_get_routes![index])
.mount("/swagger", make_swagger_ui(&swagger_ui::Config {
url: "../openapi.json".to_owned(),
..Default::default()
}))
.launch()
.await
.unwrap();
}
Okapi:Rust高性能API开发与扩展工具库
概述
Okapi是一个专为Rust语言设计的高性能API开发框架,提供了一套完整的工具集用于构建、扩展和维护Web API服务。该库结合了Rust的性能优势与现代API开发的最佳实践,支持快速构建类型安全、高性能的RESTful API和GraphQL服务。
核心特性
- 异步高性能:基于async/await语法,支持高并发处理
- 类型安全:充分利用Rust的类型系统确保API安全性
- 中间件支持:灵活的中间件架构
- OpenAPI集成:自动生成API文档
- 可扩展设计:模块化架构便于功能扩展
安装方法
在Cargo.toml中添加依赖:
[dependencies]
okapi = "0.4"
okapi-codegen = "0.4"
schemars = "0.8"
基本使用方法
1. 创建基本API端点
use okapi::openapi3::OpenApi;
use rocket_okapi::{openapi, openapi_get_routes, settings::OpenApiSettings};
use rocket::{get, routes};
#[openapi]
#[get("/hello")]
fn hello() -> Json<Value> {
Json(json!({ "message": "Hello, Okapi!" }))
}
#[launch]
fn rocket() -> _ {
let settings = OpenApiSettings::default();
rocket::build()
.mount("/", openapi_get_routes![settings: hello])
.mount("/swagger", rocket_okapi::swagger_ui::make_swagger_ui(&swagger_info))
}
2. 定义数据模型和Schema
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, JsonSchema)]
struct User {
id: u64,
username: String,
email: String,
}
#[openapi]
#[get("/users/<id>")]
fn get_user(id: u64) -> Option<Json<User>> {
// 业务逻辑实现
Some(Json(User {
id,
username: "john_doe".to_string(),
email: "john@example.com".to_string(),
}))
}
3. 错误处理示例
#[derive(Serialize, JsonSchema)]
struct ErrorResponse {
message: String,
code: u16,
}
#[openapi]
#[get("/users/<id>")]
fn get_user(id: u64) -> Result<Json<User>, Status> {
if id == 0 {
return Err(Status::NotFound);
}
Ok(Json(User {
id,
username: "john_doe".to_string(),
email: "john@example.com".to_string(),
}))
}
4. 中间件使用示例
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
#[rocket::async_trait]
impl<'r> OpenApiFromRequest<'r> for ApiKey {
type Error = ();
async fn from_request(request: &'r rocket::Request<'_>) -> Result<Self, Self::Error> {
// 自定义认证中间件实现
let key = request.headers().get_one("X-API-Key");
match key {
Some(key) if key == "secret" => Ok(ApiKey(key.to_string())),
_ => Err(()),
}
}
}
高级功能
自定义响应Schema
#[openapi]
#[get("/users", format = "json")]
fn list_users() -> Json<Vec<User>> {
Json(vec![
User {
id: 1,
username: "user1".to_string(),
email: "user1@example.com".to_string(),
},
User {
id: 2,
username: "user2".to_string(),
email: "user2@example.com".to_string(),
},
])
}
文件上传支持
#[openapi]
#[post("/upload", data = "<file>")]
async fn upload_file(file: Data<'_>) -> std::io::Result<String> {
let bytes = file.open(1.megabytes()).into_bytes().await?;
// 处理上传的文件
Ok("File uploaded successfully".to_string())
}
完整示例demo
//! Okapi完整API示例
use okapi::openapi3::OpenApi;
use rocket_okapi::{openapi, openapi_get_routes, openapi_post_routes, settings::OpenApiSettings};
use rocket::{get, post, routes, launch};
use rocket::serde::json::{Json, Value, json};
use rocket::serde::{Serialize, Deserialize};
use schemars::JsonSchema;
use rocket::data::Data;
use rocket::http::Status;
// 用户数据结构定义
#[derive(Serialize, Deserialize, JsonSchema, Clone)]
struct User {
id: u64,
username: String,
email: String,
}
// 错误响应结构
#[derive(Serialize, JsonSchema)]
struct ErrorResponse {
message: String,
code: u16,
}
// API密钥中间件
struct ApiKey(String);
// 实现OpenApiFromRequest trait用于中间件认证
#[rocket::async_trait]
impl<'r> rocket_okapi::request::OpenApiFromRequest<'r> for ApiKey {
type Error = ();
async fn from_request(request: &'r rocket::Request<'_>) -> Result<Self, Self::Error> {
// 从请求头获取API密钥
let key = request.headers().get_one("X-API-Key");
match key {
Some(key) if key == "secret123" => Ok(ApiKey(key.to_string())),
_ => Err(()),
}
}
}
// 欢迎接口
#[openapi]
#[get("/")]
fn index() -> Json<Value> {
Json(json!({
"message": "欢迎使用Okapi API服务",
"version": "1.0.0"
}))
}
// 获取用户信息
#[openapi]
#[get("/users/<id>")]
fn get_user(id: u64) -> Result<Json<User>, Status> {
if id == 0 {
return Err(Status::NotFound);
}
// 模拟从数据库获取用户数据
Ok(Json(User {
id,
username: format!("user{}", id),
email: format!("user{}@example.com", id),
}))
}
// 获取用户列表
#[openapi]
#[get("/users")]
fn list_users(_key: ApiKey) -> Json<Vec<User>> {
// 需要有效的API密钥才能访问
Json(vec![
User {
id: 1,
username: "user1".to_string(),
email: "user1@example.com".to_string(),
},
User {
id: 2,
username: "user2".to_string(),
email: "user2@example.com".to_string(),
},
])
}
// 文件上传接口
#[openapi]
#[post("/upload", data = "<file>")]
async fn upload_file(file: Data<'_>) -> std::io::Result<String> {
// 限制文件大小为1MB
let bytes = file.open(1.megabytes()).into_bytes().await?;
// 这里可以添加文件处理逻辑
// 例如保存到磁盘或云存储
Ok(format!("文件上传成功,大小: {} 字节", bytes.len()))
}
// 创建用户接口
#[openapi]
#[post("/users", data = "<user>")]
fn create_user(user: Json<User>) -> Json<Value> {
// 这里可以添加用户创建逻辑
// 例如保存到数据库
Json(json!({
"message": "用户创建成功",
"user_id": user.id
}))
}
// Rocket启动函数
#[launch]
fn rocket() -> _ {
let settings = OpenApiSettings::default();
rocket::build()
.mount("/", openapi_get_routes![settings: index, get_user, list_users])
.mount("/", openapi_post_routes![settings: upload_file, create_user])
.mount("/swagger", rocket_okapi::swagger_ui::make_swagger_ui(
&rocket_okapi::swagger_ui::SwaggerUIConfig {
url: "../openapi.json".to_string(),
..Default::default()
}
))
}
最佳实践建议
- 充分利用Rust的类型系统进行输入验证
- 使用Okapi的自动文档生成功能保持API文档最新
- 合理使用中间件进行认证和授权处理
- 针对高性能场景优化异步处理逻辑
- 定期更新Okapi版本以获得最新特性和安全修复
性能优化提示
- 使用
#[inline]
标注热点代码路径 - 合理使用缓存机制减少重复计算
- 采用连接池管理数据库连接
- 使用适当的序列化/反序列化策略
Okapi为Rust开发者提供了构建企业级API所需的完整工具链,结合Rust的语言特性,能够创建出安全、高效且易于维护的Web服务。