You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Actix中间件中异步函数的生命周期问题求助

Actix-web JWT中间件生命周期问题解决

问题场景

使用Rust的actix和sqlx构建API,开发JWT中间件时,尝试将授权用户信息存入请求扩展并传递到端点,代码实现时遇到生命周期错误。

问题代码

fn call(&self, req: ServiceRequest) -> Self::Future {
    if ApiEnv::skip_auth() {
        return Box::pin(self.service.call(req));
    }

    Box::pin(async move {
        let u = {
            let db: &Data<Database> = req.app_data().unwrap();
            let pool = db.pool.clone();
            let headers = req.headers().clone();
            let mut extensions = req.extensions_mut();

            match authorize(pool, headers).await {
                Ok(u) => {
                    extensions.insert(u);
                    Ok(())
                }
                Err(e) => Err(e),
            }
        };

        match u {
            Ok(_) => self.service.call(req).await,
            Err(e) => Err(e),
        }
    })
}

错误信息

error: lifetime may not live long enough
   --> src/middleware/jwt.rs
    |
    |     fn call(&self, req: ServiceRequest) -> Self::Future {
    |             - let's call the lifetime of this reference `'1`
...
    | /     Box::pin(async move {
    | |       let u = {
    | |         let db: &Data<Database> = req.app_data().unwrap();
    | |         let pool = db.pool.clone();
...   |
    | |       }
    | |     })
    | |______^ returning this value requires that `'1` must outlive `'static`

问题核心:Box::pin内部调用self.service.call导致生命周期不匹配;若将self.service.call(req)移到外部,req会提前被移动,无法修改扩展插入用户信息。

解决方案

问题根源是async move闭包捕获了&self,而Actix中间件的Future要求'static生命周期,需将self.service转为可安全在异步闭包中持有的类型。

方案1:用Arc包裹Service

调整中间件结构体,用Arc包裹Service,通过克隆Arc让闭包拥有独立的Service所有权:

use std::sync::Arc;

// 中间件结构体定义
struct JwtMiddleware<S> {
    service: Arc<S>,
}

// 实现call方法
fn call(&self, req: ServiceRequest) -> Self::Future {
    if ApiEnv::skip_auth() {
        return Box::pin(self.service.call(req));
    }

    // 克隆Arc,让闭包持有独立的Service引用
    let service = self.service.clone();

    Box::pin(async move {
        let auth_result = {
            let db: &Data<Database> = req.app_data().unwrap();
            let pool = db.pool.clone();
            let headers = req.headers().clone();
            let mut extensions = req.extensions_mut();

            match authorize(pool, headers).await {
                Ok(user) => {
                    extensions.insert(user);
                    Ok(())
                }
                Err(e) => Err(e),
            }
        };

        match auth_result {
            Ok(_) => service.call(req).await,
            Err(e) => Err(e.into()),
        }
    })
}

方案2:用ServiceExt::boxed转换Service

利用Actix提供的boxed方法将Service转为BoxService(实现了Clone),克隆后移入闭包:

use actix_web::dev::ServiceExt;

fn call(&self, req: ServiceRequest) -> Self::Future {
    if ApiEnv::skip_auth() {
        return Box::pin(self.service.call(req));
    }

    // 将Service转为BoxService并克隆
    let service = self.service.boxed().clone();

    Box::pin(async move {
        let auth_result = {
            let db: &Data<Database> = req.app_data().unwrap();
            let pool = db.pool.clone();
            let headers = req.headers().clone();
            let mut extensions = req.extensions_mut();

            match authorize(pool, headers).await {
                Ok(user) => {
                    extensions.insert(user);
                    Ok(())
                }
                Err(e) => Err(e),
            }
        };

        match auth_result {
            Ok(_) => service.call(req).await,
            Err(e) => Err(e.into()),
        }
    })
}

关键说明

两种方案都是通过转移/克隆Service的所有权,让异步闭包独立持有Service,避免生命周期绑定到&self,满足'static生命周期要求。


内容的提问来源于stack exchange,提问作者Eatenpancreas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 02:13:19