Rust Rocket框架:为何请求守卫可复制State,Rocket::state却不行?
问题原因与解决方法
为什么路由里能拿到可变的Connection<Redis>?
你之前的post_thing路由能直接获取mut Connection<Redis>作为参数,本质是因为Rocket的请求守卫机制在帮你做连接池管理:你注册到Rocket中的实际上是Redis的Client(或连接池),而Connection<Redis>是请求守卫从连接池中取出的独占连接实例——每个请求会拿到一个独立的、可变的连接,用完后自动归还到池里,所以可以直接用mut修饰。
你误以为Connection<Redis>是全局State,但实际上它是请求级别的临时资源,不是全局共享的State对象。
你当前代码的问题
在FromRequest中,你尝试通过state::<Connection<Redis>>()获取全局State,但这本身就是错误的:Connection是单个连接,不能被多个请求共享,所以不应该被注册为Rocket的State。而且全局State的引用是不可变的(因为要保证多线程安全共享),自然无法转为可变引用执行Redis命令。
正确的实现方式
1. 注册Redis Client为全局State
首先在启动函数中注册Redis Client(线程安全,可全局共享):
use redis::Client; use rocket::{Build, Rocket}; #[launch] fn rocket() -> Rocket<Build> { // 初始化Redis Client let redis_client = Client::open("redis://localhost:6379/") .expect("Failed to connect to Redis"); rocket::build() .manage(redis_client) // 将Client注册为全局State .mount("/", routes![post_thing]) }
2. 在FromRequest中从Client获取连接
修改User类型的FromRequest实现,先获取全局的Client引用,再从中异步获取独占的可变连接:
use rocket::request::{FromRequest, Outcome}; use rocket::http::Status; use redis::{Client, AsyncCommands}; use std::fmt; // 定义守卫错误类型 #[derive(Debug)] enum UserGuardError { FailedToGetRedisConnection, SessionIdMissing, UserNotFound, } // 为错误实现Display(Rocket需要) impl fmt::Display for UserGuardError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { UserGuardError::FailedToGetRedisConnection => write!(f, "Failed to get Redis connection"), UserGuardError::SessionIdMissing => write!(f, "Session ID missing from request"), UserGuardError::UserNotFound => write!(f, "User not found for session"), } } } struct User { id: String, // 其他用户字段 } #[rocket::async_trait] impl<'r> FromRequest<'r> for User { type Error = (Status, UserGuardError); async fn from_request(request: &'r rocket::Request<'_>) -> Outcome<Self, Self::Error> { // 1. 获取全局Redis Client的引用 let redis_client = match request.rocket().state::<Client>() { Some(client) => client, None => { return Outcome::Error(( Status::InternalServerError, UserGuardError::FailedToGetRedisConnection, )); } }; // 2. 从Client获取异步连接(可变,独占) let mut redis_conn = match redis_client.get_async_connection().await { Ok(conn) => conn, Err(_) => { return Outcome::Error(( Status::InternalServerError, UserGuardError::FailedToGetRedisConnection, )); } }; // 3. 从请求中获取Session ID(示例从Cookie读取) let session_id = match request.cookies().get("session_id") { Some(cookie) => cookie.value().to_string(), None => { return Outcome::Error(( Status::Unauthorized, UserGuardError::SessionIdMissing, )); } }; // 4. 查询Redis获取用户ID let user_id: Option<String> = match redis_conn.get(&session_id).await { Ok(id) => id, Err(_) => { return Outcome::Error(( Status::InternalServerError, UserGuardError::FailedToGetRedisConnection, )); } }; let user_id = match user_id { Some(id) => id, None => { return Outcome::Error(( Status::Unauthorized, UserGuardError::UserNotFound, )); } }; // 5. 构造并返回User实例 Outcome::Success(User { id: user_id }) } }
3. 修正原路由的连接获取逻辑(可选)
原路由的Connection<Redis>参数其实依赖于Rocket的redis请求守卫,如果你已经注册了Client State,也可以手动从Client获取连接,逻辑和上面一致:
use rocket::State; #[post("/thing", data = "<msg>")] async fn post_thing(redis_client: &State<Client>, msg: Json<Thing<'_>>) -> Value { let mut redis_conn = redis_client.get_async_connection().await.unwrap(); redis::cmd("SET") .arg(&[msg.name, msg.description]) .query_async::<_, ()>(&mut redis_conn) .await .unwrap(); json!({ "name": msg.name, "description": msg.description }) }
内容的提问来源于stack exchange,提问作者come-at-me-wolf
相关产品推荐
相关产品推荐

