actix-web implémentation plus flexible de l'interception d'authentification

La version d'actix-web utilisée dans cet article est la 4.1.0

Habituellement, lors de la mise en œuvre de l'interception d'authentification, nous penserons à utiliser un middleware. Comme actix-web-httpauth , mais il n'est pas assez flexible à utiliser. Par exemple, une interface n'a pas besoin d'être interceptée, ou deux réponses différentes sont renvoyées lors de la connexion et de la non-connexion.

Cet article vous amène à connaître une méthode amusante de Handler<Args> et FromRequest de actix-web .

Base de principe

Astuce : Si vous connaissez déjà le principe, ou si vous ne vous attendez pas à connaître le principe, vous pouvez ignorer cette section.

Dans actix-web, la méthode du gestionnaire de paramétrage de route est définie comme suit :

pub fn to<F, Args>(self, handler: F) -> Self
where
    F: Handler<Args>,
    Args: FromRequest + 'static,
    F::Output: Responder + 'static,
{
    self.service = handler_service(handler);
    self
}

Seule la handler_serviceméthode , et la contrainte du gestionnaire de paramètres est Handler<Args>, qui est définie comme suit :

pub trait Handler<Args>: Clone + 'static {
    type Output;
    type Future: Future<Output = Self::Output>;

    fn call(&self, args: Args) -> Self::Future;
}

Ici, nous pouvons deviner que Args représente n paramètres. Immédiatement en dessous de cette définition, on peut voir la handler_serviceméthode :

pub(crate) fn handler_service<F, Args>(handler: F) -> BoxedHttpServiceFactory
where
    F: Handler<Args>,
    Args: FromRequest,
    F::Output: Responder,
{
    boxed::factory(fn_service(move |req: ServiceRequest| {
        let handler = handler.clone();

        async move {
            let (req, mut payload) = req.into_parts();

            let res = match Args::from_request(&req, &mut payload).await {
                Err(err) => HttpResponse::from_error(err),

                Ok(data) => handler
                    .call(data)
                    .await
                    .respond_to(&req)
                    .map_into_boxed_body(),
            };

            Ok(ServiceResponse::new(req, res))
        }
    }))
}

Comme nous pouvons le voir Argsà partir des contraintes de , nous n'avons qu'à implémenter FromRequest, qui peuvent être utilisés comme paramètres. C'est-à-dire FromRequestque vous pouvez écrire le nom et le type de paramètre que vous souhaitez dans n'importe quelle position de paramètre dans la méthode Handle.

Par exemple : Lorsque vous souhaitez obtenir la méthode Http dans le paramètre, vous pouvez procéder comme suit :

async fn hander(method: Method) -> HttpResponse {...}

/// 因为 actix-web 内部 Method 实现了 FromRequest
impl FromRequest for Method {
    type Error = Infallible;
    type Future = Ready<Result<Self, Self::Error>>;

    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
        ok(req.method().clone())
    }
}

from_requestAprès cela, si les paramètres sont extraits avec succès, appelez handler.callla méthode pour exécuter votre interface, et si elle échoue, elle FromRequestrenverra l'erreur définie dans , afin que vous puissiez définir librement ce qui est renvoyé après l'échec de l'extraction des paramètres.

不过看到这里还是会有疑问,这个 Args 到底传入的是什么?

让我们继续往下看,会看到一个宏定义:

macro_rules! factory_tuple ({ $($param:ident)* } => {
    impl<Func, Fut, $($param,)*> Handler<($($param,)*)> for Func
    where
        Func: Fn($($param),*) -> Fut + Clone + 'static,
        Fut: Future,
    {
        type Output = Fut::Output;
        type Future = Fut;

        #[inline]
        #[allow(non_snake_case)]
        fn call(&self, ($($param,)*): ($($param,)*)) -> Self::Future {
            (self)($($param,)*)
        }
    }
});

factory_tuple! {}
factory_tuple! { A }
factory_tuple! { A B }
factory_tuple! { A B C }
factory_tuple! { A B C D }
factory_tuple! { A B C D E }
factory_tuple! { A B C D E F }
factory_tuple! { A B C D E F G }
factory_tuple! { A B C D E F G H }
factory_tuple! { A B C D E F G H I }
factory_tuple! { A B C D E F G H I J }
factory_tuple! { A B C D E F G H I J K }
factory_tuple! { A B C D E F G H I J K L }

如果你不了解宏的话,可以看看 The Book 还有 rust by example 这里不做赘述了。

可以看到这里一揽子实现了各个类型,数量范围 [0, 12],也就是把 Args 为 (), (A), (A, B)... 等都实现了。所以当你的接口超过12个参数的时候就会报错(应该不会有接口超过12个参数吧?)。

回答上面的问题: Args 就是 一堆 tuple,个数从 0 到 12。

那么又有新的问题来了,在上面的例子中 Method 实现了 FromRequest,但是在 handler_service 方法中,from_request 只被调用了一次,不应该有几个参数就调用几次吗?

这时候就需要再次看源码了,在 FromRequest 文档中点击右上角的 [source] (确保你的文档打开的版本是4.1.0),找到 312 行,会发现一个隐藏的模块 tuple_from_req,代码有点多,这里就只贴最关键的部分:

/// FromRequest implementation for tuple
#[allow(unused_parens)]
impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+)
{
    type Error = Error;
    type Future = $fut<$($T),+>;

    fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
        $fut {
            $(
                $T: ExtractFuture::Future {
                    fut: $T::from_request(req, payload)
                },
            )+
        }
    }
}

能看到这里,就已经水落石出了,原来是又用了个宏来帮tuple一个个调用 from_request 方法。所以在 handler_service 里的 Args 实际上是一个 tuple, 调用的 from_request 方法是从这里开始。

身份拦截的实现

简单定义一个用户数据,包含一个字段,代表用户id。

pub struct UserData {
    pub id: i32,
}

在我们登陆之后需要拿到拦截未登陆的用户的时候只需要在参数上写上它就好了:

async fn get_info(user: UserData) -> impl Responder {
    HttpResponse::Ok().finish()
}

Bien sûr, cela ne prendra pas effet maintenant, nous devons l' UserDataimplémenter FromRequest, nous utilisons JWT comme vérification, car ce n'est pas le but, ignorez-le ici, le code complet sera placé sur le référentiel open source.

use actix_web::{dev::Payload, error, Error, FromRequest, HttpRequest};
use std::future::{ready, Ready};
impl FromRequest for UserData {
    type Error = Error;

    type Future = Ready<Result<Self, Self::Error>>;

    fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
        ready({
            let auth = req.headers().get("Authorization");
            if let Some(val) = auth {
                let token = val.to_str().unwrap().split("Bearer ").collect::<Vec<&str>>().pop().unwrap();
                let result = auth::validate_token(token);
                match result {
                    Ok(data) => Ok(UserData { id: data.claims.id }),
                    Err(e) => {
                        eprintln!("{}", e);
                        Err(error::ErrorBadRequest("Invalid Authorization"))
                    }
                }
            } else {
                Err(error::ErrorUnauthorized("Authorization Not Found"))
            }
        })
    }
}

Après avoir implémenté cela, l'interface ne pose plus de problème.

Quand on veut se connecter et ne pas se connecter, on peut accéder à une interface, mais le contenu retourné par l'interface est différent, on peut aussi utiliser cette méthode pour réaliser :

/// 无需登陆
/// 登陆前后拿到的数据不完全相同
async fn get_public_info(user: Option<UserData>) -> impl Responder {
    if let Some(user) = user {
        HttpResponse::Ok().json(format!("public data with {}", user.id))
    } else {
        HttpResponse::Ok().json("public data")
    }
}

En effet, à l'intérieur d'actix-web, vous implémentez Option<T: FromRequest>la situation.

De plus, lorsque vous n'avez pas besoin d'utiliser l'identifiant de l'utilisateur dans l'interface, mais que vous souhaitez tout de même intercepter, c'est très simple, il suffit d'écrire les paramètres et de ne pas l'utiliser.

Comment, est-ce très flexible? :)

test des interfaces

connexion

# 登陆用户名为 123 密码为 456 的账号,返回值: "Bearer ..."
curl http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"id":123,"pwd":"456"}'

# 导出返回值到环境变量以便后续使用
export token="Bearer ..."

Obtenir des informations

# 携带token,返回值为200
curl http://127.0.0.1:8000/info -v -X POST -H "Authorization:$token"

# Headers 不包含 Authorization 返回 401 Unauthorized 未验证
curl http://127.0.0.1:8000/info -v -X POST

# 输入错误的token,返回 400 Bad Request 返回 错误请求
curl http://127.0.0.1:8000/info -v -X POST -H "Authorization:Bearer ErrorToken"

Obtenir des informations publiques

# 未携带token请求,返回公共数据:“public data" 不包含个人信息。
curl http://127.0.0.1:8000/public -v -X POST

# 携带了正确的token,返回带有个人数据的公共数据 ”public data with 123“
curl http://127.0.0.1:8000/public -v -X POST -H "Authorization:$token"

Adresse de l'entrepôt

actix-handler-args-tutoriel

Je suppose que tu aimes

Origine juejin.im/post/7121239259985477668
conseillé
Classement