rust api接口开发(以登陆和中间件鉴权为例)

rust rest api接口开发

所需依赖

  • axum
  • tokio
  • redis
shell 复制代码
cargo add axum redis
cargo add tokio --features=full

路由服务创建和运行

rust 复制代码
//子路由
let v1router = axum::Router::new();
//主路由,并将子路由绑定到主路由
let router=axum::Router::new().nest("/v1",v1router);

let l = tokio::net::TcpListener::bind("0.0.0.0:8080")
            .await
            .expect("bind 8080 failed");
        axum::serve(l, router).await.expect("serve server failed");

handle 函数到路由

router调用route等函数后会转移自身,所以你可以选择两种方式使用router:链式调用,和重新赋值
链式调用

rust 复制代码
use axum::routing::get;
let router=axum::Router::new().route("/echo1",get(echo1)).route("/echo2",post(echo2));

重新赋值

rust 复制代码
use axum::routing::get;
let router=axum::Router::new();
let router=router.route("/echo1",get(echo1));
let router=router.route("/echo2",get(echo2));

handler 函数

rust axum的handler函数相对于golang 的web框架来讲要比较智能,他已经帮你自动做好mvc中的controller层,而golang的gin框架和iris都需要或多或少自己实现或者使用mvc脚手架(例如iris/mvc),更要命的是大部分脚手架都是使用golang运行时反射实现的,性能相对于在编译期间通过宏来静态反射生成的要差许多
这是一个简单的不需要任何参数,直接返回hello的接口。当然如果你需要从body中读取json或者原body都可以在函数参数加,axum会自动识别,响应如果需要制定status也可以在响应里添加StatusCode

rust 复制代码
let router=router.route("/greet",get(||async{
	Html("hello")
}));

这个接口也可以这样写

rust 复制代码
let router=router.route("/greet",get(greets));

//函数定义
async fn greets()->Html<&'static str>{
        return Html("hello");
    }

中间件

rust 复制代码
let router=router.layer(axum::middleware::from_fn(|req:Request,next:axum::middleware::Next|async{
//做你想做的操作,next.run等效于golang web框架中xxx.Context 下的Next()
    next.run(req).await
}));

Service注册

rust 复制代码
let router = router.layer(axum::Extension(Arc::new(WebService::new(
    AuthService::new("redis://localhost:6379"),
))));

以登陆和鉴权接口演示

这里以登陆和鉴权接口进行演示,登陆成功后将token存入redis中. 为了方便演示流程,就直接忽略数据库里查询匹配,用户名和密码一样就模拟通过

rust 复制代码
#[cfg(test)]
mod web{
    use std::sync::Arc;

    use axum::{
        extract::Request, http::HeaderMap, middleware::Next, response::Html, Extension, Json,
    };
    use tokio::sync::Mutex;

    #[tokio::test]
    async fn start() {
        let v1router = axum::Router::new()
            .route("/greet", axum::routing::get(greet))
            .layer(axum::middleware::from_fn(
                |Extension(ext): Extension<Arc<WebService>>,mut req: Request, next: Next| async move {
                //token校验,没有什么也不返回,当前中间件只对v1router中服务生效
                    let token = req.headers().get("token");
                    if let None = token {
                        return axum::http::Response::<axum::body::Body>::new(
                            axum::body::Body::empty(),
                        );
                    }
                    let token = token.unwrap().to_str().unwrap();
                    let mut bl = ext.auth_service.lock().await;
                    let username=bl.check_token(token.to_string());
                    if let None=username{
                      eprintln!("not found token {token}");
                        return axum::http::Response::<axum::body::Body>::new(
                            axum::body::Body::empty(),
                        );
                    }
                    let username=username.unwrap();
                    req.headers_mut().insert("userName", username.as_str().parse().unwrap());
                    drop(bl);
                    let response: axum::http::Response<axum::body::Body> = next.run(req).await;
                    response
                },
            ));

        let router = axum::Router::new()
            .route("/login", axum::routing::post(login))
            .nest("/v1", v1router)
            .layer(axum::Extension(Arc::new(WebService::new(
                AuthService::new("redis://localhost:6379"),
            ))));

        let l = tokio::net::TcpListener::bind("0.0.0.0:8080")
            .await
            .expect("bind 8080 failed");
        axum::serve(l, router).await.expect("serve server failed");
    }
    async fn login(
        Extension(ext): Extension<Arc<WebService>>,
        Json(req): Json<args::Login>,
    ) -> Json<resp::Response<String>> {
        let mut bll = ext.auth_service.lock().await;
        match bll.login(req.username, req.password) {
            None => Json(resp::Response::error("login failed")),
            Some(token) => Json(resp::Response::ok(token)),
        }
    }
    async fn greet(headers: HeaderMap) -> Json<resp::Response<String>> {
        let username = headers.get("userName").unwrap().to_str().unwrap();
        Json(resp::Response::ok(format!("hello {username}")))
    }
    struct WebService {
        auth_service: Mutex<AuthService>,
    }
    impl WebService {
        pub fn new(auth: AuthService) -> Self {
            Self {
                auth_service: Mutex::new(auth),
            }
        }
    }
    struct AuthService {
        red_conn: redis::Client,
    }
    impl AuthService {
        pub fn new(uri: &str) -> Self {
            Self {
                red_conn: redis::Client::open(uri).expect("connect to redis failed"),
            }
        }
        pub fn login(&mut self, username: String, password: String) -> Option<String> {
            if username != password {
                return None;
            }
            let now = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_millis();
            let now = (now % (1 << 32)) as u32;
            let token = format!("{:2x}", now);
            let mut conn = self
                .red_conn
                .get_connection()
                .expect("get redis connection failed");
            let ans = redis::cmd("set")
                .arg(token.as_str())
                .arg(username)
                .arg("EX")
                .arg(60 * 60)
                .exec(&mut conn);
            if let Err(err) = ans {
                eprintln!("set token to redis error {err}");
            }
            Some(token)
        }
        pub fn check_token(&mut self, token: String) -> Option<String> {
            let mut conn = self
                .red_conn
                .get_connection()
                .expect("get redis connection failed");
            let ans = redis::cmd("get")
                .arg(token.as_str())
                .query::<String>(&mut conn);
            match ans {
                Ok(data) => Some(data),
                Err(err) => {
                    eprintln!("check from redis failed {err}");
                    None
                }
            }
        }
    }
    mod args {
        #[derive(serde::Deserialize)]
        pub struct Login {
            pub username: String,
            pub password: String,
        }
    }
    mod resp {
        #[derive(serde::Serialize)]
        pub struct Response<T> {
            ok: bool,
            reason: &'static str,
            data: Option<T>,
        }
        impl<T> Response<T> {
            pub fn ok(data: T) -> Self {
                Self {
                    ok: true,
                    reason: "",
                    data: Some(data),
                }
            }
            pub fn error(reason: &'static str) -> Self {
                Self {
                    ok: false,
                    reason: reason,
                    data: None,
                }
            }
        }
    }

}

结果展示

测试脚本

shell 复制代码
#!/bin/bash
function login(){
	curl -H 'Content-Type:application/json' -X POST http://localhost:8080/login -d '{"username":"jesko","password":"jesko"}'
}
function greet(){
	curl -H "token:$token" -X GET http://localhost:8080/v1/greet
}
for va in "$@";do
	$va
done
相关推荐
喵手1 分钟前
反射机制:你真的了解它的“能力”吗?
java·后端·java ee
用户466537015052 分钟前
git代码压缩合并
后端·github
武大打工仔6 分钟前
从零开始手搓一个MVC框架
后端
开心猴爷11 分钟前
移动端网页调试实战 Cookie 丢失问题的排查与优化
后端
用户57240561411 分钟前
解析Json
后端
舒一笑12 分钟前
Mac 上安装并使用 frpc(FRP 内网穿透客户端)指南
后端·网络协议·程序员
每天学习一丢丢18 分钟前
Spring Boot + Vue 项目用宝塔面板部署指南
vue.js·spring boot·后端
邹小邹18 分钟前
Go 1.25 强势来袭:GC 速度飙升、并发测试神器上线,内存检测更精准!
后端·go
lichenyang45322 分钟前
管理项目服务器连接数据库
数据库·后端