mirror of
https://github.com/fafhrd91/actix-web
synced 2025-08-19 04:15:38 +02:00
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1fbb52ad3b | ||
|
e5cdd22720 | ||
|
4f2e970732 | ||
|
4d45313f9d | ||
|
55a2a59906 | ||
|
61883042c2 | ||
|
799c6eb719 | ||
|
037a1c6a24 | ||
|
bfdf762062 | ||
|
477bf0d8ae | ||
|
e9fe3879df | ||
|
1a940d4c18 | ||
|
e8bdcb1c08 | ||
|
46db09428c | ||
|
90eef31cc0 | ||
|
86af02156b |
39
CHANGES.md
39
CHANGES.md
@@ -1,8 +1,45 @@
|
||||
# Changes
|
||||
|
||||
## [0.7.18] - 2019-01-10
|
||||
|
||||
### Added
|
||||
|
||||
* Add `with_cookie` for `TestRequest` to allow users to customize request cookie. #647
|
||||
|
||||
* Add `cookie` method for `TestRequest` to allow users to add cookie dynamically.
|
||||
|
||||
### Fixed
|
||||
|
||||
* StaticFiles decode special characters in request's path
|
||||
|
||||
* Fix test server listener leak #654
|
||||
|
||||
## [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.
|
||||
|
||||
|
12
Cargo.toml
12
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "0.7.15"
|
||||
version = "0.7.18"
|
||||
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"
|
||||
v_htmlescape = "0.3.2"
|
||||
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"
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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(
|
||||
|
@@ -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,
|
||||
|
@@ -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(
|
||||
|
@@ -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)
|
||||
|
197
src/extractor.rs
197
src/extractor.rs
@@ -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(
|
||||
|
30
src/fs.rs
30
src/fs.rs
@@ -11,7 +11,7 @@ use std::{cmp, io};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use askama_escape::{escape as escape_html_entity};
|
||||
use v_htmlescape::HTMLEscape;
|
||||
use bytes::Bytes;
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use futures_cpupool::{CpuFuture, CpuPool};
|
||||
@@ -569,6 +569,11 @@ macro_rules! encode_file_url {
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn escape_html_entity(s: &str) -> HTMLEscape {
|
||||
HTMLEscape::from(s)
|
||||
}
|
||||
|
||||
// " -- " & -- & ' -- ' < -- < > -- > / -- /
|
||||
macro_rules! encode_file_name {
|
||||
($entry:ident) => {
|
||||
@@ -756,7 +761,7 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
|
||||
&self,
|
||||
req: &HttpRequest<S>,
|
||||
) -> Result<AsyncResult<HttpResponse>, Error> {
|
||||
let tail: String = req.match_info().query("tail")?;
|
||||
let tail: String = req.match_info().get_decoded("tail").unwrap_or_else(|| "".to_string());
|
||||
let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?;
|
||||
|
||||
// full filepath
|
||||
@@ -1298,6 +1303,27 @@ mod tests {
|
||||
assert_eq!(bytes, data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_files_with_spaces() {
|
||||
let mut srv = test::TestServer::with_factory(|| {
|
||||
App::new().handler(
|
||||
"/",
|
||||
StaticFiles::new(".").unwrap().index_file("Cargo.toml"),
|
||||
)
|
||||
});
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/tests/test%20space.binary"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
let data = Bytes::from(fs::read("tests/test space.binary").unwrap());
|
||||
assert_eq!(bytes, data);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OnlyMethodHeadConfig;
|
||||
impl StaticFileConfig for OnlyMethodHeadConfig {
|
||||
|
@@ -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),
|
||||
|
@@ -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>> {
|
||||
|
82
src/json.rs
82
src/json.rs
@@ -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())
|
||||
}
|
||||
}
|
||||
|
18
src/lib.rs
18
src/lib.rs
@@ -100,7 +100,6 @@ extern crate failure;
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
extern crate askama_escape;
|
||||
extern crate cookie;
|
||||
extern crate futures_cpupool;
|
||||
extern crate http as modhttp;
|
||||
@@ -137,6 +136,7 @@ extern crate serde_urlencoded;
|
||||
extern crate percent_encoding;
|
||||
extern crate serde_json;
|
||||
extern crate smallvec;
|
||||
extern crate v_htmlescape;
|
||||
|
||||
extern crate actix_net;
|
||||
#[macro_use]
|
||||
@@ -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;
|
||||
|
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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> {
|
||||
|
@@ -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]
|
||||
|
@@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
48
src/test.rs
48
src/test.rs
@@ -4,8 +4,10 @@ use std::str::FromStr;
|
||||
use std::sync::mpsc;
|
||||
use std::{net, thread};
|
||||
|
||||
use actix_inner::{Actor, Addr, System};
|
||||
use actix::{Actor, Addr, System};
|
||||
use actix::actors::signal;
|
||||
|
||||
use actix_net::server::Server;
|
||||
use cookie::Cookie;
|
||||
use futures::Future;
|
||||
use http::header::HeaderName;
|
||||
@@ -66,6 +68,7 @@ pub struct TestServer {
|
||||
ssl: bool,
|
||||
conn: Addr<ClientConnector>,
|
||||
rt: Runtime,
|
||||
backend: Addr<Server>,
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
@@ -112,24 +115,25 @@ impl TestServer {
|
||||
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let local_addr = tcp.local_addr().unwrap();
|
||||
|
||||
let _ = HttpServer::new(factory)
|
||||
let srv = HttpServer::new(factory)
|
||||
.disable_signals()
|
||||
.listen(tcp)
|
||||
.keep_alive(5)
|
||||
.start();
|
||||
|
||||
tx.send((System::current(), local_addr, TestServer::get_conn()))
|
||||
tx.send((System::current(), local_addr, TestServer::get_conn(), srv))
|
||||
.unwrap();
|
||||
sys.run();
|
||||
});
|
||||
|
||||
let (system, addr, conn) = rx.recv().unwrap();
|
||||
let (system, addr, conn, backend) = rx.recv().unwrap();
|
||||
System::set_current(system);
|
||||
TestServer {
|
||||
addr,
|
||||
conn,
|
||||
ssl: false,
|
||||
rt: Runtime::new().unwrap(),
|
||||
backend,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +201,7 @@ impl TestServer {
|
||||
|
||||
/// Stop http server
|
||||
fn stop(&mut self) {
|
||||
let _ = self.backend.send(signal::Signal(signal::SignalType::Term)).wait();
|
||||
System::current().stop();
|
||||
}
|
||||
|
||||
@@ -333,8 +338,7 @@ where
|
||||
.keep_alive(5)
|
||||
.disable_signals();
|
||||
|
||||
tx.send((System::current(), addr, TestServer::get_conn()))
|
||||
.unwrap();
|
||||
|
||||
|
||||
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
||||
{
|
||||
@@ -356,18 +360,22 @@ where
|
||||
let tcp = net::TcpListener::bind(addr).unwrap();
|
||||
srv = srv.listen(tcp);
|
||||
}
|
||||
srv.start();
|
||||
let backend = srv.start();
|
||||
|
||||
tx.send((System::current(), addr, TestServer::get_conn(), backend))
|
||||
.unwrap();
|
||||
|
||||
sys.run();
|
||||
});
|
||||
|
||||
let (system, addr, conn) = rx.recv().unwrap();
|
||||
let (system, addr, conn, backend) = rx.recv().unwrap();
|
||||
System::set_current(system);
|
||||
TestServer {
|
||||
addr,
|
||||
conn,
|
||||
ssl: has_ssl,
|
||||
rt: Runtime::new().unwrap(),
|
||||
backend,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -507,6 +515,11 @@ impl TestRequest<()> {
|
||||
{
|
||||
TestRequest::default().header(key, value)
|
||||
}
|
||||
|
||||
/// Create TestRequest and set request cookie
|
||||
pub fn with_cookie(cookie: Cookie<'static>) -> TestRequest<()> {
|
||||
TestRequest::default().cookie(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> TestRequest<S> {
|
||||
@@ -543,6 +556,25 @@ impl<S: 'static> TestRequest<S> {
|
||||
self
|
||||
}
|
||||
|
||||
/// set cookie of this request
|
||||
pub fn cookie(mut self, cookie: Cookie<'static>) -> Self {
|
||||
if self.cookies.is_none() {
|
||||
let mut should_insert = true;
|
||||
let old_cookies = self.cookies.as_mut().unwrap();
|
||||
for old_cookie in old_cookies.iter() {
|
||||
if old_cookie == &cookie {
|
||||
should_insert = false
|
||||
};
|
||||
};
|
||||
if should_insert {
|
||||
old_cookies.push(cookie);
|
||||
};
|
||||
} else {
|
||||
self.cookies = Some(vec![cookie]);
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a header
|
||||
pub fn set<H: Header>(mut self, hdr: H) -> Self {
|
||||
if let Ok(value) = hdr.try_into() {
|
||||
|
@@ -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};
|
||||
//!
|
||||
|
1
tests/test space.binary
Normal file
1
tests/test space.binary
Normal file
@@ -0,0 +1 @@
|
||||
<EFBFBD>TǑɂV<EFBFBD>2<EFBFBD>vI<EFBFBD><EFBFBD><EFBFBD>\<5C>R˙<52><CB99><EFBFBD>e<EFBFBD><04>vD<76>:藽<>RV<03>Yp<59><70>;<3B><>G<><47>p!2<7F>C<EFBFBD>.<2E><0C><><EFBFBD><EFBFBD>pA!<21>ߦ<EFBFBD>x j+Uc<55><63><EFBFBD>X<13>c%<17>;<3B>"y<10><>AI
|
Reference in New Issue
Block a user