7个版本
0.2.1 | 2024年6月26日 |
---|---|
0.2.0 | 2024年6月10日 |
0.1.8 | 2023年4月15日 |
0.1.7 | 2022年8月31日 |
0.1.5 | 2022年7月25日 |
#87 in HTTP服务器
每月下载量:35
49KB
921 行
Rustgram
Rustgram是一个基于hyper构建的轻量级、快速且易于使用的http路由和中间件框架
特性
- 像Tower服务一样构建路由和中间件
- 使用yaml文件定义路由
在Cargo.toml
中安装
[dependencies]
# hyper when using Statuscode
hyper = { version = "0.14", features = ["full"] }
# tokio for the async main fn
tokio = { version = "1", features = ["full"] }
# rustgram
rustgram = "0.1"
文档
入门
- 使用一个未找到处理程序服务(例如一个函数)创建路由器
- 添加路由,对于每个HTTP方法都有一个路由函数:get、post、put、delete、head等
- 使用r函数将处理程序传递给路由器
- 使用方法函数定义此路由应匹配给定路径的哪个方法
- 设置URL路径
- 输入监听连接的套接字地址
use hyper::StatusCode;
use rustgram::{r, Router, Request, Response};
use std::net::SocketAddr;
async fn not_found_handler(_req: Request) -> Response
{
return hyper::Response::builder()
.status(StatusCode::NOT_FOUND)
.body("Not found".into())
.unwrap();
}
pub async fn test_handler(_req: Request) -> String
{
format!("test called")
}
#[tokio::main]
async fn main()
{
let mut router = Router::new(crate::not_found_handler);
router.get("/", r(test_handler));
router.get("/api", r(test_handler));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
//start the app
rustgram::start(router, addr).await;
}
中间件
- 中间件是一个服务。
- 中间件调用下一个服务(通过中间件转换获得)。
- 转换用于返回一个具有下一个服务的新中间件
- 每次将中间件应用于路由时都会调用转换函数
中间件栈的构建类似于服务调用栈。
中间件栈的顺序与应用的顺序相反。
use hyper::StatusCode;
use rustgram::service::{Service, ServiceTransform};
use rustgram::{Request, Response};
//define a middleware service
pub struct Middleware<S>
{
inner: S, //space the inner service to call it later
}
impl<S> Service<Request> for Middleware<S>
where
S: Service<Request, Output=Response>, //define the return types from the next service
{
type Output = S::Output;
fn call(&self, req: Request) -> impl Future<Output=Self::Output> + Send + 'static
{
// before the request handler from the router is called
self.inner.call(req) //call the next handler
// after the request handler is called with the response
}
}
//define a middleware transform
pub struct MiddlewareTransform;
impl<S> ServiceTransform<S> for MiddlewareTransform
where
S: Service<Request, Output=Response>, //define the return types from the next service
{
type Service = Middleware<S>;
fn transform(&self, inner: S) -> Self::Service
{
Middleware {
inner,
}
}
}
//or define a middleware transform with a function
pub fn middleware_transform<S>(inner: S) -> Middleware<S>
{
Middleware {
inner,
}
}
async fn not_found_handler(_req: Request) -> Response
{
return hyper::Response::builder()
.status(StatusCode::NOT_FOUND)
.body("Not found".into())
.unwrap();
}
pub async fn test_handler(_req: Request) -> String
{
format!("test called")
}
//Apply a middleware to a route after the r function
#[tokio::main]
async fn main()
{
let mut router = Router::new(crate::not_found_handler);
router.get("/", r(test_handler)
.add(middleware_transform)
);
router.get("/api", r(test_handler)
.add(MiddlewareTransform)
);
//apply multiple middleware to a route
router.get("/multiple", r(test_handler)
.add(MiddlewareTransform) //then this at last
.add(middleware_transform) //then this ^
.add(middleware_transform) //then this ^
.add(middleware_transform) //then this ^
.add(middleware_transform) //then this ^
.add(middleware_transform) //this is called first ^
);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
//start the app
rustgram::start(router, addr).await;
}
异步处理的中间件
如果请求处理之前或之后必须执行工作,则需要Box Future
在响应之前调用异步操作时,需要Arc以避免生命周期问题
- 使用Pin Box功能
- 将内部服务作为Arc指针使用
- 在调用异步块之前克隆Arc指针
- 在异步块中执行异步操作
use std::future::Future;
use std::sync::Arc;
use rustgram::service::{Service, ServiceTransform};
use rustgram::{Request, Response};
pub struct Middleware<S>
{
inner: Arc<S>, //use Arc here to avoid lifetime issues
}
impl<S> Service<Request> for Middleware<S>
where
S: Service<Request, Output=Response>,
{
type Output = S::Output;
fn call(&self, req: Request) -> impl Future<Output=Self::Output> + Send + 'static
{
//must clone the service to call it in the async move block. (we are cloning the arc ref, to avoid lifetime issues)
let next = self.inner.clone();
async move {
//do async fn before req
next.call(req).await
//do async fn after req
}
}
}
pub fn mw_transform<S>(inner: S) -> Middleware<S>
{
Middleware {
inner: Arc::new(inner), //use Arc here!
}
}
只有在响应之后才执行异步操作
use std::future::Future;
use rustgram::service::{Service, ServiceTransform};
use rustgram::{Request, Response};
pub struct Middleware<S>
{
inner: S,
}
impl<S> Service<Request> for Middleware<S>
where
S: Service<Request, Output=Response>,
{
type Output = S::Output;
fn call(&self, req: Request) -> impl Future<Output=Self::Output> + Send + 'static
{
//no Arc cloning needed because we move the feature into the async block
//but change the req is not possible here
let res = self.inner.call(req);
async move {
let res = res.await;
//do async fn after req
res
}
}
}
pub fn mw_transform<S>(inner: S) -> Middleware<S>
{
Middleware {
inner, //no Arc here!
}
}
处理程序返回和错误处理
路由器只使用Service traits。对于正常函数和闭包,这已经实现。
函数不需要返回Hyper Response,但它们的返回值会被转换为hyper响应。
支持的返回值包括
- Hyper Response
- String
- &'static str
- Result<String, GramStdHttpErr>
- 结果
- 结果
GramStdHttpErr被转换为超响应。
IntoResponse可以为每种类型实现。在这种情况下,它为错误实现。
如果返回HttpErr,它将在从Error创建的Response中创建。
use hyper::StatusCode;
use rustgram::{Response, Request};
use rustgram::service::IntoResponse;
pub struct HttpErr
{
http_status_code: u16,
api_error_code: u32,
msg: &'static str,
}
impl HttpErr
{
pub fn new(http_status_code: u16, api_error_code: u32, msg: &'static str) -> Self
{
Self {
http_status_code,
api_error_code,
msg
}
}
}
impl IntoResponse<Response> for HttpErr
{
fn into_response(self) -> Response
{
let status = match StatusCode::from_u16(self.http_status_code) {
Ok(s) => s,
Err(_e) => StatusCode::BAD_REQUEST,
};
//the msg for the end user
let msg = format!(
"{{\"status\": {}, \"error_message\": \"{}\"}}",
self.api_error_code, self.msg
);
hyper::Response::builder()
.status(status)
.header("Content-Type", "application/json")
.body(hyper::Body::from(msg))
.unwrap()
}
}
//example usage:
pub async fn test_handler_err(_req: Request) -> Result<String, HttpErr>
{
Err(HttpErr::new(400, 1, "Input not valid"))
}
结果
R可以是任何实现了IntoResponse的类型。
返回json字符串的示例
use rustgram::service::IntoResponse;
use rustgram::Response;
use serde::Serialize;
use serde_json::to_string;
pub struct JsonResult<T: Serialize>(pub T);
impl<T: Serialize> IntoResponse<Response> for JsonResult<T>
{
fn into_response(self) -> Response
{
//to string from serde_json
let string = match to_string(&self.0) {
Ok(s) => s,
//the Http err from the previous example
Err(_e) => return HttpErr::new(422, 1, "Json to string error", None).get_res(),
};
hyper::Response::builder()
.header("Content-Type", "application/json")
.body(string.into())
.unwrap()
}
}
//in another file
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct ResultMsg(pub String);
pub async fn test_handler_json_result(_req: Request) -> Result<JsonResult<ResultMsg>, HttpErr>
{
Ok(JsonResult(ResultMsg(String::from("Hello world"))))
}
路由构建器和组
- 组只能由路由构建器构建
- 构建器解析yml文件并创建一个新的路由文件。此文件包含一个返回路由器(稍后使用)的函数。
- 路由中的所有路由共享相同的中间件和相同的前缀
- 嵌套组也是可能的
- 为路由构建器创建第二个bin crate。
- 此crate调用构建器函数
- 设置输入和输出路径(都相对于当前工作目录)
- 使用带有route_builder功能的rustgram安装
- 每次更改路由时,构建并执行路由构建器
- 使用新文件中的route函数来获取路由器
# in a workspace just create a new crate
cargo new route_builder
在Cargo-toml文件中
rustgram = { version = "0.1", features = ["route_builder"] }
在src/main.rs
中打开main函数
use rustgram::route_parser;
fn main()
{
//input path: from the root of the current working directory
// output path: into the app crate (created via cargo new app)
route_parser::start(
"routes.yml".to_string(),
"app/src/routes.rs".to_string()
);
}
创建路由文件。
# define the namespace where the route handlers live
# or leave it empty and use the full path to the handler
base_handler: test_handler
# define the namespace for the middleware
base_mw: test_mw
# prefix for all routes
prefix: "/"
# the routes and groups. use the method followed by the path (p) and the handler (s)
routes:
- get:
p: ""
# must match the base_handler namespace
s: test_handler::test_handler
# a put route with middleware
- put:
p: ""
s: test_handler::test_handler
mw:
- mw1_transform
- mw_transform
# a group of routes.
# a prefix (p) for all routes and middleware (mw) like routes
- group:
p: admin
mw:
- mw1_transform
- mw_transform
# the routes to this group
gr:
- get:
p: ""
s: test_handler_db::test_handler_db_to_json
- get:
p: "/user/:id"
s: test_handler::test_handler
# for this route, mw is called first, then mw1, mw2 and mw3
- put:
p: "/many_mw"
s: test_handler::test_handler
mw:
- mw3_transform
- mw2_transform
- group:
p: nested
mw:
- mw1_transform
- mw_transform
gr:
# define a new group inside the group routes
- group:
p: "/management"
mw:
- mw1_transform
- mw_transform
gr:
- put:
p: "/put"
s: test_handler::test_handler
mw:
- mw5_transform
- mw4_transform
该文件被解析为
/**
# Generated route files by rustgram route builder.
Please do not modify this file. Any changes will be overridden by the next route build.
Use the returned router instead
*/
use rustgram::{r, Router};
use crate::test_handler::*;
use crate::test_mw::*;
pub(crate) fn routes(router: &mut Router)
{
router.get("/", r(test_handler::test_handler));
router.put(
"/",
r(test_handler::test_handler)
.add(mw1_transform)
.add(mw_transform),
);
router.get(
"/admin",
r(test_handler_db::test_handler_db_to_json)
.add(mw1_transform)
.add(mw_transform),
);
router.get(
"/admin/user/:id",
r(test_handler::test_handler)
.add(mw1_transform)
.add(mw_transform),
);
router.put(
"/admin/many_mw",
r(test_handler::test_handler)
.add(mw3_transform)
.add(mw2_transform)
.add(mw1_transform)
.add(mw_transform),
);
router.put(
"/nested/management/put",
r(test_handler::test_handler)
.add(mw5_transform)
.add(mw4_transform)
.add(mw3_transform)
.add(mw2_transform)
.add(mw1_transform)
.add(mw_transform),
);
}
//Now the route file can be used like this:
#[tokio::main]
async fn main()
{
let mut router = Router::new(crate::not_found_handler);
routes(&mut router);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
//start the app
rustgram::start(router, addr).await;
}
依赖项
~4–6MB
~105K SLoC