1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-24 16:02:59 +01:00

Merge branch 'master' into master

This commit is contained in:
Nikolay Kim 2018-05-24 07:46:46 -07:00 committed by GitHub
commit 836706653b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 200 additions and 69 deletions

View File

@ -1,5 +1,16 @@
# Changes # Changes
## [0.6.10] - 2018-05-xx
### Added
* Allow to use path without traling slashes for scope registration #241
### Fixed
* `TestServer::post()` actually sends `GET` request #240
## 0.6.9 (2018-05-22) ## 0.6.9 (2018-05-22)
* Drop connection if request's payload is not fully consumed #236 * Drop connection if request's payload is not fully consumed #236

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "0.6.9" 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"

View File

@ -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)
} }

View File

@ -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();

View File

@ -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, ContentEncoding}; use http::{ContentEncoding, HttpRange, Method, StatusCode};
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@ -326,11 +326,11 @@ impl Responder for NamedFile {
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));

View File

@ -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)

View File

@ -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;
} }
} }
} }

View File

@ -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
}, },

View File

@ -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()

View File

@ -506,7 +506,7 @@ impl ContentEncoder {
TransferEncoding::eof(buf) TransferEncoding::eof(buf)
} else { } else {
if !(encoding == ContentEncoding::Identity if !(encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto) || encoding == ContentEncoding::Auto)
{ {
resp.headers_mut().remove(CONTENT_LENGTH); resp.headers_mut().remove(CONTENT_LENGTH);
} }

View File

@ -273,7 +273,7 @@ where
Ok(Async::Ready(_)) => { Ok(Async::Ready(_)) => {
// non consumed payload in that case close connection // non consumed payload in that case close connection
if self.payload.is_some() && self.tasks.is_empty() { if self.payload.is_some() && self.tasks.is_empty() {
return Ok(Async::Ready(false)) return Ok(Async::Ready(false));
} }
} }
} }

View File

@ -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;

View File

@ -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(()))
} }

View File

@ -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

View File

@ -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) })
})
}) })
}) })
}) })