如何在 actix-web 2.0 中间件中获取 web::Data<Pool<MySql>>?

How can I get web::Data<Pool<MySql>> in actix-web 2.0 middleware?

通常,我可以像这样在 App 上设置数据并从 web::Data 获取数据:

let pool = sqlx::MySqlPool::connect("mysql://xxx")
    .await
    .expect("Mysql Connect error!");
HttpServer::new(move || {
    // set the data pool: Pool<MySQL>
    App::new()
        .data(pool.clone())
        .service(web::resource("/home").route(web::get().to(get_xxx)))
})
.bind("0.0.0.0:8000")?
.run()
.await;
// get the data: pool: Pool<MySQL> from argument.
pub async fn get_xxx(
    pool: web::Data<Pool<MySql>>,
    path: web::Path<String>,
) -> Result<HttpResponse, Error> {
    let mut pool = pool.clone();
    todo!()
}

如何在中间件中获取pool: Pool<MySQL>

这是一个中间件示例:

use std::task::{Context, Poll};

use actix_service::{Service, Transform};
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::{Error, HttpResponse};
use futures::future::{ok, Either, Ready};

pub struct CheckLogin;

impl<S, B> Transform<S> for CheckLogin
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = CheckLoginMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(CheckLoginMiddleware { service })
    }
}
pub struct CheckLoginMiddleware<S> {
    service: S,
}

use actix_web::http::HeaderValue;
impl<S, B> Service for CheckLoginMiddleware<S>
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Either<S::Future, Ready<Result<Self::Response, Self::Error>>>;

    fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
        self.service.poll_ready(cx)
    }

    fn call(&mut self, req: ServiceRequest) -> Self::Future {
        let pool = todo!(); // here! how can I get the pool: Pool<MySQL>
    }
}

我不知道actix-web如何将参数传递给最终的路由函数。

我觉得req.app_data()就是你想要的。

fn call(&mut self, req: ServiceRequest) -> Self::Future {
    let pool = req.app_data::<web::Data<Pool<MySql>>>().unwrap();
    // ...
}

当您调用 App::data() 时,actix-web 会将给定值存储到内部映射中。关键是它的类型。当您调用 app_data() 时,您尝试从该映射中获取值。

I don't know how actix-web passes the arguments to the final route function.

最后的路由函数只是调用 <ParamType as FromRequest>::from_request 来获取值。例如,当您尝试获取 web::Data:

时发生的情况
impl<T: 'static> FromRequest for Data<T> {
    type Config = ();
    type Error = Error;
    type Future = Ready<Result<Self, Error>>;

    #[inline]
    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
        if let Some(st) = req.app_data::<Data<T>>() {
            ok(st.clone())
        } else {
            log::debug!(
                "Failed to construct App-level Data extractor. \
                 Request path: {:?}",
                req.path()
            );
            err(ErrorInternalServerError(
                "App data is not configured, to configure use App::data()",
            ))
        }
    }
}

您可以看到在 from_request 中,web::Datareq.app_data().

获取值(您的 web::Data<T> 参数)

顺便说一句,我注意到你用的是sqlx,它的Pool里面其实有一个Arc。但是Data里面还有一个Arc。这是多余的。如果你介意这一点,你可以定义一个 Pool 的包装器类型,并为其实现 FromRequest 特性。