Actix-web 中间件、handler 间传递数据

背景:中间件 A 产生的数据,需在后续的其他中间件、Handler 中用到。 例如:Handler 中使用 Auth 中间件从 token 中解出的 user_id

在 golang 的 gin 框架中很容易实现:

func Auth(ctx *gin.Context){
	// ...
	ctx.Set("uid", uid)
}

func Foo(ctx *gin.Context){
	uid := ctx.GetInt("uid")
	// ...
}

Gin 的中间件与普通 handler 的类型相同,核心都是 gin.Context

Rust 的 actix-web 中没有上下文的概念,其中间件形式为:

pub trait Middleware<S>: 'static {

    fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
        Ok(Started::Done)
    }

    fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
        Ok(Response::Done(resp))
    }

    fn finish(&self, req: &HttpRequest<S>, resp: &HttpResponse) -> Finished {
        Finished::Done
    }
}

Handler 则是:

pub fn index(req: &HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
	// ...
}

请求没有具体上下文的概念,只能另辟蹊径。

1. 状态

Actix-web 的状态由一个 app 所有路由共享,并不是以请求为单位的状态。 HttpRequest::state() 可以进行只读访问,修改需要通过内部可变性实现。 因此认为此场景不适合用状态实现。

2. Session

SessionStorage 中间件提供会话管理解决方案,默认只有 Cookie 作为会话存储的实现。

3. Extensions

通过文档了解到 extensions 用法如下:

impl<T> middleware::Middleware<T> for Auth {
    fn start(&self, req: &HttpRequest<T>) -> Result<Started> {
	// ...
        req.extensions_mut().insert(uid);
        Ok(Started::Done)
    }
}
pub fn index(req: &HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
	// ...
	let uid = req.extensions().get::<i64>();
	// ...
}

Extensions 大致实现:

type AnyMap = HashMap<TypeId, Box<Any>, BuildHasherDefault<IdHasher>>;

#[derive(Default)]
/// A type map of request extensions.
pub struct Extensions {
    map: AnyMap,
}

结论

目前来看,actix-webextensions 最符合需求,但具体实现不同与 go 的 map[string]interface{}, 而是 Map<TypeId, Box<Any>> ,因此无法直接存放相同类型的多个值。传递的值较多、类型有重复时, 需要创建相应的类型来「打包」。

rust