使用 actix-files 提供静态文件时如何设置 "expire" 或 "Cache-Control" header?
How to set "expire" or "Cache-Control" header when serving static files with actix-files?
我想使用 actix_files.
为静态文件设置缓存 TTL
就像在 Nginx 配置中一样:expires max;
将添加这样的 header:expires: Thu, 31 Dec 2037 23:55:55 GMT
.
如何使用 actix_files
完成?
use actix_files::Files;
use actix_web::{App, HttpServer, web, HttpResponse, http, cookie, middleware};
#[actix_web::main]
async fn main() {
HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
.wrap(middleware::Compress::default())
.service(Files::new("/dist", "dist/"))
})
.bind("0.0.0.0:8080").unwrap()
.run()
.await.unwrap();
}
我建议的方法是通过 middleware。
使用 .wrap_fn
.
可以使这段代码不那么冗长
use actix_files::Files;
use actix_service::{Service, Transform};
use actix_web::{
dev::ServiceRequest,
dev::ServiceResponse,
http::header::{HeaderValue, EXPIRES},
middleware, web, App, Error, HttpServer,
};
// use actix_http::http::header::Expires;
use futures::{
future::{ok, Ready},
Future,
};
use std::pin::Pin;
use std::task::{Context, Poll};
struct MyCacheInterceptor;
impl<S, B> Transform<S> for MyCacheInterceptor
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 = MyCacheInterceptorMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(MyCacheInterceptorMiddleware { service })
}
}
pub struct MyCacheInterceptorMiddleware<S> {
service: S,
}
impl<S, B> Service for MyCacheInterceptorMiddleware<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 {
let fut = self.service.call(req);
Box::pin(async move {
let mut res = fut.await?;
let headers = res.headers_mut();
headers.append(
EXPIRES,
HeaderValue::from_static("Thu, 31 Dec 2037 23:55:55 GMT"),
);
return Ok(res);
})
}
}
#[actix_web::main]
async fn main() {
HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
.wrap(middleware::Compress::default())
.service(
web::scope("/dist")
.wrap(MyCacheInterceptor)
.service(Files::new("", ".").show_files_listing()),
)
})
.bind("0.0.0.0:8080")
.unwrap()
.run()
.await
.unwrap();
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{test, web, App};
#[actix_rt::test]
async fn test_expire_header() {
let mut app = test::init_service(
App::new().service(
web::scope("/")
.wrap(MyCacheInterceptor)
.service(Files::new("", ".").show_files_listing()),
),
)
.await;
let req = test::TestRequest::with_header("content-type", "text/plain").to_request();
let resp = test::call_service(&mut app, req).await;
assert!(resp.status().is_success());
assert!(resp.headers().get(EXPIRES).is_some());
assert_eq!(
resp.headers().get(EXPIRES).unwrap(),
HeaderValue::from_static("Thu, 31 Dec 2037 23:55:55 GMT"),
);
}
}
我想使用 actix_files.
为静态文件设置缓存 TTL就像在 Nginx 配置中一样:expires max;
将添加这样的 header:expires: Thu, 31 Dec 2037 23:55:55 GMT
.
如何使用 actix_files
完成?
use actix_files::Files;
use actix_web::{App, HttpServer, web, HttpResponse, http, cookie, middleware};
#[actix_web::main]
async fn main() {
HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
.wrap(middleware::Compress::default())
.service(Files::new("/dist", "dist/"))
})
.bind("0.0.0.0:8080").unwrap()
.run()
.await.unwrap();
}
我建议的方法是通过 middleware。
使用 .wrap_fn
.
use actix_files::Files;
use actix_service::{Service, Transform};
use actix_web::{
dev::ServiceRequest,
dev::ServiceResponse,
http::header::{HeaderValue, EXPIRES},
middleware, web, App, Error, HttpServer,
};
// use actix_http::http::header::Expires;
use futures::{
future::{ok, Ready},
Future,
};
use std::pin::Pin;
use std::task::{Context, Poll};
struct MyCacheInterceptor;
impl<S, B> Transform<S> for MyCacheInterceptor
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 = MyCacheInterceptorMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(MyCacheInterceptorMiddleware { service })
}
}
pub struct MyCacheInterceptorMiddleware<S> {
service: S,
}
impl<S, B> Service for MyCacheInterceptorMiddleware<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 {
let fut = self.service.call(req);
Box::pin(async move {
let mut res = fut.await?;
let headers = res.headers_mut();
headers.append(
EXPIRES,
HeaderValue::from_static("Thu, 31 Dec 2037 23:55:55 GMT"),
);
return Ok(res);
})
}
}
#[actix_web::main]
async fn main() {
HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
.wrap(middleware::Compress::default())
.service(
web::scope("/dist")
.wrap(MyCacheInterceptor)
.service(Files::new("", ".").show_files_listing()),
)
})
.bind("0.0.0.0:8080")
.unwrap()
.run()
.await
.unwrap();
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{test, web, App};
#[actix_rt::test]
async fn test_expire_header() {
let mut app = test::init_service(
App::new().service(
web::scope("/")
.wrap(MyCacheInterceptor)
.service(Files::new("", ".").show_files_listing()),
),
)
.await;
let req = test::TestRequest::with_header("content-type", "text/plain").to_request();
let resp = test::call_service(&mut app, req).await;
assert!(resp.status().is_success());
assert!(resp.headers().get(EXPIRES).is_some());
assert_eq!(
resp.headers().get(EXPIRES).unwrap(),
HeaderValue::from_static("Thu, 31 Dec 2037 23:55:55 GMT"),
);
}
}