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

rust rest api接口开发

所需依赖

  • axum
  • tokio
  • redis
cargo add axum redis
cargo add tokio --features=full

路由服务创建和运行

//子路由
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:链式调用,和重新赋值
链式调用

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

重新赋值

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

let router=router.route("/greet",get(||async{
	Html("hello")
}));

这个接口也可以这样写

let router=router.route("/greet",get(greets));

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

中间件

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注册

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

以登陆和鉴权接口演示

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

#[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,
                }
            }
        }
    }

}

结果展示

测试脚本

#!/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

你可能感兴趣的:(rust,开发语言,后端,中间件)