Actix-web的中间件系统使我们可以在request/response处理中添加其他行为。中间件可以挂接到传入的请求过程中,使我们能够修改请求以及暂停请求处理以尽早返回响应。
中间件也可以加入响应处理。
通常,中间件参与以下操作:
中间件为每个App
, scope
, 或 Resource
注册,并以与注册相反的顺序执行。
通常,中间件是一种实现Service trait和Transform 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(压缩)等。
日志记录是作为中间件实现的。通常将日志记录中间件注册为该应用程序的第一个中间件。日志中间件必须为每个应用程序注册。
Logger
中间件使用标准日志crate记录信息。您应该为actix_web
软件包启用Logger
,以查看访问日志(env_logger或类似记录)。
使用指定的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
%%
百分号%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’]要设置默认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
}
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
}
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
}