中间件

Middleware 中间件

Actix-web的中间件系统使我们可以在request/response处理中添加其他行为。中间件可以挂接到传入的请求过程中,使我们能够修改请求以及暂停请求处理以尽早返回响应。

中间件也可以加入响应处理。

通常,中间件参与以下操作:

  • 预处理请求
  • 后处理响应
  • 修改应用程序状态
  • 访问外部服务(redis,日志记录,会话)

中间件为每个App, scope, 或 Resource注册,并以与注册相反的顺序执行。 通常,中间件是一种实现Service traitTransform trait的类型。 trait中的每个方法都有一个默认实现。 每个方法都可以立即返回结果或*future*对象。

以下演示了如何创建一个简单的中间件:

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)
        })
    }
}

另外,对于简单的用例,可以使用wrap_fn创建临时的小型中间件:

use actix_service::Service;
use actix_web::{web, App};
use futures::future::FutureExt;

#[actix_rt::main]
async fn main() {
    let app = App::new()
        .wrap_fn(|req, srv| {
            println!("Hi from start. You requested: {}", req.path());
            srv.call(req).map(|res| {
                println!("Hi from response");
                res
            })
        })
        .route(
            "/index.html",
            web::get().to(|| async {
                "Hello, middleware!"
            }),
        );
}

Actix-web提供了几种有用的中间件,例如logging(日志记录),user sessions(用户会话),compress(压缩)等。

Logging 日志

日志记录是作为中间件实现的。通常将日志记录中间件注册为该应用程序的第一个中间件。日志中间件必须为每个应用程序注册。

Logger中间件使用标准日志crate记录信息。您应该为actix_web软件包启用Logger,以查看访问日志(env_logger或类似记录)。

Usage 用法

使用指定的format创建Logger中间件。可以使用default方法创建默认Logger,它使用默认格式:

  %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
use actix_web::middleware::Logger;
use env_logger::Env;

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    use actix_web::{App, HttpServer};

    env_logger::from_env(Env::default().default_filter_or("info")).init();

    HttpServer::new(|| {
        App::new()
            .wrap(Logger::default())
            .wrap(Logger::new("%a %{User-Agent}i"))
    })
    .bind("127.0.0.1:8088")?
    .run()
    .await
}

以下是默认日志记录格式的示例:

INFO:actix_web::middleware::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397
INFO:actix_web::middleware::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] "GET /index.html HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0" 0.000646

Format 格式

  • %% 百分号
  • %a 远程IP地址(如果使用反向代理,则为代理的IP地址)
  • %t 请求开始处理的时间
  • %P 为请求提供服务的child的进程ID
  • %r 第一行request
  • %s 响应状态码
  • %b 响应大小(以字节为单位),包括HTTP headers(标头)
  • %T 服务请求所用的时间,以秒为单位,浮动分数为.06f格式
  • %D serve(服务)请求所花费的时间(以毫秒为单位)
  • %{FOO}i request.headers[‘FOO’]
  • %{FOO}o response.headers[‘FOO’]
  • %{FOO}e os.environ[‘FOO’]

Default headers

要设置默认response headers(响应头),可以使用DefaultHeaders中间件。如果响应头已经包含指定的header,则DefaultHeaders中间件不会设置header。

use actix_web::{http, middleware, HttpResponse};

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    use actix_web::{web, App, HttpServer};

    HttpServer::new(|| {
        App::new()
            .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
            .service(
                web::resource("/test")
                    .route(web::get().to(|| HttpResponse::Ok()))
                    .route(
                        web::method(http::Method::HEAD)
                            .to(|| HttpResponse::MethodNotAllowed()),
                    ),
            )
    })
    .bind("127.0.0.1:8088")?
    .run()
    .await
}

User sessions

Actix-web提供了session管理的通用解决方案。 actix-session中间件可以与不同的后端类型一起使用,以将session数据存储在不同的后端中。

默认情况下,仅实现cookie session后端。可以添加其他后端实现。

CookieSession使用cookie作为session存储。 CookieSessionBackend创建的sessions仅限于存储少于4000字节的数据,因为payload必须适合单个cookie。 如果session超过4000字节,则会生成内部服务器错误。

Cookie可能具有signed(签名)或 private(私有)安全策略。每个都有各自的CookieSession构造函数。

客户端可以查看但不能修改已signed(签名)的cookie。客户端既不能查看也不可以修改私有cookie。

构造函数将key作为参数。这是cookie session的私钥-更改此值时,所有session数据都会丢失。

通常,您将创建一个SessionStorage中间件,并使用特定的后端实现(例如CookieSession)对其进行初始化。 要访问session数据,必须使用Session提取器。 此方法返回一个Session对象,该对象允许我们获取或设置session数据。

use actix_session::{CookieSession, Session};
use actix_web::{web, App, Error, HttpResponse, HttpServer};

async fn index(session: Session) -> Result<HttpResponse, Error> {
    // access session data
    if let Some(count) = session.get::<i32>("counter")? {
        session.set("counter", count + 1)?;
    } else {
        session.set("counter", 1)?;
    }

    Ok(HttpResponse::Ok().body(format!(
        "Count is {:?}!",
        session.get::<i32>("counter")?.unwrap()
    )))
}

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .wrap(
                CookieSession::signed(&[0; 32]) // <- create cookie based session middleware
                    .secure(false),
            )
            .service(web::resource("/").to(index))
    })
    .bind("127.0.0.1:8088")?
    .run()
    .await
}

Error handlers 错误处理

ErrorHandlers中间件允许我们提供响应的自定义处理程序。

您可以使用ErrorHandlers::handler()方法为特定状态码注册自定义错误处理程序。 您可以修改现有的响应,也可以创建一个全新的响应。 错误处理程序可以立即返回响应,也可以返回解析为响应的Future。

use actix_web::middleware::errhandlers::{ErrorHandlerResponse, ErrorHandlers};
use actix_web::{dev, http, HttpResponse, Result};

fn render_500<B>(mut res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
    res.response_mut().headers_mut().insert(
        http::header::CONTENT_TYPE,
        http::HeaderValue::from_static("Error"),
    );
    Ok(ErrorHandlerResponse::Response(res))
}

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    use actix_web::{web, App, HttpServer};

    HttpServer::new(|| {
        App::new()
            .wrap(
                ErrorHandlers::new()
                    .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500),
            )
            .service(
                web::resource("/test")
                    .route(web::get().to(|| HttpResponse::Ok()))
                    .route(web::head().to(|| HttpResponse::MethodNotAllowed())),
            )
    })
    .bind("127.0.0.1:8088")?
    .run()
    .await
}
接下来: 静态文件