1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-08-31 00:50:20 +02:00

Compare commits

...

10 Commits

Author SHA1 Message Date
Douman
799c6eb719 0.7.17 Bump 2018-12-25 16:28:36 +03:00
Douman
037a1c6a24 Bump min version of rustc
Due to actix & trust-dns requirement
2018-12-24 21:17:09 +03:00
BlueC0re
bfdf762062 Only return a single Origin value (#644)
Only return a single origin if matched.
2018-12-24 21:16:07 +03:00
Nikolay Kim
477bf0d8ae Send HTTP/1.1 100 Continue if request contains expect: continue header #634 2018-12-23 10:19:12 -08:00
Phil Booth
e9fe3879df Support custom content types in JsonConfig 2018-12-23 08:27:47 +03:00
Douman
1a940d4c18 H1 decoded should ignore header cases 2018-12-16 18:34:32 +03:00
Douman
e8bdcb1c08 Update min version of http
Closes #630
2018-12-15 09:26:56 +03:00
Douman
46db09428c Prepare release 0.7.16 2018-12-11 21:04:05 +03:00
ethanpailes
90eef31cc0 impl ResponseError for SendError when possible (#619) 2018-12-11 19:37:52 +03:00
Akos Vandra
86af02156b add impl FromRequest for Either<A,B> (#618) 2018-12-10 19:02:05 +03:00
21 changed files with 493 additions and 76 deletions

View File

@@ -1,8 +1,31 @@
# Changes
## [0.7.17] - 2018-12-25
### Added
* Support for custom content types in `JsonConfig`. #637
* Send `HTTP/1.1 100 Continue` if request contains `expect: continue` header #634
### Fixed
* HTTP1 decoder should perform case-insentive comparison for client requests (e.g. `Keep-Alive`). #631
* Access-Control-Allow-Origin header should only a return a single, matching origin. #603
## [0.7.16] - 2018-12-11
### Added
* Implement `FromRequest` extractor for `Either<A,B>`
* Implement `ResponseError` for `SendError`
## [0.7.15] - 2018-12-05
## Changed
### Changed
* `ClientConnector::resolver` now accepts `Into<Recipient>` instead of `Addr`. It enables user to implement own resolver.

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "0.7.15"
version = "0.7.17"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
@@ -61,15 +61,15 @@ flate2-rust = ["flate2/rust_backend"]
cell = ["actix-net/cell"]
[dependencies]
actix = "0.7.7"
actix-net = "0.2.2"
actix = "0.7.9"
actix-net = "0.2.6"
askama_escape = "0.1.0"
base64 = "0.10"
bitflags = "1.0"
failure = "^0.1.2"
h2 = "0.1"
http = "^0.1.8"
http = "^0.1.14"
httparse = "1.3"
log = "0.4"
mime = "0.3"
@@ -105,7 +105,7 @@ slab = "0.4"
tokio = "0.1"
tokio-io = "0.1"
tokio-tcp = "0.1"
tokio-timer = "0.2"
tokio-timer = "0.2.8"
tokio-reactor = "0.1"
tokio-current-thread = "0.1"

View File

@@ -23,7 +23,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
* Minimum supported Rust version: 1.26 or later
* Minimum supported Rust version: 1.31 or later
## Example

View File

@@ -3,8 +3,8 @@ use std::net::Shutdown;
use std::time::{Duration, Instant};
use std::{fmt, io, mem, time};
use actix::resolver::{Connect as ResolveConnect, Resolver, ResolverError};
use actix::{
use actix_inner::actors::resolver::{Connect as ResolveConnect, Resolver, ResolverError};
use actix_inner::{
fut, Actor, ActorFuture, ActorResponse, AsyncContext, Context,
ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised,
SystemService, WrapFuture,

View File

@@ -2,11 +2,12 @@
//!
//! ```rust
//! # extern crate actix_web;
//! # extern crate actix;
//! # extern crate futures;
//! # extern crate tokio;
//! # use futures::Future;
//! # use std::process;
//! use actix_web::{actix, client};
//! use actix_web::client;
//!
//! fn main() {
//! actix::run(
@@ -61,12 +62,13 @@ impl ResponseError for SendRequestError {
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate actix;
/// # extern crate futures;
/// # extern crate tokio;
/// # extern crate env_logger;
/// # use futures::Future;
/// # use std::process;
/// use actix_web::{actix, client};
/// use actix_web::client;
///
/// fn main() {
/// actix::run(

View File

@@ -6,7 +6,8 @@ use std::time::{Duration, Instant};
use std::{io, mem};
use tokio_timer::Delay;
use actix::{Addr, Request, SystemService};
use actix_inner::dev::Request;
use actix::{Addr, SystemService};
use super::{
ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect,

View File

@@ -27,11 +27,12 @@ use httprequest::HttpRequest;
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate actix;
/// # extern crate futures;
/// # extern crate tokio;
/// # use futures::Future;
/// # use std::process;
/// use actix_web::{actix, client};
/// use actix_web::client;
///
/// fn main() {
/// actix::run(

View File

@@ -5,7 +5,7 @@ use std::string::FromUtf8Error;
use std::sync::Mutex;
use std::{fmt, io, result};
use actix::MailboxError;
use actix::{MailboxError, SendError};
use cookie;
use failure::{self, Backtrace, Fail};
use futures::Canceled;
@@ -136,6 +136,10 @@ pub trait ResponseError: Fail + InternalResponseErrorAsFail {
}
}
impl<T> ResponseError for SendError<T>
where T: Send + Sync + 'static {
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.cause, f)

View File

@@ -12,10 +12,11 @@ use serde::de::{self, DeserializeOwned};
use serde_urlencoded;
use de::PathDeserializer;
use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError};
use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError, ErrorConflict};
use handler::{AsyncResult, FromRequest};
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
use httprequest::HttpRequest;
use Either;
#[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from the request's path. Information from the path is
@@ -634,6 +635,153 @@ where
}
}
/// Extract either one of two fields from the request.
///
/// If both or none of the fields can be extracted, the default behaviour is to prefer the first
/// successful, last that failed. The behaviour can be changed by setting the appropriate
/// ```EitherCollisionStrategy```.
///
/// CAVEAT: Most of the time both extractors will be run. Make sure that the extractors you specify
/// can be run one after another (or in parallel). This will always fail for extractors that modify
/// the request state (such as the `Form` extractors that read in the body stream).
/// So Either<Form<A>, Form<B>> will not work correctly - it will only succeed if it matches the first
/// option, but will always fail to match the second (since the body stream will be at the end, and
/// appear to be empty).
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// extern crate rand;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest};
/// use actix_web::error::ErrorBadRequest;
/// use actix_web::Either;
///
/// #[derive(Debug, Deserialize)]
/// struct Thing { name: String }
///
/// #[derive(Debug, Deserialize)]
/// struct OtherThing { id: String }
///
/// impl<S> FromRequest<S> for Thing {
/// type Config = ();
/// type Result = Result<Thing, Error>;
///
/// #[inline]
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// }
/// }
/// }
///
/// impl<S> FromRequest<S> for OtherThing {
/// type Config = ();
/// type Result = Result<OtherThing, Error>;
///
/// #[inline]
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
/// if rand::random() {
/// Ok(OtherThing { id: "otherthingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// }
/// }
/// }
///
/// /// extract text data from request
/// fn index(supplied_thing: Either<Thing, OtherThing>) -> Result<String> {
/// match supplied_thing {
/// Either::A(thing) => Ok(format!("Got something: {:?}", thing)),
/// Either::B(other_thing) => Ok(format!("Got anotherthing: {:?}", other_thing))
/// }
/// }
///
/// fn main() {
/// let app = App::new().resource("/users/:first", |r| {
/// r.method(http::Method::POST).with(index)
/// });
/// }
/// ```
impl<A: 'static, B: 'static, S: 'static> FromRequest<S> for Either<A,B> where A: FromRequest<S>, B: FromRequest<S> {
type Config = EitherConfig<A,B,S>;
type Result = AsyncResult<Either<A,B>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
let a = A::from_request(&req.clone(), &cfg.a).into().map(|a| Either::A(a));
let b = B::from_request(req, &cfg.b).into().map(|b| Either::B(b));
match &cfg.collision_strategy {
EitherCollisionStrategy::PreferA => AsyncResult::future(Box::new(a.or_else(|_| b))),
EitherCollisionStrategy::PreferB => AsyncResult::future(Box::new(b.or_else(|_| a))),
EitherCollisionStrategy::FastestSuccessful => AsyncResult::future(Box::new(a.select2(b).then( |r| match r {
Ok(future::Either::A((ares, _b))) => AsyncResult::ok(ares),
Ok(future::Either::B((bres, _a))) => AsyncResult::ok(bres),
Err(future::Either::A((_aerr, b))) => AsyncResult::future(Box::new(b)),
Err(future::Either::B((_berr, a))) => AsyncResult::future(Box::new(a))
}))),
EitherCollisionStrategy::ErrorA => AsyncResult::future(Box::new(b.then(|r| match r {
Err(_berr) => AsyncResult::future(Box::new(a)),
Ok(b) => AsyncResult::future(Box::new(a.then( |r| match r {
Ok(_a) => Err(ErrorConflict("Both wings of either extractor completed")),
Err(_arr) => Ok(b)
})))
}))),
EitherCollisionStrategy::ErrorB => AsyncResult::future(Box::new(a.then(|r| match r {
Err(_aerr) => AsyncResult::future(Box::new(b)),
Ok(a) => AsyncResult::future(Box::new(b.then( |r| match r {
Ok(_b) => Err(ErrorConflict("Both wings of either extractor completed")),
Err(_berr) => Ok(a)
})))
}))),
}
}
}
/// Defines the result if neither or both of the extractors supplied to an Either<A,B> extractor succeed.
#[derive(Debug)]
pub enum EitherCollisionStrategy {
/// If both are successful, return A, if both fail, return error of B
PreferA,
/// If both are successful, return B, if both fail, return error of A
PreferB,
/// Return result of the faster, error of the slower if both fail
FastestSuccessful,
/// Return error if both succeed, return error of A if both fail
ErrorA,
/// Return error if both succeed, return error of B if both fail
ErrorB
}
impl Default for EitherCollisionStrategy {
fn default() -> Self {
EitherCollisionStrategy::FastestSuccessful
}
}
///Determines Either extractor configuration
///
///By default `EitherCollisionStrategy::FastestSuccessful` is used.
pub struct EitherConfig<A,B,S> where A: FromRequest<S>, B: FromRequest<S> {
a: A::Config,
b: B::Config,
collision_strategy: EitherCollisionStrategy
}
impl<A,B,S> Default for EitherConfig<A,B,S> where A: FromRequest<S>, B: FromRequest<S> {
fn default() -> Self {
EitherConfig {
a: A::Config::default(),
b: B::Config::default(),
collision_strategy: EitherCollisionStrategy::default()
}
}
}
/// Optionally extract a field from the request or extract the Error if unsuccessful
///
/// If the FromRequest for T fails, inject Err into handler rather than returning an error response
@@ -874,6 +1022,11 @@ mod tests {
hello: String,
}
#[derive(Deserialize, Debug, PartialEq)]
struct OtherInfo {
bye: String,
}
#[test]
fn test_bytes() {
let cfg = PayloadConfig::default();
@@ -977,6 +1130,48 @@ mod tests {
}
}
#[test]
fn test_either() {
let req = TestRequest::default().finish();
let mut cfg: EitherConfig<Query<Info>, Query<OtherInfo>, _> = EitherConfig::default();
assert!(Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().is_err());
let req = TestRequest::default().uri("/index?hello=world").finish();
match Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().unwrap() {
Async::Ready(r) => assert_eq!(r, Either::A(Query(Info { hello: "world".into() }))),
_ => unreachable!(),
}
let req = TestRequest::default().uri("/index?bye=world").finish();
match Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().unwrap() {
Async::Ready(r) => assert_eq!(r, Either::B(Query(OtherInfo { bye: "world".into() }))),
_ => unreachable!(),
}
let req = TestRequest::default().uri("/index?hello=world&bye=world").finish();
cfg.collision_strategy = EitherCollisionStrategy::PreferA;
match Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().unwrap() {
Async::Ready(r) => assert_eq!(r, Either::A(Query(Info { hello: "world".into() }))),
_ => unreachable!(),
}
cfg.collision_strategy = EitherCollisionStrategy::PreferB;
match Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().unwrap() {
Async::Ready(r) => assert_eq!(r, Either::B(Query(OtherInfo { bye: "world".into() }))),
_ => unreachable!(),
}
cfg.collision_strategy = EitherCollisionStrategy::ErrorA;
assert!(Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().is_err());
cfg.collision_strategy = EitherCollisionStrategy::FastestSuccessful;
assert!(Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().is_ok());
}
#[test]
fn test_result() {
let req = TestRequest::with_header(

View File

@@ -86,7 +86,7 @@ pub trait FromRequest<S>: Sized {
/// # fn is_a_variant() -> bool { true }
/// # fn main() {}
/// ```
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum Either<A, B> {
/// First branch of the type
A(A),

View File

@@ -200,7 +200,7 @@ pub trait HttpMessage: Sized {
/// # fn main() {}
/// ```
fn json<T: DeserializeOwned>(&self) -> JsonBody<Self, T> {
JsonBody::new(self)
JsonBody::new::<()>(self, None)
}
/// Return stream to http payload processes as multipart.
@@ -213,9 +213,10 @@ pub trait HttpMessage: Sized {
/// # extern crate actix_web;
/// # extern crate env_logger;
/// # extern crate futures;
/// # extern crate actix;
/// # use std::str;
/// # use actix_web::*;
/// # use actix_web::actix::fut::FinishStream;
/// # use actix::FinishStream;
/// # use futures::{Future, Stream};
/// # use futures::future::{ok, result, Either};
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {

View File

@@ -143,7 +143,7 @@ where
let req2 = req.clone();
let err = Rc::clone(&cfg.ehandler);
Box::new(
JsonBody::new(req)
JsonBody::new(req, Some(cfg))
.limit(cfg.limit)
.map_err(move |e| (*err)(e, &req2))
.map(Json),
@@ -155,6 +155,7 @@ where
///
/// ```rust
/// # extern crate actix_web;
/// extern crate mime;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{error, http, App, HttpResponse, Json, Result};
///
@@ -173,6 +174,9 @@ where
/// r.method(http::Method::POST)
/// .with_config(index, |cfg| {
/// cfg.0.limit(4096) // <- change json extractor configuration
/// .content_type(|mime| { // <- accept text/plain content type
/// mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
/// })
/// .error_handler(|err, req| { // <- create custom error response
/// error::InternalError::from_response(
/// err, HttpResponse::Conflict().finish()).into()
@@ -184,6 +188,7 @@ where
pub struct JsonConfig<S> {
limit: usize,
ehandler: Rc<Fn(JsonPayloadError, &HttpRequest<S>) -> Error>,
content_type: Option<Box<Fn(mime::Mime) -> bool>>,
}
impl<S> JsonConfig<S> {
@@ -201,6 +206,15 @@ impl<S> JsonConfig<S> {
self.ehandler = Rc::new(f);
self
}
/// Set predicate for allowed content types
pub fn content_type<F>(&mut self, predicate: F) -> &mut Self
where
F: Fn(mime::Mime) -> bool + 'static,
{
self.content_type = Some(Box::new(predicate));
self
}
}
impl<S> Default for JsonConfig<S> {
@@ -208,6 +222,7 @@ impl<S> Default for JsonConfig<S> {
JsonConfig {
limit: 262_144,
ehandler: Rc::new(|e, _| e.into()),
content_type: None,
}
}
}
@@ -217,6 +232,7 @@ impl<S> Default for JsonConfig<S> {
/// Returns error:
///
/// * content type is not `application/json`
/// (unless specified in [`JsonConfig`](struct.JsonConfig.html))
/// * content length is greater than 256k
///
/// # Server example
@@ -253,10 +269,13 @@ pub struct JsonBody<T: HttpMessage, U: DeserializeOwned> {
impl<T: HttpMessage, U: DeserializeOwned> JsonBody<T, U> {
/// Create `JsonBody` for request.
pub fn new(req: &T) -> Self {
pub fn new<S>(req: &T, cfg: Option<&JsonConfig<S>>) -> Self {
// check content-type
let json = if let Ok(Some(mime)) = req.mime_type() {
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) ||
cfg.map_or(false, |cfg| {
cfg.content_type.as_ref().map_or(false, |predicate| predicate(mime))
})
} else {
false
};
@@ -440,4 +459,61 @@ mod tests {
.finish();
assert!(handler.handle(&req).as_err().is_none())
}
#[test]
fn test_with_json_and_bad_content_type() {
let mut cfg = JsonConfig::default();
cfg.limit(4096);
let handler = With::new(|data: Json<MyObject>| data, cfg);
let req = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
assert!(handler.handle(&req).as_err().is_some())
}
#[test]
fn test_with_json_and_good_custom_content_type() {
let mut cfg = JsonConfig::default();
cfg.limit(4096);
cfg.content_type(|mime: mime::Mime| {
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
});
let handler = With::new(|data: Json<MyObject>| data, cfg);
let req = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
assert!(handler.handle(&req).as_err().is_none())
}
#[test]
fn test_with_json_and_bad_custom_content_type() {
let mut cfg = JsonConfig::default();
cfg.limit(4096);
cfg.content_type(|mime: mime::Mime| {
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
});
let handler = With::new(|data: Json<MyObject>| data, cfg);
let req = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/html"),
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
assert!(handler.handle(&req).as_err().is_some())
}
}

View File

@@ -217,14 +217,12 @@ pub use server::Request;
pub mod actix {
//! Re-exports [actix's](https://docs.rs/actix/) prelude
extern crate actix;
pub use self::actix::actors::resolver;
pub use self::actix::actors::signal;
pub use self::actix::fut;
pub use self::actix::msgs;
pub use self::actix::prelude::*;
pub use self::actix::{run, spawn};
pub use super::actix_inner::actors::resolver;
pub use super::actix_inner::actors::signal;
pub use super::actix_inner::fut;
pub use super::actix_inner::msgs;
pub use super::actix_inner::prelude::*;
pub use super::actix_inner::{run, spawn};
}
#[cfg(feature = "openssl")]
@@ -255,7 +253,7 @@ pub mod dev {
pub use body::BodyStream;
pub use context::Drain;
pub use extractor::{FormConfig, PayloadConfig, QueryConfig, PathConfig};
pub use extractor::{FormConfig, PayloadConfig, QueryConfig, PathConfig, EitherConfig, EitherCollisionStrategy};
pub use handler::{AsyncResult, Handler};
pub use httpmessage::{MessageBody, Readlines, UrlEncoded};
pub use httpresponse::HttpResponseBuilder;

View File

@@ -442,11 +442,23 @@ impl<S> Middleware<S> for Cors {
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
}
}
AllOrSome::Some(_) => {
resp.headers_mut().insert(
header::ACCESS_CONTROL_ALLOW_ORIGIN,
self.inner.origins_str.as_ref().unwrap().clone(),
);
AllOrSome::Some(ref origins) => {
if let Some(origin) = req.headers().get(header::ORIGIN).filter(|o| {
match o.to_str() {
Ok(os) => origins.contains(os),
_ => false
}
}) {
resp.headers_mut().insert(
header::ACCESS_CONTROL_ALLOW_ORIGIN,
origin.clone(),
);
} else {
resp.headers_mut().insert(
header::ACCESS_CONTROL_ALLOW_ORIGIN,
self.inner.origins_str.as_ref().unwrap().clone()
);
};
}
}
@@ -1134,17 +1146,10 @@ mod tests {
.to_str()
.unwrap();
if origins_str.starts_with("https://www.example.com") {
assert_eq!(
"https://www.example.com, https://www.google.com",
origins_str
);
} else {
assert_eq!(
"https://www.google.com, https://www.example.com",
origins_str
);
}
assert_eq!(
"https://www.example.com",
origins_str
);
}
#[test]
@@ -1180,4 +1185,43 @@ mod tests {
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
#[test]
fn test_multiple_origins() {
let cors = Cors::build()
.allowed_origin("https://example.com")
.allowed_origin("https://example.org")
.allowed_methods(vec![Method::GET])
.finish();
let req = TestRequest::with_header("Origin", "https://example.com")
.method(Method::GET)
.finish();
let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&req, resp).unwrap().response();
print!("{:?}", resp);
assert_eq!(
&b"https://example.com"[..],
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
);
let req = TestRequest::with_header("Origin", "https://example.org")
.method(Method::GET)
.finish();
let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&req, resp).unwrap().response();
assert_eq!(
&b"https://example.org"[..],
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
);
}
}

View File

@@ -33,7 +33,8 @@
//!
//! ```rust
//! # extern crate actix_web;
//! use actix_web::{actix, server, App, HttpRequest, Result};
//! # extern crate actix;
//! use actix_web::{server, App, HttpRequest, Result};
//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend};
//!
//! fn index(req: HttpRequest) -> Result<&'static str> {

View File

@@ -7,6 +7,7 @@ use futures::{Async, Future, Poll};
use tokio_current_thread::spawn;
use tokio_timer::Delay;
use body::Binary;
use error::{Error, PayloadError};
use http::{StatusCode, Version};
use payload::{Payload, PayloadStatus, PayloadWriter};
@@ -50,32 +51,40 @@ pub struct Http1Dispatcher<T: IoStream, H: HttpHandler + 'static> {
}
enum Entry<H: HttpHandler> {
Task(H::Task),
Task(H::Task, Option<()>),
Error(Box<HttpHandlerTask>),
}
impl<H: HttpHandler> Entry<H> {
fn into_task(self) -> H::Task {
match self {
Entry::Task(task) => task,
Entry::Task(task, _) => task,
Entry::Error(_) => panic!(),
}
}
fn disconnected(&mut self) {
match *self {
Entry::Task(ref mut task) => task.disconnected(),
Entry::Task(ref mut task, _) => task.disconnected(),
Entry::Error(ref mut task) => task.disconnected(),
}
}
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
match *self {
Entry::Task(ref mut task) => task.poll_io(io),
Entry::Task(ref mut task, ref mut except) => {
match except {
Some(_) => {
let _ = io.write(&Binary::from("HTTP/1.1 100 Continue\r\n\r\n"));
}
_ => (),
};
task.poll_io(io)
}
Entry::Error(ref mut task) => task.poll_io(io),
}
}
fn poll_completed(&mut self) -> Poll<(), Error> {
match *self {
Entry::Task(ref mut task) => task.poll_completed(),
Entry::Task(ref mut task, _) => task.poll_completed(),
Entry::Error(ref mut task) => task.poll_completed(),
}
}
@@ -463,7 +472,11 @@ where
'outer: loop {
match self.decoder.decode(&mut self.buf, &self.settings) {
Ok(Some(Message::Message { mut msg, payload })) => {
Ok(Some(Message::Message {
mut msg,
mut expect,
payload,
})) => {
updated = true;
self.flags.insert(Flags::STARTED);
@@ -484,6 +497,12 @@ where
match self.settings.handler().handle(msg) {
Ok(mut task) => {
if self.tasks.is_empty() {
if expect {
expect = false;
let _ = self.stream.write(&Binary::from(
"HTTP/1.1 100 Continue\r\n\r\n",
));
}
match task.poll_io(&mut self.stream) {
Ok(Async::Ready(ready)) => {
// override keep-alive state
@@ -510,7 +529,10 @@ where
}
}
}
self.tasks.push_back(Entry::Task(task));
self.tasks.push_back(Entry::Task(
task,
if expect { Some(()) } else { None },
));
continue 'outer;
}
Err(_) => {
@@ -608,13 +630,13 @@ mod tests {
impl Message {
fn message(self) -> Request {
match self {
Message::Message { msg, payload: _ } => msg,
Message::Message { msg, .. } => msg,
_ => panic!("error"),
}
}
fn is_payload(&self) -> bool {
match *self {
Message::Message { msg: _, payload } => payload,
Message::Message { payload, .. } => payload,
_ => panic!("error"),
}
}
@@ -874,13 +896,13 @@ mod tests {
let settings = wrk_settings();
let mut reader = H1Decoder::new();
assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() }
assert! { reader.decode(&mut buf, &settings).unwrap().is_none() }
buf.extend(b"t");
assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() }
assert! { reader.decode(&mut buf, &settings).unwrap().is_none() }
buf.extend(b"es");
assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() }
assert! { reader.decode(&mut buf, &settings).unwrap().is_none() }
buf.extend(b"t: value\r\n\r\n");
match reader.decode(&mut buf, &settings) {
@@ -942,6 +964,14 @@ mod tests {
let req = parse_ready!(&mut buf);
assert!(!req.keep_alive());
let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\
connection: Close\r\n\r\n",
);
let req = parse_ready!(&mut buf);
assert!(!req.keep_alive());
}
#[test]
@@ -953,10 +983,26 @@ mod tests {
let req = parse_ready!(&mut buf);
assert!(!req.keep_alive());
let mut buf = BytesMut::from(
"GET /test HTTP/1.0\r\n\
connection: Close\r\n\r\n",
);
let req = parse_ready!(&mut buf);
assert!(!req.keep_alive());
}
#[test]
fn test_conn_keep_alive_1_0() {
let mut buf = BytesMut::from(
"GET /test HTTP/1.0\r\n\
connection: Keep-Alive\r\n\r\n",
);
let req = parse_ready!(&mut buf);
assert!(req.keep_alive());
let mut buf = BytesMut::from(
"GET /test HTTP/1.0\r\n\
connection: keep-alive\r\n\r\n",
@@ -1009,6 +1055,15 @@ mod tests {
let req = parse_ready!(&mut buf);
assert!(req.upgrade());
let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\
upgrade: Websockets\r\n\
connection: Upgrade\r\n\r\n",
);
let req = parse_ready!(&mut buf);
assert!(req.upgrade());
}
#[test]

View File

@@ -20,7 +20,11 @@ pub(crate) struct H1Decoder {
#[derive(Debug)]
pub(crate) enum Message {
Message { msg: Request, payload: bool },
Message {
msg: Request,
payload: bool,
expect: bool,
},
Chunk(Bytes),
Eof,
}
@@ -63,10 +67,11 @@ impl H1Decoder {
.parse_message(src, settings)
.map_err(DecoderError::Error)?
{
Async::Ready((msg, decoder)) => {
Async::Ready((msg, expect, decoder)) => {
self.decoder = decoder;
Ok(Some(Message::Message {
msg,
expect,
payload: self.decoder.is_some(),
}))
}
@@ -85,11 +90,12 @@ impl H1Decoder {
&self,
buf: &mut BytesMut,
settings: &ServiceConfig<H>,
) -> Poll<(Request, Option<EncodingDecoder>), ParseError> {
) -> Poll<(Request, bool, Option<EncodingDecoder>), ParseError> {
// Parse http message
let mut has_upgrade = false;
let mut chunked = false;
let mut content_length = None;
let mut expect_continue = false;
let msg = {
// Unsafe: we read only this data only after httparse parses headers into.
@@ -157,23 +163,25 @@ impl H1Decoder {
}
// transfer-encoding
header::TRANSFER_ENCODING => {
if let Ok(s) = value.to_str() {
chunked = s.to_lowercase().contains("chunked");
if let Ok(s) = value.to_str().map(|s| s.trim()) {
chunked = s.eq_ignore_ascii_case("chunked");
} else {
return Err(ParseError::Header);
}
}
// connection keep-alive state
header::CONNECTION => {
let ka = if let Ok(conn) = value.to_str() {
let ka = if let Ok(conn) =
value.to_str().map(|conn| conn.trim())
{
if version == Version::HTTP_10
&& conn.contains("keep-alive")
&& conn.eq_ignore_ascii_case("keep-alive")
{
true
} else {
version == Version::HTTP_11 && !(conn
.contains("close")
|| conn.contains("upgrade"))
version == Version::HTTP_11
&& !(conn.eq_ignore_ascii_case("close")
|| conn.eq_ignore_ascii_case("upgrade"))
}
} else {
false
@@ -184,12 +192,17 @@ impl H1Decoder {
has_upgrade = true;
// check content-length, some clients (dart)
// sends "content-length: 0" with websocket upgrade
if let Ok(val) = value.to_str() {
if val == "websocket" {
if let Ok(val) = value.to_str().map(|val| val.trim()) {
if val.eq_ignore_ascii_case("websocket") {
content_length = None;
}
}
}
header::EXPECT => {
if value == "100-continue" {
expect_continue = true
}
}
_ => (),
}
@@ -220,7 +233,7 @@ impl H1Decoder {
None
};
Ok(Async::Ready((msg, decoder)))
Ok(Async::Ready((msg, expect_continue, decoder)))
}
}

View File

@@ -457,7 +457,8 @@ impl<H: IntoHttpHandler, F: Fn() -> H + Send + Clone> HttpServer<H, F> {
///
/// ```rust
/// extern crate actix_web;
/// use actix_web::{actix, server, App, HttpResponse};
/// extern crate actix;
/// use actix_web::{server, App, HttpResponse};
///
/// fn main() {
/// let sys = actix::System::new("example"); // <- create Actix system

View File

@@ -166,7 +166,8 @@ const HW_BUFFER_SIZE: usize = 32_768;
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{actix, server, App, HttpResponse};
/// # extern crate actix;
/// use actix_web::{server, App, HttpResponse};
///
/// fn main() {
/// let sys = actix::System::new("example"); // <- create Actix system

View File

@@ -4,7 +4,7 @@ use std::str::FromStr;
use std::sync::mpsc;
use std::{net, thread};
use actix_inner::{Actor, Addr, System};
use actix::{Actor, Addr, System};
use cookie::Cookie;
use futures::Future;

View File

@@ -8,7 +8,8 @@
//!
//! ```rust
//! # extern crate actix_web;
//! # use actix_web::actix::*;
//! # extern crate actix;
//! # use actix::prelude::*;
//! # use actix_web::*;
//! use actix_web::{ws, HttpRequest, HttpResponse};
//!