mirror of
https://github.com/fafhrd91/actix-web
synced 2024-11-27 17:52:56 +01:00
add default handler
This commit is contained in:
parent
e4b3f79458
commit
ab45974e35
@ -1,5 +1,10 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
## [0.1.0-alpha.2] - 2019-04-xx
|
||||||
|
|
||||||
|
* Add default handler support
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-alpha.1] - 2019-03-28
|
## [0.1.0-alpha.1] - 2019-03-28
|
||||||
|
|
||||||
* Initial impl
|
* Initial impl
|
||||||
|
@ -7,23 +7,23 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::{cmp, io};
|
use std::{cmp, io};
|
||||||
|
|
||||||
|
use actix_service::boxed::{self, BoxedNewService, BoxedService};
|
||||||
|
use actix_service::{IntoNewService, NewService, Service};
|
||||||
|
use actix_web::dev::{
|
||||||
|
HttpServiceFactory, Payload, ResourceDef, ServiceConfig, ServiceFromRequest,
|
||||||
|
ServiceRequest, ServiceResponse,
|
||||||
|
};
|
||||||
|
use actix_web::error::{BlockingError, Error, ErrorInternalServerError};
|
||||||
|
use actix_web::http::header::DispositionType;
|
||||||
|
use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
use futures::future::{ok, Either, FutureResult};
|
||||||
use futures::{Async, Future, Poll, Stream};
|
use futures::{Async, Future, Poll, Stream};
|
||||||
use mime;
|
use mime;
|
||||||
use mime_guess::get_mime_type;
|
use mime_guess::get_mime_type;
|
||||||
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
|
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
|
||||||
use v_htmlescape::escape as escape_html_entity;
|
use v_htmlescape::escape as escape_html_entity;
|
||||||
|
|
||||||
use actix_service::{boxed::BoxedNewService, NewService, Service};
|
|
||||||
use actix_web::dev::{
|
|
||||||
HttpServiceFactory, ResourceDef, ServiceConfig, ServiceFromRequest, ServiceRequest,
|
|
||||||
ServiceResponse,
|
|
||||||
};
|
|
||||||
use actix_web::error::{BlockingError, Error, ErrorInternalServerError};
|
|
||||||
use actix_web::http::header::DispositionType;
|
|
||||||
use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder};
|
|
||||||
use futures::future::{ok, FutureResult};
|
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
mod named;
|
mod named;
|
||||||
mod range;
|
mod range;
|
||||||
@ -32,6 +32,7 @@ use self::error::{FilesError, UriSegmentError};
|
|||||||
pub use crate::named::NamedFile;
|
pub use crate::named::NamedFile;
|
||||||
pub use crate::range::HttpRange;
|
pub use crate::range::HttpRange;
|
||||||
|
|
||||||
|
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>;
|
||||||
type HttpNewService<P> =
|
type HttpNewService<P> =
|
||||||
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
|
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
|
||||||
|
|
||||||
@ -232,8 +233,6 @@ pub struct Files<S> {
|
|||||||
default: Rc<RefCell<Option<Rc<HttpNewService<S>>>>>,
|
default: Rc<RefCell<Option<Rc<HttpNewService<S>>>>>,
|
||||||
renderer: Rc<DirectoryRenderer>,
|
renderer: Rc<DirectoryRenderer>,
|
||||||
mime_override: Option<Rc<MimeOverride>>,
|
mime_override: Option<Rc<MimeOverride>>,
|
||||||
_chunk_size: usize,
|
|
||||||
_follow_symlinks: bool,
|
|
||||||
file_flags: named::Flags,
|
file_flags: named::Flags,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,8 +244,6 @@ impl<S> Clone for Files<S> {
|
|||||||
show_index: self.show_index,
|
show_index: self.show_index,
|
||||||
default: self.default.clone(),
|
default: self.default.clone(),
|
||||||
renderer: self.renderer.clone(),
|
renderer: self.renderer.clone(),
|
||||||
_chunk_size: self._chunk_size,
|
|
||||||
_follow_symlinks: self._follow_symlinks,
|
|
||||||
file_flags: self.file_flags,
|
file_flags: self.file_flags,
|
||||||
path: self.path.clone(),
|
path: self.path.clone(),
|
||||||
mime_override: self.mime_override.clone(),
|
mime_override: self.mime_override.clone(),
|
||||||
@ -274,8 +271,6 @@ impl<S: 'static> Files<S> {
|
|||||||
default: Rc::new(RefCell::new(None)),
|
default: Rc::new(RefCell::new(None)),
|
||||||
renderer: Rc::new(directory_listing),
|
renderer: Rc::new(directory_listing),
|
||||||
mime_override: None,
|
mime_override: None,
|
||||||
_chunk_size: 0,
|
|
||||||
_follow_symlinks: false,
|
|
||||||
file_flags: named::Flags::default(),
|
file_flags: named::Flags::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -334,6 +329,24 @@ impl<S: 'static> Files<S> {
|
|||||||
self.file_flags.set(named::Flags::LAST_MD, value);
|
self.file_flags.set(named::Flags::LAST_MD, value);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets default handler which is used when no matched file could be found.
|
||||||
|
pub fn default_handler<F, U>(mut self, f: F) -> Self
|
||||||
|
where
|
||||||
|
F: IntoNewService<U>,
|
||||||
|
U: NewService<
|
||||||
|
Request = ServiceRequest<S>,
|
||||||
|
Response = ServiceResponse,
|
||||||
|
Error = Error,
|
||||||
|
> + 'static,
|
||||||
|
{
|
||||||
|
// create and configure default resource
|
||||||
|
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service(
|
||||||
|
f.into_new_service().map_init_err(|_| ()),
|
||||||
|
)))));
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> HttpServiceFactory<P> for Files<P>
|
impl<P> HttpServiceFactory<P> for Files<P>
|
||||||
@ -353,41 +366,95 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> NewService for Files<P> {
|
impl<P: 'static> NewService for Files<P> {
|
||||||
type Request = ServiceRequest<P>;
|
type Request = ServiceRequest<P>;
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Service = Self;
|
type Service = FilesService<P>;
|
||||||
type InitError = ();
|
type InitError = ();
|
||||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
|
||||||
|
|
||||||
fn new_service(&self, _: &()) -> Self::Future {
|
fn new_service(&self, _: &()) -> Self::Future {
|
||||||
ok(self.clone())
|
let mut srv = FilesService {
|
||||||
|
directory: self.directory.clone(),
|
||||||
|
index: self.index.clone(),
|
||||||
|
show_index: self.show_index,
|
||||||
|
default: None,
|
||||||
|
renderer: self.renderer.clone(),
|
||||||
|
mime_override: self.mime_override.clone(),
|
||||||
|
file_flags: self.file_flags,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ref default) = *self.default.borrow() {
|
||||||
|
Box::new(
|
||||||
|
default
|
||||||
|
.new_service(&())
|
||||||
|
.map(move |default| {
|
||||||
|
srv.default = Some(default);
|
||||||
|
srv
|
||||||
|
})
|
||||||
|
.map_err(|_| ()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Box::new(ok(srv))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> Service for Files<P> {
|
pub struct FilesService<P> {
|
||||||
|
directory: PathBuf,
|
||||||
|
index: Option<String>,
|
||||||
|
show_index: bool,
|
||||||
|
default: Option<HttpService<P>>,
|
||||||
|
renderer: Rc<DirectoryRenderer>,
|
||||||
|
mime_override: Option<Rc<MimeOverride>>,
|
||||||
|
file_flags: named::Flags,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> FilesService<P> {
|
||||||
|
fn handle_err(
|
||||||
|
&mut self,
|
||||||
|
e: io::Error,
|
||||||
|
req: HttpRequest,
|
||||||
|
payload: Payload<P>,
|
||||||
|
) -> Either<
|
||||||
|
FutureResult<ServiceResponse, Error>,
|
||||||
|
Box<Future<Item = ServiceResponse, Error = Error>>,
|
||||||
|
> {
|
||||||
|
log::debug!("Files: Failed to handle {}: {}", req.path(), e);
|
||||||
|
if let Some(ref mut default) = self.default {
|
||||||
|
Either::B(default.call(ServiceRequest::from_parts(req, payload)))
|
||||||
|
} else {
|
||||||
|
Either::A(ok(ServiceResponse::from_err(e, req.clone())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> Service for FilesService<P> {
|
||||||
type Request = ServiceRequest<P>;
|
type Request = ServiceRequest<P>;
|
||||||
type Response = ServiceResponse;
|
type Response = ServiceResponse;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = FutureResult<Self::Response, Self::Error>;
|
type Future = Either<
|
||||||
|
FutureResult<Self::Response, Self::Error>,
|
||||||
|
Box<Future<Item = Self::Response, Error = Self::Error>>,
|
||||||
|
>;
|
||||||
|
|
||||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||||
Ok(Async::Ready(()))
|
Ok(Async::Ready(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||||
let (req, _) = req.into_parts();
|
let (req, pl) = req.into_parts();
|
||||||
|
|
||||||
let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) {
|
let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) {
|
||||||
Ok(item) => item,
|
Ok(item) => item,
|
||||||
Err(e) => return ok(ServiceResponse::from_err(e, req.clone())),
|
Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req.clone()))),
|
||||||
};
|
};
|
||||||
|
|
||||||
// full filepath
|
// full filepath
|
||||||
let path = match self.directory.join(&real_path.0).canonicalize() {
|
let path = match self.directory.join(&real_path.0).canonicalize() {
|
||||||
Ok(path) => path,
|
Ok(path) => path,
|
||||||
Err(e) => return ok(ServiceResponse::from_err(e, req.clone())),
|
Err(e) => return self.handle_err(e, req, pl),
|
||||||
};
|
};
|
||||||
|
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
@ -403,25 +470,25 @@ impl<P> Service for Files<P> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
named_file.flags = self.file_flags;
|
named_file.flags = self.file_flags;
|
||||||
match named_file.respond_to(&req) {
|
Either::A(ok(match named_file.respond_to(&req) {
|
||||||
Ok(item) => ok(ServiceResponse::new(req.clone(), item)),
|
Ok(item) => ServiceResponse::new(req.clone(), item),
|
||||||
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
|
Err(e) => ServiceResponse::from_err(e, req.clone()),
|
||||||
}
|
}))
|
||||||
}
|
}
|
||||||
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
|
Err(e) => return self.handle_err(e, req, pl),
|
||||||
}
|
}
|
||||||
} else if self.show_index {
|
} else if self.show_index {
|
||||||
let dir = Directory::new(self.directory.clone(), path);
|
let dir = Directory::new(self.directory.clone(), path);
|
||||||
let x = (self.renderer)(&dir, &req);
|
let x = (self.renderer)(&dir, &req);
|
||||||
match x {
|
match x {
|
||||||
Ok(resp) => ok(resp),
|
Ok(resp) => Either::A(ok(resp)),
|
||||||
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
|
Err(e) => return self.handle_err(e, req, pl),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ok(ServiceResponse::from_err(
|
Either::A(ok(ServiceResponse::from_err(
|
||||||
FilesError::IsDirectory,
|
FilesError::IsDirectory,
|
||||||
req.clone(),
|
req.clone(),
|
||||||
))
|
)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match NamedFile::open(path) {
|
match NamedFile::open(path) {
|
||||||
@ -434,11 +501,15 @@ impl<P> Service for Files<P> {
|
|||||||
|
|
||||||
named_file.flags = self.file_flags;
|
named_file.flags = self.file_flags;
|
||||||
match named_file.respond_to(&req) {
|
match named_file.respond_to(&req) {
|
||||||
Ok(item) => ok(ServiceResponse::new(req.clone(), item)),
|
Ok(item) => {
|
||||||
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
|
Either::A(ok(ServiceResponse::new(req.clone(), item)))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
Either::A(ok(ServiceResponse::from_err(e, req.clone())))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
|
Err(e) => self.handle_err(e, req, pl),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -833,15 +904,15 @@ mod tests {
|
|||||||
let mut response = test::call_success(&mut srv, request);
|
let mut response = test::call_success(&mut srv, request);
|
||||||
|
|
||||||
// with enabled compression
|
// with enabled compression
|
||||||
// {
|
{
|
||||||
// let te = response
|
let te = response
|
||||||
// .headers()
|
.headers()
|
||||||
// .get(header::TRANSFER_ENCODING)
|
.get(header::TRANSFER_ENCODING)
|
||||||
// .unwrap()
|
.unwrap()
|
||||||
// .to_str()
|
.to_str()
|
||||||
// .unwrap();
|
.unwrap();
|
||||||
// assert_eq!(te, "chunked");
|
assert_eq!(te, "chunked");
|
||||||
// }
|
}
|
||||||
|
|
||||||
let bytes =
|
let bytes =
|
||||||
test::block_on(response.take_body().fold(BytesMut::new(), |mut b, c| {
|
test::block_on(response.take_body().fold(BytesMut::new(), |mut b, c| {
|
||||||
@ -890,20 +961,32 @@ mod tests {
|
|||||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn test_named_file_content_encoding() {
|
fn test_named_file_content_encoding() {
|
||||||
// let req = TestRequest::default().method(Method::GET).finish();
|
let mut srv = test::init_service(App::new().enable_encoding().service(
|
||||||
// let file = NamedFile::open("Cargo.toml").unwrap();
|
web::resource("/").to(|| {
|
||||||
|
NamedFile::open("Cargo.toml")
|
||||||
|
.unwrap()
|
||||||
|
.set_content_encoding(header::ContentEncoding::Identity)
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
|
||||||
// assert!(file.encoding.is_none());
|
let request = TestRequest::get()
|
||||||
// let resp = file
|
.uri("/")
|
||||||
// .set_content_encoding(ContentEncoding::Identity)
|
.header(header::ACCEPT_ENCODING, "gzip")
|
||||||
// .respond_to(&req)
|
.to_request();
|
||||||
// .unwrap();
|
let res = test::call_success(&mut srv, request);
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
|
||||||
// assert!(resp.content_encoding().is_some());
|
assert_eq!(
|
||||||
// assert_eq!(resp.content_encoding().unwrap().as_str(), "identity");
|
res.headers()
|
||||||
// }
|
.get(header::CONTENT_ENCODING)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"identity"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_named_file_allowed_method() {
|
fn test_named_file_allowed_method() {
|
||||||
@ -954,22 +1037,29 @@ mod tests {
|
|||||||
let _st: Files<()> = Files::new("/", "Cargo.toml");
|
let _st: Files<()> = Files::new("/", "Cargo.toml");
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn test_default_handler_file_missing() {
|
fn test_default_handler_file_missing() {
|
||||||
// let st = Files::new(".")
|
let mut st = test::block_on(
|
||||||
// .default_handler(|_: &_| "default content");
|
Files::new("/", ".")
|
||||||
// let req = TestRequest::with_uri("/missing")
|
.default_handler(|req: ServiceRequest<_>| {
|
||||||
// .param("tail", "missing")
|
Ok(req.into_response(HttpResponse::Ok().body("default content")))
|
||||||
// .finish();
|
})
|
||||||
|
.new_service(&()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let req = TestRequest::with_uri("/missing").to_service();
|
||||||
|
|
||||||
// let resp = st.handle(&req).respond_to(&req).unwrap();
|
let mut resp = test::call_success(&mut st, req);
|
||||||
// let resp = resp.as_msg();
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
// assert_eq!(resp.status(), StatusCode::OK);
|
let bytes =
|
||||||
// assert_eq!(
|
test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| {
|
||||||
// resp.body(),
|
b.extend(c);
|
||||||
// &Body::Binary(Binary::Slice(b"default content"))
|
Ok::<_, Error>(b)
|
||||||
// );
|
}))
|
||||||
// }
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(bytes.freeze(), Bytes::from_static(b"default content"));
|
||||||
|
}
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn test_serve_index() {
|
// fn test_serve_index() {
|
||||||
|
@ -43,7 +43,7 @@ pub struct NamedFile {
|
|||||||
pub(crate) content_disposition: header::ContentDisposition,
|
pub(crate) content_disposition: header::ContentDisposition,
|
||||||
pub(crate) md: Metadata,
|
pub(crate) md: Metadata,
|
||||||
modified: Option<SystemTime>,
|
modified: Option<SystemTime>,
|
||||||
encoding: Option<ContentEncoding>,
|
pub(crate) encoding: Option<ContentEncoding>,
|
||||||
pub(crate) status_code: StatusCode,
|
pub(crate) status_code: StatusCode,
|
||||||
pub(crate) flags: Flags,
|
pub(crate) flags: Flags,
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use std::rc::Rc;
|
|||||||
use actix_http::{Error, Response};
|
use actix_http::{Error, Response};
|
||||||
use actix_service::boxed::{self, BoxedNewService, BoxedService};
|
use actix_service::boxed::{self, BoxedNewService, BoxedService};
|
||||||
use actix_service::{
|
use actix_service::{
|
||||||
ApplyTransform, IntoNewService, IntoTransform, NewService, Service, Transform,
|
apply_transform, IntoNewService, IntoTransform, NewService, Service, Transform,
|
||||||
};
|
};
|
||||||
use futures::future::{ok, Either, FutureResult};
|
use futures::future::{ok, Either, FutureResult};
|
||||||
use futures::{Async, Future, IntoFuture, Poll};
|
use futures::{Async, Future, IntoFuture, Poll};
|
||||||
@ -254,7 +254,7 @@ where
|
|||||||
>,
|
>,
|
||||||
F: IntoTransform<M, T::Service>,
|
F: IntoTransform<M, T::Service>,
|
||||||
{
|
{
|
||||||
let endpoint = ApplyTransform::new(mw, self.endpoint);
|
let endpoint = apply_transform(mw, self.endpoint);
|
||||||
Resource {
|
Resource {
|
||||||
endpoint,
|
endpoint,
|
||||||
rdef: self.rdef,
|
rdef: self.rdef,
|
||||||
|
Loading…
Reference in New Issue
Block a user