mirror of
https://github.com/fafhrd91/actix-web
synced 2025-08-08 07:43:42 +02:00
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9f9e0b98ad | ||
|
556646aaec | ||
|
174fb0b5f4 | ||
|
836706653b | ||
|
17f1a2b92a | ||
|
3b08b16c11 | ||
|
68eb2f26c9 | ||
|
72757887c9 | ||
|
eb5dbd43ae | ||
|
1f1dfac3f9 | ||
|
2479b14aba | ||
|
ac24703512 | ||
|
db0091ba6f | ||
|
2159158c30 | ||
|
76d790425f | ||
|
90968d4333 | ||
|
577a509875 |
20
CHANGES.md
20
CHANGES.md
@@ -1,5 +1,25 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
## [0.6.10] - 2018-05-24
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Allow to use path without traling slashes for scope registration #241
|
||||||
|
|
||||||
|
* Allow to set encoding for exact NamedFile #239
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* `TestServer::post()` actually sends `GET` request #240
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.9 (2018-05-22)
|
||||||
|
|
||||||
|
* Drop connection if request's payload is not fully consumed #236
|
||||||
|
|
||||||
|
* Fix streaming response with body compression
|
||||||
|
|
||||||
|
|
||||||
## 0.6.8 (2018-05-20)
|
## 0.6.8 (2018-05-20)
|
||||||
|
|
||||||
* Fix scope resource path extractor #234
|
* Fix scope resource path extractor #234
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "0.6.8"
|
version = "0.6.10"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://github.com/actix/actix-web"
|
homepage = "https://actix.rs"
|
||||||
repository = "https://github.com/actix/actix-web.git"
|
repository = "https://github.com/actix/actix-web.git"
|
||||||
documentation = "https://docs.rs/actix-web/"
|
documentation = "https://docs.rs/actix-web/"
|
||||||
categories = ["network-programming", "asynchronous",
|
categories = ["network-programming", "asynchronous",
|
||||||
|
14
README.md
14
README.md
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
||||||
|
|
||||||
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/book/actix-web/sec-12-http2.html) protocols
|
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols
|
||||||
* Streaming and pipelining
|
* Streaming and pipelining
|
||||||
* Keep-alive and slow requests handling
|
* Keep-alive and slow requests handling
|
||||||
* Client/server [WebSockets](https://actix.rs/book/actix-web/sec-11-websockets.html) support
|
* Client/server [WebSockets](https://actix.rs/docs/websockets/) support
|
||||||
* Transparent content compression/decompression (br, gzip, deflate)
|
* Transparent content compression/decompression (br, gzip, deflate)
|
||||||
* Configurable [request routing](https://actix.rs/book/actix-web/sec-6-url-dispatch.html)
|
* Configurable [request routing](https://actix.rs/docs/url-dispatch/)
|
||||||
* Graceful server shutdown
|
* Graceful server shutdown
|
||||||
* Multipart streams
|
* Multipart streams
|
||||||
* Static assets
|
* Static assets
|
||||||
@@ -18,12 +18,12 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
|||||||
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
|
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
|
||||||
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
|
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
|
||||||
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
|
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
|
||||||
* Includes an asynchronous [HTTP client](https://github.com/actix/actix-web/blob/master/src/client/mod.rs)
|
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
|
||||||
* Built on top of [Actix actor framework](https://github.com/actix/actix)
|
* Built on top of [Actix actor framework](https://github.com/actix/actix)
|
||||||
|
|
||||||
## Documentation & community resources
|
## Documentation & community resources
|
||||||
|
|
||||||
* [User Guide](https://actix.rs/book/actix-web/)
|
* [User Guide](https://actix.rs/docs/)
|
||||||
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
|
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
|
||||||
* [API Documentation (Releases)](https://docs.rs/actix-web/)
|
* [API Documentation (Releases)](https://docs.rs/actix-web/)
|
||||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||||
@@ -34,9 +34,9 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
extern crate actix_web;
|
extern crate actix_web;
|
||||||
use actix_web::{http, server, App, Path};
|
use actix_web::{http, server, App, Path, Responder};
|
||||||
|
|
||||||
fn index(info: Path<(u32, String)>) -> String {
|
fn index(info: Path<(u32, String)>) -> impl Responder {
|
||||||
format!("Hello {}! id:{}", info.1, info.0)
|
format!("Hello {}! id:{}", info.1, info.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -107,16 +107,12 @@ impl<S: 'static> HttpApplication<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let prefix_len = inner.prefix + prefix_len - 1;
|
let prefix_len = inner.prefix + prefix_len;
|
||||||
let path: &'static str =
|
let path: &'static str =
|
||||||
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
||||||
|
|
||||||
req.set_prefix_len(prefix_len as u16);
|
req.set_prefix_len(prefix_len as u16);
|
||||||
if path.is_empty() {
|
req.match_info_mut().set("tail", path);
|
||||||
req.match_info_mut().set("tail", "/");
|
|
||||||
} else {
|
|
||||||
req.match_info_mut().set("tail", path);
|
|
||||||
}
|
|
||||||
return HandlerType::Handler(idx);
|
return HandlerType::Handler(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,14 +365,6 @@ where
|
|||||||
{
|
{
|
||||||
{
|
{
|
||||||
let mut scope = Box::new(f(Scope::new()));
|
let mut scope = Box::new(f(Scope::new()));
|
||||||
|
|
||||||
let mut path = path.trim().trim_right_matches('/').to_owned();
|
|
||||||
if !path.is_empty() && !path.starts_with('/') {
|
|
||||||
path.insert(0, '/')
|
|
||||||
}
|
|
||||||
if !path.ends_with('/') {
|
|
||||||
path.push('/');
|
|
||||||
}
|
|
||||||
let parts = self.parts.as_mut().expect("Use after finish");
|
let parts = self.parts.as_mut().expect("Use after finish");
|
||||||
|
|
||||||
let filters = scope.take_filters();
|
let filters = scope.take_filters();
|
||||||
|
60
src/fs.rs
60
src/fs.rs
@@ -20,7 +20,7 @@ use mime_guess::{get_mime_type, guess_mime_type};
|
|||||||
use error::Error;
|
use error::Error;
|
||||||
use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler};
|
use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler};
|
||||||
use header;
|
use header;
|
||||||
use http::{HttpRange, Method, StatusCode};
|
use http::{ContentEncoding, HttpRange, Method, StatusCode};
|
||||||
use httpmessage::HttpMessage;
|
use httpmessage::HttpMessage;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
@@ -38,6 +38,7 @@ pub struct NamedFile {
|
|||||||
md: Metadata,
|
md: Metadata,
|
||||||
modified: Option<SystemTime>,
|
modified: Option<SystemTime>,
|
||||||
cpu_pool: Option<CpuPool>,
|
cpu_pool: Option<CpuPool>,
|
||||||
|
encoding: Option<ContentEncoding>,
|
||||||
only_get: bool,
|
only_get: bool,
|
||||||
status_code: StatusCode,
|
status_code: StatusCode,
|
||||||
}
|
}
|
||||||
@@ -58,12 +59,14 @@ impl NamedFile {
|
|||||||
let path = path.as_ref().to_path_buf();
|
let path = path.as_ref().to_path_buf();
|
||||||
let modified = md.modified().ok();
|
let modified = md.modified().ok();
|
||||||
let cpu_pool = None;
|
let cpu_pool = None;
|
||||||
|
let encoding = None;
|
||||||
Ok(NamedFile {
|
Ok(NamedFile {
|
||||||
path,
|
path,
|
||||||
file,
|
file,
|
||||||
md,
|
md,
|
||||||
modified,
|
modified,
|
||||||
cpu_pool,
|
cpu_pool,
|
||||||
|
encoding,
|
||||||
only_get: false,
|
only_get: false,
|
||||||
status_code: StatusCode::OK,
|
status_code: StatusCode::OK,
|
||||||
})
|
})
|
||||||
@@ -114,6 +117,13 @@ impl NamedFile {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set content encoding for serving this file
|
||||||
|
#[inline]
|
||||||
|
pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self {
|
||||||
|
self.encoding = Some(enc);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn etag(&self) -> Option<header::EntityTag> {
|
fn etag(&self) -> Option<header::EntityTag> {
|
||||||
// This etag format is similar to Apache's.
|
// This etag format is similar to Apache's.
|
||||||
self.modified.as_ref().map(|mtime| {
|
self.modified.as_ref().map(|mtime| {
|
||||||
@@ -219,6 +229,9 @@ impl Responder for NamedFile {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
if let Some(current_encoding) = self.encoding {
|
||||||
|
resp.content_encoding(current_encoding);
|
||||||
|
}
|
||||||
let reader = ChunkedReadFile {
|
let reader = ChunkedReadFile {
|
||||||
size: self.md.len(),
|
size: self.md.len(),
|
||||||
offset: 0,
|
offset: 0,
|
||||||
@@ -264,6 +277,9 @@ impl Responder for NamedFile {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut resp = HttpResponse::build(self.status_code);
|
let mut resp = HttpResponse::build(self.status_code);
|
||||||
|
if let Some(current_encoding) = self.encoding {
|
||||||
|
resp.content_encoding(current_encoding);
|
||||||
|
}
|
||||||
|
|
||||||
resp.if_some(self.path().extension(), |ext, resp| {
|
resp.if_some(self.path().extension(), |ext, resp| {
|
||||||
resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy())));
|
resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy())));
|
||||||
@@ -300,14 +316,15 @@ impl Responder for NamedFile {
|
|||||||
if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) {
|
if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) {
|
||||||
length = rangesvec[0].length;
|
length = rangesvec[0].length;
|
||||||
offset = rangesvec[0].start;
|
offset = rangesvec[0].start;
|
||||||
|
resp.content_encoding(ContentEncoding::Identity);
|
||||||
resp.header(
|
resp.header(
|
||||||
header::CONTENT_RANGE,
|
header::CONTENT_RANGE,
|
||||||
format!(
|
format!(
|
||||||
"bytes {}-{}/{}",
|
"bytes {}-{}/{}",
|
||||||
offset,
|
offset,
|
||||||
offset + length - 1,
|
offset + length - 1,
|
||||||
self.md.len()
|
self.md.len()
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
resp.header(header::CONTENT_RANGE, format!("bytes */{}", length));
|
resp.header(header::CONTENT_RANGE, format!("bytes */{}", length));
|
||||||
@@ -898,6 +915,7 @@ mod tests {
|
|||||||
let request = srv
|
let request = srv
|
||||||
.get()
|
.get()
|
||||||
.uri(srv.url("/t%65st/tests/test.binary"))
|
.uri(srv.url("/t%65st/tests/test.binary"))
|
||||||
|
.no_default_headers()
|
||||||
.finish()
|
.finish()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -911,6 +929,23 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(contentlength, "100");
|
assert_eq!(contentlength, "100");
|
||||||
|
|
||||||
|
// chunked
|
||||||
|
let request = srv
|
||||||
|
.get()
|
||||||
|
.uri(srv.url("/t%65st/tests/test.binary"))
|
||||||
|
.finish()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = srv.execute(request.send()).unwrap();
|
||||||
|
|
||||||
|
let te = response
|
||||||
|
.headers()
|
||||||
|
.get(header::TRANSFER_ENCODING)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(te, "chunked");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -922,6 +957,21 @@ mod tests {
|
|||||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_named_file_content_encoding() {
|
||||||
|
let req = TestRequest::default().method(Method::GET).finish();
|
||||||
|
let file = NamedFile::open("Cargo.toml").unwrap();
|
||||||
|
|
||||||
|
assert!(file.encoding.is_none());
|
||||||
|
let resp = file
|
||||||
|
.set_content_encoding(ContentEncoding::Identity)
|
||||||
|
.respond_to(&req)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(resp.content_encoding().is_some());
|
||||||
|
assert_eq!(resp.content_encoding().unwrap().as_str(), "identity");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_named_file_any_method() {
|
fn test_named_file_any_method() {
|
||||||
let req = TestRequest::default().method(Method::POST).finish();
|
let req = TestRequest::default().method(Method::POST).finish();
|
||||||
|
@@ -25,7 +25,7 @@
|
|||||||
//! Besides the API documentation (which you are currently looking
|
//! Besides the API documentation (which you are currently looking
|
||||||
//! at!), several other resources are available:
|
//! at!), several other resources are available:
|
||||||
//!
|
//!
|
||||||
//! * [User Guide](https://actix.rs/book/actix-web/)
|
//! * [User Guide](https://actix.rs/docs/)
|
||||||
//! * [Chat on gitter](https://gitter.im/actix/actix)
|
//! * [Chat on gitter](https://gitter.im/actix/actix)
|
||||||
//! * [GitHub repository](https://github.com/actix/actix-web)
|
//! * [GitHub repository](https://github.com/actix/actix-web)
|
||||||
//! * [Cargo package](https://crates.io/crates/actix-web)
|
//! * [Cargo package](https://crates.io/crates/actix-web)
|
||||||
|
@@ -58,12 +58,11 @@ impl<'a> Params<'a> {
|
|||||||
self.0.push((name, value));
|
self.0.push((name, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn remove(&mut self, name: &str)
|
pub(crate) fn remove(&mut self, name: &str) {
|
||||||
{
|
|
||||||
for idx in (0..self.0.len()).rev() {
|
for idx in (0..self.0.len()).rev() {
|
||||||
if self.0[idx].0 == name {
|
if self.0[idx].0 == name {
|
||||||
self.0.remove(idx);
|
self.0.remove(idx);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -311,8 +311,16 @@ impl Resource {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PatternType::Prefix(ref s) => if path.starts_with(s) {
|
PatternType::Prefix(ref s) => if path == s {
|
||||||
Some(s.len())
|
Some(s.len())
|
||||||
|
} else if path.starts_with(s)
|
||||||
|
&& (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/'))
|
||||||
|
{
|
||||||
|
if s.ends_with('/') {
|
||||||
|
Some(s.len() - 1)
|
||||||
|
} else {
|
||||||
|
Some(s.len())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
178
src/scope.rs
178
src/scope.rs
@@ -137,14 +137,6 @@ impl<S: 'static> Scope<S> {
|
|||||||
};
|
};
|
||||||
let mut scope = f(scope);
|
let mut scope = f(scope);
|
||||||
|
|
||||||
let mut path = path.trim().trim_right_matches('/').to_owned();
|
|
||||||
if !path.is_empty() && !path.starts_with('/') {
|
|
||||||
path.insert(0, '/')
|
|
||||||
}
|
|
||||||
if !path.ends_with('/') {
|
|
||||||
path.push('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
let state = Rc::new(state);
|
let state = Rc::new(state);
|
||||||
let filters: Vec<Box<Predicate<S>>> = vec![Box::new(FiltersWrapper {
|
let filters: Vec<Box<Predicate<S>>> = vec![Box::new(FiltersWrapper {
|
||||||
state: Rc::clone(&state),
|
state: Rc::clone(&state),
|
||||||
@@ -191,14 +183,6 @@ impl<S: 'static> Scope<S> {
|
|||||||
};
|
};
|
||||||
let mut scope = f(scope);
|
let mut scope = f(scope);
|
||||||
|
|
||||||
let mut path = path.trim().trim_right_matches('/').to_owned();
|
|
||||||
if !path.is_empty() && !path.starts_with('/') {
|
|
||||||
path.insert(0, '/')
|
|
||||||
}
|
|
||||||
if !path.ends_with('/') {
|
|
||||||
path.push('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
let filters = scope.take_filters();
|
let filters = scope.take_filters();
|
||||||
self.nested.push((
|
self.nested.push((
|
||||||
Resource::prefix("", &path),
|
Resource::prefix("", &path),
|
||||||
@@ -253,7 +237,12 @@ impl<S: 'static> Scope<S> {
|
|||||||
|
|
||||||
let mut handler = ResourceHandler::default();
|
let mut handler = ResourceHandler::default();
|
||||||
handler.method(method).with(f);
|
handler.method(method).with(f);
|
||||||
let pattern = Resource::new(handler.get_name(), path);
|
let pattern = Resource::with_prefix(
|
||||||
|
handler.get_name(),
|
||||||
|
path,
|
||||||
|
if path.is_empty() { "" } else { "/" },
|
||||||
|
false,
|
||||||
|
);
|
||||||
Rc::get_mut(&mut self.resources)
|
Rc::get_mut(&mut self.resources)
|
||||||
.expect("Can not use after configuration")
|
.expect("Can not use after configuration")
|
||||||
.push((pattern, Rc::new(UnsafeCell::new(handler))));
|
.push((pattern, Rc::new(UnsafeCell::new(handler))));
|
||||||
@@ -293,7 +282,12 @@ impl<S: 'static> Scope<S> {
|
|||||||
let mut handler = ResourceHandler::default();
|
let mut handler = ResourceHandler::default();
|
||||||
f(&mut handler);
|
f(&mut handler);
|
||||||
|
|
||||||
let pattern = Resource::new(handler.get_name(), path);
|
let pattern = Resource::with_prefix(
|
||||||
|
handler.get_name(),
|
||||||
|
path,
|
||||||
|
if path.is_empty() { "" } else { "/" },
|
||||||
|
false,
|
||||||
|
);
|
||||||
Rc::get_mut(&mut self.resources)
|
Rc::get_mut(&mut self.resources)
|
||||||
.expect("Can not use after configuration")
|
.expect("Can not use after configuration")
|
||||||
.push((pattern, Rc::new(UnsafeCell::new(handler))));
|
.push((pattern, Rc::new(UnsafeCell::new(handler))));
|
||||||
@@ -329,7 +323,6 @@ impl<S: 'static> Scope<S> {
|
|||||||
impl<S: 'static> RouteHandler<S> for Scope<S> {
|
impl<S: 'static> RouteHandler<S> for Scope<S> {
|
||||||
fn handle(&mut self, mut req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
fn handle(&mut self, mut req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||||
let path = unsafe { &*(&req.match_info()["tail"] as *const _) };
|
let path = unsafe { &*(&req.match_info()["tail"] as *const _) };
|
||||||
let path = if path == "" { "/" } else { path };
|
|
||||||
|
|
||||||
// recognize resources
|
// recognize resources
|
||||||
for &(ref pattern, ref resource) in self.resources.iter() {
|
for &(ref pattern, ref resource) in self.resources.iter() {
|
||||||
@@ -364,16 +357,12 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
|
|||||||
continue 'outer;
|
continue 'outer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let prefix_len = len + prefix_len - 1;
|
let prefix_len = len + prefix_len;
|
||||||
let path: &'static str =
|
let path: &'static str =
|
||||||
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
||||||
|
|
||||||
req.set_prefix_len(prefix_len as u16);
|
req.set_prefix_len(prefix_len as u16);
|
||||||
if path.is_empty() {
|
req.match_info_mut().set("tail", path);
|
||||||
req.match_info_mut().set("tail", "/");
|
|
||||||
} else {
|
|
||||||
req.match_info_mut().set("tail", path);
|
|
||||||
}
|
|
||||||
|
|
||||||
let hnd: &mut RouteHandler<_> =
|
let hnd: &mut RouteHandler<_> =
|
||||||
unsafe { (&mut *(handler.get())).as_mut() };
|
unsafe { (&mut *(handler.get())).as_mut() };
|
||||||
@@ -784,6 +773,59 @@ mod tests {
|
|||||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scope_root() {
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/app", |scope| {
|
||||||
|
scope
|
||||||
|
.resource("", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
.resource("/", |r| r.f(|_| HttpResponse::Created()))
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scope_root2() {
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/app/", |scope| {
|
||||||
|
scope.resource("", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scope_root3() {
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/app/", |scope| {
|
||||||
|
scope.resource("/", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_scope_route() {
|
fn test_scope_route() {
|
||||||
let mut app = App::new()
|
let mut app = App::new()
|
||||||
@@ -885,6 +927,71 @@ mod tests {
|
|||||||
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scope_with_state_root() {
|
||||||
|
struct State;
|
||||||
|
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/app", |scope| {
|
||||||
|
scope.with_state("/t1", State, |scope| {
|
||||||
|
scope
|
||||||
|
.resource("", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
.resource("/", |r| r.f(|_| HttpResponse::Created()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/t1").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/t1/").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scope_with_state_root2() {
|
||||||
|
struct State;
|
||||||
|
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/app", |scope| {
|
||||||
|
scope.with_state("/t1/", State, |scope| {
|
||||||
|
scope.resource("", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/t1").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/t1/").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scope_with_state_root3() {
|
||||||
|
struct State;
|
||||||
|
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/app", |scope| {
|
||||||
|
scope.with_state("/t1/", State, |scope| {
|
||||||
|
scope.resource("/", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/t1").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/t1/").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_scope_with_state_filter() {
|
fn test_scope_with_state_filter() {
|
||||||
struct State;
|
struct State;
|
||||||
@@ -927,6 +1034,27 @@ mod tests {
|
|||||||
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nested_scope_root() {
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/app", |scope| {
|
||||||
|
scope.nested("/t1", |scope| {
|
||||||
|
scope
|
||||||
|
.resource("", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
.resource("/", |r| r.f(|_| HttpResponse::Created()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/t1").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/t1/").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_nested_scope_filter() {
|
fn test_nested_scope_filter() {
|
||||||
let mut app = App::new()
|
let mut app = App::new()
|
||||||
|
@@ -505,6 +505,11 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
TransferEncoding::eof(buf)
|
TransferEncoding::eof(buf)
|
||||||
} else {
|
} else {
|
||||||
|
if !(encoding == ContentEncoding::Identity
|
||||||
|
|| encoding == ContentEncoding::Auto)
|
||||||
|
{
|
||||||
|
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||||
|
}
|
||||||
ContentEncoder::streaming_encoding(buf, version, resp)
|
ContentEncoder::streaming_encoding(buf, version, resp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -270,7 +270,12 @@ where
|
|||||||
debug!("Error sending data: {}", err);
|
debug!("Error sending data: {}", err);
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
_ => (),
|
Ok(Async::Ready(_)) => {
|
||||||
|
// non consumed payload in that case close connection
|
||||||
|
if self.payload.is_some() && self.tasks.is_empty() {
|
||||||
|
return Ok(Async::Ready(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
|
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
|
||||||
|
|
||||||
use bytes::{BytesMut, BufMut};
|
use bytes::{BufMut, BytesMut};
|
||||||
use futures::{Async, Poll};
|
use futures::{Async, Poll};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
@@ -136,7 +136,7 @@ impl HttpHandler for Box<HttpHandler> {
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait HttpHandlerTask {
|
pub trait HttpHandlerTask {
|
||||||
/// Poll task, this method is used before or after *io* object is available
|
/// Poll task, this method is used before or after *io* object is available
|
||||||
fn poll(&mut self) -> Poll<(), Error>{
|
fn poll(&mut self) -> Poll<(), Error> {
|
||||||
Ok(Async::Ready(()))
|
Ok(Async::Ready(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -217,7 +217,7 @@ impl TestServer {
|
|||||||
|
|
||||||
/// Create `POST` request
|
/// Create `POST` request
|
||||||
pub fn post(&self) -> ClientRequestBuilder {
|
pub fn post(&self) -> ClientRequestBuilder {
|
||||||
ClientRequest::get(self.url("/").as_str())
|
ClientRequest::post(self.url("/").as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create `HEAD` request
|
/// Create `HEAD` request
|
||||||
|
@@ -374,9 +374,7 @@ fn test_scope_and_path_extractor() {
|
|||||||
App::new().scope("/sc", |scope| {
|
App::new().scope("/sc", |scope| {
|
||||||
scope.resource("/{num}/index.html", |r| {
|
scope.resource("/{num}/index.html", |r| {
|
||||||
r.route()
|
r.route()
|
||||||
.with(|p: Path<(usize,)>| {
|
.with(|p: Path<(usize,)>| format!("Welcome {}!", p.0))
|
||||||
format!("Welcome {}!", p.0)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -410,10 +408,9 @@ fn test_nested_scope_and_path_extractor() {
|
|||||||
App::new().scope("/sc", |scope| {
|
App::new().scope("/sc", |scope| {
|
||||||
scope.nested("/{num}", |scope| {
|
scope.nested("/{num}", |scope| {
|
||||||
scope.resource("/{num}/index.html", |r| {
|
scope.resource("/{num}/index.html", |r| {
|
||||||
r.route()
|
r.route().with(|p: Path<(usize, usize)>| {
|
||||||
.with(|p: Path<(usize, usize)>| {
|
format!("Welcome {} {}!", p.0, p.1)
|
||||||
format!("Welcome {} {}!", p.0, p.1)
|
})
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@@ -54,6 +54,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
|
|||||||
Hello World Hello World Hello World Hello World Hello World";
|
Hello World Hello World Hello World Hello World Hello World";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
fn test_start() {
|
fn test_start() {
|
||||||
let _ = test::TestServer::unused_addr();
|
let _ = test::TestServer::unused_addr();
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
@@ -96,8 +97,7 @@ fn test_start() {
|
|||||||
|
|
||||||
// resume
|
// resume
|
||||||
let _ = srv_addr.send(server::ResumeServer).wait();
|
let _ = srv_addr.send(server::ResumeServer).wait();
|
||||||
thread::sleep(time::Duration::from_millis(200));
|
thread::sleep(time::Duration::from_millis(400));
|
||||||
|
|
||||||
{
|
{
|
||||||
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
|
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
|
||||||
.finish()
|
.finish()
|
||||||
|
Reference in New Issue
Block a user