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

Compare commits

..

76 Commits

Author SHA1 Message Date
Nikolay Kim
53da55aa3c alpha4 release 2019-04-07 23:42:05 -07:00
Nikolay Kim
aa78565453 use objects pool for HttpRequest; optimize nested services call 2019-04-07 23:06:21 -07:00
Nikolay Kim
75b213a6f0 refactor FromRequest trait 2019-04-07 14:43:07 -07:00
Nikolay Kim
3c650ca194 remove buffer capacity for payload 2019-04-07 10:40:45 -07:00
Nikolay Kim
219baf3323 remove PayloadWriter trait 2019-04-07 10:29:26 -07:00
Nikolay Kim
ec09d6fbe6 optimize encode headers and body split 2019-04-07 10:03:38 -07:00
Nikolay Kim
68d2203dd6 run travis with stable rust only 2019-04-07 08:17:29 -07:00
Nikolay Kim
748289f0ff use custom headers map; more optimizations 2019-04-06 15:02:02 -07:00
Nikolay Kim
4ef46e26f9 Merge branch 'master' of github.com:actix/actix-web 2019-04-06 08:13:14 -07:00
Nikolay Kim
3872d3ba5a refactor h1 dispatcher 2019-04-06 08:12:58 -07:00
Darin
b1523ab78c started 1.0 migration guide (#758) 2019-04-06 07:39:20 -07:00
Nikolay Kim
fbedaec661 add expect: 100-continue support #141 2019-04-05 16:46:44 -07:00
Nikolay Kim
02fcaca3da add backward compatibility 2019-04-05 11:36:26 -07:00
Darin
18593d8476 updated Connector docs and renamed service() to finish() (#757)
* added Connector to actix-web::client namespace

* updated Connector, renaming service() to finish() and adding docs

* added doc for finish method on Connector
2019-04-05 11:34:27 -07:00
Nikolay Kim
b6dacaa23a remove SendError and SendResponse services 2019-04-05 11:29:42 -07:00
Nikolay Kim
f89321fd01 fix import 2019-04-05 10:50:11 -07:00
Nikolay Kim
0d4a8e1b1c update actix-connect 2019-04-05 10:35:14 -07:00
Darin
162cd3eecd added Connector to actix-web::client namespace (#756) 2019-04-05 07:37:00 -07:00
nasa
a655bdac52 Fix clippy warning (#755) 2019-04-05 12:34:24 +03:00
Nikolay Kim
309c480782 encoder sent uncompressed data before compressed 2019-04-04 15:03:40 -07:00
Nikolay Kim
9c205f9f1d update tests for content-encoding 2019-04-04 14:00:56 -07:00
Nikolay Kim
1f5c0f50f9 Add minimal std::error::Error impl for Error 2019-04-04 13:23:38 -07:00
Nikolay Kim
d8bc66a18e Use thread pool for response body comression 2019-04-04 13:17:55 -07:00
Nikolay Kim
bc834f6a03 remove some static contraints 2019-04-04 10:59:34 -07:00
Nikolay Kim
dc7c3d37a1 upgrade router 2019-04-03 21:45:30 -07:00
Nikolay Kim
1e2bd68e83 Render error and return as response body 2019-04-03 19:55:19 -07:00
Nikolay Kim
954fe21751 set response error body 2019-04-03 19:07:25 -07:00
Haze
7d6085ddbd Add %U (URLPath) for logger (#752)
* Add %R (Route) for logger

* Requested Updates (Route => URLPath, %R => %U)
2019-04-03 17:41:42 -07:00
Nikolay Kim
cef3dc3586 added app_data() method 2019-04-03 15:25:52 -07:00
Nikolay Kim
237bfba1ed add App::configure() - allow to offload app configuration to different methods 2019-04-03 15:09:31 -07:00
Nikolay Kim
dfa0abf5a5 Export IntoHeaderValue 2019-04-03 12:44:47 -07:00
Nikolay Kim
e738361e09 move multipart support to separate crate 2019-04-03 12:28:58 -07:00
Nikolay Kim
f56072954b remove PayloadBuffer 2019-04-03 03:20:20 -07:00
Nikolay Kim
2a89b995aa do not cleanup travis build 2019-04-02 21:56:38 -07:00
Nikolay Kim
442f5057dd alpha.3 release 2019-04-02 21:49:31 -07:00
Nikolay Kim
19eef36f8f Merge branch 'tarpaulin' 2019-04-02 21:11:03 -07:00
Nikolay Kim
51d5006ccf Detect socket disconnection during protocol selection 2019-04-02 20:50:25 -07:00
Nikolay Kim
3aebe09e5c travis 2019-04-02 19:21:22 -07:00
Nikolay Kim
4227cddd30 fix dev dependencies 2019-04-02 15:00:10 -07:00
Nikolay Kim
db1f7651a3 more patch cratesio 2019-04-02 14:47:59 -07:00
Nikolay Kim
00000fb316 mut obj 2019-04-02 14:27:54 -07:00
Nikolay Kim
f100976ef0 rename close_connection to force_close 2019-04-02 14:08:30 -07:00
Nikolay Kim
deac983bc7 fix test-server workspace setup 2019-04-02 14:04:28 -07:00
Nikolay Kim
bca31eb7ad remove Deref 2019-04-02 13:35:01 -07:00
Nikolay Kim
e282ef7925 return back consuming builder 2019-04-02 12:51:16 -07:00
Nikolay Kim
49a499ce74 properly allocate read buffer 2019-04-02 11:11:32 -07:00
Nikolay Kim
d067b1d5f1 do not use static 2019-04-02 10:53:44 -07:00
Nikolay Kim
c27fbdc35f Preallocate read buffer for h1 codec, #749 2019-04-02 10:19:56 -07:00
Nikolay Kim
1bd0995d7a remove unneded & 2019-04-01 18:00:38 -07:00
Nikolay Kim
2d43489278 ClientRequest::json() accepts reference instead of object 2019-04-01 17:53:30 -07:00
Nikolay Kim
89a0a50e14 Merge branch 'master' of github.com:actix/actix-web 2019-04-01 15:20:04 -07:00
Nikolay Kim
38afc93304 Use non-consuming builder pattern for ClientRequest 2019-04-01 15:19:34 -07:00
Darin
03c84be1f2 Merge pull request #750 from Dowwie/master
added docs for wrap and wrap_fn
2019-04-01 17:37:04 -04:00
dowwie
6d169f4c9c Merge branch 'master' of https://github.com/Dowwie/actix-web 2019-04-01 15:10:49 -04:00
dowwie
3dd3f7bc92 updated scope wrap doc 2019-04-01 15:10:28 -04:00
Darin
e6936d9f73 Merge branch 'master' into master 2019-04-01 14:53:23 -04:00
dowwie
03dfbdfcdd updated wrap and wrap fn descriptions, still requiring viable examples 2019-04-01 14:52:05 -04:00
Nikolay Kim
5c4e4edda4 add ClientResponse::json() 2019-04-01 11:51:18 -07:00
Nikolay Kim
c5fa6c1abe do not consume response 2019-04-01 11:29:50 -07:00
Nikolay Kim
6c195d8521 add Derev<Target=RequestHead> for ClientRequest 2019-04-01 10:26:25 -07:00
Nikolay Kim
96fd61f3d5 rust 1.31.0 compatibility 2019-04-01 10:26:09 -07:00
dowwie
8800b8ef13 mentioned re-use in wrap doc 2019-04-01 09:59:21 -04:00
dowwie
220c04b7b3 added docs for wrap and wrap_fn 2019-04-01 09:30:11 -04:00
Nikolay Kim
34695f4bce rename test methods; update tests 2019-03-31 20:43:00 -07:00
Nikolay Kim
15c5a3bcfb fix test 2019-03-31 18:57:54 -07:00
Nikolay Kim
ab45974e35 add default handler 2019-03-31 18:19:18 -07:00
Nikolay Kim
e4b3f79458 allocate enough space 2019-03-31 17:05:02 -07:00
Nikolay Kim
ce8294740e fix tests with disabled features 2019-03-31 17:04:34 -07:00
Llaurence
ddf5089bff Warn when an unsealed private cookie isn't valid UTF-8 (#746) 2019-03-31 16:26:56 +03:00
Nikolay Kim
7596d0b7cb fix fn_guard doc string 2019-03-30 20:48:00 -07:00
Nikolay Kim
1a871d708e update guard doc test 2019-03-30 12:13:21 -07:00
Nikolay Kim
351df84cca update stable release api doc link 2019-03-30 11:37:56 -07:00
Nikolay Kim
6fcbe4bcda add fn_guard 2019-03-30 11:33:31 -07:00
Nikolay Kim
457b75c995 update api docs; move web to submodule 2019-03-30 10:04:38 -07:00
Nikolay Kim
724e9c2efb replace deprecated fn 2019-03-30 07:56:09 -07:00
Douman
2e159d1eb9 test-server: Request functions should accept path (#743) 2019-03-30 07:53:45 -07:00
117 changed files with 4617 additions and 2806 deletions

View File

@@ -10,9 +10,9 @@ matrix:
include:
- rust: stable
- rust: beta
- rust: nightly-2019-03-02
- rust: nightly-2019-04-02
allow_failures:
- rust: nightly-2019-03-02
- rust: nightly-2019-04-02
env:
global:
@@ -25,7 +25,7 @@ before_install:
- sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
before_cache: |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-04-02" ]]; then
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin
fi
@@ -42,14 +42,14 @@ script:
after_success:
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cargo doc --all-features --no-deps &&
cargo doc --all-features &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
git clone https://github.com/davisp/ghp-import.git &&
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
echo "Uploaded documentation"
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-04-02" ]]; then
taskset -c 0 cargo tarpaulin --out Xml --all --all-features
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"

View File

@@ -1,5 +1,39 @@
# Changes
## [1.0.0-alpha.4] - 2019-04-08
### Added
* `App::configure()` allow to offload app configuration to different methods
* Added `URLPath` option for logger
* Added `ServiceRequest::app_data()`, returns `Data<T>`
* Added `ServiceFromRequest::app_data()`, returns `Data<T>`
### Changed
* `FromRequest` trait refactoring
* Move multipart support to actix-multipart crate
## [1.0.0-alpha.3] - 2019-04-02
### Changed
* Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()`
* Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()`
* Removed `Deref` impls
### Removed
* Removed unused `actix_web::web::md()`
## [1.0.0-alpha.2] - 2019-03-29
### Added

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "1.0.0-alpha.2"
version = "1.0.0-alpha.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
@@ -30,8 +30,10 @@ members = [
"actix-http",
"actix-files",
"actix-session",
"actix-multipart",
"actix-web-actors",
"actix-web-codegen",
"test-server",
]
[package.metadata.docs.rs]
@@ -66,23 +68,22 @@ rust-tls = ["rustls", "actix-server/rust-tls"]
[dependencies]
actix-codec = "0.1.1"
actix-service = "0.3.4"
actix-service = "0.3.6"
actix-utils = "0.3.4"
actix-router = "0.1.0"
actix-router = "0.1.2"
actix-rt = "0.2.2"
actix-web-codegen = "0.1.0-alpha.1"
actix-http = { version = "0.1.0-alpha.2", features=["fail"] }
actix-server = "0.4.1"
actix-http = { version = "0.1.0-alpha.4", features=["fail"] }
actix-server = "0.4.2"
actix-server-config = "0.1.0"
actix-threadpool = "0.1.0"
awc = { version = "0.1.0-alpha.2", optional = true }
awc = { version = "0.1.0-alpha.4", optional = true }
bytes = "0.4"
derive_more = "0.14"
encoding = "0.2"
futures = "0.1"
hashbrown = "0.1.8"
httparse = "1.3"
log = "0.4"
mime = "0.3"
net2 = "0.2.33"
@@ -100,8 +101,8 @@ openssl = { version="0.10", optional = true }
rustls = { version = "^0.15", optional = true }
[dev-dependencies]
actix-http = { version = "0.1.0-alpha.2", features=["ssl", "brotli", "flate2-zlib"] }
actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] }
actix-http = { version = "0.1.0-alpha.4", features=["ssl", "brotli", "flate2-zlib"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
rand = "0.6"
env_logger = "0.6"
serde_derive = "1.0"
@@ -113,3 +114,14 @@ flate2 = "1.0.2"
lto = true
opt-level = 3
codegen-units = 1
[patch.crates-io]
actix = { git = "https://github.com/actix/actix.git" }
actix-web = { path = "." }
actix-http = { path = "actix-http" }
actix-http-test = { path = "test-server" }
actix-web-codegen = { path = "actix-web-codegen" }
actix-web-actors = { path = "actix-web-actors" }
actix-session = { path = "actix-session" }
actix-files = { path = "actix-files" }
awc = { path = "awc" }

View File

@@ -1,3 +1,58 @@
## 1.0
* `State` is now `Data`. You register Data during the App initialization process
and then access it from handlers either using a Data extractor or using
HttpRequest's api.
instead of
```rust
App.with_state(T)
```
use App's `data` method
```rust
App.new()
.data(T)
```
and either use the Data extractor within your handler
```rust
use actix_web::web::Data;
fn endpoint_handler(Data<T>)){
...
}
```
.. or access your Data element from the HttpRequest
```rust
fn endpoint_handler(req: HttpRequest) {
let data: Option<Data<T>> = req.app_data::<T>();
}
```
* AsyncResponder is deprecated.
instead of
```rust
use actix_web::AsyncResponder;
fn endpoint_handler(...) -> impl Future<Item=HttpResponse, Error=Error>{
...
.responder()
}
```
.. simply omit AsyncResponder and the corresponding responder() finish method
## 0.7.15
* The `' '` character is not percent decoded anymore before matching routes. If you need to use it in

View File

@@ -19,7 +19,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* [User Guide](https://actix.rs/docs/)
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
* [API Documentation (Releases)](https://docs.rs/actix-web/0.7.18/actix_web/)
* [API Documentation (0.7 Release)](https://docs.rs/actix-web/0.7.19/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.32 or later

View File

@@ -1,5 +1,15 @@
# Changes
## [0.1.0-alpha.4] - 2019-04-08
* Update actix-web to alpha4
## [0.1.0-alpha.2] - 2019-04-02
* Add default handler support
## [0.1.0-alpha.1] - 2019-03-28
* Initial impl

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-files"
version = "0.1.0-alpha.2"
version = "0.1.0-alpha.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for actix web."
readme = "README.md"
@@ -18,7 +18,7 @@ name = "actix_files"
path = "src/lib.rs"
[dependencies]
actix-web = "1.0.0-alpha.2"
actix-web = "1.0.0-alpha.4"
actix-service = "0.3.4"
bitflags = "1"
bytes = "0.4"
@@ -31,4 +31,4 @@ percent-encoding = "1.0"
v_htmlescape = "0.4"
[dev-dependencies]
actix-web = { version = "1.0.0-alpha.2", features=["ssl"] }
actix-web = { version = "1.0.0-alpha.4", features=["ssl"] }

View File

@@ -7,22 +7,22 @@ use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::{cmp, io};
use bytes::Bytes;
use futures::{Async, Future, Poll, Stream};
use mime;
use mime_guess::get_mime_type;
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use v_htmlescape::escape as escape_html_entity;
use actix_service::{boxed::BoxedNewService, NewService, Service};
use actix_service::boxed::{self, BoxedNewService, BoxedService};
use actix_service::{IntoNewService, NewService, Service};
use actix_web::dev::{
HttpServiceFactory, ResourceDef, ServiceConfig, ServiceFromRequest, ServiceRequest,
HttpServiceFactory, Payload, ResourceDef, ServiceConfig, ServiceRequest,
ServiceResponse,
};
use actix_web::error::{BlockingError, Error, ErrorInternalServerError};
use actix_web::http::header::DispositionType;
use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder};
use futures::future::{ok, FutureResult};
use bytes::Bytes;
use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, Poll, Stream};
use mime;
use mime_guess::get_mime_type;
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use v_htmlescape::escape as escape_html_entity;
mod error;
mod named;
@@ -32,6 +32,7 @@ use self::error::{FilesError, UriSegmentError};
pub use crate::named::NamedFile;
pub use crate::range::HttpRange;
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>;
type HttpNewService<P> =
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
@@ -232,8 +233,6 @@ pub struct Files<S> {
default: Rc<RefCell<Option<Rc<HttpNewService<S>>>>>,
renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>,
_chunk_size: usize,
_follow_symlinks: bool,
file_flags: named::Flags,
}
@@ -245,8 +244,6 @@ impl<S> Clone for Files<S> {
show_index: self.show_index,
default: self.default.clone(),
renderer: self.renderer.clone(),
_chunk_size: self._chunk_size,
_follow_symlinks: self._follow_symlinks,
file_flags: self.file_flags,
path: self.path.clone(),
mime_override: self.mime_override.clone(),
@@ -274,8 +271,6 @@ impl<S: 'static> Files<S> {
default: Rc::new(RefCell::new(None)),
renderer: Rc::new(directory_listing),
mime_override: None,
_chunk_size: 0,
_follow_symlinks: false,
file_flags: named::Flags::default(),
}
}
@@ -334,6 +329,24 @@ impl<S: 'static> Files<S> {
self.file_flags.set(named::Flags::LAST_MD, value);
self
}
/// Sets default handler which is used when no matched file could be found.
pub fn default_handler<F, U>(mut self, f: F) -> Self
where
F: IntoNewService<U>,
U: NewService<
Request = ServiceRequest<S>,
Response = ServiceResponse,
Error = Error,
> + 'static,
{
// create and configure default resource
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service(
f.into_new_service().map_init_err(|_| ()),
)))));
self
}
}
impl<P> HttpServiceFactory<P> for Files<P>
@@ -353,41 +366,95 @@ where
}
}
impl<P> NewService for Files<P> {
impl<P: 'static> NewService for Files<P> {
type Request = ServiceRequest<P>;
type Response = ServiceResponse;
type Error = Error;
type Service = Self;
type Service = FilesService<P>;
type InitError = ();
type Future = FutureResult<Self::Service, Self::InitError>;
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
fn new_service(&self, _: &()) -> Self::Future {
ok(self.clone())
let mut srv = FilesService {
directory: self.directory.clone(),
index: self.index.clone(),
show_index: self.show_index,
default: None,
renderer: self.renderer.clone(),
mime_override: self.mime_override.clone(),
file_flags: self.file_flags,
};
if let Some(ref default) = *self.default.borrow() {
Box::new(
default
.new_service(&())
.map(move |default| {
srv.default = Some(default);
srv
})
.map_err(|_| ()),
)
} else {
Box::new(ok(srv))
}
}
}
impl<P> Service for Files<P> {
pub struct FilesService<P> {
directory: PathBuf,
index: Option<String>,
show_index: bool,
default: Option<HttpService<P>>,
renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags,
}
impl<P> FilesService<P> {
fn handle_err(
&mut self,
e: io::Error,
req: HttpRequest,
payload: Payload<P>,
) -> Either<
FutureResult<ServiceResponse, Error>,
Box<Future<Item = ServiceResponse, Error = Error>>,
> {
log::debug!("Files: Failed to handle {}: {}", req.path(), e);
if let Some(ref mut default) = self.default {
default.call(ServiceRequest::from_parts(req, payload))
} else {
Either::A(ok(ServiceResponse::from_err(e, req.clone())))
}
}
}
impl<P> Service for FilesService<P> {
type Request = ServiceRequest<P>;
type Response = ServiceResponse;
type Error = Error;
type Future = FutureResult<Self::Response, Self::Error>;
type Future = Either<
FutureResult<Self::Response, Self::Error>,
Box<Future<Item = Self::Response, Error = Self::Error>>,
>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
let (req, _) = req.into_parts();
let (req, pl) = req.into_parts();
let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) {
Ok(item) => item,
Err(e) => return ok(ServiceResponse::from_err(e, req.clone())),
Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req.clone()))),
};
// full filepath
let path = match self.directory.join(&real_path.0).canonicalize() {
Ok(path) => path,
Err(e) => return ok(ServiceResponse::from_err(e, req.clone())),
Err(e) => return self.handle_err(e, req, pl),
};
if path.is_dir() {
@@ -403,25 +470,25 @@ impl<P> Service for Files<P> {
}
named_file.flags = self.file_flags;
match named_file.respond_to(&req) {
Ok(item) => ok(ServiceResponse::new(req.clone(), item)),
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
}
Either::A(ok(match named_file.respond_to(&req) {
Ok(item) => ServiceResponse::new(req.clone(), item),
Err(e) => ServiceResponse::from_err(e, req.clone()),
}))
}
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
Err(e) => return self.handle_err(e, req, pl),
}
} else if self.show_index {
let dir = Directory::new(self.directory.clone(), path);
let x = (self.renderer)(&dir, &req);
match x {
Ok(resp) => ok(resp),
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
Ok(resp) => Either::A(ok(resp)),
Err(e) => return self.handle_err(e, req, pl),
}
} else {
ok(ServiceResponse::from_err(
Either::A(ok(ServiceResponse::from_err(
FilesError::IsDirectory,
req.clone(),
))
)))
}
} else {
match NamedFile::open(path) {
@@ -434,11 +501,15 @@ impl<P> Service for Files<P> {
named_file.flags = self.file_flags;
match named_file.respond_to(&req) {
Ok(item) => ok(ServiceResponse::new(req.clone(), item)),
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
Ok(item) => {
Either::A(ok(ServiceResponse::new(req.clone(), item)))
}
Err(e) => {
Either::A(ok(ServiceResponse::from_err(e, req.clone())))
}
}
}
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
Err(e) => self.handle_err(e, req, pl),
}
}
}
@@ -480,7 +551,7 @@ impl<P> FromRequest<P> for PathBufWrp {
type Error = UriSegmentError;
type Future = Result<Self, Self::Error>;
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
PathBufWrp::get_pathbuf(req.match_info().path())
}
}
@@ -495,7 +566,10 @@ mod tests {
use bytes::BytesMut;
use super::*;
use actix_web::http::{header, header::DispositionType, Method, StatusCode};
use actix_web::http::header::{
self, ContentDisposition, DispositionParam, DispositionType,
};
use actix_web::http::{Method, StatusCode};
use actix_web::test::{self, TestRequest};
use actix_web::App;
@@ -612,7 +686,6 @@ mod tests {
#[test]
fn test_named_file_image_attachment() {
use header::{ContentDisposition, DispositionParam, DispositionType};
let cd = ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![DispositionParam::Filename(String::from("test.png"))],
@@ -890,20 +963,50 @@ mod tests {
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
}
// #[test]
// fn test_named_file_content_encoding() {
// let req = TestRequest::default().method(Method::GET).finish();
// let file = NamedFile::open("Cargo.toml").unwrap();
#[test]
fn test_named_file_content_encoding() {
let mut srv = test::init_service(App::new().enable_encoding().service(
web::resource("/").to(|| {
NamedFile::open("Cargo.toml")
.unwrap()
.set_content_encoding(header::ContentEncoding::Identity)
}),
));
// assert!(file.encoding.is_none());
// let resp = file
// .set_content_encoding(ContentEncoding::Identity)
// .respond_to(&req)
// .unwrap();
let request = TestRequest::get()
.uri("/")
.header(header::ACCEPT_ENCODING, "gzip")
.to_request();
let res = test::call_success(&mut srv, request);
assert_eq!(res.status(), StatusCode::OK);
assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
}
// assert!(resp.content_encoding().is_some());
// assert_eq!(resp.content_encoding().unwrap().as_str(), "identity");
// }
#[test]
fn test_named_file_content_encoding_gzip() {
let mut srv = test::init_service(App::new().enable_encoding().service(
web::resource("/").to(|| {
NamedFile::open("Cargo.toml")
.unwrap()
.set_content_encoding(header::ContentEncoding::Gzip)
}),
));
let request = TestRequest::get()
.uri("/")
.header(header::ACCEPT_ENCODING, "gzip")
.to_request();
let res = test::call_success(&mut srv, request);
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers()
.get(header::CONTENT_ENCODING)
.unwrap()
.to_str()
.unwrap(),
"gzip"
);
}
#[test]
fn test_named_file_allowed_method() {
@@ -954,22 +1057,29 @@ mod tests {
let _st: Files<()> = Files::new("/", "Cargo.toml");
}
// #[test]
// fn test_default_handler_file_missing() {
// let st = Files::new(".")
// .default_handler(|_: &_| "default content");
// let req = TestRequest::with_uri("/missing")
// .param("tail", "missing")
// .finish();
#[test]
fn test_default_handler_file_missing() {
let mut st = test::block_on(
Files::new("/", ".")
.default_handler(|req: ServiceRequest<_>| {
Ok(req.into_response(HttpResponse::Ok().body("default content")))
})
.new_service(&()),
)
.unwrap();
let req = TestRequest::with_uri("/missing").to_srv_request();
// let resp = st.handle(&req).respond_to(&req).unwrap();
// let resp = resp.as_msg();
// assert_eq!(resp.status(), StatusCode::OK);
// assert_eq!(
// resp.body(),
// &Body::Binary(Binary::Slice(b"default content"))
// );
// }
let mut resp = test::call_success(&mut st, req);
assert_eq!(resp.status(), StatusCode::OK);
let bytes =
test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| {
b.extend(c);
Ok::<_, Error>(b)
}))
.unwrap();
assert_eq!(bytes.freeze(), Bytes::from_static(b"default content"));
}
// #[test]
// fn test_serve_index() {

View File

@@ -43,7 +43,7 @@ pub struct NamedFile {
pub(crate) content_disposition: header::ContentDisposition,
pub(crate) md: Metadata,
modified: Option<SystemTime>,
encoding: Option<ContentEncoding>,
pub(crate) encoding: Option<ContentEncoding>,
pub(crate) status_code: StatusCode,
pub(crate) flags: Flags,
}
@@ -296,10 +296,9 @@ impl Responder for NamedFile {
header::CONTENT_DISPOSITION,
self.content_disposition.to_string(),
);
// TODO blocking by compressing
// if let Some(current_encoding) = self.encoding {
// resp.content_encoding(current_encoding);
// }
if let Some(current_encoding) = self.encoding {
resp.encoding(current_encoding);
}
let reader = ChunkedReadFile {
size: self.md.len(),
offset: 0,
@@ -345,7 +344,7 @@ impl Responder for NamedFile {
// check last modified
let not_modified = if !none_match(etag.as_ref(), req) {
true
} else if req.headers().contains_key(header::IF_NONE_MATCH) {
} else if req.headers().contains_key(&header::IF_NONE_MATCH) {
false
} else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) =
(last_modified, req.get_header())
@@ -379,7 +378,7 @@ impl Responder for NamedFile {
let mut offset = 0;
// check for range header
if let Some(ranges) = req.headers().get(header::RANGE) {
if let Some(ranges) = req.headers().get(&header::RANGE) {
if let Ok(rangesheader) = ranges.to_str() {
if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) {
length = rangesvec[0].length;

View File

@@ -1,5 +1,39 @@
# Changes
## [0.1.0-alpha.4] - 2019-04-08
### Added
* Add minimal `std::error::Error` impl for `Error`
### Changed
* Export IntoHeaderValue
* Render error and return as response body
* Use thread pool for response body comression
### Deleted
* Removed PayloadBuffer
## [0.1.0-alpha.3] - 2019-04-02
### Added
* Warn when an unsealed private cookie isn't valid UTF-8
### Fixed
* Rust 1.31.0 compatibility
* Preallocate read buffer for h1 codec
* Detect socket disconnection during protocol selection
## [0.1.0-alpha.2] - 2019-03-29
### Added

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "0.1.0-alpha.2"
version = "0.1.0-alpha.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http primitives"
readme = "README.md"
@@ -48,10 +48,10 @@ fail = ["failure"]
secure-cookies = ["ring"]
[dependencies]
actix-service = "0.3.4"
actix-service = "0.3.6"
actix-codec = "0.1.2"
actix-connect = "0.1.0"
actix-utils = "0.3.4"
actix-connect = "0.1.2"
actix-utils = "0.3.5"
actix-server-config = "0.1.0"
actix-threadpool = "0.1.0"
@@ -59,7 +59,9 @@ base64 = "0.10"
bitflags = "1.0"
bytes = "0.4"
byteorder = "1.2"
copyless = "0.1.2"
derive_more = "0.14"
either = "1.5.2"
encoding = "0.2"
futures = "0.1"
hashbrown = "0.1.8"
@@ -100,7 +102,7 @@ openssl = { version="0.10", optional = true }
actix-rt = "0.2.2"
actix-server = { version = "0.4.1", features=["ssl"] }
actix-connect = { version = "0.1.0", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
env_logger = "0.6"
serde_derive = "1.0"
openssl = { version="0.10" }

View File

@@ -1,28 +0,0 @@
use std::{env, io};
use actix_codec::Framed;
use actix_http::{h1, Response, SendResponse, ServiceConfig};
use actix_server::{Io, Server};
use actix_service::{fn_service, NewService};
use actix_utils::framed::IntoFramed;
use actix_utils::stream::TakeItem;
use futures::Future;
use tokio_tcp::TcpStream;
fn main() -> io::Result<()> {
env::set_var("RUST_LOG", "framed_hello=info");
env_logger::init();
Server::build()
.bind("framed_hello", "127.0.0.1:8080", || {
fn_service(|io: Io<TcpStream>| Ok(io.into_parts().0))
.and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())))
.and_then(TakeItem::new().map_err(|_| ()))
.and_then(|(_req, _framed): (_, Framed<_, _>)| {
SendResponse::send(_framed, Response::Ok().body("Hello world!"))
.map_err(|_| ())
.map(|_| ())
})
})?
.run()
}

View File

@@ -153,7 +153,7 @@ impl MessageBody for Body {
if len == 0 {
Ok(Async::Ready(None))
} else {
Ok(Async::Ready(Some(bin.split_to(len))))
Ok(Async::Ready(Some(mem::replace(bin, Bytes::new()))))
}
}
Body::Message(ref mut body) => body.poll_next(),
@@ -359,7 +359,7 @@ where
}
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
self.stream.poll().map_err(|e| e.into())
self.stream.poll().map_err(std::convert::Into::into)
}
}

View File

@@ -1,45 +1,57 @@
use std::fmt::Debug;
use std::fmt;
use std::marker::PhantomData;
use actix_server_config::ServerConfig as SrvConfig;
use actix_service::{IntoNewService, NewService};
use actix_service::{IntoNewService, NewService, Service};
use crate::body::MessageBody;
use crate::config::{KeepAlive, ServiceConfig};
use crate::error::Error;
use crate::h1::{ExpectHandler, H1Service};
use crate::h2::H2Service;
use crate::request::Request;
use crate::response::Response;
use crate::h1::H1Service;
use crate::h2::H2Service;
use crate::service::HttpService;
/// A http service builder
///
/// This type can be used to construct an instance of `http service` through a
/// builder-like pattern.
pub struct HttpServiceBuilder<T, S> {
pub struct HttpServiceBuilder<T, S, X = ExpectHandler> {
keep_alive: KeepAlive,
client_timeout: u64,
client_disconnect: u64,
expect: X,
_t: PhantomData<(T, S)>,
}
impl<T, S> HttpServiceBuilder<T, S>
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler>
where
S: NewService<SrvConfig, Request = Request>,
S::Error: Debug + 'static,
S::Service: 'static,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
{
/// Create instance of `ServiceConfigBuilder`
pub fn new() -> HttpServiceBuilder<T, S> {
pub fn new() -> Self {
HttpServiceBuilder {
keep_alive: KeepAlive::Timeout(5),
client_timeout: 5000,
client_disconnect: 0,
expect: ExpectHandler,
_t: PhantomData,
}
}
}
impl<T, S, X> HttpServiceBuilder<T, S, X>
where
S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
{
/// Set server keep-alive setting.
///
/// By default keep alive is set to a 5 seconds.
@@ -95,10 +107,12 @@ where
// }
/// Finish service configuration and create *http service* for HTTP/1 protocol.
pub fn h1<F, P, B>(self, service: F) -> H1Service<T, P, S, B>
pub fn h1<F, P, B>(self, service: F) -> H1Service<T, P, S, B, X>
where
B: MessageBody + 'static,
F: IntoNewService<S, SrvConfig>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
{
let cfg = ServiceConfig::new(
@@ -106,7 +120,7 @@ where
self.client_timeout,
self.client_disconnect,
);
H1Service::with_config(cfg, service.into_new_service())
H1Service::with_config(cfg, service.into_new_service()).expect(self.expect)
}
/// Finish service configuration and create *http service* for HTTP/2 protocol.
@@ -114,7 +128,10 @@ where
where
B: MessageBody + 'static,
F: IntoNewService<S, SrvConfig>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
<S::Service as Service>::Future: 'static,
{
let cfg = ServiceConfig::new(
self.keep_alive,
@@ -129,7 +146,10 @@ where
where
B: MessageBody + 'static,
F: IntoNewService<S, SrvConfig>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
<S::Service as Service>::Future: 'static,
{
let cfg = ServiceConfig::new(
self.keep_alive,

View File

@@ -69,7 +69,7 @@ where
}
}
impl<T: AsyncRead + AsyncWrite + 'static> IoConnection<T> {
impl<T: AsyncRead + AsyncWrite> IoConnection<T> {
pub(crate) fn new(
io: ConnectionType<T>,
created: time::Instant,

View File

@@ -21,8 +21,18 @@ use openssl::ssl::SslConnector;
#[cfg(not(feature = "ssl"))]
type SslConnector = ();
/// Http client connector builde instance.
/// `Connector` type uses builder-like pattern for connector service construction.
/// Manages http client network connectivity
/// The `Connector` type uses a builder-like combinator pattern for service
/// construction that finishes by calling the `.finish()` method.
///
/// ```rust,ignore
/// use std::time::Duration;
/// use actix_http::client::Connector;
///
/// let connector = Connector::new()
/// .timeout(Duration::from_secs(5))
/// .finish();
/// ```
pub struct Connector<T, U> {
connector: T,
timeout: Duration,
@@ -164,7 +174,9 @@ where
}
/// Finish configuration process and create connector service.
pub fn service(
/// The Connector builder always concludes by calling `finish()` last in
/// its combinator chain.
pub fn finish(
self,
) -> impl Service<Request = Uri, Response = impl Connection, Error = ConnectError> + Clone
{
@@ -253,6 +265,15 @@ where
}
}
}
#[doc(hidden)]
#[deprecated(since = "0.1.0-alpha4", note = "please use `.finish()` method")]
pub fn service(
self,
) -> impl Service<Request = Uri, Response = impl Connection, Error = ConnectError> + Clone
{
self.finish()
}
}
#[cfg(not(feature = "ssl"))]

View File

@@ -104,10 +104,9 @@ where
let (parts, body) = resp.into_parts();
let payload = if head_req { Payload::None } else { body.into() };
let mut head = ResponseHead::default();
let mut head = ResponseHead::new(parts.status);
head.version = parts.version;
head.status = parts.status;
head.headers = parts.headers;
head.headers = parts.headers.into();
Ok((head, payload))
})

View File

@@ -21,6 +21,7 @@ use tokio_timer::{sleep, Delay};
use super::connection::{ConnectionType, IoConnection};
use super::error::ConnectError;
#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq)]
pub enum Protocol {
Http1,

View File

@@ -470,7 +470,9 @@ impl<'a> Iterator for Iter<'a> {
#[cfg(test)]
mod test {
use super::{Cookie, CookieJar, Key};
#[cfg(feature = "secure-cookies")]
use super::Key;
use super::{Cookie, CookieJar};
#[test]
#[allow(deprecated)]

View File

@@ -59,7 +59,7 @@ mod parse;
#[macro_use]
mod secure;
#[cfg(feature = "secure-cookies")]
pub use secure::*;
pub use self::secure::*;
use std::borrow::Cow;
use std::fmt;
@@ -68,11 +68,11 @@ use std::str::FromStr;
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use time::{Duration, Tm};
pub use builder::CookieBuilder;
pub use draft::*;
pub use jar::{CookieJar, Delta, Iter};
use parse::parse_cookie;
pub use parse::ParseError;
pub use self::builder::CookieBuilder;
pub use self::draft::*;
pub use self::jar::{CookieJar, Delta, Iter};
use self::parse::parse_cookie;
pub use self::parse::ParseError;
#[derive(Debug, Clone)]
enum CookieStr {

View File

@@ -68,8 +68,8 @@ impl Key {
encryption_key.copy_from_slice(&both_keys[SIGNED_KEY_LEN..]);
Key {
signing_key: signing_key,
encryption_key: encryption_key,
signing_key,
encryption_key,
}
}

View File

@@ -1,3 +1,6 @@
use std::str;
use log::warn;
use ring::aead::{open_in_place, seal_in_place, Aad, Algorithm, Nonce, AES_256_GCM};
use ring::aead::{OpeningKey, SealingKey};
use ring::rand::{SecureRandom, SystemRandom};
@@ -34,7 +37,7 @@ impl<'a> PrivateJar<'a> {
let mut key_array = [0u8; KEY_LEN];
key_array.copy_from_slice(key.encryption());
PrivateJar {
parent: parent,
parent,
key: key_array,
}
}
@@ -57,9 +60,16 @@ impl<'a> PrivateJar<'a> {
let unsealed = open_in_place(&key, nonce, ad, 0, sealed)
.map_err(|_| "invalid key/nonce/value: bad seal")?;
::std::str::from_utf8(unsealed)
.map(|s| s.to_string())
.map_err(|_| "bad unsealed utf8")
if let Ok(unsealed_utf8) = str::from_utf8(unsealed) {
Ok(unsealed_utf8.to_string())
} else {
warn!(
"Private cookie does not have utf8 content!
It is likely the secret key used to encrypt them has been leaked.
Please change it as soon as possible."
);
Err("bad unsealed utf8")
}
}
/// Returns a reference to the `Cookie` inside this jar with the name `name`
@@ -147,34 +157,12 @@ impl<'a> PrivateJar<'a> {
/// Encrypts the cookie's value with
/// authenticated encryption assuring confidentiality, integrity, and authenticity.
fn encrypt_cookie(&self, cookie: &mut Cookie) {
let mut data;
let output_len = {
// Create the `SealingKey` structure.
let key = SealingKey::new(ALGO, &self.key).expect("sealing key creation");
// Create a vec to hold the [nonce | cookie value | overhead].
let overhead = ALGO.tag_len();
let cookie_val = cookie.value().as_bytes();
data = vec![0; NONCE_LEN + cookie_val.len() + overhead];
// Randomly generate the nonce, then copy the cookie value as input.
let (nonce, in_out) = data.split_at_mut(NONCE_LEN);
SystemRandom::new()
.fill(nonce)
.expect("couldn't random fill nonce");
in_out[..cookie_val.len()].copy_from_slice(cookie_val);
let nonce = Nonce::try_assume_unique_for_key(nonce)
.expect("invalid length of `nonce`");
// Use cookie's name as associated data to prevent value swapping.
let ad = Aad::from(cookie.name().as_bytes());
// Perform the actual sealing operation and get the output length.
seal_in_place(&key, nonce, ad, in_out, overhead).expect("in-place seal")
};
let name = cookie.name().as_bytes();
let value = cookie.value().as_bytes();
let data = encrypt_name_value(name, value, &self.key);
// Base64 encode the nonce and encrypted value.
let sealed_value = base64::encode(&data[..(NONCE_LEN + output_len)]);
let sealed_value = base64::encode(&data);
cookie.set_value(sealed_value);
}
@@ -206,9 +194,38 @@ impl<'a> PrivateJar<'a> {
}
}
fn encrypt_name_value(name: &[u8], value: &[u8], key: &[u8]) -> Vec<u8> {
// Create the `SealingKey` structure.
let key = SealingKey::new(ALGO, key).expect("sealing key creation");
// Create a vec to hold the [nonce | cookie value | overhead].
let overhead = ALGO.tag_len();
let mut data = vec![0; NONCE_LEN + value.len() + overhead];
// Randomly generate the nonce, then copy the cookie value as input.
let (nonce, in_out) = data.split_at_mut(NONCE_LEN);
SystemRandom::new()
.fill(nonce)
.expect("couldn't random fill nonce");
in_out[..value.len()].copy_from_slice(value);
let nonce =
Nonce::try_assume_unique_for_key(nonce).expect("invalid length of `nonce`");
// Use cookie's name as associated data to prevent value swapping.
let ad = Aad::from(name);
// Perform the actual sealing operation and get the output length.
let output_len =
seal_in_place(&key, nonce, ad, in_out, overhead).expect("in-place seal");
// Remove the overhead and return the sealed content.
data.truncate(NONCE_LEN + output_len);
data
}
#[cfg(test)]
mod test {
use super::{Cookie, CookieJar, Key};
use super::{encrypt_name_value, Cookie, CookieJar, Key};
#[test]
fn simple() {
@@ -223,4 +240,30 @@ mod test {
let mut jar = CookieJar::new();
assert_secure_behaviour!(jar, jar.private(&key));
}
#[test]
fn non_utf8() {
let key = Key::generate();
let mut jar = CookieJar::new();
let name = "malicious";
let mut assert_non_utf8 = |value: &[u8]| {
let sealed = encrypt_name_value(name.as_bytes(), value, &key.encryption());
let encoded = base64::encode(&sealed);
assert_eq!(
jar.private(&key).unseal(name, &encoded),
Err("bad unsealed utf8")
);
jar.add(Cookie::new(name, encoded));
assert_eq!(jar.private(&key).get(name), None);
};
assert_non_utf8(&[0x72, 0xfb, 0xdf, 0x74]); // rûst in ISO/IEC 8859-1
let mut malicious =
String::from(r#"{"id":"abc123??%X","admin":true}"#).into_bytes();
malicious[8] |= 0b1100_0000;
malicious[9] |= 0b1100_0000;
assert_non_utf8(&malicious);
}
}

View File

@@ -31,7 +31,7 @@ impl<'a> SignedJar<'a> {
#[doc(hidden)]
pub fn new(parent: &'a mut CookieJar, key: &Key) -> SignedJar<'a> {
SignedJar {
parent: parent,
parent,
key: SigningKey::new(HMAC_DIGEST, key.signing()),
}
}

View File

@@ -55,7 +55,7 @@ where
#[inline]
pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder<S> {
// check content-encoding
let encoding = if let Some(enc) = headers.get(CONTENT_ENCODING) {
let encoding = if let Some(enc) = headers.get(&CONTENT_ENCODING) {
if let Ok(enc) = enc.to_str() {
ContentEncoding::from(enc)
} else {

View File

@@ -1,13 +1,13 @@
//! Stream encoder
use std::io::{self, Write};
use bytes::Bytes;
use futures::{Async, Poll};
use actix_threadpool::{run, CpuFuture};
#[cfg(feature = "brotli")]
use brotli2::write::BrotliEncoder;
use bytes::Bytes;
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
use flate2::write::{GzEncoder, ZlibEncoder};
use futures::{Async, Future, Poll};
use crate::body::{Body, BodySize, MessageBody, ResponseBody};
use crate::http::header::{ContentEncoding, CONTENT_ENCODING};
@@ -16,9 +16,13 @@ use crate::{Error, ResponseHead};
use super::Writer;
const INPLACE: usize = 2049;
pub struct Encoder<B> {
eof: bool,
body: EncoderBody<B>,
encoder: Option<ContentEncoder>,
fut: Option<CpuFuture<ContentEncoder, io::Error>>,
}
impl<B: MessageBody> Encoder<B> {
@@ -27,73 +31,60 @@ impl<B: MessageBody> Encoder<B> {
head: &mut ResponseHead,
body: ResponseBody<B>,
) -> ResponseBody<Encoder<B>> {
let has_ce = head.headers().contains_key(CONTENT_ENCODING);
match body {
ResponseBody::Other(b) => match b {
Body::None => ResponseBody::Other(Body::None),
Body::Empty => ResponseBody::Other(Body::Empty),
Body::Bytes(buf) => {
if !(has_ce
|| encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto)
{
let mut enc = ContentEncoder::encoder(encoding).unwrap();
let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|| head.status == StatusCode::SWITCHING_PROTOCOLS
|| encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto);
// TODO return error!
let _ = enc.write(buf.as_ref());
let body = enc.finish().unwrap();
update_head(encoding, head);
ResponseBody::Other(Body::Bytes(body))
let body = match body {
ResponseBody::Other(b) => match b {
Body::None => return ResponseBody::Other(Body::None),
Body::Empty => return ResponseBody::Other(Body::Empty),
Body::Bytes(buf) => {
if can_encode {
EncoderBody::Bytes(buf)
} else {
ResponseBody::Other(Body::Bytes(buf))
}
}
Body::Message(stream) => {
if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS {
ResponseBody::Body(Encoder {
body: EncoderBody::Other(stream),
encoder: None,
})
} else {
update_head(encoding, head);
head.no_chunking(false);
ResponseBody::Body(Encoder {
body: EncoderBody::Other(stream),
encoder: ContentEncoder::encoder(encoding),
})
return ResponseBody::Other(Body::Bytes(buf));
}
}
Body::Message(stream) => EncoderBody::BoxedStream(stream),
},
ResponseBody::Body(stream) => {
if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS {
ResponseBody::Body(Encoder {
body: EncoderBody::Body(stream),
encoder: None,
})
} else {
update_head(encoding, head);
head.no_chunking(false);
ResponseBody::Body(Encoder {
body: EncoderBody::Body(stream),
encoder: ContentEncoder::encoder(encoding),
})
}
}
ResponseBody::Body(stream) => EncoderBody::Stream(stream),
};
if can_encode {
update_head(encoding, head);
head.no_chunking(false);
ResponseBody::Body(Encoder {
body,
eof: false,
fut: None,
encoder: ContentEncoder::encoder(encoding),
})
} else {
ResponseBody::Body(Encoder {
body,
eof: false,
fut: None,
encoder: None,
})
}
}
}
enum EncoderBody<B> {
Body(B),
Other(Box<dyn MessageBody>),
Bytes(Bytes),
Stream(B),
BoxedStream(Box<dyn MessageBody>),
}
impl<B: MessageBody> MessageBody for Encoder<B> {
fn length(&self) -> BodySize {
if self.encoder.is_none() {
match self.body {
EncoderBody::Body(ref b) => b.length(),
EncoderBody::Other(ref b) => b.length(),
EncoderBody::Bytes(ref b) => b.length(),
EncoderBody::Stream(ref b) => b.length(),
EncoderBody::BoxedStream(ref b) => b.length(),
}
} else {
BodySize::Stream
@@ -102,16 +93,47 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
loop {
if self.eof {
return Ok(Async::Ready(None));
}
if let Some(ref mut fut) = self.fut {
let mut encoder = futures::try_ready!(fut.poll());
let chunk = encoder.take();
self.encoder = Some(encoder);
self.fut.take();
if !chunk.is_empty() {
return Ok(Async::Ready(Some(chunk)));
}
}
let result = match self.body {
EncoderBody::Body(ref mut b) => b.poll_next()?,
EncoderBody::Other(ref mut b) => b.poll_next()?,
EncoderBody::Bytes(ref mut b) => {
if b.is_empty() {
Async::Ready(None)
} else {
Async::Ready(Some(std::mem::replace(b, Bytes::new())))
}
}
EncoderBody::Stream(ref mut b) => b.poll_next()?,
EncoderBody::BoxedStream(ref mut b) => b.poll_next()?,
};
match result {
Async::NotReady => return Ok(Async::NotReady),
Async::Ready(Some(chunk)) => {
if let Some(ref mut encoder) = self.encoder {
if encoder.write(&chunk)? {
return Ok(Async::Ready(Some(encoder.take())));
if let Some(mut encoder) = self.encoder.take() {
if chunk.len() < INPLACE {
encoder.write(&chunk)?;
let chunk = encoder.take();
self.encoder = Some(encoder);
if !chunk.is_empty() {
return Ok(Async::Ready(Some(chunk)));
}
} else {
self.fut = Some(run(move || {
encoder.write(&chunk)?;
Ok(encoder)
}));
}
} else {
return Ok(Async::Ready(Some(chunk)));
@@ -123,6 +145,7 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
if chunk.is_empty() {
return Ok(Async::Ready(None));
} else {
self.eof = true;
return Ok(Async::Ready(Some(chunk)));
}
} else {
@@ -203,11 +226,11 @@ impl ContentEncoder {
}
}
fn write(&mut self, data: &[u8]) -> Result<bool, io::Error> {
fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(!encoder.get_ref().buf.is_empty()),
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding br encoding: {}", err);
Err(err)
@@ -215,7 +238,7 @@ impl ContentEncoder {
},
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(!encoder.get_ref().buf.is_empty()),
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding gzip encoding: {}", err);
Err(err)
@@ -223,7 +246,7 @@ impl ContentEncoder {
},
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(!encoder.get_ref().buf.is_empty()),
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding deflate encoding: {}", err);
Err(err)

View File

@@ -18,8 +18,6 @@ use tokio_timer::Error as TimerError;
// re-export for convinience
pub use crate::cookie::ParseError as CookieParseError;
use crate::body::Body;
use crate::response::Response;
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
@@ -49,13 +47,6 @@ impl Error {
pub fn as_response_error(&self) -> &ResponseError {
self.cause.as_ref()
}
/// Converts error to a response instance and set error message as response body
pub fn response_with_message(self) -> Response {
let message = format!("{}", self);
let resp: Response = self.into();
resp.set_body(Body::from(message))
}
}
/// Error that can be converted to `Response`
@@ -80,6 +71,26 @@ impl fmt::Debug for Error {
}
}
impl From<()> for Error {
fn from(_: ()) -> Self {
Error::from(UnitError)
}
}
impl std::error::Error for Error {
fn description(&self) -> &str {
"actix-http::Error"
}
fn cause(&self) -> Option<&dyn std::error::Error> {
None
}
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
/// Convert `Error` to a `Response` instance
impl From<Error> for Response {
fn from(err: Error) -> Self {
@@ -106,6 +117,13 @@ impl<E: ResponseError> ResponseError for TimeoutError<E> {
}
}
#[derive(Debug, Display)]
#[display(fmt = "UnknownError")]
struct UnitError;
/// `InternalServerError` for `JsonError`
impl ResponseError for UnitError {}
/// `InternalServerError` for `JsonError`
impl ResponseError for JsonError {}
@@ -115,6 +133,10 @@ impl ResponseError for FormError {}
/// `InternalServerError` for `TimerError`
impl ResponseError for TimerError {}
#[cfg(feature = "ssl")]
/// `InternalServerError` for `SslError`
impl ResponseError for openssl::ssl::Error {}
/// Return `BAD_REQUEST` for `de::value::Error`
impl ResponseError for DeError {
fn error_response(&self) -> Response {
@@ -326,7 +348,7 @@ impl ResponseError for crate::cookie::ParseError {
/// A set of errors that can occur during dispatching http requests
pub enum DispatchError {
/// Service error
Service,
Service(Error),
/// An `io::Error` that occurred while trying to read or write to a network
/// stream.

View File

@@ -10,7 +10,7 @@ use http::header::{
use http::{Method, Version};
use super::decoder::{PayloadDecoder, PayloadItem, PayloadType};
use super::{decoder, encoder};
use super::{decoder, encoder, reserve_readbuf};
use super::{Message, MessageType};
use crate::body::BodySize;
use crate::config::ServiceConfig;
@@ -150,6 +150,7 @@ impl Decoder for ClientCodec {
} else {
self.inner.payload = None;
}
reserve_readbuf(src);
Ok(Some(req))
} else {
Ok(None)
@@ -168,7 +169,10 @@ impl Decoder for ClientPayloadCodec {
);
Ok(match self.inner.payload.as_mut().unwrap().decode(src)? {
Some(PayloadItem::Chunk(chunk)) => Some(Some(chunk)),
Some(PayloadItem::Chunk(chunk)) => {
reserve_readbuf(src);
Some(Some(chunk))
}
Some(PayloadItem::Eof) => {
self.inner.payload.take();
Some(None)

View File

@@ -22,8 +22,8 @@ use crate::response::Response;
bitflags! {
struct Flags: u8 {
const HEAD = 0b0000_0001;
const KEEPALIVE_ENABLED = 0b0000_1000;
const STREAM = 0b0001_0000;
const KEEPALIVE_ENABLED = 0b0000_0010;
const STREAM = 0b0000_0100;
}
}
@@ -31,7 +31,7 @@ const AVERAGE_HEADER_SIZE: usize = 30;
/// HTTP/1 Codec
pub struct Codec {
config: ServiceConfig,
pub(crate) config: ServiceConfig,
decoder: decoder::MessageDecoder<Request>,
payload: Option<PayloadDecoder>,
version: Version,
@@ -39,8 +39,8 @@ pub struct Codec {
// encoder part
flags: Flags,
headers_size: u32,
encoder: encoder::MessageEncoder<Response<()>>,
// headers_size: u32,
}
impl Default for Codec {
@@ -73,21 +73,30 @@ impl Codec {
ctype: ConnectionType::Close,
flags,
headers_size: 0,
// headers_size: 0,
encoder: encoder::MessageEncoder::default(),
}
}
#[inline]
/// Check if request is upgrade
pub fn upgrade(&self) -> bool {
self.ctype == ConnectionType::Upgrade
}
#[inline]
/// Check if last response is keep-alive
pub fn keepalive(&self) -> bool {
self.ctype == ConnectionType::KeepAlive
}
#[inline]
/// Check if keep-alive enabled on server level
pub fn keepalive_enabled(&self) -> bool {
self.flags.contains(Flags::KEEPALIVE_ENABLED)
}
#[inline]
/// Check last request's message type
pub fn message_type(&self) -> MessageType {
if self.flags.contains(Flags::STREAM) {
@@ -176,7 +185,7 @@ impl Encoder for Codec {
self.ctype,
&self.config,
)?;
self.headers_size = (dst.len() - len) as u32;
// self.headers_size = (dst.len() - len) as u32;
}
Message::Chunk(Some(bytes)) => {
self.encoder.encode_chunk(bytes.as_ref(), dst)?;

View File

@@ -5,11 +5,12 @@ use actix_codec::Decoder;
use bytes::{Bytes, BytesMut};
use futures::{Async, Poll};
use http::header::{HeaderName, HeaderValue};
use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version};
use http::{header, HttpTryFrom, Method, StatusCode, Uri, Version};
use httparse;
use log::{debug, error, trace};
use crate::error::ParseError;
use crate::header::HeaderMap;
use crate::message::{ConnectionType, ResponseHead};
use crate::request::Request;
@@ -51,6 +52,8 @@ pub(crate) enum PayloadLength {
pub(crate) trait MessageType: Sized {
fn set_connection_type(&mut self, ctype: Option<ConnectionType>);
fn set_expect(&mut self);
fn headers_mut(&mut self) -> &mut HeaderMap;
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError>;
@@ -62,6 +65,7 @@ pub(crate) trait MessageType: Sized {
) -> Result<PayloadLength, ParseError> {
let mut ka = None;
let mut has_upgrade = false;
let mut expect = false;
let mut chunked = false;
let mut content_length = None;
@@ -69,73 +73,81 @@ pub(crate) trait MessageType: Sized {
let headers = self.headers_mut();
for idx in raw_headers.iter() {
if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1])
{
// Unsafe: httparse check header value for valid utf-8
let value = unsafe {
HeaderValue::from_shared_unchecked(
slice.slice(idx.value.0, idx.value.1),
)
};
match name {
header::CONTENT_LENGTH => {
if let Ok(s) = value.to_str() {
if let Ok(len) = s.parse::<u64>() {
let name =
HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap();
// Unsafe: httparse check header value for valid utf-8
let value = unsafe {
HeaderValue::from_shared_unchecked(
slice.slice(idx.value.0, idx.value.1),
)
};
match name {
header::CONTENT_LENGTH => {
if let Ok(s) = value.to_str() {
if let Ok(len) = s.parse::<u64>() {
if len != 0 {
content_length = Some(len);
} else {
debug!("illegal Content-Length: {:?}", s);
return Err(ParseError::Header);
}
} else {
debug!("illegal Content-Length: {:?}", value);
debug!("illegal Content-Length: {:?}", s);
return Err(ParseError::Header);
}
} else {
debug!("illegal Content-Length: {:?}", value);
return Err(ParseError::Header);
}
// transfer-encoding
header::TRANSFER_ENCODING => {
if let Ok(s) = value.to_str().map(|s| s.trim()) {
chunked = s.eq_ignore_ascii_case("chunked");
} else {
return Err(ParseError::Header);
}
}
// transfer-encoding
header::TRANSFER_ENCODING => {
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 => {
ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim())
{
if conn.eq_ignore_ascii_case("keep-alive") {
Some(ConnectionType::KeepAlive)
} else if conn.eq_ignore_ascii_case("close") {
Some(ConnectionType::Close)
} else if conn.eq_ignore_ascii_case("upgrade") {
Some(ConnectionType::Upgrade)
} else {
None
}
}
// connection keep-alive state
header::CONNECTION => {
ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) {
if conn.eq_ignore_ascii_case("keep-alive") {
Some(ConnectionType::KeepAlive)
} else if conn.eq_ignore_ascii_case("close") {
Some(ConnectionType::Close)
} else if conn.eq_ignore_ascii_case("upgrade") {
Some(ConnectionType::Upgrade)
} else {
None
};
}
header::UPGRADE => {
has_upgrade = true;
// check content-length, some clients (dart)
// sends "content-length: 0" with websocket upgrade
if let Ok(val) = value.to_str().map(|val| val.trim()) {
if val.eq_ignore_ascii_case("websocket") {
content_length = None;
}
}
} else {
None
};
}
header::UPGRADE => {
has_upgrade = true;
// check content-length, some clients (dart)
// sends "content-length: 0" with websocket upgrade
if let Ok(val) = value.to_str().map(|val| val.trim()) {
if val.eq_ignore_ascii_case("websocket") {
content_length = None;
}
}
_ => (),
}
headers.append(name, value);
} else {
return Err(ParseError::Header);
header::EXPECT => {
let bytes = value.as_bytes();
if bytes.len() >= 4 && &bytes[0..4] == b"100-" {
expect = true;
}
}
_ => (),
}
headers.append(name, value);
}
}
self.set_connection_type(ka);
if expect {
self.set_expect()
}
// https://tools.ietf.org/html/rfc7230#section-3.3.3
if chunked {
@@ -163,6 +175,10 @@ impl MessageType for Request {
}
}
fn set_expect(&mut self) {
self.head_mut().set_expect();
}
fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.head_mut().headers
}
@@ -198,10 +214,10 @@ impl MessageType for Request {
let mut msg = Request::new();
// convert headers
let len = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?;
let length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?;
// payload decoder
let decoder = match len {
let decoder = match length {
PayloadLength::Payload(pl) => pl,
PayloadLength::Upgrade => {
// upgrade(websocket)
@@ -235,6 +251,8 @@ impl MessageType for ResponseHead {
}
}
fn set_expect(&mut self) {}
fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
@@ -266,13 +284,14 @@ impl MessageType for ResponseHead {
}
};
let mut msg = ResponseHead::default();
let mut msg = ResponseHead::new(status);
msg.version = ver;
// convert headers
let len = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?;
let length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?;
// message payload
let decoder = if let PayloadLength::Payload(pl) = len {
let decoder = if let PayloadLength::Payload(pl) = length {
pl
} else if status == StatusCode::SWITCHING_PROTOCOLS {
// switching protocol or connect
@@ -284,9 +303,6 @@ impl MessageType for ResponseHead {
PayloadType::None
};
msg.status = status;
msg.version = ver;
Ok(Some((msg, decoder)))
}
}
@@ -610,6 +626,7 @@ mod tests {
use super::*;
use crate::error::ParseError;
use crate::http::header::{HeaderName, SET_COOKIE};
use crate::httpmessage::HttpMessage;
impl PayloadType {
@@ -770,7 +787,13 @@ mod tests {
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(*req.method(), Method::GET);
assert_eq!(req.path(), "/test");
assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value");
assert_eq!(
req.headers()
.get(HeaderName::try_from("test").unwrap())
.unwrap()
.as_bytes(),
b"value"
);
}
#[test]
@@ -785,12 +808,11 @@ mod tests {
let val: Vec<_> = req
.headers()
.get_all("Set-Cookie")
.iter()
.get_all(SET_COOKIE)
.map(|v| v.to_str().unwrap().to_owned())
.collect();
assert_eq!(val[0], "c1=cookie1");
assert_eq!(val[1], "c2=cookie2");
assert_eq!(val[1], "c1=cookie1");
assert_eq!(val[0], "c2=cookie2");
}
#[test]

View File

@@ -1,65 +1,79 @@
use std::collections::VecDeque;
use std::fmt::Debug;
use std::mem;
use std::time::Instant;
use std::{fmt, io};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder};
use actix_service::Service;
use actix_utils::cloneable::CloneableService;
use bitflags::bitflags;
use futures::{Async, Future, Poll, Sink, Stream};
use log::{debug, error, trace};
use bytes::{BufMut, BytesMut};
use futures::{Async, Future, Poll};
use log::{error, trace};
use tokio_timer::Delay;
use crate::body::{Body, BodySize, MessageBody, ResponseBody};
use crate::config::ServiceConfig;
use crate::error::DispatchError;
use crate::error::{DispatchError, Error};
use crate::error::{ParseError, PayloadError};
use crate::request::Request;
use crate::response::Response;
use super::codec::Codec;
use super::payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter};
use super::payload::{Payload, PayloadSender, PayloadStatus};
use super::{Message, MessageType};
const LW_BUFFER_SIZE: usize = 4096;
const HW_BUFFER_SIZE: usize = 32_768;
const MAX_PIPELINED_MESSAGES: usize = 16;
bitflags! {
pub struct Flags: u8 {
const STARTED = 0b0000_0001;
const KEEPALIVE_ENABLED = 0b0000_0010;
const KEEPALIVE = 0b0000_0100;
const POLLED = 0b0000_1000;
const SHUTDOWN = 0b0010_0000;
const DISCONNECTED = 0b0100_0000;
const DROPPING = 0b1000_0000;
const KEEPALIVE = 0b0000_0010;
const POLLED = 0b0000_0100;
const SHUTDOWN = 0b0000_1000;
const READ_DISCONNECT = 0b0001_0000;
const WRITE_DISCONNECT = 0b0010_0000;
const DROPPING = 0b0100_0000;
}
}
/// Dispatcher for HTTP/1.1 protocol
pub struct Dispatcher<T, S: Service<Request = Request> + 'static, B: MessageBody>
pub struct Dispatcher<T, S, B, X>
where
S::Error: Debug,
S: Service<Request = Request>,
S::Error: Into<Error>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
{
inner: Option<InnerDispatcher<T, S, B>>,
inner: Option<InnerDispatcher<T, S, B, X>>,
}
struct InnerDispatcher<T, S: Service<Request = Request> + 'static, B: MessageBody>
struct InnerDispatcher<T, S, B, X>
where
S::Error: Debug,
S: Service<Request = Request>,
S::Error: Into<Error>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
{
service: CloneableService<S>,
expect: CloneableService<X>,
flags: Flags,
framed: Framed<T, Codec>,
error: Option<DispatchError>,
config: ServiceConfig,
state: State<S, B>,
state: State<S, B, X>,
payload: Option<PayloadSender>,
messages: VecDeque<DispatcherMessage>,
ka_expire: Instant,
ka_timer: Option<Delay>,
io: T,
read_buf: BytesMut,
write_buf: BytesMut,
codec: Codec,
}
enum DispatcherMessage {
@@ -67,13 +81,24 @@ enum DispatcherMessage {
Error(Response<()>),
}
enum State<S: Service<Request = Request>, B: MessageBody> {
enum State<S, B, X>
where
S: Service<Request = Request>,
X: Service<Request = Request, Response = Request>,
B: MessageBody,
{
None,
ExpectCall(X::Future),
ServiceCall(S::Future),
SendPayload(ResponseBody<B>),
}
impl<S: Service<Request = Request>, B: MessageBody> State<S, B> {
impl<S, B, X> State<S, B, X>
where
S: Service<Request = Request>,
X: Service<Request = Request, Response = Request>,
B: MessageBody,
{
fn is_empty(&self) -> bool {
if let State::None = self {
true
@@ -81,36 +106,73 @@ impl<S: Service<Request = Request>, B: MessageBody> State<S, B> {
false
}
}
fn is_call(&self) -> bool {
if let State::ServiceCall(_) = self {
true
} else {
false
}
}
}
impl<T, S, B> Dispatcher<T, S, B>
impl<S, B, X> fmt::Debug for State<S, B, X>
where
T: AsyncRead + AsyncWrite,
S: Service<Request = Request> + 'static,
S::Error: Debug,
S::Response: Into<Response<B>>,
S: Service<Request = Request>,
X: Service<Request = Request, Response = Request>,
B: MessageBody,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
State::None => write!(f, "State::None"),
State::ExpectCall(_) => write!(f, "State::ExceptCall"),
State::ServiceCall(_) => write!(f, "State::ServiceCall"),
State::SendPayload(_) => write!(f, "State::SendPayload"),
}
}
}
impl<T, S, B, X> Dispatcher<T, S, B, X>
where
T: AsyncRead + AsyncWrite,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
{
/// Create http/1 dispatcher.
pub fn new(stream: T, config: ServiceConfig, service: CloneableService<S>) -> Self {
pub fn new(
stream: T,
config: ServiceConfig,
service: CloneableService<S>,
expect: CloneableService<X>,
) -> Self {
Dispatcher::with_timeout(
Framed::new(stream, Codec::new(config.clone())),
stream,
Codec::new(config.clone()),
config,
BytesMut::with_capacity(HW_BUFFER_SIZE),
None,
service,
expect,
)
}
/// Create http/1 dispatcher with slow request timeout.
pub fn with_timeout(
framed: Framed<T, Codec>,
io: T,
codec: Codec,
config: ServiceConfig,
read_buf: BytesMut,
timeout: Option<Delay>,
service: CloneableService<S>,
expect: CloneableService<X>,
) -> Self {
let keepalive = config.keep_alive_enabled();
let flags = if keepalive {
Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED
Flags::KEEPALIVE
} else {
Flags::empty()
};
@@ -126,14 +188,17 @@ where
Dispatcher {
inner: Some(InnerDispatcher {
framed,
io,
codec,
read_buf,
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
payload: None,
state: State::None,
error: None,
messages: VecDeque::new(),
service,
expect,
flags,
config,
ka_expire,
ka_timer,
}),
@@ -141,20 +206,20 @@ where
}
}
impl<T, S, B> InnerDispatcher<T, S, B>
impl<T, S, B, X> InnerDispatcher<T, S, B, X>
where
T: AsyncRead + AsyncWrite,
S: Service<Request = Request> + 'static,
S::Error: Debug,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
{
fn can_read(&self) -> bool {
if self.flags.contains(Flags::DISCONNECTED) {
return false;
}
if let Some(ref info) = self.payload {
if self.flags.contains(Flags::READ_DISCONNECT) {
false
} else if let Some(ref info) = self.payload {
info.need_read() == PayloadStatus::Read
} else {
true
@@ -163,41 +228,61 @@ where
// if checked is set to true, delay disconnect until all tasks have finished.
fn client_disconnected(&mut self) {
self.flags.insert(Flags::DISCONNECTED);
self.flags
.insert(Flags::READ_DISCONNECT | Flags::WRITE_DISCONNECT);
if let Some(mut payload) = self.payload.take() {
payload.set_error(PayloadError::Incomplete(None));
}
}
/// Flush stream
fn poll_flush(&mut self) -> Poll<bool, DispatchError> {
if !self.framed.is_write_buf_empty() {
match self.framed.poll_complete() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(err) => {
debug!("Error sending data: {}", err);
Err(err.into())
}
Ok(Async::Ready(_)) => {
// if payload is not consumed we can not use connection
if self.payload.is_some() && self.state.is_empty() {
return Err(DispatchError::PayloadIsNotConsumed);
}
Ok(Async::Ready(true))
}
}
} else {
Ok(Async::Ready(false))
///
/// true - got whouldblock
/// false - didnt get whouldblock
fn poll_flush(&mut self) -> Result<bool, DispatchError> {
if self.write_buf.is_empty() {
return Ok(false);
}
let len = self.write_buf.len();
let mut written = 0;
while written < len {
match self.io.write(&self.write_buf[written..]) {
Ok(0) => {
return Err(DispatchError::Io(io::Error::new(
io::ErrorKind::WriteZero,
"",
)));
}
Ok(n) => {
written += n;
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
if written > 0 {
let _ = self.write_buf.split_to(written);
}
return Ok(true);
}
Err(err) => return Err(DispatchError::Io(err)),
}
}
if written > 0 {
if written == self.write_buf.len() {
unsafe { self.write_buf.set_len(0) }
} else {
let _ = self.write_buf.split_to(written);
}
}
Ok(false)
}
fn send_response(
&mut self,
message: Response<()>,
body: ResponseBody<B>,
) -> Result<State<S, B>, DispatchError> {
self.framed
.force_send(Message::Item((message, body.length())))
) -> Result<State<S, B, X>, DispatchError> {
self.codec
.encode(Message::Item((message, body.length())), &mut self.write_buf)
.map_err(|err| {
if let Some(mut payload) = self.payload.take() {
payload.set_error(PayloadError::Incomplete(None));
@@ -205,91 +290,133 @@ where
DispatchError::Io(err)
})?;
self.flags
.set(Flags::KEEPALIVE, self.framed.get_codec().keepalive());
self.flags.set(Flags::KEEPALIVE, self.codec.keepalive());
match body.length() {
BodySize::None | BodySize::Empty => Ok(State::None),
_ => Ok(State::SendPayload(body)),
}
}
fn poll_response(&mut self) -> Result<(), DispatchError> {
let mut retry = self.can_read();
fn send_continue(&mut self) {
self.write_buf
.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
}
fn poll_response(&mut self) -> Result<bool, DispatchError> {
loop {
let state = match mem::replace(&mut self.state, State::None) {
let state = match self.state {
State::None => match self.messages.pop_front() {
Some(DispatcherMessage::Item(req)) => {
Some(self.handle_request(req)?)
}
Some(DispatcherMessage::Error(res)) => {
self.send_response(res, ResponseBody::Other(Body::Empty))?;
None
Some(self.send_response(res, ResponseBody::Other(Body::Empty))?)
}
None => None,
},
State::ServiceCall(mut fut) => match fut.poll() {
Ok(Async::Ready(res)) => {
let (res, body) = res.into().replace_body(());
Some(self.send_response(res, body)?)
State::ExpectCall(ref mut fut) => match fut.poll() {
Ok(Async::Ready(req)) => {
self.send_continue();
self.state = State::ServiceCall(self.service.call(req));
continue;
}
Ok(Async::NotReady) => {
self.state = State::ServiceCall(fut);
None
}
Err(_e) => {
let res: Response = Response::InternalServerError().finish();
Ok(Async::NotReady) => None,
Err(e) => {
let res: Response = e.into().into();
let (res, body) = res.replace_body(());
Some(self.send_response(res, body.into_body())?)
}
},
State::SendPayload(mut stream) => {
State::ServiceCall(ref mut fut) => match fut.poll() {
Ok(Async::Ready(res)) => {
let (res, body) = res.into().replace_body(());
self.state = self.send_response(res, body)?;
continue;
}
Ok(Async::NotReady) => None,
Err(e) => {
let res: Response = e.into().into();
let (res, body) = res.replace_body(());
Some(self.send_response(res, body.into_body())?)
}
},
State::SendPayload(ref mut stream) => {
loop {
if !self.framed.is_write_buf_full() {
if self.write_buf.len() < HW_BUFFER_SIZE {
match stream
.poll_next()
.map_err(|_| DispatchError::Unknown)?
{
Async::Ready(Some(item)) => {
self.framed
.force_send(Message::Chunk(Some(item)))?;
self.codec.encode(
Message::Chunk(Some(item)),
&mut self.write_buf,
)?;
continue;
}
Async::Ready(None) => {
self.framed.force_send(Message::Chunk(None))?;
}
Async::NotReady => {
self.state = State::SendPayload(stream);
return Ok(());
self.codec.encode(
Message::Chunk(None),
&mut self.write_buf,
)?;
self.state = State::None;
}
Async::NotReady => return Ok(false),
}
} else {
self.state = State::SendPayload(stream);
return Ok(());
return Ok(true);
}
break;
}
None
continue;
}
};
match state {
Some(state) => self.state = state,
None => {
// if read-backpressure is enabled and we consumed some data.
// we may read more data and retry
if !retry && self.can_read() && self.poll_request()? {
retry = self.can_read();
// set new state
if let Some(state) = state {
self.state = state;
if !self.state.is_empty() {
continue;
}
} else {
// if read-backpressure is enabled and we consumed some data.
// we may read more data and retry
if self.state.is_call() {
if self.poll_request()? {
continue;
}
break;
} else if !self.messages.is_empty() {
continue;
}
}
break;
}
Ok(())
Ok(false)
}
fn handle_request(&mut self, req: Request) -> Result<State<S, B>, DispatchError> {
fn handle_request(&mut self, req: Request) -> Result<State<S, B, X>, DispatchError> {
// Handle `EXPECT: 100-Continue` header
let req = if req.head().expect() {
let mut task = self.expect.call(req);
match task.poll() {
Ok(Async::Ready(req)) => {
self.send_continue();
req
}
Ok(Async::NotReady) => return Ok(State::ExpectCall(task)),
Err(e) => {
let e = e.into();
let res: Response = e.into();
let (res, body) = res.replace_body(());
return self.send_response(res, body.into_body());
}
}
} else {
req
};
// Call service
let mut task = self.service.call(req);
match task.poll() {
Ok(Async::Ready(res)) => {
@@ -297,8 +424,8 @@ where
self.send_response(res, body)
}
Ok(Async::NotReady) => Ok(State::ServiceCall(task)),
Err(_e) => {
let res: Response = Response::InternalServerError().finish();
Err(e) => {
let res: Response = e.into().into();
let (res, body) = res.replace_body(());
self.send_response(res, body.into_body())
}
@@ -308,20 +435,20 @@ where
/// Process one incoming requests
pub(self) fn poll_request(&mut self) -> Result<bool, DispatchError> {
// limit a mount of non processed requests
if self.messages.len() >= MAX_PIPELINED_MESSAGES {
if self.messages.len() >= MAX_PIPELINED_MESSAGES || !self.can_read() {
return Ok(false);
}
let mut updated = false;
loop {
match self.framed.poll() {
Ok(Async::Ready(Some(msg))) => {
match self.codec.decode(&mut self.read_buf) {
Ok(Some(msg)) => {
updated = true;
self.flags.insert(Flags::STARTED);
match msg {
Message::Item(mut req) => {
match self.framed.get_codec().message_type() {
match self.codec.message_type() {
MessageType::Payload | MessageType::Stream => {
let (ps, pl) = Payload::create(false);
let (req1, _) =
@@ -329,10 +456,6 @@ where
req = req1;
self.payload = Some(ps);
}
//MessageType::Stream => {
// self.unhandled = Some(req);
// return Ok(updated);
//}
_ => (),
}
@@ -350,7 +473,7 @@ where
error!(
"Internal server error: unexpected payload chunk"
);
self.flags.insert(Flags::DISCONNECTED);
self.flags.insert(Flags::READ_DISCONNECT);
self.messages.push_back(DispatcherMessage::Error(
Response::InternalServerError().finish().drop_body(),
));
@@ -363,7 +486,7 @@ where
payload.feed_eof();
} else {
error!("Internal server error: unexpected eof");
self.flags.insert(Flags::DISCONNECTED);
self.flags.insert(Flags::READ_DISCONNECT);
self.messages.push_back(DispatcherMessage::Error(
Response::InternalServerError().finish().drop_body(),
));
@@ -373,11 +496,7 @@ where
}
}
}
Ok(Async::Ready(None)) => {
self.client_disconnected();
break;
}
Ok(Async::NotReady) => break,
Ok(None) => break,
Err(ParseError::Io(e)) => {
self.client_disconnected();
self.error = Some(DispatchError::Io(e));
@@ -392,15 +511,15 @@ where
self.messages.push_back(DispatcherMessage::Error(
Response::BadRequest().finish().drop_body(),
));
self.flags.insert(Flags::DISCONNECTED);
self.flags.insert(Flags::READ_DISCONNECT);
self.error = Some(e.into());
break;
}
}
}
if self.ka_timer.is_some() && updated {
if let Some(expire) = self.config.keep_alive_expire() {
if updated && self.ka_timer.is_some() {
if let Some(expire) = self.codec.config.keep_alive_expire() {
self.ka_expire = expire;
}
}
@@ -412,10 +531,10 @@ where
if self.ka_timer.is_none() {
// shutdown timeout
if self.flags.contains(Flags::SHUTDOWN) {
if let Some(interval) = self.config.client_disconnect_timer() {
if let Some(interval) = self.codec.config.client_disconnect_timer() {
self.ka_timer = Some(Delay::new(interval));
} else {
self.flags.insert(Flags::DISCONNECTED);
self.flags.insert(Flags::READ_DISCONNECT);
return Ok(());
}
} else {
@@ -433,13 +552,14 @@ where
return Err(DispatchError::DisconnectTimeout);
} else if self.ka_timer.as_mut().unwrap().deadline() >= self.ka_expire {
// check for any outstanding tasks
if self.state.is_empty() && self.framed.is_write_buf_empty() {
if self.state.is_empty() && self.write_buf.is_empty() {
if self.flags.contains(Flags::STARTED) {
trace!("Keep-alive timeout, close connection");
self.flags.insert(Flags::SHUTDOWN);
// start shutdown timer
if let Some(deadline) = self.config.client_disconnect_timer()
if let Some(deadline) =
self.codec.config.client_disconnect_timer()
{
if let Some(timer) = self.ka_timer.as_mut() {
timer.reset(deadline);
@@ -447,7 +567,7 @@ where
}
} else {
// no shutdown timeout, drop socket
self.flags.insert(Flags::DISCONNECTED);
self.flags.insert(Flags::WRITE_DISCONNECT);
return Ok(());
}
} else {
@@ -464,7 +584,8 @@ where
self.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
self.state = State::None;
}
} else if let Some(deadline) = self.config.keep_alive_expire() {
} else if let Some(deadline) = self.codec.config.keep_alive_expire()
{
if let Some(timer) = self.ka_timer.as_mut() {
timer.reset(deadline);
let _ = timer.poll();
@@ -482,13 +603,15 @@ where
}
}
impl<T, S, B> Future for Dispatcher<T, S, B>
impl<T, S, B, X> Future for Dispatcher<T, S, B, X>
where
T: AsyncRead + AsyncWrite,
S: Service<Request = Request>,
S::Error: Debug,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
{
type Item = ();
type Error = DispatchError;
@@ -496,34 +619,60 @@ where
#[inline]
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let inner = self.inner.as_mut().unwrap();
inner.poll_keepalive()?;
if inner.flags.contains(Flags::SHUTDOWN) {
inner.poll_keepalive()?;
if inner.flags.contains(Flags::DISCONNECTED) {
if inner.flags.contains(Flags::WRITE_DISCONNECT) {
Ok(Async::Ready(()))
} else {
// try_ready!(inner.poll_flush());
match inner.framed.get_mut().shutdown()? {
Async::Ready(_) => Ok(Async::Ready(())),
Async::NotReady => Ok(Async::NotReady),
// flush buffer
inner.poll_flush()?;
if !inner.write_buf.is_empty() {
Ok(Async::NotReady)
} else {
match inner.io.shutdown()? {
Async::Ready(_) => Ok(Async::Ready(())),
Async::NotReady => Ok(Async::NotReady),
}
}
}
} else {
inner.poll_keepalive()?;
// read socket into a buf
if !inner.flags.contains(Flags::READ_DISCONNECT) {
if let Some(true) = read_available(&mut inner.io, &mut inner.read_buf)? {
inner.flags.insert(Flags::READ_DISCONNECT)
}
}
inner.poll_request()?;
loop {
inner.poll_response()?;
if let Async::Ready(false) = inner.poll_flush()? {
if inner.write_buf.remaining_mut() < LW_BUFFER_SIZE {
inner.write_buf.reserve(HW_BUFFER_SIZE);
}
let need_write = inner.poll_response()?;
// we didnt get WouldBlock from write operation,
// so data get written to kernel completely (OSX)
// and we have to write again otherwise response can get stuck
if inner.poll_flush()? || !need_write {
break;
}
}
if inner.flags.contains(Flags::DISCONNECTED) {
// client is gone
if inner.flags.contains(Flags::WRITE_DISCONNECT) {
return Ok(Async::Ready(()));
}
let is_empty = inner.state.is_empty();
// read half is closed and we do not processing any responses
if inner.flags.contains(Flags::READ_DISCONNECT) && is_empty {
inner.flags.insert(Flags::SHUTDOWN);
}
// keep-alive and stream errors
if inner.state.is_empty() && inner.framed.is_write_buf_empty() {
if is_empty && inner.write_buf.is_empty() {
if let Some(err) = inner.error.take() {
Err(err)
}
@@ -547,20 +696,61 @@ where
}
}
fn read_available<T>(io: &mut T, buf: &mut BytesMut) -> Result<Option<bool>, io::Error>
where
T: io::Read,
{
let mut read_some = false;
loop {
if buf.remaining_mut() < LW_BUFFER_SIZE {
buf.reserve(HW_BUFFER_SIZE);
}
let read = unsafe { io.read(buf.bytes_mut()) };
match read {
Ok(n) => {
if n == 0 {
return Ok(Some(true));
} else {
read_some = true;
unsafe {
buf.advance_mut(n);
}
}
}
Err(e) => {
return if e.kind() == io::ErrorKind::WouldBlock {
if read_some {
Ok(Some(false))
} else {
Ok(None)
}
} else if e.kind() == io::ErrorKind::ConnectionReset && read_some {
Ok(Some(true))
} else {
Err(e)
};
}
}
}
}
#[cfg(test)]
mod tests {
use std::{cmp, io};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_service::IntoService;
use bytes::{Buf, Bytes};
use bytes::{Buf, Bytes, BytesMut};
use futures::future::{lazy, ok};
use super::*;
use crate::error::Error;
use crate::h1::ExpectHandler;
struct Buffer {
buf: Bytes,
write_buf: BytesMut,
err: Option<io::Error>,
}
@@ -568,6 +758,7 @@ mod tests {
fn new(data: &'static str) -> Buffer {
Buffer {
buf: Bytes::from(data),
write_buf: BytesMut::new(),
err: None,
}
}
@@ -593,6 +784,7 @@ mod tests {
impl io::Write for Buffer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.write_buf.extend(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
@@ -620,16 +812,19 @@ mod tests {
CloneableService::new(
(|_| ok::<_, Error>(Response::Ok().finish())).into_service(),
),
CloneableService::new(ExpectHandler),
);
assert!(h1.poll().is_ok());
assert!(h1.poll().is_ok());
assert!(h1.poll().is_err());
assert!(h1
.inner
.as_ref()
.unwrap()
.flags
.contains(Flags::DISCONNECTED));
// assert_eq!(h1.tasks.len(), 1);
.contains(Flags::READ_DISCONNECT));
assert_eq!(
&h1.inner.as_ref().unwrap().io.write_buf[..26],
b"HTTP/1.1 400 Bad Request\r\n"
);
ok::<_, ()>(())
}));
}

View File

@@ -6,15 +6,15 @@ use std::str::FromStr;
use std::{cmp, fmt, io, mem};
use bytes::{BufMut, Bytes, BytesMut};
use http::header::{
HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use http::{HeaderMap, Method, StatusCode, Version};
use crate::body::BodySize;
use crate::config::ServiceConfig;
use crate::header::ContentEncoding;
use crate::header::{map, ContentEncoding};
use crate::helpers;
use crate::http::header::{
HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use crate::http::{HeaderMap, Method, StatusCode, Version};
use crate::message::{ConnectionType, Head, RequestHead, ResponseHead};
use crate::request::Request;
use crate::response::Response;
@@ -41,8 +41,6 @@ impl<T: MessageType> Default for MessageEncoder<T> {
pub(crate) trait MessageType: Sized {
fn status(&self) -> Option<StatusCode>;
// fn connection_type(&self) -> Option<ConnectionType>;
fn headers(&self) -> &HeaderMap;
fn chunked(&self) -> bool;
@@ -76,32 +74,31 @@ pub(crate) trait MessageType: Sized {
match length {
BodySize::Stream => {
if chunked {
dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n")
dst.put_slice(b"\r\ntransfer-encoding: chunked\r\n")
} else {
skip_len = false;
dst.extend_from_slice(b"\r\n");
dst.put_slice(b"\r\n");
}
}
BodySize::Empty => {
dst.extend_from_slice(b"\r\ncontent-length: 0\r\n");
dst.put_slice(b"\r\ncontent-length: 0\r\n");
}
BodySize::Sized(len) => helpers::write_content_length(len, dst),
BodySize::Sized64(len) => {
dst.extend_from_slice(b"\r\ncontent-length: ");
write!(dst.writer(), "{}", len)?;
dst.extend_from_slice(b"\r\n");
dst.put_slice(b"\r\ncontent-length: ");
write!(dst.writer(), "{}\r\n", len)?;
}
BodySize::None => dst.extend_from_slice(b"\r\n"),
BodySize::None => dst.put_slice(b"\r\n"),
}
// Connection
match ctype {
ConnectionType::Upgrade => dst.extend_from_slice(b"connection: upgrade\r\n"),
ConnectionType::Upgrade => dst.put_slice(b"connection: upgrade\r\n"),
ConnectionType::KeepAlive if version < Version::HTTP_11 => {
dst.extend_from_slice(b"connection: keep-alive\r\n")
dst.put_slice(b"connection: keep-alive\r\n")
}
ConnectionType::Close if version >= Version::HTTP_11 => {
dst.extend_from_slice(b"connection: close\r\n")
dst.put_slice(b"connection: close\r\n")
}
_ => (),
}
@@ -111,7 +108,7 @@ pub(crate) trait MessageType: Sized {
let mut has_date = false;
let mut remaining = dst.remaining_mut();
let mut buf = unsafe { &mut *(dst.bytes_mut() as *mut [u8]) };
for (key, value) in self.headers() {
for (key, value) in self.headers().inner.iter() {
match *key {
CONNECTION => continue,
TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue,
@@ -120,31 +117,59 @@ pub(crate) trait MessageType: Sized {
}
_ => (),
}
let v = value.as_ref();
let k = key.as_str().as_bytes();
let len = k.len() + v.len() + 4;
if len > remaining {
unsafe {
dst.advance_mut(pos);
match value {
map::Value::One(ref val) => {
let v = val.as_ref();
let len = k.len() + v.len() + 4;
if len > remaining {
unsafe {
dst.advance_mut(pos);
}
pos = 0;
dst.reserve(len * 2);
remaining = dst.remaining_mut();
unsafe {
buf = &mut *(dst.bytes_mut() as *mut _);
}
}
buf[pos..pos + k.len()].copy_from_slice(k);
pos += k.len();
buf[pos..pos + 2].copy_from_slice(b": ");
pos += 2;
buf[pos..pos + v.len()].copy_from_slice(v);
pos += v.len();
buf[pos..pos + 2].copy_from_slice(b"\r\n");
pos += 2;
remaining -= len;
}
pos = 0;
dst.reserve(len);
remaining = dst.remaining_mut();
unsafe {
buf = &mut *(dst.bytes_mut() as *mut _);
map::Value::Multi(ref vec) => {
for val in vec {
let v = val.as_ref();
let len = k.len() + v.len() + 4;
if len > remaining {
unsafe {
dst.advance_mut(pos);
}
pos = 0;
dst.reserve(len * 2);
remaining = dst.remaining_mut();
unsafe {
buf = &mut *(dst.bytes_mut() as *mut _);
}
}
buf[pos..pos + k.len()].copy_from_slice(k);
pos += k.len();
buf[pos..pos + 2].copy_from_slice(b": ");
pos += 2;
buf[pos..pos + v.len()].copy_from_slice(v);
pos += v.len();
buf[pos..pos + 2].copy_from_slice(b"\r\n");
pos += 2;
remaining -= len;
}
}
}
buf[pos..pos + k.len()].copy_from_slice(k);
pos += k.len();
buf[pos..pos + 2].copy_from_slice(b": ");
pos += 2;
buf[pos..pos + v.len()].copy_from_slice(v);
pos += v.len();
buf[pos..pos + 2].copy_from_slice(b"\r\n");
pos += 2;
remaining -= len;
}
unsafe {
dst.advance_mut(pos);
@@ -171,10 +196,6 @@ impl MessageType for Response<()> {
self.head().chunked()
}
//fn connection_type(&self) -> Option<ConnectionType> {
// self.head().ctype
//}
fn headers(&self) -> &HeaderMap {
&self.head().headers
}
@@ -186,7 +207,7 @@ impl MessageType for Response<()> {
// status line
helpers::write_status_line(head.version, head.status.as_u16(), dst);
dst.extend_from_slice(reason);
dst.put_slice(reason);
Ok(())
}
}
@@ -205,6 +226,7 @@ impl MessageType for RequestHead {
}
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> {
dst.reserve(256 + self.headers.len() * AVERAGE_HEADER_SIZE);
write!(
Writer(dst),
"{} {} {}",

View File

@@ -0,0 +1,36 @@
use actix_service::{NewService, Service};
use futures::future::{ok, FutureResult};
use futures::{Async, Poll};
use crate::error::Error;
use crate::request::Request;
pub struct ExpectHandler;
impl NewService for ExpectHandler {
type Request = Request;
type Response = Request;
type Error = Error;
type Service = ExpectHandler;
type InitError = Error;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &()) -> Self::Future {
ok(ExpectHandler)
}
}
impl Service for ExpectHandler {
type Request = Request;
type Response = Request;
type Error = Error;
type Future = FutureResult<Self::Response, Self::Error>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, req: Request) -> Self::Future {
ok(req)
}
}

View File

@@ -1,18 +1,20 @@
//! HTTP/1 implementation
use bytes::Bytes;
use bytes::{Bytes, BytesMut};
mod client;
mod codec;
mod decoder;
mod dispatcher;
mod encoder;
mod expect;
mod payload;
mod service;
pub use self::client::{ClientCodec, ClientPayloadCodec};
pub use self::codec::Codec;
pub use self::dispatcher::Dispatcher;
pub use self::payload::{Payload, PayloadBuffer};
pub use self::expect::ExpectHandler;
pub use self::payload::Payload;
pub use self::service::{H1Service, H1ServiceHandler, OneRequest};
#[derive(Debug)]
@@ -38,6 +40,16 @@ pub enum MessageType {
Stream,
}
const LW: usize = 2 * 1024;
const HW: usize = 32 * 1024;
pub(crate) fn reserve_readbuf(src: &mut BytesMut) {
let cap = src.capacity();
if cap < LW {
src.reserve(HW - cap);
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -1,10 +1,9 @@
//! Payload stream
use std::cell::RefCell;
use std::cmp;
use std::collections::VecDeque;
use std::rc::{Rc, Weak};
use bytes::{Bytes, BytesMut};
use bytes::Bytes;
use futures::task::current as current_task;
use futures::task::Task;
use futures::{Async, Poll, Stream};
@@ -15,7 +14,7 @@ use crate::error::PayloadError;
pub(crate) const MAX_BUFFER_SIZE: usize = 32_768;
#[derive(Debug, PartialEq)]
pub(crate) enum PayloadStatus {
pub enum PayloadStatus {
Read,
Pause,
Dropped,
@@ -78,14 +77,6 @@ impl Payload {
pub fn unread_data(&mut self, data: Bytes) {
self.inner.borrow_mut().unread_data(data);
}
#[inline]
/// Set read buffer capacity
///
/// Default buffer capacity is 32Kb.
pub fn set_read_buffer_capacity(&mut self, cap: usize) {
self.inner.borrow_mut().capacity = cap;
}
}
impl Stream for Payload {
@@ -98,58 +89,35 @@ impl Stream for Payload {
}
}
impl Clone for Payload {
fn clone(&self) -> Payload {
Payload {
inner: Rc::clone(&self.inner),
}
}
}
/// Payload writer interface.
pub(crate) trait PayloadWriter {
/// Set stream error.
fn set_error(&mut self, err: PayloadError);
/// Write eof into a stream which closes reading side of a stream.
fn feed_eof(&mut self);
/// Feed bytes into a payload stream
fn feed_data(&mut self, data: Bytes);
/// Need read data
fn need_read(&self) -> PayloadStatus;
}
/// Sender part of the payload stream
pub struct PayloadSender {
inner: Weak<RefCell<Inner>>,
}
impl PayloadWriter for PayloadSender {
impl PayloadSender {
#[inline]
fn set_error(&mut self, err: PayloadError) {
pub fn set_error(&mut self, err: PayloadError) {
if let Some(shared) = self.inner.upgrade() {
shared.borrow_mut().set_error(err)
}
}
#[inline]
fn feed_eof(&mut self) {
pub fn feed_eof(&mut self) {
if let Some(shared) = self.inner.upgrade() {
shared.borrow_mut().feed_eof()
}
}
#[inline]
fn feed_data(&mut self, data: Bytes) {
pub fn feed_data(&mut self, data: Bytes) {
if let Some(shared) = self.inner.upgrade() {
shared.borrow_mut().feed_data(data)
}
}
#[inline]
fn need_read(&self) -> PayloadStatus {
pub fn need_read(&self) -> PayloadStatus {
// we check need_read only if Payload (other side) is alive,
// otherwise always return true (consume payload)
if let Some(shared) = self.inner.upgrade() {
@@ -177,7 +145,6 @@ struct Inner {
err: Option<PayloadError>,
need_read: bool,
items: VecDeque<Bytes>,
capacity: usize,
task: Option<Task>,
io_task: Option<Task>,
}
@@ -190,7 +157,6 @@ impl Inner {
err: None,
items: VecDeque::new(),
need_read: true,
capacity: MAX_BUFFER_SIZE,
task: None,
io_task: None,
}
@@ -210,7 +176,7 @@ impl Inner {
fn feed_data(&mut self, data: Bytes) {
self.len += data.len();
self.items.push_back(data);
self.need_read = self.len < self.capacity;
self.need_read = self.len < MAX_BUFFER_SIZE;
if let Some(task) = self.task.take() {
task.notify()
}
@@ -224,7 +190,7 @@ impl Inner {
fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> {
if let Some(data) = self.items.pop_front() {
self.len -= data.len();
self.need_read = self.len < self.capacity;
self.need_read = self.len < MAX_BUFFER_SIZE;
if self.need_read && self.task.is_none() && !self.eof {
self.task = Some(current_task());
@@ -258,407 +224,12 @@ impl Inner {
}
}
/// Payload buffer
pub struct PayloadBuffer<S> {
len: usize,
items: VecDeque<Bytes>,
stream: S,
}
impl<S> PayloadBuffer<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
/// Create new `PayloadBuffer` instance
pub fn new(stream: S) -> Self {
PayloadBuffer {
len: 0,
items: VecDeque::new(),
stream,
}
}
/// Get mutable reference to an inner stream.
pub fn get_mut(&mut self) -> &mut S {
&mut self.stream
}
#[inline]
fn poll_stream(&mut self) -> Poll<bool, PayloadError> {
self.stream.poll().map(|res| match res {
Async::Ready(Some(data)) => {
self.len += data.len();
self.items.push_back(data);
Async::Ready(true)
}
Async::Ready(None) => Async::Ready(false),
Async::NotReady => Async::NotReady,
})
}
/// Read first available chunk of bytes
#[inline]
pub fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> {
if let Some(data) = self.items.pop_front() {
self.len -= data.len();
Ok(Async::Ready(Some(data)))
} else {
match self.poll_stream()? {
Async::Ready(true) => self.readany(),
Async::Ready(false) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
}
}
}
/// Check if buffer contains enough bytes
#[inline]
pub fn can_read(&mut self, size: usize) -> Poll<Option<bool>, PayloadError> {
if size <= self.len {
Ok(Async::Ready(Some(true)))
} else {
match self.poll_stream()? {
Async::Ready(true) => self.can_read(size),
Async::Ready(false) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
}
}
}
/// Return reference to the first chunk of data
#[inline]
pub fn get_chunk(&mut self) -> Poll<Option<&[u8]>, PayloadError> {
if self.items.is_empty() {
match self.poll_stream()? {
Async::Ready(true) => (),
Async::Ready(false) => return Ok(Async::Ready(None)),
Async::NotReady => return Ok(Async::NotReady),
}
}
match self.items.front().map(|c| c.as_ref()) {
Some(chunk) => Ok(Async::Ready(Some(chunk))),
None => Ok(Async::NotReady),
}
}
/// Read exact number of bytes
#[inline]
pub fn read_exact(&mut self, size: usize) -> Poll<Option<Bytes>, PayloadError> {
if size <= self.len {
self.len -= size;
let mut chunk = self.items.pop_front().unwrap();
if size < chunk.len() {
let buf = chunk.split_to(size);
self.items.push_front(chunk);
Ok(Async::Ready(Some(buf)))
} else if size == chunk.len() {
Ok(Async::Ready(Some(chunk)))
} else {
let mut buf = BytesMut::with_capacity(size);
buf.extend_from_slice(&chunk);
while buf.len() < size {
let mut chunk = self.items.pop_front().unwrap();
let rem = cmp::min(size - buf.len(), chunk.len());
buf.extend_from_slice(&chunk.split_to(rem));
if !chunk.is_empty() {
self.items.push_front(chunk);
}
}
Ok(Async::Ready(Some(buf.freeze())))
}
} else {
match self.poll_stream()? {
Async::Ready(true) => self.read_exact(size),
Async::Ready(false) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
}
}
}
/// Remove specified amount if bytes from buffer
#[inline]
pub fn drop_bytes(&mut self, size: usize) {
if size <= self.len {
self.len -= size;
let mut len = 0;
while len < size {
let mut chunk = self.items.pop_front().unwrap();
let rem = cmp::min(size - len, chunk.len());
len += rem;
if rem < chunk.len() {
chunk.split_to(rem);
self.items.push_front(chunk);
}
}
}
}
/// Copy buffered data
pub fn copy(&mut self, size: usize) -> Poll<Option<BytesMut>, PayloadError> {
if size <= self.len {
let mut buf = BytesMut::with_capacity(size);
for chunk in &self.items {
if buf.len() < size {
let rem = cmp::min(size - buf.len(), chunk.len());
buf.extend_from_slice(&chunk[..rem]);
}
if buf.len() == size {
return Ok(Async::Ready(Some(buf)));
}
}
}
match self.poll_stream()? {
Async::Ready(true) => self.copy(size),
Async::Ready(false) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
}
}
/// Read until specified ending
pub fn read_until(&mut self, line: &[u8]) -> Poll<Option<Bytes>, PayloadError> {
let mut idx = 0;
let mut num = 0;
let mut offset = 0;
let mut found = false;
let mut length = 0;
for no in 0..self.items.len() {
{
let chunk = &self.items[no];
for (pos, ch) in chunk.iter().enumerate() {
if *ch == line[idx] {
idx += 1;
if idx == line.len() {
num = no;
offset = pos + 1;
length += pos + 1;
found = true;
break;
}
} else {
idx = 0
}
}
if !found {
length += chunk.len()
}
}
if found {
let mut buf = BytesMut::with_capacity(length);
if num > 0 {
for _ in 0..num {
buf.extend_from_slice(&self.items.pop_front().unwrap());
}
}
if offset > 0 {
let mut chunk = self.items.pop_front().unwrap();
buf.extend_from_slice(&chunk.split_to(offset));
if !chunk.is_empty() {
self.items.push_front(chunk)
}
}
self.len -= length;
return Ok(Async::Ready(Some(buf.freeze())));
}
}
match self.poll_stream()? {
Async::Ready(true) => self.read_until(line),
Async::Ready(false) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
}
}
/// Read bytes until new line delimiter
pub fn readline(&mut self) -> Poll<Option<Bytes>, PayloadError> {
self.read_until(b"\n")
}
/// Put unprocessed data back to the buffer
pub fn unprocessed(&mut self, data: Bytes) {
self.len += data.len();
self.items.push_front(data);
}
/// Get remaining data from the buffer
pub fn remaining(&mut self) -> Bytes {
self.items
.iter_mut()
.fold(BytesMut::new(), |mut b, c| {
b.extend_from_slice(c);
b
})
.freeze()
}
}
#[cfg(test)]
mod tests {
use super::*;
use actix_rt::Runtime;
use futures::future::{lazy, result};
#[test]
fn test_basic() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (_, payload) = Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(payload.len, 0);
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
}
#[test]
fn test_eof() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (mut sender, payload) = Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
sender.feed_data(Bytes::from("data"));
sender.feed_eof();
assert_eq!(
Async::Ready(Some(Bytes::from("data"))),
payload.readany().ok().unwrap()
);
assert_eq!(payload.len, 0);
assert_eq!(Async::Ready(None), payload.readany().ok().unwrap());
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
}
#[test]
fn test_err() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (mut sender, payload) = Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
sender.set_error(PayloadError::Incomplete(None));
payload.readany().err().unwrap();
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
}
#[test]
fn test_readany() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (mut sender, payload) = Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
assert_eq!(
Async::Ready(Some(Bytes::from("line1"))),
payload.readany().ok().unwrap()
);
assert_eq!(payload.len, 0);
assert_eq!(
Async::Ready(Some(Bytes::from("line2"))),
payload.readany().ok().unwrap()
);
assert_eq!(payload.len, 0);
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
}
#[test]
fn test_readexactly() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (mut sender, payload) = Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap());
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
assert_eq!(
Async::Ready(Some(Bytes::from_static(b"li"))),
payload.read_exact(2).ok().unwrap()
);
assert_eq!(payload.len, 3);
assert_eq!(
Async::Ready(Some(Bytes::from_static(b"ne1l"))),
payload.read_exact(4).ok().unwrap()
);
assert_eq!(payload.len, 4);
sender.set_error(PayloadError::Incomplete(None));
payload.read_exact(10).err().unwrap();
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
}
#[test]
fn test_readuntil() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (mut sender, payload) = Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap());
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
assert_eq!(
Async::Ready(Some(Bytes::from("line"))),
payload.read_until(b"ne").ok().unwrap()
);
assert_eq!(payload.len, 1);
assert_eq!(
Async::Ready(Some(Bytes::from("1line2"))),
payload.read_until(b"2").ok().unwrap()
);
assert_eq!(payload.len, 0);
sender.set_error(PayloadError::Incomplete(None));
payload.read_until(b"b").err().unwrap();
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
}
#[test]
fn test_unread_data() {
Runtime::new()

View File

@@ -1,4 +1,4 @@
use std::fmt::Debug;
use std::fmt;
use std::marker::PhantomData;
use actix_codec::{AsyncRead, AsyncWrite, Framed};
@@ -10,27 +10,28 @@ use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream};
use crate::body::MessageBody;
use crate::config::{KeepAlive, ServiceConfig};
use crate::error::{DispatchError, ParseError};
use crate::error::{DispatchError, Error, ParseError};
use crate::request::Request;
use crate::response::Response;
use super::codec::Codec;
use super::dispatcher::Dispatcher;
use super::Message;
use super::{ExpectHandler, Message};
/// `NewService` implementation for HTTP1 transport
pub struct H1Service<T, P, S, B> {
pub struct H1Service<T, P, S, B, X = ExpectHandler> {
srv: S,
cfg: ServiceConfig,
expect: X,
_t: PhantomData<(T, P, B)>,
}
impl<T, P, S, B> H1Service<T, P, S, B>
where
S: NewService<SrvConfig, Request = Request>,
S::Error: Debug,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
S::Service: 'static,
B: MessageBody,
{
/// Create new `HttpService` instance with default config.
@@ -40,6 +41,7 @@ where
H1Service {
cfg,
srv: service.into_new_service(),
expect: ExpectHandler,
_t: PhantomData,
}
}
@@ -52,30 +54,59 @@ where
H1Service {
cfg,
srv: service.into_new_service(),
expect: ExpectHandler,
_t: PhantomData,
}
}
}
impl<T, P, S, B> NewService<SrvConfig> for H1Service<T, P, S, B>
impl<T, P, S, B, X> H1Service<T, P, S, B, X>
where
S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
S::InitError: fmt::Debug,
B: MessageBody,
{
pub fn expect<U>(self, expect: U) -> H1Service<T, P, S, B, U>
where
U: NewService<Request = Request, Response = Request>,
U::Error: Into<Error>,
U::InitError: fmt::Debug,
{
H1Service {
expect,
cfg: self.cfg,
srv: self.srv,
_t: PhantomData,
}
}
}
impl<T, P, S, B, X> NewService<SrvConfig> for H1Service<T, P, S, B, X>
where
T: AsyncRead + AsyncWrite,
S: NewService<SrvConfig, Request = Request>,
S::Error: Debug,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
S::Service: 'static,
S::InitError: fmt::Debug,
B: MessageBody,
X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
{
type Request = Io<T, P>;
type Response = ();
type Error = DispatchError;
type InitError = S::InitError;
type Service = H1ServiceHandler<T, P, S::Service, B>;
type Future = H1ServiceResponse<T, P, S, B>;
type InitError = ();
type Service = H1ServiceHandler<T, P, S::Service, B, X::Service>;
type Future = H1ServiceResponse<T, P, S, B, X>;
fn new_service(&self, cfg: &SrvConfig) -> Self::Future {
H1ServiceResponse {
fut: self.srv.new_service(cfg).into_future(),
fut_ex: Some(self.expect.new_service(&())),
expect: None,
cfg: Some(self.cfg.clone()),
_t: PhantomData,
}
@@ -83,78 +114,136 @@ where
}
#[doc(hidden)]
pub struct H1ServiceResponse<T, P, S: NewService<SrvConfig, Request = Request>, B> {
fut: <S::Future as IntoFuture>::Future,
pub struct H1ServiceResponse<T, P, S, B, X>
where
S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
{
fut: S::Future,
fut_ex: Option<X::Future>,
expect: Option<X::Service>,
cfg: Option<ServiceConfig>,
_t: PhantomData<(T, P, B)>,
}
impl<T, P, S, B> Future for H1ServiceResponse<T, P, S, B>
impl<T, P, S, B, X> Future for H1ServiceResponse<T, P, S, B, X>
where
T: AsyncRead + AsyncWrite,
S: NewService<SrvConfig, Request = Request>,
S::Service: 'static,
S::Error: Debug,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
S::InitError: fmt::Debug,
B: MessageBody,
X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
{
type Item = H1ServiceHandler<T, P, S::Service, B>;
type Error = S::InitError;
type Item = H1ServiceHandler<T, P, S::Service, B, X::Service>;
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let service = try_ready!(self.fut.poll());
if let Some(ref mut fut) = self.fut_ex {
let expect = try_ready!(fut
.poll()
.map_err(|e| log::error!("Init http service error: {:?}", e)));
self.expect = Some(expect);
self.fut_ex.take();
}
let service = try_ready!(self
.fut
.poll()
.map_err(|e| log::error!("Init http service error: {:?}", e)));
Ok(Async::Ready(H1ServiceHandler::new(
self.cfg.take().unwrap(),
service,
self.expect.take().unwrap(),
)))
}
}
/// `Service` implementation for HTTP1 transport
pub struct H1ServiceHandler<T, P, S: 'static, B> {
pub struct H1ServiceHandler<T, P, S, B, X> {
srv: CloneableService<S>,
expect: CloneableService<X>,
cfg: ServiceConfig,
_t: PhantomData<(T, P, B)>,
}
impl<T, P, S, B> H1ServiceHandler<T, P, S, B>
impl<T, P, S, B, X> H1ServiceHandler<T, P, S, B, X>
where
S: Service<Request = Request>,
S::Error: Debug,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
{
fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler<T, P, S, B> {
fn new(cfg: ServiceConfig, srv: S, expect: X) -> H1ServiceHandler<T, P, S, B, X> {
H1ServiceHandler {
srv: CloneableService::new(srv),
expect: CloneableService::new(expect),
cfg,
_t: PhantomData,
}
}
}
impl<T, P, S, B> Service for H1ServiceHandler<T, P, S, B>
impl<T, P, S, B, X> Service for H1ServiceHandler<T, P, S, B, X>
where
T: AsyncRead + AsyncWrite,
S: Service<Request = Request>,
S::Error: Debug,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
{
type Request = Io<T, P>;
type Response = ();
type Error = DispatchError;
type Future = Dispatcher<T, S, B>;
type Future = Dispatcher<T, S, B, X>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.srv.poll_ready().map_err(|e| {
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service
})
let ready = self
.expect
.poll_ready()
.map_err(|e| {
let e = e.into();
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service(e)
})?
.is_ready();
let ready = self
.srv
.poll_ready()
.map_err(|e| {
let e = e.into();
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service(e)
})?
.is_ready()
&& ready;
if ready {
Ok(Async::Ready(()))
} else {
Ok(Async::NotReady)
}
}
fn call(&mut self, req: Self::Request) -> Self::Future {
Dispatcher::new(req.into_parts().0, self.cfg.clone(), self.srv.clone())
Dispatcher::new(
req.into_parts().0,
self.cfg.clone(),
self.srv.clone(),
self.expect.clone(),
)
}
}

View File

@@ -31,7 +31,7 @@ const CHUNK_SIZE: usize = 16_384;
/// Dispatcher for HTTP/2 protocol
pub struct Dispatcher<
T: AsyncRead + AsyncWrite,
S: Service<Request = Request> + 'static,
S: Service<Request = Request>,
B: MessageBody,
> {
service: CloneableService<S>,
@@ -45,8 +45,9 @@ pub struct Dispatcher<
impl<T, S, B> Dispatcher<T, S, B>
where
T: AsyncRead + AsyncWrite,
S: Service<Request = Request> + 'static,
S::Error: fmt::Debug,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
S::Response: Into<Response<B>>,
B: MessageBody + 'static,
{
@@ -86,8 +87,9 @@ where
impl<T, S, B> Future for Dispatcher<T, S, B>
where
T: AsyncRead + AsyncWrite,
S: Service<Request = Request> + 'static,
S::Error: fmt::Debug,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
S::Response: Into<Response<B>>,
B: MessageBody + 'static,
{
@@ -114,8 +116,8 @@ where
head.uri = parts.uri;
head.method = parts.method;
head.version = parts.version;
head.headers = parts.headers;
tokio_current_thread::spawn(ServiceResponse::<S, B> {
head.headers = parts.headers.into();
tokio_current_thread::spawn(ServiceResponse::<S::Future, B> {
state: ServiceResponseState::ServiceCall(
self.service.call(req),
Some(res),
@@ -130,22 +132,22 @@ where
}
}
struct ServiceResponse<S: Service, B> {
state: ServiceResponseState<S, B>,
struct ServiceResponse<F, B> {
state: ServiceResponseState<F, B>,
config: ServiceConfig,
buffer: Option<Bytes>,
}
enum ServiceResponseState<S: Service, B> {
ServiceCall(S::Future, Option<SendResponse<Bytes>>),
enum ServiceResponseState<F, B> {
ServiceCall(F, Option<SendResponse<Bytes>>),
SendPayload(SendStream<Bytes>, ResponseBody<B>),
}
impl<S, B> ServiceResponse<S, B>
impl<F, B> ServiceResponse<F, B>
where
S: Service<Request = Request> + 'static,
S::Error: fmt::Debug,
S::Response: Into<Response<B>>,
F: Future,
F::Error: Into<Error>,
F::Item: Into<Response<B>>,
B: MessageBody + 'static,
{
fn prepare_response(
@@ -209,11 +211,11 @@ where
}
}
impl<S, B> Future for ServiceResponse<S, B>
impl<F, B> Future for ServiceResponse<F, B>
where
S: Service<Request = Request> + 'static,
S::Error: fmt::Debug,
S::Response: Into<Response<B>>,
F: Future,
F::Error: Into<Error>,
F::Item: Into<Response<B>>,
B: MessageBody + 'static,
{
type Item = ();

View File

@@ -32,9 +32,9 @@ pub struct H2Service<T, P, S, B> {
impl<T, P, S, B> H2Service<T, P, S, B>
where
S: NewService<SrvConfig, Request = Request>,
S::Service: 'static,
S::Error: Debug + 'static,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
{
/// Create new `HttpService` instance.
@@ -65,9 +65,9 @@ impl<T, P, S, B> NewService<SrvConfig> for H2Service<T, P, S, B>
where
T: AsyncRead + AsyncWrite,
S: NewService<SrvConfig, Request = Request>,
S::Service: 'static,
S::Error: Debug,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
{
type Request = Io<T, P>;
@@ -97,9 +97,9 @@ impl<T, P, S, B> Future for H2ServiceResponse<T, P, S, B>
where
T: AsyncRead + AsyncWrite,
S: NewService<SrvConfig, Request = Request>,
S::Service: 'static,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
S::Error: Debug,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
{
type Item = H2ServiceHandler<T, P, S::Service, B>;
@@ -115,7 +115,7 @@ where
}
/// `Service` implementation for http/2 transport
pub struct H2ServiceHandler<T, P, S: 'static, B> {
pub struct H2ServiceHandler<T, P, S, B> {
srv: CloneableService<S>,
cfg: ServiceConfig,
_t: PhantomData<(T, P, B)>,
@@ -123,8 +123,9 @@ pub struct H2ServiceHandler<T, P, S: 'static, B> {
impl<T, P, S, B> H2ServiceHandler<T, P, S, B>
where
S: Service<Request = Request> + 'static,
S::Error: Debug,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
S::Response: Into<Response<B>>,
B: MessageBody + 'static,
{
@@ -140,8 +141,9 @@ where
impl<T, P, S, B> Service for H2ServiceHandler<T, P, S, B>
where
T: AsyncRead + AsyncWrite,
S: Service<Request = Request> + 'static,
S::Error: Debug,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
S::Response: Into<Response<B>>,
B: MessageBody + 'static,
{
@@ -152,8 +154,9 @@ where
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.srv.poll_ready().map_err(|e| {
let e = e.into();
error!("Service readiness error: {:?}", e);
DispatchError::Service
DispatchError::Service(e)
})
}
@@ -168,11 +171,10 @@ where
}
}
enum State<
T: AsyncRead + AsyncWrite,
S: Service<Request = Request> + 'static,
B: MessageBody,
> {
enum State<T: AsyncRead + AsyncWrite, S: Service<Request = Request>, B: MessageBody>
where
S::Future: 'static,
{
Incoming(Dispatcher<T, S, B>),
Handshake(
Option<CloneableService<S>>,
@@ -184,8 +186,9 @@ enum State<
pub struct H2ServiceHandlerResponse<T, S, B>
where
T: AsyncRead + AsyncWrite,
S: Service<Request = Request> + 'static,
S::Error: Debug,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
S::Response: Into<Response<B>>,
B: MessageBody + 'static,
{
@@ -195,8 +198,9 @@ where
impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
where
T: AsyncRead + AsyncWrite,
S: Service<Request = Request> + 'static,
S::Error: Debug,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
S::Response: Into<Response<B>>,
B: MessageBody,
{

View File

@@ -64,7 +64,7 @@ impl Header for CacheControl {
where
T: crate::HttpMessage,
{
let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?;
let directives = from_comma_delimited(msg.headers().get_all(&Self::name()))?;
if !directives.is_empty() {
Ok(CacheControl(directives))
} else {

View File

@@ -444,7 +444,7 @@ impl Header for ContentDisposition {
}
fn parse<T: crate::HttpMessage>(msg: &T) -> Result<Self, crate::error::ParseError> {
if let Some(h) = msg.headers().get(Self::name()) {
if let Some(h) = msg.headers().get(&Self::name()) {
Self::from_raw(&h)
} else {
Err(crate::error::ParseError::Header)

View File

@@ -73,12 +73,12 @@ impl Header for IfRange {
T: HttpMessage,
{
let etag: Result<EntityTag, _> =
from_one_raw_str(msg.headers().get(header::IF_RANGE));
from_one_raw_str(msg.headers().get(&header::IF_RANGE));
if let Ok(etag) = etag {
return Ok(IfRange::EntityTag(etag));
}
let date: Result<HttpDate, _> =
from_one_raw_str(msg.headers().get(header::IF_RANGE));
from_one_raw_str(msg.headers().get(&header::IF_RANGE));
if let Ok(date) = date {
return Ok(IfRange::Date(date));
}

View File

@@ -0,0 +1,384 @@
use either::Either;
use hashbrown::hash_map::{self, Entry};
use hashbrown::HashMap;
use http::header::{HeaderName, HeaderValue};
use http::HttpTryFrom;
/// A set of HTTP headers
///
/// `HeaderMap` is an multimap of [`HeaderName`] to values.
///
/// [`HeaderName`]: struct.HeaderName.html
#[derive(Debug)]
pub struct HeaderMap {
pub(crate) inner: HashMap<HeaderName, Value>,
}
#[derive(Debug)]
pub(crate) enum Value {
One(HeaderValue),
Multi(Vec<HeaderValue>),
}
impl Value {
fn get(&self) -> &HeaderValue {
match self {
Value::One(ref val) => val,
Value::Multi(ref val) => &val[0],
}
}
fn get_mut(&mut self) -> &mut HeaderValue {
match self {
Value::One(ref mut val) => val,
Value::Multi(ref mut val) => &mut val[0],
}
}
fn append(&mut self, val: HeaderValue) {
match self {
Value::One(_) => {
let data = std::mem::replace(self, Value::Multi(vec![val]));
match data {
Value::One(val) => self.append(val),
Value::Multi(_) => unreachable!(),
}
}
Value::Multi(ref mut vec) => vec.push(val),
}
}
}
impl HeaderMap {
/// Create an empty `HeaderMap`.
///
/// The map will be created without any capacity. This function will not
/// allocate.
pub fn new() -> Self {
HeaderMap {
inner: HashMap::new(),
}
}
/// Create an empty `HeaderMap` with the specified capacity.
///
/// The returned map will allocate internal storage in order to hold about
/// `capacity` elements without reallocating. However, this is a "best
/// effort" as there are usage patterns that could cause additional
/// allocations before `capacity` headers are stored in the map.
///
/// More capacity than requested may be allocated.
pub fn with_capacity(capacity: usize) -> HeaderMap {
HeaderMap {
inner: HashMap::with_capacity(capacity),
}
}
/// Returns the number of keys stored in the map.
///
/// This number could be be less than or equal to actual headers stored in
/// the map.
pub fn len(&self) -> usize {
self.inner.len()
}
/// Returns true if the map contains no elements.
pub fn is_empty(&self) -> bool {
self.inner.len() == 0
}
/// Clears the map, removing all key-value pairs. Keeps the allocated memory
/// for reuse.
pub fn clear(&mut self) {
self.inner.clear();
}
/// Returns the number of headers the map can hold without reallocating.
///
/// This number is an approximation as certain usage patterns could cause
/// additional allocations before the returned capacity is filled.
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
/// Reserves capacity for at least `additional` more headers to be inserted
/// into the `HeaderMap`.
///
/// The header map may reserve more space to avoid frequent reallocations.
/// Like with `with_capacity`, this will be a "best effort" to avoid
/// allocations until `additional` more headers are inserted. Certain usage
/// patterns could cause additional allocations before the number is
/// reached.
pub fn reserve(&mut self, additional: usize) {
self.inner.reserve(additional)
}
/// Returns a reference to the value associated with the key.
///
/// If there are multiple values associated with the key, then the first one
/// is returned. Use `get_all` to get all values associated with a given
/// key. Returns `None` if there are no values associated with the key.
pub fn get<N: AsName>(&self, name: N) -> Option<&HeaderValue> {
self.get2(name).map(|v| v.get())
}
fn get2<N: AsName>(&self, name: N) -> Option<&Value> {
match name.as_name() {
Either::Left(name) => self.inner.get(name),
Either::Right(s) => {
if let Ok(name) = HeaderName::try_from(s) {
self.inner.get(&name)
} else {
None
}
}
}
}
/// Returns a view of all values associated with a key.
///
/// The returned view does not incur any allocations and allows iterating
/// the values associated with the key. See [`GetAll`] for more details.
/// Returns `None` if there are no values associated with the key.
///
/// [`GetAll`]: struct.GetAll.html
pub fn get_all<N: AsName>(&self, name: N) -> GetAll {
GetAll {
idx: 0,
item: self.get2(name),
}
}
/// Returns a mutable reference to the value associated with the key.
///
/// If there are multiple values associated with the key, then the first one
/// is returned. Use `entry` to get all values associated with a given
/// key. Returns `None` if there are no values associated with the key.
pub fn get_mut<N: AsName>(&mut self, name: N) -> Option<&mut HeaderValue> {
match name.as_name() {
Either::Left(name) => self.inner.get_mut(name).map(|v| v.get_mut()),
Either::Right(s) => {
if let Ok(name) = HeaderName::try_from(s) {
self.inner.get_mut(&name).map(|v| v.get_mut())
} else {
None
}
}
}
}
/// Returns true if the map contains a value for the specified key.
pub fn contains_key<N: AsName>(&self, key: N) -> bool {
match key.as_name() {
Either::Left(name) => self.inner.contains_key(name),
Either::Right(s) => {
if let Ok(name) = HeaderName::try_from(s) {
self.inner.contains_key(&name)
} else {
false
}
}
}
}
/// An iterator visiting all key-value pairs.
///
/// The iteration order is arbitrary, but consistent across platforms for
/// the same crate version. Each key will be yielded once per associated
/// value. So, if a key has 3 associated values, it will be yielded 3 times.
pub fn iter(&self) -> Iter {
Iter::new(self.inner.iter())
}
/// An iterator visiting all keys.
///
/// The iteration order is arbitrary, but consistent across platforms for
/// the same crate version. Each key will be yielded only once even if it
/// has multiple associated values.
pub fn keys(&self) -> Keys {
Keys(self.inner.keys())
}
/// Inserts a key-value pair into the map.
///
/// If the map did not previously have this key present, then `None` is
/// returned.
///
/// If the map did have this key present, the new value is associated with
/// the key and all previous values are removed. **Note** that only a single
/// one of the previous values is returned. If there are multiple values
/// that have been previously associated with the key, then the first one is
/// returned. See `insert_mult` on `OccupiedEntry` for an API that returns
/// all values.
///
/// The key is not updated, though; this matters for types that can be `==`
/// without being identical.
pub fn insert(&mut self, key: HeaderName, val: HeaderValue) {
let _ = self.inner.insert(key, Value::One(val));
}
/// Inserts a key-value pair into the map.
///
/// If the map did not previously have this key present, then `false` is
/// returned.
///
/// If the map did have this key present, the new value is pushed to the end
/// of the list of values currently associated with the key. The key is not
/// updated, though; this matters for types that can be `==` without being
/// identical.
pub fn append(&mut self, key: HeaderName, value: HeaderValue) {
match self.inner.entry(key) {
Entry::Occupied(mut entry) => entry.get_mut().append(value),
Entry::Vacant(entry) => {
entry.insert(Value::One(value));
}
}
}
/// Removes all headers for a particular header name from the map.
pub fn remove<N: AsName>(&mut self, key: N) {
match key.as_name() {
Either::Left(name) => {
let _ = self.inner.remove(name);
}
Either::Right(s) => {
if let Ok(name) = HeaderName::try_from(s) {
let _ = self.inner.remove(&name);
}
}
}
}
}
#[doc(hidden)]
pub trait AsName {
fn as_name(&self) -> Either<&HeaderName, &str>;
}
impl AsName for HeaderName {
fn as_name(&self) -> Either<&HeaderName, &str> {
Either::Left(self)
}
}
impl<'a> AsName for &'a HeaderName {
fn as_name(&self) -> Either<&HeaderName, &str> {
Either::Left(self)
}
}
impl<'a> AsName for &'a str {
fn as_name(&self) -> Either<&HeaderName, &str> {
Either::Right(self)
}
}
impl AsName for String {
fn as_name(&self) -> Either<&HeaderName, &str> {
Either::Right(self.as_str())
}
}
impl<'a> AsName for &'a String {
fn as_name(&self) -> Either<&HeaderName, &str> {
Either::Right(self.as_str())
}
}
pub struct GetAll<'a> {
idx: usize,
item: Option<&'a Value>,
}
impl<'a> Iterator for GetAll<'a> {
type Item = &'a HeaderValue;
#[inline]
fn next(&mut self) -> Option<&'a HeaderValue> {
if let Some(ref val) = self.item {
match val {
Value::One(ref val) => {
self.item.take();
Some(val)
}
Value::Multi(ref vec) => {
if self.idx < vec.len() {
let item = Some(&vec[self.idx]);
self.idx += 1;
item
} else {
self.item.take();
None
}
}
}
} else {
None
}
}
}
pub struct Keys<'a>(hash_map::Keys<'a, HeaderName, Value>);
impl<'a> Iterator for Keys<'a> {
type Item = &'a HeaderName;
#[inline]
fn next(&mut self) -> Option<&'a HeaderName> {
self.0.next()
}
}
impl<'a> IntoIterator for &'a HeaderMap {
type Item = (&'a HeaderName, &'a HeaderValue);
type IntoIter = Iter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub struct Iter<'a> {
idx: usize,
current: Option<(&'a HeaderName, &'a Vec<HeaderValue>)>,
iter: hash_map::Iter<'a, HeaderName, Value>,
}
impl<'a> Iter<'a> {
fn new(iter: hash_map::Iter<'a, HeaderName, Value>) -> Self {
Self {
iter,
idx: 0,
current: None,
}
}
}
impl<'a> Iterator for Iter<'a> {
type Item = (&'a HeaderName, &'a HeaderValue);
#[inline]
fn next(&mut self) -> Option<(&'a HeaderName, &'a HeaderValue)> {
if let Some(ref mut item) = self.current {
if self.idx < item.1.len() {
let item = (item.0, &item.1[self.idx]);
self.idx += 1;
return Some(item);
} else {
self.idx = 0;
self.current.take();
}
}
if let Some(item) = self.iter.next() {
match item.1 {
Value::One(ref value) => Some((item.0, value)),
Value::Multi(ref vec) => {
self.current = Some((item.0, vec));
self.next()
}
}
} else {
None
}
}
}

View File

@@ -4,7 +4,6 @@
use std::{fmt, str::FromStr};
use bytes::{Bytes, BytesMut};
use http::header::GetAll;
use http::Error as HttpError;
use mime::Mime;
@@ -14,6 +13,7 @@ use crate::error::ParseError;
use crate::httpmessage::HttpMessage;
mod common;
pub(crate) mod map;
mod shared;
#[doc(hidden)]
pub use self::common::*;
@@ -21,6 +21,9 @@ pub use self::common::*;
pub use self::shared::*;
#[doc(hidden)]
pub use self::map::GetAll;
pub use self::map::HeaderMap;
/// A trait for any object that will represent a header field and value.
pub trait Header
where
@@ -33,7 +36,6 @@ where
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
}
#[doc(hidden)]
/// A trait for any object that can be Converted to a `HeaderValue`
pub trait IntoHeaderValue: Sized {
/// The type returned in the event of a conversion error.
@@ -97,6 +99,26 @@ impl IntoHeaderValue for String {
}
}
impl IntoHeaderValue for usize {
type Error = InvalidHeaderValueBytes;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let s = format!("{}", self);
HeaderValue::from_shared(Bytes::from(s))
}
}
impl IntoHeaderValue for u64 {
type Error = InvalidHeaderValueBytes;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let s = format!("{}", self);
HeaderValue::from_shared(Bytes::from(s))
}
}
impl IntoHeaderValue for Mime {
type Error = InvalidHeaderValueBytes;
@@ -202,8 +224,8 @@ impl fmt::Write for Writer {
#[inline]
#[doc(hidden)]
/// Reads a comma-delimited raw header into a Vec.
pub fn from_comma_delimited<T: FromStr>(
all: GetAll<HeaderValue>,
pub fn from_comma_delimited<'a, I: Iterator<Item = &'a HeaderValue> + 'a, T: FromStr>(
all: I,
) -> Result<Vec<T>, ParseError> {
let mut result = Vec::new();
for h in all {
@@ -361,6 +383,17 @@ pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result
fmt::Display::fmt(&encoded, f)
}
/// Convert http::HeaderMap to a HeaderMap
impl From<http::HeaderMap> for HeaderMap {
fn from(map: http::HeaderMap) -> HeaderMap {
let mut new_map = HeaderMap::with_capacity(map.capacity());
for (h, v) in map.iter() {
new_map.append(h.clone(), v.clone());
}
new_map
}
}
mod percent_encoding_http {
use percent_encoding::{self, define_encode_set};

View File

@@ -8,7 +8,7 @@ macro_rules! STATIC_RESP {
($name:ident, $status:expr) => {
#[allow(non_snake_case, missing_docs)]
pub fn $name() -> ResponseBuilder {
Response::build($status)
ResponseBuilder::new($status)
}
};
}

View File

@@ -4,13 +4,13 @@ use std::str;
use encoding::all::UTF_8;
use encoding::label::encoding_from_whatwg_label;
use encoding::EncodingRef;
use http::{header, HeaderMap};
use http::header;
use mime::Mime;
use crate::cookie::Cookie;
use crate::error::{ContentTypeError, CookieParseError, ParseError};
use crate::extensions::Extensions;
use crate::header::Header;
use crate::header::{Header, HeaderMap};
use crate::payload::Payload;
struct Cookies(Vec<Cookie<'static>>);

View File

@@ -1,5 +1,9 @@
//! Basic http primitives for actix-net framework.
#![allow(clippy::type_complexity, clippy::new_without_default)]
#![allow(
clippy::type_complexity,
clippy::new_without_default,
clippy::borrow_interior_mutable_const
)]
#[macro_use]
extern crate log;
@@ -8,6 +12,7 @@ pub mod body;
mod builder;
pub mod client;
mod config;
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust", feature = "brotli"))]
pub mod encoding;
mod extensions;
mod header;
@@ -36,7 +41,7 @@ pub use self::message::{Message, RequestHead, ResponseHead};
pub use self::payload::{Payload, PayloadStream};
pub use self::request::Request;
pub use self::response::{Response, ResponseBuilder};
pub use self::service::{HttpService, SendError, SendResponse};
pub use self::service::HttpService;
pub mod http {
//! Various HTTP related types
@@ -44,10 +49,11 @@ pub mod http {
// re-exports
pub use http::header::{HeaderName, HeaderValue};
pub use http::uri::PathAndQuery;
pub use http::{uri, Error, HeaderMap, HttpTryFrom, Uri};
pub use http::{uri, Error, HttpTryFrom, Uri};
pub use http::{Method, StatusCode, Version};
pub use crate::cookie::{Cookie, CookieBuilder};
pub use crate::header::HeaderMap;
/// Various http headers
pub mod header {

View File

@@ -1,11 +1,12 @@
use std::cell::{Ref, RefCell, RefMut};
use std::collections::VecDeque;
use std::rc::Rc;
use bitflags::bitflags;
use copyless::BoxHelper;
use crate::extensions::Extensions;
use crate::http::{header, HeaderMap, Method, StatusCode, Uri, Version};
use crate::header::HeaderMap;
use crate::http::{header, Method, StatusCode, Uri, Version};
/// Represents various types of connection
#[derive(Copy, Clone, PartialEq, Debug)]
@@ -23,7 +24,8 @@ bitflags! {
const CLOSE = 0b0000_0001;
const KEEP_ALIVE = 0b0000_0010;
const UPGRADE = 0b0000_0100;
const NO_CHUNKING = 0b0000_1000;
const EXPECT = 0b0000_1000;
const NO_CHUNKING = 0b0001_0000;
}
}
@@ -145,6 +147,17 @@ impl RequestHead {
self.flags.remove(Flags::NO_CHUNKING);
}
}
#[inline]
/// Request contains `EXPECT` header
pub fn expect(&self) -> bool {
self.flags.contains(Flags::EXPECT)
}
#[inline]
pub(crate) fn set_expect(&mut self) {
self.flags.insert(Flags::EXPECT);
}
}
#[derive(Debug)]
@@ -157,32 +170,20 @@ pub struct ResponseHead {
flags: Flags,
}
impl Default for ResponseHead {
fn default() -> ResponseHead {
impl ResponseHead {
/// Create new instance of `ResponseHead` type
#[inline]
pub fn new(status: StatusCode) -> ResponseHead {
ResponseHead {
status,
version: Version::default(),
status: StatusCode::OK,
headers: HeaderMap::with_capacity(16),
headers: HeaderMap::with_capacity(12),
reason: None,
flags: Flags::empty(),
extensions: RefCell::new(Extensions::new()),
}
}
}
impl Head for ResponseHead {
fn clear(&mut self) {
self.reason = None;
self.flags = Flags::empty();
self.headers.clear();
}
fn pool() -> &'static MessagePool<Self> {
RESPONSE_POOL.with(|p| *p)
}
}
impl ResponseHead {
/// Message extensions
#[inline]
pub fn extensions(&self) -> Ref<Extensions> {
@@ -288,7 +289,6 @@ impl ResponseHead {
pub struct Message<T: Head> {
head: Rc<T>,
pool: &'static MessagePool<T>,
}
impl<T: Head> Message<T> {
@@ -302,7 +302,6 @@ impl<T: Head> Clone for Message<T> {
fn clone(&self) -> Self {
Message {
head: self.head.clone(),
pool: self.pool,
}
}
}
@@ -324,39 +323,70 @@ impl<T: Head> std::ops::DerefMut for Message<T> {
impl<T: Head> Drop for Message<T> {
fn drop(&mut self) {
if Rc::strong_count(&self.head) == 1 {
self.pool.release(self.head.clone());
T::pool().release(self.head.clone());
}
}
}
pub(crate) struct BoxedResponseHead {
head: Option<Box<ResponseHead>>,
}
impl BoxedResponseHead {
/// Get new message from the pool of objects
pub fn new(status: StatusCode) -> Self {
RESPONSE_POOL.with(|p| p.get_message(status))
}
}
impl std::ops::Deref for BoxedResponseHead {
type Target = ResponseHead;
fn deref(&self) -> &Self::Target {
self.head.as_ref().unwrap()
}
}
impl std::ops::DerefMut for BoxedResponseHead {
fn deref_mut(&mut self) -> &mut Self::Target {
self.head.as_mut().unwrap()
}
}
impl Drop for BoxedResponseHead {
fn drop(&mut self) {
RESPONSE_POOL.with(|p| p.release(self.head.take().unwrap()))
}
}
#[doc(hidden)]
/// Request's objects pool
pub struct MessagePool<T: Head>(RefCell<VecDeque<Rc<T>>>);
pub struct MessagePool<T: Head>(RefCell<Vec<Rc<T>>>);
#[doc(hidden)]
/// Request's objects pool
pub struct BoxedResponsePool(RefCell<Vec<Box<ResponseHead>>>);
thread_local!(static REQUEST_POOL: &'static MessagePool<RequestHead> = MessagePool::<RequestHead>::create());
thread_local!(static RESPONSE_POOL: &'static MessagePool<ResponseHead> = MessagePool::<ResponseHead>::create());
thread_local!(static RESPONSE_POOL: &'static BoxedResponsePool = BoxedResponsePool::create());
impl<T: Head> MessagePool<T> {
fn create() -> &'static MessagePool<T> {
let pool = MessagePool(RefCell::new(VecDeque::with_capacity(128)));
let pool = MessagePool(RefCell::new(Vec::with_capacity(128)));
Box::leak(Box::new(pool))
}
/// Get message from the pool
#[inline]
fn get_message(&'static self) -> Message<T> {
if let Some(mut msg) = self.0.borrow_mut().pop_front() {
if let Some(mut msg) = self.0.borrow_mut().pop() {
if let Some(r) = Rc::get_mut(&mut msg) {
r.clear();
}
Message {
head: msg,
pool: self,
}
Message { head: msg }
} else {
Message {
head: Rc::new(T::default()),
pool: self,
}
}
}
@@ -366,7 +396,39 @@ impl<T: Head> MessagePool<T> {
fn release(&self, msg: Rc<T>) {
let v = &mut self.0.borrow_mut();
if v.len() < 128 {
v.push_front(msg);
v.push(msg);
}
}
}
impl BoxedResponsePool {
fn create() -> &'static BoxedResponsePool {
let pool = BoxedResponsePool(RefCell::new(Vec::with_capacity(128)));
Box::leak(Box::new(pool))
}
/// Get message from the pool
#[inline]
fn get_message(&'static self, status: StatusCode) -> BoxedResponseHead {
if let Some(mut head) = self.0.borrow_mut().pop() {
head.reason = None;
head.status = status;
head.headers.clear();
head.flags = Flags::empty();
BoxedResponseHead { head: Some(head) }
} else {
BoxedResponseHead {
head: Some(Box::alloc().init(ResponseHead::new(status))),
}
}
}
#[inline]
/// Release request instance
fn release(&self, msg: Box<ResponseHead>) {
let v = &mut self.0.borrow_mut();
if v.len() < 128 {
v.push(msg);
}
}
}

View File

@@ -1,9 +1,10 @@
use std::cell::{Ref, RefMut};
use std::fmt;
use http::{header, HeaderMap, Method, Uri, Version};
use http::{header, Method, Uri, Version};
use crate::extensions::Extensions;
use crate::header::HeaderMap;
use crate::httpmessage::HttpMessage;
use crate::message::{Message, RequestHead};
use crate::payload::{Payload, PayloadStream};
@@ -161,7 +162,7 @@ impl<P> fmt::Debug for Request<P> {
writeln!(f, " query: ?{:?}", q)?;
}
writeln!(f, " headers:")?;
for (key, val) in self.headers().iter() {
for (key, val) in self.headers() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())

View File

@@ -1,13 +1,11 @@
//! Http response
use std::cell::{Ref, RefMut};
use std::io::Write;
use std::{fmt, str};
use std::{fmt, io, str};
use bytes::{BufMut, Bytes, BytesMut};
use futures::future::{ok, FutureResult, IntoFuture};
use futures::Stream;
use http::header::{self, HeaderName, HeaderValue};
use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode};
use serde::Serialize;
use serde_json;
@@ -16,11 +14,13 @@ use crate::cookie::{Cookie, CookieJar};
use crate::error::Error;
use crate::extensions::Extensions;
use crate::header::{Header, IntoHeaderValue};
use crate::message::{ConnectionType, Message, ResponseHead};
use crate::http::header::{self, HeaderName, HeaderValue};
use crate::http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode};
use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead};
/// An HTTP Response
pub struct Response<B = Body> {
head: Message<ResponseHead>,
head: BoxedResponseHead,
body: ResponseBody<B>,
error: Option<Error>,
}
@@ -41,11 +41,8 @@ impl Response<Body> {
/// Constructs a response
#[inline]
pub fn new(status: StatusCode) -> Response {
let mut head: Message<ResponseHead> = Message::new();
head.status = status;
Response {
head,
head: BoxedResponseHead::new(status),
body: ResponseBody::Body(Body::Empty),
error: None,
}
@@ -55,8 +52,12 @@ impl Response<Body> {
#[inline]
pub fn from_error(error: Error) -> Response {
let mut resp = error.as_response_error().error_response();
let mut buf = BytesMut::new();
let _ = write!(Writer(&mut buf), "{}", error);
resp.headers_mut()
.insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain"));
resp.error = Some(error);
resp
resp.set_body(Body::from(buf))
}
/// Convert response to response with body
@@ -74,6 +75,16 @@ impl Response<Body> {
}
impl<B> Response<B> {
/// Constructs a response with body
#[inline]
pub fn with_body(status: StatusCode, body: B) -> Response<B> {
Response {
head: BoxedResponseHead::new(status),
body: ResponseBody::Body(body),
error: None,
}
}
#[inline]
/// Http message part of the response
pub fn head(&self) -> &ResponseHead {
@@ -86,18 +97,6 @@ impl<B> Response<B> {
&mut *self.head
}
/// Constructs a response with body
#[inline]
pub fn with_body(status: StatusCode, body: B) -> Response<B> {
let mut head: Message<ResponseHead> = Message::new();
head.status = status;
Response {
head,
body: ResponseBody::Body(body),
error: None,
}
}
/// The source `error` for this response
#[inline]
pub fn error(&self) -> Option<&Error> {
@@ -132,7 +131,7 @@ impl<B> Response<B> {
#[inline]
pub fn cookies(&self) -> CookieIter {
CookieIter {
iter: self.head.headers.get_all(header::SET_COOKIE).iter(),
iter: self.head.headers.get_all(header::SET_COOKIE),
}
}
@@ -154,7 +153,6 @@ impl<B> Response<B> {
let h = &mut self.head.headers;
let vals: Vec<HeaderValue> = h
.get_all(header::SET_COOKIE)
.iter()
.map(|v| v.to_owned())
.collect();
h.remove(header::SET_COOKIE);
@@ -204,7 +202,7 @@ impl<B> Response<B> {
}
/// Set a body
pub(crate) fn set_body<B2>(self, body: B2) -> Response<B2> {
pub fn set_body<B2>(self, body: B2) -> Response<B2> {
Response {
head: self.head,
body: ResponseBody::Body(body),
@@ -213,7 +211,7 @@ impl<B> Response<B> {
}
/// Drop request's body
pub(crate) fn drop_body(self) -> Response<()> {
pub fn drop_body(self) -> Response<()> {
Response {
head: self.head,
body: ResponseBody::Body(()),
@@ -282,7 +280,7 @@ impl IntoFuture for Response {
}
pub struct CookieIter<'a> {
iter: header::ValueIter<'a, HeaderValue>,
iter: header::GetAll<'a>,
}
impl<'a> Iterator for CookieIter<'a> {
@@ -299,24 +297,34 @@ impl<'a> Iterator for CookieIter<'a> {
}
}
pub struct Writer<'a>(pub &'a mut BytesMut);
impl<'a> io::Write for Writer<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
/// An HTTP response builder
///
/// This type can be used to construct an instance of `Response` through a
/// builder-like pattern.
pub struct ResponseBuilder {
head: Option<Message<ResponseHead>>,
head: Option<BoxedResponseHead>,
err: Option<HttpError>,
cookies: Option<CookieJar>,
}
impl ResponseBuilder {
#[inline]
/// Create response builder
pub fn new(status: StatusCode) -> Self {
let mut head: Message<ResponseHead> = Message::new();
head.status = status;
ResponseBuilder {
head: Some(head),
head: Some(BoxedResponseHead::new(status)),
err: None,
cookies: None,
}
@@ -540,15 +548,13 @@ impl ResponseBuilder {
/// }
/// ```
pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self {
{
if self.cookies.is_none() {
self.cookies = Some(CookieJar::new())
}
let jar = self.cookies.as_mut().unwrap();
let cookie = cookie.clone().into_owned();
jar.add_original(cookie.clone());
jar.remove(cookie);
if self.cookies.is_none() {
self.cookies = Some(CookieJar::new())
}
let jar = self.cookies.as_mut().unwrap();
let cookie = cookie.clone().into_owned();
jar.add_original(cookie.clone());
jar.remove(cookie);
self
}
@@ -590,6 +596,7 @@ impl ResponseBuilder {
head.extensions.borrow_mut()
}
#[inline]
/// Set a body and generate `Response`.
///
/// `ResponseBuilder` can not be used after this call.
@@ -610,9 +617,7 @@ impl ResponseBuilder {
if let Some(ref jar) = self.cookies {
for cookie in jar.delta() {
match HeaderValue::from_str(&cookie.to_string()) {
Ok(val) => {
let _ = response.headers.append(header::SET_COOKIE, val);
}
Ok(val) => response.headers.append(header::SET_COOKIE, val),
Err(e) => return Response::from(Error::from(e)).into_body(),
};
}
@@ -637,6 +642,7 @@ impl ResponseBuilder {
self.body(Body::from_message(BodyStream::new(stream)))
}
#[inline]
/// Set a json body and generate `Response`
///
/// `ResponseBuilder` can not be used after this call.
@@ -685,13 +691,13 @@ impl ResponseBuilder {
#[inline]
fn parts<'a>(
parts: &'a mut Option<Message<ResponseHead>>,
parts: &'a mut Option<BoxedResponseHead>,
err: &Option<HttpError>,
) -> Option<&'a mut Message<ResponseHead>> {
) -> Option<&'a mut ResponseHead> {
if err.is_some() {
return None;
}
parts.as_mut()
parts.as_mut().map(|r| &mut **r)
}
/// Convert `Response` to a `ResponseBuilder`. Body get dropped.
@@ -724,7 +730,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
let mut jar: Option<CookieJar> = None;
let cookies = CookieIter {
iter: head.headers.get_all(header::SET_COOKIE).iter(),
iter: head.headers.get_all(header::SET_COOKIE),
};
for c in cookies {
if let Some(ref mut j) = jar {
@@ -736,11 +742,12 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
}
}
let mut msg: Message<ResponseHead> = Message::new();
let mut msg = BoxedResponseHead::new(head.status);
msg.version = head.version;
msg.status = head.status;
msg.reason = head.reason;
msg.headers = head.headers.clone();
for (k, v) in &head.headers {
msg.headers.append(k.clone(), v.clone());
}
msg.no_chunking(!head.chunked());
ResponseBuilder {
@@ -829,7 +836,7 @@ impl From<BytesMut> for Response {
mod tests {
use super::*;
use crate::body::Body;
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE};
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE};
#[test]
fn test_debug() {
@@ -860,13 +867,12 @@ mod tests {
.max_age(time::Duration::days(1))
.finish(),
)
.del_cookie(&cookies[0])
.del_cookie(&cookies[1])
.finish();
let mut val: Vec<_> = resp
.headers()
.get_all("Set-Cookie")
.iter()
.get_all(SET_COOKIE)
.map(|v| v.to_str().unwrap().to_owned())
.collect();
val.sort();
@@ -895,9 +901,9 @@ mod tests {
let mut iter = r.cookies();
let v = iter.next().unwrap();
assert_eq!((v.name(), v.value()), ("original", "val100"));
let v = iter.next().unwrap();
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
let v = iter.next().unwrap();
assert_eq!((v.name(), v.value()), ("original", "val100"));
}
#[test]
@@ -1033,6 +1039,7 @@ mod tests {
resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/octet-stream")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().get_ref(), b"test");
}

View File

@@ -1,37 +1,37 @@
use std::fmt::Debug;
use std::marker::PhantomData;
use std::{fmt, io};
use actix_codec::{AsyncRead, AsyncWrite, Framed, FramedParts};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_server_config::{Io as ServerIo, Protocol, ServerConfig as SrvConfig};
use actix_service::{IntoNewService, NewService, Service};
use actix_utils::cloneable::CloneableService;
use bytes::{Buf, BufMut, Bytes, BytesMut};
use futures::{try_ready, Async, Future, IntoFuture, Poll};
use h2::server::{self, Handshake};
use log::error;
use crate::body::MessageBody;
use crate::builder::HttpServiceBuilder;
use crate::config::{KeepAlive, ServiceConfig};
use crate::error::DispatchError;
use crate::error::{DispatchError, Error};
use crate::request::Request;
use crate::response::Response;
use crate::{h1, h2::Dispatcher};
/// `NewService` HTTP1.1/HTTP2 transport implementation
pub struct HttpService<T, P, S, B> {
pub struct HttpService<T, P, S, B, X = h1::ExpectHandler> {
srv: S,
cfg: ServiceConfig,
expect: X,
_t: PhantomData<(T, P, B)>,
}
impl<T, S, B> HttpService<T, (), S, B>
where
S: NewService<SrvConfig, Request = Request>,
S::Service: 'static,
S::Error: Debug + 'static,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
{
/// Create builder for `HttpService` instance.
@@ -43,9 +43,10 @@ where
impl<T, P, S, B> HttpService<T, P, S, B>
where
S: NewService<SrvConfig, Request = Request>,
S::Service: 'static,
S::Error: Debug + 'static,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
{
/// Create new `HttpService` instance.
@@ -55,6 +56,7 @@ where
HttpService {
cfg,
srv: service.into_new_service(),
expect: h1::ExpectHandler,
_t: PhantomData,
}
}
@@ -67,30 +69,65 @@ where
HttpService {
cfg,
srv: service.into_new_service(),
expect: h1::ExpectHandler,
_t: PhantomData,
}
}
}
impl<T, P, S, B> NewService<SrvConfig> for HttpService<T, P, S, B>
impl<T, P, S, B, X> HttpService<T, P, S, B, X>
where
T: AsyncRead + AsyncWrite + 'static,
S: NewService<SrvConfig, Request = Request>,
S::Service: 'static,
S::Error: Debug,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
{
/// Provide service for `EXPECT: 100-Continue` support.
///
/// Service get called with request that contains `EXPECT` header.
/// Service must return request in case of success, in that case
/// request will be forwarded to main service.
pub fn expect<U>(self, expect: U) -> HttpService<T, P, S, B, U>
where
U: NewService<Request = Request, Response = Request>,
U::Error: Into<Error>,
U::InitError: fmt::Debug,
{
HttpService {
expect,
cfg: self.cfg,
srv: self.srv,
_t: PhantomData,
}
}
}
impl<T, P, S, B, X> NewService<SrvConfig> for HttpService<T, P, S, B, X>
where
T: AsyncRead + AsyncWrite,
S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
{
type Request = ServerIo<T, P>;
type Response = ();
type Error = DispatchError;
type InitError = S::InitError;
type Service = HttpServiceHandler<T, P, S::Service, B>;
type Future = HttpServiceResponse<T, P, S, B>;
type InitError = ();
type Service = HttpServiceHandler<T, P, S::Service, B, X::Service>;
type Future = HttpServiceResponse<T, P, S, B, X>;
fn new_service(&self, cfg: &SrvConfig) -> Self::Future {
HttpServiceResponse {
fut: self.srv.new_service(cfg).into_future(),
fut_ex: Some(self.expect.new_service(&())),
expect: None,
cfg: Some(self.cfg.clone()),
_t: PhantomData,
}
@@ -98,74 +135,122 @@ where
}
#[doc(hidden)]
pub struct HttpServiceResponse<T, P, S: NewService<SrvConfig>, B> {
fut: <S::Future as IntoFuture>::Future,
pub struct HttpServiceResponse<T, P, S: NewService<SrvConfig>, B, X: NewService> {
fut: S::Future,
fut_ex: Option<X::Future>,
expect: Option<X::Service>,
cfg: Option<ServiceConfig>,
_t: PhantomData<(T, P, B)>,
}
impl<T, P, S, B> Future for HttpServiceResponse<T, P, S, B>
impl<T, P, S, B, X> Future for HttpServiceResponse<T, P, S, B, X>
where
T: AsyncRead + AsyncWrite,
S: NewService<SrvConfig, Request = Request>,
S::Service: 'static,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
S::Error: Debug,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
{
type Item = HttpServiceHandler<T, P, S::Service, B>;
type Error = S::InitError;
type Item = HttpServiceHandler<T, P, S::Service, B, X::Service>;
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let service = try_ready!(self.fut.poll());
if let Some(ref mut fut) = self.fut_ex {
let expect = try_ready!(fut
.poll()
.map_err(|e| log::error!("Init http service error: {:?}", e)));
self.expect = Some(expect);
self.fut_ex.take();
}
let service = try_ready!(self
.fut
.poll()
.map_err(|e| log::error!("Init http service error: {:?}", e)));
Ok(Async::Ready(HttpServiceHandler::new(
self.cfg.take().unwrap(),
service,
self.expect.take().unwrap(),
)))
}
}
/// `Service` implementation for http transport
pub struct HttpServiceHandler<T, P, S: 'static, B> {
pub struct HttpServiceHandler<T, P, S, B, X> {
srv: CloneableService<S>,
expect: CloneableService<X>,
cfg: ServiceConfig,
_t: PhantomData<(T, P, B)>,
_t: PhantomData<(T, P, B, X)>,
}
impl<T, P, S, B> HttpServiceHandler<T, P, S, B>
impl<T, P, S, B, X> HttpServiceHandler<T, P, S, B, X>
where
S: Service<Request = Request> + 'static,
S::Error: Debug,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
S::Response: Into<Response<B>>,
B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
{
fn new(cfg: ServiceConfig, srv: S) -> HttpServiceHandler<T, P, S, B> {
fn new(cfg: ServiceConfig, srv: S, expect: X) -> HttpServiceHandler<T, P, S, B, X> {
HttpServiceHandler {
cfg,
srv: CloneableService::new(srv),
expect: CloneableService::new(expect),
_t: PhantomData,
}
}
}
impl<T, P, S, B> Service for HttpServiceHandler<T, P, S, B>
impl<T, P, S, B, X> Service for HttpServiceHandler<T, P, S, B, X>
where
T: AsyncRead + AsyncWrite + 'static,
S: Service<Request = Request> + 'static,
S::Error: Debug,
T: AsyncRead + AsyncWrite,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
S::Response: Into<Response<B>>,
B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
{
type Request = ServerIo<T, P>;
type Response = ();
type Error = DispatchError;
type Future = HttpServiceHandlerResponse<T, S, B>;
type Future = HttpServiceHandlerResponse<T, S, B, X>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.srv.poll_ready().map_err(|e| {
error!("Service readiness error: {:?}", e);
DispatchError::Service
})
let ready = self
.expect
.poll_ready()
.map_err(|e| {
let e = e.into();
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service(e)
})?
.is_ready();
let ready = self
.srv
.poll_ready()
.map_err(|e| {
let e = e.into();
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service(e)
})?
.is_ready()
&& ready;
if ready {
Ok(Async::Ready(()))
} else {
Ok(Async::NotReady)
}
}
fn call(&mut self, req: Self::Request) -> Self::Future {
@@ -189,6 +274,7 @@ where
io,
self.cfg.clone(),
self.srv.clone(),
self.expect.clone(),
)),
},
_ => HttpServiceHandlerResponse {
@@ -197,43 +283,63 @@ where
BytesMut::with_capacity(14),
self.cfg.clone(),
self.srv.clone(),
self.expect.clone(),
))),
},
}
}
}
enum State<T, S: Service<Request = Request> + 'static, B: MessageBody>
enum State<T, S, B, X>
where
S::Error: fmt::Debug,
T: AsyncRead + AsyncWrite + 'static,
S: Service<Request = Request>,
S::Future: 'static,
S::Error: Into<Error>,
T: AsyncRead + AsyncWrite,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
{
H1(h1::Dispatcher<T, S, B>),
H1(h1::Dispatcher<T, S, B, X>),
H2(Dispatcher<Io<T>, S, B>),
Unknown(Option<(T, BytesMut, ServiceConfig, CloneableService<S>)>),
Unknown(
Option<(
T,
BytesMut,
ServiceConfig,
CloneableService<S>,
CloneableService<X>,
)>,
),
Handshake(Option<(Handshake<Io<T>, Bytes>, ServiceConfig, CloneableService<S>)>),
}
pub struct HttpServiceHandlerResponse<T, S, B>
pub struct HttpServiceHandlerResponse<T, S, B, X>
where
T: AsyncRead + AsyncWrite + 'static,
S: Service<Request = Request> + 'static,
S::Error: Debug,
T: AsyncRead + AsyncWrite,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
S::Response: Into<Response<B>>,
B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
{
state: State<T, S, B>,
state: State<T, S, B, X>,
}
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
impl<T, S, B> Future for HttpServiceHandlerResponse<T, S, B>
impl<T, S, B, X> Future for HttpServiceHandlerResponse<T, S, B, X>
where
T: AsyncRead + AsyncWrite,
S: Service<Request = Request> + 'static,
S::Error: Debug,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
{
type Item = ();
type Error = DispatchError;
@@ -247,7 +353,10 @@ where
loop {
unsafe {
let b = item.1.bytes_mut();
let n = { try_ready!(item.0.poll_read(b)) };
let n = try_ready!(item.0.poll_read(b));
if n == 0 {
return Ok(Async::Ready(()));
}
item.1.advance_mut(n);
if item.1.len() >= HTTP2_PREFACE.len() {
break;
@@ -257,7 +366,7 @@ where
} else {
panic!()
}
let (io, buf, cfg, srv) = data.take().unwrap();
let (io, buf, cfg, srv, expect) = data.take().unwrap();
if buf[..14] == HTTP2_PREFACE[..] {
let io = Io {
inner: io,
@@ -266,13 +375,15 @@ where
self.state =
State::Handshake(Some((server::handshake(io), cfg, srv)));
} else {
let framed = Framed::from_parts(FramedParts::with_read_buf(
self.state = State::H1(h1::Dispatcher::with_timeout(
io,
h1::Codec::new(cfg.clone()),
cfg,
buf,
));
self.state =
State::H1(h1::Dispatcher::with_timeout(framed, cfg, None, srv))
None,
srv,
expect,
))
}
self.poll()
}
@@ -328,13 +439,13 @@ impl<T: io::Write> io::Write for Io<T> {
}
}
impl<T: AsyncRead + 'static> AsyncRead for Io<T> {
impl<T: AsyncRead> AsyncRead for Io<T> {
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
self.inner.prepare_uninitialized_buffer(buf)
}
}
impl<T: AsyncWrite + 'static> AsyncWrite for Io<T> {
impl<T: AsyncWrite> AsyncWrite for Io<T> {
fn shutdown(&mut self) -> Poll<(), io::Error> {
self.inner.shutdown()
}

View File

@@ -1,5 +0,0 @@
mod senderror;
mod service;
pub use self::senderror::{SendError, SendResponse};
pub use self::service::HttpService;

View File

@@ -1,241 +0,0 @@
use std::marker::PhantomData;
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_service::{NewService, Service};
use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, Poll, Sink};
use crate::body::{BodySize, MessageBody, ResponseBody};
use crate::error::{Error, ResponseError};
use crate::h1::{Codec, Message};
use crate::response::Response;
pub struct SendError<T, R, E>(PhantomData<(T, R, E)>);
impl<T, R, E> Default for SendError<T, R, E>
where
T: AsyncRead + AsyncWrite,
E: ResponseError,
{
fn default() -> Self {
SendError(PhantomData)
}
}
impl<T, R, E> NewService for SendError<T, R, E>
where
T: AsyncRead + AsyncWrite,
E: ResponseError,
{
type Request = Result<R, (E, Framed<T, Codec>)>;
type Response = R;
type Error = (E, Framed<T, Codec>);
type InitError = ();
type Service = SendError<T, R, E>;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &()) -> Self::Future {
ok(SendError(PhantomData))
}
}
impl<T, R, E> Service for SendError<T, R, E>
where
T: AsyncRead + AsyncWrite,
E: ResponseError,
{
type Request = Result<R, (E, Framed<T, Codec>)>;
type Response = R;
type Error = (E, Framed<T, Codec>);
type Future = Either<FutureResult<R, (E, Framed<T, Codec>)>, SendErrorFut<T, R, E>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, req: Result<R, (E, Framed<T, Codec>)>) -> Self::Future {
match req {
Ok(r) => Either::A(ok(r)),
Err((e, framed)) => {
let res = e.error_response().set_body(format!("{}", e));
let (res, _body) = res.replace_body(());
Either::B(SendErrorFut {
framed: Some(framed),
res: Some((res, BodySize::Empty).into()),
err: Some(e),
_t: PhantomData,
})
}
}
}
}
pub struct SendErrorFut<T, R, E> {
res: Option<Message<(Response<()>, BodySize)>>,
framed: Option<Framed<T, Codec>>,
err: Option<E>,
_t: PhantomData<R>,
}
impl<T, R, E> Future for SendErrorFut<T, R, E>
where
E: ResponseError,
T: AsyncRead + AsyncWrite,
{
type Item = R;
type Error = (E, Framed<T, Codec>);
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(res) = self.res.take() {
if self.framed.as_mut().unwrap().force_send(res).is_err() {
return Err((self.err.take().unwrap(), self.framed.take().unwrap()));
}
}
match self.framed.as_mut().unwrap().poll_complete() {
Ok(Async::Ready(_)) => {
Err((self.err.take().unwrap(), self.framed.take().unwrap()))
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(_) => Err((self.err.take().unwrap(), self.framed.take().unwrap())),
}
}
}
pub struct SendResponse<T, B>(PhantomData<(T, B)>);
impl<T, B> Default for SendResponse<T, B> {
fn default() -> Self {
SendResponse(PhantomData)
}
}
impl<T, B> SendResponse<T, B>
where
T: AsyncRead + AsyncWrite,
B: MessageBody,
{
pub fn send(
framed: Framed<T, Codec>,
res: Response<B>,
) -> impl Future<Item = Framed<T, Codec>, Error = Error> {
// extract body from response
let (res, body) = res.replace_body(());
// write response
SendResponseFut {
res: Some(Message::Item((res, body.length()))),
body: Some(body),
framed: Some(framed),
}
}
}
impl<T, B> NewService for SendResponse<T, B>
where
T: AsyncRead + AsyncWrite,
B: MessageBody,
{
type Request = (Response<B>, Framed<T, Codec>);
type Response = Framed<T, Codec>;
type Error = Error;
type InitError = ();
type Service = SendResponse<T, B>;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &()) -> Self::Future {
ok(SendResponse(PhantomData))
}
}
impl<T, B> Service for SendResponse<T, B>
where
T: AsyncRead + AsyncWrite,
B: MessageBody,
{
type Request = (Response<B>, Framed<T, Codec>);
type Response = Framed<T, Codec>;
type Error = Error;
type Future = SendResponseFut<T, B>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, (res, framed): (Response<B>, Framed<T, Codec>)) -> Self::Future {
let (res, body) = res.replace_body(());
SendResponseFut {
res: Some(Message::Item((res, body.length()))),
body: Some(body),
framed: Some(framed),
}
}
}
pub struct SendResponseFut<T, B> {
res: Option<Message<(Response<()>, BodySize)>>,
body: Option<ResponseBody<B>>,
framed: Option<Framed<T, Codec>>,
}
impl<T, B> Future for SendResponseFut<T, B>
where
T: AsyncRead + AsyncWrite,
B: MessageBody,
{
type Item = Framed<T, Codec>;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
let mut body_ready = self.body.is_some();
let framed = self.framed.as_mut().unwrap();
// send body
if self.res.is_none() && self.body.is_some() {
while body_ready && self.body.is_some() && !framed.is_write_buf_full() {
match self.body.as_mut().unwrap().poll_next()? {
Async::Ready(item) => {
// body is done
if item.is_none() {
let _ = self.body.take();
}
framed.force_send(Message::Chunk(item))?;
}
Async::NotReady => body_ready = false,
}
}
}
// flush write buffer
if !framed.is_write_buf_empty() {
match framed.poll_complete()? {
Async::Ready(_) => {
if body_ready {
continue;
} else {
return Ok(Async::NotReady);
}
}
Async::NotReady => return Ok(Async::NotReady),
}
}
// send response
if let Some(res) = self.res.take() {
framed.force_send(res)?;
continue;
}
if self.body.is_some() {
if body_ready {
continue;
} else {
return Ok(Async::NotReady);
}
} else {
break;
}
}
Ok(Async::Ready(self.framed.take().unwrap()))
}
}

View File

@@ -4,10 +4,11 @@ use std::str::FromStr;
use bytes::Bytes;
use http::header::{self, HeaderName, HeaderValue};
use http::{HeaderMap, HttpTryFrom, Method, Uri, Version};
use http::{HttpTryFrom, Method, Uri, Version};
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use crate::cookie::{Cookie, CookieJar};
use crate::header::HeaderMap;
use crate::header::{Header, IntoHeaderValue};
use crate::payload::Payload;
use crate::Request;
@@ -177,6 +178,6 @@ impl TestRequest {
}
#[inline]
fn parts<'a>(parts: &'a mut Option<Inner>) -> &'a mut Inner {
fn parts(parts: &mut Option<Inner>) -> &mut Inner {
parts.as_mut().expect("cannot reuse test request builder")
}

View File

@@ -172,13 +172,14 @@ impl Parser {
};
if payload_len < 126 {
dst.reserve(p_len + 2 + if mask { 4 } else { 0 });
dst.put_slice(&[one, two | payload_len as u8]);
} else if payload_len <= 65_535 {
dst.reserve(p_len + 4);
dst.reserve(p_len + 4 + if mask { 4 } else { 0 });
dst.put_slice(&[one, two | 126]);
dst.put_u16_be(payload_len as u16);
} else {
dst.reserve(p_len + 10);
dst.reserve(p_len + 10 + if mask { 4 } else { 0 });
dst.put_slice(&[one, two | 127]);
dst.put_u64_be(payload_len as u64);
};
@@ -186,7 +187,7 @@ impl Parser {
if mask {
let mask = rand::random::<u32>();
dst.put_u32_le(mask);
dst.extend_from_slice(payload.as_ref());
dst.put_slice(payload.as_ref());
let pos = dst.len() - payload_len;
apply_mask(&mut dst[pos..], mask);
} else {

View File

@@ -35,10 +35,10 @@ fn test_h1_v2() {
.finish(|_| future::ok::<_, ()>(Response::Ok().body(STR)))
.map(|_| ())
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
let request = srv.get().header("x-test", "111").send();
let request = srv.get("/").header("x-test", "111").send();
let response = srv.block_on(request).unwrap();
assert!(response.status().is_success());
@@ -46,7 +46,7 @@ fn test_h1_v2() {
let bytes = srv.load_body(response).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
let response = srv.block_on(srv.post().send()).unwrap();
let response = srv.block_on(srv.post("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -61,7 +61,9 @@ fn test_connection_close() {
.finish(|_| ok::<_, ()>(Response::Ok().body(STR)))
.map(|_| ())
});
let response = srv.block_on(srv.get().close_connection().send()).unwrap();
println!("REQ: {:?}", srv.get("/").force_close());
let response = srv.block_on(srv.get("/").force_close().send()).unwrap();
println!("RES: {:?}", response);
assert!(response.status().is_success());
}

View File

@@ -16,6 +16,7 @@ use actix_http::{
body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response,
};
#[cfg(feature = "ssl")]
fn load_body<S>(stream: S) -> impl Future<Item = BytesMut, Error = PayloadError>
where
S: Stream<Item = Bytes, Error = PayloadError>,
@@ -36,7 +37,7 @@ fn test_h1() {
.h1(|_| future::ok::<_, ()>(Response::Ok().finish()))
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
}
@@ -54,7 +55,7 @@ fn test_h1_2() {
.map(|_| ())
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
}
@@ -97,7 +98,7 @@ fn test_h2() -> std::io::Result<()> {
)
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert!(response.status().is_success());
Ok(())
}
@@ -120,7 +121,7 @@ fn test_h2_1() -> std::io::Result<()> {
)
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert!(response.status().is_success());
Ok(())
}
@@ -144,7 +145,7 @@ fn test_h2_body() -> std::io::Result<()> {
)
});
let response = srv.block_on(srv.sget().send_body(data.clone())).unwrap();
let response = srv.block_on(srv.sget("/").send_body(data.clone())).unwrap();
assert!(response.status().is_success());
let body = srv.load_body(response).unwrap();
@@ -346,6 +347,7 @@ fn test_content_length() {
}
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_content_length() {
use actix_http::http::{
@@ -435,7 +437,7 @@ fn test_h1_headers() {
})
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -443,6 +445,7 @@ fn test_h1_headers() {
assert_eq!(bytes, Bytes::from(data2));
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_headers() {
let data = STR.repeat(10);
@@ -479,7 +482,7 @@ fn test_h2_headers() {
}).map_err(|_| ()))
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -515,7 +518,7 @@ fn test_h1_body() {
HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().body(STR)))
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -523,6 +526,7 @@ fn test_h1_body() {
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_body2() {
let openssl = ssl_acceptor().unwrap();
@@ -537,7 +541,7 @@ fn test_h2_body2() {
)
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -551,7 +555,7 @@ fn test_h1_head_empty() {
HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR)))
});
let response = srv.block_on(srv.head().send()).unwrap();
let response = srv.block_on(srv.head("/").send()).unwrap();
assert!(response.status().is_success());
{
@@ -567,6 +571,7 @@ fn test_h1_head_empty() {
assert!(bytes.is_empty());
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_head_empty() {
let openssl = ssl_acceptor().unwrap();
@@ -581,7 +586,7 @@ fn test_h2_head_empty() {
)
});
let response = srv.block_on(srv.shead().send()).unwrap();
let response = srv.block_on(srv.shead("/").send()).unwrap();
assert!(response.status().is_success());
assert_eq!(response.version(), http::Version::HTTP_2);
@@ -606,7 +611,7 @@ fn test_h1_head_binary() {
})
});
let response = srv.block_on(srv.head().send()).unwrap();
let response = srv.block_on(srv.head("/").send()).unwrap();
assert!(response.status().is_success());
{
@@ -622,6 +627,7 @@ fn test_h1_head_binary() {
assert!(bytes.is_empty());
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_head_binary() {
let openssl = ssl_acceptor().unwrap();
@@ -640,7 +646,7 @@ fn test_h2_head_binary() {
)
});
let response = srv.block_on(srv.shead().send()).unwrap();
let response = srv.block_on(srv.shead("/").send()).unwrap();
assert!(response.status().is_success());
{
@@ -662,7 +668,7 @@ fn test_h1_head_binary2() {
HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR)))
});
let response = srv.block_on(srv.head().send()).unwrap();
let response = srv.block_on(srv.head("/").send()).unwrap();
assert!(response.status().is_success());
{
@@ -674,6 +680,7 @@ fn test_h1_head_binary2() {
}
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_head_binary2() {
let openssl = ssl_acceptor().unwrap();
@@ -688,7 +695,7 @@ fn test_h2_head_binary2() {
)
});
let response = srv.block_on(srv.shead().send()).unwrap();
let response = srv.block_on(srv.shead("/").send()).unwrap();
assert!(response.status().is_success());
{
@@ -712,7 +719,7 @@ fn test_h1_body_length() {
})
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -720,6 +727,7 @@ fn test_h1_body_length() {
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_body_length() {
let openssl = ssl_acceptor().unwrap();
@@ -739,7 +747,7 @@ fn test_h2_body_length() {
)
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -760,7 +768,7 @@ fn test_h1_body_chunked_explicit() {
})
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
assert_eq!(
response
@@ -779,6 +787,7 @@ fn test_h1_body_chunked_explicit() {
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_body_chunked_explicit() {
let openssl = ssl_acceptor().unwrap();
@@ -801,7 +810,7 @@ fn test_h2_body_chunked_explicit() {
)
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert!(response.status().is_success());
assert!(!response.headers().contains_key(header::TRANSFER_ENCODING));
@@ -821,7 +830,7 @@ fn test_h1_body_chunked_implicit() {
})
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
assert_eq!(
response
@@ -853,14 +862,15 @@ fn test_h1_response_http_error_handling() {
}))
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
// read response
let bytes = srv.load_body(response).unwrap();
assert!(bytes.is_empty());
assert_eq!(bytes, Bytes::from_static(b"failed to parse header value"));
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_response_http_error_handling() {
let openssl = ssl_acceptor().unwrap();
@@ -885,12 +895,12 @@ fn test_h2_response_http_error_handling() {
)
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
// read response
let bytes = srv.load_body(response).unwrap();
assert!(bytes.is_empty());
assert_eq!(bytes, Bytes::from_static(b"failed to parse header value"));
}
#[test]
@@ -900,14 +910,15 @@ fn test_h1_service_error() {
.h1(|_| Err::<Response, Error>(error::ErrorBadRequest("error")))
});
let response = srv.block_on(srv.get().send()).unwrap();
assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
let response = srv.block_on(srv.get("/").send()).unwrap();
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
// read response
let bytes = srv.load_body(response).unwrap();
assert!(bytes.is_empty());
assert_eq!(bytes, Bytes::from_static(b"error"));
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_service_error() {
let openssl = ssl_acceptor().unwrap();
@@ -923,7 +934,7 @@ fn test_h2_service_error() {
)
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
// read response

View File

@@ -0,0 +1,5 @@
# Changes
## [0.1.0-alpha.1] - 2019-04-xx
* Split multipart support to separate crate

View File

@@ -0,0 +1,34 @@
[package]
name = "actix-multipart"
version = "0.1.0-alpha.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Multipart support for actix web framework."
readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-multipart/"
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
workspace = ".."
edition = "2018"
[lib]
name = "actix_multipart"
path = "src/lib.rs"
[dependencies]
actix-web = "1.0.0-alpha.3"
actix-service = "0.3.4"
bytes = "0.4"
derive_more = "0.14"
httparse = "1.3"
futures = "0.1.25"
log = "0.4"
mime = "0.3"
time = "0.1"
twoway = "0.2"
[dev-dependencies]
actix-rt = "0.2.2"
actix-http = "0.1.0-alpha.3"

View File

@@ -0,0 +1 @@
# Multipart support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-multipart)](https://crates.io/crates/actix-multipart) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

View File

@@ -0,0 +1,46 @@
//! Error and Result module
use actix_web::error::{ParseError, PayloadError};
use actix_web::http::StatusCode;
use actix_web::{HttpResponse, ResponseError};
use derive_more::{Display, From};
/// A set of errors that can occur during parsing multipart streams
#[derive(Debug, Display, From)]
pub enum MultipartError {
/// Content-Type header is not found
#[display(fmt = "No Content-type header found")]
NoContentType,
/// Can not parse Content-Type header
#[display(fmt = "Can not parse Content-Type header")]
ParseContentType,
/// Multipart boundary is not found
#[display(fmt = "Multipart boundary is not found")]
Boundary,
/// Multipart stream is incomplete
#[display(fmt = "Multipart stream is incomplete")]
Incomplete,
/// Error during field parsing
#[display(fmt = "{}", _0)]
Parse(ParseError),
/// Payload error
#[display(fmt = "{}", _0)]
Payload(PayloadError),
}
/// Return `BadRequest` for `MultipartError`
impl ResponseError for MultipartError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_multipart_error() {
let resp: HttpResponse = MultipartError::Boundary.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
}

View File

@@ -0,0 +1,54 @@
//! Multipart payload support
use bytes::Bytes;
use futures::Stream;
use actix_web::error::{Error, PayloadError};
use actix_web::{dev::Payload, FromRequest, HttpRequest};
use crate::server::Multipart;
/// Get request's payload as multipart stream
///
/// Content-type: multipart/form-data;
///
/// ## Server example
///
/// ```rust
/// # use futures::{Future, Stream};
/// # use futures::future::{ok, result, Either};
/// use actix_web::{web, HttpResponse, Error};
/// use actix_multipart as mp;
///
/// fn index(payload: mp::Multipart) -> impl Future<Item = HttpResponse, Error = Error> {
/// payload.from_err() // <- get multipart stream for current request
/// .and_then(|item| match item { // <- iterate over multipart items
/// mp::Item::Field(field) => {
/// // Field in turn is stream of *Bytes* object
/// Either::A(field.from_err()
/// .fold((), |_, chunk| {
/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk));
/// Ok::<_, Error>(())
/// }))
/// },
/// mp::Item::Nested(mp) => {
/// // Or item could be nested Multipart stream
/// Either::B(ok(()))
/// }
/// })
/// .fold((), |_, _| Ok::<_, Error>(()))
/// .map(|_| HttpResponse::Ok().into())
/// }
/// # fn main() {}
/// ```
impl<P> FromRequest<P> for Multipart
where
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
type Error = Error;
type Future = Result<Multipart, Error>;
#[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
Ok(Multipart::new(req.headers(), payload.take()))
}
}

View File

@@ -0,0 +1,6 @@
mod error;
mod extractor;
mod server;
pub use self::error::MultipartError;
pub use self::server::{Field, Item, Multipart};

View File

@@ -4,26 +4,22 @@ use std::marker::PhantomData;
use std::rc::Rc;
use std::{cmp, fmt};
use bytes::Bytes;
use bytes::{Bytes, BytesMut};
use futures::task::{current as current_task, Task};
use futures::{Async, Poll, Stream};
use httparse;
use mime;
use crate::error::{Error, MultipartError, ParseError, PayloadError};
use crate::extract::FromRequest;
use crate::http::header::{
use actix_web::error::{ParseError, PayloadError};
use actix_web::http::header::{
self, ContentDisposition, HeaderMap, HeaderName, HeaderValue,
};
use crate::http::HttpTryFrom;
use crate::service::ServiceFromRequest;
use crate::HttpMessage;
use actix_web::http::HttpTryFrom;
use crate::error::MultipartError;
const MAX_HEADERS: usize = 32;
type PayloadBuffer =
actix_http::h1::PayloadBuffer<Box<dyn Stream<Item = Bytes, Error = PayloadError>>>;
/// The server-side implementation of `multipart/form-data` requests.
///
/// This will parse the incoming stream into `MultipartItem` instances via its
@@ -37,59 +33,13 @@ pub struct Multipart {
}
/// Multipart item
pub enum MultipartItem {
pub enum Item {
/// Multipart field
Field(MultipartField),
Field(Field),
/// Nested multipart stream
Nested(Multipart),
}
/// Get request's payload as multipart stream
///
/// Content-type: multipart/form-data;
///
/// ## Server example
///
/// ```rust
/// # use futures::{Future, Stream};
/// # use futures::future::{ok, result, Either};
/// use actix_web::{web, HttpResponse, Error};
///
/// fn index(payload: web::Multipart) -> impl Future<Item = HttpResponse, Error = Error> {
/// payload.from_err() // <- get multipart stream for current request
/// .and_then(|item| match item { // <- iterate over multipart items
/// web::MultipartItem::Field(field) => {
/// // Field in turn is stream of *Bytes* object
/// Either::A(field.from_err()
/// .fold((), |_, chunk| {
/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk));
/// Ok::<_, Error>(())
/// }))
/// },
/// web::MultipartItem::Nested(mp) => {
/// // Or item could be nested Multipart stream
/// Either::B(ok(()))
/// }
/// })
/// .fold((), |_, _| Ok::<_, Error>(()))
/// .map(|_| HttpResponse::Ok().into())
/// }
/// # fn main() {}
/// ```
impl<P> FromRequest<P> for Multipart
where
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
type Error = Error;
type Future = Result<Multipart, Error>;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
let pl = req.take_payload();
Ok(Multipart::new(req.headers(), pl))
}
}
enum InnerMultipartItem {
None,
Field(Rc<RefCell<InnerField>>),
@@ -142,7 +92,7 @@ impl Multipart {
/// Extract boundary info from headers.
fn boundary(headers: &HeaderMap) -> Result<String, MultipartError> {
if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
if let Some(content_type) = headers.get(&header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
if let Ok(ct) = content_type.parse::<mime::Mime>() {
if let Some(boundary) = ct.get_param(mime::BOUNDARY) {
@@ -163,14 +113,18 @@ impl Multipart {
}
impl Stream for Multipart {
type Item = MultipartItem;
type Item = Item;
type Error = MultipartError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if let Some(err) = self.error.take() {
Err(err)
} else if self.safety.current() {
self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety)
let mut inner = self.inner.as_mut().unwrap().borrow_mut();
if let Some(payload) = inner.payload.get_mut(&self.safety) {
payload.poll_stream()?;
}
inner.poll(&self.safety)
} else {
Ok(Async::NotReady)
}
@@ -178,11 +132,18 @@ impl Stream for Multipart {
}
impl InnerMultipart {
fn read_headers(payload: &mut PayloadBuffer) -> Poll<HeaderMap, MultipartError> {
match payload.read_until(b"\r\n\r\n")? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(bytes)) => {
fn read_headers(
payload: &mut PayloadBuffer,
) -> Result<Option<HeaderMap>, MultipartError> {
match payload.read_until(b"\r\n\r\n") {
None => {
if payload.eof {
Err(MultipartError::Incomplete)
} else {
Ok(None)
}
}
Some(bytes) => {
let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS];
match httparse::parse_headers(&bytes, &mut hdrs) {
Ok(httparse::Status::Complete((_, hdrs))) => {
@@ -199,7 +160,7 @@ impl InnerMultipart {
return Err(ParseError::Header.into());
}
}
Ok(Async::Ready(headers))
Ok(Some(headers))
}
Ok(httparse::Status::Partial) => Err(ParseError::Header.into()),
Err(err) => Err(ParseError::from(err).into()),
@@ -211,23 +172,28 @@ impl InnerMultipart {
fn read_boundary(
payload: &mut PayloadBuffer,
boundary: &str,
) -> Poll<bool, MultipartError> {
) -> Result<Option<bool>, MultipartError> {
// TODO: need to read epilogue
match payload.readline()? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(chunk)) => {
match payload.readline() {
None => {
if payload.eof {
Err(MultipartError::Incomplete)
} else {
Ok(None)
}
}
Some(chunk) => {
if chunk.len() == boundary.len() + 4
&& &chunk[..2] == b"--"
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes()
{
Ok(Async::Ready(false))
Ok(Some(false))
} else if chunk.len() == boundary.len() + 6
&& &chunk[..2] == b"--"
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes()
&& &chunk[boundary.len() + 2..boundary.len() + 4] == b"--"
{
Ok(Async::Ready(true))
Ok(Some(true))
} else {
Err(MultipartError::Boundary)
}
@@ -238,11 +204,11 @@ impl InnerMultipart {
fn skip_until_boundary(
payload: &mut PayloadBuffer,
boundary: &str,
) -> Poll<bool, MultipartError> {
) -> Result<Option<bool>, MultipartError> {
let mut eof = false;
loop {
match payload.readline()? {
Async::Ready(Some(chunk)) => {
match payload.readline() {
Some(chunk) => {
if chunk.is_empty() {
//ValueError("Could not find starting boundary %r"
//% (self._boundary))
@@ -267,14 +233,19 @@ impl InnerMultipart {
}
}
}
Async::NotReady => return Ok(Async::NotReady),
Async::Ready(None) => return Err(MultipartError::Incomplete),
None => {
return if payload.eof {
Err(MultipartError::Incomplete)
} else {
Ok(None)
};
}
}
}
Ok(Async::Ready(eof))
Ok(Some(eof))
}
fn poll(&mut self, safety: &Safety) -> Poll<Option<MultipartItem>, MultipartError> {
fn poll(&mut self, safety: &Safety) -> Poll<Option<Item>, MultipartError> {
if self.state == InnerState::Eof {
Ok(Async::Ready(None))
} else {
@@ -317,7 +288,7 @@ impl InnerMultipart {
payload,
&self.boundary,
)? {
Async::Ready(eof) => {
Some(eof) => {
if eof {
self.state = InnerState::Eof;
return Ok(Async::Ready(None));
@@ -325,14 +296,14 @@ impl InnerMultipart {
self.state = InnerState::Headers;
}
}
Async::NotReady => return Ok(Async::NotReady),
None => return Ok(Async::NotReady),
}
}
// read boundary
InnerState::Boundary => {
match InnerMultipart::read_boundary(payload, &self.boundary)? {
Async::NotReady => return Ok(Async::NotReady),
Async::Ready(eof) => {
None => return Ok(Async::NotReady),
Some(eof) => {
if eof {
self.state = InnerState::Eof;
return Ok(Async::Ready(None));
@@ -347,8 +318,7 @@ impl InnerMultipart {
// read field headers for next field
if self.state == InnerState::Headers {
if let Async::Ready(headers) = InnerMultipart::read_headers(payload)?
{
if let Some(headers) = InnerMultipart::read_headers(payload)? {
self.state = InnerState::Boundary;
headers
} else {
@@ -364,7 +334,7 @@ impl InnerMultipart {
// content type
let mut mt = mime::APPLICATION_OCTET_STREAM;
if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
if let Some(content_type) = headers.get(&header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
if let Ok(ct) = content_type.parse::<mime::Mime>() {
mt = ct;
@@ -389,7 +359,7 @@ impl InnerMultipart {
self.item = InnerMultipartItem::Multipart(Rc::clone(&inner));
Ok(Async::Ready(Some(MultipartItem::Nested(Multipart {
Ok(Async::Ready(Some(Item::Nested(Multipart {
safety: safety.clone(),
error: None,
inner: Some(inner),
@@ -402,9 +372,12 @@ impl InnerMultipart {
)?));
self.item = InnerMultipartItem::Field(Rc::clone(&field));
Ok(Async::Ready(Some(MultipartItem::Field(
MultipartField::new(safety.clone(), headers, mt, field),
))))
Ok(Async::Ready(Some(Item::Field(Field::new(
safety.clone(),
headers,
mt,
field,
)))))
}
}
}
@@ -418,21 +391,21 @@ impl Drop for InnerMultipart {
}
/// A single field in a multipart stream
pub struct MultipartField {
pub struct Field {
ct: mime::Mime,
headers: HeaderMap,
inner: Rc<RefCell<InnerField>>,
safety: Safety,
}
impl MultipartField {
impl Field {
fn new(
safety: Safety,
headers: HeaderMap,
ct: mime::Mime,
inner: Rc<RefCell<InnerField>>,
) -> Self {
MultipartField {
Field {
ct,
headers,
inner,
@@ -454,7 +427,7 @@ impl MultipartField {
pub fn content_disposition(&self) -> Option<ContentDisposition> {
// RFC 7578: 'Each part MUST contain a Content-Disposition header field
// where the disposition type is "form-data".'
if let Some(content_disposition) = self.headers.get(header::CONTENT_DISPOSITION)
if let Some(content_disposition) = self.headers.get(&header::CONTENT_DISPOSITION)
{
ContentDisposition::from_raw(content_disposition).ok()
} else {
@@ -463,22 +436,28 @@ impl MultipartField {
}
}
impl Stream for MultipartField {
impl Stream for Field {
type Item = Bytes;
type Error = MultipartError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if self.safety.current() {
self.inner.borrow_mut().poll(&self.safety)
let mut inner = self.inner.borrow_mut();
if let Some(payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety)
{
payload.poll_stream()?;
}
inner.poll(&self.safety)
} else {
Ok(Async::NotReady)
}
}
}
impl fmt::Debug for MultipartField {
impl fmt::Debug for Field {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "\nMultipartField: {}", self.ct)?;
writeln!(f, "\nField: {}", self.ct)?;
writeln!(f, " boundary: {}", self.inner.borrow().boundary)?;
writeln!(f, " headers:")?;
for (key, val) in self.headers.iter() {
@@ -501,7 +480,7 @@ impl InnerField {
boundary: String,
headers: &HeaderMap,
) -> Result<InnerField, PayloadError> {
let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) {
let len = if let Some(len) = headers.get(&header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
Some(len)
@@ -532,10 +511,8 @@ impl InnerField {
if *size == 0 {
Ok(Async::Ready(None))
} else {
match payload.readany() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => Err(MultipartError::Incomplete),
Ok(Async::Ready(Some(mut chunk))) => {
match payload.read_max(*size) {
Some(mut chunk) => {
let len = cmp::min(chunk.len() as u64, *size);
*size -= len;
let ch = chunk.split_to(len as usize);
@@ -544,7 +521,13 @@ impl InnerField {
}
Ok(Async::Ready(Some(ch)))
}
Err(err) => Err(err.into()),
None => {
if payload.eof && (*size != 0) {
Err(MultipartError::Incomplete)
} else {
Ok(Async::NotReady)
}
}
}
}
}
@@ -555,16 +538,26 @@ impl InnerField {
payload: &mut PayloadBuffer,
boundary: &str,
) -> Poll<Option<Bytes>, MultipartError> {
match payload.read_until(b"\r")? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(mut chunk)) => {
match payload.read_until(b"\r") {
None => {
if payload.eof {
Err(MultipartError::Incomplete)
} else {
Ok(Async::NotReady)
}
}
Some(mut chunk) => {
if chunk.len() == 1 {
payload.unprocessed(chunk);
match payload.read_exact(boundary.len() + 4)? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(mut chunk)) => {
match payload.read_exact(boundary.len() + 4) {
None => {
if payload.eof {
Err(MultipartError::Incomplete)
} else {
Ok(Async::NotReady)
}
}
Some(mut chunk) => {
if &chunk[..2] == b"\r\n"
&& &chunk[2..4] == b"--"
&& &chunk[4..] == boundary.as_bytes()
@@ -606,10 +599,9 @@ impl InnerField {
Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)),
Async::Ready(None) => {
self.eof = true;
match payload.readline()? {
Async::NotReady => Async::NotReady,
Async::Ready(None) => Async::Ready(None),
Async::Ready(Some(line)) => {
match payload.readline() {
None => Async::Ready(None),
Some(line) => {
if line.as_ref() != b"\r\n" {
log::warn!("multipart field did not read all the data or it is malformed");
}
@@ -711,14 +703,86 @@ impl Drop for Safety {
}
}
/// Payload buffer
struct PayloadBuffer {
eof: bool,
buf: BytesMut,
stream: Box<dyn Stream<Item = Bytes, Error = PayloadError>>,
}
impl PayloadBuffer {
/// Create new `PayloadBuffer` instance
fn new<S>(stream: S) -> Self
where
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
PayloadBuffer {
eof: false,
buf: BytesMut::new(),
stream: Box::new(stream),
}
}
fn poll_stream(&mut self) -> Result<(), PayloadError> {
loop {
match self.stream.poll()? {
Async::Ready(Some(data)) => self.buf.extend_from_slice(&data),
Async::Ready(None) => {
self.eof = true;
return Ok(());
}
Async::NotReady => return Ok(()),
}
}
}
/// Read exact number of bytes
#[inline]
fn read_exact(&mut self, size: usize) -> Option<Bytes> {
if size <= self.buf.len() {
Some(self.buf.split_to(size).freeze())
} else {
None
}
}
fn read_max(&mut self, size: u64) -> Option<Bytes> {
if !self.buf.is_empty() {
let size = std::cmp::min(self.buf.len() as u64, size) as usize;
Some(self.buf.split_to(size).freeze())
} else {
None
}
}
/// Read until specified ending
pub fn read_until(&mut self, line: &[u8]) -> Option<Bytes> {
twoway::find_bytes(&self.buf, line)
.map(|idx| self.buf.split_to(idx + line.len()).freeze())
}
/// Read bytes until new line delimiter
pub fn readline(&mut self) -> Option<Bytes> {
self.read_until(b"\n")
}
/// Put unprocessed data back to the buffer
pub fn unprocessed(&mut self, data: Bytes) {
let buf = BytesMut::from(data);
let buf = std::mem::replace(&mut self.buf, buf);
self.buf.extend_from_slice(&buf);
}
}
#[cfg(test)]
mod tests {
use actix_http::h1::Payload;
use bytes::Bytes;
use futures::unsync::mpsc;
use super::*;
use crate::http::header::{DispositionParam, DispositionType};
use crate::test::run_on;
use actix_web::http::header::{DispositionParam, DispositionType};
use actix_web::test::run_on;
#[test]
fn test_boundary() {
@@ -799,9 +863,9 @@ mod tests {
);
let mut multipart = Multipart::new(&headers, payload);
match multipart.poll() {
Ok(Async::Ready(Some(item))) => match item {
MultipartItem::Field(mut field) => {
match multipart.poll().unwrap() {
Async::Ready(Some(item)) => match item {
Item::Field(mut field) => {
{
let cd = field.content_disposition().unwrap();
assert_eq!(cd.disposition, DispositionType::FormData);
@@ -813,12 +877,12 @@ mod tests {
assert_eq!(field.content_type().type_(), mime::TEXT);
assert_eq!(field.content_type().subtype(), mime::PLAIN);
match field.poll() {
Ok(Async::Ready(Some(chunk))) => assert_eq!(chunk, "test"),
match field.poll().unwrap() {
Async::Ready(Some(chunk)) => assert_eq!(chunk, "test"),
_ => unreachable!(),
}
match field.poll() {
Ok(Async::Ready(None)) => (),
match field.poll().unwrap() {
Async::Ready(None) => (),
_ => unreachable!(),
}
}
@@ -827,9 +891,9 @@ mod tests {
_ => unreachable!(),
}
match multipart.poll() {
Ok(Async::Ready(Some(item))) => match item {
MultipartItem::Field(mut field) => {
match multipart.poll().unwrap() {
Async::Ready(Some(item)) => match item {
Item::Field(mut field) => {
assert_eq!(field.content_type().type_(), mime::TEXT);
assert_eq!(field.content_type().subtype(), mime::PLAIN);
@@ -847,10 +911,110 @@ mod tests {
_ => unreachable!(),
}
match multipart.poll() {
Ok(Async::Ready(None)) => (),
match multipart.poll().unwrap() {
Async::Ready(None) => (),
_ => unreachable!(),
}
});
}
#[test]
fn test_basic() {
run_on(|| {
let (_, payload) = Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(payload.buf.len(), 0);
payload.poll_stream().unwrap();
assert_eq!(None, payload.read_max(1));
})
}
#[test]
fn test_eof() {
run_on(|| {
let (mut sender, payload) = Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_max(4));
sender.feed_data(Bytes::from("data"));
sender.feed_eof();
payload.poll_stream().unwrap();
assert_eq!(Some(Bytes::from("data")), payload.read_max(4));
assert_eq!(payload.buf.len(), 0);
assert_eq!(None, payload.read_max(1));
assert!(payload.eof);
})
}
#[test]
fn test_err() {
run_on(|| {
let (mut sender, payload) = Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_max(1));
sender.set_error(PayloadError::Incomplete(None));
payload.poll_stream().err().unwrap();
})
}
#[test]
fn test_readmax() {
run_on(|| {
let (mut sender, payload) = Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
payload.poll_stream().unwrap();
assert_eq!(payload.buf.len(), 10);
assert_eq!(Some(Bytes::from("line1")), payload.read_max(5));
assert_eq!(payload.buf.len(), 5);
assert_eq!(Some(Bytes::from("line2")), payload.read_max(5));
assert_eq!(payload.buf.len(), 0);
})
}
#[test]
fn test_readexactly() {
run_on(|| {
let (mut sender, payload) = Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_exact(2));
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
payload.poll_stream().unwrap();
assert_eq!(Some(Bytes::from_static(b"li")), payload.read_exact(2));
assert_eq!(payload.buf.len(), 8);
assert_eq!(Some(Bytes::from_static(b"ne1l")), payload.read_exact(4));
assert_eq!(payload.buf.len(), 4);
})
}
#[test]
fn test_readuntil() {
run_on(|| {
let (mut sender, payload) = Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_until(b"ne"));
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
payload.poll_stream().unwrap();
assert_eq!(Some(Bytes::from("line")), payload.read_until(b"ne"));
assert_eq!(payload.buf.len(), 6);
assert_eq!(Some(Bytes::from("1line2")), payload.read_until(b"2"));
assert_eq!(payload.buf.len(), 0);
})
}
}

View File

@@ -1,5 +1,13 @@
# Changes
## [0.1.0-alpha.4] - 2019-04-08
* Update actix-web
## [0.1.0-alpha.3] - 2019-04-02
* Update actix-web
## [0.1.0-alpha.2] - 2019-03-29
* Update actix-web

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-session"
version = "0.1.0-alpha.2"
version = "0.1.0-alpha.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Session for actix web framework."
readme = "README.md"
@@ -24,7 +24,7 @@ default = ["cookie-session"]
cookie-session = ["actix-web/secure-cookies"]
[dependencies]
actix-web = "1.0.0-alpha.2"
actix-web = "1.0.0-alpha.4"
actix-service = "0.3.4"
bytes = "0.4"
derive_more = "0.14"

View File

@@ -333,6 +333,7 @@ mod tests {
let request = test::TestRequest::get().to_request();
let response = test::block_on(app.call(request)).unwrap();
assert!(response
.response()
.cookies()
.find(|c| c.name() == "actix-session")
.is_some());
@@ -352,6 +353,7 @@ mod tests {
let request = test::TestRequest::get().to_request();
let response = test::block_on(app.call(request)).unwrap();
assert!(response
.response()
.cookies()
.find(|c| c.name() == "actix-session")
.is_some());

View File

@@ -45,8 +45,8 @@
use std::cell::RefCell;
use std::rc::Rc;
use actix_web::dev::{ServiceFromRequest, ServiceRequest, ServiceResponse};
use actix_web::{Error, FromRequest, HttpMessage};
use actix_web::dev::{Extensions, Payload, ServiceRequest, ServiceResponse};
use actix_web::{Error, FromRequest, HttpMessage, HttpRequest};
use hashbrown::HashMap;
use serde::de::DeserializeOwned;
use serde::Serialize;
@@ -123,7 +123,7 @@ impl Session {
data: impl Iterator<Item = (String, String)>,
req: &mut ServiceRequest<P>,
) {
let session = Session::get_session(req);
let session = Session::get_session(&mut *req.extensions_mut());
let mut inner = session.0.borrow_mut();
inner.state.extend(data);
}
@@ -144,12 +144,12 @@ impl Session {
}
}
fn get_session<R: HttpMessage>(req: R) -> Session {
if let Some(s_impl) = req.extensions().get::<Rc<RefCell<SessionInner>>>() {
fn get_session(extensions: &mut Extensions) -> Session {
if let Some(s_impl) = extensions.get::<Rc<RefCell<SessionInner>>>() {
return Session(Rc::clone(&s_impl));
}
let inner = Rc::new(RefCell::new(SessionInner::default()));
req.extensions_mut().insert(inner.clone());
extensions.insert(inner.clone());
Session(inner)
}
}
@@ -177,8 +177,8 @@ impl<P> FromRequest<P> for Session {
type Future = Result<Session, Error>;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
Ok(Session::get_session(req))
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
Ok(Session::get_session(&mut *req.extensions_mut()))
}
}
@@ -190,13 +190,13 @@ mod tests {
#[test]
fn session() {
let mut req = test::TestRequest::default().to_service();
let mut req = test::TestRequest::default().to_srv_request();
Session::set_session(
vec![("key".to_string(), "\"value\"".to_string())].into_iter(),
&mut req,
);
let session = Session::get_session(&mut req);
let session = Session::get_session(&mut *req.extensions_mut());
let res = session.get::<String>("key").unwrap();
assert_eq!(res, Some("value".to_string()));

View File

@@ -1,5 +1,9 @@
# Changes
## [0.1.0-alpha.3] - 2019-04-02
* Update actix-http and actix-web
## [0.1.0-alpha.2] - 2019-03-29
* Update actix-http and actix-web

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web-actors"
version = "1.0.0-alpha.2"
version = "1.0.0-alpha.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for actix web framework."
readme = "README.md"
@@ -19,12 +19,12 @@ path = "src/lib.rs"
[dependencies]
actix = "0.8.0-alpha.2"
actix-web = "1.0.0-alpha.2"
actix-http = "0.1.0-alpha.2"
actix-web = "1.0.0-alpha.3"
actix-http = "0.1.0-alpha.3"
actix-codec = "0.1.2"
bytes = "0.4"
futures = "0.1.25"
[dev-dependencies]
env_logger = "0.6"
actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }

View File

@@ -20,7 +20,7 @@ pub use actix_http::ws::{
use actix_web::dev::HttpResponseBuilder;
use actix_web::error::{Error, ErrorInternalServerError, PayloadError};
use actix_web::http::{header, Method, StatusCode};
use actix_web::{HttpMessage, HttpRequest, HttpResponse};
use actix_web::{HttpRequest, HttpResponse};
use bytes::{Bytes, BytesMut};
use futures::sync::oneshot::Sender;
use futures::{Async, Future, Poll, Stream};
@@ -50,7 +50,7 @@ pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeErro
}
// Check for "UPGRADE" to websocket header
let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) {
let has_hdr = if let Some(hdr) = req.headers().get(&header::UPGRADE) {
if let Ok(s) = hdr.to_str() {
s.to_ascii_lowercase().contains("websocket")
} else {
@@ -64,16 +64,16 @@ pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeErro
}
// Upgrade connection
if !req.upgrade() {
if !req.head().upgrade() {
return Err(HandshakeError::NoConnectionUpgrade);
}
// check supported version
if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) {
if !req.headers().contains_key(&header::SEC_WEBSOCKET_VERSION) {
return Err(HandshakeError::NoVersionHeader);
}
let supported_ver = {
if let Some(hdr) = req.headers().get(header::SEC_WEBSOCKET_VERSION) {
if let Some(hdr) = req.headers().get(&header::SEC_WEBSOCKET_VERSION) {
hdr == "13" || hdr == "8" || hdr == "7"
} else {
false
@@ -84,11 +84,11 @@ pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeErro
}
// check client handshake for validity
if !req.headers().contains_key(header::SEC_WEBSOCKET_KEY) {
if !req.headers().contains_key(&header::SEC_WEBSOCKET_KEY) {
return Err(HandshakeError::BadWebsocketKey);
}
let key = {
let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap();
let key = req.headers().get(&header::SEC_WEBSOCKET_KEY).unwrap();
hash_key(key.as_ref())
};

View File

@@ -1,5 +1,30 @@
# Changes
## [0.1.0-alpha.4] - 2019-04-08
### Changed
* Update actix-http dependency
## [0.1.0-alpha.3] - 2019-04-02
### Added
* Export `MessageBody` type
* `ClientResponse::json()` - Loads and parse `application/json` encoded body
### Changed
* `ClientRequest::json()` accepts reference instead of object.
* `ClientResponse::body()` does not consume response object.
* Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()`
## [0.1.0-alpha.2] - 2019-03-29
### Added

View File

@@ -1,6 +1,6 @@
[package]
name = "awc"
version = "0.1.0-alpha.2"
version = "0.1.0-alpha.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http client."
readme = "README.md"
@@ -38,12 +38,13 @@ flate2-rust = ["actix-http/flate2-rust"]
[dependencies]
actix-codec = "0.1.1"
actix-service = "0.3.4"
actix-http = "0.1.0-alpa.2"
actix-http = "0.1.0-alpha.4"
base64 = "0.10.1"
bytes = "0.4"
derive_more = "0.14"
futures = "0.1.25"
log =" 0.4"
mime = "0.3"
percent-encoding = "1.0"
rand = "0.6"
serde = "1.0"
@@ -54,14 +55,13 @@ openssl = { version="0.10", optional = true }
[dev-dependencies]
actix-rt = "0.2.2"
actix-web = { version = "1.0.0-alpha.2", features=["ssl"] }
actix-http = { version = "0.1.0-alpa.2", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] }
actix-web = { version = "1.0.0-alpha.4", features=["ssl"] }
actix-http = { version = "0.1.0-alpha.4", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
actix-utils = "0.3.4"
actix-server = { version = "0.4.1", features=["ssl"] }
brotli2 = { version="0.3.2" }
flate2 = { version="1.0.2" }
env_logger = "0.6"
mime = "0.3"
rand = "0.6"
tokio-tcp = "0.1"

View File

@@ -31,7 +31,7 @@ impl ClientBuilder {
headers: HeaderMap::new(),
timeout: Some(Duration::from_secs(5)),
connector: RefCell::new(Box::new(ConnectorWrapper(
Connector::new().service(),
Connector::new().finish(),
))),
},
}

View File

@@ -4,6 +4,9 @@ pub use actix_http::error::PayloadError;
pub use actix_http::ws::HandshakeError as WsHandshakeError;
pub use actix_http::ws::ProtocolError as WsProtocolError;
use actix_http::{Response, ResponseError};
use serde_json::error::Error as JsonError;
use actix_http::http::{header::HeaderValue, Error as HttpError, StatusCode};
use derive_more::{Display, From};
@@ -47,3 +50,24 @@ impl From<HttpError> for WsClientError {
WsClientError::SendRequest(err.into())
}
}
/// A set of errors that can occur during parsing json payloads
#[derive(Debug, Display, From)]
pub enum JsonPayloadError {
/// Content type error
#[display(fmt = "Content type error")]
ContentType,
/// Deserialize error
#[display(fmt = "Json deserialize error: {}", _0)]
Deserialize(JsonError),
/// Payload error
#[display(fmt = "Error that occur during reading payload: {}", _0)]
Payload(PayloadError),
}
/// Return `InternlaServerError` for `JsonPayloadError`
impl ResponseError for JsonPayloadError {
fn error_response(&self) -> Response {
Response::new(StatusCode::INTERNAL_SERVER_ERROR)
}
}

View File

@@ -39,7 +39,7 @@ pub mod ws;
pub use self::builder::ClientBuilder;
pub use self::request::ClientRequest;
pub use self::response::ClientResponse;
pub use self::response::{ClientResponse, JsonBody, MessageBody};
use self::connect::{Connect, ConnectorWrapper};
@@ -78,7 +78,7 @@ impl Default for Client {
fn default() -> Self {
Client(Rc::new(ClientConfig {
connector: RefCell::new(Box::new(ConnectorWrapper(
Connector::new().service(),
Connector::new().finish(),
))),
headers: HeaderMap::new(),
timeout: Some(Duration::from_secs(5)),
@@ -104,8 +104,8 @@ impl Client {
{
let mut req = ClientRequest::new(method, url, self.0.clone());
for (key, value) in &self.0.headers {
req.head.headers.insert(key.clone(), value.clone());
for (key, value) in self.0.headers.iter() {
req = req.set_header_if_none(key.clone(), value.clone());
}
req
}
@@ -119,8 +119,8 @@ impl Client {
Uri: HttpTryFrom<U>,
{
let mut req = self.request(head.method.clone(), url);
for (key, value) in &head.headers {
req.head.headers.insert(key.clone(), value.clone());
for (key, value) in head.headers.iter() {
req = req.set_header_if_none(key.clone(), value.clone());
}
req
}
@@ -187,7 +187,7 @@ impl Client {
Uri: HttpTryFrom<U>,
{
let mut req = ws::WebsocketsRequest::new(url, self.0.clone());
for (key, value) in &self.0.headers {
for (key, value) in self.0.headers.iter() {
req.head.headers.insert(key.clone(), value.clone());
}
req

View File

@@ -17,8 +17,8 @@ use actix_http::cookie::{Cookie, CookieJar};
use actix_http::encoding::Decoder;
use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue};
use actix_http::http::{
uri, ConnectionType, Error as HttpError, HeaderName, HeaderValue, HttpTryFrom,
Method, Uri, Version,
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue,
HttpTryFrom, Method, Uri, Version,
};
use actix_http::{Error, Payload, RequestHead};
@@ -116,6 +116,18 @@ impl ClientRequest {
self
}
#[inline]
/// Returns request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.head.headers
}
#[inline]
/// Returns request's mutable headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.head.headers
}
/// Set a header.
///
/// ```rust
@@ -165,7 +177,7 @@ impl ClientRequest {
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
self.head.headers.append(key, value);
let _ = self.head.headers.append(key, value);
}
Err(e) => self.err = Some(e.into()),
},
@@ -183,7 +195,7 @@ impl ClientRequest {
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
self.head.headers.insert(key, value);
let _ = self.head.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
},
@@ -203,7 +215,7 @@ impl ClientRequest {
if !self.head.headers.contains_key(&key) {
match value.try_into() {
Ok(value) => {
self.head.headers.insert(key, value);
let _ = self.head.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
}
@@ -214,9 +226,10 @@ impl ClientRequest {
self
}
/// Close connection
/// Force close connection instead of returning it back to connections pool.
/// This setting affect only http/1 connections.
#[inline]
pub fn close_connection(mut self) -> Self {
pub fn force_close(mut self) -> Self {
self.head.set_connection_type(ConnectionType::Close);
self
}
@@ -380,10 +393,10 @@ impl ClientRequest {
}
// set default headers
let slf = if self.default_headers {
if self.default_headers {
// set request host header
if let Some(host) = self.head.uri.host() {
if !self.head.headers.contains_key(header::HOST) {
if !self.head.headers.contains_key(&header::HOST) {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match self.head.uri.port_u16() {
@@ -401,28 +414,44 @@ impl ClientRequest {
}
// user agent
self.set_header_if_none(
header::USER_AGENT,
concat!("awc/", env!("CARGO_PKG_VERSION")),
)
} else {
self
};
if !self.head.headers.contains_key(&header::USER_AGENT) {
self.head.headers.insert(
header::USER_AGENT,
HeaderValue::from_static(concat!("awc/", env!("CARGO_PKG_VERSION"))),
);
}
}
// set cookies
if let Some(ref mut jar) = self.cookies {
let mut cookie = String::new();
for c in jar.delta() {
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
let _ = write!(&mut cookie, "; {}={}", name, value);
}
self.head.headers.insert(
header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
}
let slf = self;
// enable br only for https
let https = slf
.head
.uri
.scheme_part()
.map(|s| s == &uri::Scheme::HTTPS)
.unwrap_or(true);
#[cfg(any(
feature = "brotli",
feature = "flate2-zlib",
feature = "flate2-rust"
))]
let mut slf = {
let slf = {
let https = slf
.head
.uri
.scheme_part()
.map(|s| s == &uri::Scheme::HTTPS)
.unwrap_or(true);
if https {
slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING)
} else {
@@ -435,23 +464,8 @@ impl ClientRequest {
}
};
let mut head = slf.head;
// set cookies
if let Some(ref mut jar) = slf.cookies {
let mut cookie = String::new();
for c in jar.delta() {
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
let _ = write!(&mut cookie, "; {}={}", name, value);
}
head.headers.insert(
header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
}
let config = slf.config;
let head = slf.head;
let config = slf.config.as_ref();
let response_decompress = slf.response_decompress;
let fut = config
@@ -485,21 +499,18 @@ impl ClientRequest {
/// Set a JSON body and generate `ClientRequest`
pub fn send_json<T: Serialize>(
self,
value: T,
value: &T,
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
> {
let body = match serde_json::to_string(&value) {
let body = match serde_json::to_string(value) {
Ok(body) => body,
Err(e) => return Either::A(err(Error::from(e).into())),
};
// set content-type
let slf = if !self.head.headers.contains_key(header::CONTENT_TYPE) {
self.header(header::CONTENT_TYPE, "application/json")
} else {
self
};
let slf = self.set_header_if_none(header::CONTENT_TYPE, "application/json");
Either::B(slf.send_body(Body::Bytes(Bytes::from(body))))
}
@@ -509,21 +520,21 @@ impl ClientRequest {
/// `ClientRequestBuilder` can not be used after this call.
pub fn send_form<T: Serialize>(
self,
value: T,
value: &T,
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
> {
let body = match serde_urlencoded::to_string(&value) {
let body = match serde_urlencoded::to_string(value) {
Ok(body) => body,
Err(e) => return Either::A(err(Error::from(e).into())),
};
let slf = if !self.head.headers.contains_key(header::CONTENT_TYPE) {
self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
} else {
self
};
// set content-type
let slf = self.set_header_if_none(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
);
Either::B(slf.send_body(Body::Bytes(Bytes::from(body))))
}
@@ -628,12 +639,11 @@ mod tests {
#[test]
fn client_basic_auth() {
test::run_on(|| {
let client = Client::new()
let req = Client::new()
.get("/")
.basic_auth("username", Some("password"));
assert_eq!(
client
.head
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
@@ -642,10 +652,9 @@ mod tests {
"Basic dXNlcm5hbWU6cGFzc3dvcmQ="
);
let client = Client::new().get("/").basic_auth("username", None);
let req = Client::new().get("/").basic_auth("username", None);
assert_eq!(
client
.head
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
@@ -659,10 +668,9 @@ mod tests {
#[test]
fn client_bearer_auth() {
test::run_on(|| {
let client = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n");
let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n");
assert_eq!(
client
.head
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()

View File

@@ -1,16 +1,18 @@
use std::cell::{Ref, RefMut};
use std::fmt;
use std::marker::PhantomData;
use bytes::{Bytes, BytesMut};
use futures::{Future, Poll, Stream};
use futures::{Async, Future, Poll, Stream};
use actix_http::error::PayloadError;
use actix_http::cookie::Cookie;
use actix_http::error::{CookieParseError, PayloadError};
use actix_http::http::header::{CONTENT_LENGTH, SET_COOKIE};
use actix_http::http::{HeaderMap, StatusCode, Version};
use actix_http::{Extensions, HttpMessage, Payload, PayloadStream, ResponseHead};
use serde::de::DeserializeOwned;
use actix_http::cookie::Cookie;
use actix_http::error::CookieParseError;
use crate::error::JsonPayloadError;
/// Client Response
pub struct ClientResponse<S = PayloadStream> {
@@ -44,7 +46,7 @@ impl<S> HttpMessage for ClientResponse<S> {
if self.extensions().get::<Cookies>().is_none() {
let mut cookies = Vec::new();
for hdr in self.headers().get_all(SET_COOKIE) {
for hdr in self.headers().get_all(&SET_COOKIE) {
let s = std::str::from_utf8(hdr.as_bytes())
.map_err(CookieParseError::from)?;
cookies.push(Cookie::parse_encoded(s)?.into_owned());
@@ -81,7 +83,7 @@ impl<S> ClientResponse<S> {
}
#[inline]
/// Returns Request's headers.
/// Returns request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.head().headers
}
@@ -102,12 +104,23 @@ impl<S> ClientResponse<S> {
impl<S> ClientResponse<S>
where
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
S: Stream<Item = Bytes, Error = PayloadError>,
{
/// Load http response's body.
pub fn body(self) -> MessageBody<S> {
/// Loads http response's body.
pub fn body(&mut self) -> MessageBody<S> {
MessageBody::new(self)
}
/// Loads and parse `application/json` encoded body.
/// Return `JsonBody<T>` future. It resolves to a `T` value.
///
/// Returns error:
///
/// * content type is not `application/json`
/// * content length is greater than 256k
pub fn json<T: DeserializeOwned>(&mut self) -> JsonBody<S, T> {
JsonBody::new(self)
}
}
impl<S> Stream for ClientResponse<S>
@@ -135,21 +148,19 @@ impl<S> fmt::Debug for ClientResponse<S> {
/// Future that resolves to a complete http message body.
pub struct MessageBody<S> {
limit: usize,
length: Option<usize>,
stream: Option<ClientResponse<S>>,
err: Option<PayloadError>,
fut: Option<Box<Future<Item = Bytes, Error = PayloadError>>>,
fut: Option<ReadBody<S>>,
}
impl<S> MessageBody<S>
where
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
S: Stream<Item = Bytes, Error = PayloadError>,
{
/// Create `MessageBody` for request.
pub fn new(res: ClientResponse<S>) -> MessageBody<S> {
pub fn new(res: &mut ClientResponse<S>) -> MessageBody<S> {
let mut len = None;
if let Some(l) = res.headers().get(CONTENT_LENGTH) {
if let Some(l) = res.headers().get(&CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() {
len = Some(l)
@@ -162,24 +173,22 @@ where
}
MessageBody {
limit: 262_144,
length: len,
stream: Some(res),
fut: None,
err: None,
fut: Some(ReadBody::new(res.take_payload(), 262_144)),
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
if let Some(ref mut fut) = self.fut {
fut.limit = limit;
}
self
}
fn err(e: PayloadError) -> Self {
MessageBody {
stream: None,
limit: 262_144,
fut: None,
err: Some(e),
length: None,
@@ -189,44 +198,149 @@ where
impl<S> Future for MessageBody<S>
where
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
S: Stream<Item = Bytes, Error = PayloadError>,
{
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut {
return fut.poll();
}
if let Some(err) = self.err.take() {
return Err(err);
}
if let Some(len) = self.length.take() {
if len > self.limit {
if len > self.fut.as_ref().unwrap().limit {
return Err(PayloadError::Overflow);
}
}
// future
let limit = self.limit;
self.fut = Some(Box::new(
self.stream
.take()
.expect("Can not be used second time")
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
self.fut.as_mut().unwrap().poll()
}
}
/// Response's payload json parser, it resolves to a deserialized `T` value.
///
/// Returns error:
///
/// * content type is not `application/json`
/// * content length is greater than 64k
pub struct JsonBody<S, U> {
length: Option<usize>,
err: Option<JsonPayloadError>,
fut: Option<ReadBody<S>>,
_t: PhantomData<U>,
}
impl<S, U> JsonBody<S, U>
where
S: Stream<Item = Bytes, Error = PayloadError>,
U: DeserializeOwned,
{
/// Create `JsonBody` for request.
pub fn new(req: &mut ClientResponse<S>) -> Self {
// check content-type
let json = if let Ok(Some(mime)) = req.mime_type() {
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
} else {
false
};
if !json {
return JsonBody {
length: None,
fut: None,
err: Some(JsonPayloadError::ContentType),
_t: PhantomData,
};
}
let mut len = None;
if let Some(l) = req.headers().get(&CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() {
len = Some(l)
}
}
}
JsonBody {
length: len,
err: None,
fut: Some(ReadBody::new(req.take_payload(), 65536)),
_t: PhantomData,
}
}
/// Change max size of payload. By default max size is 64Kb
pub fn limit(mut self, limit: usize) -> Self {
if let Some(ref mut fut) = self.fut {
fut.limit = limit;
}
self
}
}
impl<T, U> Future for JsonBody<T, U>
where
T: Stream<Item = Bytes, Error = PayloadError>,
U: DeserializeOwned,
{
type Item = U;
type Error = JsonPayloadError;
fn poll(&mut self) -> Poll<U, JsonPayloadError> {
if let Some(err) = self.err.take() {
return Err(err);
}
if let Some(len) = self.length.take() {
if len > self.fut.as_ref().unwrap().limit {
return Err(JsonPayloadError::Payload(PayloadError::Overflow));
}
}
let body = futures::try_ready!(self.fut.as_mut().unwrap().poll());
Ok(Async::Ready(serde_json::from_slice::<U>(&body)?))
}
}
struct ReadBody<S> {
stream: Payload<S>,
buf: BytesMut,
limit: usize,
}
impl<S> ReadBody<S> {
fn new(stream: Payload<S>, limit: usize) -> Self {
Self {
stream,
buf: BytesMut::with_capacity(std::cmp::min(limit, 32768)),
limit,
}
}
}
impl<S> Future for ReadBody<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
return match self.stream.poll()? {
Async::Ready(Some(chunk)) => {
if (self.buf.len() + chunk.len()) > self.limit {
Err(PayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
self.buf.extend_from_slice(&chunk);
continue;
}
})
.map(|body| body.freeze()),
));
self.poll()
}
Async::Ready(None) => Ok(Async::Ready(self.buf.take().freeze())),
Async::NotReady => Ok(Async::NotReady),
};
}
}
}
@@ -234,24 +348,26 @@ where
mod tests {
use super::*;
use futures::Async;
use serde::{Deserialize, Serialize};
use crate::{http::header, test::TestResponse};
use crate::{http::header, test::block_on, test::TestResponse};
#[test]
fn test_body() {
let req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish();
let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish();
match req.body().poll().err().unwrap() {
PayloadError::UnknownLength => (),
_ => unreachable!("error"),
}
let req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish();
let mut req =
TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish();
match req.body().poll().err().unwrap() {
PayloadError::Overflow => (),
_ => unreachable!("error"),
}
let req = TestResponse::default()
let mut req = TestResponse::default()
.set_payload(Bytes::from_static(b"test"))
.finish();
match req.body().poll().ok().unwrap() {
@@ -259,7 +375,7 @@ mod tests {
_ => unreachable!("error"),
}
let req = TestResponse::default()
let mut req = TestResponse::default()
.set_payload(Bytes::from_static(b"11111111111111"))
.finish();
match req.body().limit(5).poll().err().unwrap() {
@@ -267,4 +383,76 @@ mod tests {
_ => unreachable!("error"),
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct MyObject {
name: String,
}
fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool {
match err {
JsonPayloadError::Payload(PayloadError::Overflow) => match other {
JsonPayloadError::Payload(PayloadError::Overflow) => true,
_ => false,
},
JsonPayloadError::ContentType => match other {
JsonPayloadError::ContentType => true,
_ => false,
},
_ => false,
}
}
#[test]
fn test_json_body() {
let mut req = TestResponse::default().finish();
let json = block_on(JsonBody::<_, MyObject>::new(&mut req));
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let mut req = TestResponse::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/text"),
)
.finish();
let json = block_on(JsonBody::<_, MyObject>::new(&mut req));
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let mut req = TestResponse::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000"),
)
.finish();
let json = block_on(JsonBody::<_, MyObject>::new(&mut req).limit(100));
assert!(json_eq(
json.err().unwrap(),
JsonPayloadError::Payload(PayloadError::Overflow)
));
let mut req = TestResponse::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
let json = block_on(JsonBody::<_, MyObject>::new(&mut req));
assert_eq!(
json.ok().unwrap(),
MyObject {
name: "test".to_owned()
}
);
}
}

View File

@@ -3,9 +3,11 @@ use std::fmt::Write as FmtWrite;
use actix_http::cookie::{Cookie, CookieJar};
use actix_http::http::header::{self, Header, HeaderValue, IntoHeaderValue};
use actix_http::http::{HeaderName, HttpTryFrom, Version};
use actix_http::http::{HeaderName, HttpTryFrom, StatusCode, Version};
use actix_http::{h1, Payload, ResponseHead};
use bytes::Bytes;
#[cfg(test)]
use futures::Future;
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use crate::ClientResponse;
@@ -18,7 +20,7 @@ thread_local! {
}
#[cfg(test)]
pub fn run_on<F, R>(f: F) -> R
pub(crate) fn run_on<F, R>(f: F) -> R
where
F: Fn() -> R,
{
@@ -29,6 +31,14 @@ where
.unwrap()
}
#[cfg(test)]
pub(crate) fn block_on<F>(f: F) -> Result<F::Item, F::Error>
where
F: Future,
{
RT.with(move |rt| rt.borrow_mut().block_on(f))
}
/// Test `ClientResponse` builder
pub struct TestResponse {
head: ResponseHead,
@@ -39,7 +49,7 @@ pub struct TestResponse {
impl Default for TestResponse {
fn default() -> TestResponse {
TestResponse {
head: ResponseHead::default(),
head: ResponseHead::new(StatusCode::OK),
cookies: CookieJar::new(),
payload: None,
}

View File

@@ -70,7 +70,7 @@ impl WebsocketsRequest {
/// Set supported websocket protocols
pub fn protocols<U, V>(mut self, protos: U) -> Self
where
U: IntoIterator<Item = V> + 'static,
U: IntoIterator<Item = V>,
V: AsRef<str>,
{
let mut protos = protos
@@ -236,7 +236,7 @@ impl WebsocketsRequest {
let mut slf = if self.default_headers {
// set request host header
if let Some(host) = self.head.uri.host() {
if !self.head.headers.contains_key(header::HOST) {
if !self.head.headers.contains_key(&header::HOST) {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match self.head.uri.port_u16() {
@@ -324,7 +324,7 @@ impl WebsocketsRequest {
return Err(WsClientError::InvalidResponseStatus(head.status));
}
// Check for "UPGRADE" to websocket header
let has_hdr = if let Some(hdr) = head.headers.get(header::UPGRADE) {
let has_hdr = if let Some(hdr) = head.headers.get(&header::UPGRADE) {
if let Ok(s) = hdr.to_str() {
s.to_ascii_lowercase().contains("websocket")
} else {
@@ -338,7 +338,7 @@ impl WebsocketsRequest {
return Err(WsClientError::InvalidUpgradeHeader);
}
// Check for "CONNECTION" header
if let Some(conn) = head.headers.get(header::CONNECTION) {
if let Some(conn) = head.headers.get(&header::CONNECTION) {
if let Ok(s) = conn.to_str() {
if !s.to_ascii_lowercase().contains("upgrade") {
log::trace!("Invalid connection header: {}", s);
@@ -355,7 +355,7 @@ impl WebsocketsRequest {
return Err(WsClientError::MissingConnectionHeader);
}
if let Some(hdr_key) = head.headers.get(header::SEC_WEBSOCKET_ACCEPT) {
if let Some(hdr_key) = head.headers.get(&header::SEC_WEBSOCKET_ACCEPT) {
let encoded = ws::hash_key(key.as_ref());
if hdr_key.as_bytes() != encoded.as_bytes() {
log::trace!(

View File

@@ -44,15 +44,15 @@ fn test_simple() {
))
});
let request = srv.get().header("x-test", "111").send();
let response = srv.block_on(request).unwrap();
let request = srv.get("/").header("x-test", "111").send();
let mut response = srv.block_on(request).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
let response = srv.block_on(srv.post().send()).unwrap();
let mut response = srv.block_on(srv.post("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -114,7 +114,7 @@ fn test_timeout_override() {
// let mut srv =
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
// let request = srv.get().header("Connection", "close").finish().unwrap();
// let request = srv.get("/").header("Connection", "close").finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
// }
@@ -128,7 +128,7 @@ fn test_timeout_override() {
// })
// });
// let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap();
// let request = srv.get("/").uri(srv.url("/?qp=5").as_str()).finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
@@ -139,7 +139,7 @@ fn test_timeout_override() {
// let mut srv =
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
// let request = srv.get().disable_decompress().finish().unwrap();
// let request = srv.get("/").disable_decompress().finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
@@ -177,7 +177,7 @@ fn test_client_gzip_encoding() {
});
// client request
let response = srv.block_on(srv.post().send()).unwrap();
let mut response = srv.block_on(srv.post("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -200,7 +200,7 @@ fn test_client_gzip_encoding_large() {
});
// client request
let response = srv.block_on(srv.post().send()).unwrap();
let mut response = srv.block_on(srv.post("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -229,7 +229,7 @@ fn test_client_gzip_encoding_large_random() {
});
// client request
let response = srv.block_on(srv.post().send_body(data.clone())).unwrap();
let mut response = srv.block_on(srv.post("/").send_body(data.clone())).unwrap();
assert!(response.status().is_success());
// read response
@@ -253,7 +253,7 @@ fn test_client_brotli_encoding() {
});
// client request
let response = srv.block_on(srv.post().send_body(STR)).unwrap();
let mut response = srv.block_on(srv.post("/").send_body(STR)).unwrap();
assert!(response.status().is_success());
// read response
@@ -375,7 +375,7 @@ fn test_client_brotli_encoding() {
// let body = once(Ok(Bytes::from_static(STR.as_ref())));
// let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap();
// let request = srv.get("/").body(Body::Streaming(Box::new(body))).unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
@@ -395,7 +395,7 @@ fn test_client_brotli_encoding() {
// })
// });
// let request = srv.get().finish().unwrap();
// let request = srv.get("/").finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
@@ -459,7 +459,7 @@ fn test_client_cookie_handling() {
))
});
let request = srv.get().cookie(cookie1.clone()).cookie(cookie2.clone());
let request = srv.get("/").cookie(cookie1.clone()).cookie(cookie2.clone());
let response = srv.block_on(request.send()).unwrap();
assert!(response.status().is_success());
let c1 = response.cookie("cookie1").expect("Missing cookie1");
@@ -472,7 +472,7 @@ fn test_client_cookie_handling() {
// fn test_default_headers() {
// let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
// let request = srv.get().finish().unwrap();
// let request = srv.get("/").finish().unwrap();
// let repr = format!("{:?}", request);
// assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\""));
// assert!(repr.contains(concat!(
@@ -482,7 +482,7 @@ fn test_client_cookie_handling() {
// )));
// let request_override = srv
// .get()
// .get("/")
// .header("User-Agent", "test")
// .header("Accept-Encoding", "over_test")
// .finish()
@@ -551,7 +551,7 @@ fn client_basic_auth() {
});
// set authorization header to Basic <base64 encoded username:password>
let request = srv.get().basic_auth("username", Some("password"));
let request = srv.get("/").basic_auth("username", Some("password"));
let response = srv.block_on(request.send()).unwrap();
assert!(response.status().is_success());
}
@@ -579,7 +579,7 @@ fn client_bearer_auth() {
});
// set authorization header to Bearer <token>
let request = srv.get().bearer_auth("someS3cr3tAutht0k3n");
let request = srv.get("/").bearer_auth("someS3cr3tAutht0k3n");
let response = srv.block_on(request.send()).unwrap();
assert!(response.status().is_success());
}

View File

@@ -11,7 +11,7 @@ use futures::future::{ok, Either};
use futures::{Future, Sink, Stream};
use tokio_tcp::TcpStream;
use actix_http::{h1, ws, ResponseError, SendResponse, ServiceConfig};
use actix_http::{body::BodySize, h1, ws, ResponseError, ServiceConfig};
fn ws_service(req: ws::Frame) -> impl Future<Item = ws::Message, Error = io::Error> {
match req {
@@ -46,26 +46,34 @@ fn test_simple() {
match ws::verify_handshake(&req) {
Err(e) => {
// validation failed
let res = e.error_response();
Either::A(
SendResponse::send(framed, e.error_response())
framed
.send(h1::Message::Item((
res.drop_body(),
BodySize::Empty,
)))
.map_err(|_| ())
.map(|_| ()),
)
}
Ok(_) => {
let res = ws::handshake_response(&req).finish();
Either::B(
// send handshake response
SendResponse::send(
framed,
ws::handshake_response(&req).finish(),
)
.map_err(|_| ())
.and_then(|framed| {
// start websocket service
let framed = framed.into_framed(ws::Codec::new());
ws::Transport::with(framed, ws_service)
.map_err(|_| ())
}),
framed
.send(h1::Message::Item((
res.drop_body(),
BodySize::None,
)))
.map_err(|_| ())
.and_then(|framed| {
// start websocket service
let framed =
framed.into_framed(ws::Codec::new());
ws::Transport::with(framed, ws_service)
.map_err(|_| ())
}),
)
}
}

View File

@@ -8,14 +8,14 @@ use actix_http::encoding::{Decoder, Encoder};
use actix_server_config::ServerConfig;
use actix_service::boxed::{self, BoxedNewService};
use actix_service::{
ApplyTransform, IntoNewService, IntoTransform, NewService, Transform,
apply_transform, IntoNewService, IntoTransform, NewService, Transform,
};
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
use bytes::Bytes;
use futures::{IntoFuture, Stream};
use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory};
use crate::config::{AppConfig, AppConfigInner};
use crate::config::{AppConfig, AppConfigInner, RouterConfig};
use crate::data::{Data, DataFactory};
use crate::dev::{Payload, PayloadStream, ResourceDef};
use crate::error::{Error, PayloadError};
@@ -112,7 +112,29 @@ where
self
}
/// Register a middleware.
/// Registers middleware, in the form of a middleware component (type),
/// that runs during inbound and/or outbound processing in the request
/// lifecycle (request -> response), modifying request/response as
/// necessary, across all requests managed by the *Application*.
///
/// Use middleware when you need to read or modify *every* request or response in some way.
///
/// ```rust
/// use actix_service::Service;
/// # use futures::Future;
/// use actix_web::{middleware, web, App};
/// use actix_web::http::{header::CONTENT_TYPE, HeaderValue};
///
/// fn index() -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .wrap(middleware::Logger::default())
/// .route("/index.html", web::get().to(index));
/// }
/// ```
pub fn wrap<M, B, F>(
self,
mw: F,
@@ -138,7 +160,7 @@ where
F: IntoTransform<M, AppRouting<Out>>,
{
let fref = Rc::new(RefCell::new(None));
let endpoint = ApplyTransform::new(mw, AppEntry::new(fref.clone()));
let endpoint = apply_transform(mw, AppEntry::new(fref.clone()));
AppRouter {
endpoint,
chain: self.chain,
@@ -152,7 +174,12 @@ where
}
}
/// Register a middleware function.
/// Registers middleware, in the form of a closure, that runs during inbound
/// and/or outbound processing in the request lifecycle (request -> response),
/// modifying request/response as necessary, across all requests managed by
/// the *Application*.
///
/// Use middleware when you need to read or modify *every* request or response in some way.
///
/// ```rust
/// use actix_service::Service;
@@ -230,6 +257,55 @@ where
}
}
/// Run external configuration as part of the application building
/// process
///
/// This function is useful for moving parts of configuration to a
/// different module or even library. For example,
/// some of the resource's configuration could be moved to different module.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{web, middleware, App, HttpResponse};
///
/// // this function could be located in different module
/// fn config<P>(cfg: &mut web::RouterConfig<P>) {
/// cfg.service(web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
///
/// fn main() {
/// let app = App::new()
/// .wrap(middleware::Logger::default())
/// .configure(config) // <- register resources
/// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
/// }
/// ```
pub fn configure<F>(mut self, f: F) -> AppRouter<T, Out, Body, AppEntry<Out>>
where
F: Fn(&mut RouterConfig<Out>),
{
let mut cfg = RouterConfig::new();
f(&mut cfg);
self.data.extend(cfg.data);
let fref = Rc::new(RefCell::new(None));
AppRouter {
chain: self.chain,
default: None,
endpoint: AppEntry::new(fref.clone()),
factory_ref: fref,
data: self.data,
config: self.config,
services: cfg.services,
external: cfg.external,
_t: PhantomData,
}
}
/// Configure route for a specific path.
///
/// This is a simplified version of the `App::service()` method.
@@ -355,6 +431,45 @@ where
InitError = (),
>,
{
/// Run external configuration as part of the application building
/// process
///
/// This function is useful for moving parts of configuration to a
/// different module or even library. For example,
/// some of the resource's configuration could be moved to different module.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{web, middleware, App, HttpResponse};
///
/// // this function could be located in different module
/// fn config<P>(cfg: &mut web::RouterConfig<P>) {
/// cfg.service(web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
///
/// fn main() {
/// let app = App::new()
/// .wrap(middleware::Logger::default())
/// .configure(config) // <- register resources
/// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
/// }
/// ```
pub fn configure<F>(mut self, f: F) -> Self
where
F: Fn(&mut RouterConfig<P>),
{
let mut cfg = RouterConfig::new();
f(&mut cfg);
self.data.extend(cfg.data);
self.services.extend(cfg.services);
self.external.extend(cfg.external);
self
}
/// Configure route for a specific path.
///
/// This is a simplified version of the `App::service()` method.
@@ -400,7 +515,13 @@ where
self
}
/// Register a middleware.
/// Registers middleware, in the form of a middleware component (type),
/// that runs during inbound and/or outbound processing in the request
/// lifecycle (request -> response), modifying request/response as
/// necessary, across all requests managed by the *Route*.
///
/// Use middleware when you need to read or modify *every* request or response in some way.
///
pub fn wrap<M, B1, F>(
self,
mw: F,
@@ -426,7 +547,7 @@ where
B1: MessageBody,
F: IntoTransform<M, T::Service>,
{
let endpoint = ApplyTransform::new(mw, self.endpoint);
let endpoint = apply_transform(mw, self.endpoint);
AppRouter {
endpoint,
chain: self.chain,
@@ -440,7 +561,13 @@ where
}
}
/// Register a middleware function.
/// Registers middleware, in the form of a closure, that runs during inbound
/// and/or outbound processing in the request lifecycle (request -> response),
/// modifying request/response as necessary, across all requests managed by
/// the *Route*.
///
/// Use middleware when you need to read or modify *every* request or response in some way.
///
pub fn wrap_fn<B1, F, R>(
self,
mw: F,

View File

@@ -14,6 +14,7 @@ use crate::config::{AppConfig, ServiceConfig};
use crate::data::{DataFactory, DataFactoryResult};
use crate::error::Error;
use crate::guard::Guard;
use crate::request::{HttpRequest, HttpRequestPool};
use crate::rmap::ResourceMap;
use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse};
@@ -21,7 +22,10 @@ type Guards = Vec<Box<Guard>>;
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>;
type HttpNewService<P> =
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
type BoxedResponse = Box<Future<Item = ServiceResponse, Error = Error>>;
type BoxedResponse = Either<
FutureResult<ServiceResponse, Error>,
Box<Future<Item = ServiceResponse, Error = Error>>,
>;
/// Service factory to convert `Request` to a `ServiceRequest<S>`.
/// It also executes data factories.
@@ -191,6 +195,7 @@ where
chain: self.chain.take().unwrap(),
rmap: self.rmap.clone(),
config: self.config.clone(),
pool: HttpRequestPool::create(),
}
.and_then(self.endpoint.take().unwrap()),
))
@@ -208,6 +213,7 @@ where
chain: C,
rmap: Rc<ResourceMap>,
config: AppConfig,
pool: &'static HttpRequestPool,
}
impl<C, P> Service for AppInitService<C, P>
@@ -224,13 +230,24 @@ where
}
fn call(&mut self, req: Request) -> Self::Future {
let req = ServiceRequest::new(
Path::new(Url::new(req.uri().clone())),
req,
self.rmap.clone(),
self.config.clone(),
);
self.chain.call(req)
let (head, payload) = req.into_parts();
let req = if let Some(mut req) = self.pool.get_request() {
let inner = Rc::get_mut(&mut req.0).unwrap();
inner.path.get_mut().update(&head.uri);
inner.path.reset();
inner.head = head;
req
} else {
HttpRequest::new(
Path::new(Url::new(head.uri.clone())),
head,
self.rmap.clone(),
self.config.clone(),
self.pool,
)
};
self.chain.call(ServiceRequest::from_parts(req, payload))
}
}
@@ -353,7 +370,7 @@ impl<P> Service for AppRouting<P> {
type Request = ServiceRequest<P>;
type Response = ServiceResponse;
type Error = Error;
type Future = Either<BoxedResponse, FutureResult<Self::Response, Self::Error>>;
type Future = BoxedResponse;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
if self.ready.is_none() {
@@ -376,12 +393,12 @@ impl<P> Service for AppRouting<P> {
});
if let Some((srv, _info)) = res {
Either::A(srv.call(req))
srv.call(req)
} else if let Some(ref mut default) = self.default {
Either::A(default.call(req))
default.call(req)
} else {
let req = req.into_parts().0;
Either::B(ok(ServiceResponse::new(req, Response::NotFound().finish())))
Either::A(ok(ServiceResponse::new(req, Response::NotFound().finish())))
}
}
}

View File

@@ -5,11 +5,18 @@ use std::rc::Rc;
use actix_http::Extensions;
use actix_router::ResourceDef;
use actix_service::{boxed, IntoNewService, NewService};
use futures::IntoFuture;
use crate::data::{Data, DataFactory};
use crate::error::Error;
use crate::guard::Guard;
use crate::resource::Resource;
use crate::rmap::ResourceMap;
use crate::service::{ServiceRequest, ServiceResponse};
use crate::route::Route;
use crate::service::{
HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest,
ServiceResponse,
};
type Guards = Vec<Box<Guard>>;
type HttpNewService<P> =
@@ -157,3 +164,140 @@ impl Default for AppConfigInner {
}
}
}
/// Router config. It is used for external configuration.
/// Part of application configuration could be offloaded
/// to set of external methods. This could help with
/// modularization of big application configuration.
pub struct RouterConfig<P: 'static> {
pub(crate) services: Vec<Box<ServiceFactory<P>>>,
pub(crate) data: Vec<Box<DataFactory>>,
pub(crate) external: Vec<ResourceDef>,
}
impl<P: 'static> RouterConfig<P> {
pub(crate) fn new() -> Self {
Self {
services: Vec::new(),
data: Vec::new(),
external: Vec::new(),
}
}
/// Set application data. Applicatin data could be accessed
/// by using `Data<T>` extractor where `T` is data type.
///
/// This is same as `App::data()` method.
pub fn data<S: 'static>(&mut self, data: S) -> &mut Self {
self.data.push(Box::new(Data::new(data)));
self
}
/// Set application data factory. This function is
/// similar to `.data()` but it accepts data factory. Data object get
/// constructed asynchronously during application initialization.
///
/// This is same as `App::data_dactory()` method.
pub fn data_factory<F, R>(&mut self, data: F) -> &mut Self
where
F: Fn() -> R + 'static,
R: IntoFuture + 'static,
R::Error: std::fmt::Debug,
{
self.data.push(Box::new(data));
self
}
/// Configure route for a specific path.
///
/// This is same as `App::route()` method.
pub fn route(&mut self, path: &str, mut route: Route<P>) -> &mut Self {
self.service(
Resource::new(path)
.add_guards(route.take_guards())
.route(route),
)
}
/// Register http service.
///
/// This is same as `App::service()` method.
pub fn service<F>(&mut self, factory: F) -> &mut Self
where
F: HttpServiceFactory<P> + 'static,
{
self.services
.push(Box::new(ServiceFactoryWrapper::new(factory)));
self
}
/// Register an external resource.
///
/// External resources are useful for URL generation purposes only
/// and are never considered for matching at request time. Calls to
/// `HttpRequest::url_for()` will work as expected.
///
/// This is same as `App::external_service()` method.
pub fn external_resource<N, U>(&mut self, name: N, url: U) -> &mut Self
where
N: AsRef<str>,
U: AsRef<str>,
{
let mut rdef = ResourceDef::new(url.as_ref());
*rdef.name_mut() = name.as_ref().to_string();
self.external.push(rdef);
self
}
}
#[cfg(test)]
mod tests {
use actix_service::Service;
use super::*;
use crate::http::StatusCode;
use crate::test::{block_on, init_service, TestRequest};
use crate::{web, App, HttpResponse};
#[test]
fn test_data() {
let cfg = |cfg: &mut RouterConfig<_>| {
cfg.data(10usize);
};
let mut srv =
init_service(App::new().configure(cfg).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_data_factory() {
let cfg = |cfg: &mut RouterConfig<_>| {
cfg.data_factory(|| Ok::<_, ()>(10usize));
};
let mut srv =
init_service(App::new().configure(cfg).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let cfg2 = |cfg: &mut RouterConfig<_>| {
cfg.data_factory(|| Ok::<_, ()>(10u32));
};
let mut srv = init_service(
App::new()
.service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()))
.configure(cfg2),
);
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
}

View File

@@ -5,8 +5,9 @@ use actix_http::error::{Error, ErrorInternalServerError};
use actix_http::Extensions;
use futures::{Async, Future, IntoFuture, Poll};
use crate::dev::Payload;
use crate::extract::FromRequest;
use crate::service::ServiceFromRequest;
use crate::request::HttpRequest;
/// Application data factory
pub(crate) trait DataFactory {
@@ -91,8 +92,8 @@ impl<T: 'static, P> FromRequest<P> for Data<T> {
type Future = Result<Self, Error>;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
if let Some(st) = req.config().extensions().get::<Data<T>>() {
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
if let Some(st) = req.app_config().extensions().get::<Data<T>>() {
Ok(st.clone())
} else {
Err(ErrorInternalServerError(
@@ -230,7 +231,7 @@ impl<T: 'static, P> FromRequest<P> for RouteData<T> {
type Future = Result<Self, Error>;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
if let Some(st) = req.route_data::<T>() {
Ok(st.clone())
} else {

View File

@@ -79,7 +79,7 @@ pub enum JsonPayloadError {
Payload(PayloadError),
}
/// Return `BadRequest` for `UrlencodedError`
/// Return `BadRequest` for `JsonPayloadError`
impl ResponseError for JsonPayloadError {
fn error_response(&self) -> HttpResponse {
match *self {
@@ -121,36 +121,6 @@ impl ResponseError for ReadlinesError {
}
}
/// A set of errors that can occur during parsing multipart streams
#[derive(Debug, Display, From)]
pub enum MultipartError {
/// Content-Type header is not found
#[display(fmt = "No Content-type header found")]
NoContentType,
/// Can not parse Content-Type header
#[display(fmt = "Can not parse Content-Type header")]
ParseContentType,
/// Multipart boundary is not found
#[display(fmt = "Multipart boundary is not found")]
Boundary,
/// Multipart stream is incomplete
#[display(fmt = "Multipart stream is incomplete")]
Incomplete,
/// Error during field parsing
#[display(fmt = "{}", _0)]
Parse(ParseError),
/// Payload error
#[display(fmt = "{}", _0)]
Payload(PayloadError),
}
/// Return `BadRequest` for `MultipartError`
impl ResponseError for MultipartError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST)
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -180,10 +150,4 @@ mod tests {
let resp: HttpResponse = ReadlinesError::EncodingError.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_multipart_error() {
let resp: HttpResponse = MultipartError::Boundary.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
}

View File

@@ -4,7 +4,8 @@ use actix_http::error::Error;
use futures::future::ok;
use futures::{future, Async, Future, IntoFuture, Poll};
use crate::service::ServiceFromRequest;
use crate::dev::Payload;
use crate::request::HttpRequest;
/// Trait implemented by types that can be extracted from request.
///
@@ -17,7 +18,14 @@ pub trait FromRequest<P>: Sized {
type Future: IntoFuture<Item = Self, Error = Self::Error>;
/// Convert request to a Self
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future;
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future;
/// Convert request to a Self
///
/// This method uses `Payload::None` as payload stream.
fn extract(req: &HttpRequest) -> Self::Future {
Self::from_request(req, &mut Payload::None)
}
}
/// Optionally extract a field from the request
@@ -28,7 +36,7 @@ pub trait FromRequest<P>: Sized {
///
/// ```rust
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{web, dev, App, Error, FromRequest};
/// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest};
/// use actix_web::error::ErrorBadRequest;
/// use rand;
///
@@ -41,7 +49,7 @@ pub trait FromRequest<P>: Sized {
/// type Error = Error;
/// type Future = Result<Self, Self::Error>;
///
/// fn from_request(req: &mut dev::ServiceFromRequest<P>) -> Self::Future {
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// } else {
@@ -76,14 +84,18 @@ where
type Future = Box<Future<Item = Option<T>, Error = Error>>;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
Box::new(T::from_request(req).into_future().then(|r| match r {
Ok(v) => future::ok(Some(v)),
Err(e) => {
log::debug!("Error for Option<T> extractor: {}", e.into());
future::ok(None)
}
}))
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
Box::new(
T::from_request(req, payload)
.into_future()
.then(|r| match r {
Ok(v) => future::ok(Some(v)),
Err(e) => {
log::debug!("Error for Option<T> extractor: {}", e.into());
future::ok(None)
}
}),
)
}
}
@@ -95,7 +107,7 @@ where
///
/// ```rust
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{web, dev, App, Result, Error, FromRequest};
/// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest};
/// use actix_web::error::ErrorBadRequest;
/// use rand;
///
@@ -108,7 +120,7 @@ where
/// type Error = Error;
/// type Future = Result<Thing, Error>;
///
/// fn from_request(req: &mut dev::ServiceFromRequest<P>) -> Self::Future {
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// } else {
@@ -141,11 +153,15 @@ where
type Future = Box<Future<Item = Result<T, T::Error>, Error = Error>>;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
Box::new(T::from_request(req).into_future().then(|res| match res {
Ok(v) => ok(Ok(v)),
Err(e) => ok(Err(e)),
}))
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
Box::new(
T::from_request(req, payload)
.into_future()
.then(|res| match res {
Ok(v) => ok(Ok(v)),
Err(e) => ok(Err(e)),
}),
)
}
}
@@ -154,7 +170,7 @@ impl<P> FromRequest<P> for () {
type Error = Error;
type Future = Result<(), Error>;
fn from_request(_req: &mut ServiceFromRequest<P>) -> Self::Future {
fn from_request(_: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
Ok(())
}
}
@@ -168,10 +184,10 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
type Error = Error;
type Future = $fut_type<P, $($T),+>;
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
$fut_type {
items: <($(Option<$T>,)+)>::default(),
futs: ($($T::from_request(req).into_future(),)+),
futs: ($($T::from_request(req, payload).into_future(),)+),
}
}
}
@@ -247,25 +263,25 @@ mod tests {
#[test]
fn test_option() {
let mut req = TestRequest::with_header(
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.route_data(FormConfig::default().limit(4096))
.to_from();
.to_http_parts();
let r = block_on(Option::<Form<Info>>::from_request(&mut req)).unwrap();
let r = block_on(Option::<Form<Info>>::from_request(&req, &mut pl)).unwrap();
assert_eq!(r, None);
let mut req = TestRequest::with_header(
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"hello=world"))
.to_from();
.to_http_parts();
let r = block_on(Option::<Form<Info>>::from_request(&mut req)).unwrap();
let r = block_on(Option::<Form<Info>>::from_request(&req, &mut pl)).unwrap();
assert_eq!(
r,
Some(Form(Info {
@@ -273,29 +289,29 @@ mod tests {
}))
);
let mut req = TestRequest::with_header(
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"bye=world"))
.to_from();
.to_http_parts();
let r = block_on(Option::<Form<Info>>::from_request(&mut req)).unwrap();
let r = block_on(Option::<Form<Info>>::from_request(&req, &mut pl)).unwrap();
assert_eq!(r, None);
}
#[test]
fn test_result() {
let mut req = TestRequest::with_header(
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.to_from();
.to_http_parts();
let r = block_on(Result::<Form<Info>, Error>::from_request(&mut req))
let r = block_on(Result::<Form<Info>, Error>::from_request(&req, &mut pl))
.unwrap()
.unwrap();
assert_eq!(
@@ -305,15 +321,16 @@ mod tests {
})
);
let mut req = TestRequest::with_header(
let (req, mut pl) = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"bye=world"))
.to_from();
.to_http_parts();
let r = block_on(Result::<Form<Info>, Error>::from_request(&mut req)).unwrap();
let r =
block_on(Result::<Form<Info>, Error>::from_request(&req, &mut pl)).unwrap();
assert!(r.is_err());
}
@@ -336,37 +353,38 @@ mod tests {
#[test]
fn test_request_extract() {
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_from();
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
let resource = ResourceDef::new("/{key}/{value}/");
resource.match_path(req.match_info_mut());
let s = Path::<MyStruct>::from_request(&mut req).unwrap();
let (req, mut pl) = req.into_parts();
let s = Path::<MyStruct>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.key, "name");
assert_eq!(s.value, "user1");
let s = Path::<(String, String)>::from_request(&mut req).unwrap();
let s = Path::<(String, String)>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.0, "name");
assert_eq!(s.1, "user1");
let s = Query::<Id>::from_request(&mut req).unwrap();
let s = Query::<Id>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.id, "test");
let mut req = TestRequest::with_uri("/name/32/").to_from();
let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
let resource = ResourceDef::new("/{key}/{value}/");
resource.match_path(req.match_info_mut());
let s = Path::<Test2>::from_request(&mut req).unwrap();
let (req, mut pl) = req.into_parts();
let s = Path::<Test2>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.as_ref().key, "name");
assert_eq!(s.value, 32);
let s = Path::<(String, u8)>::from_request(&mut req).unwrap();
let s = Path::<(String, u8)>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.0, "name");
assert_eq!(s.1, 32);
let res = Path::<Vec<String>>::from_request(&mut req).unwrap();
let res = Path::<Vec<String>>::from_request(&req, &mut pl).unwrap();
assert_eq!(res[0], "name".to_owned());
assert_eq!(res[1], "32".to_owned());
}
}

View File

@@ -19,7 +19,7 @@
//! App::new().service(web::resource("/index.html").route(
//! web::route()
//! .guard(guard::Post())
//! .guard(|head: &dev::RequestHead| head.method == http::Method::GET)
//! .guard(guard::fn_guard(|head| head.method == http::Method::GET))
//! .to(|| HttpResponse::MethodNotAllowed()))
//! );
//! }
@@ -39,15 +39,46 @@ pub trait Guard {
fn check(&self, request: &RequestHead) -> bool;
}
#[doc(hidden)]
pub struct FnGuard<F: Fn(&RequestHead) -> bool + 'static>(F);
/// Create guard object for supplied function.
///
/// ```rust
/// use actix_web::{guard, web, App, HttpResponse};
///
/// fn main() {
/// App::new().service(web::resource("/index.html").route(
/// web::route()
/// .guard(
/// guard::fn_guard(
/// |req| req.headers()
/// .contains_key("content-type")))
/// .to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
pub fn fn_guard<F>(f: F) -> impl Guard
where
F: Fn(&RequestHead) -> bool,
{
FnGuard(f)
}
struct FnGuard<F: Fn(&RequestHead) -> bool>(F);
impl<F> Guard for FnGuard<F>
where
F: Fn(&RequestHead) -> bool,
{
fn check(&self, head: &RequestHead) -> bool {
(self.0)(head)
}
}
impl<F> Guard for F
where
F: Fn(&RequestHead) -> bool + 'static,
F: Fn(&RequestHead) -> bool,
{
fn check(&self, head: &RequestHead) -> bool {
(*self)(head)
(self)(head)
}
}
@@ -93,7 +124,6 @@ impl Guard for AnyGuard {
/// Return guard that matches if all of the supplied guards.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{guard, web, App, HttpResponse};
///
/// fn main() {
@@ -279,13 +309,13 @@ mod tests {
.to_http_request();
let pred = Header("transfer-encoding", "chunked");
assert!(pred.check(&req));
assert!(pred.check(req.head()));
let pred = Header("transfer-encoding", "other");
assert!(!pred.check(&req));
assert!(!pred.check(req.head()));
let pred = Header("content-type", "other");
assert!(!pred.check(&req));
assert!(!pred.check(req.head()));
}
// #[test]
@@ -311,50 +341,50 @@ mod tests {
.method(Method::POST)
.to_http_request();
assert!(Get().check(&req));
assert!(!Get().check(&req2));
assert!(Post().check(&req2));
assert!(!Post().check(&req));
assert!(Get().check(req.head()));
assert!(!Get().check(req2.head()));
assert!(Post().check(req2.head()));
assert!(!Post().check(req.head()));
let r = TestRequest::default().method(Method::PUT).to_http_request();
assert!(Put().check(&r));
assert!(!Put().check(&req));
assert!(Put().check(r.head()));
assert!(!Put().check(req.head()));
let r = TestRequest::default()
.method(Method::DELETE)
.to_http_request();
assert!(Delete().check(&r));
assert!(!Delete().check(&req));
assert!(Delete().check(r.head()));
assert!(!Delete().check(req.head()));
let r = TestRequest::default()
.method(Method::HEAD)
.to_http_request();
assert!(Head().check(&r));
assert!(!Head().check(&req));
assert!(Head().check(r.head()));
assert!(!Head().check(req.head()));
let r = TestRequest::default()
.method(Method::OPTIONS)
.to_http_request();
assert!(Options().check(&r));
assert!(!Options().check(&req));
assert!(Options().check(r.head()));
assert!(!Options().check(req.head()));
let r = TestRequest::default()
.method(Method::CONNECT)
.to_http_request();
assert!(Connect().check(&r));
assert!(!Connect().check(&req));
assert!(Connect().check(r.head()));
assert!(!Connect().check(req.head()));
let r = TestRequest::default()
.method(Method::PATCH)
.to_http_request();
assert!(Patch().check(&r));
assert!(!Patch().check(&req));
assert!(Patch().check(r.head()));
assert!(!Patch().check(req.head()));
let r = TestRequest::default()
.method(Method::TRACE)
.to_http_request();
assert!(Trace().check(&r));
assert!(!Trace().check(&req));
assert!(Trace().check(r.head()));
assert!(!Trace().check(req.head()));
}
#[test]
@@ -363,13 +393,13 @@ mod tests {
.method(Method::TRACE)
.to_http_request();
assert!(Not(Get()).check(&r));
assert!(!Not(Trace()).check(&r));
assert!(Not(Get()).check(r.head()));
assert!(!Not(Trace()).check(r.head()));
assert!(All(Trace()).and(Trace()).check(&r));
assert!(!All(Get()).and(Trace()).check(&r));
assert!(All(Trace()).and(Trace()).check(r.head()));
assert!(!All(Get()).and(Trace()).check(r.head()));
assert!(Any(Get()).or(Trace()).check(&r));
assert!(!Any(Get()).or(Get()).check(&r));
assert!(Any(Get()).or(Trace()).check(r.head()));
assert!(!Any(Get()).or(Get()).check(r.head()));
}
}

View File

@@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
use actix_http::{Error, Extensions, Response};
use actix_http::{Error, Extensions, Payload, Response};
use actix_service::{NewService, Service, Void};
use futures::future::{ok, FutureResult};
use futures::{try_ready, Async, Future, IntoFuture, Poll};
@@ -10,7 +10,7 @@ use futures::{try_ready, Async, Future, IntoFuture, Poll};
use crate::extract::FromRequest;
use crate::request::HttpRequest;
use crate::responder::Responder;
use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse};
use crate::service::{ServiceRequest, ServiceResponse};
/// Handler converter factory
pub trait Factory<T, R>: Clone
@@ -22,8 +22,8 @@ where
impl<F, R> Factory<(), R> for F
where
F: Fn() -> R + Clone + 'static,
R: Responder + 'static,
F: Fn() -> R + Clone,
R: Responder,
{
fn call(&self, _: ()) -> R {
(self)()
@@ -52,40 +52,24 @@ where
}
}
}
impl<F, T, R> NewService for Handler<F, T, R>
impl<F, T, R> Clone for Handler<F, T, R>
where
F: Factory<T, R>,
R: Responder + 'static,
R: Responder,
{
type Request = (T, HttpRequest);
type Response = ServiceResponse;
type Error = Void;
type InitError = ();
type Service = HandlerService<F, T, R>;
type Future = FutureResult<Self::Service, ()>;
fn new_service(&self, _: &()) -> Self::Future {
ok(HandlerService {
fn clone(&self) -> Self {
Self {
hnd: self.hnd.clone(),
_t: PhantomData,
})
}
}
}
#[doc(hidden)]
pub struct HandlerService<F, T, R>
impl<F, T, R> Service for Handler<F, T, R>
where
F: Factory<T, R>,
R: Responder + 'static,
{
hnd: F,
_t: PhantomData<(T, R)>,
}
impl<F, T, R> Service for HandlerService<F, T, R>
where
F: Factory<T, R>,
R: Responder + 'static,
R: Responder,
{
type Request = (T, HttpRequest);
type Response = ServiceResponse;
@@ -184,41 +168,23 @@ where
}
}
}
impl<F, T, R> NewService for AsyncHandler<F, T, R>
impl<F, T, R> Clone for AsyncHandler<F, T, R>
where
F: AsyncFactory<T, R>,
R: IntoFuture,
R::Item: Into<Response>,
R::Error: Into<Error>,
{
type Request = (T, HttpRequest);
type Response = ServiceResponse;
type Error = Error;
type InitError = ();
type Service = AsyncHandlerService<F, T, R>;
type Future = FutureResult<Self::Service, ()>;
fn new_service(&self, _: &()) -> Self::Future {
ok(AsyncHandlerService {
fn clone(&self) -> Self {
AsyncHandler {
hnd: self.hnd.clone(),
_t: PhantomData,
})
}
}
}
#[doc(hidden)]
pub struct AsyncHandlerService<F, T, R>
where
F: AsyncFactory<T, R>,
R: IntoFuture,
R::Item: Into<Response>,
R::Error: Into<Error>,
{
hnd: F,
_t: PhantomData<(T, R)>,
}
impl<F, T, R> Service for AsyncHandlerService<F, T, R>
impl<F, T, R> Service for AsyncHandler<F, T, R>
where
F: AsyncFactory<T, R>,
R: IntoFuture,
@@ -227,7 +193,7 @@ where
{
type Request = (T, HttpRequest);
type Response = ServiceResponse;
type Error = Error;
type Error = Void;
type Future = AsyncHandlerServiceResponse<R::Future>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
@@ -255,7 +221,7 @@ where
T::Error: Into<Error>,
{
type Item = ServiceResponse;
type Error = Error;
type Error = Void;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.fut.poll() {
@@ -276,87 +242,112 @@ where
}
/// Extract arguments from request
pub struct Extract<P, T: FromRequest<P>> {
pub struct Extract<P, T: FromRequest<P>, S> {
config: Rc<RefCell<Option<Rc<Extensions>>>>,
service: S,
_t: PhantomData<(P, T)>,
}
impl<P, T: FromRequest<P>> Extract<P, T> {
pub fn new(config: Rc<RefCell<Option<Rc<Extensions>>>>) -> Self {
impl<P, T: FromRequest<P>, S> Extract<P, T, S> {
pub fn new(config: Rc<RefCell<Option<Rc<Extensions>>>>, service: S) -> Self {
Extract {
config,
service,
_t: PhantomData,
}
}
}
impl<P, T: FromRequest<P>> NewService for Extract<P, T> {
impl<P, T: FromRequest<P>, S> NewService for Extract<P, T, S>
where
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>
+ Clone,
{
type Request = ServiceRequest<P>;
type Response = (T, HttpRequest);
type Error = (Error, ServiceFromRequest<P>);
type Response = ServiceResponse;
type Error = (Error, ServiceRequest<P>);
type InitError = ();
type Service = ExtractService<P, T>;
type Service = ExtractService<P, T, S>;
type Future = FutureResult<Self::Service, ()>;
fn new_service(&self, _: &()) -> Self::Future {
ok(ExtractService {
_t: PhantomData,
config: self.config.borrow().clone(),
service: self.service.clone(),
})
}
}
pub struct ExtractService<P, T: FromRequest<P>> {
pub struct ExtractService<P, T: FromRequest<P>, S> {
config: Option<Rc<Extensions>>,
service: S,
_t: PhantomData<(P, T)>,
}
impl<P, T: FromRequest<P>> Service for ExtractService<P, T> {
impl<P, T: FromRequest<P>, S> Service for ExtractService<P, T, S>
where
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>
+ Clone,
{
type Request = ServiceRequest<P>;
type Response = (T, HttpRequest);
type Error = (Error, ServiceFromRequest<P>);
type Future = ExtractResponse<P, T>;
type Response = ServiceResponse;
type Error = (Error, ServiceRequest<P>);
type Future = ExtractResponse<P, T, S>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
let mut req = ServiceFromRequest::new(req, self.config.clone());
let (mut req, mut payload) = req.into_parts();
req.set_route_data(self.config.clone());
let fut = T::from_request(&req, &mut payload).into_future();
ExtractResponse {
fut: T::from_request(&mut req).into_future(),
req: Some(req),
fut,
fut_s: None,
req: Some((req, payload)),
service: self.service.clone(),
}
}
}
pub struct ExtractResponse<P, T: FromRequest<P>> {
req: Option<ServiceFromRequest<P>>,
pub struct ExtractResponse<P, T: FromRequest<P>, S: Service> {
req: Option<(HttpRequest, Payload<P>)>,
service: S,
fut: <T::Future as IntoFuture>::Future,
fut_s: Option<S::Future>,
}
impl<P, T: FromRequest<P>> Future for ExtractResponse<P, T> {
type Item = (T, HttpRequest);
type Error = (Error, ServiceFromRequest<P>);
impl<P, T: FromRequest<P>, S> Future for ExtractResponse<P, T, S>
where
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>,
{
type Item = ServiceResponse;
type Error = (Error, ServiceRequest<P>);
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let item = try_ready!(self
.fut
.poll()
.map_err(|e| (e.into(), self.req.take().unwrap())));
if let Some(ref mut fut) = self.fut_s {
return fut.poll().map_err(|_| panic!());
}
let req = self.req.take().unwrap();
let req = req.into_request();
let item = try_ready!(self.fut.poll().map_err(|e| {
let (req, payload) = self.req.take().unwrap();
let req = ServiceRequest::from_parts(req, payload);
(e.into(), req)
}));
Ok(Async::Ready((item, req)))
self.fut_s = Some(self.service.call((item, self.req.take().unwrap().0)));
self.poll()
}
}
/// FromRequest trait impl for tuples
macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => {
impl<Func, $($T,)+ Res> Factory<($($T,)+), Res> for Func
where Func: Fn($($T,)+) -> Res + Clone + 'static,
Res: Responder + 'static,
where Func: Fn($($T,)+) -> Res + Clone,
Res: Responder,
{
fn call(&self, param: ($($T,)+)) -> Res {
(self)($(param.$n,)+)
@@ -365,7 +356,7 @@ macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => {
impl<Func, $($T,)+ Res> AsyncFactory<($($T,)+), Res> for Func
where Func: Fn($($T,)+) -> Res + Clone + 'static,
Res: IntoFuture + 'static,
Res: IntoFuture,
Res::Item: Into<Response>,
Res::Error: Into<Error>,
{

View File

@@ -33,7 +33,7 @@ impl ConnectionInfo {
let peer = None;
// load forwarded header
for hdr in req.headers.get_all(header::FORWARDED) {
for hdr in req.headers.get_all(&header::FORWARDED) {
if let Ok(val) = hdr.to_str() {
for pair in val.split(';') {
for el in pair.split(',') {
@@ -69,7 +69,7 @@ impl ConnectionInfo {
if scheme.is_none() {
if let Some(h) = req
.headers
.get(HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap())
.get(&HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap())
{
if let Ok(h) = h.to_str() {
scheme = h.split(',').next().map(|v| v.trim());
@@ -87,14 +87,14 @@ impl ConnectionInfo {
if host.is_none() {
if let Some(h) = req
.headers
.get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap())
.get(&HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap())
{
if let Ok(h) = h.to_str() {
host = h.split(',').next().map(|v| v.trim());
}
}
if host.is_none() {
if let Some(h) = req.headers.get(header::HOST) {
if let Some(h) = req.headers.get(&header::HOST) {
host = h.to_str().ok();
}
if host.is_none() {
@@ -110,7 +110,7 @@ impl ConnectionInfo {
if remote.is_none() {
if let Some(h) = req
.headers
.get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap())
.get(&HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap())
{
if let Ok(h) = h.to_str() {
remote = h.split(',').next().map(|v| v.trim());

View File

@@ -42,6 +42,9 @@
//! represents an HTTP server instance and is used to instantiate and
//! configure servers.
//!
//! * [web](web/index.html): This module
//! provide essentials helper functions and types for application registration.
//!
//! * [HttpRequest](struct.HttpRequest.html) and
//! [HttpResponse](struct.HttpResponse.html): These structs
//! represent HTTP requests and responses and expose various methods
@@ -59,7 +62,7 @@
//! * SSL support with OpenSSL or `native-tls`
//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`)
//! * Supports [Actix actor framework](https://github.com/actix/actix)
//! * Supported Rust version: 1.32 or later
//! * Supported Rust version: 1.31 or later
//!
//! ## Package feature
//!
@@ -67,7 +70,7 @@
//! * `tls` - enables ssl support via `native-tls` crate
//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2`
//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2`
//! * `cookies` - enables cookies support, includes `ring` crate as
//! * `secure-cookies` - enables secure cookies support, includes `ring` crate as
//! dependency
//! * `brotli` - enables `brotli` compression support, requires `c`
//! compiler
@@ -98,6 +101,7 @@ mod server;
mod service;
pub mod test;
mod types;
pub mod web;
#[allow(unused_imports)]
#[macro_use]
@@ -134,12 +138,9 @@ pub mod dev {
pub use crate::config::{AppConfig, ServiceConfig};
pub use crate::info::ConnectionInfo;
pub use crate::rmap::ResourceMap;
pub use crate::service::{
HttpServiceFactory, ServiceFromRequest, ServiceRequest, ServiceResponse,
};
pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse};
pub use crate::types::form::UrlEncoded;
pub use crate::types::json::JsonBody;
pub use crate::types::payload::HttpMessageBody;
pub use crate::types::readlines::Readlines;
pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody};
@@ -159,213 +160,6 @@ pub mod dev {
}
}
pub mod web {
//! Various types
use actix_http::{http::Method, Response};
use actix_service::{fn_transform, Service, Transform};
use futures::{Future, IntoFuture};
pub use actix_http::Response as HttpResponse;
pub use bytes::{Bytes, BytesMut};
use crate::error::{BlockingError, Error};
use crate::extract::FromRequest;
use crate::handler::{AsyncFactory, Factory};
use crate::resource::Resource;
use crate::responder::Responder;
use crate::route::Route;
use crate::scope::Scope;
use crate::service::{ServiceRequest, ServiceResponse};
pub use crate::data::{Data, RouteData};
pub use crate::request::HttpRequest;
pub use crate::types::*;
/// Create resource for a specific path.
///
/// Resources may have variable path segments. For example, a
/// resource with the path `/a/{name}/c` would match all incoming
/// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`.
///
/// A variable segment is specified in the form `{identifier}`,
/// where the identifier can be used later in a request handler to
/// access the matched value for that segment. This is done by
/// looking up the identifier in the `Params` object returned by
/// `HttpRequest.match_info()` method.
///
/// By default, each segment matches the regular expression `[^{}/]+`.
///
/// You can also specify a custom regex in the form `{identifier:regex}`:
///
/// For instance, to route `GET`-requests on any route matching
/// `/users/{userid}/{friend}` and store `userid` and `friend` in
/// the exposed `Params` object:
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{web, http, App, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/users/{userid}/{friend}")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
pub fn resource<P: 'static>(path: &str) -> Resource<P> {
Resource::new(path)
}
/// Configure scope for common root path.
///
/// Scopes collect multiple paths under a common path prefix.
/// Scope path can contain variable path segments as resources.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{web, App, HttpRequest, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::scope("/{project_id}")
/// .service(web::resource("/path1").to(|| HttpResponse::Ok()))
/// .service(web::resource("/path2").to(|| HttpResponse::Ok()))
/// .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
///
/// In the above example, three routes get added:
/// * /{project_id}/path1
/// * /{project_id}/path2
/// * /{project_id}/path3
///
pub fn scope<P: 'static>(path: &str) -> Scope<P> {
Scope::new(path)
}
/// Create *route* without configuration.
pub fn route<P: 'static>() -> Route<P> {
Route::new()
}
/// Create *route* with `GET` method guard.
pub fn get<P: 'static>() -> Route<P> {
Route::new().method(Method::GET)
}
/// Create *route* with `POST` method guard.
pub fn post<P: 'static>() -> Route<P> {
Route::new().method(Method::POST)
}
/// Create *route* with `PUT` method guard.
pub fn put<P: 'static>() -> Route<P> {
Route::new().method(Method::PUT)
}
/// Create *route* with `PATCH` method guard.
pub fn patch<P: 'static>() -> Route<P> {
Route::new().method(Method::PATCH)
}
/// Create *route* with `DELETE` method guard.
pub fn delete<P: 'static>() -> Route<P> {
Route::new().method(Method::DELETE)
}
/// Create *route* with `HEAD` method guard.
pub fn head<P: 'static>() -> Route<P> {
Route::new().method(Method::HEAD)
}
/// Create *route* and add method guard.
pub fn method<P: 'static>(method: Method) -> Route<P> {
Route::new().method(method)
}
/// Create a new route and add handler.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn index() -> HttpResponse {
/// unimplemented!()
/// }
///
/// App::new().service(
/// web::resource("/").route(
/// web::to(index))
/// );
/// ```
pub fn to<F, I, R, P: 'static>(handler: F) -> Route<P>
where
F: Factory<I, R> + 'static,
I: FromRequest<P> + 'static,
R: Responder + 'static,
{
Route::new().to(handler)
}
/// Create a new route and add async handler.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse, Error};
///
/// fn index() -> impl futures::Future<Item=HttpResponse, Error=Error> {
/// futures::future::ok(HttpResponse::Ok().finish())
/// }
///
/// App::new().service(web::resource("/").route(
/// web::to_async(index))
/// );
/// ```
pub fn to_async<F, I, R, P: 'static>(handler: F) -> Route<P>
where
F: AsyncFactory<I, R>,
I: FromRequest<P> + 'static,
R: IntoFuture + 'static,
R::Item: Into<Response>,
R::Error: Into<Error>,
{
Route::new().to_async(handler)
}
/// Execute blocking function on a thread pool, returns future that resolves
/// to result of the function execution.
pub fn block<F, I, E>(f: F) -> impl Future<Item = I, Error = BlockingError<E>>
where
F: FnOnce() -> Result<I, E> + Send + 'static,
I: Send + 'static,
E: Send + std::fmt::Debug + 'static,
{
actix_threadpool::run(f).from_err()
}
/// Create middleare
pub fn md<F, R, S, P, B>(
f: F,
) -> impl Transform<
S,
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>
where
S: Service<
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
>,
F: FnMut(ServiceRequest<P>, &mut S) -> R + Clone,
R: IntoFuture<Item = ServiceResponse<B>, Error = Error>,
{
fn_transform(f)
}
}
#[cfg(feature = "client")]
pub mod client {
//! An HTTP Client
@@ -393,5 +187,7 @@ pub mod client {
pub use awc::error::{
ConnectError, InvalidUrl, PayloadError, SendRequestError, WsClientError,
};
pub use awc::{test, Client, ClientBuilder, ClientRequest, ClientResponse};
pub use awc::{
test, Client, ClientBuilder, ClientRequest, ClientResponse, Connector,
};
}

View File

@@ -70,10 +70,8 @@ impl Default for Compress {
impl<S, P, B> Transform<S> for Compress
where
P: 'static,
B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<Encoder<B>>;
@@ -97,10 +95,8 @@ pub struct CompressMiddleware<S> {
impl<S, P, B> Service for CompressMiddleware<S>
where
P: 'static,
B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<Encoder<B>>;
@@ -113,7 +109,7 @@ where
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
// negotiate content-encoding
let encoding = if let Some(val) = req.headers.get(ACCEPT_ENCODING) {
let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) {
if let Ok(enc) = val.to_str() {
AcceptEncoding::parse(enc, self.encoding)
} else {
@@ -134,10 +130,8 @@ where
#[doc(hidden)]
pub struct CompressResponse<S, P, B>
where
P: 'static,
B: MessageBody,
S: Service,
S::Future: 'static,
B: MessageBody,
{
fut: S::Future,
encoding: ContentEncoding,
@@ -146,10 +140,8 @@ where
impl<S, P, B> Future for CompressResponse<S, P, B>
where
P: 'static,
B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
{
type Item = ServiceResponse<Encoder<B>>;
type Error = S::Error;
@@ -157,7 +149,7 @@ where
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let resp = futures::try_ready!(self.fut.poll());
let enc = if let Some(enc) = resp.head().extensions().get::<Enc>() {
let enc = if let Some(enc) = resp.response().extensions().get::<Enc>() {
enc.0
} else {
self.encoding

View File

@@ -51,7 +51,7 @@ use crate::error::{ResponseError, Result};
use crate::http::header::{self, HeaderName, HeaderValue};
use crate::http::{self, HttpTryFrom, Method, StatusCode, Uri};
use crate::service::{ServiceRequest, ServiceResponse};
use crate::{HttpMessage, HttpResponse};
use crate::HttpResponse;
/// A set of errors that can occur during processing CORS
#[derive(Debug, Display)]
@@ -477,8 +477,9 @@ fn cors<'a>(
impl<S, P, B> IntoTransform<CorsFactory, S> for Cors
where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
P: 'static,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
S::Error: 'static,
B: 'static,
{
fn into_transform(self) -> CorsFactory {
@@ -541,7 +542,6 @@ where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
S::Error: 'static,
P: 'static,
B: 'static,
{
type Request = ServiceRequest<P>;
@@ -584,7 +584,7 @@ struct Inner {
impl Inner {
fn validate_origin(&self, req: &RequestHead) -> Result<(), CorsError> {
if let Some(hdr) = req.headers().get(header::ORIGIN) {
if let Some(hdr) = req.headers().get(&header::ORIGIN) {
if let Ok(origin) = hdr.to_str() {
return match self.origins {
AllOrSome::All => Ok(()),
@@ -608,7 +608,7 @@ impl Inner {
AllOrSome::All => {
if self.send_wildcard {
Some(HeaderValue::from_static("*"))
} else if let Some(origin) = req.headers().get(header::ORIGIN) {
} else if let Some(origin) = req.headers().get(&header::ORIGIN) {
Some(origin.clone())
} else {
None
@@ -617,7 +617,7 @@ impl Inner {
AllOrSome::Some(ref origins) => {
if let Some(origin) =
req.headers()
.get(header::ORIGIN)
.get(&header::ORIGIN)
.filter(|o| match o.to_str() {
Ok(os) => origins.contains(os),
_ => false,
@@ -632,7 +632,7 @@ impl Inner {
}
fn validate_allowed_method(&self, req: &RequestHead) -> Result<(), CorsError> {
if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) {
if let Some(hdr) = req.headers().get(&header::ACCESS_CONTROL_REQUEST_METHOD) {
if let Ok(meth) = hdr.to_str() {
if let Ok(method) = Method::try_from(meth) {
return self
@@ -653,7 +653,7 @@ impl Inner {
AllOrSome::All => Ok(()),
AllOrSome::Some(ref allowed_headers) => {
if let Some(hdr) =
req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
req.headers().get(&header::ACCESS_CONTROL_REQUEST_HEADERS)
{
if let Ok(headers) = hdr.to_str() {
let mut hdrs = HashSet::new();
@@ -683,7 +683,6 @@ where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
S::Error: 'static,
P: 'static,
B: 'static,
{
type Request = ServiceRequest<P>;
@@ -702,9 +701,9 @@ where
if self.inner.preflight && Method::OPTIONS == *req.method() {
if let Err(e) = self
.inner
.validate_origin(&req)
.and_then(|_| self.inner.validate_allowed_method(&req))
.and_then(|_| self.inner.validate_allowed_headers(&req))
.validate_origin(req.head())
.and_then(|_| self.inner.validate_allowed_method(req.head()))
.and_then(|_| self.inner.validate_allowed_headers(req.head()))
{
return Either::A(ok(req.error_response(e)));
}
@@ -721,7 +720,7 @@ where
.unwrap(),
)
} else if let Some(hdr) =
req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
req.headers().get(&header::ACCESS_CONTROL_REQUEST_HEADERS)
{
Some(hdr.clone())
} else {
@@ -739,7 +738,7 @@ where
let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers);
})
.if_some(
self.inner.access_control_allow_origin(&req),
self.inner.access_control_allow_origin(req.head()),
|origin, resp| {
let _ = resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin);
},
@@ -760,9 +759,9 @@ where
.into_body();
Either::A(ok(req.into_response(res)))
} else if req.headers().contains_key(header::ORIGIN) {
} else if req.headers().contains_key(&header::ORIGIN) {
// Only check requests with a origin header.
if let Err(e) = self.inner.validate_origin(&req) {
if let Err(e) = self.inner.validate_origin(req.head()) {
return Either::A(ok(req.error_response(e)));
}
@@ -771,7 +770,7 @@ where
Either::B(Either::B(Box::new(self.service.call(req).and_then(
move |mut res| {
if let Some(origin) =
inner.access_control_allow_origin(&res.request())
inner.access_control_allow_origin(res.request().head())
{
res.headers_mut()
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
@@ -791,7 +790,7 @@ where
}
if inner.vary_header {
let value =
if let Some(hdr) = res.headers_mut().get(header::VARY) {
if let Some(hdr) = res.headers_mut().get(&header::VARY) {
let mut val: Vec<u8> =
Vec::with_capacity(hdr.as_bytes().len() + 8);
val.extend(hdr.as_bytes());
@@ -826,7 +825,6 @@ mod tests {
+ 'static,
S::Future: 'static,
S::Error: 'static,
P: 'static,
B: 'static,
{
block_on(
@@ -848,8 +846,8 @@ mod tests {
#[test]
fn validate_origin_allows_all_origins() {
let mut cors = Cors::new().finish(test::ok_service());
let req =
TestRequest::with_header("Origin", "https://www.example.com").to_service();
let req = TestRequest::with_header("Origin", "https://www.example.com")
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(resp.status(), StatusCode::OK);
@@ -867,20 +865,20 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
assert!(cors.inner.validate_allowed_method(&req).is_err());
assert!(cors.inner.validate_allowed_headers(&req).is_err());
assert!(cors.inner.validate_allowed_method(req.head()).is_err());
assert!(cors.inner.validate_allowed_headers(req.head()).is_err());
let resp = test::call_success(&mut cors, req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "put")
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
assert!(cors.inner.validate_allowed_method(&req).is_err());
assert!(cors.inner.validate_allowed_headers(&req).is_err());
assert!(cors.inner.validate_allowed_method(req.head()).is_err());
assert!(cors.inner.validate_allowed_headers(req.head()).is_err());
let req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
@@ -889,26 +887,26 @@ mod tests {
"AUTHORIZATION,ACCEPT",
)
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(
&b"*"[..],
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.get(&header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
);
assert_eq!(
&b"3600"[..],
resp.headers()
.get(header::ACCESS_CONTROL_MAX_AGE)
.get(&header::ACCESS_CONTROL_MAX_AGE)
.unwrap()
.as_bytes()
);
let hdr = resp
.headers()
.get(header::ACCESS_CONTROL_ALLOW_HEADERS)
.get(&header::ACCESS_CONTROL_ALLOW_HEADERS)
.unwrap()
.to_str()
.unwrap();
@@ -935,7 +933,7 @@ mod tests {
"AUTHORIZATION,ACCEPT",
)
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(resp.status(), StatusCode::OK);
@@ -960,10 +958,10 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.unknown.com")
.method(Method::GET)
.to_service();
cors.inner.validate_origin(&req).unwrap();
cors.inner.validate_allowed_method(&req).unwrap();
cors.inner.validate_allowed_headers(&req).unwrap();
.to_srv_request();
cors.inner.validate_origin(req.head()).unwrap();
cors.inner.validate_allowed_method(req.head()).unwrap();
cors.inner.validate_allowed_headers(req.head()).unwrap();
}
#[test]
@@ -974,7 +972,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::GET)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(resp.status(), StatusCode::OK);
@@ -984,7 +982,7 @@ mod tests {
fn test_no_origin_response() {
let mut cors = Cors::new().disable_preflight().finish(test::ok_service());
let req = TestRequest::default().method(Method::GET).to_service();
let req = TestRequest::default().method(Method::GET).to_srv_request();
let resp = test::call_success(&mut cors, req);
assert!(resp
.headers()
@@ -993,7 +991,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(
&b"https://www.example.com"[..],
@@ -1019,7 +1017,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(
@@ -1066,7 +1064,7 @@ mod tests {
}));
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(
&b"Accept, Origin"[..],
@@ -1082,7 +1080,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
let origins_str = resp
@@ -1105,7 +1103,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://example.com")
.method(Method::GET)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(
@@ -1118,7 +1116,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://example.org")
.method(Method::GET)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(
@@ -1141,7 +1139,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(
@@ -1155,7 +1153,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://example.org")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(

View File

@@ -10,7 +10,6 @@ use futures::{Async, Poll, Stream};
use crate::dev::Payload;
use crate::error::{Error, PayloadError};
use crate::service::ServiceRequest;
use crate::HttpMessage;
/// `Middleware` for decompressing request's payload.
/// `Decompress` middleware must be added with `App::chain()` method.

View File

@@ -131,11 +131,11 @@ where
// set response headers
for (key, value) in inner.headers.iter() {
if !res.headers().contains_key(key) {
res.headers_mut().insert(key, value.clone());
res.headers_mut().insert(key.clone(), value.clone());
}
}
// default content-type
if inner.ct && !res.headers().contains_key(CONTENT_TYPE) {
if inner.ct && !res.headers().contains_key(&CONTENT_TYPE) {
res.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/octet-stream"),
@@ -166,11 +166,11 @@ mod tests {
)
.unwrap();
let req = TestRequest::default().to_service();
let req = TestRequest::default().to_srv_request();
let resp = block_on(mw.call(req)).unwrap();
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let req = TestRequest::default().to_service();
let req = TestRequest::default().to_srv_request();
let srv = FnService::new(|req: ServiceRequest<_>| {
req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish())
});
@@ -192,7 +192,7 @@ mod tests {
let mut mw =
block_on(DefaultHeaders::new().content_type().new_transform(srv)).unwrap();
let req = TestRequest::default().to_service();
let req = TestRequest::default().to_srv_request();
let resp = block_on(mw.call(req)).unwrap();
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),

View File

@@ -180,7 +180,7 @@ mod tests {
)
.unwrap();
let resp = test::call_success(&mut mw, TestRequest::default().to_service());
let resp = test::call_success(&mut mw, TestRequest::default().to_srv_request());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
}
@@ -206,7 +206,7 @@ mod tests {
)
.unwrap();
let resp = test::call_success(&mut mw, TestRequest::default().to_service());
let resp = test::call_success(&mut mw, TestRequest::default().to_srv_request());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
}
}

View File

@@ -58,10 +58,8 @@ use time::Duration;
use crate::cookie::{Cookie, CookieJar, Key, SameSite};
use crate::error::{Error, Result};
use crate::http::header::{self, HeaderValue};
use crate::request::HttpRequest;
use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse};
use crate::FromRequest;
use crate::HttpMessage;
use crate::service::{ServiceRequest, ServiceResponse};
use crate::{dev::Payload, FromRequest, HttpMessage, HttpRequest};
/// The extractor type to obtain your identity from a request.
///
@@ -147,7 +145,7 @@ impl<P> FromRequest<P> for Identity {
type Future = Result<Identity, Error>;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
Ok(Identity(req.clone()))
}
}
@@ -202,10 +200,11 @@ impl<T> IdentityService<T> {
impl<S, T, P, B> Transform<S> for IdentityService<T>
where
P: 'static,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
S::Future: 'static,
S::Error: 'static,
T: IdentityPolicy,
P: 'static,
B: 'static,
{
type Request = ServiceRequest<P>;
@@ -235,6 +234,7 @@ where
B: 'static,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
S::Future: 'static,
S::Error: 'static,
T: IdentityPolicy,
{
type Request = ServiceRequest<P>;
@@ -507,7 +507,7 @@ mod tests {
let resp =
test::call_success(&mut srv, TestRequest::with_uri("/login").to_request());
assert_eq!(resp.status(), StatusCode::OK);
let c = resp.cookies().next().unwrap().to_owned();
let c = resp.response().cookies().next().unwrap().to_owned();
let resp = test::call_success(
&mut srv,

View File

@@ -14,8 +14,9 @@ use time;
use crate::dev::{BodySize, MessageBody, ResponseBody};
use crate::error::{Error, Result};
use crate::http::{HeaderName, HttpTryFrom};
use crate::service::{ServiceRequest, ServiceResponse};
use crate::{HttpMessage, HttpResponse};
use crate::HttpResponse;
/// `Middleware` for logging request and response info to the terminal.
///
@@ -65,6 +66,8 @@ use crate::{HttpMessage, HttpResponse};
///
/// `%D` Time taken to serve the request, in milliseconds
///
/// `%U` Request URL
///
/// `%{FOO}i` request.headers['FOO']
///
/// `%{FOO}o` response.headers['FOO']
@@ -201,7 +204,7 @@ where
if let Some(ref mut format) = self.format {
for unit in &mut format.0 {
unit.render_response(&res);
unit.render_response(res.response());
}
}
@@ -272,7 +275,7 @@ impl Format {
/// Returns `None` if the format string syntax is incorrect.
pub fn new(s: &str) -> Format {
log::trace!("Access log format: {}", s);
let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbTD]?)").unwrap();
let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrUsbTD]?)").unwrap();
let mut idx = 0;
let mut results = Vec::new();
@@ -286,8 +289,12 @@ impl Format {
if let Some(key) = cap.get(2) {
results.push(match cap.get(3).unwrap().as_str() {
"i" => FormatText::RequestHeader(key.as_str().to_owned()),
"o" => FormatText::ResponseHeader(key.as_str().to_owned()),
"i" => FormatText::RequestHeader(
HeaderName::try_from(key.as_str()).unwrap(),
),
"o" => FormatText::ResponseHeader(
HeaderName::try_from(key.as_str()).unwrap(),
),
"e" => FormatText::EnvironHeader(key.as_str().to_owned()),
_ => unreachable!(),
})
@@ -300,6 +307,7 @@ impl Format {
"r" => FormatText::RequestLine,
"s" => FormatText::ResponseStatus,
"b" => FormatText::ResponseSize,
"U" => FormatText::UrlPath,
"T" => FormatText::Time,
"D" => FormatText::TimeMillis,
_ => FormatText::Str(m.as_str().to_owned()),
@@ -328,8 +336,9 @@ pub enum FormatText {
Time,
TimeMillis,
RemoteAddr,
RequestHeader(String),
ResponseHeader(String),
UrlPath,
RequestHeader(HeaderName),
ResponseHeader(HeaderName),
EnvironHeader(String),
}
@@ -413,6 +422,7 @@ impl FormatText {
))
};
}
FormatText::UrlPath => *self = FormatText::Str(format!("{}", req.path())),
FormatText::RequestTime => {
*self = FormatText::Str(format!(
"{:?}",
@@ -469,44 +479,71 @@ mod tests {
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
)
.to_service();
.to_srv_request();
let _res = block_on(srv.call(req));
}
// #[test]
// fn test_default_format() {
// let format = Format::default();
#[test]
fn test_url_path() {
let mut format = Format::new("%T %U");
let req = TestRequest::with_header(
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
)
.uri("/test/route/yeah")
.to_srv_request();
// let req = TestRequest::with_header(
// header::USER_AGENT,
// header::HeaderValue::from_static("ACTIX-WEB"),
// )
// .finish();
// let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
// let entry_time = time::now();
let now = time::now();
for unit in &mut format.0 {
unit.render_request(now, &req);
}
// let render = |fmt: &mut Formatter| {
// for unit in &format.0 {
// unit.render(fmt, &req, &resp, entry_time)?;
// }
// Ok(())
// };
// let s = format!("{}", FormatDisplay(&render));
// assert!(s.contains("GET / HTTP/1.1"));
// assert!(s.contains("200 0"));
// assert!(s.contains("ACTIX-WEB"));
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
for unit in &mut format.0 {
unit.render_response(&resp);
}
// let req = TestRequest::with_uri("/?test").finish();
// let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
// let entry_time = time::now();
let render = |fmt: &mut Formatter| {
for unit in &format.0 {
unit.render(fmt, 1024, now)?;
}
Ok(())
};
let s = format!("{}", FormatDisplay(&render));
println!("{}", s);
assert!(s.contains("/test/route/yeah"));
}
// let render = |fmt: &mut Formatter| {
// for unit in &format.0 {
// unit.render(fmt, &req, &resp, entry_time)?;
// }
// Ok(())
// };
// let s = format!("{}", FormatDisplay(&render));
// assert!(s.contains("GET /?test HTTP/1.1"));
// }
#[test]
fn test_default_format() {
let mut format = Format::default();
let req = TestRequest::with_header(
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
)
.to_srv_request();
let now = time::now();
for unit in &mut format.0 {
unit.render_request(now, &req);
}
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
for unit in &mut format.0 {
unit.render_response(&resp);
}
let entry_time = time::now();
let render = |fmt: &mut Formatter| {
for unit in &format.0 {
unit.render(fmt, 1024, entry_time)?;
}
Ok(())
};
let s = format!("{}", FormatDisplay(&render));
assert!(s.contains("GET / HTTP/1.1"));
assert!(s.contains("200 1024"));
assert!(s.contains("ACTIX-WEB"));
}
}

View File

@@ -1,6 +1,5 @@
use std::cell::{Ref, RefMut};
use std::cell::{Ref, RefCell, RefMut};
use std::fmt;
use std::ops::Deref;
use std::rc::Rc;
use actix_http::http::{HeaderMap, Method, Uri, Version};
@@ -8,36 +7,42 @@ use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead};
use actix_router::{Path, Url};
use crate::config::AppConfig;
use crate::data::Data;
use crate::data::{Data, RouteData};
use crate::error::UrlGenerationError;
use crate::extract::FromRequest;
use crate::info::ConnectionInfo;
use crate::rmap::ResourceMap;
use crate::service::ServiceFromRequest;
#[derive(Clone)]
/// An HTTP Request
pub struct HttpRequest {
pub struct HttpRequest(pub(crate) Rc<HttpRequestInner>);
pub(crate) struct HttpRequestInner {
pub(crate) head: Message<RequestHead>,
pub(crate) path: Path<Url>,
rmap: Rc<ResourceMap>,
config: AppConfig,
route_data: Option<Rc<Extensions>>,
pool: &'static HttpRequestPool,
}
impl HttpRequest {
#[inline]
pub(crate) fn new(
head: Message<RequestHead>,
path: Path<Url>,
head: Message<RequestHead>,
rmap: Rc<ResourceMap>,
config: AppConfig,
pool: &'static HttpRequestPool,
) -> HttpRequest {
HttpRequest {
HttpRequest(Rc::new(HttpRequestInner {
head,
path,
rmap,
config,
}
pool,
route_data: None,
}))
}
}
@@ -45,7 +50,14 @@ impl HttpRequest {
/// This method returns reference to the request head
#[inline]
pub fn head(&self) -> &RequestHead {
&self.head
&self.0.head
}
/// This method returns muttable reference to the request head.
/// panics if multiple references of http request exists.
#[inline]
pub(crate) fn head_mut(&mut self) -> &mut RequestHead {
&mut Rc::get_mut(&mut self.0).unwrap().head
}
/// Request's uri.
@@ -66,6 +78,12 @@ impl HttpRequest {
self.head().version
}
#[inline]
/// Returns request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.head().headers
}
/// The target path of this Request.
#[inline]
pub fn path(&self) -> &str {
@@ -92,23 +110,24 @@ impl HttpRequest {
/// access the matched value for that segment.
#[inline]
pub fn match_info(&self) -> &Path<Url> {
&self.path
&self.0.path
}
/// App config
#[inline]
pub fn config(&self) -> &AppConfig {
&self.config
pub(crate) fn match_info_mut(&mut self) -> &mut Path<Url> {
&mut Rc::get_mut(&mut self.0).unwrap().path
}
/// Get an application data stored with `App::data()` method during
/// application configuration.
pub fn app_data<T: 'static>(&self) -> Option<Data<T>> {
if let Some(st) = self.config.extensions().get::<Data<T>>() {
Some(st.clone())
} else {
None
}
/// Request extensions
#[inline]
pub fn extensions(&self) -> Ref<Extensions> {
self.head().extensions()
}
/// Mutable reference to a the request's extensions
#[inline]
pub fn extensions_mut(&self) -> RefMut<Extensions> {
self.head().extensions_mut()
}
/// Generate url for named resource
@@ -139,7 +158,7 @@ impl HttpRequest {
U: IntoIterator<Item = I>,
I: AsRef<str>,
{
self.rmap.url_for(&self, name, elements)
self.0.rmap.url_for(&self, name, elements)
}
/// Generate url for named resource
@@ -154,15 +173,37 @@ impl HttpRequest {
/// Get *ConnectionInfo* for the current request.
#[inline]
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
ConnectionInfo::get(&*self, &*self.config())
ConnectionInfo::get(self.head(), &*self.app_config())
}
}
impl Deref for HttpRequest {
type Target = RequestHead;
/// App config
#[inline]
pub fn app_config(&self) -> &AppConfig {
&self.0.config
}
fn deref(&self) -> &RequestHead {
self.head()
/// Get an application data stored with `App::data()` method during
/// application configuration.
pub fn app_data<T: 'static>(&self) -> Option<Data<T>> {
if let Some(st) = self.0.config.extensions().get::<Data<T>>() {
Some(st.clone())
} else {
None
}
}
/// Load route data. Route data could be set during
/// route configuration with `Route::data()` method.
pub fn route_data<T: 'static>(&self) -> Option<&RouteData<T>> {
if let Some(ref ext) = self.0.route_data {
ext.get::<RouteData<T>>()
} else {
None
}
}
pub(crate) fn set_route_data(&mut self, data: Option<Rc<Extensions>>) {
Rc::get_mut(&mut self.0).unwrap().route_data = data;
}
}
@@ -178,13 +219,13 @@ impl HttpMessage for HttpRequest {
/// Request extensions
#[inline]
fn extensions(&self) -> Ref<Extensions> {
self.head.extensions()
self.0.head.extensions()
}
/// Mutable reference to a the request's extensions
#[inline]
fn extensions_mut(&self) -> RefMut<Extensions> {
self.head.extensions_mut()
self.0.head.extensions_mut()
}
#[inline]
@@ -193,6 +234,17 @@ impl HttpMessage for HttpRequest {
}
}
impl Drop for HttpRequest {
fn drop(&mut self) {
if Rc::strong_count(&self.0) == 1 {
let v = &mut self.0.pool.0.borrow_mut();
if v.len() < 128 {
v.push(self.0.clone());
}
}
}
}
/// It is possible to get `HttpRequest` as an extractor handler parameter
///
/// ## Example
@@ -218,7 +270,7 @@ impl<P> FromRequest<P> for HttpRequest {
type Future = Result<Self, Error>;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
Ok(req.clone())
}
}
@@ -228,8 +280,8 @@ impl fmt::Debug for HttpRequest {
writeln!(
f,
"\nHttpRequest {:?} {}:{}",
self.head.version,
self.head.method,
self.0.head.version,
self.0.head.method,
self.path()
)?;
if !self.query_string().is_empty() {
@@ -246,6 +298,26 @@ impl fmt::Debug for HttpRequest {
}
}
/// Request's objects pool
pub(crate) struct HttpRequestPool(RefCell<Vec<Rc<HttpRequestInner>>>);
impl HttpRequestPool {
pub(crate) fn create() -> &'static HttpRequestPool {
let pool = HttpRequestPool(RefCell::new(Vec::with_capacity(128)));
Box::leak(Box::new(pool))
}
/// Get message from the pool
#[inline]
pub(crate) fn get_request(&self) -> Option<HttpRequest> {
if let Some(inner) = self.0.borrow_mut().pop() {
Some(HttpRequest(inner))
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -277,10 +349,10 @@ mod tests {
{
let cookies = req.cookies().unwrap();
assert_eq!(cookies.len(), 2);
assert_eq!(cookies[0].name(), "cookie1");
assert_eq!(cookies[0].value(), "value1");
assert_eq!(cookies[1].name(), "cookie2");
assert_eq!(cookies[1].value(), "value2");
assert_eq!(cookies[0].name(), "cookie2");
assert_eq!(cookies[0].value(), "value2");
assert_eq!(cookies[1].name(), "cookie1");
assert_eq!(cookies[1].value(), "value1");
}
let cookie = req.cookie("cookie1");

View File

@@ -4,7 +4,7 @@ use std::rc::Rc;
use actix_http::{Error, Response};
use actix_service::boxed::{self, BoxedNewService, BoxedService};
use actix_service::{
ApplyTransform, IntoNewService, IntoTransform, NewService, Service, Transform,
apply_transform, IntoNewService, IntoTransform, NewService, Service, Transform,
};
use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, IntoFuture, Poll};
@@ -254,7 +254,7 @@ where
>,
F: IntoTransform<M, T::Service>,
{
let endpoint = ApplyTransform::new(mw, self.endpoint);
let endpoint = apply_transform(mw, self.endpoint);
Resource {
endpoint,
rdef: self.rdef,
@@ -487,11 +487,8 @@ impl<P> Service for ResourceService<P> {
type Response = ServiceResponse;
type Error = Error;
type Future = Either<
FutureResult<ServiceResponse, Error>,
Box<Future<Item = ServiceResponse, Error = Error>>,
Either<
Box<Future<Item = Self::Response, Error = Self::Error>>,
FutureResult<Self::Response, Self::Error>,
>,
>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
@@ -501,17 +498,17 @@ impl<P> Service for ResourceService<P> {
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
for route in self.routes.iter_mut() {
if route.check(&mut req) {
return Either::A(route.call(req));
return route.call(req);
}
}
if let Some(ref mut default) = self.default {
Either::B(Either::A(default.call(req)))
default.call(req)
} else {
let req = req.into_parts().0;
Either::B(Either::B(ok(ServiceResponse::new(
Either::A(ok(ServiceResponse::new(
req,
Response::MethodNotAllowed().finish(),
))))
)))
}
}
}

View File

@@ -313,7 +313,7 @@ pub(crate) mod tests {
let req = TestRequest::with_uri("/some").to_request();
let resp = TestRequest::block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
match resp.body() {
match resp.response().body() {
ResponseBody::Body(Body::Bytes(ref b)) => {
let bytes: Bytes = b.clone().into();
assert_eq!(bytes, Bytes::from_static(b"some"));

Some files were not shown because too many files have changed in this diff Show More