1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-07-02 03: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",
"actix-http",
"actix-cors",
#"actix-files",
"actix-files",
#"actix-framed",
#"actix-session",
"actix-identity",
@ -126,7 +126,7 @@ actix-http-test = { path = "test-server" }
actix-web-codegen = { path = "actix-web-codegen" }
# actix-web-actors = { path = "actix-web-actors" }
# actix-session = { path = "actix-session" }
# actix-files = { path = "actix-files" }
actix-files = { path = "actix-files" }
# actix-multipart = { path = "actix-multipart" }
awc = { path = "awc" }

View file

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

View file

@ -18,6 +18,7 @@ use actix_web::http::header::{
use actix_web::http::{ContentEncoding, StatusCode};
use actix_web::middleware::BodyEncoding;
use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder};
use futures::future::{ready, Ready};
use crate::range::HttpRange;
use crate::ChunkedReadFile;
@ -255,62 +256,8 @@ impl NamedFile {
pub(crate) fn last_modified(&self) -> Option<header::HttpDate> {
self.modified.map(|mtime| mtime.into())
}
}
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 = Result<HttpResponse, Error>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
pub fn into_response(self, req: &HttpRequest) -> Result<HttpResponse, Error> {
if self.status_code != StatusCode::OK {
let mut resp = HttpResponse::build(self.status_code);
resp.set(header::ContentType(self.content_type.clone()))
@ -442,8 +389,67 @@ impl Responder for NamedFile {
counter: 0,
};
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)))
}
}
}
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 bytes::BytesMut;
use derive_more::{Display, From};
use futures::channel::oneshot::Canceled;
pub use futures::channel::oneshot::Canceled;
use http::uri::InvalidUri;
use http::{header, Error as HttpError, StatusCode};
use httparse;