1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-07-04 04:55:48 +00:00

migrate actix-files

This commit is contained in:
Nikolay Kim 2019-11-21 11:31:31 +06:00
parent 6ac4ac66b9
commit 69cadcdedb
5 changed files with 664 additions and 607 deletions

View file

@ -32,7 +32,7 @@ members = [
"awc", "awc",
"actix-http", "actix-http",
"actix-cors", "actix-cors",
#"actix-files", "actix-files",
#"actix-framed", #"actix-framed",
#"actix-session", #"actix-session",
"actix-identity", "actix-identity",
@ -126,7 +126,7 @@ actix-http-test = { path = "test-server" }
actix-web-codegen = { path = "actix-web-codegen" } actix-web-codegen = { path = "actix-web-codegen" }
# actix-web-actors = { path = "actix-web-actors" } # actix-web-actors = { path = "actix-web-actors" }
# actix-session = { path = "actix-session" } # actix-session = { path = "actix-session" }
# actix-files = { path = "actix-files" } actix-files = { path = "actix-files" }
# actix-multipart = { path = "actix-multipart" } # actix-multipart = { path = "actix-multipart" }
awc = { path = "awc" } awc = { path = "awc" }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "actix-files" name = "actix-files"
version = "0.1.7" version = "0.2.0-alpha.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for actix web." description = "Static files support for actix web."
readme = "README.md" readme = "README.md"
@ -11,19 +11,19 @@ documentation = "https://docs.rs/actix-files/"
categories = ["asynchronous", "web-programming::http-server"] categories = ["asynchronous", "web-programming::http-server"]
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
edition = "2018" edition = "2018"
# workspace = ".." workspace = ".."
[lib] [lib]
name = "actix_files" name = "actix_files"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = { version = "1.0.9", default-features = false } actix-web = { version = "2.0.0-alpha.1", default-features = false }
actix-http = "0.2.11" actix-http = "0.3.0-alpha.1"
actix-service = "0.4.2" actix-service = "1.0.0-alpha.1"
bitflags = "1" bitflags = "1"
bytes = "0.4" bytes = "0.4"
futures = "0.1.24" futures = "0.3.1"
derive_more = "0.15.0" derive_more = "0.15.0"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
@ -32,4 +32,4 @@ percent-encoding = "2.1"
v_htmlescape = "0.4" v_htmlescape = "0.4"
[dev-dependencies] [dev-dependencies]
actix-web = { version = "1.0.9", features=["ssl"] } actix-web = { version = "2.0.0-alpha.1", features=["openssl"] }

View file

@ -4,25 +4,28 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt::Write; use std::fmt::Write;
use std::fs::{DirEntry, File}; use std::fs::{DirEntry, File};
use std::future::Future;
use std::io::{Read, Seek}; use std::io::{Read, Seek};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll};
use std::{cmp, io}; use std::{cmp, io};
use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::boxed::{self, BoxedNewService, BoxedService};
use actix_service::{IntoNewService, NewService, Service}; use actix_service::{IntoServiceFactory, Service, ServiceFactory};
use actix_web::dev::{ use actix_web::dev::{
AppService, HttpServiceFactory, Payload, ResourceDef, ServiceRequest, AppService, HttpServiceFactory, Payload, ResourceDef, ServiceRequest,
ServiceResponse, ServiceResponse,
}; };
use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; use actix_web::error::{Canceled, Error, ErrorInternalServerError};
use actix_web::guard::Guard; use actix_web::guard::Guard;
use actix_web::http::header::{self, DispositionType}; use actix_web::http::header::{self, DispositionType};
use actix_web::http::Method; use actix_web::http::Method;
use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder}; use actix_web::{web, FromRequest, HttpRequest, HttpResponse};
use bytes::Bytes; use bytes::Bytes;
use futures::future::{ok, Either, FutureResult}; use futures::future::{ok, ready, Either, FutureExt, LocalBoxFuture, Ready};
use futures::{Async, Future, Poll, Stream}; use futures::Stream;
use mime; use mime;
use mime_guess::from_ext; use mime_guess::from_ext;
use percent_encoding::{utf8_percent_encode, CONTROLS}; use percent_encoding::{utf8_percent_encode, CONTROLS};
@ -54,32 +57,34 @@ pub struct ChunkedReadFile {
size: u64, size: u64,
offset: u64, offset: u64,
file: Option<File>, file: Option<File>,
fut: Option<Box<dyn Future<Item = (File, Bytes), Error = BlockingError<io::Error>>>>, fut: Option<
LocalBoxFuture<'static, Result<Result<(File, Bytes), io::Error>, Canceled>>,
>,
counter: u64, counter: u64,
} }
fn handle_error(err: BlockingError<io::Error>) -> Error {
match err {
BlockingError::Error(err) => err.into(),
BlockingError::Canceled => ErrorInternalServerError("Unexpected error"),
}
}
impl Stream for ChunkedReadFile { impl Stream for ChunkedReadFile {
type Item = Bytes; type Item = Result<Bytes, Error>;
type Error = Error;
fn poll(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(
if self.fut.is_some() { mut self: Pin<&mut Self>,
return match self.fut.as_mut().unwrap().poll().map_err(handle_error)? { cx: &mut Context,
Async::Ready((file, bytes)) => { ) -> Poll<Option<Self::Item>> {
if let Some(ref mut fut) = self.fut {
return match Pin::new(fut).poll(cx) {
Poll::Ready(Err(_)) => Poll::Ready(Some(Err(ErrorInternalServerError(
"Unexpected error",
)
.into()))),
Poll::Ready(Ok(Ok((file, bytes)))) => {
self.fut.take(); self.fut.take();
self.file = Some(file); self.file = Some(file);
self.offset += bytes.len() as u64; self.offset += bytes.len() as u64;
self.counter += bytes.len() as u64; self.counter += bytes.len() as u64;
Ok(Async::Ready(Some(bytes))) Poll::Ready(Some(Ok(bytes)))
} }
Async::NotReady => Ok(Async::NotReady), Poll::Ready(Ok(Err(e))) => Poll::Ready(Some(Err(e.into()))),
Poll::Pending => Poll::Pending,
}; };
} }
@ -88,10 +93,11 @@ impl Stream for ChunkedReadFile {
let counter = self.counter; let counter = self.counter;
if size == counter { if size == counter {
Ok(Async::Ready(None)) Poll::Ready(None)
} else { } else {
let mut file = self.file.take().expect("Use after completion"); let mut file = self.file.take().expect("Use after completion");
self.fut = Some(Box::new(web::block(move || { self.fut = Some(
web::block(move || {
let max_bytes: usize; let max_bytes: usize;
max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize;
let mut buf = Vec::with_capacity(max_bytes); let mut buf = Vec::with_capacity(max_bytes);
@ -102,8 +108,10 @@ impl Stream for ChunkedReadFile {
return Err(io::ErrorKind::UnexpectedEof.into()); return Err(io::ErrorKind::UnexpectedEof.into());
} }
Ok((file, Bytes::from(buf))) Ok((file, Bytes::from(buf)))
}))); })
self.poll() .boxed_local(),
);
self.poll_next(cx)
} }
} }
} }
@ -367,8 +375,8 @@ 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.
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: IntoNewService<U>, F: IntoServiceFactory<U>,
U: NewService< U: ServiceFactory<
Config = (), Config = (),
Request = ServiceRequest, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
@ -376,8 +384,8 @@ impl Files {
> + 'static, > + 'static,
{ {
// create and configure default resource // create and configure default resource
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory(
f.into_new_service().map_init_err(|_| ()), f.into_factory().map_init_err(|_| ()),
))))); )))));
self self
@ -398,14 +406,14 @@ impl HttpServiceFactory for Files {
} }
} }
impl NewService for Files { impl ServiceFactory for Files {
type Request = ServiceRequest; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Config = (); type Config = ();
type Service = FilesService; type Service = FilesService;
type InitError = (); type InitError = ();
type Future = Box<dyn Future<Item = Self::Service, Error = Self::InitError>>; type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
let mut srv = FilesService { let mut srv = FilesService {
@ -421,17 +429,18 @@ impl NewService for Files {
}; };
if let Some(ref default) = *self.default.borrow() { if let Some(ref default) = *self.default.borrow() {
Box::new(
default default
.new_service(&()) .new_service(&())
.map(move |default| { .map(move |result| match result {
Ok(default) => {
srv.default = Some(default); srv.default = Some(default);
srv Ok(srv)
}
Err(_) => Err(()),
}) })
.map_err(|_| ()), .boxed_local()
)
} else { } else {
Box::new(ok(srv)) ok(srv).boxed_local()
} }
} }
} }
@ -454,14 +463,14 @@ impl FilesService {
e: io::Error, e: io::Error,
req: ServiceRequest, req: ServiceRequest,
) -> Either< ) -> Either<
FutureResult<ServiceResponse, Error>, Ready<Result<ServiceResponse, Error>>,
Box<dyn Future<Item = ServiceResponse, Error = Error>>, LocalBoxFuture<'static, Result<ServiceResponse, Error>>,
> { > {
log::debug!("Files: Failed to handle {}: {}", req.path(), e); log::debug!("Files: Failed to handle {}: {}", req.path(), e);
if let Some(ref mut default) = self.default { if let Some(ref mut default) = self.default {
default.call(req) Either::Right(default.call(req))
} else { } else {
Either::A(ok(req.error_response(e))) Either::Left(ok(req.error_response(e)))
} }
} }
} }
@ -471,17 +480,15 @@ impl Service for FilesService {
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Future = Either< type Future = Either<
FutureResult<Self::Response, Self::Error>, Ready<Result<Self::Response, Self::Error>>,
Box<dyn Future<Item = Self::Response, Error = Self::Error>>, LocalBoxFuture<'static, Result<Self::Response, Self::Error>>,
>; >;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Ok(Async::Ready(())) Poll::Ready(Ok(()))
} }
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
// let (req, pl) = req.into_parts();
let is_method_valid = if let Some(guard) = &self.guards { let is_method_valid = if let Some(guard) = &self.guards {
// execute user defined guards // execute user defined guards
(**guard).check(req.head()) (**guard).check(req.head())
@ -494,7 +501,7 @@ impl Service for FilesService {
}; };
if !is_method_valid { if !is_method_valid {
return Either::A(ok(req.into_response( return Either::Left(ok(req.into_response(
actix_web::HttpResponse::MethodNotAllowed() actix_web::HttpResponse::MethodNotAllowed()
.header(header::CONTENT_TYPE, "text/plain") .header(header::CONTENT_TYPE, "text/plain")
.body("Request did not meet this resource's requirements."), .body("Request did not meet this resource's requirements."),
@ -503,7 +510,7 @@ impl Service for FilesService {
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 Either::A(ok(req.error_response(e))), Err(e) => return Either::Left(ok(req.error_response(e))),
}; };
// full filepath // full filepath
@ -516,7 +523,7 @@ impl Service for FilesService {
if let Some(ref redir_index) = self.index { if let Some(ref redir_index) = self.index {
if self.redirect_to_slash && !req.path().ends_with('/') { if self.redirect_to_slash && !req.path().ends_with('/') {
let redirect_to = format!("{}/", req.path()); let redirect_to = format!("{}/", req.path());
return Either::A(ok(req.into_response( return Either::Left(ok(req.into_response(
HttpResponse::Found() HttpResponse::Found()
.header(header::LOCATION, redirect_to) .header(header::LOCATION, redirect_to)
.body("") .body("")
@ -536,7 +543,7 @@ impl Service for FilesService {
named_file.flags = self.file_flags; named_file.flags = self.file_flags;
let (req, _) = req.into_parts(); let (req, _) = req.into_parts();
Either::A(ok(match named_file.respond_to(&req) { Either::Left(ok(match named_file.into_response(&req) {
Ok(item) => ServiceResponse::new(req, item), Ok(item) => ServiceResponse::new(req, item),
Err(e) => ServiceResponse::from_err(e, req), Err(e) => ServiceResponse::from_err(e, req),
})) }))
@ -548,11 +555,11 @@ impl Service for FilesService {
let (req, _) = req.into_parts(); let (req, _) = req.into_parts();
let x = (self.renderer)(&dir, &req); let x = (self.renderer)(&dir, &req);
match x { match x {
Ok(resp) => Either::A(ok(resp)), Ok(resp) => Either::Left(ok(resp)),
Err(e) => Either::A(ok(ServiceResponse::from_err(e, req))), Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))),
} }
} else { } else {
Either::A(ok(ServiceResponse::from_err( Either::Left(ok(ServiceResponse::from_err(
FilesError::IsDirectory, FilesError::IsDirectory,
req.into_parts().0, req.into_parts().0,
))) )))
@ -568,11 +575,11 @@ impl Service for FilesService {
named_file.flags = self.file_flags; named_file.flags = self.file_flags;
let (req, _) = req.into_parts(); let (req, _) = req.into_parts();
match named_file.respond_to(&req) { match named_file.into_response(&req) {
Ok(item) => { Ok(item) => {
Either::A(ok(ServiceResponse::new(req.clone(), item))) Either::Left(ok(ServiceResponse::new(req.clone(), item)))
} }
Err(e) => Either::A(ok(ServiceResponse::from_err(e, req))), Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))),
} }
} }
Err(e) => self.handle_err(e, req), Err(e) => self.handle_err(e, req),
@ -615,11 +622,11 @@ impl PathBufWrp {
impl FromRequest for PathBufWrp { impl FromRequest for PathBufWrp {
type Error = UriSegmentError; type Error = UriSegmentError;
type Future = Result<Self, Self::Error>; type Future = Ready<Result<Self, Self::Error>>;
type Config = (); type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
PathBufWrp::get_pathbuf(req.match_info().path()) ready(PathBufWrp::get_pathbuf(req.match_info().path()))
} }
} }
@ -630,8 +637,6 @@ mod tests {
use std::ops::Add; use std::ops::Add;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use bytes::BytesMut;
use super::*; use super::*;
use actix_web::guard; use actix_web::guard;
use actix_web::http::header::{ use actix_web::http::header::{
@ -639,8 +644,8 @@ mod tests {
}; };
use actix_web::http::{Method, StatusCode}; use actix_web::http::{Method, StatusCode};
use actix_web::middleware::Compress; use actix_web::middleware::Compress;
use actix_web::test::{self, TestRequest}; use actix_web::test::{self, block_on, TestRequest};
use actix_web::App; use actix_web::{App, Responder};
#[test] #[test]
fn test_file_extension_to_mime() { fn test_file_extension_to_mime() {
@ -656,6 +661,7 @@ mod tests {
#[test] #[test]
fn test_if_modified_since_without_if_none_match() { fn test_if_modified_since_without_if_none_match() {
block_on(async {
let file = NamedFile::open("Cargo.toml").unwrap(); let file = NamedFile::open("Cargo.toml").unwrap();
let since = let since =
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
@ -663,12 +669,14 @@ mod tests {
let req = TestRequest::default() let req = TestRequest::default()
.header(header::IF_MODIFIED_SINCE, since) .header(header::IF_MODIFIED_SINCE, since)
.to_http_request(); .to_http_request();
let resp = file.respond_to(&req).unwrap(); let resp = file.respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
})
} }
#[test] #[test]
fn test_if_modified_since_with_if_none_match() { fn test_if_modified_since_with_if_none_match() {
block_on(async {
let file = NamedFile::open("Cargo.toml").unwrap(); let file = NamedFile::open("Cargo.toml").unwrap();
let since = let since =
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
@ -677,12 +685,14 @@ mod tests {
.header(header::IF_NONE_MATCH, "miss_etag") .header(header::IF_NONE_MATCH, "miss_etag")
.header(header::IF_MODIFIED_SINCE, since) .header(header::IF_MODIFIED_SINCE, since)
.to_http_request(); .to_http_request();
let resp = file.respond_to(&req).unwrap(); let resp = file.respond_to(&req).await.unwrap();
assert_ne!(resp.status(), StatusCode::NOT_MODIFIED); assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
})
} }
#[test] #[test]
fn test_named_file_text() { fn test_named_file_text() {
block_on(async {
assert!(NamedFile::open("test--").is_err()); assert!(NamedFile::open("test--").is_err());
let mut file = NamedFile::open("Cargo.toml").unwrap(); let mut file = NamedFile::open("Cargo.toml").unwrap();
{ {
@ -694,7 +704,7 @@ mod tests {
} }
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req).unwrap(); let resp = file.respond_to(&req).await.unwrap();
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/x-toml" "text/x-toml"
@ -703,10 +713,12 @@ mod tests {
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"Cargo.toml\"" "inline; filename=\"Cargo.toml\""
); );
})
} }
#[test] #[test]
fn test_named_file_content_disposition() { fn test_named_file_content_disposition() {
block_on(async {
assert!(NamedFile::open("test--").is_err()); assert!(NamedFile::open("test--").is_err());
let mut file = NamedFile::open("Cargo.toml").unwrap(); let mut file = NamedFile::open("Cargo.toml").unwrap();
{ {
@ -718,7 +730,7 @@ mod tests {
} }
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req).unwrap(); let resp = file.respond_to(&req).await.unwrap();
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"Cargo.toml\"" "inline; filename=\"Cargo.toml\""
@ -728,12 +740,14 @@ mod tests {
.unwrap() .unwrap()
.disable_content_disposition(); .disable_content_disposition();
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req).unwrap(); let resp = file.respond_to(&req).await.unwrap();
assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none()); assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none());
})
} }
#[test] #[test]
fn test_named_file_non_ascii_file_name() { fn test_named_file_non_ascii_file_name() {
block_on(async {
let mut file = let mut file =
NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml") NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml")
.unwrap(); .unwrap();
@ -746,7 +760,7 @@ mod tests {
} }
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req).unwrap(); let resp = file.respond_to(&req).await.unwrap();
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/x-toml" "text/x-toml"
@ -755,10 +769,12 @@ mod tests {
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"貨物.toml\"; filename*=UTF-8''%E8%B2%A8%E7%89%A9.toml" "inline; filename=\"貨物.toml\"; filename*=UTF-8''%E8%B2%A8%E7%89%A9.toml"
); );
})
} }
#[test] #[test]
fn test_named_file_set_content_type() { fn test_named_file_set_content_type() {
block_on(async {
let mut file = NamedFile::open("Cargo.toml") let mut file = NamedFile::open("Cargo.toml")
.unwrap() .unwrap()
.set_content_type(mime::TEXT_XML); .set_content_type(mime::TEXT_XML);
@ -771,7 +787,7 @@ mod tests {
} }
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req).unwrap(); let resp = file.respond_to(&req).await.unwrap();
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/xml" "text/xml"
@ -780,10 +796,12 @@ mod tests {
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"Cargo.toml\"" "inline; filename=\"Cargo.toml\""
); );
})
} }
#[test] #[test]
fn test_named_file_image() { fn test_named_file_image() {
block_on(async {
let mut file = NamedFile::open("tests/test.png").unwrap(); let mut file = NamedFile::open("tests/test.png").unwrap();
{ {
file.file(); file.file();
@ -794,7 +812,7 @@ mod tests {
} }
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req).unwrap(); let resp = file.respond_to(&req).await.unwrap();
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"image/png" "image/png"
@ -803,10 +821,12 @@ mod tests {
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"test.png\"" "inline; filename=\"test.png\""
); );
})
} }
#[test] #[test]
fn test_named_file_image_attachment() { fn test_named_file_image_attachment() {
block_on(async {
let cd = ContentDisposition { let cd = ContentDisposition {
disposition: DispositionType::Attachment, disposition: DispositionType::Attachment,
parameters: vec![DispositionParam::Filename(String::from("test.png"))], parameters: vec![DispositionParam::Filename(String::from("test.png"))],
@ -823,7 +843,7 @@ mod tests {
} }
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req).unwrap(); let resp = file.respond_to(&req).await.unwrap();
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"image/png" "image/png"
@ -832,10 +852,12 @@ mod tests {
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"attachment; filename=\"test.png\"" "attachment; filename=\"test.png\""
); );
})
} }
#[test] #[test]
fn test_named_file_binary() { fn test_named_file_binary() {
block_on(async {
let mut file = NamedFile::open("tests/test.binary").unwrap(); let mut file = NamedFile::open("tests/test.binary").unwrap();
{ {
file.file(); file.file();
@ -846,7 +868,7 @@ mod tests {
} }
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req).unwrap(); let resp = file.respond_to(&req).await.unwrap();
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"application/octet-stream" "application/octet-stream"
@ -855,10 +877,12 @@ mod tests {
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"attachment; filename=\"test.binary\"" "attachment; filename=\"test.binary\""
); );
})
} }
#[test] #[test]
fn test_named_file_status_code_text() { fn test_named_file_status_code_text() {
block_on(async {
let mut file = NamedFile::open("Cargo.toml") let mut file = NamedFile::open("Cargo.toml")
.unwrap() .unwrap()
.set_status_code(StatusCode::NOT_FOUND); .set_status_code(StatusCode::NOT_FOUND);
@ -871,7 +895,7 @@ mod tests {
} }
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req).unwrap(); let resp = file.respond_to(&req).await.unwrap();
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/x-toml" "text/x-toml"
@ -881,10 +905,12 @@ mod tests {
"inline; filename=\"Cargo.toml\"" "inline; filename=\"Cargo.toml\""
); );
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
})
} }
#[test] #[test]
fn test_mime_override() { fn test_mime_override() {
block_on(async {
fn all_attachment(_: &mime::Name) -> DispositionType { fn all_attachment(_: &mime::Name) -> DispositionType {
DispositionType::Attachment DispositionType::Attachment
} }
@ -895,10 +921,11 @@ mod tests {
.mime_override(all_attachment) .mime_override(all_attachment)
.index_file("Cargo.toml"), .index_file("Cargo.toml"),
), ),
); )
.await;
let request = TestRequest::get().uri("/").to_request(); let request = TestRequest::get().uri("/").to_request();
let response = test::call_service(&mut srv, request); let response = test::call_service(&mut srv, request).await;
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
let content_disposition = response let content_disposition = response
@ -909,20 +936,23 @@ mod tests {
.to_str() .to_str()
.expect("Convert CONTENT_DISPOSITION to str"); .expect("Convert CONTENT_DISPOSITION to str");
assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\""); assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\"");
})
} }
#[test] #[test]
fn test_named_file_ranges_status_code() { fn test_named_file_ranges_status_code() {
block_on(async {
let mut srv = test::init_service( let mut srv = test::init_service(
App::new().service(Files::new("/test", ".").index_file("Cargo.toml")), App::new().service(Files::new("/test", ".").index_file("Cargo.toml")),
); )
.await;
// Valid range header // Valid range header
let request = TestRequest::get() let request = TestRequest::get()
.uri("/t%65st/Cargo.toml") .uri("/t%65st/Cargo.toml")
.header(header::RANGE, "bytes=10-20") .header(header::RANGE, "bytes=10-20")
.to_request(); .to_request();
let response = test::call_service(&mut srv, request); let response = test::call_service(&mut srv, request).await;
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
// Invalid range header // Invalid range header
@ -930,16 +960,20 @@ mod tests {
.uri("/t%65st/Cargo.toml") .uri("/t%65st/Cargo.toml")
.header(header::RANGE, "bytes=1-0") .header(header::RANGE, "bytes=1-0")
.to_request(); .to_request();
let response = test::call_service(&mut srv, request); let response = test::call_service(&mut srv, request).await;
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
})
} }
#[test] #[test]
fn test_named_file_content_range_headers() { fn test_named_file_content_range_headers() {
block_on(async {
let mut srv = test::init_service( let mut srv = test::init_service(
App::new().service(Files::new("/test", ".").index_file("tests/test.binary")), App::new()
); .service(Files::new("/test", ".").index_file("tests/test.binary")),
)
.await;
// Valid range header // Valid range header
let request = TestRequest::get() let request = TestRequest::get()
@ -947,7 +981,7 @@ mod tests {
.header(header::RANGE, "bytes=10-20") .header(header::RANGE, "bytes=10-20")
.to_request(); .to_request();
let response = test::call_service(&mut srv, request); let response = test::call_service(&mut srv, request).await;
let contentrange = response let contentrange = response
.headers() .headers()
.get(header::CONTENT_RANGE) .get(header::CONTENT_RANGE)
@ -962,7 +996,7 @@ mod tests {
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-5") .header(header::RANGE, "bytes=10-5")
.to_request(); .to_request();
let response = test::call_service(&mut srv, request); let response = test::call_service(&mut srv, request).await;
let contentrange = response let contentrange = response
.headers() .headers()
@ -972,22 +1006,26 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!(contentrange, "bytes */100"); assert_eq!(contentrange, "bytes */100");
})
} }
#[test] #[test]
fn test_named_file_content_length_headers() { fn test_named_file_content_length_headers() {
block_on(async {
// use actix_web::body::{MessageBody, ResponseBody}; // use actix_web::body::{MessageBody, ResponseBody};
let mut srv = test::init_service( let mut srv = test::init_service(
App::new().service(Files::new("test", ".").index_file("tests/test.binary")), App::new()
); .service(Files::new("test", ".").index_file("tests/test.binary")),
)
.await;
// Valid range header // Valid range header
let request = TestRequest::get() let request = TestRequest::get()
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-20") .header(header::RANGE, "bytes=10-20")
.to_request(); .to_request();
let _response = test::call_service(&mut srv, request); let _response = test::call_service(&mut srv, request).await;
// let contentlength = response // let contentlength = response
// .headers() // .headers()
@ -1002,7 +1040,7 @@ mod tests {
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-8") .header(header::RANGE, "bytes=10-8")
.to_request(); .to_request();
let response = test::call_service(&mut srv, request); let response = test::call_service(&mut srv, request).await;
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
// Without range header // Without range header
@ -1010,7 +1048,7 @@ mod tests {
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
// .no_default_headers() // .no_default_headers()
.to_request(); .to_request();
let _response = test::call_service(&mut srv, request); let _response = test::call_service(&mut srv, request).await;
// let contentlength = response // let contentlength = response
// .headers() // .headers()
@ -1024,7 +1062,7 @@ mod tests {
let request = TestRequest::get() let request = TestRequest::get()
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
.to_request(); .to_request();
let mut response = test::call_service(&mut srv, request); let response = test::call_service(&mut srv, request).await;
// with enabled compression // with enabled compression
// { // {
@ -1037,28 +1075,27 @@ mod tests {
// assert_eq!(te, "chunked"); // assert_eq!(te, "chunked");
// } // }
let bytes = let bytes = test::read_body(response).await;
test::block_on(response.take_body().fold(BytesMut::new(), |mut b, c| {
b.extend(c);
Ok::<_, Error>(b)
}))
.unwrap();
let data = Bytes::from(fs::read("tests/test.binary").unwrap()); let data = Bytes::from(fs::read("tests/test.binary").unwrap());
assert_eq!(bytes.freeze(), data); assert_eq!(bytes, data);
})
} }
#[test] #[test]
fn test_head_content_length_headers() { fn test_head_content_length_headers() {
block_on(async {
let mut srv = test::init_service( let mut srv = test::init_service(
App::new().service(Files::new("test", ".").index_file("tests/test.binary")), App::new()
); .service(Files::new("test", ".").index_file("tests/test.binary")),
)
.await;
// Valid range header // Valid range header
let request = TestRequest::default() let request = TestRequest::default()
.method(Method::HEAD) .method(Method::HEAD)
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
.to_request(); .to_request();
let _response = test::call_service(&mut srv, request); let _response = test::call_service(&mut srv, request).await;
// TODO: fix check // TODO: fix check
// let contentlength = response // let contentlength = response
@ -1068,100 +1105,112 @@ mod tests {
// .to_str() // .to_str()
// .unwrap(); // .unwrap();
// assert_eq!(contentlength, "100"); // assert_eq!(contentlength, "100");
})
} }
#[test] #[test]
fn test_static_files_with_spaces() { fn test_static_files_with_spaces() {
block_on(async {
let mut srv = test::init_service( let mut srv = test::init_service(
App::new().service(Files::new("/", ".").index_file("Cargo.toml")), App::new().service(Files::new("/", ".").index_file("Cargo.toml")),
); )
.await;
let request = TestRequest::get() let request = TestRequest::get()
.uri("/tests/test%20space.binary") .uri("/tests/test%20space.binary")
.to_request(); .to_request();
let mut response = test::call_service(&mut srv, request); let response = test::call_service(&mut srv, request).await;
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
let bytes = let bytes = test::read_body(response).await;
test::block_on(response.take_body().fold(BytesMut::new(), |mut b, c| {
b.extend(c);
Ok::<_, Error>(b)
}))
.unwrap();
let data = Bytes::from(fs::read("tests/test space.binary").unwrap()); let data = Bytes::from(fs::read("tests/test space.binary").unwrap());
assert_eq!(bytes.freeze(), data); assert_eq!(bytes, data);
})
} }
#[test] #[test]
fn test_files_not_allowed() { fn test_files_not_allowed() {
let mut srv = test::init_service(App::new().service(Files::new("/", "."))); block_on(async {
let mut srv =
test::init_service(App::new().service(Files::new("/", "."))).await;
let req = TestRequest::default() let req = TestRequest::default()
.uri("/Cargo.toml") .uri("/Cargo.toml")
.method(Method::POST) .method(Method::POST)
.to_request(); .to_request();
let resp = test::call_service(&mut srv, req); let resp = test::call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let mut srv = test::init_service(App::new().service(Files::new("/", "."))); let mut srv =
test::init_service(App::new().service(Files::new("/", "."))).await;
let req = TestRequest::default() let req = TestRequest::default()
.method(Method::PUT) .method(Method::PUT)
.uri("/Cargo.toml") .uri("/Cargo.toml")
.to_request(); .to_request();
let resp = test::call_service(&mut srv, req); let resp = test::call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
})
} }
#[test] #[test]
fn test_files_guards() { fn test_files_guards() {
block_on(async {
let mut srv = test::init_service( let mut srv = test::init_service(
App::new().service(Files::new("/", ".").use_guards(guard::Post())), App::new().service(Files::new("/", ".").use_guards(guard::Post())),
); )
.await;
let req = TestRequest::default() let req = TestRequest::default()
.uri("/Cargo.toml") .uri("/Cargo.toml")
.method(Method::POST) .method(Method::POST)
.to_request(); .to_request();
let resp = test::call_service(&mut srv, req); let resp = test::call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
})
} }
#[test] #[test]
fn test_named_file_content_encoding() { fn test_named_file_content_encoding() {
let mut srv = test::init_service(App::new().wrap(Compress::default()).service( block_on(async {
let mut srv =
test::init_service(App::new().wrap(Compress::default()).service(
web::resource("/").to(|| { web::resource("/").to(|| {
NamedFile::open("Cargo.toml") NamedFile::open("Cargo.toml")
.unwrap() .unwrap()
.set_content_encoding(header::ContentEncoding::Identity) .set_content_encoding(header::ContentEncoding::Identity)
}), }),
)); ))
.await;
let request = TestRequest::get() let request = TestRequest::get()
.uri("/") .uri("/")
.header(header::ACCEPT_ENCODING, "gzip") .header(header::ACCEPT_ENCODING, "gzip")
.to_request(); .to_request();
let res = test::call_service(&mut srv, request); let res = test::call_service(&mut srv, request).await;
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
})
} }
#[test] #[test]
fn test_named_file_content_encoding_gzip() { fn test_named_file_content_encoding_gzip() {
let mut srv = test::init_service(App::new().wrap(Compress::default()).service( block_on(async {
let mut srv =
test::init_service(App::new().wrap(Compress::default()).service(
web::resource("/").to(|| { web::resource("/").to(|| {
NamedFile::open("Cargo.toml") NamedFile::open("Cargo.toml")
.unwrap() .unwrap()
.set_content_encoding(header::ContentEncoding::Gzip) .set_content_encoding(header::ContentEncoding::Gzip)
}), }),
)); ))
.await;
let request = TestRequest::get() let request = TestRequest::get()
.uri("/") .uri("/")
.header(header::ACCEPT_ENCODING, "gzip") .header(header::ACCEPT_ENCODING, "gzip")
.to_request(); .to_request();
let res = test::call_service(&mut srv, request); let res = test::call_service(&mut srv, request).await;
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert_eq!( assert_eq!(
res.headers() res.headers()
@ -1171,59 +1220,64 @@ mod tests {
.unwrap(), .unwrap(),
"gzip" "gzip"
); );
})
} }
#[test] #[test]
fn test_named_file_allowed_method() { fn test_named_file_allowed_method() {
block_on(async {
let req = TestRequest::default().method(Method::GET).to_http_request(); let req = TestRequest::default().method(Method::GET).to_http_request();
let file = NamedFile::open("Cargo.toml").unwrap(); let file = NamedFile::open("Cargo.toml").unwrap();
let resp = file.respond_to(&req).unwrap(); let resp = file.respond_to(&req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
})
} }
#[test] #[test]
fn test_static_files() { fn test_static_files() {
block_on(async {
let mut srv = test::init_service( let mut srv = test::init_service(
App::new().service(Files::new("/", ".").show_files_listing()), App::new().service(Files::new("/", ".").show_files_listing()),
); )
.await;
let req = TestRequest::with_uri("/missing").to_request(); let req = TestRequest::with_uri("/missing").to_request();
let resp = test::call_service(&mut srv, req); let resp = test::call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut srv = test::init_service(App::new().service(Files::new("/", "."))); let mut srv =
test::init_service(App::new().service(Files::new("/", "."))).await;
let req = TestRequest::default().to_request(); let req = TestRequest::default().to_request();
let resp = test::call_service(&mut srv, req); let resp = test::call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut srv = test::init_service( let mut srv = test::init_service(
App::new().service(Files::new("/", ".").show_files_listing()), App::new().service(Files::new("/", ".").show_files_listing()),
); )
.await;
let req = TestRequest::with_uri("/tests").to_request(); let req = TestRequest::with_uri("/tests").to_request();
let mut resp = test::call_service(&mut srv, req); let resp = test::call_service(&mut srv, req).await;
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/html; charset=utf-8" "text/html; charset=utf-8"
); );
let bytes = let bytes = test::read_body(resp).await;
test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| {
b.extend(c);
Ok::<_, Error>(b)
}))
.unwrap();
assert!(format!("{:?}", bytes).contains("/tests/test.png")); assert!(format!("{:?}", bytes).contains("/tests/test.png"));
})
} }
#[test] #[test]
fn test_redirect_to_slash_directory() { fn test_redirect_to_slash_directory() {
block_on(async {
// should not redirect if no index // should not redirect if no index
let mut srv = test::init_service( let mut srv = test::init_service(
App::new().service(Files::new("/", ".").redirect_to_slash_directory()), App::new().service(Files::new("/", ".").redirect_to_slash_directory()),
); )
.await;
let req = TestRequest::with_uri("/tests").to_request(); let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&mut srv, req); let resp = test::call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
// should redirect if index present // should redirect if index present
@ -1233,15 +1287,17 @@ mod tests {
.index_file("test.png") .index_file("test.png")
.redirect_to_slash_directory(), .redirect_to_slash_directory(),
), ),
); )
.await;
let req = TestRequest::with_uri("/tests").to_request(); let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&mut srv, req); let resp = test::call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::FOUND);
// should not redirect if the path is wrong // should not redirect if the path is wrong
let req = TestRequest::with_uri("/not_existing").to_request(); let req = TestRequest::with_uri("/not_existing").to_request();
let resp = test::call_service(&mut srv, req); let resp = test::call_service(&mut srv, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
})
} }
#[test] #[test]
@ -1252,26 +1308,21 @@ mod tests {
#[test] #[test]
fn test_default_handler_file_missing() { fn test_default_handler_file_missing() {
let mut st = test::block_on( block_on(async {
Files::new("/", ".") let mut st = Files::new("/", ".")
.default_handler(|req: ServiceRequest| { .default_handler(|req: ServiceRequest| {
Ok(req.into_response(HttpResponse::Ok().body("default content"))) ok(req.into_response(HttpResponse::Ok().body("default content")))
}) })
.new_service(&()), .new_service(&())
) .await
.unwrap(); .unwrap();
let req = TestRequest::with_uri("/missing").to_srv_request(); let req = TestRequest::with_uri("/missing").to_srv_request();
let mut resp = test::call_service(&mut st, req); let resp = test::call_service(&mut st, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let bytes = let bytes = test::read_body(resp).await;
test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| { assert_eq!(bytes, Bytes::from_static(b"default content"));
b.extend(c); })
Ok::<_, Error>(b)
}))
.unwrap();
assert_eq!(bytes.freeze(), Bytes::from_static(b"default content"));
} }
// #[test] // #[test]

View file

@ -18,6 +18,7 @@ use actix_web::http::header::{
use actix_web::http::{ContentEncoding, StatusCode}; use actix_web::http::{ContentEncoding, StatusCode};
use actix_web::middleware::BodyEncoding; use actix_web::middleware::BodyEncoding;
use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder};
use futures::future::{ready, Ready};
use crate::range::HttpRange; use crate::range::HttpRange;
use crate::ChunkedReadFile; use crate::ChunkedReadFile;
@ -255,62 +256,8 @@ impl NamedFile {
pub(crate) fn last_modified(&self) -> Option<header::HttpDate> { pub(crate) fn last_modified(&self) -> Option<header::HttpDate> {
self.modified.map(|mtime| mtime.into()) self.modified.map(|mtime| mtime.into())
} }
}
impl Deref for NamedFile { pub fn into_response(self, req: &HttpRequest) -> Result<HttpResponse, Error> {
type Target = File;
fn deref(&self) -> &File {
&self.file
}
}
impl DerefMut for NamedFile {
fn deref_mut(&mut self) -> &mut File {
&mut self.file
}
}
/// Returns true if `req` has no `If-Match` header or one which matches `etag`.
fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
match req.get_header::<header::IfMatch>() {
None | Some(header::IfMatch::Any) => true,
Some(header::IfMatch::Items(ref items)) => {
if let Some(some_etag) = etag {
for item in items {
if item.strong_eq(some_etag) {
return true;
}
}
}
false
}
}
}
/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`.
fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
match req.get_header::<header::IfNoneMatch>() {
Some(header::IfNoneMatch::Any) => false,
Some(header::IfNoneMatch::Items(ref items)) => {
if let Some(some_etag) = etag {
for item in items {
if item.weak_eq(some_etag) {
return false;
}
}
}
true
}
None => true,
}
}
impl Responder for NamedFile {
type Error = Error;
type Future = Result<HttpResponse, Error>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
if self.status_code != StatusCode::OK { if self.status_code != StatusCode::OK {
let mut resp = HttpResponse::build(self.status_code); let mut resp = HttpResponse::build(self.status_code);
resp.set(header::ContentType(self.content_type.clone())) resp.set(header::ContentType(self.content_type.clone()))
@ -442,8 +389,67 @@ impl Responder for NamedFile {
counter: 0, counter: 0,
}; };
if offset != 0 || length != self.md.len() { if offset != 0 || length != self.md.len() {
return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)); Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader))
}; } else {
Ok(resp.body(SizedStream::new(length, reader))) Ok(resp.body(SizedStream::new(length, reader)))
} }
} }
}
impl Deref for NamedFile {
type Target = File;
fn deref(&self) -> &File {
&self.file
}
}
impl DerefMut for NamedFile {
fn deref_mut(&mut self) -> &mut File {
&mut self.file
}
}
/// Returns true if `req` has no `If-Match` header or one which matches `etag`.
fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
match req.get_header::<header::IfMatch>() {
None | Some(header::IfMatch::Any) => true,
Some(header::IfMatch::Items(ref items)) => {
if let Some(some_etag) = etag {
for item in items {
if item.strong_eq(some_etag) {
return true;
}
}
}
false
}
}
}
/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`.
fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
match req.get_header::<header::IfNoneMatch>() {
Some(header::IfNoneMatch::Any) => false,
Some(header::IfNoneMatch::Items(ref items)) => {
if let Some(some_etag) = etag {
for item in items {
if item.weak_eq(some_etag) {
return false;
}
}
}
true
}
None => true,
}
}
impl Responder for NamedFile {
type Error = Error;
type Future = Ready<Result<HttpResponse, Error>>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
ready(self.into_response(req))
}
}

View file

@ -9,7 +9,7 @@ use std::{fmt, io, result};
use actix_utils::timeout::TimeoutError; use actix_utils::timeout::TimeoutError;
use bytes::BytesMut; use bytes::BytesMut;
use derive_more::{Display, From}; use derive_more::{Display, From};
use futures::channel::oneshot::Canceled; pub use futures::channel::oneshot::Canceled;
use http::uri::InvalidUri; use http::uri::InvalidUri;
use http::{header, Error as HttpError, StatusCode}; use http::{header, Error as HttpError, StatusCode};
use httparse; use httparse;