Rust actix-web 中间件修改请求数据

Rust actix-web middleware modifying request data

是否有推荐的方法来修改在 actix-web 上收到的请求。

有没有修改的例子 我正在寻找将数据添加到请求对象并使其可供下游中间件和处理程序处理的方法。

“https://actix.rs/docs/middleware/”的中间件文档说以下内容“Actix-web 的中间件系统允许我们向 request/response 处理添加额外的行为。中间件可以连接到一个传入的请求进程,使我们能够修改请求以及停止请求处理以 return 尽早响应。“

该页面没有关于如何修改请求的示例。

让我们看看下面的代码(从“https://actix.rs/docs/middleware/”获得),以某种方式向请求添加数据的代码是什么?

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

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

// There are two steps in middleware processing.
// 1. Middleware initialization, middleware factory gets called with
//    next service in chain as parameter.
// 2. Middleware's call method gets called with normal request.
pub struct SayHi;

// Middleware factory is `Transform` trait from actix-service crate
// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S> for SayHi
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = SayHiMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(SayHiMiddleware { service })
    }
}

pub struct SayHiMiddleware<S> {
    service: S,
}

impl<S, B> Service for SayHiMiddleware<S>
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Pin<Box<dyn Future<Output = 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 {
        println!("Hi from start. You requested: {}", req.path());

        let fut = self.service.call(req);

        Box::pin(async move {
            let res = fut.await?;

            println!("Hi from response");
            Ok(res)
        })
    }
}

我也在研究如何做到这一点,令人失望的是他们在他们的文档中谈论它但没有显示任何示例。

我发现您可以按如下方式编辑 headers 或扩展,否则我不知道如何编辑它。

Headers

  • 从服务请求中获取 headers,let headers = req.headers_mut()
  • 使用 HeaderMap,您可以从那里插入、删除、清除等
  • 插入示例:headers.insert(HeaderName::from_lowercase(b"content-length").unwrap(), HeaderValue::from_static("hello"));

Extensions

  • 获取请求的扩展 let extensions = req.extensions_mut()
  • 加入分机extensions.insert("foo".to_string());
  • 一个班轮:req.extensions_mut().insert("foo".to_string());
  • 稍后获取该扩展名:req.extensions().get::<String>()

当然可以修改来自中间件的请求和相关联的响应。这是一个简短的示例(适用于 Actix v4):

use x_contrib::{actix_http, actix_web, body, futures, log, HttpMessage, HttpResponseBuilder};
use std::cell::RefCell;

use log::Level;
use std::pin::Pin;

use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::Error;
use futures::future::{ok, Ready};
use futures::task::{Context, Poll};
use std::future::Future;
use std::rc::Rc;
use std::str;

use crate::middleware::utility::{ApiMiddlewareUtility, MiddlewareUtility};
use crate::request;
use x_common::prelude::*;
use x_common::settings;
use x_contrib::actix_http::h1::Payload;
use x_contrib::body::{EitherBody, MessageBody};
use x_contrib::futures::future::err;
use x_contrib::futures::StreamExt;
use x_contrib::web::{Buf, BytesMut};

pub struct LogEvents;

impl<S: 'static, B> Transform<S, ServiceRequest> for LogEvents
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: MessageBody + 'static,
{
    type Response = ServiceResponse<EitherBody<B>>;
    type Error = Error;
    type Transform = LogEventsMiddleware<S>;
    type InitError = ();
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(LogEventsMiddleware {
            service: Rc::new(RefCell::new(service)),
        })
    }
}

pub struct LogEventsMiddleware<S> {
    service: Rc<RefCell<S>>,
}

impl<S: 'static, B> Service<ServiceRequest> for LogEventsMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: MessageBody + 'static,
{
    type Response = ServiceResponse<EitherBody<B>>;
    type Error = Error;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;

    actix_web::dev::forward_ready!(service);

    fn call(&self, mut req: ServiceRequest) -> Self::Future {
        let svc = self.service.clone();
        let log_level = settings::get_setting("log_level").unwrap_or("info".to_owned());

        Box::pin(async move {
            match log_level.as_str() {
                "debug" | "trace" | "info" => {
                    let route = req.path().to_owned();

                    /* we only process requests that are json */
                    if !MiddlewareUtility::is_json_request(&req) {
                        let res: ServiceResponse = svc.call(req).await?.map_into_boxed_body();
                        return Ok(res.map_into_right_body());
                    }

                    /* extract and log the request */
                    let mut request_body = BytesMut::new();
                    while let Some(chunk) = req.take_payload().next().await {
                        request_body.extend_from_slice(&chunk?);
                    }

                    match str::from_utf8(&request_body.to_vec().as_slice()) {
                        Ok(str) => {
                            /* identify routes that we will redact the body from,
                            these are items that contain sensitive information we do not want to log
                             */
                            match route.as_str() {
                                "/x/protected_endpoint" => {
                                    tracing::info!({ body = "Redacted" }, "HTTP Request");
                                }
                                _ => {
                                    tracing::info!({body = %str}, "HTTP Request");
                                }
                            }
                        }
                        Err(_) => {}
                    };

                    let (payload_sender, mut orig_payload) = Payload::create(true);
                    orig_payload.unread_data(request_body.freeze());
                    req.set_payload(actix_http::Payload::from(orig_payload));

                    /* extract and log the response */
                    let res: ServiceResponse = svc.call(req).await?.map_into_boxed_body();
                    if !MiddlewareUtility::is_json_response(&res) {
                        return Ok(res.map_into_right_body());
                    }

                    let res_status = res.status().clone();
                    let res_headers = res.headers().clone();
                    let new_request = res.request().clone();
                    let body_bytes = body::to_bytes(res.into_body()).await?;
                    match str::from_utf8(&body_bytes) {
                        Ok(str) => {
                            tracing::info!({body = %str}, "HTTP Response");
                            str
                        }
                        Err(_) => "Unknown",
                    };

                    /* build an identical response */
                    let mut new_response = HttpResponseBuilder::new(res_status);
                    for (header_name, header_value) in res_headers {
                        new_response.insert_header((header_name.as_str(), header_value));
                    }
                    let new_response = new_response.body(body_bytes.to_vec());

                    Ok(ServiceResponse::new(
                        new_request,
                        new_response.map_into_right_body(),
                    ))
                }
                _ => {
                    let res: ServiceResponse = svc.call(req).await?.map_into_boxed_body();
                    Ok(res.map_into_right_body())
                }
            }
        })
    }
}

此特定示例与 tracing_actix_web 集成,这是一个出色的遥测工具,如果 request/response 是 json,则将 request/response 记录到 jaeger。

有一点要注意,据我所知,一旦你读出请求,你必须重新组装它,与响应一样。因此这个例子在做什么。