mirror of
https://github.com/actix/actix-web.git
synced 2024-11-25 19:11:10 +00:00
Named file service (#2135)
This commit is contained in:
parent
f462aaa7b6
commit
26e9c80626
4 changed files with 165 additions and 3 deletions
|
@ -199,6 +199,18 @@ impl Files {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets default handler which is used when no matched file could be found.
|
/// Sets default handler which is used when no matched file could be found.
|
||||||
|
///
|
||||||
|
/// For example, you could set a fall back static file handler:
|
||||||
|
/// ```rust
|
||||||
|
/// use actix_files::{Files, NamedFile};
|
||||||
|
///
|
||||||
|
/// # fn run() -> Result<(), actix_web::Error> {
|
||||||
|
/// let files = Files::new("/", "./static")
|
||||||
|
/// .index_file("index.html")
|
||||||
|
/// .default_handler(NamedFile::open("./static/404.html")?);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
pub fn default_handler<F, U>(mut self, f: F) -> Self
|
pub fn default_handler<F, U>(mut self, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: IntoServiceFactory<U, ServiceRequest>,
|
F: IntoServiceFactory<U, ServiceRequest>,
|
||||||
|
|
|
@ -755,6 +755,80 @@ mod tests {
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_serve_named_file() {
|
||||||
|
let srv =
|
||||||
|
test::init_service(App::new().service(NamedFile::open("Cargo.toml").unwrap()))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = TestRequest::get().uri("/Cargo.toml").to_request();
|
||||||
|
let res = test::call_service(&srv, req).await;
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let bytes = test::read_body(res).await;
|
||||||
|
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
||||||
|
assert_eq!(bytes, data);
|
||||||
|
|
||||||
|
let req = TestRequest::get().uri("/test/unknown").to_request();
|
||||||
|
let res = test::call_service(&srv, req).await;
|
||||||
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_serve_named_file_prefix() {
|
||||||
|
let srv = test::init_service(
|
||||||
|
App::new()
|
||||||
|
.service(web::scope("/test").service(NamedFile::open("Cargo.toml").unwrap())),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = TestRequest::get().uri("/test/Cargo.toml").to_request();
|
||||||
|
let res = test::call_service(&srv, req).await;
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let bytes = test::read_body(res).await;
|
||||||
|
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
||||||
|
assert_eq!(bytes, data);
|
||||||
|
|
||||||
|
let req = TestRequest::get().uri("/Cargo.toml").to_request();
|
||||||
|
let res = test::call_service(&srv, req).await;
|
||||||
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_named_file_default_service() {
|
||||||
|
let srv = test::init_service(
|
||||||
|
App::new().default_service(NamedFile::open("Cargo.toml").unwrap()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
for route in ["/foobar", "/baz", "/"].iter() {
|
||||||
|
let req = TestRequest::get().uri(route).to_request();
|
||||||
|
let res = test::call_service(&srv, req).await;
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let bytes = test::read_body(res).await;
|
||||||
|
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
||||||
|
assert_eq!(bytes, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_default_handler_named_file() {
|
||||||
|
let st = Files::new("/", ".")
|
||||||
|
.default_handler(NamedFile::open("Cargo.toml").unwrap())
|
||||||
|
.new_service(())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let req = TestRequest::with_uri("/missing").to_srv_request();
|
||||||
|
let resp = test::call_service(&st, req).await;
|
||||||
|
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
let bytes = test::read_body(resp).await;
|
||||||
|
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
||||||
|
assert_eq!(bytes, data);
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_symlinks() {
|
async fn test_symlinks() {
|
||||||
let srv = test::init_service(App::new().service(Files::new("test", "."))).await;
|
let srv = test::init_service(App::new().service(Files::new("test", "."))).await;
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
use actix_service::{Service, ServiceFactory};
|
||||||
|
use actix_utils::future::{ok, ready, Ready};
|
||||||
|
use actix_web::dev::{AppService, HttpServiceFactory, ResourceDef};
|
||||||
use std::fs::{File, Metadata};
|
use std::fs::{File, Metadata};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
@ -8,14 +11,14 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{BodyEncoding, SizedStream},
|
dev::{BodyEncoding, ServiceRequest, ServiceResponse, SizedStream},
|
||||||
http::{
|
http::{
|
||||||
header::{
|
header::{
|
||||||
self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue,
|
self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue,
|
||||||
},
|
},
|
||||||
ContentEncoding, StatusCode,
|
ContentEncoding, StatusCode,
|
||||||
},
|
},
|
||||||
HttpMessage, HttpRequest, HttpResponse, Responder,
|
Error, HttpMessage, HttpRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use mime_guess::from_path;
|
use mime_guess::from_path;
|
||||||
|
@ -39,6 +42,29 @@ impl Default for Flags {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A file with an associated name.
|
/// A file with an associated name.
|
||||||
|
///
|
||||||
|
/// `NamedFile` can be registered as services:
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::App;
|
||||||
|
/// use actix_files::NamedFile;
|
||||||
|
///
|
||||||
|
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let app = App::new()
|
||||||
|
/// .service(NamedFile::open("./static/index.html")?);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// They can also be returned from handlers:
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{Responder, get};
|
||||||
|
/// use actix_files::NamedFile;
|
||||||
|
///
|
||||||
|
/// #[get("/")]
|
||||||
|
/// async fn index() -> impl Responder {
|
||||||
|
/// NamedFile::open("./static/index.html")
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NamedFile {
|
pub struct NamedFile {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
@ -480,3 +506,53 @@ impl Responder for NamedFile {
|
||||||
self.into_response(req)
|
self.into_response(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ServiceFactory<ServiceRequest> for NamedFile {
|
||||||
|
type Response = ServiceResponse;
|
||||||
|
type Error = Error;
|
||||||
|
type Config = ();
|
||||||
|
type InitError = ();
|
||||||
|
type Service = NamedFileService;
|
||||||
|
type Future = Ready<Result<Self::Service, ()>>;
|
||||||
|
|
||||||
|
fn new_service(&self, _: ()) -> Self::Future {
|
||||||
|
ok(NamedFileService {
|
||||||
|
path: self.path.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NamedFileService {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service<ServiceRequest> for NamedFileService {
|
||||||
|
type Response = ServiceResponse;
|
||||||
|
type Error = Error;
|
||||||
|
type Future = Ready<Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
actix_service::always_ready!();
|
||||||
|
|
||||||
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
|
let (req, _) = req.into_parts();
|
||||||
|
ready(
|
||||||
|
NamedFile::open(&self.path)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
.map(|f| f.into_response(&req))
|
||||||
|
.map(|res| ServiceResponse::new(req, res)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpServiceFactory for NamedFile {
|
||||||
|
fn register(self, config: &mut AppService) {
|
||||||
|
config.register_service(
|
||||||
|
ResourceDef::root_prefix(self.path.to_string_lossy().as_ref()),
|
||||||
|
None,
|
||||||
|
self,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use actix_http::HttpService;
|
use actix_http::HttpService;
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
use actix_service::{map_config, fn_service, ServiceFactoryExt};
|
use actix_service::{fn_service, map_config, ServiceFactoryExt};
|
||||||
use actix_utils::future::ok;
|
use actix_utils::future::ok;
|
||||||
use actix_web::http::Version;
|
use actix_web::http::Version;
|
||||||
use actix_web::{dev::AppConfig, web, App, HttpResponse};
|
use actix_web::{dev::AppConfig, web, App, HttpResponse};
|
||||||
|
|
Loading…
Reference in a new issue