在Warp中是否有一种方法可以将验证作为过滤器的一部分来完成?

9

我已经定义了一个路由和端点函数,并注入了一些依赖项。

pub fn route1() -> BoxedFilter<(String, ParamType)> {
    warp::get()
        .and(warp::path::param())
        .and(warp::filters::query::query())
        .and(warp::path::end())
        .boxed()
}

pub async fn handler1(
    query: String,
    param: ParamType,
    dependency: DependencyType,
) -> Result<impl warp::Reply, warp::Rejection> {
}

let api = api::routes::route1()
    .and(warp::any().map(move || dependency))
    .and_then(api::hanlders::hander1);

所有似乎都很正常。

然而,我想要一个能够坐在多个端点前面的东西,检查查询参数中是否有有效密钥。在 handler1 中,我可以添加:

if !param.key_valid {
    return Ok(warp::reply::with_status(
        warp::reply::json(&""),
        StatusCode::BAD_REQUEST,
    ));
}

我不想对每个处理程序都添加这个。

似乎我可以通过filter来实现,但我想不出如何做到。我尝试使用.map(),但返回多个项目会将其转换为元组,并且我必须更改下游函数签名。理想情况下,我希望找到一种方式来添加验证或其他过滤器,可以拒绝请求,而不需要任何下游值知道它们。


很难回答你的问题,因为它没有包含一个 [MRE]。我们无法确定代码中存在哪些 crates(及其版本)、types、traits、fields等。如果可能的话,您可以在Rust Playground上尝试重现错误,否则可以在全新的Cargo项目中进行,然后[编辑]您的问题以包括额外的信息。这里有一些Rust特定的MRE提示,您可以使用它们来缩小您的原始代码以便在此处发布。谢谢! - Shepmaster
一旦验证完成,handler1内是否需要使用param - Shepmaster
1个回答

7

这可以通过 warp 的 拒绝示例 很好地展示:

拒绝表示过滤器不应继续处理请求的情况,但是另一个过滤器 可以 处理它。

从“div-by”头中提取分母,或使用 DivideByZero 拒绝。

您需要:

  1. 使用 Filter::and_then 来使用现有的过滤器(在本例中为 query())并执行验证。如果验证失败,返回自定义拒绝。
  2. 使用 Filter::recover 适当处理自定义拒绝和任何其他可能的错误。

应用于您的情况:

use serde::Deserialize;
use std::{convert::Infallible, net::IpAddr};
use warp::{filters::BoxedFilter, http::StatusCode, reject::Reject, Filter, Rejection, Reply};

fn route1() -> BoxedFilter<(String, ParamType)> {
    warp::get()
        .and(warp::path::param())
        .and(validated_query())
        .and(warp::path::end())
        .boxed()
}

#[derive(Debug)]
struct Invalid;
impl Reject for Invalid {}

fn validated_query() -> impl Filter<Extract = (ParamType,), Error = Rejection> + Copy {
    warp::filters::query::query().and_then(|param: ParamType| async move {
        if param.valid {
            Ok(param)
        } else {
            Err(warp::reject::custom(Invalid))
        }
    })
}

async fn report_invalid(r: Rejection) -> Result<impl Reply, Infallible> {
    let reply = warp::reply::reply();

    if let Some(Invalid) = r.find() {
        Ok(warp::reply::with_status(reply, StatusCode::BAD_REQUEST))
    } else {
        // Do better error handling here
        Ok(warp::reply::with_status(
            reply,
            StatusCode::INTERNAL_SERVER_ERROR,
        ))
    }
}

async fn handler1(
    _query: String,
    _param: ParamType,
    _dependency: DependencyType,
) -> Result<impl warp::Reply, warp::Rejection> {
    Ok(warp::reply::reply())
}

struct DependencyType;

#[derive(Deserialize)]
struct ParamType {
    valid: bool,
}

#[tokio::main]
async fn main() {
    let api = route1()
        .and(warp::any().map(move || DependencyType))
        .and_then(handler1)
        .recover(report_invalid);

    let ip: IpAddr = "127.0.0.1".parse().unwrap();
    let port = 8888;
    warp::serve(api).run((ip, port)).await;
}

并且去除无关行的curl输出:

% curl -v '127.0.0.1:8888/dummy/?valid=false'
< HTTP/1.1 400 Bad Request

% curl -v '127.0.0.1:8888/dummy/?valid=true'
< HTTP/1.1 200 OK

Cargo.toml

[dependencies]
warp = "0.2.2"
serde = { version = "1.0.104", features = ["derive"] }
tokio = { version = "0.2.13", features = ["full"] }

这似乎已经非常接近我所寻找的了。有没有办法在BoxedFilter上执行它,以便它会像 let api = route1().and(validated_query).and... 这样显示,而不是在路由定义阶段执行? - CyanRook

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接