使用 tower::service_fn 将路由路径参数提取到 Axum 处理程序中?

问题描述 投票:0回答:1

是否可以将 URL 路径参数提取到 Axum

route_service
处理程序中,该处理程序是使用下面我的
tower::service_fn
中看到的
main
错误处理方法实现的?从 Tower 文档中,我看到
service_fn
收到了请求,但除此之外,我无法弄清楚如何使用
Path
来提取我的
person_id
路径的
/person/:person_id
路径参数部分。

这是我用来实现迄今为止所拥有的内容的 Axum 文档页面:https://docs.rs/axum/latest/axum/error_handling/index.html#routing-to-fallible-services

我之所以实施此实施有几个原因。

  1. 我想使用
    ?
    运算符来从外部 API 获取实际响应,无论是成功还是错误。
  2. 我希望能够在出现错误时使用
    reqwest::Error
    状态代码,并为我的 API 的错误响应返回该状态代码。
async fn get_person(/*
    Is it possible to extract the person_id here given my implemntation?
     */) -> Result<Json<serde_json::Value>, reqwest::Error> {
    let request_url = format!("https://swapi.dev/api/people/{}", "1asdf".to_owned());
    let response = reqwest::get(request_url).await?;
    if response.status().is_success() {
        let json = response.json::<serde_json::Value>().await?;
        Ok(Json(json))
    } else {
        Err(response.error_for_status().unwrap_err())
    }
}

#[tokio::main]
async fn main() {
    dotenv().ok();
    let client = reqwest::Client::new();

    let cors = CorsLayer::new()
        .allow_methods([Method::GET])
        .allow_origin(Any);

    let faillible_person_service = tower::service_fn(|req: Request| async {
        let body = get_person().await?;
        Ok::<_, reqwest::Error>(body.into_response())
    });

    let app = Router::new()
        .route("/", get(get_weather))
        .route_service(
            "/person/:person_id",
            HandleError::new(faillible_person_service, handle_reqwest_error),
        )
        .layer(cors)
        .with_state(client);
    let listener = tokio::net::TcpListener::bind("127.0.0.1:1337")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn handle_reqwest_error(err: reqwest::Error) -> Response {
    // Rather than have my API return a 200, return the status code from the Reqwest call
    let status = err.status();
    let status = status.unwrap_or(reqwest::StatusCode::INTERNAL_SERVER_ERROR);
    let status_as_u16 = status.as_u16();
    let axum_status =
        StatusCode::from_u16(status_as_u16).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
    let res_json = Json(serde_json::json!({
        "error": {
            "message": format!("Something went wrong: {}", err),
        },
    }));
    return (axum_status, res_json).into_response();
}
rust rust-axum rust-tower
1个回答
0
投票

提取路径参数等数据(又名提取器),通过

FromRequest
FromRequestParts
特征来表达。路径参数只需要后者,因为主体不相关。

要在塔服务中使用它们,您可以拆分请求并手动调用提取器方法。像这样的东西:

use axum::extract::{FromRequestParts, Path, Request};
use axum::response::{IntoResponse, Response};
use axum::{Json, Router};

async fn get_person(person_id: String) -> Result<Json<serde_json::Value>, reqwest::Error> {
    ...
}

let faillible_person_service = tower::service_fn(|req: Request| async {
    let (mut req_parts, _req_body) = req.into_parts();

    let person_id = match Path::<String>::from_request_parts(&mut req_parts, &()).await {
        Ok(Path(person_id)) => person_id,
        Err(rejection) => return Ok(rejection.into_response()),
    };

    let body = get_person(person_id).await?;
    
    ...
});

match
是这样完成的,因为路径提取(以及大多数提取器)可能会失败,但 Axum 的设计方式是它的所有错误都会返回响应,所以这就是它的作用。您可以使用此模式使用更多
FromRequestParts
提取器。如果您需要主体(即
FromRequest
提取器),则必须最后完成并通过
Request::from_parts
给出重新组装的请求。


老实说,我真的不会推荐这个,而且你的理由听起来并不合理。如果您的目标只是简单地返回 reqwest 响应作为 Axum 响应,我在这里有一个答案,描述了您如何做到这一点:A proxy with axum 0.7 and reqwest 0.12 based on http 1?

© www.soinside.com 2019 - 2024. All rights reserved.