1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-12 21:43:41 +02:00

Compare commits

...

30 Commits

Author SHA1 Message Date
9306631d6e Fix segfault in ServerSettings::get_response_builder() 2018-05-11 21:19:48 -07:00
487a713ca0 update doc string 2018-05-11 15:01:15 -07:00
095ad328ee prepare release 2018-05-10 15:45:06 -07:00
a38afa0cec --no-count for tarpaulin 2018-05-10 13:05:56 -07:00
9619698543 doc string 2018-05-10 13:04:56 -07:00
4b1a471b35 add more examples for extractor config 2018-05-10 13:03:43 -07:00
b6039b0bff add doc string 2018-05-10 11:04:03 -07:00
d8fa43034f export ExtractorConfig type 2018-05-10 11:00:22 -07:00
92f993e054 Fix client request timeout handling 2018-05-10 09:37:38 -07:00
c172deb0f3 Merge pull request #219 from benjamingroeber/improve-readme
correct order of format arguments in readme example
2018-05-10 09:15:50 -07:00
dee6aed010 Merge branch 'master' into improve-readme 2018-05-10 09:15:44 -07:00
76f021a6e3 add tests for ErrorXXX helpers 2018-05-10 09:13:26 -07:00
2f244ea028 fix order of name and id in readme example 2018-05-10 18:12:59 +02:00
5f5ddc8f01 Merge pull request #218 from Dowwie/master
added error response functions for 501,502,503,504
2018-05-10 08:59:23 -07:00
8b473745cb added error response functions for 501,502,503,504 2018-05-10 11:26:38 -04:00
18575ee1ee Add Router::with_async() method for async handler registration 2018-05-09 16:27:31 -07:00
e58b38fd13 deprecate WsWrite from top level mod 2018-05-09 06:12:16 -07:00
b043c34632 bump version 2018-05-09 06:05:44 -07:00
b748bf3b0d make api public 2018-05-09 06:05:16 -07:00
be12d5e6fc make WsWriter trait optional 2018-05-09 05:48:06 -07:00
7c4941f868 update migration doc 2018-05-08 18:48:09 -07:00
d1f5c457c4 Merge branch 'master' of github.com:actix/actix-web 2018-05-08 18:35:52 -07:00
c26c5fd9a4 prep release 2018-05-08 18:34:36 -07:00
4a73d1c8c1 Merge pull request #216 from lcowell/lcowell-scoupe
replace typo `scoupe` with `scope`
2018-05-08 17:47:20 -07:00
7c395fcc83 replace typo scoupe with scope 2018-05-08 17:40:18 -07:00
54c33a7aff Allow to exclude certain endpoints from logging #211 2018-05-08 16:30:34 -07:00
47d80382b2 Fix http/2 payload streaming #215 2018-05-08 15:44:50 -07:00
ba816a8562 Merge pull request #214 from niklasf/de-path-404
let Path::from_request() fail with ErrorNotFound
2018-05-08 14:41:05 -07:00
6f75b0e95e let Path::from_request() fail with ErrorNotFound 2018-05-08 22:59:46 +02:00
b3cc43bb9b Fix connector's default keep-alive and lifetime settings #212 2018-05-08 13:41:04 -07:00
26 changed files with 680 additions and 82 deletions

View File

@ -38,7 +38,7 @@ script:
- | - |
if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
USE_SKEPTIC=1 cargo tarpaulin --out Xml USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count
bash <(curl -s https://codecov.io/bash) bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage" echo "Uploaded code coverage"
fi fi

View File

@ -1,5 +1,35 @@
# Changes # Changes
## 0.6.4 (2018-05-11)
* Fix segfault in ServerSettings::get_response_builder()
## 0.6.3 (2018-05-10)
* Add `Router::with_async()` method for async handler registration.
* Added error response functions for 501,502,503,504
* Fix client request timeout handling
## 0.6.2 (2018-05-09)
* WsWriter trait is optional.
## 0.6.1 (2018-05-08)
* Fix http/2 payload streaming #215
* Fix connector's default `keep-alive` and `lifetime` settings #212
* Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214
* Allow to exclude certain endpoints from logging #211
## 0.6.0 (2018-05-08) ## 0.6.0 (2018-05-08)
* Add route scopes #202 * Add route scopes #202

View File

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

@ -1,5 +1,7 @@
## Migration from 0.5 to 0.6 ## Migration from 0.5 to 0.6
* `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
* `ws::Message::Close` now includes optional close reason. * `ws::Message::Close` now includes optional close reason.
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
@ -11,6 +13,17 @@
* `HttpRequest::extensions()` returns read only reference to the request's Extension * `HttpRequest::extensions()` returns read only reference to the request's Extension
`HttpRequest::extensions_mut()` returns mutable reference. `HttpRequest::extensions_mut()` returns mutable reference.
* Instead of
`use actix_web::middleware::{
CookieSessionBackend, CookieSessionError, RequestSession,
Session, SessionBackend, SessionImpl, SessionStorage};`
use `actix_web::middleware::session`
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError,
RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
* `FromRequest::from_request()` accepts mutable reference to a request * `FromRequest::from_request()` accepts mutable reference to a request
* `FromRequest::Result` has to implement `Into<Reply<Self>>` * `FromRequest::Result` has to implement `Into<Reply<Self>>`
@ -33,6 +46,9 @@
let q = Query::<HashMap<String, String>>::extract(req); let q = Query::<HashMap<String, String>>::extract(req);
``` ```
* Websocket operations are implemented as `WsWriter` trait.
you need to use `use actix_web::ws::WsWriter`
## Migration from 0.4 to 0.5 ## Migration from 0.4 to 0.5

View File

@ -36,7 +36,7 @@ extern crate actix_web;
use actix_web::{http, server, App, Path}; use actix_web::{http, server, App, Path};
fn index(info: Path<(u32, String)>) -> String { fn index(info: Path<(u32, String)>) -> String {
format!("Hello {}! id:{}", info.0, info.1) format!("Hello {}! id:{}", info.1, info.0)
} }
fn main() { fn main() {

View File

@ -1,8 +1,15 @@
extern crate version_check; extern crate version_check;
fn main() { fn main() {
match version_check::is_min_version("1.26.0") {
Some((true, _)) => println!("cargo:rustc-cfg=actix_impl_trait"),
_ => (),
};
match version_check::is_nightly() { match version_check::is_nightly() {
Some(true) => println!("cargo:rustc-cfg=actix_nightly"), Some(true) => {
println!("cargo:rustc-cfg=actix_nightly");
println!("cargo:rustc-cfg=actix_impl_trait");
}
Some(false) => (), Some(false) => (),
None => (), None => (),
}; };

View File

@ -223,8 +223,8 @@ impl Default for ClientConnector {
pool: Rc::new(Pool::new(Rc::clone(&_modified))), pool: Rc::new(Pool::new(Rc::clone(&_modified))),
pool_modified: _modified, pool_modified: _modified,
connector: builder.build().unwrap(), connector: builder.build().unwrap(),
conn_lifetime: Duration::from_secs(15), conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15),
limit: 100, limit: 100,
limit_per_host: 0, limit_per_host: 0,
acquired: 0, acquired: 0,
@ -243,8 +243,8 @@ impl Default for ClientConnector {
subscriber: None, subscriber: None,
pool: Rc::new(Pool::new(Rc::clone(&_modified))), pool: Rc::new(Pool::new(Rc::clone(&_modified))),
pool_modified: _modified, pool_modified: _modified,
conn_lifetime: Duration::from_secs(15), conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15),
limit: 100, limit: 100,
limit_per_host: 0, limit_per_host: 0,
acquired: 0, acquired: 0,

View File

@ -49,6 +49,7 @@ use httpresponse::HttpResponse;
impl ResponseError for SendRequestError { impl ResponseError for SendRequestError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { match *self {
SendRequestError::Timeout => HttpResponse::GatewayTimeout(),
SendRequestError::Connector(_) => HttpResponse::BadGateway(), SendRequestError::Connector(_) => HttpResponse::BadGateway(),
_ => HttpResponse::InternalServerError(), _ => HttpResponse::InternalServerError(),
}.into() }.into()

View File

@ -194,6 +194,7 @@ impl Future for SendRequest {
self.state = State::Send(pl); self.state = State::Send(pl);
} }
State::Send(mut pl) => { State::Send(mut pl) => {
pl.poll_timeout()?;
pl.poll_write().map_err(|e| { pl.poll_write().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str()) io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str())
})?; })?;
@ -315,7 +316,7 @@ impl Pipeline {
{ {
Async::NotReady => need_run = true, Async::NotReady => need_run = true,
Async::Ready(_) => { Async::Ready(_) => {
let _ = self.poll_timeout().map_err(|e| { self.poll_timeout().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("{}", e)) io::Error::new(io::ErrorKind::Other, format!("{}", e))
})?; })?;
} }
@ -371,16 +372,15 @@ impl Pipeline {
} }
} }
fn poll_timeout(&mut self) -> Poll<(), SendRequestError> { fn poll_timeout(&mut self) -> Result<(), SendRequestError> {
if self.timeout.is_some() { if self.timeout.is_some() {
match self.timeout.as_mut().unwrap().poll() { match self.timeout.as_mut().unwrap().poll() {
Ok(Async::Ready(())) => Err(SendRequestError::Timeout), Ok(Async::Ready(())) => return Err(SendRequestError::Timeout),
Ok(Async::NotReady) => Ok(Async::NotReady), Ok(Async::NotReady) => (),
Err(_) => unreachable!(), Err(_) => unreachable!(),
} }
} else {
Ok(Async::NotReady)
} }
Ok(())
} }
#[inline] #[inline]

View File

@ -757,6 +757,46 @@ where
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into() InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into()
} }
/// Helper function that creates wrapper of any error and
/// generate *NOT IMPLEMENTED* response.
#[allow(non_snake_case)]
pub fn ErrorNotImplemented<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::NOT_IMPLEMENTED).into()
}
/// Helper function that creates wrapper of any error and
/// generate *BAD GATEWAY* response.
#[allow(non_snake_case)]
pub fn ErrorBadGateway<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::BAD_GATEWAY).into()
}
/// Helper function that creates wrapper of any error and
/// generate *SERVICE UNAVAILABLE* response.
#[allow(non_snake_case)]
pub fn ErrorServiceUnavailable<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::SERVICE_UNAVAILABLE).into()
}
/// Helper function that creates wrapper of any error and
/// generate *GATEWAY TIMEOUT* response.
#[allow(non_snake_case)]
pub fn ErrorGatewayTimeout<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -912,4 +952,52 @@ mod tests {
let resp: HttpResponse = err.error_response(); let resp: HttpResponse = err.error_response();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test]
fn test_error_helpers() {
let r: HttpResponse = ErrorBadRequest("err").into();
assert_eq!(r.status(), StatusCode::BAD_REQUEST);
let r: HttpResponse = ErrorUnauthorized("err").into();
assert_eq!(r.status(), StatusCode::UNAUTHORIZED);
let r: HttpResponse = ErrorForbidden("err").into();
assert_eq!(r.status(), StatusCode::FORBIDDEN);
let r: HttpResponse = ErrorNotFound("err").into();
assert_eq!(r.status(), StatusCode::NOT_FOUND);
let r: HttpResponse = ErrorMethodNotAllowed("err").into();
assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED);
let r: HttpResponse = ErrorRequestTimeout("err").into();
assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT);
let r: HttpResponse = ErrorConflict("err").into();
assert_eq!(r.status(), StatusCode::CONFLICT);
let r: HttpResponse = ErrorGone("err").into();
assert_eq!(r.status(), StatusCode::GONE);
let r: HttpResponse = ErrorPreconditionFailed("err").into();
assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED);
let r: HttpResponse = ErrorExpectationFailed("err").into();
assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED);
let r: HttpResponse = ErrorInternalServerError("err").into();
assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR);
let r: HttpResponse = ErrorNotImplemented("err").into();
assert_eq!(r.status(), StatusCode::NOT_IMPLEMENTED);
let r: HttpResponse = ErrorBadGateway("err").into();
assert_eq!(r.status(), StatusCode::BAD_GATEWAY);
let r: HttpResponse = ErrorServiceUnavailable("err").into();
assert_eq!(r.status(), StatusCode::SERVICE_UNAVAILABLE);
let r: HttpResponse = ErrorGatewayTimeout("err").into();
assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT);
}
} }

View File

@ -11,7 +11,7 @@ use serde::de::{self, DeserializeOwned};
use serde_urlencoded; use serde_urlencoded;
use de::PathDeserializer; use de::PathDeserializer;
use error::{Error, ErrorBadRequest}; use error::{Error, ErrorNotFound, ErrorBadRequest};
use handler::{AsyncResult, FromRequest}; use handler::{AsyncResult, FromRequest};
use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
use httprequest::HttpRequest; use httprequest::HttpRequest;
@ -108,7 +108,7 @@ where
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result { fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
let req = req.clone(); let req = req.clone();
de::Deserialize::deserialize(PathDeserializer::new(&req)) de::Deserialize::deserialize(PathDeserializer::new(&req))
.map_err(|e| e.into()) .map_err(ErrorNotFound)
.map(|inner| Path { inner }) .map(|inner| Path { inner })
} }
} }

View File

@ -492,14 +492,15 @@ where
/// } /// }
/// ///
/// /// extract path info using serde /// /// extract path info using serde
/// fn index(state: State<MyApp>, info: Path<Info>) -> String { /// fn index(data: (State<MyApp>, Path<Info>)) -> String {
/// format!("{} {}!", state.msg, info.username) /// let (state, path) = data;
/// format!("{} {}!", state.msg, path.username)
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = App::with_state(MyApp{msg: "Welcome"}).resource( /// let app = App::with_state(MyApp{msg: "Welcome"}).resource(
/// "/{username}/index.html", // <- define path parameters /// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor /// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// } /// }
/// ``` /// ```
pub struct State<S>(HttpRequest<S>); pub struct State<S>(HttpRequest<S>);

View File

@ -175,6 +175,9 @@ pub use httprequest::HttpRequest;
pub use httpresponse::HttpResponse; pub use httpresponse::HttpResponse;
pub use json::Json; pub use json::Json;
pub use scope::Scope; pub use scope::Scope;
#[doc(hidden)]
#[deprecated(since = "0.6.2", note = "please use `use actix_web::ws::WsWriter`")]
pub use ws::WsWriter; pub use ws::WsWriter;
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
@ -210,6 +213,7 @@ pub mod dev {
pub use resource::ResourceHandler; pub use resource::ResourceHandler;
pub use route::Route; pub use route::Route;
pub use router::{Resource, ResourceType, Router}; pub use router::{Resource, ResourceType, Router};
pub use with::ExtractorConfig;
} }
pub mod http { pub mod http {

View File

@ -1,7 +1,7 @@
//! Request logging middleware //! Request logging middleware
use std::collections::HashSet;
use std::env; use std::env;
use std::fmt; use std::fmt::{self, Display, Formatter};
use std::fmt::{Display, Formatter};
use libc; use libc;
use regex::Regex; use regex::Regex;
@ -74,6 +74,7 @@ use middleware::{Finished, Middleware, Started};
/// ///
pub struct Logger { pub struct Logger {
format: Format, format: Format,
exclude: HashSet<String>,
} }
impl Logger { impl Logger {
@ -81,8 +82,15 @@ impl Logger {
pub fn new(format: &str) -> Logger { pub fn new(format: &str) -> Logger {
Logger { Logger {
format: Format::new(format), format: Format::new(format),
exclude: HashSet::new(),
} }
} }
/// Ignore and do not log access info for specified path.
pub fn exclude<T: Into<String>>(mut self, path: T) -> Self {
self.exclude.insert(path.into());
self
}
} }
impl Default for Logger { impl Default for Logger {
@ -94,6 +102,7 @@ impl Default for Logger {
fn default() -> Logger { fn default() -> Logger {
Logger { Logger {
format: Format::default(), format: Format::default(),
exclude: HashSet::new(),
} }
} }
} }
@ -102,21 +111,23 @@ struct StartTime(time::Tm);
impl Logger { impl Logger {
fn log<S>(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) { fn log<S>(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) {
let entry_time = req.extensions().get::<StartTime>().unwrap().0; if let Some(entry_time) = req.extensions().get::<StartTime>() {
let render = |fmt: &mut Formatter| {
let render = |fmt: &mut Formatter| { for unit in &self.format.0 {
for unit in &self.format.0 { unit.render(fmt, req, resp, entry_time.0)?;
unit.render(fmt, req, resp, entry_time)?; }
} Ok(())
Ok(()) };
}; info!("{}", FormatDisplay(&render));
info!("{}", FormatDisplay(&render)); }
} }
} }
impl<S> Middleware<S> for Logger { impl<S> Middleware<S> for Logger {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> { fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
req.extensions_mut().insert(StartTime(time::now())); if !self.exclude.contains(req.path()) {
req.extensions_mut().insert(StartTime(time::now()));
}
Ok(Started::Done) Ok(Started::Done)
} }

View File

@ -492,8 +492,8 @@ impl<S: 'static, H> ProcessResponse<S, H> {
if let Some(err) = self.resp.error() { if let Some(err) = self.resp.error() {
if self.resp.status().is_server_error() { if self.resp.status().is_server_error() {
error!( error!(
"Error occured during request handling: {}", "Error occured during request handling, status: {} {}",
err self.resp.status(), err
); );
} else { } else {
warn!( warn!(

View File

@ -1,9 +1,11 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc; use std::rc::Rc;
use futures::Future;
use http::{Method, StatusCode}; use http::{Method, StatusCode};
use smallvec::SmallVec; use smallvec::SmallVec;
use error::Error;
use handler::{AsyncResult, FromRequest, Handler, Responder}; use handler::{AsyncResult, FromRequest, Handler, Responder};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@ -183,6 +185,25 @@ impl<S: 'static> ResourceHandler<S> {
self.routes.last_mut().unwrap().with(handler); self.routes.last_mut().unwrap().with(handler);
} }
/// Register a new route and add async handler.
///
/// This is shortcut for:
///
/// ```rust,ignore
/// Application::resource("/", |r| r.route().with_async(index)
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
where
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
{
self.routes.push(Route::default());
self.routes.last_mut().unwrap().with_async(handler);
}
/// Register a resource middleware /// Register a resource middleware
/// ///
/// This is similar to `App's` middlewares, but /// This is similar to `App's` middlewares, but

View File

@ -13,7 +13,7 @@ use httpresponse::HttpResponse;
use middleware::{Finished as MiddlewareFinished, Middleware, use middleware::{Finished as MiddlewareFinished, Middleware,
Response as MiddlewareResponse, Started as MiddlewareStarted}; Response as MiddlewareResponse, Started as MiddlewareStarted};
use pred::Predicate; use pred::Predicate;
use with::{ExtractorConfig, With, With2, With3}; use with::{ExtractorConfig, With, With2, With3, WithAsync};
/// Resource route definition /// Resource route definition
/// ///
@ -129,6 +129,34 @@ impl<S: 'static> Route<S> {
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor /// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// } /// }
/// ``` /// ```
///
/// It is possible to use tuples for specifing multiple extractors for one
/// handler function.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// # use std::collections::HashMap;
/// use actix_web::{http, App, Query, Path, Result, Json};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: (Path<Info>, Query<HashMap<String, String>>, Json<Info>)) -> Result<String> {
/// Ok(format!("Welcome {}!", info.0.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// }
/// ```
pub fn with<T, F, R>(&mut self, handler: F) -> ExtractorConfig<S, T> pub fn with<T, F, R>(&mut self, handler: F) -> ExtractorConfig<S, T>
where where
F: Fn(T) -> R + 'static, F: Fn(T) -> R + 'static,
@ -140,6 +168,49 @@ impl<S: 'static> Route<S> {
cfg cfg
} }
/// Set async handler function, use request extractor for parameters.
/// Also this method needs to be used if your handler function returns
/// `impl Future<>`
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Path, Error, http};
/// use futures::Future;
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: Path<Info>) -> Box<Future<Item=&'static str, Error=Error>> {
/// unimplemented!()
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET)
/// .with_async(index)); // <- use `with` extractor
/// }
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F) -> ExtractorConfig<S, T>
where
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
{
let cfg = ExtractorConfig::default();
self.h(WithAsync::new(handler, Clone::clone(&cfg)));
cfg
}
#[doc(hidden)]
/// Set handler function, use request extractor for both parameters. /// Set handler function, use request extractor for both parameters.
/// ///
/// ```rust /// ```rust
@ -189,6 +260,7 @@ impl<S: 'static> Route<S> {
(cfg1, cfg2) (cfg1, cfg2)
} }
#[doc(hidden)]
/// Set handler function, use request extractor for all parameters. /// Set handler function, use request extractor for all parameters.
pub fn with3<T1, T2, T3, F, R>( pub fn with3<T1, T2, T3, F, R>(
&mut self, handler: F, &mut self, handler: F,

View File

@ -76,7 +76,7 @@ impl<S: 'static> Scope<S> {
mem::replace(&mut self.filters, Vec::new()) mem::replace(&mut self.filters, Vec::new())
} }
/// Add match predicate to scoupe. /// Add match predicate to scope.
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
@ -787,10 +787,10 @@ mod tests {
let mut app = App::new() let mut app = App::new()
.scope("app", |scope| { .scope("app", |scope| {
scope scope
.route("/path1", Method::GET, |r: HttpRequest<_>| { .route("/path1", Method::GET, |_: HttpRequest<_>| {
HttpResponse::Ok() HttpResponse::Ok()
}) })
.route("/path1", Method::DELETE, |r: HttpRequest<_>| { .route("/path1", Method::DELETE, |_: HttpRequest<_>| {
HttpResponse::Ok() HttpResponse::Ok()
}) })
}) })

View File

@ -343,24 +343,27 @@ impl<H: 'static> Entry<H> {
} }
fn poll_payload(&mut self) { fn poll_payload(&mut self) {
if !self.flags.contains(EntryFlags::REOF) { while !self.flags.contains(EntryFlags::REOF)
if self.payload.need_read() == PayloadStatus::Read { && self.payload.need_read() == PayloadStatus::Read
if let Err(err) = self.recv.release_capacity().release_capacity(32_768) { {
self.payload.set_error(PayloadError::Http2(err))
}
} else if let Err(err) = self.recv.release_capacity().release_capacity(0) {
self.payload.set_error(PayloadError::Http2(err))
}
match self.recv.poll() { match self.recv.poll() {
Ok(Async::Ready(Some(chunk))) => { Ok(Async::Ready(Some(chunk))) => {
let l = chunk.len();
self.payload.feed_data(chunk); self.payload.feed_data(chunk);
if let Err(err) = self.recv.release_capacity().release_capacity(l) {
self.payload.set_error(PayloadError::Http2(err));
break;
}
} }
Ok(Async::Ready(None)) => { Ok(Async::Ready(None)) => {
self.flags.insert(EntryFlags::REOF); self.flags.insert(EntryFlags::REOF);
self.payload.feed_eof();
}
Ok(Async::NotReady) => break,
Err(err) => {
self.payload.set_error(PayloadError::Http2(err));
break;
} }
Ok(Async::NotReady) => (),
Err(err) => self.payload.set_error(PayloadError::Http2(err)),
} }
} }
} }

View File

@ -16,7 +16,6 @@ use body::Body;
use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool};
/// Various server settings /// Various server settings
#[derive(Clone)]
pub struct ServerSettings { pub struct ServerSettings {
addr: Option<net::SocketAddr>, addr: Option<net::SocketAddr>,
secure: bool, secure: bool,
@ -28,6 +27,18 @@ pub struct ServerSettings {
unsafe impl Sync for ServerSettings {} unsafe impl Sync for ServerSettings {}
unsafe impl Send for ServerSettings {} unsafe impl Send for ServerSettings {}
impl Clone for ServerSettings {
fn clone(&self) -> Self {
ServerSettings {
addr: self.addr,
secure: self.secure,
host: self.host.clone(),
cpu_pool: self.cpu_pool.clone(),
responses: HttpResponsePool::pool(),
}
}
}
struct InnerCpuPool { struct InnerCpuPool {
cpu_pool: UnsafeCell<Option<CpuPool>>, cpu_pool: UnsafeCell<Option<CpuPool>>,
} }

View File

@ -9,6 +9,62 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
/// Extractor configuration
///
/// `Route::with()` and `Route::with_async()` returns instance
/// of the `ExtractorConfig` type. It could be used for extractor configuration.
///
/// In this example `Form<FormData>` configured.
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Form, Result, http};
///
/// #[derive(Deserialize)]
/// struct FormData {
/// username: String,
/// }
///
/// fn index(form: Form<FormData>) -> Result<String> {
/// Ok(format!("Welcome {}!", form.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html", |r| {
/// r.method(http::Method::GET)
/// .with(index)
/// .limit(4096);} // <- change form extractor configuration
/// );
/// }
/// ```
///
/// Same could be donce with multiple extractors
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Form, Path, Result, http};
///
/// #[derive(Deserialize)]
/// struct FormData {
/// username: String,
/// }
///
/// fn index(data: (Path<(String,)>, Form<FormData>)) -> Result<String> {
/// Ok(format!("Welcome {}!", data.1.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html", |r| {
/// r.method(http::Method::GET)
/// .with(index)
/// .1.limit(4096);} // <- change form extractor configuration
/// );
/// }
/// ```
pub struct ExtractorConfig<S: 'static, T: FromRequest<S>> { pub struct ExtractorConfig<S: 'static, T: FromRequest<S>> {
cfg: Rc<UnsafeCell<T::Config>>, cfg: Rc<UnsafeCell<T::Config>>,
} }
@ -167,6 +223,145 @@ where
} }
} }
pub struct WithAsync<T, S, F, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<E>,
T: FromRequest<S>,
S: 'static,
{
hnd: Rc<UnsafeCell<F>>,
cfg: ExtractorConfig<S, T>,
_s: PhantomData<S>,
}
impl<T, S, F, R, I, E> WithAsync<T, S, F, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<Error>,
T: FromRequest<S>,
S: 'static,
{
pub fn new(f: F, cfg: ExtractorConfig<S, T>) -> Self {
WithAsync {
cfg,
hnd: Rc::new(UnsafeCell::new(f)),
_s: PhantomData,
}
}
}
impl<T, S, F, R, I, E> Handler<S> for WithAsync<T, S, F, R, I, E>
where
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithAsyncHandlerFut {
req,
started: false,
hnd: Rc::clone(&self.hnd),
cfg: self.cfg.clone(),
fut1: None,
fut2: None,
fut3: None,
};
match fut.poll() {
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => AsyncResult::err(e),
}
}
}
struct WithAsyncHandlerFut<T, S, F, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
started: bool,
hnd: Rc<UnsafeCell<F>>,
cfg: ExtractorConfig<S, T>,
req: HttpRequest<S>,
fut1: Option<Box<Future<Item = T, Error = Error>>>,
fut2: Option<R>,
fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
}
impl<T, S, F, R, I, E> Future for WithAsyncHandlerFut<T, S, F, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
type Item = HttpResponse;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut3 {
return fut.poll();
}
if self.fut2.is_some() {
return match self.fut2.as_mut().unwrap().poll() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(r)) => match r.respond_to(&self.req) {
Ok(r) => match r.into().into() {
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut3 = Some(fut);
self.poll()
}
},
Err(e) => Err(e.into()),
},
Err(e) => Err(e.into()),
};
}
let item = if !self.started {
self.started = true;
let reply = T::from_request(&self.req, self.cfg.as_ref()).into();
match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut1 = Some(fut);
return self.poll();
}
}
} else {
match self.fut1.as_mut().unwrap().poll()? {
Async::Ready(item) => item,
Async::NotReady => return Ok(Async::NotReady),
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
self.fut2 = Some((*hnd)(item));
self.poll()
}
}
pub struct With2<T1, T2, S, F, R> pub struct With2<T1, T2, S, F, R>
where where
F: Fn(T1, T2) -> R, F: Fn(T1, T2) -> R,

View File

@ -518,24 +518,22 @@ impl ClientWriter {
fn as_mut(&mut self) -> &mut Inner { fn as_mut(&mut self) -> &mut Inner {
unsafe { &mut *self.inner.get() } unsafe { &mut *self.inner.get() }
} }
}
impl WsWriter for ClientWriter {
/// Send text frame /// Send text frame
#[inline] #[inline]
fn text<T: Into<Binary>>(&mut self, text: T) { pub fn text<T: Into<Binary>>(&mut self, text: T) {
self.write(Frame::message(text.into(), OpCode::Text, true, true)); self.write(Frame::message(text.into(), OpCode::Text, true, true));
} }
/// Send binary frame /// Send binary frame
#[inline] #[inline]
fn binary<B: Into<Binary>>(&mut self, data: B) { pub fn binary<B: Into<Binary>>(&mut self, data: B) {
self.write(Frame::message(data, OpCode::Binary, true, true)); self.write(Frame::message(data, OpCode::Binary, true, true));
} }
/// Send ping frame /// Send ping frame
#[inline] #[inline]
fn ping(&mut self, message: &str) { pub fn ping(&mut self, message: &str) {
self.write(Frame::message( self.write(Frame::message(
Vec::from(message), Vec::from(message),
OpCode::Ping, OpCode::Ping,
@ -546,7 +544,7 @@ impl WsWriter for ClientWriter {
/// Send pong frame /// Send pong frame
#[inline] #[inline]
fn pong(&mut self, message: &str) { pub fn pong(&mut self, message: &str) {
self.write(Frame::message( self.write(Frame::message(
Vec::from(message), Vec::from(message),
OpCode::Pong, OpCode::Pong,
@ -557,7 +555,39 @@ impl WsWriter for ClientWriter {
/// Send close frame /// Send close frame
#[inline] #[inline]
fn close(&mut self, reason: Option<CloseReason>) { pub fn close(&mut self, reason: Option<CloseReason>) {
self.write(Frame::close(reason, true)); self.write(Frame::close(reason, true));
} }
} }
impl WsWriter for ClientWriter {
/// Send text frame
#[inline]
fn send_text<T: Into<Binary>>(&mut self, text: T) {
self.text(text)
}
/// Send binary frame
#[inline]
fn send_binary<B: Into<Binary>>(&mut self, data: B) {
self.binary(data)
}
/// Send ping frame
#[inline]
fn send_ping(&mut self, message: &str) {
self.ping(message)
}
/// Send pong frame
#[inline]
fn send_pong(&mut self, message: &str) {
self.pong(message)
}
/// Send close frame
#[inline]
fn send_close(&mut self, reason: Option<CloseReason>) {
self.close(reason);
}
}

View File

@ -149,6 +149,46 @@ where
Drain::new(rx) Drain::new(rx)
} }
/// Send text frame
#[inline]
pub fn text<T: Into<Binary>>(&mut self, text: T) {
self.write(Frame::message(text.into(), OpCode::Text, true, false));
}
/// Send binary frame
#[inline]
pub fn binary<B: Into<Binary>>(&mut self, data: B) {
self.write(Frame::message(data, OpCode::Binary, true, false));
}
/// Send ping frame
#[inline]
pub fn ping(&mut self, message: &str) {
self.write(Frame::message(
Vec::from(message),
OpCode::Ping,
true,
false,
));
}
/// Send pong frame
#[inline]
pub fn pong(&mut self, message: &str) {
self.write(Frame::message(
Vec::from(message),
OpCode::Pong,
true,
false,
));
}
/// Send close frame
#[inline]
pub fn close(&mut self, reason: Option<CloseReason>) {
self.write(Frame::close(reason, false));
}
/// Check if connection still open /// Check if connection still open
#[inline] #[inline]
pub fn connected(&self) -> bool { pub fn connected(&self) -> bool {
@ -181,42 +221,32 @@ where
{ {
/// Send text frame /// Send text frame
#[inline] #[inline]
fn text<T: Into<Binary>>(&mut self, text: T) { fn send_text<T: Into<Binary>>(&mut self, text: T) {
self.write(Frame::message(text.into(), OpCode::Text, true, false)); self.text(text)
} }
/// Send binary frame /// Send binary frame
#[inline] #[inline]
fn binary<B: Into<Binary>>(&mut self, data: B) { fn send_binary<B: Into<Binary>>(&mut self, data: B) {
self.write(Frame::message(data, OpCode::Binary, true, false)); self.binary(data)
} }
/// Send ping frame /// Send ping frame
#[inline] #[inline]
fn ping(&mut self, message: &str) { fn send_ping(&mut self, message: &str) {
self.write(Frame::message( self.ping(message)
Vec::from(message),
OpCode::Ping,
true,
false,
));
} }
/// Send pong frame /// Send pong frame
#[inline] #[inline]
fn pong(&mut self, message: &str) { fn send_pong(&mut self, message: &str) {
self.write(Frame::message( self.pong(message)
Vec::from(message),
OpCode::Pong,
true,
false,
));
} }
/// Send close frame /// Send close frame
#[inline] #[inline]
fn close(&mut self, reason: Option<CloseReason>) { fn send_close(&mut self, reason: Option<CloseReason>) {
self.write(Frame::close(reason, false)); self.close(reason)
} }
} }

View File

@ -343,15 +343,15 @@ where
/// Common writing methods for a websocket. /// Common writing methods for a websocket.
pub trait WsWriter { pub trait WsWriter {
/// Send a text /// Send a text
fn text<T: Into<Binary>>(&mut self, text: T); fn send_text<T: Into<Binary>>(&mut self, text: T);
/// Send a binary /// Send a binary
fn binary<B: Into<Binary>>(&mut self, data: B); fn send_binary<B: Into<Binary>>(&mut self, data: B);
/// Send a ping message /// Send a ping message
fn ping(&mut self, message: &str); fn send_ping(&mut self, message: &str);
/// Send a pong message /// Send a pong message
fn pong(&mut self, message: &str); fn send_pong(&mut self, message: &str);
/// Close the connection /// Close the connection
fn close(&mut self, reason: Option<CloseReason>); fn send_close(&mut self, reason: Option<CloseReason>);
} }
#[cfg(test)] #[cfg(test)]

View File

@ -9,6 +9,7 @@ extern crate tokio_core;
extern crate serde_derive; extern crate serde_derive;
extern crate serde_json; extern crate serde_json;
use std::io;
use std::time::Duration; use std::time::Duration;
use actix::*; use actix::*;
@ -377,6 +378,83 @@ fn test_path_and_query_extractor2_async4() {
assert_eq!(response.status(), StatusCode::BAD_REQUEST); assert_eq!(response.status(), StatusCode::BAD_REQUEST);
} }
#[cfg(actix_impl_trait)]
fn test_impl_trait(
data: (Json<Value>, Path<PParam>, Query<PParam>),
) -> impl Future<Item = String, Error = io::Error> {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| {
Ok(format!(
"Welcome {} - {}!",
data.1.username,
(data.0).0
))
})
}
#[cfg(actix_impl_trait)]
fn test_impl_trait_err(
_data: (Json<Value>, Path<PParam>, Query<PParam>),
) -> impl Future<Item = String, Error = io::Error> {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other")))
}
#[cfg(actix_impl_trait)]
#[test]
fn test_path_and_query_extractor2_async4_impl_trait() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with_async(test_impl_trait)
});
});
// client request
let request = srv.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
// client request
let request = srv.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[cfg(actix_impl_trait)]
#[test]
fn test_path_and_query_extractor2_async4_impl_trait_err() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with_async(test_impl_trait_err)
});
});
// client request
let request = srv.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test] #[test]
fn test_non_ascii_route() { fn test_non_ascii_route() {
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {

View File

@ -809,7 +809,7 @@ fn test_h2() {
}) })
}); });
let _res = core.run(tcp); let _res = core.run(tcp);
// assert_eq!(res.unwrap(), Bytes::from_static(STR.as_ref())); // assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref()));
} }
#[test] #[test]