1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-08-22 21:55:10 +02:00

Compare commits

..

59 Commits

Author SHA1 Message Date
Nikolay Kim
5740f1e63a prepare actix-framed release 2019-04-16 11:18:47 -07:00
Nikolay Kim
c943e95812 update dependencies 2019-04-16 11:17:29 -07:00
Nikolay Kim
4c0ebd55d3 prepare actix-http-test release 2019-04-16 11:02:26 -07:00
Nikolay Kim
e7ec77aa81 update readme 2019-04-16 10:50:37 -07:00
Nikolay Kim
ddfd7523f7 prepare awc release 2019-04-16 10:49:38 -07:00
Nikolay Kim
2986077a28 no need for feature 2019-04-16 10:32:48 -07:00
Nikolay Kim
3744957804 actix_http::encoding always available 2019-04-16 10:27:58 -07:00
Nikolay Kim
420d3064c5 Add .peer_addr() #744 2019-04-16 10:11:38 -07:00
Nikolay Kim
eb4f6b74fb Merge branch 'master' of github.com:actix/actix-web 2019-04-16 09:58:07 -07:00
Nikolay Kim
a116c4c2c7 Expose peer addr via Request::peer_addr() and RequestHead::peer_addr 2019-04-16 09:54:02 -07:00
Travis Harmon
7f674febb1 add 422 to httpcodes.rs (#782) 2019-04-15 16:55:06 -07:00
Nikolay Kim
14252f5ef2 use test::call_service 2019-04-15 09:09:21 -07:00
Nikolay Kim
7a28b32f6d Rename test::call_success to test::call_service 2019-04-15 07:44:07 -07:00
Nikolay Kim
09cdf1e302 Rename RouterConfig to ServiceConfig 2019-04-15 07:32:49 -07:00
Nikolay Kim
1eebd47072 fix warnings 2019-04-14 21:00:16 -07:00
Nikolay Kim
002c41a7ca update trust-dns 2019-04-14 20:45:44 -07:00
Nikolay Kim
ab4fda6084 update tests 2019-04-14 20:20:33 -07:00
Nikolay Kim
f9078d41cd add test::read_response; fix TestRequest::app_data() 2019-04-14 19:52:12 -07:00
Darin
4cc2b38059 added read_response_json for testing (#776)
* added read_response_json for testing

* cleaned up

* modied docs for read_response_json

* typo in doc

* test code in doc should compile now

* use type coercion in doc

* removed generic R, replaced with Request
2019-04-14 16:25:45 -07:00
Nikolay Kim
d7040dc303 alpha.6 release 2019-04-14 08:09:32 -07:00
Nikolay Kim
6bc1a0c76b Do not set default headers for websocket request 2019-04-14 07:43:53 -07:00
Nikolay Kim
5bd5651faa Allow to use any service as default service 2019-04-13 22:25:00 -07:00
Nikolay Kim
32ac159ba2 update migration 2019-04-13 16:51:41 -07:00
Nikolay Kim
ee33f52736 make extractor config type explicit 2019-04-13 16:35:25 -07:00
Nikolay Kim
4f30fa9d46 Remove generic type for request payload, always use default 2019-04-13 14:50:54 -07:00
Nikolay Kim
043f6e77ae remove nested multipart support 2019-04-13 10:11:07 -07:00
Nikolay Kim
48518df883 do not generate all docs; use docs.rs for 1.0 docs 2019-04-13 09:35:23 -07:00
Nikolay Kim
1f2b15397d prepare alpha5 release 2019-04-12 14:00:45 -07:00
Nikolay Kim
87167f6581 update actix-connect 2019-04-12 12:33:11 -07:00
Nikolay Kim
b4768a8f81 add TestRequest::run(), allows to run async functions 2019-04-12 11:28:57 -07:00
Nikolay Kim
3fb7343e73 provide during test request construction 2019-04-12 11:22:18 -07:00
Nikolay Kim
5cfba5ff16 add FramedRequest builder for testing 2019-04-12 11:15:58 -07:00
Nikolay Kim
67c34a5937 Add Debug impl for BoxedSocket 2019-04-11 16:01:54 -07:00
Nikolay Kim
94d7a7f873 custom future for SendError service 2019-04-11 15:12:23 -07:00
Nikolay Kim
d86567fbdc revert SendResponse::Error type 2019-04-11 14:18:58 -07:00
Nikolay Kim
d115b3b3ed ws verifyciation takes RequestHead; add SendError utility service 2019-04-11 14:00:32 -07:00
Nikolay Kim
6420a2fe1f update client example 2019-04-10 20:57:18 -07:00
Nikolay Kim
0eed9e5257 add more migration 2019-04-10 20:51:57 -07:00
Nikolay Kim
7801fcb993 update migration 2019-04-10 20:47:28 -07:00
Nikolay Kim
e55be4dba6 add FramedRequest helper methods 2019-04-10 19:57:34 -07:00
Nikolay Kim
12e1dad42e export TestBuffer 2019-04-10 19:43:09 -07:00
Nikolay Kim
7cd59c38d3 rename framed App 2019-04-10 18:08:28 -07:00
Nikolay Kim
8dc4a88aa6 add actix-framed 2019-04-10 15:06:27 -07:00
Nikolay Kim
52aebb3bca fmt 2019-04-10 15:05:03 -07:00
Nikolay Kim
6b42b2aaee remove framed for now 2019-04-10 12:55:56 -07:00
Darin
6ab9838977 added some error logging for extractors: Data, Json, Query, and Path (#765)
* added some error logging for extractors

* changed log::error to log::debug and fixed position of log for path

* added request path to debug logs
2019-04-10 12:45:13 -07:00
Nikolay Kim
9d82d4dfb9 Fix body propagation in Response::from_error. #760 2019-04-10 12:43:31 -07:00
Nikolay Kim
9bb40c249f add h1::SendResponse future; renamed to MessageBody::size 2019-04-10 12:24:17 -07:00
Douman
046b7a1425 Expand codegen to allow specify guards and async 2019-04-10 15:43:18 +03:00
Nikolay Kim
c22a3a71f2 fix test 2019-04-08 19:07:11 -07:00
Nikolay Kim
9c9940d88d update readme 2019-04-08 17:53:19 -07:00
Nikolay Kim
561f83d044 add upgrade service support to h1 dispatcher 2019-04-08 17:51:14 -07:00
Nikolay Kim
43d325a139 allow to specify upgrade service 2019-04-08 14:51:16 -07:00
Nikolay Kim
0a6dd0efdf fix compression tests 2019-04-08 12:48:39 -07:00
Nikolay Kim
b921abf18f set host header for http1 connections 2019-04-08 12:48:26 -07:00
Darin
9bcd5d6664 updated legacy code in call_success example (#762) 2019-04-08 11:20:46 -07:00
Nikolay Kim
bc58dbb2f5 add async expect service test 2019-04-08 11:19:56 -07:00
Nikolay Kim
b1547bbbb6 do not set default headers 2019-04-08 11:09:57 -07:00
Nikolay Kim
a7fdac1043 fix expect service registration and tests 2019-04-08 10:31:29 -07:00
119 changed files with 4980 additions and 2471 deletions

View File

@@ -42,7 +42,7 @@ script:
after_success: after_success:
- | - |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cargo doc --all-features && cargo doc --no-deps --all-features &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html && 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 && 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 && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&

View File

@@ -1,5 +1,50 @@
# Changes # Changes
## [1.0.0-beta.1] - 2019-04-xx
### Added
* Add helper functions for reading test response body,
`test::read_response()` and test::read_response_json()`
* Add `.peer_addr()` #744
### Changed
* Rename `RouterConfig` to `ServiceConfig`
* Rename `test::call_success` to `test::call_service`
### Fixed
* Fixed `TestRequest::app_data()`
## [1.0.0-alpha.6] - 2019-04-14
### Changed
* Allow to use any service as default service.
* Remove generic type for request payload, always use default.
* Removed `Decompress` middleware. Bytes, String, Json, Form extractors
automatically decompress payload.
* Make extractor config type explicit. Add `FromRequest::Config` associated type.
## [1.0.0-alpha.5] - 2019-04-12
### Added
* Added async io `TestBuffer` for testing.
### Deleted
* Removed native-tls support
## [1.0.0-alpha.4] - 2019-04-08 ## [1.0.0-alpha.4] - 2019-04-08
### Added ### Added
@@ -18,6 +63,10 @@
* Move multipart support to actix-multipart crate * Move multipart support to actix-multipart crate
### Fixed
* Fix body propagation in Response::from_error. #760
## [1.0.0-alpha.3] - 2019-04-02 ## [1.0.0-alpha.3] - 2019-04-02

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "1.0.0-alpha.4" version = "1.0.0-alpha.6"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md" readme = "README.md"
@@ -29,6 +29,7 @@ members = [
"awc", "awc",
"actix-http", "actix-http",
"actix-files", "actix-files",
"actix-framed",
"actix-session", "actix-session",
"actix-multipart", "actix-multipart",
"actix-web-actors", "actix-web-actors",
@@ -37,7 +38,7 @@ members = [
] ]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["ssl", "tls", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"] features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"]
[features] [features]
default = ["brotli", "flate2-zlib", "secure-cookies", "client"] default = ["brotli", "flate2-zlib", "secure-cookies", "client"]
@@ -57,9 +58,6 @@ flate2-rust = ["actix-http/flate2-rust"]
# sessions feature, session require "ring" crate and c compiler # sessions feature, session require "ring" crate and c compiler
secure-cookies = ["actix-http/secure-cookies"] secure-cookies = ["actix-http/secure-cookies"]
# tls
tls = ["native-tls", "actix-server/ssl"]
# openssl # openssl
ssl = ["openssl", "actix-server/ssl", "awc/ssl"] ssl = ["openssl", "actix-server/ssl", "awc/ssl"]
@@ -67,23 +65,23 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"]
rust-tls = ["rustls", "actix-server/rust-tls"] rust-tls = ["rustls", "actix-server/rust-tls"]
[dependencies] [dependencies]
actix-codec = "0.1.1" actix-codec = "0.1.2"
actix-service = "0.3.6" actix-service = "0.3.6"
actix-utils = "0.3.4" actix-utils = "0.3.4"
actix-router = "0.1.2" actix-router = "0.1.2"
actix-rt = "0.2.2" actix-rt = "0.2.2"
actix-web-codegen = "0.1.0-alpha.1" actix-web-codegen = "0.1.0-alpha.6"
actix-http = { version = "0.1.0-alpha.4", features=["fail"] } actix-http = { version = "0.1.0", features=["fail"] }
actix-server = "0.4.2" actix-server = "0.4.3"
actix-server-config = "0.1.0" actix-server-config = "0.1.1"
actix-threadpool = "0.1.0" actix-threadpool = "0.1.0"
awc = { version = "0.1.0-alpha.4", optional = true } awc = { version = "0.1.0", optional = true }
bytes = "0.4" bytes = "0.4"
derive_more = "0.14" derive_more = "0.14"
encoding = "0.2" encoding = "0.2"
futures = "0.1" futures = "0.1"
hashbrown = "0.1.8" hashbrown = "0.2.2"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
net2 = "0.2.33" net2 = "0.2.33"
@@ -91,18 +89,18 @@ parking_lot = "0.7"
regex = "1.0" regex = "1.0"
serde = { version = "1.0", features=["derive"] } serde = { version = "1.0", features=["derive"] }
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "^0.5.3" serde_urlencoded = "0.5.3"
time = "0.1" time = "0.1"
url = { version="1.7", features=["query_encoding"] } url = { version="1.7", features=["query_encoding"] }
# ssl support # ssl support
native-tls = { version="0.2", optional = true }
openssl = { version="0.10", optional = true } openssl = { version="0.10", optional = true }
rustls = { version = "^0.15", optional = true } rustls = { version = "^0.15", optional = true }
[dev-dependencies] [dev-dependencies]
actix-http = { version = "0.1.0-alpha.4", features=["ssl", "brotli", "flate2-zlib"] } actix-http = { version = "0.1.0", features=["ssl", "brotli", "flate2-zlib"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] }
actix-files = { version = "0.1.0-alpha.6" }
rand = "0.6" rand = "0.6"
env_logger = "0.6" env_logger = "0.6"
serde_derive = "1.0" serde_derive = "1.0"
@@ -116,10 +114,8 @@ opt-level = 3
codegen-units = 1 codegen-units = 1
[patch.crates-io] [patch.crates-io]
actix = { git = "https://github.com/actix/actix.git" }
actix-web = { path = "." } actix-web = { path = "." }
actix-http = { path = "actix-http" } actix-http = { path = "actix-http" }
actix-http-test = { path = "test-server" }
actix-web-codegen = { path = "actix-web-codegen" } actix-web-codegen = { path = "actix-web-codegen" }
actix-web-actors = { path = "actix-web-actors" } actix-web-actors = { path = "actix-web-actors" }
actix-session = { path = "actix-session" } actix-session = { path = "actix-session" }

View File

@@ -1,5 +1,97 @@
## 1.0 ## 1.0
* Resource registration. 1.0 version uses generalized resource
registration via `.service()` method.
instead of
```rust
App.new().resource("/welcome", |r| r.f(welcome))
```
use App's or Scope's `.service()` method. `.service()` method accepts
object that implements `HttpServiceFactory` trait. By default
actix-web provides `Resource` and `Scope` services.
```rust
App.new().service(
web::resource("/welcome")
.route(web::get().to(welcome))
.route(web::post().to(post_handler))
```
* Scope registration.
instead of
```rust
let app = App::new().scope("/{project_id}", |scope| {
scope
.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
.resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
.resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
});
```
use `.service()` for registration and `web::scope()` as scope object factory.
```rust
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()))
);
```
* `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`.
instead of
```rust
App.new().resource("/welcome", |r| r.with(welcome))
```
use `.to()` or `.to_async()` methods
```rust
App.new().service(web::resource("/welcome").to(welcome))
```
* Passing arguments to handler with extractors, multiple arguments are allowed
instead of
```rust
fn welcome((body, req): (Bytes, HttpRequest)) -> ... {
...
}
```
use multiple arguments
```rust
fn welcome(body: Bytes, req: HttpRequest) -> ... {
...
}
```
* `.f()`, `.a()` and `.h()` handler registration methods have been removed.
Use `.to()` for handlers and `.to_async()` for async handlers. Handler function
must use extractors.
instead of
```rust
App.new().resource("/welcome", |r| r.f(welcome))
```
use App's `to()` or `to_async()` methods
```rust
App.new().service(web::resource("/welcome").to(welcome))
```
* `State` is now `Data`. You register Data during the App initialization process * `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 and then access it from handlers either using a Data extractor or using
HttpRequest's api. HttpRequest's api.
@@ -36,7 +128,7 @@ HttpRequest's api.
``` ```
* AsyncResponder is deprecated. * AsyncResponder is removed.
instead of instead of
@@ -52,6 +144,69 @@ HttpRequest's api.
.. simply omit AsyncResponder and the corresponding responder() finish method .. simply omit AsyncResponder and the corresponding responder() finish method
* Middleware
instead of
```rust
let app = App::new()
.middleware(middleware::Logger::default())
```
use `.wrap()` method
```rust
let app = App::new()
.wrap(middleware::Logger::default())
.route("/index.html", web::get().to(index));
```
* `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()`
method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead.
instead if
```rust
fn index(req: &HttpRequest) -> Responder {
req.body()
.and_then(|body| {
...
})
}
use
```rust
fn index(body: Bytes) -> Responder {
...
}
```
* `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type
* StaticFiles and NamedFile has been move to separate create.
instead of `use actix_web::fs::StaticFile`
use `use actix_files::Files`
instead of `use actix_web::fs::Namedfile`
use `use actix_files::NamedFile`
* Multipart has been move to separate create.
instead of `use actix_web::multipart::Multipart`
use `use actix_multipart::Multipart`
* Response compression is not enabled by default.
To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`.
* Session middleware moved to actix-session crate
* Actors support have been moved to `actix-web-actors` crate
## 0.7.15 ## 0.7.15

View File

@@ -18,8 +18,8 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
## Documentation & community resources ## Documentation & community resources
* [User Guide](https://actix.rs/docs/) * [User Guide](https://actix.rs/docs/)
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) * [API Documentation (1.0)](https://docs.rs/actix-web/)
* [API Documentation (0.7 Release)](https://docs.rs/actix-web/0.7.19/actix_web/) * [API Documentation (0.7)](https://docs.rs/actix-web/0.7.19/actix_web/)
* [Chat on gitter](https://gitter.im/actix/actix) * [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-web](https://crates.io/crates/actix-web) * Cargo package: [actix-web](https://crates.io/crates/actix-web)
* Minimum supported Rust version: 1.32 or later * Minimum supported Rust version: 1.32 or later
@@ -36,8 +36,7 @@ fn index(info: web::Path<(u32, String)>) -> impl Responder {
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
HttpServer::new( HttpServer::new(
|| App::new().service( || App::new().service(
web::resource("/{id}/{name}/index.html") web::resource("/{id}/{name}/index.html").to(index)))
.route(web::get().to(index))))
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .run()
} }

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ use std::{cmp, io};
use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::boxed::{self, BoxedNewService, BoxedService};
use actix_service::{IntoNewService, NewService, Service}; use actix_service::{IntoNewService, NewService, Service};
use actix_web::dev::{ use actix_web::dev::{
HttpServiceFactory, Payload, ResourceDef, ServiceConfig, ServiceRequest, AppService, HttpServiceFactory, Payload, ResourceDef, ServiceRequest,
ServiceResponse, ServiceResponse,
}; };
use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; use actix_web::error::{BlockingError, Error, ErrorInternalServerError};
@@ -32,9 +32,8 @@ use self::error::{FilesError, UriSegmentError};
pub use crate::named::NamedFile; pub use crate::named::NamedFile;
pub use crate::range::HttpRange; pub use crate::range::HttpRange;
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>; type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
type HttpNewService<P> = type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
/// Return the MIME type associated with a filename extension (case-insensitive). /// Return the MIME type associated with a filename extension (case-insensitive).
/// If `ext` is empty or no associated type for the extension was found, returns /// If `ext` is empty or no associated type for the extension was found, returns
@@ -225,18 +224,18 @@ type MimeOverride = Fn(&mime::Name) -> DispositionType;
/// .service(fs::Files::new("/static", ".")); /// .service(fs::Files::new("/static", "."));
/// } /// }
/// ``` /// ```
pub struct Files<S> { pub struct Files {
path: String, path: String,
directory: PathBuf, directory: PathBuf,
index: Option<String>, index: Option<String>,
show_index: bool, show_index: bool,
default: Rc<RefCell<Option<Rc<HttpNewService<S>>>>>, default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
renderer: Rc<DirectoryRenderer>, renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>, mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags, file_flags: named::Flags,
} }
impl<S> Clone for Files<S> { impl Clone for Files {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
directory: self.directory.clone(), directory: self.directory.clone(),
@@ -251,13 +250,13 @@ impl<S> Clone for Files<S> {
} }
} }
impl<S: 'static> Files<S> { impl Files {
/// Create new `Files` instance for specified base directory. /// Create new `Files` instance for specified base directory.
/// ///
/// `File` uses `ThreadPool` for blocking filesystem operations. /// `File` uses `ThreadPool` for blocking filesystem operations.
/// By default pool with 5x threads of available cpus is used. /// By default pool with 5x threads of available cpus is used.
/// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable.
pub fn new<T: Into<PathBuf>>(path: &str, dir: T) -> Files<S> { pub fn new<T: Into<PathBuf>>(path: &str, dir: T) -> Files {
let dir = dir.into().canonicalize().unwrap_or_else(|_| PathBuf::new()); let dir = dir.into().canonicalize().unwrap_or_else(|_| PathBuf::new());
if !dir.is_dir() { if !dir.is_dir() {
log::error!("Specified path is not a directory"); log::error!("Specified path is not a directory");
@@ -335,7 +334,7 @@ impl<S: 'static> Files<S> {
where where
F: IntoNewService<U>, F: IntoNewService<U>,
U: NewService< U: NewService<
Request = ServiceRequest<S>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
> + 'static, > + 'static,
@@ -349,11 +348,8 @@ impl<S: 'static> Files<S> {
} }
} }
impl<P> HttpServiceFactory<P> for Files<P> impl HttpServiceFactory for Files {
where fn register(self, config: &mut AppService) {
P: 'static,
{
fn register(self, config: &mut ServiceConfig<P>) {
if self.default.borrow().is_none() { if self.default.borrow().is_none() {
*self.default.borrow_mut() = Some(config.default_service()); *self.default.borrow_mut() = Some(config.default_service());
} }
@@ -366,11 +362,11 @@ where
} }
} }
impl<P: 'static> NewService for Files<P> { impl NewService for Files {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Service = FilesService<P>; type Service = FilesService;
type InitError = (); type InitError = ();
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>; type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
@@ -401,22 +397,22 @@ impl<P: 'static> NewService for Files<P> {
} }
} }
pub struct FilesService<P> { pub struct FilesService {
directory: PathBuf, directory: PathBuf,
index: Option<String>, index: Option<String>,
show_index: bool, show_index: bool,
default: Option<HttpService<P>>, default: Option<HttpService>,
renderer: Rc<DirectoryRenderer>, renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>, mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags, file_flags: named::Flags,
} }
impl<P> FilesService<P> { impl FilesService {
fn handle_err( fn handle_err(
&mut self, &mut self,
e: io::Error, e: io::Error,
req: HttpRequest, req: HttpRequest,
payload: Payload<P>, payload: Payload,
) -> Either< ) -> Either<
FutureResult<ServiceResponse, Error>, FutureResult<ServiceResponse, Error>,
Box<Future<Item = ServiceResponse, Error = Error>>, Box<Future<Item = ServiceResponse, Error = Error>>,
@@ -430,8 +426,8 @@ impl<P> FilesService<P> {
} }
} }
impl<P> Service for FilesService<P> { impl Service for FilesService {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Future = Either< type Future = Either<
@@ -443,7 +439,7 @@ impl<P> Service for FilesService<P> {
Ok(Async::Ready(())) Ok(Async::Ready(()))
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
let (req, pl) = req.into_parts(); let (req, pl) = req.into_parts();
let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) { let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) {
@@ -547,11 +543,12 @@ impl PathBufWrp {
} }
} }
impl<P> FromRequest<P> for PathBufWrp { impl FromRequest for PathBufWrp {
type Error = UriSegmentError; type Error = UriSegmentError;
type Future = Result<Self, Self::Error>; type Future = Result<Self, Self::Error>;
type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
PathBufWrp::get_pathbuf(req.match_info().path()) PathBufWrp::get_pathbuf(req.match_info().path())
} }
} }
@@ -570,6 +567,7 @@ mod tests {
self, ContentDisposition, DispositionParam, DispositionType, self, ContentDisposition, DispositionParam, DispositionType,
}; };
use actix_web::http::{Method, StatusCode}; use actix_web::http::{Method, StatusCode};
use actix_web::middleware::Compress;
use actix_web::test::{self, TestRequest}; use actix_web::test::{self, TestRequest};
use actix_web::App; use actix_web::App;
@@ -777,7 +775,7 @@ mod tests {
); );
let request = TestRequest::get().uri("/").to_request(); let request = TestRequest::get().uri("/").to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
let content_disposition = response let content_disposition = response
@@ -801,7 +799,7 @@ mod tests {
.uri("/t%65st/Cargo.toml") .uri("/t%65st/Cargo.toml")
.header(header::RANGE, "bytes=10-20") .header(header::RANGE, "bytes=10-20")
.to_request(); .to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
// Invalid range header // Invalid range header
@@ -809,7 +807,7 @@ mod tests {
.uri("/t%65st/Cargo.toml") .uri("/t%65st/Cargo.toml")
.header(header::RANGE, "bytes=1-0") .header(header::RANGE, "bytes=1-0")
.to_request(); .to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
} }
@@ -826,7 +824,7 @@ mod tests {
.header(header::RANGE, "bytes=10-20") .header(header::RANGE, "bytes=10-20")
.to_request(); .to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
let contentrange = response let contentrange = response
.headers() .headers()
.get(header::CONTENT_RANGE) .get(header::CONTENT_RANGE)
@@ -841,7 +839,7 @@ mod tests {
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-5") .header(header::RANGE, "bytes=10-5")
.to_request(); .to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
let contentrange = response let contentrange = response
.headers() .headers()
@@ -864,7 +862,7 @@ mod tests {
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-20") .header(header::RANGE, "bytes=10-20")
.to_request(); .to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
let contentlength = response let contentlength = response
.headers() .headers()
@@ -880,7 +878,7 @@ mod tests {
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-8") .header(header::RANGE, "bytes=10-8")
.to_request(); .to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
// Without range header // Without range header
@@ -888,7 +886,7 @@ mod tests {
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
// .no_default_headers() // .no_default_headers()
.to_request(); .to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
let contentlength = response let contentlength = response
.headers() .headers()
@@ -903,7 +901,7 @@ mod tests {
let request = TestRequest::get() let request = TestRequest::get()
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
.to_request(); .to_request();
let mut response = test::call_success(&mut srv, request); let mut response = test::call_service(&mut srv, request);
// with enabled compression // with enabled compression
// { // {
@@ -934,7 +932,7 @@ mod tests {
let request = TestRequest::get() let request = TestRequest::get()
.uri("/tests/test%20space.binary") .uri("/tests/test%20space.binary")
.to_request(); .to_request();
let mut response = test::call_success(&mut srv, request); let mut response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
let bytes = let bytes =
@@ -965,7 +963,7 @@ mod tests {
#[test] #[test]
fn test_named_file_content_encoding() { fn test_named_file_content_encoding() {
let mut srv = test::init_service(App::new().enable_encoding().service( let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
web::resource("/").to(|| { web::resource("/").to(|| {
NamedFile::open("Cargo.toml") NamedFile::open("Cargo.toml")
.unwrap() .unwrap()
@@ -977,14 +975,14 @@ mod tests {
.uri("/") .uri("/")
.header(header::ACCEPT_ENCODING, "gzip") .header(header::ACCEPT_ENCODING, "gzip")
.to_request(); .to_request();
let res = test::call_success(&mut srv, request); let res = test::call_service(&mut srv, request);
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
} }
#[test] #[test]
fn test_named_file_content_encoding_gzip() { fn test_named_file_content_encoding_gzip() {
let mut srv = test::init_service(App::new().enable_encoding().service( let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
web::resource("/").to(|| { web::resource("/").to(|| {
NamedFile::open("Cargo.toml") NamedFile::open("Cargo.toml")
.unwrap() .unwrap()
@@ -996,7 +994,7 @@ mod tests {
.uri("/") .uri("/")
.header(header::ACCEPT_ENCODING, "gzip") .header(header::ACCEPT_ENCODING, "gzip")
.to_request(); .to_request();
let res = test::call_success(&mut srv, request); let res = test::call_service(&mut srv, request);
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert_eq!( assert_eq!(
res.headers() res.headers()
@@ -1023,20 +1021,20 @@ mod tests {
); );
let req = TestRequest::with_uri("/missing").to_request(); let req = TestRequest::with_uri("/missing").to_request();
let resp = test::call_success(&mut srv, req); let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut srv = test::init_service(App::new().service(Files::new("/", "."))); let mut srv = test::init_service(App::new().service(Files::new("/", ".")));
let req = TestRequest::default().to_request(); let req = TestRequest::default().to_request();
let resp = test::call_success(&mut srv, req); let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut srv = test::init_service( let mut srv = test::init_service(
App::new().service(Files::new("/", ".").show_files_listing()), App::new().service(Files::new("/", ".").show_files_listing()),
); );
let req = TestRequest::with_uri("/tests").to_request(); let req = TestRequest::with_uri("/tests").to_request();
let mut resp = test::call_success(&mut srv, req); let mut resp = test::call_service(&mut srv, req);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/html; charset=utf-8" "text/html; charset=utf-8"
@@ -1053,15 +1051,15 @@ mod tests {
#[test] #[test]
fn test_static_files_bad_directory() { fn test_static_files_bad_directory() {
let _st: Files<()> = Files::new("/", "missing"); let _st: Files = Files::new("/", "missing");
let _st: Files<()> = Files::new("/", "Cargo.toml"); let _st: Files = Files::new("/", "Cargo.toml");
} }
#[test] #[test]
fn test_default_handler_file_missing() { fn test_default_handler_file_missing() {
let mut st = test::block_on( let mut st = test::block_on(
Files::new("/", ".") Files::new("/", ".")
.default_handler(|req: ServiceRequest<_>| { .default_handler(|req: ServiceRequest| {
Ok(req.into_response(HttpResponse::Ok().body("default content"))) Ok(req.into_response(HttpResponse::Ok().body("default content")))
}) })
.new_service(&()), .new_service(&()),
@@ -1069,7 +1067,7 @@ mod tests {
.unwrap(); .unwrap();
let req = TestRequest::with_uri("/missing").to_srv_request(); let req = TestRequest::with_uri("/missing").to_srv_request();
let mut resp = test::call_success(&mut st, req); let mut resp = test::call_service(&mut st, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let bytes = let bytes =
test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| { test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| {

View File

@@ -15,7 +15,7 @@ use actix_web::http::header::{
self, ContentDisposition, DispositionParam, DispositionType, self, ContentDisposition, DispositionParam, DispositionType,
}; };
use actix_web::http::{ContentEncoding, Method, StatusCode}; use actix_web::http::{ContentEncoding, Method, StatusCode};
use actix_web::middleware::encoding::BodyEncoding; use actix_web::middleware::BodyEncoding;
use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder};
use crate::range::HttpRange; use crate::range::HttpRange;

37
actix-framed/Cargo.toml Normal file
View File

@@ -0,0 +1,37 @@
[package]
name = "actix-framed"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix framed app server"
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-framed/"
categories = ["network-programming", "asynchronous",
"web-programming::http-server",
"web-programming::websocket"]
license = "MIT/Apache-2.0"
edition = "2018"
workspace =".."
[lib]
name = "actix_framed"
path = "src/lib.rs"
[dependencies]
actix-codec = "0.1.2"
actix-service = "0.3.6"
actix-utils = "0.3.4"
actix-router = "0.1.2"
actix-rt = "0.2.2"
actix-http = "0.1.0"
bytes = "0.4"
futures = "0.1.25"
log = "0.4"
[dev-dependencies]
actix-server = { version = "0.4.3", features=["ssl"] }
actix-connect = { version = "0.1.4", features=["ssl"] }
actix-http-test = { version = "0.1.0", features=["ssl"] }

201
actix-framed/LICENSE-APACHE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017-NOW Nikolay Kim
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

25
actix-framed/LICENSE-MIT Normal file
View File

@@ -0,0 +1,25 @@
Copyright (c) 2017 Nikolay Kim
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

1
actix-framed/README.md Normal file
View File

@@ -0,0 +1 @@
# Framed app for actix web [![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-framed)](https://crates.io/crates/actix-framed) [![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)

10
actix-framed/changes.md Normal file
View File

@@ -0,0 +1,10 @@
# Changes
## [0.1.0] - 2019-04-16
* Update tests
## [0.1.0-alpha.1] - 2019-04-12
* Initial release

215
actix-framed/src/app.rs Normal file
View File

@@ -0,0 +1,215 @@
use std::rc::Rc;
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::h1::{Codec, SendResponse};
use actix_http::{Error, Request, Response};
use actix_router::{Path, Router, Url};
use actix_service::{IntoNewService, NewService, Service};
use actix_utils::cloneable::CloneableService;
use futures::{Async, Future, Poll};
use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService};
use crate::request::FramedRequest;
use crate::state::State;
type BoxedResponse = Box<Future<Item = (), Error = Error>>;
pub trait HttpServiceFactory {
type Factory: NewService;
fn path(&self) -> &str;
fn create(self) -> Self::Factory;
}
/// Application builder
pub struct FramedApp<T, S = ()> {
state: State<S>,
services: Vec<(String, BoxedHttpNewService<FramedRequest<T, S>>)>,
}
impl<T: 'static> FramedApp<T, ()> {
pub fn new() -> Self {
FramedApp {
state: State::new(()),
services: Vec::new(),
}
}
}
impl<T: 'static, S: 'static> FramedApp<T, S> {
pub fn with(state: S) -> FramedApp<T, S> {
FramedApp {
services: Vec::new(),
state: State::new(state),
}
}
pub fn service<U>(mut self, factory: U) -> Self
where
U: HttpServiceFactory,
U::Factory: NewService<
Request = FramedRequest<T, S>,
Response = (),
Error = Error,
InitError = (),
> + 'static,
<U::Factory as NewService>::Future: 'static,
<U::Factory as NewService>::Service: Service<
Request = FramedRequest<T, S>,
Response = (),
Error = Error,
Future = Box<Future<Item = (), Error = Error>>,
>,
{
let path = factory.path().to_string();
self.services
.push((path, Box::new(HttpNewService::new(factory.create()))));
self
}
}
impl<T, S> IntoNewService<FramedAppFactory<T, S>> for FramedApp<T, S>
where
T: AsyncRead + AsyncWrite + 'static,
S: 'static,
{
fn into_new_service(self) -> FramedAppFactory<T, S> {
FramedAppFactory {
state: self.state,
services: Rc::new(self.services),
}
}
}
#[derive(Clone)]
pub struct FramedAppFactory<T, S> {
state: State<S>,
services: Rc<Vec<(String, BoxedHttpNewService<FramedRequest<T, S>>)>>,
}
impl<T, S, C> NewService<C> for FramedAppFactory<T, S>
where
T: AsyncRead + AsyncWrite + 'static,
S: 'static,
{
type Request = (Request, Framed<T, Codec>);
type Response = ();
type Error = Error;
type InitError = ();
type Service = CloneableService<FramedAppService<T, S>>;
type Future = CreateService<T, S>;
fn new_service(&self, _: &C) -> Self::Future {
CreateService {
fut: self
.services
.iter()
.map(|(path, service)| {
CreateServiceItem::Future(
Some(path.clone()),
service.new_service(&()),
)
})
.collect(),
state: self.state.clone(),
}
}
}
#[doc(hidden)]
pub struct CreateService<T, S> {
fut: Vec<CreateServiceItem<T, S>>,
state: State<S>,
}
enum CreateServiceItem<T, S> {
Future(
Option<String>,
Box<Future<Item = BoxedHttpService<FramedRequest<T, S>>, Error = ()>>,
),
Service(String, BoxedHttpService<FramedRequest<T, S>>),
}
impl<S: 'static, T: 'static> Future for CreateService<T, S>
where
T: AsyncRead + AsyncWrite,
{
type Item = CloneableService<FramedAppService<T, S>>;
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut done = true;
// poll http services
for item in &mut self.fut {
let res = match item {
CreateServiceItem::Future(ref mut path, ref mut fut) => {
match fut.poll()? {
Async::Ready(service) => Some((path.take().unwrap(), service)),
Async::NotReady => {
done = false;
None
}
}
}
CreateServiceItem::Service(_, _) => continue,
};
if let Some((path, service)) = res {
*item = CreateServiceItem::Service(path, service);
}
}
if done {
let router = self
.fut
.drain(..)
.fold(Router::build(), |mut router, item| {
match item {
CreateServiceItem::Service(path, service) => {
router.path(&path, service);
}
CreateServiceItem::Future(_, _) => unreachable!(),
}
router
});
Ok(Async::Ready(CloneableService::new(FramedAppService {
router: router.finish(),
state: self.state.clone(),
})))
} else {
Ok(Async::NotReady)
}
}
}
pub struct FramedAppService<T, S> {
state: State<S>,
router: Router<BoxedHttpService<FramedRequest<T, S>>>,
}
impl<S: 'static, T: 'static> Service for FramedAppService<T, S>
where
T: AsyncRead + AsyncWrite,
{
type Request = (Request, Framed<T, Codec>);
type Response = ();
type Error = Error;
type Future = BoxedResponse;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, (req, framed): (Request, Framed<T, Codec>)) -> Self::Future {
let mut path = Path::new(Url::new(req.uri().clone()));
if let Some((srv, _info)) = self.router.recognize_mut(&mut path) {
return srv.call(FramedRequest::new(req, framed, path, self.state.clone()));
}
Box::new(
SendResponse::new(framed, Response::NotFound().finish()).then(|_| Ok(())),
)
}
}

View File

@@ -0,0 +1,88 @@
use actix_http::Error;
use actix_service::{NewService, Service};
use futures::{Future, Poll};
pub(crate) type BoxedHttpService<Req> = Box<
Service<
Request = Req,
Response = (),
Error = Error,
Future = Box<Future<Item = (), Error = Error>>,
>,
>;
pub(crate) type BoxedHttpNewService<Req> = Box<
NewService<
Request = Req,
Response = (),
Error = Error,
InitError = (),
Service = BoxedHttpService<Req>,
Future = Box<Future<Item = BoxedHttpService<Req>, Error = ()>>,
>,
>;
pub(crate) struct HttpNewService<T: NewService>(T);
impl<T> HttpNewService<T>
where
T: NewService<Response = (), Error = Error>,
T::Response: 'static,
T::Future: 'static,
T::Service: Service<Future = Box<Future<Item = (), Error = Error>>> + 'static,
<T::Service as Service>::Future: 'static,
{
pub fn new(service: T) -> Self {
HttpNewService(service)
}
}
impl<T> NewService for HttpNewService<T>
where
T: NewService<Response = (), Error = Error>,
T::Request: 'static,
T::Future: 'static,
T::Service: Service<Future = Box<Future<Item = (), Error = Error>>> + 'static,
<T::Service as Service>::Future: 'static,
{
type Request = T::Request;
type Response = ();
type Error = Error;
type InitError = ();
type Service = BoxedHttpService<T::Request>;
type Future = Box<Future<Item = Self::Service, Error = ()>>;
fn new_service(&self, _: &()) -> Self::Future {
Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| {
let service: BoxedHttpService<_> = Box::new(HttpServiceWrapper { service });
Ok(service)
}))
}
}
struct HttpServiceWrapper<T: Service> {
service: T,
}
impl<T> Service for HttpServiceWrapper<T>
where
T: Service<
Response = (),
Future = Box<Future<Item = (), Error = Error>>,
Error = Error,
>,
T::Request: 'static,
{
type Request = T::Request;
type Response = ();
type Error = Error;
type Future = Box<Future<Item = (), Error = Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, req: Self::Request) -> Self::Future {
self.service.call(req)
}
}

16
actix-framed/src/lib.rs Normal file
View File

@@ -0,0 +1,16 @@
mod app;
mod helpers;
mod request;
mod route;
mod service;
mod state;
pub mod test;
// re-export for convinience
pub use actix_http::{http, Error, HttpMessage, Response, ResponseError};
pub use self::app::{FramedApp, FramedAppService};
pub use self::request::FramedRequest;
pub use self::route::FramedRoute;
pub use self::service::{SendError, VerifyWebSockets};
pub use self::state::State;

170
actix-framed/src/request.rs Normal file
View File

@@ -0,0 +1,170 @@
use std::cell::{Ref, RefMut};
use actix_codec::Framed;
use actix_http::http::{HeaderMap, Method, Uri, Version};
use actix_http::{h1::Codec, Extensions, Request, RequestHead};
use actix_router::{Path, Url};
use crate::state::State;
pub struct FramedRequest<Io, S = ()> {
req: Request,
framed: Framed<Io, Codec>,
state: State<S>,
pub(crate) path: Path<Url>,
}
impl<Io, S> FramedRequest<Io, S> {
pub fn new(
req: Request,
framed: Framed<Io, Codec>,
path: Path<Url>,
state: State<S>,
) -> Self {
Self {
req,
framed,
state,
path,
}
}
}
impl<Io, S> FramedRequest<Io, S> {
/// Split request into a parts
pub fn into_parts(self) -> (Request, Framed<Io, Codec>, State<S>) {
(self.req, self.framed, self.state)
}
/// This method returns reference to the request head
#[inline]
pub fn head(&self) -> &RequestHead {
self.req.head()
}
/// This method returns muttable reference to the request head.
/// panics if multiple references of http request exists.
#[inline]
pub fn head_mut(&mut self) -> &mut RequestHead {
self.req.head_mut()
}
/// Shared application state
#[inline]
pub fn state(&self) -> &S {
self.state.get_ref()
}
/// Request's uri.
#[inline]
pub fn uri(&self) -> &Uri {
&self.head().uri
}
/// Read the Request method.
#[inline]
pub fn method(&self) -> &Method {
&self.head().method
}
/// Read the Request Version.
#[inline]
pub fn version(&self) -> Version {
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 {
self.head().uri.path()
}
/// The query string in the URL.
///
/// E.g., id=10
#[inline]
pub fn query_string(&self) -> &str {
if let Some(query) = self.uri().query().as_ref() {
query
} else {
""
}
}
/// Get a reference to the Path parameters.
///
/// Params is a container for url parameters.
/// 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.
#[inline]
pub fn match_info(&self) -> &Path<Url> {
&self.path
}
/// 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()
}
}
#[cfg(test)]
mod tests {
use actix_http::http::{HeaderName, HeaderValue, HttpTryFrom};
use actix_http::test::{TestBuffer, TestRequest};
use super::*;
#[test]
fn test_reqest() {
let buf = TestBuffer::empty();
let framed = Framed::new(buf, Codec::default());
let req = TestRequest::with_uri("/index.html?q=1")
.header("content-type", "test")
.finish();
let path = Path::new(Url::new(req.uri().clone()));
let mut freq = FramedRequest::new(req, framed, path, State::new(10u8));
assert_eq!(*freq.state(), 10);
assert_eq!(freq.version(), Version::HTTP_11);
assert_eq!(freq.method(), Method::GET);
assert_eq!(freq.path(), "/index.html");
assert_eq!(freq.query_string(), "q=1");
assert_eq!(
freq.headers()
.get("content-type")
.unwrap()
.to_str()
.unwrap(),
"test"
);
freq.head_mut().headers.insert(
HeaderName::try_from("x-hdr").unwrap(),
HeaderValue::from_static("test"),
);
assert_eq!(
freq.headers().get("x-hdr").unwrap().to_str().unwrap(),
"test"
);
freq.extensions_mut().insert(100usize);
assert_eq!(*freq.extensions().get::<usize>().unwrap(), 100usize);
let (_, _, state) = freq.into_parts();
assert_eq!(*state, 10);
}
}

156
actix-framed/src/route.rs Normal file
View File

@@ -0,0 +1,156 @@
use std::fmt;
use std::marker::PhantomData;
use actix_codec::{AsyncRead, AsyncWrite};
use actix_http::{http::Method, Error};
use actix_service::{NewService, Service};
use futures::future::{ok, FutureResult};
use futures::{Async, Future, IntoFuture, Poll};
use log::error;
use crate::app::HttpServiceFactory;
use crate::request::FramedRequest;
/// Resource route definition
///
/// Route uses builder-like pattern for configuration.
/// If handler is not explicitly set, default *404 Not Found* handler is used.
pub struct FramedRoute<Io, S, F = (), R = ()> {
handler: F,
pattern: String,
methods: Vec<Method>,
state: PhantomData<(Io, S, R)>,
}
impl<Io, S> FramedRoute<Io, S> {
pub fn new(pattern: &str) -> Self {
FramedRoute {
handler: (),
pattern: pattern.to_string(),
methods: Vec::new(),
state: PhantomData,
}
}
pub fn get(path: &str) -> FramedRoute<Io, S> {
FramedRoute::new(path).method(Method::GET)
}
pub fn post(path: &str) -> FramedRoute<Io, S> {
FramedRoute::new(path).method(Method::POST)
}
pub fn put(path: &str) -> FramedRoute<Io, S> {
FramedRoute::new(path).method(Method::PUT)
}
pub fn delete(path: &str) -> FramedRoute<Io, S> {
FramedRoute::new(path).method(Method::DELETE)
}
pub fn method(mut self, method: Method) -> Self {
self.methods.push(method);
self
}
pub fn to<F, R>(self, handler: F) -> FramedRoute<Io, S, F, R>
where
F: FnMut(FramedRequest<Io, S>) -> R,
R: IntoFuture<Item = ()>,
R::Future: 'static,
R::Error: fmt::Debug,
{
FramedRoute {
handler,
pattern: self.pattern,
methods: self.methods,
state: PhantomData,
}
}
}
impl<Io, S, F, R> HttpServiceFactory for FramedRoute<Io, S, F, R>
where
Io: AsyncRead + AsyncWrite + 'static,
F: FnMut(FramedRequest<Io, S>) -> R + Clone,
R: IntoFuture<Item = ()>,
R::Future: 'static,
R::Error: fmt::Display,
{
type Factory = FramedRouteFactory<Io, S, F, R>;
fn path(&self) -> &str {
&self.pattern
}
fn create(self) -> Self::Factory {
FramedRouteFactory {
handler: self.handler,
methods: self.methods,
_t: PhantomData,
}
}
}
pub struct FramedRouteFactory<Io, S, F, R> {
handler: F,
methods: Vec<Method>,
_t: PhantomData<(Io, S, R)>,
}
impl<Io, S, F, R> NewService for FramedRouteFactory<Io, S, F, R>
where
Io: AsyncRead + AsyncWrite + 'static,
F: FnMut(FramedRequest<Io, S>) -> R + Clone,
R: IntoFuture<Item = ()>,
R::Future: 'static,
R::Error: fmt::Display,
{
type Request = FramedRequest<Io, S>;
type Response = ();
type Error = Error;
type InitError = ();
type Service = FramedRouteService<Io, S, F, R>;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &()) -> Self::Future {
ok(FramedRouteService {
handler: self.handler.clone(),
methods: self.methods.clone(),
_t: PhantomData,
})
}
}
pub struct FramedRouteService<Io, S, F, R> {
handler: F,
methods: Vec<Method>,
_t: PhantomData<(Io, S, R)>,
}
impl<Io, S, F, R> Service for FramedRouteService<Io, S, F, R>
where
Io: AsyncRead + AsyncWrite + 'static,
F: FnMut(FramedRequest<Io, S>) -> R + Clone,
R: IntoFuture<Item = ()>,
R::Future: 'static,
R::Error: fmt::Display,
{
type Request = FramedRequest<Io, S>;
type Response = ();
type Error = Error;
type Future = Box<Future<Item = (), Error = Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, req: FramedRequest<Io, S>) -> Self::Future {
Box::new((self.handler)(req).into_future().then(|res| {
if let Err(e) = res {
error!("Error in request handler: {}", e);
}
Ok(())
}))
}
}

147
actix-framed/src/service.rs Normal file
View File

@@ -0,0 +1,147 @@
use std::marker::PhantomData;
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::body::BodySize;
use actix_http::error::ResponseError;
use actix_http::h1::{Codec, Message};
use actix_http::ws::{verify_handshake, HandshakeError};
use actix_http::{Request, Response};
use actix_service::{NewService, Service};
use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, IntoFuture, Poll, Sink};
/// Service that verifies incoming request if it is valid websocket
/// upgrade request. In case of error returns `HandshakeError`
pub struct VerifyWebSockets<T> {
_t: PhantomData<T>,
}
impl<T> Default for VerifyWebSockets<T> {
fn default() -> Self {
VerifyWebSockets { _t: PhantomData }
}
}
impl<T, C> NewService<C> for VerifyWebSockets<T> {
type Request = (Request, Framed<T, Codec>);
type Response = (Request, Framed<T, Codec>);
type Error = (HandshakeError, Framed<T, Codec>);
type InitError = ();
type Service = VerifyWebSockets<T>;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &C) -> Self::Future {
ok(VerifyWebSockets { _t: PhantomData })
}
}
impl<T> Service for VerifyWebSockets<T> {
type Request = (Request, Framed<T, Codec>);
type Response = (Request, Framed<T, Codec>);
type Error = (HandshakeError, Framed<T, Codec>);
type Future = FutureResult<Self::Response, Self::Error>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, (req, framed): (Request, Framed<T, Codec>)) -> Self::Future {
match verify_handshake(req.head()) {
Err(e) => Err((e, framed)).into_future(),
Ok(_) => Ok((req, framed)).into_future(),
}
}
}
/// Send http/1 error 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, C> NewService<C> for SendError<T, R, E>
where
T: AsyncRead + AsyncWrite + 'static,
R: 'static,
E: ResponseError + 'static,
{
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, _: &C) -> Self::Future {
ok(SendError(PhantomData))
}
}
impl<T, R, E> Service for SendError<T, R, E>
where
T: AsyncRead + AsyncWrite + 'static,
R: 'static,
E: ResponseError + 'static,
{
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().drop_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())),
}
}
}

29
actix-framed/src/state.rs Normal file
View File

@@ -0,0 +1,29 @@
use std::ops::Deref;
use std::sync::Arc;
/// Application state
pub struct State<S>(Arc<S>);
impl<S> State<S> {
pub fn new(state: S) -> State<S> {
State(Arc::new(state))
}
pub fn get_ref(&self) -> &S {
self.0.as_ref()
}
}
impl<S> Deref for State<S> {
type Target = S;
fn deref(&self) -> &S {
self.0.as_ref()
}
}
impl<S> Clone for State<S> {
fn clone(&self) -> State<S> {
State(self.0.clone())
}
}

153
actix-framed/src/test.rs Normal file
View File

@@ -0,0 +1,153 @@
//! Various helpers for Actix applications to use during testing.
use actix_codec::Framed;
use actix_http::h1::Codec;
use actix_http::http::header::{Header, HeaderName, IntoHeaderValue};
use actix_http::http::{HttpTryFrom, Method, Uri, Version};
use actix_http::test::{TestBuffer, TestRequest as HttpTestRequest};
use actix_router::{Path, Url};
use actix_rt::Runtime;
use futures::IntoFuture;
use crate::{FramedRequest, State};
/// Test `Request` builder.
pub struct TestRequest<S = ()> {
req: HttpTestRequest,
path: Path<Url>,
state: State<S>,
}
impl Default for TestRequest<()> {
fn default() -> TestRequest {
TestRequest {
req: HttpTestRequest::default(),
path: Path::new(Url::new(Uri::default())),
state: State::new(()),
}
}
}
impl TestRequest<()> {
/// Create TestRequest and set request uri
pub fn with_uri(path: &str) -> Self {
Self::get().uri(path)
}
/// Create TestRequest and set header
pub fn with_hdr<H: Header>(hdr: H) -> Self {
Self::default().set(hdr)
}
/// Create TestRequest and set header
pub fn with_header<K, V>(key: K, value: V) -> Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
Self::default().header(key, value)
}
/// Create TestRequest and set method to `Method::GET`
pub fn get() -> Self {
Self::default().method(Method::GET)
}
/// Create TestRequest and set method to `Method::POST`
pub fn post() -> Self {
Self::default().method(Method::POST)
}
}
impl<S> TestRequest<S> {
/// Create TestRequest and set request uri
pub fn with_state(state: S) -> TestRequest<S> {
let req = TestRequest::get();
TestRequest {
state: State::new(state),
req: req.req,
path: req.path,
}
}
/// Set HTTP version of this request
pub fn version(mut self, ver: Version) -> Self {
self.req.version(ver);
self
}
/// Set HTTP method of this request
pub fn method(mut self, meth: Method) -> Self {
self.req.method(meth);
self
}
/// Set HTTP Uri of this request
pub fn uri(mut self, path: &str) -> Self {
self.req.uri(path);
self
}
/// Set a header
pub fn set<H: Header>(mut self, hdr: H) -> Self {
self.req.set(hdr);
self
}
/// Set a header
pub fn header<K, V>(mut self, key: K, value: V) -> Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
self.req.header(key, value);
self
}
/// Set request path pattern parameter
pub fn param(mut self, name: &'static str, value: &'static str) -> Self {
self.path.add_static(name, value);
self
}
/// Complete request creation and generate `Request` instance
pub fn finish(mut self) -> FramedRequest<TestBuffer, S> {
let req = self.req.finish();
self.path.get_mut().update(req.uri());
let framed = Framed::new(TestBuffer::empty(), Codec::default());
FramedRequest::new(req, framed, self.path, self.state)
}
/// This method generates `FramedRequest` instance and executes async handler
pub fn run<F, R, I, E>(self, f: F) -> Result<I, E>
where
F: FnOnce(FramedRequest<TestBuffer, S>) -> R,
R: IntoFuture<Item = I, Error = E>,
{
let mut rt = Runtime::new().unwrap();
rt.block_on(f(self.finish()).into_future())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let req = TestRequest::with_uri("/index.html")
.header("x-test", "test")
.param("test", "123")
.finish();
assert_eq!(*req.state(), ());
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(req.method(), Method::GET);
assert_eq!(req.path(), "/index.html");
assert_eq!(req.query_string(), "");
assert_eq!(
req.headers().get("x-test").unwrap().to_str().unwrap(),
"test"
);
assert_eq!(&req.match_info()["test"], "123");
}
}

View File

@@ -0,0 +1,136 @@
use actix_codec::{AsyncRead, AsyncWrite};
use actix_http::{body, ws, Error, HttpService, Response};
use actix_http_test::TestServer;
use actix_service::{IntoNewService, NewService};
use actix_utils::framed::FramedTransport;
use bytes::{Bytes, BytesMut};
use futures::future::{self, ok};
use futures::{Future, Sink, Stream};
use actix_framed::{FramedApp, FramedRequest, FramedRoute, SendError, VerifyWebSockets};
fn ws_service<T: AsyncRead + AsyncWrite>(
req: FramedRequest<T>,
) -> impl Future<Item = (), Error = Error> {
let (req, framed, _) = req.into_parts();
let res = ws::handshake(req.head()).unwrap().message_body(());
framed
.send((res, body::BodySize::None).into())
.map_err(|_| panic!())
.and_then(|framed| {
FramedTransport::new(framed.into_framed(ws::Codec::new()), service)
.map_err(|_| panic!())
})
}
fn service(msg: ws::Frame) -> impl Future<Item = ws::Message, Error = Error> {
let msg = match msg {
ws::Frame::Ping(msg) => ws::Message::Pong(msg),
ws::Frame::Text(text) => {
ws::Message::Text(String::from_utf8_lossy(&text.unwrap()).to_string())
}
ws::Frame::Binary(bin) => ws::Message::Binary(bin.unwrap().freeze()),
ws::Frame::Close(reason) => ws::Message::Close(reason),
_ => panic!(),
};
ok(msg)
}
#[test]
fn test_simple() {
let mut srv = TestServer::new(|| {
HttpService::build()
.upgrade(
FramedApp::new().service(FramedRoute::get("/index.html").to(ws_service)),
)
.finish(|_| future::ok::<_, Error>(Response::NotFound()))
});
assert!(srv.ws_at("/test").is_err());
// client service
let framed = srv.ws_at("/index.html").unwrap();
let framed = srv
.block_on(framed.send(ws::Message::Text("text".to_string())))
.unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text")))));
let framed = srv
.block_on(framed.send(ws::Message::Binary("text".into())))
.unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(
item,
Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into())))
);
let framed = srv
.block_on(framed.send(ws::Message::Ping("text".into())))
.unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into())));
let framed = srv
.block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))))
.unwrap();
let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(
item,
Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into())))
);
}
#[test]
fn test_service() {
let mut srv = TestServer::new(|| {
actix_http::h1::OneRequest::new().map_err(|_| ()).and_then(
VerifyWebSockets::default()
.then(SendError::default())
.map_err(|_| ())
.and_then(
FramedApp::new()
.service(FramedRoute::get("/index.html").to(ws_service))
.into_new_service()
.map_err(|_| ()),
),
)
});
assert!(srv.ws_at("/test").is_err());
// client service
let framed = srv.ws_at("/index.html").unwrap();
let framed = srv
.block_on(framed.send(ws::Message::Text("text".to_string())))
.unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text")))));
let framed = srv
.block_on(framed.send(ws::Message::Binary("text".into())))
.unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(
item,
Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into())))
);
let framed = srv
.block_on(framed.send(ws::Message::Ping("text".into())))
.unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into())));
let framed = srv
.block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))))
.unwrap();
let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(
item,
Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into())))
);
}

View File

@@ -1,9 +1,39 @@
# Changes # Changes
## [0.1.0] - 2019-04-16
### Added
* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr`
### Changed
* `actix_http::encoding` always available
* use trust-dns-resolver 0.11.0
## [0.1.0-alpha.5] - 2019-04-12
### Added
* Allow to use custom service for upgrade requests
* Added `h1::SendResponse` future.
### Changed
* MessageBody::length() renamed to MessageBody::size() for consistency
* ws handshake verification functions take RequestHead instead of Request
## [0.1.0-alpha.4] - 2019-04-08 ## [0.1.0-alpha.4] - 2019-04-08
### Added ### Added
* Allow to use custom `Expect` handler
* Add minimal `std::error::Error` impl for `Error` * Add minimal `std::error::Error` impl for `Error`
### Changed ### Changed

View File

@@ -1,12 +1,12 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "0.1.0-alpha.4" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http primitives" description = "Actix http primitives"
readme = "README.md" readme = "README.md"
keywords = ["actix", "http", "framework", "async", "futures"] keywords = ["actix", "http", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-http.git" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-http/" documentation = "https://docs.rs/actix-http/"
categories = ["network-programming", "asynchronous", categories = ["network-programming", "asynchronous",
"web-programming::http-server", "web-programming::http-server",
@@ -18,10 +18,6 @@ workspace = ".."
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["ssl", "fail", "brotli", "flate2-zlib", "secure-cookies"] features = ["ssl", "fail", "brotli", "flate2-zlib", "secure-cookies"]
[badges]
travis-ci = { repository = "actix/actix-web", branch = "master" }
codecov = { repository = "actix/actix-web", branch = "master", service = "github" }
[lib] [lib]
name = "actix_http" name = "actix_http"
path = "src/lib.rs" path = "src/lib.rs"
@@ -50,9 +46,9 @@ secure-cookies = ["ring"]
[dependencies] [dependencies]
actix-service = "0.3.6" actix-service = "0.3.6"
actix-codec = "0.1.2" actix-codec = "0.1.2"
actix-connect = "0.1.2" actix-connect = "0.1.4"
actix-utils = "0.3.5" actix-utils = "0.3.5"
actix-server-config = "0.1.0" actix-server-config = "0.1.1"
actix-threadpool = "0.1.0" actix-threadpool = "0.1.0"
base64 = "0.10" base64 = "0.10"
@@ -64,9 +60,9 @@ derive_more = "0.14"
either = "1.5.2" either = "1.5.2"
encoding = "0.2" encoding = "0.2"
futures = "0.1" futures = "0.1"
hashbrown = "0.1.8" hashbrown = "0.2.2"
h2 = "0.1.16" h2 = "0.1.16"
http = "0.1.16" http = "0.1.17"
httparse = "1.3" httparse = "1.3"
indexmap = "1.0" indexmap = "1.0"
lazy_static = "1.0" lazy_static = "1.0"
@@ -80,19 +76,19 @@ serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
sha1 = "0.6" sha1 = "0.6"
slab = "0.4" slab = "0.4"
serde_urlencoded = "0.5.3" serde_urlencoded = "0.5.5"
time = "0.1" time = "0.1"
tokio-tcp = "0.1.3" tokio-tcp = "0.1.3"
tokio-timer = "0.2" tokio-timer = "0.2.8"
tokio-current-thread = "0.1" tokio-current-thread = "0.1"
trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } trust-dns-resolver = { version="0.11.0", default-features = false }
# for secure cookie # for secure cookie
ring = { version = "0.14.6", optional = true } ring = { version = "0.14.6", optional = true }
# compression # compression
brotli2 = { version="^0.3.2", optional = true } brotli2 = { version="0.3.2", optional = true }
flate2 = { version="^1.0.2", optional = true, default-features = false } flate2 = { version="1.0.7", optional = true, default-features = false }
# optional deps # optional deps
failure = { version = "0.1.5", optional = true } failure = { version = "0.1.5", optional = true }
@@ -100,9 +96,9 @@ openssl = { version="0.10", optional = true }
[dev-dependencies] [dev-dependencies]
actix-rt = "0.2.2" actix-rt = "0.2.2"
actix-server = { version = "0.4.1", features=["ssl"] } actix-server = { version = "0.4.3", features=["ssl"] }
actix-connect = { version = "0.1.0", features=["ssl"] } actix-connect = { version = "0.1.4", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] }
env_logger = "0.6" env_logger = "0.6"
serde_derive = "1.0" serde_derive = "1.0"
openssl = { version="0.10" } openssl = { version="0.10" }

View File

@@ -1,4 +1,4 @@
# Actix http [![Build Status](https://travis-ci.org/actix/actix-http.svg?branch=master)](https://travis-ci.org/actix/actix-http) [![codecov](https://codecov.io/gh/actix/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![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) # Actix http [![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-http)](https://crates.io/crates/actix-http) [![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)
Actix http Actix http
@@ -8,7 +8,7 @@ Actix http
* [API Documentation](https://docs.rs/actix-http/) * [API Documentation](https://docs.rs/actix-http/)
* [Chat on gitter](https://gitter.im/actix/actix) * [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-http](https://crates.io/crates/actix-http) * Cargo package: [actix-http](https://crates.io/crates/actix-http)
* Minimum supported Rust version: 1.26 or later * Minimum supported Rust version: 1.31 or later
## Example ## Example

View File

@@ -30,13 +30,13 @@ impl BodySize {
/// Type that provides this trait can be streamed to a peer. /// Type that provides this trait can be streamed to a peer.
pub trait MessageBody { pub trait MessageBody {
fn length(&self) -> BodySize; fn size(&self) -> BodySize;
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error>; fn poll_next(&mut self) -> Poll<Option<Bytes>, Error>;
} }
impl MessageBody for () { impl MessageBody for () {
fn length(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Empty BodySize::Empty
} }
@@ -46,8 +46,8 @@ impl MessageBody for () {
} }
impl<T: MessageBody> MessageBody for Box<T> { impl<T: MessageBody> MessageBody for Box<T> {
fn length(&self) -> BodySize { fn size(&self) -> BodySize {
self.as_ref().length() self.as_ref().size()
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
@@ -86,10 +86,10 @@ impl<B: MessageBody> ResponseBody<B> {
} }
impl<B: MessageBody> MessageBody for ResponseBody<B> { impl<B: MessageBody> MessageBody for ResponseBody<B> {
fn length(&self) -> BodySize { fn size(&self) -> BodySize {
match self { match self {
ResponseBody::Body(ref body) => body.length(), ResponseBody::Body(ref body) => body.size(),
ResponseBody::Other(ref body) => body.length(), ResponseBody::Other(ref body) => body.size(),
} }
} }
@@ -135,12 +135,12 @@ impl Body {
} }
impl MessageBody for Body { impl MessageBody for Body {
fn length(&self) -> BodySize { fn size(&self) -> BodySize {
match self { match self {
Body::None => BodySize::None, Body::None => BodySize::None,
Body::Empty => BodySize::Empty, Body::Empty => BodySize::Empty,
Body::Bytes(ref bin) => BodySize::Sized(bin.len()), Body::Bytes(ref bin) => BodySize::Sized(bin.len()),
Body::Message(ref body) => body.length(), Body::Message(ref body) => body.size(),
} }
} }
@@ -185,7 +185,7 @@ impl fmt::Debug for Body {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
Body::None => write!(f, "Body::None"), Body::None => write!(f, "Body::None"),
Body::Empty => write!(f, "Body::Zero"), Body::Empty => write!(f, "Body::Empty"),
Body::Bytes(ref b) => write!(f, "Body::Bytes({:?})", b), Body::Bytes(ref b) => write!(f, "Body::Bytes({:?})", b),
Body::Message(_) => write!(f, "Body::Message(_)"), Body::Message(_) => write!(f, "Body::Message(_)"),
} }
@@ -235,7 +235,7 @@ impl From<BytesMut> for Body {
} }
impl MessageBody for Bytes { impl MessageBody for Bytes {
fn length(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
@@ -249,7 +249,7 @@ impl MessageBody for Bytes {
} }
impl MessageBody for BytesMut { impl MessageBody for BytesMut {
fn length(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
@@ -265,7 +265,7 @@ impl MessageBody for BytesMut {
} }
impl MessageBody for &'static str { impl MessageBody for &'static str {
fn length(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
@@ -281,7 +281,7 @@ impl MessageBody for &'static str {
} }
impl MessageBody for &'static [u8] { impl MessageBody for &'static [u8] {
fn length(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
@@ -297,7 +297,7 @@ impl MessageBody for &'static [u8] {
} }
impl MessageBody for Vec<u8> { impl MessageBody for Vec<u8> {
fn length(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
@@ -314,7 +314,7 @@ impl MessageBody for Vec<u8> {
} }
impl MessageBody for String { impl MessageBody for String {
fn length(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
@@ -354,7 +354,7 @@ where
S: Stream<Item = Bytes, Error = E>, S: Stream<Item = Bytes, Error = E>,
E: Into<Error>, E: Into<Error>,
{ {
fn length(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Stream BodySize::Stream
} }
@@ -383,7 +383,7 @@ impl<S> MessageBody for SizedStream<S>
where where
S: Stream<Item = Bytes, Error = Error>, S: Stream<Item = Bytes, Error = Error>,
{ {
fn length(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Sized(self.size) BodySize::Sized(self.size)
} }
@@ -416,47 +416,117 @@ mod tests {
#[test] #[test]
fn test_static_str() { fn test_static_str() {
assert_eq!(Body::from("").length(), BodySize::Sized(0)); assert_eq!(Body::from("").size(), BodySize::Sized(0));
assert_eq!(Body::from("test").length(), BodySize::Sized(4)); assert_eq!(Body::from("test").size(), BodySize::Sized(4));
assert_eq!(Body::from("test").get_ref(), b"test"); assert_eq!(Body::from("test").get_ref(), b"test");
assert_eq!("test".size(), BodySize::Sized(4));
assert_eq!(
"test".poll_next().unwrap(),
Async::Ready(Some(Bytes::from("test")))
);
} }
#[test] #[test]
fn test_static_bytes() { fn test_static_bytes() {
assert_eq!(Body::from(b"test".as_ref()).length(), BodySize::Sized(4)); assert_eq!(Body::from(b"test".as_ref()).size(), BodySize::Sized(4));
assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test"); assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test");
assert_eq!( assert_eq!(
Body::from_slice(b"test".as_ref()).length(), Body::from_slice(b"test".as_ref()).size(),
BodySize::Sized(4) BodySize::Sized(4)
); );
assert_eq!(Body::from_slice(b"test".as_ref()).get_ref(), b"test"); assert_eq!(Body::from_slice(b"test".as_ref()).get_ref(), b"test");
assert_eq!((&b"test"[..]).size(), BodySize::Sized(4));
assert_eq!(
(&b"test"[..]).poll_next().unwrap(),
Async::Ready(Some(Bytes::from("test")))
);
} }
#[test] #[test]
fn test_vec() { fn test_vec() {
assert_eq!(Body::from(Vec::from("test")).length(), BodySize::Sized(4)); assert_eq!(Body::from(Vec::from("test")).size(), BodySize::Sized(4));
assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test"); assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test");
assert_eq!(Vec::from("test").size(), BodySize::Sized(4));
assert_eq!(
Vec::from("test").poll_next().unwrap(),
Async::Ready(Some(Bytes::from("test")))
);
} }
#[test] #[test]
fn test_bytes() { fn test_bytes() {
assert_eq!(Body::from(Bytes::from("test")).length(), BodySize::Sized(4)); let mut b = Bytes::from("test");
assert_eq!(Body::from(Bytes::from("test")).get_ref(), b"test"); assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4));
}
#[test]
fn test_string() {
let b = "test".to_owned();
assert_eq!(Body::from(b.clone()).length(), BodySize::Sized(4));
assert_eq!(Body::from(b.clone()).get_ref(), b"test"); assert_eq!(Body::from(b.clone()).get_ref(), b"test");
assert_eq!(Body::from(&b).length(), BodySize::Sized(4));
assert_eq!(Body::from(&b).get_ref(), b"test"); assert_eq!(b.size(), BodySize::Sized(4));
assert_eq!(
b.poll_next().unwrap(),
Async::Ready(Some(Bytes::from("test")))
);
} }
#[test] #[test]
fn test_bytes_mut() { fn test_bytes_mut() {
let b = BytesMut::from("test"); let mut b = BytesMut::from("test");
assert_eq!(Body::from(b.clone()).length(), BodySize::Sized(4)); assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4));
assert_eq!(Body::from(b).get_ref(), b"test"); assert_eq!(Body::from(b.clone()).get_ref(), b"test");
assert_eq!(b.size(), BodySize::Sized(4));
assert_eq!(
b.poll_next().unwrap(),
Async::Ready(Some(Bytes::from("test")))
);
}
#[test]
fn test_string() {
let mut b = "test".to_owned();
assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4));
assert_eq!(Body::from(b.clone()).get_ref(), b"test");
assert_eq!(Body::from(&b).size(), BodySize::Sized(4));
assert_eq!(Body::from(&b).get_ref(), b"test");
assert_eq!(b.size(), BodySize::Sized(4));
assert_eq!(
b.poll_next().unwrap(),
Async::Ready(Some(Bytes::from("test")))
);
}
#[test]
fn test_unit() {
assert_eq!(().size(), BodySize::Empty);
assert_eq!(().poll_next().unwrap(), Async::Ready(None));
}
#[test]
fn test_box() {
let mut val = Box::new(());
assert_eq!(val.size(), BodySize::Empty);
assert_eq!(val.poll_next().unwrap(), Async::Ready(None));
}
#[test]
fn test_body_eq() {
assert!(Body::None == Body::None);
assert!(Body::None != Body::Empty);
assert!(Body::Empty == Body::Empty);
assert!(Body::Empty != Body::None);
assert!(
Body::Bytes(Bytes::from_static(b"1"))
== Body::Bytes(Bytes::from_static(b"1"))
);
assert!(Body::Bytes(Bytes::from_static(b"1")) != Body::None);
}
#[test]
fn test_body_debug() {
assert!(format!("{:?}", Body::None).contains("Body::None"));
assert!(format!("{:?}", Body::Empty).contains("Body::Empty"));
assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains("1"));
} }
} }

View File

@@ -1,13 +1,14 @@
use std::fmt; use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use actix_codec::Framed;
use actix_server_config::ServerConfig as SrvConfig; use actix_server_config::ServerConfig as SrvConfig;
use actix_service::{IntoNewService, NewService, Service}; use actix_service::{IntoNewService, NewService, Service};
use crate::body::MessageBody; use crate::body::MessageBody;
use crate::config::{KeepAlive, ServiceConfig}; use crate::config::{KeepAlive, ServiceConfig};
use crate::error::Error; use crate::error::Error;
use crate::h1::{ExpectHandler, H1Service}; use crate::h1::{Codec, ExpectHandler, H1Service, UpgradeHandler};
use crate::h2::H2Service; use crate::h2::H2Service;
use crate::request::Request; use crate::request::Request;
use crate::response::Response; use crate::response::Response;
@@ -17,15 +18,16 @@ use crate::service::HttpService;
/// ///
/// This type can be used to construct an instance of `http service` through a /// This type can be used to construct an instance of `http service` through a
/// builder-like pattern. /// builder-like pattern.
pub struct HttpServiceBuilder<T, S, X = ExpectHandler> { pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
keep_alive: KeepAlive, keep_alive: KeepAlive,
client_timeout: u64, client_timeout: u64,
client_disconnect: u64, client_disconnect: u64,
expect: X, expect: X,
upgrade: Option<U>,
_t: PhantomData<(T, S)>, _t: PhantomData<(T, S)>,
} }
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler> impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler<T>>
where where
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
@@ -38,12 +40,13 @@ where
client_timeout: 5000, client_timeout: 5000,
client_disconnect: 0, client_disconnect: 0,
expect: ExpectHandler, expect: ExpectHandler,
upgrade: None,
_t: PhantomData, _t: PhantomData,
} }
} }
} }
impl<T, S, X> HttpServiceBuilder<T, S, X> impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
where where
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
@@ -51,11 +54,14 @@ where
X: NewService<Request = Request, Response = Request>, X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: NewService<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
{ {
/// Set server keep-alive setting. /// Set server keep-alive setting.
/// ///
/// By default keep alive is set to a 5 seconds. /// By default keep alive is set to a 5 seconds.
pub fn keep_alive<U: Into<KeepAlive>>(mut self, val: U) -> Self { pub fn keep_alive<W: Into<KeepAlive>>(mut self, val: W) -> Self {
self.keep_alive = val.into(); self.keep_alive = val.into();
self self
} }
@@ -87,27 +93,51 @@ where
self self
} }
// #[cfg(feature = "ssl")] /// Provide service for `EXPECT: 100-Continue` support.
// /// Configure alpn protocols for SslAcceptorBuilder. ///
// pub fn configure_openssl( /// Service get called with request that contains `EXPECT` header.
// builder: &mut openssl::ssl::SslAcceptorBuilder, /// Service must return request in case of success, in that case
// ) -> io::Result<()> { /// request will be forwarded to main service.
// let protos: &[u8] = b"\x02h2"; pub fn expect<F, X1>(self, expect: F) -> HttpServiceBuilder<T, S, X1, U>
// builder.set_alpn_select_callback(|_, protos| { where
// const H2: &[u8] = b"\x02h2"; F: IntoNewService<X1>,
// if protos.windows(3).any(|window| window == H2) { X1: NewService<Request = Request, Response = Request>,
// Ok(b"h2") X1::Error: Into<Error>,
// } else { X1::InitError: fmt::Debug,
// Err(openssl::ssl::AlpnError::NOACK) {
// } HttpServiceBuilder {
// }); keep_alive: self.keep_alive,
// builder.set_alpn_protos(&protos)?; client_timeout: self.client_timeout,
client_disconnect: self.client_disconnect,
expect: expect.into_new_service(),
upgrade: self.upgrade,
_t: PhantomData,
}
}
// Ok(()) /// Provide service for custom `Connection: UPGRADE` support.
// } ///
/// If service is provided then normal requests handling get halted
/// and this service get called with original request and framed object.
pub fn upgrade<F, U1>(self, upgrade: F) -> HttpServiceBuilder<T, S, X, U1>
where
F: IntoNewService<U1>,
U1: NewService<Request = (Request, Framed<T, Codec>), Response = ()>,
U1::Error: fmt::Display,
U1::InitError: fmt::Debug,
{
HttpServiceBuilder {
keep_alive: self.keep_alive,
client_timeout: self.client_timeout,
client_disconnect: self.client_disconnect,
expect: self.expect,
upgrade: Some(upgrade.into_new_service()),
_t: PhantomData,
}
}
/// Finish service configuration and create *http service* for HTTP/1 protocol. /// 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, X> pub fn h1<F, P, B>(self, service: F) -> H1Service<T, P, S, B, X, U>
where where
B: MessageBody + 'static, B: MessageBody + 'static,
F: IntoNewService<S, SrvConfig>, F: IntoNewService<S, SrvConfig>,
@@ -120,7 +150,9 @@ where
self.client_timeout, self.client_timeout,
self.client_disconnect, self.client_disconnect,
); );
H1Service::with_config(cfg, service.into_new_service()).expect(self.expect) H1Service::with_config(cfg, service.into_new_service())
.expect(self.expect)
.upgrade(self.upgrade)
} }
/// Finish service configuration and create *http service* for HTTP/2 protocol. /// Finish service configuration and create *http service* for HTTP/2 protocol.
@@ -142,7 +174,7 @@ where
} }
/// Finish service configuration and create `HttpService` instance. /// Finish service configuration and create `HttpService` instance.
pub fn finish<F, P, B>(self, service: F) -> HttpService<T, P, S, B> pub fn finish<F, P, B>(self, service: F) -> HttpService<T, P, S, B, X, U>
where where
B: MessageBody + 'static, B: MessageBody + 'static,
F: IntoNewService<S, SrvConfig>, F: IntoNewService<S, SrvConfig>,
@@ -157,5 +189,7 @@ where
self.client_disconnect, self.client_disconnect,
); );
HttpService::with_config(cfg, service.into_new_service()) HttpService::with_config(cfg, service.into_new_service())
.expect(self.expect)
.upgrade(self.upgrade)
} }
} }

View File

@@ -12,7 +12,7 @@ use crate::message::{RequestHead, ResponseHead};
use crate::payload::Payload; use crate::payload::Payload;
use super::error::SendRequestError; use super::error::SendRequestError;
use super::pool::Acquired; use super::pool::{Acquired, Protocol};
use super::{h1proto, h2proto}; use super::{h1proto, h2proto};
pub(crate) enum ConnectionType<Io> { pub(crate) enum ConnectionType<Io> {
@@ -24,6 +24,8 @@ pub trait Connection {
type Io: AsyncRead + AsyncWrite; type Io: AsyncRead + AsyncWrite;
type Future: Future<Item = (ResponseHead, Payload), Error = SendRequestError>; type Future: Future<Item = (ResponseHead, Payload), Error = SendRequestError>;
fn protocol(&self) -> Protocol;
/// Send request and body /// Send request and body
fn send_request<B: MessageBody + 'static>( fn send_request<B: MessageBody + 'static>(
self, self,
@@ -94,6 +96,14 @@ where
type Io = T; type Io = T;
type Future = Box<Future<Item = (ResponseHead, Payload), Error = SendRequestError>>; type Future = Box<Future<Item = (ResponseHead, Payload), Error = SendRequestError>>;
fn protocol(&self) -> Protocol {
match self.io {
Some(ConnectionType::H1(_)) => Protocol::Http1,
Some(ConnectionType::H2(_)) => Protocol::Http2,
None => Protocol::Http1,
}
}
fn send_request<B: MessageBody + 'static>( fn send_request<B: MessageBody + 'static>(
mut self, mut self,
head: RequestHead, head: RequestHead,
@@ -161,6 +171,13 @@ where
type Io = EitherIo<A, B>; type Io = EitherIo<A, B>;
type Future = Box<Future<Item = (ResponseHead, Payload), Error = SendRequestError>>; type Future = Box<Future<Item = (ResponseHead, Payload), Error = SendRequestError>>;
fn protocol(&self) -> Protocol {
match self {
EitherConnection::A(con) => con.protocol(),
EitherConnection::B(con) => con.protocol(),
}
}
fn send_request<RB: MessageBody + 'static>( fn send_request<RB: MessageBody + 'static>(
self, self,
head: RequestHead, head: RequestHead,

View File

@@ -57,8 +57,7 @@ impl Connector<(), ()> {
let ssl = { let ssl = {
#[cfg(feature = "ssl")] #[cfg(feature = "ssl")]
{ {
use log::error; use openssl::ssl::SslMethod;
use openssl::ssl::{SslConnector, SslMethod};
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
let _ = ssl let _ = ssl

View File

@@ -1,12 +1,14 @@
use std::io::Write;
use std::{io, time}; use std::{io, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use bytes::Bytes; use bytes::{BufMut, Bytes, BytesMut};
use futures::future::{ok, Either}; use futures::future::{ok, Either};
use futures::{Async, Future, Poll, Sink, Stream}; use futures::{Async, Future, Poll, Sink, Stream};
use crate::error::PayloadError; use crate::error::PayloadError;
use crate::h1; use crate::h1;
use crate::http::header::{IntoHeaderValue, HOST};
use crate::message::{RequestHead, ResponseHead}; use crate::message::{RequestHead, ResponseHead};
use crate::payload::{Payload, PayloadStream}; use crate::payload::{Payload, PayloadStream};
@@ -17,7 +19,7 @@ use crate::body::{BodySize, MessageBody};
pub(crate) fn send_request<T, B>( pub(crate) fn send_request<T, B>(
io: T, io: T,
head: RequestHead, mut head: RequestHead,
body: B, body: B,
created: time::Instant, created: time::Instant,
pool: Option<Acquired<T>>, pool: Option<Acquired<T>>,
@@ -26,20 +28,41 @@ where
T: AsyncRead + AsyncWrite + 'static, T: AsyncRead + AsyncWrite + 'static,
B: MessageBody, B: MessageBody,
{ {
// set request host header
if !head.headers.contains_key(HOST) {
if let Some(host) = head.uri.host() {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match head.uri.port_u16() {
None | Some(80) | Some(443) => write!(wrt, "{}", host),
Some(port) => write!(wrt, "{}:{}", host, port),
};
match wrt.get_mut().take().freeze().try_into() {
Ok(value) => {
head.headers.insert(HOST, value);
}
Err(e) => {
log::error!("Can not set HOST header {}", e);
}
}
}
}
let io = H1Connection { let io = H1Connection {
created, created,
pool, pool,
io: Some(io), io: Some(io),
}; };
let len = body.length(); let len = body.size();
// create Framed and send reqest // create Framed and send reqest
Framed::new(io, h1::ClientCodec::default()) Framed::new(io, h1::ClientCodec::default())
.send((head, len).into()) .send((head, len).into())
.from_err() .from_err()
// send request body // send request body
.and_then(move |framed| match body.length() { .and_then(move |framed| match body.size() {
BodySize::None | BodySize::Empty | BodySize::Sized(0) => { BodySize::None | BodySize::Empty | BodySize::Sized(0) => {
Either::A(ok(framed)) Either::A(ok(framed))
} }

View File

@@ -27,9 +27,9 @@ where
T: AsyncRead + AsyncWrite + 'static, T: AsyncRead + AsyncWrite + 'static,
B: MessageBody, B: MessageBody,
{ {
trace!("Sending client request: {:?} {:?}", head, body.length()); trace!("Sending client request: {:?} {:?}", head, body.size());
let head_req = head.method == Method::HEAD; let head_req = head.method == Method::HEAD;
let length = body.length(); let length = body.size();
let eof = match length { let eof = match length {
BodySize::None | BodySize::Empty | BodySize::Sized(0) => true, BodySize::None | BodySize::Empty | BodySize::Sized(0) => true,
_ => false, _ => false,
@@ -107,7 +107,6 @@ where
let mut head = ResponseHead::new(parts.status); let mut head = ResponseHead::new(parts.status);
head.version = parts.version; head.version = parts.version;
head.headers = parts.headers.into(); head.headers = parts.headers.into();
Ok((head, payload)) Ok((head, payload))
}) })
.from_err() .from_err()

View File

@@ -9,3 +9,4 @@ mod pool;
pub use self::connection::Connection; pub use self::connection::Connection;
pub use self::connector::Connector; pub use self::connector::Connector;
pub use self::error::{ConnectError, InvalidUrl, SendRequestError}; pub use self::error::{ConnectError, InvalidUrl, SendRequestError};
pub use self::pool::Protocol;

View File

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

View File

@@ -79,12 +79,12 @@ enum EncoderBody<B> {
} }
impl<B: MessageBody> MessageBody for Encoder<B> { impl<B: MessageBody> MessageBody for Encoder<B> {
fn length(&self) -> BodySize { fn size(&self) -> BodySize {
if self.encoder.is_none() { if self.encoder.is_none() {
match self.body { match self.body {
EncoderBody::Bytes(ref b) => b.length(), EncoderBody::Bytes(ref b) => b.size(),
EncoderBody::Stream(ref b) => b.length(), EncoderBody::Stream(ref b) => b.size(),
EncoderBody::BoxedStream(ref b) => b.length(), EncoderBody::BoxedStream(ref b) => b.size(),
} }
} else { } else {
BodySize::Stream BodySize::Stream

View File

@@ -1,11 +1,13 @@
//! Error and Result module //! Error and Result module
use std::cell::RefCell; use std::cell::RefCell;
use std::io::Write;
use std::str::Utf8Error; use std::str::Utf8Error;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use std::{fmt, io, result}; use std::{fmt, io, result};
pub use actix_threadpool::BlockingError; pub use actix_threadpool::BlockingError;
use actix_utils::timeout::TimeoutError; use actix_utils::timeout::TimeoutError;
use bytes::BytesMut;
use derive_more::{Display, From}; use derive_more::{Display, From};
use futures::Canceled; use futures::Canceled;
use http::uri::InvalidUri; use http::uri::InvalidUri;
@@ -17,7 +19,9 @@ use serde_urlencoded::ser::Error as FormError;
use tokio_timer::Error as TimerError; use tokio_timer::Error as TimerError;
// re-export for convinience // re-export for convinience
use crate::body::Body;
pub use crate::cookie::ParseError as CookieParseError; pub use crate::cookie::ParseError as CookieParseError;
use crate::helpers::Writer;
use crate::response::Response; use crate::response::Response;
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
@@ -57,6 +61,18 @@ pub trait ResponseError: fmt::Debug + fmt::Display {
fn error_response(&self) -> Response { fn error_response(&self) -> Response {
Response::new(StatusCode::INTERNAL_SERVER_ERROR) Response::new(StatusCode::INTERNAL_SERVER_ERROR)
} }
/// Constructs an error response
fn render_response(&self) -> Response {
let mut resp = self.error_response();
let mut buf = BytesMut::new();
let _ = write!(Writer(&mut buf), "{}", self);
resp.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
);
resp.set_body(Body::from(buf))
}
} }
impl fmt::Display for Error { impl fmt::Display for Error {
@@ -137,6 +153,10 @@ impl ResponseError for TimerError {}
/// `InternalServerError` for `SslError` /// `InternalServerError` for `SslError`
impl ResponseError for openssl::ssl::Error {} impl ResponseError for openssl::ssl::Error {}
#[cfg(feature = "ssl")]
/// `InternalServerError` for `SslError`
impl ResponseError for openssl::ssl::HandshakeError<tokio_tcp::TcpStream> {}
/// Return `BAD_REQUEST` for `de::value::Error` /// Return `BAD_REQUEST` for `de::value::Error`
impl ResponseError for DeError { impl ResponseError for DeError {
fn error_response(&self) -> Response { fn error_response(&self) -> Response {
@@ -350,6 +370,9 @@ pub enum DispatchError {
/// Service error /// Service error
Service(Error), Service(Error),
/// Upgrade service error
Upgrade,
/// An `io::Error` that occurred while trying to read or write to a network /// An `io::Error` that occurred while trying to read or write to a network
/// stream. /// stream.
#[display(fmt = "IO error: {}", _0)] #[display(fmt = "IO error: {}", _0)]
@@ -474,7 +497,16 @@ where
{ {
fn error_response(&self) -> Response { fn error_response(&self) -> Response {
match self.status { match self.status {
InternalErrorType::Status(st) => Response::new(st), InternalErrorType::Status(st) => {
let mut res = Response::new(st);
let mut buf = BytesMut::new();
let _ = write!(Writer(&mut buf), "{}", self);
res.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
);
res.set_body(Body::from(buf))
}
InternalErrorType::Response(ref resp) => { InternalErrorType::Response(ref resp) => {
if let Some(resp) = resp.borrow_mut().take() { if let Some(resp) = resp.borrow_mut().take() {
resp resp
@@ -484,6 +516,11 @@ where
} }
} }
} }
/// Constructs an error response
fn render_response(&self) -> Response {
self.error_response()
}
} }
/// Convert Response to a Error /// Convert Response to a Error

View File

@@ -1,6 +1,6 @@
#![allow(unused_imports, unused_variables, dead_code)] #![allow(unused_imports, unused_variables, dead_code)]
use std::fmt; use std::io::Write;
use std::io::{self, Write}; use std::{fmt, io, net};
use actix_codec::{Decoder, Encoder}; use actix_codec::{Decoder, Encoder};
use bitflags::bitflags; use bitflags::bitflags;
@@ -40,7 +40,6 @@ pub struct Codec {
// encoder part // encoder part
flags: Flags, flags: Flags,
encoder: encoder::MessageEncoder<Response<()>>, encoder: encoder::MessageEncoder<Response<()>>,
// headers_size: u32,
} }
impl Default for Codec { impl Default for Codec {
@@ -67,13 +66,11 @@ impl Codec {
}; };
Codec { Codec {
config, config,
flags,
decoder: decoder::MessageDecoder::default(), decoder: decoder::MessageDecoder::default(),
payload: None, payload: None,
version: Version::HTTP_11, version: Version::HTTP_11,
ctype: ConnectionType::Close, ctype: ConnectionType::Close,
flags,
// headers_size: 0,
encoder: encoder::MessageEncoder::default(), encoder: encoder::MessageEncoder::default(),
} }
} }

View File

@@ -1,8 +1,9 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use std::time::Instant; use std::time::Instant;
use std::{fmt, io}; use std::{fmt, io, net};
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder}; use actix_codec::{Decoder, Encoder, Framed, FramedParts};
use actix_server_config::IoStream;
use actix_service::Service; use actix_service::Service;
use actix_utils::cloneable::CloneableService; use actix_utils::cloneable::CloneableService;
use bitflags::bitflags; use bitflags::bitflags;
@@ -34,33 +35,54 @@ bitflags! {
const SHUTDOWN = 0b0000_1000; const SHUTDOWN = 0b0000_1000;
const READ_DISCONNECT = 0b0001_0000; const READ_DISCONNECT = 0b0001_0000;
const WRITE_DISCONNECT = 0b0010_0000; const WRITE_DISCONNECT = 0b0010_0000;
const DROPPING = 0b0100_0000; const UPGRADE = 0b0100_0000;
} }
} }
/// Dispatcher for HTTP/1.1 protocol /// Dispatcher for HTTP/1.1 protocol
pub struct Dispatcher<T, S, B, X> pub struct Dispatcher<T, S, B, X, U>
where where
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
B: MessageBody, B: MessageBody,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{ {
inner: Option<InnerDispatcher<T, S, B, X>>, inner: DispatcherState<T, S, B, X, U>,
} }
struct InnerDispatcher<T, S, B, X> enum DispatcherState<T, S, B, X, U>
where where
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
B: MessageBody, B: MessageBody,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
Normal(InnerDispatcher<T, S, B, X, U>),
Upgrade(U::Future),
None,
}
struct InnerDispatcher<T, S, B, X, U>
where
S: Service<Request = Request>,
S::Error: Into<Error>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{ {
service: CloneableService<S>, service: CloneableService<S>,
expect: CloneableService<X>, expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>,
flags: Flags, flags: Flags,
peer_addr: Option<net::SocketAddr>,
error: Option<DispatchError>, error: Option<DispatchError>,
state: State<S, B, X>, state: State<S, B, X>,
@@ -78,6 +100,7 @@ where
enum DispatcherMessage { enum DispatcherMessage {
Item(Request), Item(Request),
Upgrade(Request),
Error(Response<()>), Error(Response<()>),
} }
@@ -116,31 +139,39 @@ where
} }
} }
impl<S, B, X> fmt::Debug for State<S, B, X> enum PollResponse {
where Upgrade(Request),
S: Service<Request = Request>, DoNothing,
X: Service<Request = Request, Response = Request>, DrainWriteBuf,
B: MessageBody, }
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { impl PartialEq for PollResponse {
fn eq(&self, other: &PollResponse) -> bool {
match self { match self {
State::None => write!(f, "State::None"), PollResponse::DrainWriteBuf => match other {
State::ExpectCall(_) => write!(f, "State::ExceptCall"), PollResponse::DrainWriteBuf => true,
State::ServiceCall(_) => write!(f, "State::ServiceCall"), _ => false,
State::SendPayload(_) => write!(f, "State::SendPayload"), },
PollResponse::DoNothing => match other {
PollResponse::DoNothing => true,
_ => false,
},
_ => false,
} }
} }
} }
impl<T, S, B, X> Dispatcher<T, S, B, X> impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
B: MessageBody, B: MessageBody,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{ {
/// Create http/1 dispatcher. /// Create http/1 dispatcher.
pub fn new( pub fn new(
@@ -148,6 +179,7 @@ where
config: ServiceConfig, config: ServiceConfig,
service: CloneableService<S>, service: CloneableService<S>,
expect: CloneableService<X>, expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>,
) -> Self { ) -> Self {
Dispatcher::with_timeout( Dispatcher::with_timeout(
stream, stream,
@@ -157,6 +189,7 @@ where
None, None,
service, service,
expect, expect,
upgrade,
) )
} }
@@ -169,6 +202,7 @@ where
timeout: Option<Delay>, timeout: Option<Delay>,
service: CloneableService<S>, service: CloneableService<S>,
expect: CloneableService<X>, expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>,
) -> Self { ) -> Self {
let keepalive = config.keep_alive_enabled(); let keepalive = config.keep_alive_enabled();
let flags = if keepalive { let flags = if keepalive {
@@ -187,17 +221,19 @@ where
}; };
Dispatcher { Dispatcher {
inner: Some(InnerDispatcher { inner: DispatcherState::Normal(InnerDispatcher {
io,
codec,
read_buf,
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
payload: None, payload: None,
state: State::None, state: State::None,
error: None, error: None,
peer_addr: io.peer_addr(),
messages: VecDeque::new(), messages: VecDeque::new(),
io,
codec,
read_buf,
service, service,
expect, expect,
upgrade,
flags, flags,
ka_expire, ka_expire,
ka_timer, ka_timer,
@@ -206,18 +242,23 @@ where
} }
} }
impl<T, S, B, X> InnerDispatcher<T, S, B, X> impl<T, S, B, X, U> InnerDispatcher<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
B: MessageBody, B: MessageBody,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{ {
fn can_read(&self) -> bool { fn can_read(&self) -> bool {
if self.flags.contains(Flags::READ_DISCONNECT) { if self
.flags
.intersects(Flags::READ_DISCONNECT | Flags::UPGRADE)
{
false false
} else if let Some(ref info) = self.payload { } else if let Some(ref info) = self.payload {
info.need_read() == PayloadStatus::Read info.need_read() == PayloadStatus::Read
@@ -282,7 +323,7 @@ where
body: ResponseBody<B>, body: ResponseBody<B>,
) -> Result<State<S, B, X>, DispatchError> { ) -> Result<State<S, B, X>, DispatchError> {
self.codec self.codec
.encode(Message::Item((message, body.length())), &mut self.write_buf) .encode(Message::Item((message, body.size())), &mut self.write_buf)
.map_err(|err| { .map_err(|err| {
if let Some(mut payload) = self.payload.take() { if let Some(mut payload) = self.payload.take() {
payload.set_error(PayloadError::Incomplete(None)); payload.set_error(PayloadError::Incomplete(None));
@@ -291,7 +332,7 @@ where
})?; })?;
self.flags.set(Flags::KEEPALIVE, self.codec.keepalive()); self.flags.set(Flags::KEEPALIVE, self.codec.keepalive());
match body.length() { match body.size() {
BodySize::None | BodySize::Empty => Ok(State::None), BodySize::None | BodySize::Empty => Ok(State::None),
_ => Ok(State::SendPayload(body)), _ => Ok(State::SendPayload(body)),
} }
@@ -302,7 +343,7 @@ where
.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
} }
fn poll_response(&mut self) -> Result<bool, DispatchError> { fn poll_response(&mut self) -> Result<PollResponse, DispatchError> {
loop { loop {
let state = match self.state { let state = match self.state {
State::None => match self.messages.pop_front() { State::None => match self.messages.pop_front() {
@@ -312,6 +353,9 @@ where
Some(DispatcherMessage::Error(res)) => { Some(DispatcherMessage::Error(res)) => {
Some(self.send_response(res, ResponseBody::Other(Body::Empty))?) Some(self.send_response(res, ResponseBody::Other(Body::Empty))?)
} }
Some(DispatcherMessage::Upgrade(req)) => {
return Ok(PollResponse::Upgrade(req));
}
None => None, None => None,
}, },
State::ExpectCall(ref mut fut) => match fut.poll() { State::ExpectCall(ref mut fut) => match fut.poll() {
@@ -361,10 +405,10 @@ where
)?; )?;
self.state = State::None; self.state = State::None;
} }
Async::NotReady => return Ok(false), Async::NotReady => return Ok(PollResponse::DoNothing),
} }
} else { } else {
return Ok(true); return Ok(PollResponse::DrainWriteBuf);
} }
break; break;
} }
@@ -392,7 +436,7 @@ where
break; break;
} }
Ok(false) Ok(PollResponse::DoNothing)
} }
fn handle_request(&mut self, req: Request) -> Result<State<S, B, X>, DispatchError> { fn handle_request(&mut self, req: Request) -> Result<State<S, B, X>, DispatchError> {
@@ -448,16 +492,20 @@ where
match msg { match msg {
Message::Item(mut req) => { Message::Item(mut req) => {
match self.codec.message_type() { let pl = self.codec.message_type();
MessageType::Payload | MessageType::Stream => { req.head_mut().peer_addr = self.peer_addr;
if pl == MessageType::Stream && self.upgrade.is_some() {
self.messages.push_back(DispatcherMessage::Upgrade(req));
break;
}
if pl == MessageType::Payload || pl == MessageType::Stream {
let (ps, pl) = Payload::create(false); let (ps, pl) = Payload::create(false);
let (req1, _) = let (req1, _) =
req.replace_payload(crate::Payload::H1(pl)); req.replace_payload(crate::Payload::H1(pl));
req = req1; req = req1;
self.payload = Some(ps); self.payload = Some(ps);
} }
_ => (),
}
// handle request early // handle request early
if self.state.is_empty() { if self.state.is_empty() {
@@ -603,22 +651,25 @@ where
} }
} }
impl<T, S, B, X> Future for Dispatcher<T, S, B, X> impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
B: MessageBody, B: MessageBody,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{ {
type Item = (); type Item = ();
type Error = DispatchError; type Error = DispatchError;
#[inline] #[inline]
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let inner = self.inner.as_mut().unwrap(); match self.inner {
DispatcherState::Normal(ref mut inner) => {
inner.poll_keepalive()?; inner.poll_keepalive()?;
if inner.flags.contains(Flags::SHUTDOWN) { if inner.flags.contains(Flags::SHUTDOWN) {
@@ -639,7 +690,9 @@ where
} else { } else {
// read socket into a buf // read socket into a buf
if !inner.flags.contains(Flags::READ_DISCONNECT) { if !inner.flags.contains(Flags::READ_DISCONNECT) {
if let Some(true) = read_available(&mut inner.io, &mut inner.read_buf)? { if let Some(true) =
read_available(&mut inner.io, &mut inner.read_buf)?
{
inner.flags.insert(Flags::READ_DISCONNECT) inner.flags.insert(Flags::READ_DISCONNECT)
} }
} }
@@ -649,12 +702,34 @@ where
if inner.write_buf.remaining_mut() < LW_BUFFER_SIZE { if inner.write_buf.remaining_mut() < LW_BUFFER_SIZE {
inner.write_buf.reserve(HW_BUFFER_SIZE); inner.write_buf.reserve(HW_BUFFER_SIZE);
} }
let need_write = inner.poll_response()?; let result = inner.poll_response()?;
let drain = result == PollResponse::DrainWriteBuf;
// switch to upgrade handler
if let PollResponse::Upgrade(req) = result {
if let DispatcherState::Normal(inner) =
std::mem::replace(&mut self.inner, DispatcherState::None)
{
let mut parts = FramedParts::with_read_buf(
inner.io,
inner.codec,
inner.read_buf,
);
parts.write_buf = inner.write_buf;
let framed = Framed::from_parts(parts);
self.inner = DispatcherState::Upgrade(
inner.upgrade.unwrap().call((req, framed)),
);
return self.poll();
} else {
panic!()
}
}
// we didnt get WouldBlock from write operation, // we didnt get WouldBlock from write operation,
// so data get written to kernel completely (OSX) // so data get written to kernel completely (OSX)
// and we have to write again otherwise response can get stuck // and we have to write again otherwise response can get stuck
if inner.poll_flush()? || !need_write { if inner.poll_flush()? || !drain {
break; break;
} }
} }
@@ -694,6 +769,13 @@ where
} }
} }
} }
DispatcherState::Upgrade(ref mut fut) => fut.poll().map_err(|e| {
error!("Upgrade handler error: {}", e);
DispatchError::Upgrade
}),
DispatcherState::None => panic!(),
}
}
} }
fn read_available<T>(io: &mut T, buf: &mut BytesMut) -> Result<Option<bool>, io::Error> fn read_available<T>(io: &mut T, buf: &mut BytesMut) -> Result<Option<bool>, io::Error>
@@ -737,94 +819,35 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{cmp, io};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_service::IntoService; use actix_service::IntoService;
use bytes::{Buf, Bytes, BytesMut};
use futures::future::{lazy, ok}; use futures::future::{lazy, ok};
use super::*; use super::*;
use crate::error::Error; use crate::error::Error;
use crate::h1::ExpectHandler; use crate::h1::{ExpectHandler, UpgradeHandler};
use crate::test::TestBuffer;
struct Buffer {
buf: Bytes,
write_buf: BytesMut,
err: Option<io::Error>,
}
impl Buffer {
fn new(data: &'static str) -> Buffer {
Buffer {
buf: Bytes::from(data),
write_buf: BytesMut::new(),
err: None,
}
}
}
impl AsyncRead for Buffer {}
impl io::Read for Buffer {
fn read(&mut self, dst: &mut [u8]) -> Result<usize, io::Error> {
if self.buf.is_empty() {
if self.err.is_some() {
Err(self.err.take().unwrap())
} else {
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
}
} else {
let size = cmp::min(self.buf.len(), dst.len());
let b = self.buf.split_to(size);
dst[..size].copy_from_slice(&b);
Ok(size)
}
}
}
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<()> {
Ok(())
}
}
impl AsyncWrite for Buffer {
fn shutdown(&mut self) -> Poll<(), io::Error> {
Ok(Async::Ready(()))
}
fn write_buf<B: Buf>(&mut self, _: &mut B) -> Poll<usize, io::Error> {
Ok(Async::NotReady)
}
}
#[test] #[test]
fn test_req_parse_err() { fn test_req_parse_err() {
let mut sys = actix_rt::System::new("test"); let mut sys = actix_rt::System::new("test");
let _ = sys.block_on(lazy(|| { let _ = sys.block_on(lazy(|| {
let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n");
let mut h1 = Dispatcher::new( let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<TestBuffer>>::new(
buf, buf,
ServiceConfig::default(), ServiceConfig::default(),
CloneableService::new( CloneableService::new(
(|_| ok::<_, Error>(Response::Ok().finish())).into_service(), (|_| ok::<_, Error>(Response::Ok().finish())).into_service(),
), ),
CloneableService::new(ExpectHandler), CloneableService::new(ExpectHandler),
None,
); );
assert!(h1.poll().is_err()); assert!(h1.poll().is_err());
assert!(h1
.inner if let DispatcherState::Normal(ref inner) = h1.inner {
.as_ref() assert!(inner.flags.contains(Flags::READ_DISCONNECT));
.unwrap() assert_eq!(&inner.io.write_buf[..26], b"HTTP/1.1 400 Bad Request\r\n");
.flags }
.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::<_, ()>(()) ok::<_, ()>(())
})); }));
} }

View File

@@ -9,6 +9,8 @@ mod encoder;
mod expect; mod expect;
mod payload; mod payload;
mod service; mod service;
mod upgrade;
mod utils;
pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::client::{ClientCodec, ClientPayloadCodec};
pub use self::codec::Codec; pub use self::codec::Codec;
@@ -16,6 +18,8 @@ pub use self::dispatcher::Dispatcher;
pub use self::expect::ExpectHandler; pub use self::expect::ExpectHandler;
pub use self::payload::Payload; pub use self::payload::Payload;
pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; pub use self::service::{H1Service, H1ServiceHandler, OneRequest};
pub use self::upgrade::UpgradeHandler;
pub use self::utils::SendResponse;
#[derive(Debug)] #[derive(Debug)]
/// Codec message /// Codec message

View File

@@ -1,8 +1,8 @@
use std::fmt; use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::Framed;
use actix_server_config::{Io, ServerConfig as SrvConfig}; use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig};
use actix_service::{IntoNewService, NewService, Service}; use actix_service::{IntoNewService, NewService, Service};
use actix_utils::cloneable::CloneableService; use actix_utils::cloneable::CloneableService;
use futures::future::{ok, FutureResult}; use futures::future::{ok, FutureResult};
@@ -16,13 +16,14 @@ use crate::response::Response;
use super::codec::Codec; use super::codec::Codec;
use super::dispatcher::Dispatcher; use super::dispatcher::Dispatcher;
use super::{ExpectHandler, Message}; use super::{ExpectHandler, Message, UpgradeHandler};
/// `NewService` implementation for HTTP1 transport /// `NewService` implementation for HTTP1 transport
pub struct H1Service<T, P, S, B, X = ExpectHandler> { pub struct H1Service<T, P, S, B, X = ExpectHandler, U = UpgradeHandler<T>> {
srv: S, srv: S,
cfg: ServiceConfig, cfg: ServiceConfig,
expect: X, expect: X,
upgrade: Option<U>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, P, B)>,
} }
@@ -42,6 +43,7 @@ where
cfg, cfg,
srv: service.into_new_service(), srv: service.into_new_service(),
expect: ExpectHandler, expect: ExpectHandler,
upgrade: None,
_t: PhantomData, _t: PhantomData,
} }
} }
@@ -55,12 +57,13 @@ where
cfg, cfg,
srv: service.into_new_service(), srv: service.into_new_service(),
expect: ExpectHandler, expect: ExpectHandler,
upgrade: None,
_t: PhantomData, _t: PhantomData,
} }
} }
} }
impl<T, P, S, B, X> H1Service<T, P, S, B, X> impl<T, P, S, B, X, U> H1Service<T, P, S, B, X, U>
where where
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
@@ -68,24 +71,40 @@ where
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
B: MessageBody, B: MessageBody,
{ {
pub fn expect<U>(self, expect: U) -> H1Service<T, P, S, B, U> pub fn expect<X1>(self, expect: X1) -> H1Service<T, P, S, B, X1, U>
where where
U: NewService<Request = Request, Response = Request>, X1: NewService<Request = Request, Response = Request>,
U::Error: Into<Error>, X1::Error: Into<Error>,
U::InitError: fmt::Debug, X1::InitError: fmt::Debug,
{ {
H1Service { H1Service {
expect, expect,
cfg: self.cfg, cfg: self.cfg,
srv: self.srv, srv: self.srv,
upgrade: self.upgrade,
_t: PhantomData,
}
}
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> H1Service<T, P, S, B, X, U1>
where
U1: NewService<Request = (Request, Framed<T, Codec>), Response = ()>,
U1::Error: fmt::Display,
U1::InitError: fmt::Debug,
{
H1Service {
upgrade,
cfg: self.cfg,
srv: self.srv,
expect: self.expect,
_t: PhantomData, _t: PhantomData,
} }
} }
} }
impl<T, P, S, B, X> NewService<SrvConfig> for H1Service<T, P, S, B, X> impl<T, P, S, B, X, U> NewService<SrvConfig> for H1Service<T, P, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@@ -94,19 +113,24 @@ where
X: NewService<Request = Request, Response = Request>, X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: NewService<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
{ {
type Request = Io<T, P>; type Request = Io<T, P>;
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type InitError = (); type InitError = ();
type Service = H1ServiceHandler<T, P, S::Service, B, X::Service>; type Service = H1ServiceHandler<T, P, S::Service, B, X::Service, U::Service>;
type Future = H1ServiceResponse<T, P, S, B, X>; type Future = H1ServiceResponse<T, P, S, B, X, U>;
fn new_service(&self, cfg: &SrvConfig) -> Self::Future { fn new_service(&self, cfg: &SrvConfig) -> Self::Future {
H1ServiceResponse { H1ServiceResponse {
fut: self.srv.new_service(cfg).into_future(), fut: self.srv.new_service(cfg).into_future(),
fut_ex: Some(self.expect.new_service(&())), fut_ex: Some(self.expect.new_service(&())),
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(&())),
expect: None, expect: None,
upgrade: None,
cfg: Some(self.cfg.clone()), cfg: Some(self.cfg.clone()),
_t: PhantomData, _t: PhantomData,
} }
@@ -114,7 +138,7 @@ where
} }
#[doc(hidden)] #[doc(hidden)]
pub struct H1ServiceResponse<T, P, S, B, X> pub struct H1ServiceResponse<T, P, S, B, X, U>
where where
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
@@ -122,17 +146,22 @@ where
X: NewService<Request = Request, Response = Request>, X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: NewService<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
{ {
fut: S::Future, fut: S::Future,
fut_ex: Option<X::Future>, fut_ex: Option<X::Future>,
fut_upg: Option<U::Future>,
expect: Option<X::Service>, expect: Option<X::Service>,
upgrade: Option<U::Service>,
cfg: Option<ServiceConfig>, cfg: Option<ServiceConfig>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, P, B)>,
} }
impl<T, P, S, B, X> Future for H1ServiceResponse<T, P, S, B, X> impl<T, P, S, B, X, U> Future for H1ServiceResponse<T, P, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@@ -141,8 +170,11 @@ where
X: NewService<Request = Request, Response = Request>, X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: NewService<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
{ {
type Item = H1ServiceHandler<T, P, S::Service, B, X::Service>; type Item = H1ServiceHandler<T, P, S::Service, B, X::Service, U::Service>;
type Error = (); type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@@ -154,6 +186,14 @@ where
self.fut_ex.take(); self.fut_ex.take();
} }
if let Some(ref mut fut) = self.fut_upg {
let upgrade = try_ready!(fut
.poll()
.map_err(|e| log::error!("Init http service error: {:?}", e)));
self.upgrade = Some(upgrade);
self.fut_ex.take();
}
let service = try_ready!(self let service = try_ready!(self
.fut .fut
.poll() .poll()
@@ -162,19 +202,21 @@ where
self.cfg.take().unwrap(), self.cfg.take().unwrap(),
service, service,
self.expect.take().unwrap(), self.expect.take().unwrap(),
self.upgrade.take(),
))) )))
} }
} }
/// `Service` implementation for HTTP1 transport /// `Service` implementation for HTTP1 transport
pub struct H1ServiceHandler<T, P, S, B, X> { pub struct H1ServiceHandler<T, P, S, B, X, U> {
srv: CloneableService<S>, srv: CloneableService<S>,
expect: CloneableService<X>, expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>,
cfg: ServiceConfig, cfg: ServiceConfig,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, P, B)>,
} }
impl<T, P, S, B, X> H1ServiceHandler<T, P, S, B, X> impl<T, P, S, B, X, U> H1ServiceHandler<T, P, S, B, X, U>
where where
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
@@ -182,31 +224,41 @@ where
B: MessageBody, B: MessageBody,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{ {
fn new(cfg: ServiceConfig, srv: S, expect: X) -> H1ServiceHandler<T, P, S, B, X> { fn new(
cfg: ServiceConfig,
srv: S,
expect: X,
upgrade: Option<U>,
) -> H1ServiceHandler<T, P, S, B, X, U> {
H1ServiceHandler { H1ServiceHandler {
srv: CloneableService::new(srv), srv: CloneableService::new(srv),
expect: CloneableService::new(expect), expect: CloneableService::new(expect),
upgrade: upgrade.map(|s| CloneableService::new(s)),
cfg, cfg,
_t: PhantomData, _t: PhantomData,
} }
} }
} }
impl<T, P, S, B, X> Service for H1ServiceHandler<T, P, S, B, X> impl<T, P, S, B, X, U> Service for H1ServiceHandler<T, P, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
B: MessageBody, B: MessageBody,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{ {
type Request = Io<T, P>; type Request = Io<T, P>;
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type Future = Dispatcher<T, S, B, X>; type Future = Dispatcher<T, S, B, X, U>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
let ready = self let ready = self
@@ -243,6 +295,7 @@ where
self.cfg.clone(), self.cfg.clone(),
self.srv.clone(), self.srv.clone(),
self.expect.clone(), self.expect.clone(),
self.upgrade.clone(),
) )
} }
} }
@@ -256,7 +309,7 @@ pub struct OneRequest<T, P> {
impl<T, P> OneRequest<T, P> impl<T, P> OneRequest<T, P>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
{ {
/// Create new `H1SimpleService` instance. /// Create new `H1SimpleService` instance.
pub fn new() -> Self { pub fn new() -> Self {
@@ -269,7 +322,7 @@ where
impl<T, P> NewService<SrvConfig> for OneRequest<T, P> impl<T, P> NewService<SrvConfig> for OneRequest<T, P>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
{ {
type Request = Io<T, P>; type Request = Io<T, P>;
type Response = (Request, Framed<T, Codec>); type Response = (Request, Framed<T, Codec>);
@@ -295,7 +348,7 @@ pub struct OneRequestService<T, P> {
impl<T, P> Service for OneRequestService<T, P> impl<T, P> Service for OneRequestService<T, P>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
{ {
type Request = Io<T, P>; type Request = Io<T, P>;
type Response = (Request, Framed<T, Codec>); type Response = (Request, Framed<T, Codec>);
@@ -319,14 +372,14 @@ where
#[doc(hidden)] #[doc(hidden)]
pub struct OneRequestServiceResponse<T> pub struct OneRequestServiceResponse<T>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
{ {
framed: Option<Framed<T, Codec>>, framed: Option<Framed<T, Codec>>,
} }
impl<T> Future for OneRequestServiceResponse<T> impl<T> Future for OneRequestServiceResponse<T>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
{ {
type Item = (Request, Framed<T, Codec>); type Item = (Request, Framed<T, Codec>);
type Error = ParseError; type Error = ParseError;

View File

@@ -0,0 +1,40 @@
use std::marker::PhantomData;
use actix_codec::Framed;
use actix_service::{NewService, Service};
use futures::future::FutureResult;
use futures::{Async, Poll};
use crate::error::Error;
use crate::h1::Codec;
use crate::request::Request;
pub struct UpgradeHandler<T>(PhantomData<T>);
impl<T> NewService for UpgradeHandler<T> {
type Request = (Request, Framed<T, Codec>);
type Response = ();
type Error = Error;
type Service = UpgradeHandler<T>;
type InitError = Error;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &()) -> Self::Future {
unimplemented!()
}
}
impl<T> Service for UpgradeHandler<T> {
type Request = (Request, Framed<T, Codec>);
type Response = ();
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, _: Self::Request) -> Self::Future {
unimplemented!()
}
}

View File

@@ -0,0 +1,92 @@
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use futures::{Async, Future, Poll, Sink};
use crate::body::{BodySize, MessageBody, ResponseBody};
use crate::error::Error;
use crate::h1::{Codec, Message};
use crate::response::Response;
/// Send http/1 response
pub struct SendResponse<T, B> {
res: Option<Message<(Response<()>, BodySize)>>,
body: Option<ResponseBody<B>>,
framed: Option<Framed<T, Codec>>,
}
impl<T, B> SendResponse<T, B>
where
B: MessageBody,
{
pub fn new(framed: Framed<T, Codec>, response: Response<B>) -> Self {
let (res, body) = response.into_parts();
SendResponse {
res: Some((res, body.size()).into()),
body: Some(body),
framed: Some(framed),
}
}
}
impl<T, B> Future for SendResponse<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

@@ -1,9 +1,10 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::time::Instant; use std::time::Instant;
use std::{fmt, mem}; use std::{fmt, mem, net};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_server_config::IoStream;
use actix_service::Service; use actix_service::Service;
use actix_utils::cloneable::CloneableService; use actix_utils::cloneable::CloneableService;
use bitflags::bitflags; use bitflags::bitflags;
@@ -29,14 +30,11 @@ use crate::response::Response;
const CHUNK_SIZE: usize = 16_384; const CHUNK_SIZE: usize = 16_384;
/// Dispatcher for HTTP/2 protocol /// Dispatcher for HTTP/2 protocol
pub struct Dispatcher< pub struct Dispatcher<T: IoStream, S: Service<Request = Request>, B: MessageBody> {
T: AsyncRead + AsyncWrite,
S: Service<Request = Request>,
B: MessageBody,
> {
service: CloneableService<S>, service: CloneableService<S>,
connection: Connection<T, Bytes>, connection: Connection<T, Bytes>,
config: ServiceConfig, config: ServiceConfig,
peer_addr: Option<net::SocketAddr>,
ka_expire: Instant, ka_expire: Instant,
ka_timer: Option<Delay>, ka_timer: Option<Delay>,
_t: PhantomData<B>, _t: PhantomData<B>,
@@ -44,7 +42,7 @@ pub struct Dispatcher<
impl<T, S, B> Dispatcher<T, S, B> impl<T, S, B> Dispatcher<T, S, B>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -56,6 +54,7 @@ where
connection: Connection<T, Bytes>, connection: Connection<T, Bytes>,
config: ServiceConfig, config: ServiceConfig,
timeout: Option<Delay>, timeout: Option<Delay>,
peer_addr: Option<net::SocketAddr>,
) -> Self { ) -> Self {
// let keepalive = config.keep_alive_enabled(); // let keepalive = config.keep_alive_enabled();
// let flags = if keepalive { // let flags = if keepalive {
@@ -76,9 +75,10 @@ where
Dispatcher { Dispatcher {
service, service,
config, config,
peer_addr,
connection,
ka_expire, ka_expire,
ka_timer, ka_timer,
connection,
_t: PhantomData, _t: PhantomData,
} }
} }
@@ -86,7 +86,7 @@ where
impl<T, S, B> Future for Dispatcher<T, S, B> impl<T, S, B> Future for Dispatcher<T, S, B>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -117,6 +117,7 @@ where
head.method = parts.method; head.method = parts.method;
head.version = parts.version; head.version = parts.version;
head.headers = parts.headers.into(); head.headers = parts.headers.into();
head.peer_addr = self.peer_addr;
tokio_current_thread::spawn(ServiceResponse::<S::Future, B> { tokio_current_thread::spawn(ServiceResponse::<S::Future, B> {
state: ServiceResponseState::ServiceCall( state: ServiceResponseState::ServiceCall(
self.service.call(req), self.service.call(req),
@@ -153,10 +154,10 @@ where
fn prepare_response( fn prepare_response(
&self, &self,
head: &ResponseHead, head: &ResponseHead,
length: &mut BodySize, size: &mut BodySize,
) -> http::Response<()> { ) -> http::Response<()> {
let mut has_date = false; let mut has_date = false;
let mut skip_len = length != &BodySize::Stream; let mut skip_len = size != &BodySize::Stream;
let mut res = http::Response::new(()); let mut res = http::Response::new(());
*res.status_mut() = head.status; *res.status_mut() = head.status;
@@ -166,14 +167,14 @@ where
match head.status { match head.status {
http::StatusCode::NO_CONTENT http::StatusCode::NO_CONTENT
| http::StatusCode::CONTINUE | http::StatusCode::CONTINUE
| http::StatusCode::PROCESSING => *length = BodySize::None, | http::StatusCode::PROCESSING => *size = BodySize::None,
http::StatusCode::SWITCHING_PROTOCOLS => { http::StatusCode::SWITCHING_PROTOCOLS => {
skip_len = true; skip_len = true;
*length = BodySize::Stream; *size = BodySize::Stream;
} }
_ => (), _ => (),
} }
let _ = match length { let _ = match size {
BodySize::None | BodySize::Stream => None, BodySize::None | BodySize::Stream => None,
BodySize::Empty => res BodySize::Empty => res
.headers_mut() .headers_mut()
@@ -229,16 +230,15 @@ where
let (res, body) = res.into().replace_body(()); let (res, body) = res.into().replace_body(());
let mut send = send.take().unwrap(); let mut send = send.take().unwrap();
let mut length = body.length(); let mut size = body.size();
let h2_res = self.prepare_response(res.head(), &mut length); let h2_res = self.prepare_response(res.head(), &mut size);
let stream = send let stream =
.send_response(h2_res, length.is_eof()) send.send_response(h2_res, size.is_eof()).map_err(|e| {
.map_err(|e| {
trace!("Error sending h2 response: {:?}", e); trace!("Error sending h2 response: {:?}", e);
})?; })?;
if length.is_eof() { if size.is_eof() {
Ok(Async::Ready(())) Ok(Async::Ready(()))
} else { } else {
self.state = ServiceResponseState::SendPayload(stream, body); self.state = ServiceResponseState::SendPayload(stream, body);
@@ -251,16 +251,15 @@ where
let (res, body) = res.replace_body(()); let (res, body) = res.replace_body(());
let mut send = send.take().unwrap(); let mut send = send.take().unwrap();
let mut length = body.length(); let mut size = body.size();
let h2_res = self.prepare_response(res.head(), &mut length); let h2_res = self.prepare_response(res.head(), &mut size);
let stream = send let stream =
.send_response(h2_res, length.is_eof()) send.send_response(h2_res, size.is_eof()).map_err(|e| {
.map_err(|e| {
trace!("Error sending h2 response: {:?}", e); trace!("Error sending h2 response: {:?}", e);
})?; })?;
if length.is_eof() { if size.is_eof() {
Ok(Async::Ready(())) Ok(Async::Ready(()))
} else { } else {
self.state = ServiceResponseState::SendPayload( self.state = ServiceResponseState::SendPayload(

View File

@@ -3,7 +3,7 @@ use std::marker::PhantomData;
use std::{io, net}; use std::{io, net};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_server_config::{Io, ServerConfig as SrvConfig}; use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig};
use actix_service::{IntoNewService, NewService, Service}; use actix_service::{IntoNewService, NewService, Service};
use actix_utils::cloneable::CloneableService; use actix_utils::cloneable::CloneableService;
use bytes::Bytes; use bytes::Bytes;
@@ -63,7 +63,7 @@ where
impl<T, P, S, B> NewService<SrvConfig> for H2Service<T, P, S, B> impl<T, P, S, B> NewService<SrvConfig> for H2Service<T, P, S, B>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@@ -95,7 +95,7 @@ pub struct H2ServiceResponse<T, P, S: NewService<SrvConfig, Request = Request>,
impl<T, P, S, B> Future for H2ServiceResponse<T, P, S, B> impl<T, P, S, B> Future for H2ServiceResponse<T, P, S, B>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@@ -140,7 +140,7 @@ where
impl<T, P, S, B> Service for H2ServiceHandler<T, P, S, B> impl<T, P, S, B> Service for H2ServiceHandler<T, P, S, B>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -161,17 +161,20 @@ where
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, req: Self::Request) -> Self::Future {
let io = req.into_parts().0;
let peer_addr = io.peer_addr();
H2ServiceHandlerResponse { H2ServiceHandlerResponse {
state: State::Handshake( state: State::Handshake(
Some(self.srv.clone()), Some(self.srv.clone()),
Some(self.cfg.clone()), Some(self.cfg.clone()),
server::handshake(req.into_parts().0), peer_addr,
server::handshake(io),
), ),
} }
} }
} }
enum State<T: AsyncRead + AsyncWrite, S: Service<Request = Request>, B: MessageBody> enum State<T: IoStream, S: Service<Request = Request>, B: MessageBody>
where where
S::Future: 'static, S::Future: 'static,
{ {
@@ -179,13 +182,14 @@ where
Handshake( Handshake(
Option<CloneableService<S>>, Option<CloneableService<S>>,
Option<ServiceConfig>, Option<ServiceConfig>,
Option<net::SocketAddr>,
Handshake<T, Bytes>, Handshake<T, Bytes>,
), ),
} }
pub struct H2ServiceHandlerResponse<T, S, B> pub struct H2ServiceHandlerResponse<T, S, B>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -197,7 +201,7 @@ where
impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B> impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -210,14 +214,19 @@ where
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.state { match self.state {
State::Incoming(ref mut disp) => disp.poll(), State::Incoming(ref mut disp) => disp.poll(),
State::Handshake(ref mut srv, ref mut config, ref mut handshake) => { State::Handshake(
match handshake.poll() { ref mut srv,
ref mut config,
ref peer_addr,
ref mut handshake,
) => match handshake.poll() {
Ok(Async::Ready(conn)) => { Ok(Async::Ready(conn)) => {
self.state = State::Incoming(Dispatcher::new( self.state = State::Incoming(Dispatcher::new(
srv.take().unwrap(), srv.take().unwrap(),
conn, conn,
config.take().unwrap(), config.take().unwrap(),
None, None,
peer_addr.clone(),
)); ));
self.poll() self.poll()
} }
@@ -226,8 +235,7 @@ where
trace!("H2 handshake error: {}", err); trace!("H2 handshake error: {}", err);
Err(err.into()) Err(err.into())
} }
} },
}
} }
} }
} }

View File

@@ -1,6 +1,7 @@
use std::{io, mem, ptr, slice};
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use http::Version; use http::Version;
use std::{mem, ptr, slice};
const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
2021222324252627282930313233343536373839\ 2021222324252627282930313233343536373839\
@@ -167,6 +168,18 @@ pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
} }
} }
pub(crate) 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(())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -60,6 +60,7 @@ impl Response {
STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
STATIC_RESP!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY);
STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR);
STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED); STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED);

View File

@@ -12,7 +12,6 @@ pub mod body;
mod builder; mod builder;
pub mod client; pub mod client;
mod config; mod config;
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust", feature = "brotli"))]
pub mod encoding; pub mod encoding;
mod extensions; mod extensions;
mod header; mod header;

View File

@@ -1,4 +1,5 @@
use std::cell::{Ref, RefCell, RefMut}; use std::cell::{Ref, RefCell, RefMut};
use std::net;
use std::rc::Rc; use std::rc::Rc;
use bitflags::bitflags; use bitflags::bitflags;
@@ -43,6 +44,7 @@ pub struct RequestHead {
pub version: Version, pub version: Version,
pub headers: HeaderMap, pub headers: HeaderMap,
pub extensions: RefCell<Extensions>, pub extensions: RefCell<Extensions>,
pub peer_addr: Option<net::SocketAddr>,
flags: Flags, flags: Flags,
} }
@@ -54,6 +56,7 @@ impl Default for RequestHead {
version: Version::HTTP_11, version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16), headers: HeaderMap::with_capacity(16),
flags: Flags::empty(), flags: Flags::empty(),
peer_addr: None,
extensions: RefCell::new(Extensions::new()), extensions: RefCell::new(Extensions::new()),
} }
} }

View File

@@ -53,6 +53,7 @@ where
type Item = Bytes; type Item = Bytes;
type Error = PayloadError; type Error = PayloadError;
#[inline]
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
match self { match self {
Payload::None => Ok(Async::Ready(None)), Payload::None => Ok(Async::Ready(None)),

View File

@@ -1,5 +1,5 @@
use std::cell::{Ref, RefMut}; use std::cell::{Ref, RefMut};
use std::fmt; use std::{fmt, net};
use http::{header, Method, Uri, Version}; use http::{header, Method, Uri, Version};
@@ -139,6 +139,7 @@ impl<P> Request<P> {
} }
/// Check if request requires connection upgrade /// Check if request requires connection upgrade
#[inline]
pub fn upgrade(&self) -> bool { pub fn upgrade(&self) -> bool {
if let Some(conn) = self.head().headers.get(header::CONNECTION) { if let Some(conn) = self.head().headers.get(header::CONNECTION) {
if let Ok(s) = conn.to_str() { if let Ok(s) = conn.to_str() {
@@ -147,6 +148,15 @@ impl<P> Request<P> {
} }
self.head().method == Method::CONNECT self.head().method == Method::CONNECT
} }
/// Peer socket address
///
/// Peer address is actual socket address, if proxy is used in front of
/// actix http server, then peer address would be address of this proxy.
#[inline]
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
self.head().peer_addr
}
} }
impl<P> fmt::Debug for Request<P> { impl<P> fmt::Debug for Request<P> {

View File

@@ -1,7 +1,7 @@
//! Http response //! Http response
use std::cell::{Ref, RefMut}; use std::cell::{Ref, RefMut};
use std::io::Write; use std::io::Write;
use std::{fmt, io, str}; use std::{fmt, str};
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{BufMut, Bytes, BytesMut};
use futures::future::{ok, FutureResult, IntoFuture}; use futures::future::{ok, FutureResult, IntoFuture};
@@ -51,13 +51,9 @@ impl Response<Body> {
/// Constructs an error response /// Constructs an error response
#[inline] #[inline]
pub fn from_error(error: Error) -> Response { pub fn from_error(error: Error) -> Response {
let mut resp = error.as_response_error().error_response(); let mut resp = error.as_response_error().render_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.error = Some(error);
resp.set_body(Body::from(buf)) resp
} }
/// Convert response to response with body /// Convert response to response with body
@@ -210,6 +206,18 @@ impl<B> Response<B> {
} }
} }
/// Split response and body
pub fn into_parts(self) -> (Response<()>, ResponseBody<B>) {
(
Response {
head: self.head,
body: ResponseBody::Body(()),
error: self.error,
},
self.body,
)
}
/// Drop request's body /// Drop request's body
pub fn drop_body(self) -> Response<()> { pub fn drop_body(self) -> Response<()> {
Response { Response {
@@ -264,7 +272,7 @@ impl<B: MessageBody> fmt::Debug for Response<B> {
for (key, val) in self.head.headers.iter() { for (key, val) in self.head.headers.iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val); let _ = writeln!(f, " {:?}: {:?}", key, val);
} }
let _ = writeln!(f, " body: {:?}", self.body.length()); let _ = writeln!(f, " body: {:?}", self.body.size());
res res
} }
} }
@@ -297,18 +305,6 @@ 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 /// An HTTP response builder
/// ///
/// This type can be used to construct an instance of `Response` through a /// This type can be used to construct an instance of `Response` through a

View File

@@ -1,8 +1,10 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::{fmt, io}; use std::{fmt, io, net};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_server_config::{Io as ServerIo, Protocol, ServerConfig as SrvConfig}; use actix_server_config::{
Io as ServerIo, IoStream, Protocol, ServerConfig as SrvConfig,
};
use actix_service::{IntoNewService, NewService, Service}; use actix_service::{IntoNewService, NewService, Service};
use actix_utils::cloneable::CloneableService; use actix_utils::cloneable::CloneableService;
use bytes::{Buf, BufMut, Bytes, BytesMut}; use bytes::{Buf, BufMut, Bytes, BytesMut};
@@ -18,10 +20,11 @@ use crate::response::Response;
use crate::{h1, h2::Dispatcher}; use crate::{h1, h2::Dispatcher};
/// `NewService` HTTP1.1/HTTP2 transport implementation /// `NewService` HTTP1.1/HTTP2 transport implementation
pub struct HttpService<T, P, S, B, X = h1::ExpectHandler> { pub struct HttpService<T, P, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler<T>> {
srv: S, srv: S,
cfg: ServiceConfig, cfg: ServiceConfig,
expect: X, expect: X,
upgrade: Option<U>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, P, B)>,
} }
@@ -57,6 +60,7 @@ where
cfg, cfg,
srv: service.into_new_service(), srv: service.into_new_service(),
expect: h1::ExpectHandler, expect: h1::ExpectHandler,
upgrade: None,
_t: PhantomData, _t: PhantomData,
} }
} }
@@ -70,12 +74,13 @@ where
cfg, cfg,
srv: service.into_new_service(), srv: service.into_new_service(),
expect: h1::ExpectHandler, expect: h1::ExpectHandler,
upgrade: None,
_t: PhantomData, _t: PhantomData,
} }
} }
} }
impl<T, P, S, B, X> HttpService<T, P, S, B, X> impl<T, P, S, B, X, U> HttpService<T, P, S, B, X, U>
where where
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
@@ -88,24 +93,44 @@ where
/// Service get called with request that contains `EXPECT` header. /// Service get called with request that contains `EXPECT` header.
/// Service must return request in case of success, in that case /// Service must return request in case of success, in that case
/// request will be forwarded to main service. /// request will be forwarded to main service.
pub fn expect<U>(self, expect: U) -> HttpService<T, P, S, B, U> pub fn expect<X1>(self, expect: X1) -> HttpService<T, P, S, B, X1, U>
where where
U: NewService<Request = Request, Response = Request>, X1: NewService<Request = Request, Response = Request>,
U::Error: Into<Error>, X1::Error: Into<Error>,
U::InitError: fmt::Debug, X1::InitError: fmt::Debug,
{ {
HttpService { HttpService {
expect, expect,
cfg: self.cfg, cfg: self.cfg,
srv: self.srv, srv: self.srv,
upgrade: self.upgrade,
_t: PhantomData,
}
}
/// Provide service for custom `Connection: UPGRADE` support.
///
/// If service is provided then normal requests handling get halted
/// and this service get called with original request and framed object.
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> HttpService<T, P, S, B, X, U1>
where
U1: NewService<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U1::Error: fmt::Display,
U1::InitError: fmt::Debug,
{
HttpService {
upgrade,
cfg: self.cfg,
srv: self.srv,
expect: self.expect,
_t: PhantomData, _t: PhantomData,
} }
} }
} }
impl<T, P, S, B, X> NewService<SrvConfig> for HttpService<T, P, S, B, X> impl<T, P, S, B, X, U> NewService<SrvConfig> for HttpService<T, P, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
@@ -115,19 +140,24 @@ where
X: NewService<Request = Request, Response = Request>, X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: NewService<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
{ {
type Request = ServerIo<T, P>; type Request = ServerIo<T, P>;
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type InitError = (); type InitError = ();
type Service = HttpServiceHandler<T, P, S::Service, B, X::Service>; type Service = HttpServiceHandler<T, P, S::Service, B, X::Service, U::Service>;
type Future = HttpServiceResponse<T, P, S, B, X>; type Future = HttpServiceResponse<T, P, S, B, X, U>;
fn new_service(&self, cfg: &SrvConfig) -> Self::Future { fn new_service(&self, cfg: &SrvConfig) -> Self::Future {
HttpServiceResponse { HttpServiceResponse {
fut: self.srv.new_service(cfg).into_future(), fut: self.srv.new_service(cfg).into_future(),
fut_ex: Some(self.expect.new_service(&())), fut_ex: Some(self.expect.new_service(&())),
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(&())),
expect: None, expect: None,
upgrade: None,
cfg: Some(self.cfg.clone()), cfg: Some(self.cfg.clone()),
_t: PhantomData, _t: PhantomData,
} }
@@ -135,17 +165,26 @@ where
} }
#[doc(hidden)] #[doc(hidden)]
pub struct HttpServiceResponse<T, P, S: NewService<SrvConfig>, B, X: NewService> { pub struct HttpServiceResponse<
T,
P,
S: NewService<SrvConfig>,
B,
X: NewService,
U: NewService,
> {
fut: S::Future, fut: S::Future,
fut_ex: Option<X::Future>, fut_ex: Option<X::Future>,
fut_upg: Option<U::Future>,
expect: Option<X::Service>, expect: Option<X::Service>,
upgrade: Option<U::Service>,
cfg: Option<ServiceConfig>, cfg: Option<ServiceConfig>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, P, B)>,
} }
impl<T, P, S, B, X> Future for HttpServiceResponse<T, P, S, B, X> impl<T, P, S, B, X, U> Future for HttpServiceResponse<T, P, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
@@ -155,8 +194,11 @@ where
X: NewService<Request = Request, Response = Request>, X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: NewService<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
{ {
type Item = HttpServiceHandler<T, P, S::Service, B, X::Service>; type Item = HttpServiceHandler<T, P, S::Service, B, X::Service, U::Service>;
type Error = (); type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@@ -168,6 +210,14 @@ where
self.fut_ex.take(); self.fut_ex.take();
} }
if let Some(ref mut fut) = self.fut_upg {
let upgrade = try_ready!(fut
.poll()
.map_err(|e| log::error!("Init http service error: {:?}", e)));
self.upgrade = Some(upgrade);
self.fut_ex.take();
}
let service = try_ready!(self let service = try_ready!(self
.fut .fut
.poll() .poll()
@@ -176,19 +226,21 @@ where
self.cfg.take().unwrap(), self.cfg.take().unwrap(),
service, service,
self.expect.take().unwrap(), self.expect.take().unwrap(),
self.upgrade.take(),
))) )))
} }
} }
/// `Service` implementation for http transport /// `Service` implementation for http transport
pub struct HttpServiceHandler<T, P, S, B, X> { pub struct HttpServiceHandler<T, P, S, B, X, U> {
srv: CloneableService<S>, srv: CloneableService<S>,
expect: CloneableService<X>, expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>,
cfg: ServiceConfig, cfg: ServiceConfig,
_t: PhantomData<(T, P, B, X)>, _t: PhantomData<(T, P, B, X)>,
} }
impl<T, P, S, B, X> HttpServiceHandler<T, P, S, B, X> impl<T, P, S, B, X, U> HttpServiceHandler<T, P, S, B, X, U>
where where
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
@@ -197,20 +249,28 @@ where
B: MessageBody + 'static, B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
{ {
fn new(cfg: ServiceConfig, srv: S, expect: X) -> HttpServiceHandler<T, P, S, B, X> { fn new(
cfg: ServiceConfig,
srv: S,
expect: X,
upgrade: Option<U>,
) -> HttpServiceHandler<T, P, S, B, X, U> {
HttpServiceHandler { HttpServiceHandler {
cfg, cfg,
srv: CloneableService::new(srv), srv: CloneableService::new(srv),
expect: CloneableService::new(expect), expect: CloneableService::new(expect),
upgrade: upgrade.map(|s| CloneableService::new(s)),
_t: PhantomData, _t: PhantomData,
} }
} }
} }
impl<T, P, S, B, X> Service for HttpServiceHandler<T, P, S, B, X> impl<T, P, S, B, X, U> Service for HttpServiceHandler<T, P, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -218,11 +278,13 @@ where
B: MessageBody + 'static, B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
{ {
type Request = ServerIo<T, P>; type Request = ServerIo<T, P>;
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type Future = HttpServiceHandlerResponse<T, S, B, X>; type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
let ready = self let ready = self
@@ -257,6 +319,7 @@ where
let (io, _, proto) = req.into_parts(); let (io, _, proto) = req.into_parts();
match proto { match proto {
Protocol::Http2 => { Protocol::Http2 => {
let peer_addr = io.peer_addr();
let io = Io { let io = Io {
inner: io, inner: io,
unread: None, unread: None,
@@ -266,6 +329,7 @@ where
server::handshake(io), server::handshake(io),
self.cfg.clone(), self.cfg.clone(),
self.srv.clone(), self.srv.clone(),
peer_addr,
))), ))),
} }
} }
@@ -275,6 +339,7 @@ where
self.cfg.clone(), self.cfg.clone(),
self.srv.clone(), self.srv.clone(),
self.expect.clone(), self.expect.clone(),
self.upgrade.clone(),
)), )),
}, },
_ => HttpServiceHandlerResponse { _ => HttpServiceHandlerResponse {
@@ -284,23 +349,26 @@ where
self.cfg.clone(), self.cfg.clone(),
self.srv.clone(), self.srv.clone(),
self.expect.clone(), self.expect.clone(),
self.upgrade.clone(),
))), ))),
}, },
} }
} }
} }
enum State<T, S, B, X> enum State<T, S, B, X, U>
where where
S: Service<Request = Request>, S: Service<Request = Request>,
S::Future: 'static, S::Future: 'static,
S::Error: Into<Error>, S::Error: Into<Error>,
T: AsyncRead + AsyncWrite, T: IoStream,
B: MessageBody, B: MessageBody,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
{ {
H1(h1::Dispatcher<T, S, B, X>), H1(h1::Dispatcher<T, S, B, X, U>),
H2(Dispatcher<Io<T>, S, B>), H2(Dispatcher<Io<T>, S, B>),
Unknown( Unknown(
Option<( Option<(
@@ -309,14 +377,22 @@ where
ServiceConfig, ServiceConfig,
CloneableService<S>, CloneableService<S>,
CloneableService<X>, CloneableService<X>,
Option<CloneableService<U>>,
)>,
),
Handshake(
Option<(
Handshake<Io<T>, Bytes>,
ServiceConfig,
CloneableService<S>,
Option<net::SocketAddr>,
)>, )>,
), ),
Handshake(Option<(Handshake<Io<T>, Bytes>, ServiceConfig, CloneableService<S>)>),
} }
pub struct HttpServiceHandlerResponse<T, S, B, X> pub struct HttpServiceHandlerResponse<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -324,15 +400,17 @@ where
B: MessageBody + 'static, B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
{ {
state: State<T, S, B, X>, state: State<T, S, B, X, U>,
} }
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
impl<T, S, B, X> Future for HttpServiceHandlerResponse<T, S, B, X> impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -340,6 +418,8 @@ where
B: MessageBody, B: MessageBody,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
{ {
type Item = (); type Item = ();
type Error = DispatchError; type Error = DispatchError;
@@ -366,14 +446,19 @@ where
} else { } else {
panic!() panic!()
} }
let (io, buf, cfg, srv, expect) = data.take().unwrap(); let (io, buf, cfg, srv, expect, upgrade) = data.take().unwrap();
if buf[..14] == HTTP2_PREFACE[..] { if buf[..14] == HTTP2_PREFACE[..] {
let peer_addr = io.peer_addr();
let io = Io { let io = Io {
inner: io, inner: io,
unread: Some(buf), unread: Some(buf),
}; };
self.state = self.state = State::Handshake(Some((
State::Handshake(Some((server::handshake(io), cfg, srv))); server::handshake(io),
cfg,
srv,
peer_addr,
)));
} else { } else {
self.state = State::H1(h1::Dispatcher::with_timeout( self.state = State::H1(h1::Dispatcher::with_timeout(
io, io,
@@ -383,6 +468,7 @@ where
None, None,
srv, srv,
expect, expect,
upgrade,
)) ))
} }
self.poll() self.poll()
@@ -400,8 +486,8 @@ where
} else { } else {
panic!() panic!()
}; };
let (_, cfg, srv) = data.take().unwrap(); let (_, cfg, srv, peer_addr) = data.take().unwrap();
self.state = State::H2(Dispatcher::new(srv, conn, cfg, None)); self.state = State::H2(Dispatcher::new(srv, conn, cfg, None, peer_addr));
self.poll() self.poll()
} }
} }
@@ -453,3 +539,25 @@ impl<T: AsyncWrite> AsyncWrite for Io<T> {
self.inner.write_buf(buf) self.inner.write_buf(buf)
} }
} }
impl<T: IoStream> IoStream for Io<T> {
#[inline]
fn peer_addr(&self) -> Option<net::SocketAddr> {
self.inner.peer_addr()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.inner.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<std::time::Duration>) -> io::Result<()> {
self.inner.set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<std::time::Duration>) -> io::Result<()> {
self.inner.set_keepalive(dur)
}
}

View File

@@ -1,8 +1,12 @@
//! Test Various helpers for Actix applications to use during testing. //! Test Various helpers for Actix applications to use during testing.
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
use std::io;
use std::str::FromStr; use std::str::FromStr;
use bytes::Bytes; use actix_codec::{AsyncRead, AsyncWrite};
use actix_server_config::IoStream;
use bytes::{Buf, Bytes, BytesMut};
use futures::{Async, Poll};
use http::header::{self, HeaderName, HeaderValue}; use http::header::{self, HeaderName, HeaderValue};
use http::{HttpTryFrom, Method, Uri, Version}; use http::{HttpTryFrom, Method, Uri, Version};
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
@@ -181,3 +185,86 @@ impl TestRequest {
fn parts(parts: &mut Option<Inner>) -> &mut Inner { fn parts(parts: &mut Option<Inner>) -> &mut Inner {
parts.as_mut().expect("cannot reuse test request builder") parts.as_mut().expect("cannot reuse test request builder")
} }
/// Async io buffer
pub struct TestBuffer {
pub read_buf: BytesMut,
pub write_buf: BytesMut,
pub err: Option<io::Error>,
}
impl TestBuffer {
/// Create new TestBuffer instance
pub fn new<T>(data: T) -> TestBuffer
where
BytesMut: From<T>,
{
TestBuffer {
read_buf: BytesMut::from(data),
write_buf: BytesMut::new(),
err: None,
}
}
/// Create new empty TestBuffer instance
pub fn empty() -> TestBuffer {
TestBuffer::new("")
}
/// Add extra data to read buffer.
pub fn extend_read_buf<T: AsRef<[u8]>>(&mut self, data: T) {
self.read_buf.extend_from_slice(data.as_ref())
}
}
impl io::Read for TestBuffer {
fn read(&mut self, dst: &mut [u8]) -> Result<usize, io::Error> {
if self.read_buf.is_empty() {
if self.err.is_some() {
Err(self.err.take().unwrap())
} else {
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
}
} else {
let size = std::cmp::min(self.read_buf.len(), dst.len());
let b = self.read_buf.split_to(size);
dst[..size].copy_from_slice(&b);
Ok(size)
}
}
}
impl io::Write for TestBuffer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.write_buf.extend(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl AsyncRead for TestBuffer {}
impl AsyncWrite for TestBuffer {
fn shutdown(&mut self) -> Poll<(), io::Error> {
Ok(Async::Ready(()))
}
fn write_buf<B: Buf>(&mut self, _: &mut B) -> Poll<usize, io::Error> {
Ok(Async::NotReady)
}
}
impl IoStream for TestBuffer {
fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> {
Ok(())
}
fn set_linger(&mut self, _dur: Option<std::time::Duration>) -> io::Result<()> {
Ok(())
}
fn set_keepalive(&mut self, _dur: Option<std::time::Duration>) -> io::Result<()> {
Ok(())
}
}

View File

@@ -9,21 +9,18 @@ use derive_more::{Display, From};
use http::{header, Method, StatusCode}; use http::{header, Method, StatusCode};
use crate::error::ResponseError; use crate::error::ResponseError;
use crate::httpmessage::HttpMessage; use crate::message::RequestHead;
use crate::request::Request;
use crate::response::{Response, ResponseBuilder}; use crate::response::{Response, ResponseBuilder};
mod codec; mod codec;
mod frame; mod frame;
mod mask; mod mask;
mod proto; mod proto;
mod service;
mod transport; mod transport;
pub use self::codec::{Codec, Frame, Message}; pub use self::codec::{Codec, Frame, Message};
pub use self::frame::Parser; pub use self::frame::Parser;
pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode}; pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode};
pub use self::service::VerifyWebSockets;
pub use self::transport::Transport; pub use self::transport::Transport;
/// Websocket protocol errors /// Websocket protocol errors
@@ -112,7 +109,7 @@ impl ResponseError for HandshakeError {
// /// `protocols` is a sequence of known protocols. On successful handshake, // /// `protocols` is a sequence of known protocols. On successful handshake,
// /// the returned response headers contain the first protocol in this list // /// the returned response headers contain the first protocol in this list
// /// which the server also knows. // /// which the server also knows.
pub fn handshake(req: &Request) -> Result<ResponseBuilder, HandshakeError> { pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
verify_handshake(req)?; verify_handshake(req)?;
Ok(handshake_response(req)) Ok(handshake_response(req))
} }
@@ -121,9 +118,9 @@ pub fn handshake(req: &Request) -> Result<ResponseBuilder, HandshakeError> {
// /// `protocols` is a sequence of known protocols. On successful handshake, // /// `protocols` is a sequence of known protocols. On successful handshake,
// /// the returned response headers contain the first protocol in this list // /// the returned response headers contain the first protocol in this list
// /// which the server also knows. // /// which the server also knows.
pub fn verify_handshake(req: &Request) -> Result<(), HandshakeError> { pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> {
// WebSocket accepts only GET // WebSocket accepts only GET
if *req.method() != Method::GET { if req.method != Method::GET {
return Err(HandshakeError::GetMethodRequired); return Err(HandshakeError::GetMethodRequired);
} }
@@ -171,7 +168,7 @@ pub fn verify_handshake(req: &Request) -> Result<(), HandshakeError> {
/// Create websocket's handshake response /// Create websocket's handshake response
/// ///
/// This function returns handshake `Response`, ready to send to peer. /// This function returns handshake `Response`, ready to send to peer.
pub fn handshake_response(req: &Request) -> ResponseBuilder { pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
let key = { let key = {
let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap(); let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap();
proto::hash_key(key.as_ref()) proto::hash_key(key.as_ref())
@@ -195,13 +192,13 @@ mod tests {
let req = TestRequest::default().method(Method::POST).finish(); let req = TestRequest::default().method(Method::POST).finish();
assert_eq!( assert_eq!(
HandshakeError::GetMethodRequired, HandshakeError::GetMethodRequired,
verify_handshake(&req).err().unwrap() verify_handshake(req.head()).err().unwrap()
); );
let req = TestRequest::default().finish(); let req = TestRequest::default().finish();
assert_eq!( assert_eq!(
HandshakeError::NoWebsocketUpgrade, HandshakeError::NoWebsocketUpgrade,
verify_handshake(&req).err().unwrap() verify_handshake(req.head()).err().unwrap()
); );
let req = TestRequest::default() let req = TestRequest::default()
@@ -209,7 +206,7 @@ mod tests {
.finish(); .finish();
assert_eq!( assert_eq!(
HandshakeError::NoWebsocketUpgrade, HandshakeError::NoWebsocketUpgrade,
verify_handshake(&req).err().unwrap() verify_handshake(req.head()).err().unwrap()
); );
let req = TestRequest::default() let req = TestRequest::default()
@@ -220,7 +217,7 @@ mod tests {
.finish(); .finish();
assert_eq!( assert_eq!(
HandshakeError::NoConnectionUpgrade, HandshakeError::NoConnectionUpgrade,
verify_handshake(&req).err().unwrap() verify_handshake(req.head()).err().unwrap()
); );
let req = TestRequest::default() let req = TestRequest::default()
@@ -235,7 +232,7 @@ mod tests {
.finish(); .finish();
assert_eq!( assert_eq!(
HandshakeError::NoVersionHeader, HandshakeError::NoVersionHeader,
verify_handshake(&req).err().unwrap() verify_handshake(req.head()).err().unwrap()
); );
let req = TestRequest::default() let req = TestRequest::default()
@@ -254,7 +251,7 @@ mod tests {
.finish(); .finish();
assert_eq!( assert_eq!(
HandshakeError::UnsupportedVersion, HandshakeError::UnsupportedVersion,
verify_handshake(&req).err().unwrap() verify_handshake(req.head()).err().unwrap()
); );
let req = TestRequest::default() let req = TestRequest::default()
@@ -273,7 +270,7 @@ mod tests {
.finish(); .finish();
assert_eq!( assert_eq!(
HandshakeError::BadWebsocketKey, HandshakeError::BadWebsocketKey,
verify_handshake(&req).err().unwrap() verify_handshake(req.head()).err().unwrap()
); );
let req = TestRequest::default() let req = TestRequest::default()
@@ -296,7 +293,7 @@ mod tests {
.finish(); .finish();
assert_eq!( assert_eq!(
StatusCode::SWITCHING_PROTOCOLS, StatusCode::SWITCHING_PROTOCOLS,
handshake_response(&req).finish().status() handshake_response(req.head()).finish().status()
); );
} }

View File

@@ -1,52 +0,0 @@
use std::marker::PhantomData;
use actix_codec::Framed;
use actix_service::{NewService, Service};
use futures::future::{ok, FutureResult};
use futures::{Async, IntoFuture, Poll};
use crate::h1::Codec;
use crate::request::Request;
use super::{verify_handshake, HandshakeError};
pub struct VerifyWebSockets<T> {
_t: PhantomData<T>,
}
impl<T> Default for VerifyWebSockets<T> {
fn default() -> Self {
VerifyWebSockets { _t: PhantomData }
}
}
impl<T> NewService for VerifyWebSockets<T> {
type Request = (Request, Framed<T, Codec>);
type Response = (Request, Framed<T, Codec>);
type Error = (HandshakeError, Framed<T, Codec>);
type InitError = ();
type Service = VerifyWebSockets<T>;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &()) -> Self::Future {
ok(VerifyWebSockets { _t: PhantomData })
}
}
impl<T> Service for VerifyWebSockets<T> {
type Request = (Request, Framed<T, Codec>);
type Response = (Request, Framed<T, Codec>);
type Error = (HandshakeError, Framed<T, Codec>);
type Future = FutureResult<Self::Response, Self::Error>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, (req, framed): (Request, Framed<T, Codec>)) -> Self::Future {
match verify_handshake(&req) {
Err(e) => Err((e, framed)).into_future(),
Ok(_) => Ok((req, framed)).into_future(),
}
}
}

View File

@@ -31,9 +31,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
fn test_h1_v2() { fn test_h1_v2() {
env_logger::init(); env_logger::init();
let mut srv = TestServer::new(move || { let mut srv = TestServer::new(move || {
HttpService::build() HttpService::build().finish(|_| future::ok::<_, ()>(Response::Ok().body(STR)))
.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()); assert!(response.status().is_success());

View File

@@ -5,10 +5,11 @@ use std::{net, thread};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_http_test::TestServer; use actix_http_test::TestServer;
use actix_server_config::ServerConfig; use actix_server_config::ServerConfig;
use actix_service::{fn_cfg_factory, NewService}; use actix_service::{fn_cfg_factory, fn_service, NewService};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::future::{self, ok, Future}; use futures::future::{self, ok, Future};
use futures::stream::{once, Stream}; use futures::stream::{once, Stream};
use tokio_timer::sleep;
use actix_http::body::Body; use actix_http::body::Body;
use actix_http::error::PayloadError; use actix_http::error::PayloadError;
@@ -34,7 +35,10 @@ fn test_h1() {
.keep_alive(KeepAlive::Disabled) .keep_alive(KeepAlive::Disabled)
.client_timeout(1000) .client_timeout(1000)
.client_disconnect(1000) .client_disconnect(1000)
.h1(|_| future::ok::<_, ()>(Response::Ok().finish())) .h1(|req: Request| {
assert!(req.peer_addr().is_some());
future::ok::<_, ()>(Response::Ok().finish())
})
}); });
let response = srv.block_on(srv.get("/").send()).unwrap(); let response = srv.block_on(srv.get("/").send()).unwrap();
@@ -49,6 +53,7 @@ fn test_h1_2() {
.client_timeout(1000) .client_timeout(1000)
.client_disconnect(1000) .client_disconnect(1000)
.finish(|req: Request| { .finish(|req: Request| {
assert!(req.peer_addr().is_some());
assert_eq!(req.version(), http::Version::HTTP_11); assert_eq!(req.version(), http::Version::HTTP_11);
future::ok::<_, ()>(Response::Ok().finish()) future::ok::<_, ()>(Response::Ok().finish())
}) })
@@ -114,6 +119,7 @@ fn test_h2_1() -> std::io::Result<()> {
.and_then( .and_then(
HttpService::build() HttpService::build()
.finish(|req: Request| { .finish(|req: Request| {
assert!(req.peer_addr().is_some());
assert_eq!(req.version(), http::Version::HTTP_2); assert_eq!(req.version(), http::Version::HTTP_2);
future::ok::<_, Error>(Response::Ok().finish()) future::ok::<_, Error>(Response::Ok().finish())
}) })
@@ -153,6 +159,62 @@ fn test_h2_body() -> std::io::Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_expect_continue() {
let srv = TestServer::new(|| {
HttpService::build()
.expect(fn_service(|req: Request| {
if req.head().uri.query() == Some("yes=") {
Ok(req)
} else {
Err(error::ErrorPreconditionFailed("error"))
}
}))
.finish(|_| future::ok::<_, ()>(Response::Ok().finish()))
});
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n");
let mut data = String::new();
let _ = stream.read_to_string(&mut data);
assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length"));
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n");
let mut data = String::new();
let _ = stream.read_to_string(&mut data);
assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n"));
}
#[test]
fn test_expect_continue_h1() {
let srv = TestServer::new(|| {
HttpService::build()
.expect(fn_service(|req: Request| {
sleep(Duration::from_millis(20)).then(move |_| {
if req.head().uri.query() == Some("yes=") {
Ok(req)
} else {
Err(error::ErrorPreconditionFailed("error"))
}
})
}))
.h1(|_| future::ok::<_, ()>(Response::Ok().finish()))
});
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n");
let mut data = String::new();
let _ = stream.read_to_string(&mut data);
assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length"));
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n");
let mut data = String::new();
let _ = stream.read_to_string(&mut data);
assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n"));
}
#[test] #[test]
fn test_slow_request() { fn test_slow_request() {
let srv = TestServer::new(|| { let srv = TestServer::new(|| {

View File

@@ -0,0 +1,76 @@
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::{body, h1, ws, Error, HttpService, Request, Response};
use actix_http_test::TestServer;
use actix_utils::framed::FramedTransport;
use bytes::{Bytes, BytesMut};
use futures::future::{self, ok};
use futures::{Future, Sink, Stream};
fn ws_service<T: AsyncRead + AsyncWrite>(
(req, framed): (Request, Framed<T, h1::Codec>),
) -> impl Future<Item = (), Error = Error> {
let res = ws::handshake(req.head()).unwrap().message_body(());
framed
.send((res, body::BodySize::None).into())
.map_err(|_| panic!())
.and_then(|framed| {
FramedTransport::new(framed.into_framed(ws::Codec::new()), service)
.map_err(|_| panic!())
})
}
fn service(msg: ws::Frame) -> impl Future<Item = ws::Message, Error = Error> {
let msg = match msg {
ws::Frame::Ping(msg) => ws::Message::Pong(msg),
ws::Frame::Text(text) => {
ws::Message::Text(String::from_utf8_lossy(&text.unwrap()).to_string())
}
ws::Frame::Binary(bin) => ws::Message::Binary(bin.unwrap().freeze()),
ws::Frame::Close(reason) => ws::Message::Close(reason),
_ => panic!(),
};
ok(msg)
}
#[test]
fn test_simple() {
let mut srv = TestServer::new(|| {
HttpService::build()
.upgrade(ws_service)
.finish(|_| future::ok::<_, ()>(Response::NotFound()))
});
// client service
let framed = srv.ws().unwrap();
let framed = srv
.block_on(framed.send(ws::Message::Text("text".to_string())))
.unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text")))));
let framed = srv
.block_on(framed.send(ws::Message::Binary("text".into())))
.unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(
item,
Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into())))
);
let framed = srv
.block_on(framed.send(ws::Message::Ping("text".into())))
.unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into())));
let framed = srv
.block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))))
.unwrap();
let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(
item,
Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into())))
);
}

View File

@@ -2,4 +2,6 @@
## [0.1.0-alpha.1] - 2019-04-xx ## [0.1.0-alpha.1] - 2019-04-xx
* Do not support nested multipart
* Split multipart support to separate crate * Split multipart support to separate crate

View File

@@ -18,7 +18,7 @@ name = "actix_multipart"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = "1.0.0-alpha.3" actix-web = "1.0.0-alpha.6"
actix-service = "0.3.4" actix-service = "0.3.4"
bytes = "0.4" bytes = "0.4"
derive_more = "0.14" derive_more = "0.14"
@@ -31,4 +31,4 @@ twoway = "0.2"
[dev-dependencies] [dev-dependencies]
actix-rt = "0.2.2" actix-rt = "0.2.2"
actix-http = "0.1.0-alpha.3" actix-http = "0.1.0"

View File

@@ -16,6 +16,9 @@ pub enum MultipartError {
/// Multipart boundary is not found /// Multipart boundary is not found
#[display(fmt = "Multipart boundary is not found")] #[display(fmt = "Multipart boundary is not found")]
Boundary, Boundary,
/// Nested multipart is not supported
#[display(fmt = "Nested multipart is not supported")]
Nested,
/// Multipart stream is incomplete /// Multipart stream is incomplete
#[display(fmt = "Multipart stream is incomplete")] #[display(fmt = "Multipart stream is incomplete")]
Incomplete, Incomplete,

View File

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

View File

@@ -3,4 +3,4 @@ mod extractor;
mod server; mod server;
pub use self::error::MultipartError; pub use self::error::MultipartError;
pub use self::server::{Field, Item, Multipart}; pub use self::server::{Field, Multipart};

View File

@@ -32,18 +32,9 @@ pub struct Multipart {
inner: Option<Rc<RefCell<InnerMultipart>>>, inner: Option<Rc<RefCell<InnerMultipart>>>,
} }
/// Multipart item
pub enum Item {
/// Multipart field
Field(Field),
/// Nested multipart stream
Nested(Multipart),
}
enum InnerMultipartItem { enum InnerMultipartItem {
None, None,
Field(Rc<RefCell<InnerField>>), Field(Rc<RefCell<InnerField>>),
Multipart(Rc<RefCell<InnerMultipart>>),
} }
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@@ -113,7 +104,7 @@ impl Multipart {
} }
impl Stream for Multipart { impl Stream for Multipart {
type Item = Item; type Item = Field;
type Error = MultipartError; type Error = MultipartError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
@@ -245,7 +236,7 @@ impl InnerMultipart {
Ok(Some(eof)) Ok(Some(eof))
} }
fn poll(&mut self, safety: &Safety) -> Poll<Option<Item>, MultipartError> { fn poll(&mut self, safety: &Safety) -> Poll<Option<Field>, MultipartError> {
if self.state == InnerState::Eof { if self.state == InnerState::Eof {
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} else { } else {
@@ -262,14 +253,7 @@ impl InnerMultipart {
Async::Ready(None) => true, Async::Ready(None) => true,
} }
} }
InnerMultipartItem::Multipart(ref mut multipart) => { InnerMultipartItem::None => false,
match multipart.borrow_mut().poll(safety)? {
Async::NotReady => return Ok(Async::NotReady),
Async::Ready(Some(_)) => continue,
Async::Ready(None) => true,
}
}
_ => false,
}; };
if stop { if stop {
self.item = InnerMultipartItem::None; self.item = InnerMultipartItem::None;
@@ -346,24 +330,7 @@ impl InnerMultipart {
// nested multipart stream // nested multipart stream
if mt.type_() == mime::MULTIPART { if mt.type_() == mime::MULTIPART {
let inner = if let Some(boundary) = mt.get_param(mime::BOUNDARY) { Err(MultipartError::Nested)
Rc::new(RefCell::new(InnerMultipart {
payload: self.payload.clone(),
boundary: boundary.as_str().to_owned(),
state: InnerState::FirstBoundary,
item: InnerMultipartItem::None,
}))
} else {
return Err(MultipartError::Boundary);
};
self.item = InnerMultipartItem::Multipart(Rc::clone(&inner));
Ok(Async::Ready(Some(Item::Nested(Multipart {
safety: safety.clone(),
error: None,
inner: Some(inner),
}))))
} else { } else {
let field = Rc::new(RefCell::new(InnerField::new( let field = Rc::new(RefCell::new(InnerField::new(
self.payload.clone(), self.payload.clone(),
@@ -372,12 +339,12 @@ impl InnerMultipart {
)?)); )?));
self.item = InnerMultipartItem::Field(Rc::clone(&field)); self.item = InnerMultipartItem::Field(Rc::clone(&field));
Ok(Async::Ready(Some(Item::Field(Field::new( Ok(Async::Ready(Some(Field::new(
safety.clone(), safety.clone(),
headers, headers,
mt, mt,
field, field,
))))) ))))
} }
} }
} }
@@ -864,16 +831,11 @@ mod tests {
let mut multipart = Multipart::new(&headers, payload); let mut multipart = Multipart::new(&headers, payload);
match multipart.poll().unwrap() { match multipart.poll().unwrap() {
Async::Ready(Some(item)) => match item { Async::Ready(Some(mut field)) => {
Item::Field(mut field) => {
{
let cd = field.content_disposition().unwrap(); let cd = field.content_disposition().unwrap();
assert_eq!(cd.disposition, DispositionType::FormData); assert_eq!(cd.disposition, DispositionType::FormData);
assert_eq!( assert_eq!(cd.parameters[0], DispositionParam::Name("file".into()));
cd.parameters[0],
DispositionParam::Name("file".into())
);
}
assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().type_(), mime::TEXT);
assert_eq!(field.content_type().subtype(), mime::PLAIN); assert_eq!(field.content_type().subtype(), mime::PLAIN);
@@ -887,13 +849,10 @@ mod tests {
} }
} }
_ => unreachable!(), _ => unreachable!(),
},
_ => unreachable!(),
} }
match multipart.poll().unwrap() { match multipart.poll().unwrap() {
Async::Ready(Some(item)) => match item { Async::Ready(Some(mut field)) => {
Item::Field(mut field) => {
assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().type_(), mime::TEXT);
assert_eq!(field.content_type().subtype(), mime::PLAIN); assert_eq!(field.content_type().subtype(), mime::PLAIN);
@@ -907,8 +866,6 @@ mod tests {
} }
} }
_ => unreachable!(), _ => unreachable!(),
},
_ => unreachable!(),
} }
match multipart.poll().unwrap() { match multipart.poll().unwrap() {

View File

@@ -1,5 +1,9 @@
# Changes # Changes
## [0.1.0-alpha.6] - 2019-04-14
* Update actix-web alpha.6
## [0.1.0-alpha.4] - 2019-04-08 ## [0.1.0-alpha.4] - 2019-04-08
* Update actix-web * Update actix-web

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-session" name = "actix-session"
version = "0.1.0-alpha.4" version = "0.1.0-alpha.6"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Session for actix web framework." description = "Session for actix web framework."
readme = "README.md" readme = "README.md"
@@ -24,12 +24,12 @@ default = ["cookie-session"]
cookie-session = ["actix-web/secure-cookies"] cookie-session = ["actix-web/secure-cookies"]
[dependencies] [dependencies]
actix-web = "1.0.0-alpha.4" actix-web = "1.0.0-alpha.6"
actix-service = "0.3.4" actix-service = "0.3.4"
bytes = "0.4" bytes = "0.4"
derive_more = "0.14" derive_more = "0.14"
futures = "0.1.25" futures = "0.1.25"
hashbrown = "0.1.8" hashbrown = "0.2.0"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
time = "0.1" time = "0.1"

View File

@@ -120,7 +120,7 @@ impl CookieSessionInner {
Ok(()) Ok(())
} }
fn load<P>(&self, req: &ServiceRequest<P>) -> HashMap<String, String> { fn load(&self, req: &ServiceRequest) -> HashMap<String, String> {
if let Ok(cookies) = req.cookies() { if let Ok(cookies) = req.cookies() {
for cookie in cookies.iter() { for cookie in cookies.iter() {
if cookie.name() == self.name { if cookie.name() == self.name {
@@ -256,13 +256,13 @@ impl CookieSession {
} }
} }
impl<S, P, B: 'static> Transform<S> for CookieSession impl<S, B: 'static> Transform<S> for CookieSession
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static, S::Error: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = S::Error; type Error = S::Error;
type InitError = (); type InitError = ();
@@ -283,23 +283,22 @@ pub struct CookieSessionMiddleware<S> {
inner: Rc<CookieSessionInner>, inner: Rc<CookieSessionInner>,
} }
impl<S, P, B: 'static> Service for CookieSessionMiddleware<S> impl<S, B: 'static> Service for CookieSessionMiddleware<S>
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static, S::Error: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = S::Error; type Error = S::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>; type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
//self.service.poll_ready().map_err(|e| e.into())
self.service.poll_ready() self.service.poll_ready()
} }
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let inner = self.inner.clone(); let inner = self.inner.clone();
let state = self.inner.load(&req); let state = self.inner.load(&req);
Session::set_session(state.into_iter(), &mut req); Session::set_session(state.into_iter(), &mut req);

View File

@@ -119,9 +119,9 @@ impl Session {
inner.state.clear() inner.state.clear()
} }
pub fn set_session<P>( pub fn set_session(
data: impl Iterator<Item = (String, String)>, data: impl Iterator<Item = (String, String)>,
req: &mut ServiceRequest<P>, req: &mut ServiceRequest,
) { ) {
let session = Session::get_session(&mut *req.extensions_mut()); let session = Session::get_session(&mut *req.extensions_mut());
let mut inner = session.0.borrow_mut(); let mut inner = session.0.borrow_mut();
@@ -172,12 +172,13 @@ impl Session {
/// } /// }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
impl<P> FromRequest<P> for Session { impl FromRequest for Session {
type Error = Error; type Error = Error;
type Future = Result<Session, Error>; type Future = Result<Session, Error>;
type Config = ();
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
Ok(Session::get_session(&mut *req.extensions_mut())) Ok(Session::get_session(&mut *req.extensions_mut()))
} }
} }

View File

@@ -18,13 +18,13 @@ name = "actix_web_actors"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix = "0.8.0-alpha.2" actix = "0.8.0"
actix-web = "1.0.0-alpha.3" actix-web = "1.0.0-alpha.5"
actix-http = "0.1.0-alpha.3" actix-http = "0.1.0"
actix-codec = "0.1.2" actix-codec = "0.1.2"
bytes = "0.4" bytes = "0.4"
futures = "0.1.25" futures = "0.1.25"
[dev-dependencies] [dev-dependencies]
env_logger = "0.6" env_logger = "0.6"
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] }

View File

@@ -199,7 +199,7 @@ mod tests {
use actix::Actor; use actix::Actor;
use actix_web::http::StatusCode; use actix_web::http::StatusCode;
use actix_web::test::{block_on, call_success, init_service, TestRequest}; use actix_web::test::{block_on, call_service, init_service, TestRequest};
use actix_web::{web, App, HttpResponse}; use actix_web::{web, App, HttpResponse};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
@@ -237,7 +237,7 @@ mod tests {
}))); })));
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let mut resp = call_success(&mut srv, req); let mut resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let body = block_on(resp.take_body().fold( let body = block_on(resp.take_body().fold(

View File

@@ -1,5 +1,9 @@
# Changes # Changes
## [0.1.0-alpha.6] - 2019-04-14
* Gen code for actix-web 1.0.0-alpha.6
## [0.1.0-alpha.1] - 2019-03-28 ## [0.1.0-alpha.1] - 2019-03-28
* Initial impl * Initial impl

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-web-codegen" name = "actix-web-codegen"
version = "0.1.0-alpha.1" version = "0.1.0-alpha.6"
description = "Actix web proc macros" description = "Actix web proc macros"
readme = "README.md" readme = "README.md"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
@@ -16,6 +16,7 @@ quote = "0.6"
syn = { version = "0.15", features = ["full", "parsing"] } syn = { version = "0.15", features = ["full", "parsing"] }
[dev-dependencies] [dev-dependencies]
actix-web = { version = "1.0.0-alpha.2" } actix-web = { version = "1.0.0-alpha.6" }
actix-http = { version = "0.1.0-alpha.2", features=["ssl"] } actix-http = { version = "0.1.0", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] }
futures = { version = "0.1" }

View File

@@ -1,118 +1,94 @@
#![recursion_limit = "512"] #![recursion_limit = "512"]
//! Actix-web codegen module
//!
//! Generators for routes and scopes
//!
//! ## Route
//!
//! Macros:
//!
//! - [get](attr.get.html)
//! - [post](attr.post.html)
//! - [put](attr.put.html)
//! - [delete](attr.delete.html)
//!
//! ### Attributes:
//!
//! - `"path"` - Raw literal string with path for which to register handle. Mandatory.
//! - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard`
//!
//! ## Notes
//!
//! Function name can be specified as any expression that is going to be accessible to the generate
//! code (e.g `my_guard` or `my_module::my_guard`)
//!
//! ## Example:
//!
//! ```rust
//! use actix_web::HttpResponse;
//! use actix_web_codegen::get;
//! use futures::{future, Future};
//!
//! #[get("/test")]
//! fn async_test() -> impl Future<Item=HttpResponse, Error=actix_web::Error> {
//! future::ok(HttpResponse::Ok().finish())
//! }
//! ```
extern crate proc_macro; extern crate proc_macro;
mod route;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote;
use syn::parse_macro_input; use syn::parse_macro_input;
/// #[get("path")] attribute /// Creates route handler with `GET` method guard.
///
/// Syntax: `#[get("path"[, attributes])]`
///
/// ## Attributes:
///
/// - `"path"` - Raw literal string with path for which to register handler. Mandatory.
/// - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard`
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn get(args: TokenStream, input: TokenStream) -> TokenStream { pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
if args.is_empty() { let gen = route::Args::new(&args, input, route::GuardType::Get);
panic!("invalid server definition, expected: #[get(\"some path\")]"); gen.generate()
} }
// path /// Creates route handler with `POST` method guard.
let path = match args[0] { ///
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => { /// Syntax: `#[post("path"[, attributes])]`
let fname = quote!(#fname).to_string(); ///
fname.as_str()[1..fname.len() - 1].to_owned() /// Attributes are the same as in [get](attr.get.html)
}
_ => panic!("resource path"),
};
let ast: syn::ItemFn = syn::parse(input).unwrap();
let name = ast.ident.clone();
(quote! {
#[allow(non_camel_case_types)]
struct #name;
impl<P: 'static> actix_web::dev::HttpServiceFactory<P> for #name {
fn register(self, config: &mut actix_web::dev::ServiceConfig<P>) {
#ast
actix_web::dev::HttpServiceFactory::register(
actix_web::Resource::new(#path)
.guard(actix_web::guard::Get())
.to(#name), config);
}
}
})
.into()
}
/// #[post("path")] attribute
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn post(args: TokenStream, input: TokenStream) -> TokenStream { pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
if args.is_empty() { let gen = route::Args::new(&args, input, route::GuardType::Post);
panic!("invalid server definition, expected: #[post(\"some path\")]"); gen.generate()
} }
// path /// Creates route handler with `PUT` method guard.
let path = match args[0] { ///
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => { /// Syntax: `#[put("path"[, attributes])]`
let fname = quote!(#fname).to_string(); ///
fname.as_str()[1..fname.len() - 1].to_owned() /// Attributes are the same as in [get](attr.get.html)
}
_ => panic!("resource path"),
};
let ast: syn::ItemFn = syn::parse(input).unwrap();
let name = ast.ident.clone();
(quote! {
#[allow(non_camel_case_types)]
struct #name;
impl<P: 'static> actix_web::dev::HttpServiceFactory<P> for #name {
fn register(self, config: &mut actix_web::dev::ServiceConfig<P>) {
#ast
actix_web::dev::HttpServiceFactory::register(
actix_web::Resource::new(#path)
.guard(actix_web::guard::Post())
.to(#name), config);
}
}
})
.into()
}
/// #[put("path")] attribute
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn put(args: TokenStream, input: TokenStream) -> TokenStream { pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
if args.is_empty() { let gen = route::Args::new(&args, input, route::GuardType::Put);
panic!("invalid server definition, expected: #[put(\"some path\")]"); gen.generate()
} }
// path /// Creates route handler with `DELETE` method guard.
let path = match args[0] { ///
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => { /// Syntax: `#[delete("path"[, attributes])]`
let fname = quote!(#fname).to_string(); ///
fname.as_str()[1..fname.len() - 1].to_owned() /// Attributes are the same as in [get](attr.get.html)
} #[proc_macro_attribute]
_ => panic!("resource path"), pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
}; let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Delete);
let ast: syn::ItemFn = syn::parse(input).unwrap(); gen.generate()
let name = ast.ident.clone();
(quote! {
#[allow(non_camel_case_types)]
struct #name;
impl<P: 'static> actix_web::dev::HttpServiceFactory<P> for #name {
fn register(self, config: &mut actix_web::dev::ServiceConfig<P>) {
#ast
actix_web::dev::HttpServiceFactory::register(
actix_web::Resource::new(#path)
.guard(actix_web::guard::Put())
.to(#name), config);
}
}
})
.into()
} }

View File

@@ -0,0 +1,184 @@
extern crate proc_macro;
use std::fmt;
use proc_macro::TokenStream;
use quote::quote;
enum ResourceType {
Async,
Sync,
}
impl fmt::Display for ResourceType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&ResourceType::Async => write!(f, "to_async"),
&ResourceType::Sync => write!(f, "to"),
}
}
}
#[derive(PartialEq)]
pub enum GuardType {
Get,
Post,
Put,
Delete,
}
impl fmt::Display for GuardType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&GuardType::Get => write!(f, "Get"),
&GuardType::Post => write!(f, "Post"),
&GuardType::Put => write!(f, "Put"),
&GuardType::Delete => write!(f, "Delete"),
}
}
}
pub struct Args {
name: syn::Ident,
path: String,
ast: syn::ItemFn,
resource_type: ResourceType,
pub guard: GuardType,
pub extra_guards: Vec<String>,
}
impl fmt::Display for Args {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let ast = &self.ast;
let guards = format!(".guard(actix_web::guard::{}())", self.guard);
let guards = self.extra_guards.iter().fold(guards, |acc, val| {
format!("{}.guard(actix_web::guard::fn_guard({}))", acc, val)
});
write!(
f,
"
#[allow(non_camel_case_types)]
pub struct {name};
impl actix_web::dev::HttpServiceFactory for {name} {{
fn register(self, config: &mut actix_web::dev::AppService) {{
{ast}
let resource = actix_web::Resource::new(\"{path}\"){guards}.{to}({name});
actix_web::dev::HttpServiceFactory::register(resource, config)
}}
}}",
name = self.name,
ast = quote!(#ast),
path = self.path,
guards = guards,
to = self.resource_type
)
}
}
fn guess_resource_type(typ: &syn::Type) -> ResourceType {
let mut guess = ResourceType::Sync;
match typ {
syn::Type::ImplTrait(typ) => {
for bound in typ.bounds.iter() {
match bound {
syn::TypeParamBound::Trait(bound) => {
for bound in bound.path.segments.iter() {
if bound.ident == "Future" {
guess = ResourceType::Async;
break;
} else if bound.ident == "Responder" {
guess = ResourceType::Sync;
break;
}
}
}
_ => (),
}
}
}
_ => (),
}
guess
}
impl Args {
pub fn new(
args: &Vec<syn::NestedMeta>,
input: TokenStream,
guard: GuardType,
) -> Self {
if args.is_empty() {
panic!(
"invalid server definition, expected: #[{}(\"some path\")]",
guard
);
}
let ast: syn::ItemFn = syn::parse(input).expect("Parse input as function");
let name = ast.ident.clone();
let mut extra_guards = Vec::new();
let mut path = None;
for arg in args {
match arg {
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
if path.is_some() {
panic!("Multiple paths specified! Should be only one!")
}
let fname = quote!(#fname).to_string();
path = Some(fname.as_str()[1..fname.len() - 1].to_owned())
}
syn::NestedMeta::Meta(syn::Meta::NameValue(ident)) => {
match ident.ident.to_string().to_lowercase().as_str() {
"guard" => match ident.lit {
syn::Lit::Str(ref text) => extra_guards.push(text.value()),
_ => panic!("Attribute guard expects literal string!"),
},
attr => panic!(
"Unknown attribute key is specified: {}. Allowed: guard",
attr
),
}
}
attr => panic!("Unknown attribute{:?}", attr),
}
}
let resource_type = if ast.asyncness.is_some() {
ResourceType::Async
} else {
match ast.decl.output {
syn::ReturnType::Default => {
panic!("Function {} has no return type. Cannot be used as handler")
}
syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()),
}
};
let path = path.unwrap();
Self {
name,
path,
ast,
resource_type,
guard,
extra_guards,
}
}
pub fn generate(&self) -> TokenStream {
let text = self.to_string();
match text.parse() {
Ok(res) => res,
Err(error) => panic!("Error: {:?}\nGenerated code: {}", error, text),
}
}
}

View File

@@ -1,15 +1,40 @@
use actix_http::HttpService; use actix_http::HttpService;
use actix_http_test::TestServer; use actix_http_test::TestServer;
use actix_web::{get, http, App, HttpResponse, Responder}; use actix_web::{http, App, HttpResponse, Responder};
use actix_web_codegen::get;
use futures::{future, Future};
#[get("/test")] #[get("/test")]
fn test() -> impl Responder { fn test() -> impl Responder {
HttpResponse::Ok() HttpResponse::Ok()
} }
#[get("/test")]
fn auto_async() -> impl Future<Item = HttpResponse, Error = actix_web::Error> {
future::ok(HttpResponse::Ok().finish())
}
#[get("/test")]
fn auto_sync() -> impl Future<Item = HttpResponse, Error = actix_web::Error> {
future::ok(HttpResponse::Ok().finish())
}
#[test] #[test]
fn test_body() { fn test_body() {
let mut srv = TestServer::new(|| HttpService::new(App::new().service(test))); let mut srv = TestServer::new(|| HttpService::new(App::new().service(test)));
let request = srv.request(http::Method::GET, srv.url("/test"));
let response = srv.block_on(request.send()).unwrap();
assert!(response.status().is_success());
let mut srv = TestServer::new(|| HttpService::new(App::new().service(auto_sync)));
let request = srv.request(http::Method::GET, srv.url("/test"));
let response = srv.block_on(request.send()).unwrap();
assert!(response.status().is_success());
}
#[test]
fn test_auto_async() {
let mut srv = TestServer::new(|| HttpService::new(App::new().service(auto_async)));
let request = srv.request(http::Method::GET, srv.url("/test")); let request = srv.request(http::Method::GET, srv.url("/test"));
let response = srv.block_on(request.send()).unwrap(); let response = srv.block_on(request.send()).unwrap();

View File

@@ -1,5 +1,28 @@
# Changes # Changes
## [0.1.0] - 2019-04-16
* No changes
## [0.1.0-alpha.6] - 2019-04-14
### Changed
* Do not set default headers for websocket request
## [0.1.0-alpha.5] - 2019-04-12
### Changed
* Do not set any default headers
### Added
* Add Debug impl for BoxedSocket
## [0.1.0-alpha.4] - 2019-04-08 ## [0.1.0-alpha.4] - 2019-04-08
### Changed ### Changed

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "awc" name = "awc"
version = "0.1.0-alpha.4" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http client." description = "Actix http client."
readme = "README.md" readme = "README.md"
@@ -36,9 +36,9 @@ flate2-zlib = ["actix-http/flate2-zlib"]
flate2-rust = ["actix-http/flate2-rust"] flate2-rust = ["actix-http/flate2-rust"]
[dependencies] [dependencies]
actix-codec = "0.1.1" actix-codec = "0.1.2"
actix-service = "0.3.4" actix-service = "0.3.6"
actix-http = "0.1.0-alpha.4" actix-http = "0.1.0"
base64 = "0.10.1" base64 = "0.10.1"
bytes = "0.4" bytes = "0.4"
derive_more = "0.14" derive_more = "0.14"
@@ -55,11 +55,11 @@ openssl = { version="0.10", optional = true }
[dev-dependencies] [dev-dependencies]
actix-rt = "0.2.2" actix-rt = "0.2.2"
actix-web = { version = "1.0.0-alpha.4", features=["ssl"] } actix-web = { version = "1.0.0-alpha.6", features=["ssl"] }
actix-http = { version = "0.1.0-alpha.4", features=["ssl"] } actix-http = { version = "0.1.0", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] }
actix-utils = "0.3.4" actix-utils = "0.3.4"
actix-server = { version = "0.4.1", features=["ssl"] } actix-server = { version = "0.4.3", features=["ssl"] }
brotli2 = { version="0.3.2" } brotli2 = { version="0.3.2" }
flate2 = { version="1.0.2" } flate2 = { version="1.0.2" }
env_logger = "0.6" env_logger = "0.6"

View File

@@ -1 +1,33 @@
# Actix http client [![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/awc)](https://crates.io/crates/awc) [![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) # Actix http client [![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/awc)](https://crates.io/crates/awc) [![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)
An HTTP Client
## Documentation & community resources
* [User Guide](https://actix.rs/docs/)
* [API Documentation](https://docs.rs/awc/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [awc](https://crates.io/crates/awc)
* Minimum supported Rust version: 1.33 or later
## Example
```rust
use actix_rt::System;
use awc::Client;
use futures::future::{Future, lazy};
fn main() {
System::new("test").block_on(lazy(|| {
let mut client = Client::default();
client.get("http://www.rust-lang.org") // <- Create request builder
.header("User-Agent", "Actix-web")
.send() // <- Send http request
.and_then(|response| { // <- server http response
println!("Response: {:?}", response);
Ok(())
})
}));
}
```

View File

@@ -1,4 +1,4 @@
use std::io; use std::{fmt, io};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::body::Body; use actix_http::body::Body;
@@ -102,6 +102,12 @@ impl<T: AsyncRead + AsyncWrite> AsyncSocket for Socket<T> {
pub struct BoxedSocket(Box<dyn AsyncSocket>); pub struct BoxedSocket(Box<dyn AsyncSocket>);
impl fmt::Debug for BoxedSocket {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "BoxedSocket")
}
}
impl io::Read for BoxedSocket { impl io::Read for BoxedSocket {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.as_read_mut().read(buf) self.0.as_read_mut().read(buf)

View File

@@ -61,7 +61,6 @@ pub struct ClientRequest {
pub(crate) head: RequestHead, pub(crate) head: RequestHead,
err: Option<HttpError>, err: Option<HttpError>,
cookies: Option<CookieJar>, cookies: Option<CookieJar>,
default_headers: bool,
response_decompress: bool, response_decompress: bool,
timeout: Option<Duration>, timeout: Option<Duration>,
config: Rc<ClientConfig>, config: Rc<ClientConfig>,
@@ -79,7 +78,6 @@ impl ClientRequest {
err: None, err: None,
cookies: None, cookies: None,
timeout: None, timeout: None,
default_headers: true,
response_decompress: true, response_decompress: true,
} }
.method(method) .method(method)
@@ -316,13 +314,6 @@ impl ClientRequest {
self self
} }
/// Do not add default request headers.
/// By default `Date` and `User-Agent` headers are set.
pub fn no_default_headers(mut self) -> Self {
self.default_headers = false;
self
}
/// Disable automatic decompress of response's body /// Disable automatic decompress of response's body
pub fn no_decompress(mut self) -> Self { pub fn no_decompress(mut self) -> Self {
self.response_decompress = false; self.response_decompress = false;
@@ -392,36 +383,6 @@ impl ClientRequest {
return Either::A(err(InvalidUrl::UnknownScheme.into())); return Either::A(err(InvalidUrl::UnknownScheme.into()));
} }
// set 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) {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match self.head.uri.port_u16() {
None | Some(80) | Some(443) => write!(wrt, "{}", host),
Some(port) => write!(wrt, "{}:{}", host, port),
};
match wrt.get_mut().take().freeze().try_into() {
Ok(value) => {
self.head.headers.insert(header::HOST, value);
}
Err(e) => return Either::A(err(HttpError::from(e).into())),
}
}
}
// user agent
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 // set cookies
if let Some(ref mut jar) = self.cookies { if let Some(ref mut jar) = self.cookies {
let mut cookie = String::new(); let mut cookie = String::new();
@@ -436,7 +397,7 @@ impl ClientRequest {
); );
} }
let slf = self; let mut slf = self;
// enable br only for https // enable br only for https
#[cfg(any( #[cfg(any(
@@ -444,7 +405,8 @@ impl ClientRequest {
feature = "flate2-zlib", feature = "flate2-zlib",
feature = "flate2-rust" feature = "flate2-rust"
))] ))]
let slf = { {
if slf.response_decompress {
let https = slf let https = slf
.head .head
.uri .uri
@@ -453,16 +415,16 @@ impl ClientRequest {
.unwrap_or(true); .unwrap_or(true);
if https { if https {
slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING) slf = slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING)
} else { } else {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
{ {
slf.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") slf = slf
} .set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate")
#[cfg(not(any(feature = "flate2-zlib", feature = "flate2-rust")))]
slf
} }
}; };
}
}
let head = slf.head; let head = slf.head;
let config = slf.config.as_ref(); let config = slf.config.as_ref();
@@ -582,22 +544,36 @@ impl fmt::Debug for ClientRequest {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::time::SystemTime;
use super::*; use super::*;
use crate::{test, Client}; use crate::Client;
#[test] #[test]
fn test_debug() { fn test_debug() {
test::run_on(|| {
let request = Client::new().get("/").header("x-test", "111"); let request = Client::new().get("/").header("x-test", "111");
let repr = format!("{:?}", request); let repr = format!("{:?}", request);
assert!(repr.contains("ClientRequest")); assert!(repr.contains("ClientRequest"));
assert!(repr.contains("x-test")); assert!(repr.contains("x-test"));
}) }
#[test]
fn test_basics() {
let mut req = Client::new()
.put("/")
.version(Version::HTTP_2)
.set(header::Date(SystemTime::now().into()))
.content_type("plain/text")
.content_length(100);
assert!(req.headers().contains_key(header::CONTENT_TYPE));
assert!(req.headers().contains_key(header::DATE));
assert_eq!(req.head.version, Version::HTTP_2);
let _ = req.headers_mut();
let _ = req.send_body("");
} }
#[test] #[test]
fn test_client_header() { fn test_client_header() {
test::run_on(|| {
let req = Client::build() let req = Client::build()
.header(header::CONTENT_TYPE, "111") .header(header::CONTENT_TYPE, "111")
.finish() .finish()
@@ -612,12 +588,10 @@ mod tests {
.unwrap(), .unwrap(),
"111" "111"
); );
})
} }
#[test] #[test]
fn test_client_header_override() { fn test_client_header_override() {
test::run_on(|| {
let req = Client::build() let req = Client::build()
.header(header::CONTENT_TYPE, "111") .header(header::CONTENT_TYPE, "111")
.finish() .finish()
@@ -633,12 +607,10 @@ mod tests {
.unwrap(), .unwrap(),
"222" "222"
); );
})
} }
#[test] #[test]
fn client_basic_auth() { fn client_basic_auth() {
test::run_on(|| {
let req = Client::new() let req = Client::new()
.get("/") .get("/")
.basic_auth("username", Some("password")); .basic_auth("username", Some("password"));
@@ -662,12 +634,10 @@ mod tests {
.unwrap(), .unwrap(),
"Basic dXNlcm5hbWU=" "Basic dXNlcm5hbWU="
); );
});
} }
#[test] #[test]
fn client_bearer_auth() { fn client_bearer_auth() {
test::run_on(|| {
let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n"); let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n");
assert_eq!( assert_eq!(
req.head req.head
@@ -678,6 +648,5 @@ mod tests {
.unwrap(), .unwrap(),
"Bearer someS3cr3tAutht0k3n" "Bearer someS3cr3tAutht0k3n"
); );
})
} }
} }

View File

@@ -134,3 +134,23 @@ impl TestResponse {
} }
} }
} }
#[cfg(test)]
mod tests {
use std::time::SystemTime;
use super::*;
use crate::{cookie, http::header};
#[test]
fn test_basics() {
let res = TestResponse::default()
.version(Version::HTTP_2)
.set(header::Date(SystemTime::now().into()))
.cookie(cookie::Cookie::build("name", "value").finish())
.finish();
assert!(res.headers().contains_key(header::SET_COOKIE));
assert!(res.headers().contains_key(header::DATE));
assert_eq!(res.version(), Version::HTTP_2);
}
}

View File

@@ -1,13 +1,11 @@
//! Websockets client //! Websockets client
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
use std::io::Write;
use std::rc::Rc; use std::rc::Rc;
use std::{fmt, str}; use std::{fmt, str};
use actix_codec::Framed; use actix_codec::Framed;
use actix_http::cookie::{Cookie, CookieJar}; use actix_http::cookie::{Cookie, CookieJar};
use actix_http::{ws, Payload, RequestHead}; use actix_http::{ws, Payload, RequestHead};
use bytes::{BufMut, BytesMut};
use futures::future::{err, Either, Future}; use futures::future::{err, Either, Future};
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use tokio_timer::Timeout; use tokio_timer::Timeout;
@@ -33,7 +31,6 @@ pub struct WebsocketsRequest {
protocols: Option<String>, protocols: Option<String>,
max_size: usize, max_size: usize,
server_mode: bool, server_mode: bool,
default_headers: bool,
cookies: Option<CookieJar>, cookies: Option<CookieJar>,
config: Rc<ClientConfig>, config: Rc<ClientConfig>,
} }
@@ -63,7 +60,6 @@ impl WebsocketsRequest {
max_size: 65_536, max_size: 65_536,
server_mode: false, server_mode: false,
cookies: None, cookies: None,
default_headers: true,
} }
} }
@@ -119,13 +115,6 @@ impl WebsocketsRequest {
self self
} }
/// Do not add default request headers.
/// By default `Date` and `User-Agent` headers are set.
pub fn no_default_headers(mut self) -> Self {
self.default_headers = false;
self
}
/// Append a header. /// Append a header.
/// ///
/// Header gets appended to existing header. /// Header gets appended to existing header.
@@ -188,10 +177,9 @@ impl WebsocketsRequest {
} }
/// Set HTTP basic authorization header /// Set HTTP basic authorization header
pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> Self pub fn basic_auth<U>(self, username: U, password: Option<&str>) -> Self
where where
U: fmt::Display, U: fmt::Display,
P: fmt::Display,
{ {
let auth = match password { let auth = match password {
Some(password) => format!("{}:{}", username, password), Some(password) => format!("{}:{}", username, password),
@@ -232,67 +220,36 @@ impl WebsocketsRequest {
return Either::A(err(InvalidUrl::UnknownScheme.into())); return Either::A(err(InvalidUrl::UnknownScheme.into()));
} }
// set default headers
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) {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match self.head.uri.port_u16() {
None | Some(80) | Some(443) => write!(wrt, "{}", host),
Some(port) => write!(wrt, "{}:{}", host, port),
};
match wrt.get_mut().take().freeze().try_into() {
Ok(value) => {
self.head.headers.insert(header::HOST, value);
}
Err(e) => return Either::A(err(HttpError::from(e).into())),
}
}
}
// user agent
self.set_header_if_none(
header::USER_AGENT,
concat!("awc/", env!("CARGO_PKG_VERSION")),
)
} else {
self
};
let mut head = slf.head;
// set cookies // set cookies
if let Some(ref mut jar) = slf.cookies { if let Some(ref mut jar) = self.cookies {
let mut cookie = String::new(); let mut cookie = String::new();
for c in jar.delta() { for c in jar.delta() {
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
let _ = write!(&mut cookie, "; {}={}", name, value); let _ = write!(&mut cookie, "; {}={}", name, value);
} }
head.headers.insert( self.head.headers.insert(
header::COOKIE, header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
); );
} }
// origin // origin
if let Some(origin) = slf.origin.take() { if let Some(origin) = self.origin.take() {
head.headers.insert(header::ORIGIN, origin); self.head.headers.insert(header::ORIGIN, origin);
} }
head.set_connection_type(ConnectionType::Upgrade); self.head.set_connection_type(ConnectionType::Upgrade);
head.headers self.head
.headers
.insert(header::UPGRADE, HeaderValue::from_static("websocket")); .insert(header::UPGRADE, HeaderValue::from_static("websocket"));
head.headers.insert( self.head.headers.insert(
header::SEC_WEBSOCKET_VERSION, header::SEC_WEBSOCKET_VERSION,
HeaderValue::from_static("13"), HeaderValue::from_static("13"),
); );
if let Some(protocols) = slf.protocols.take() { if let Some(protocols) = self.protocols.take() {
head.headers.insert( self.head.headers.insert(
header::SEC_WEBSOCKET_PROTOCOL, header::SEC_WEBSOCKET_PROTOCOL,
HeaderValue::try_from(protocols.as_str()).unwrap(), HeaderValue::try_from(protocols.as_str()).unwrap(),
); );
@@ -304,15 +261,16 @@ impl WebsocketsRequest {
let sec_key: [u8; 16] = rand::random(); let sec_key: [u8; 16] = rand::random();
let key = base64::encode(&sec_key); let key = base64::encode(&sec_key);
head.headers.insert( self.head.headers.insert(
header::SEC_WEBSOCKET_KEY, header::SEC_WEBSOCKET_KEY,
HeaderValue::try_from(key.as_str()).unwrap(), HeaderValue::try_from(key.as_str()).unwrap(),
); );
let max_size = slf.max_size; let head = self.head;
let server_mode = slf.server_mode; let max_size = self.max_size;
let server_mode = self.server_mode;
let fut = slf let fut = self
.config .config
.connector .connector
.borrow_mut() .borrow_mut()
@@ -387,7 +345,7 @@ impl WebsocketsRequest {
}); });
// set request timeout // set request timeout
if let Some(timeout) = slf.config.timeout { if let Some(timeout) = self.config.timeout {
Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| { Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| {
if let Some(e) = e.into_inner() { if let Some(e) = e.into_inner() {
e e
@@ -400,3 +358,117 @@ impl WebsocketsRequest {
} }
} }
} }
impl fmt::Debug for WebsocketsRequest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
"\nWebsocketsRequest {}:{}",
self.head.method, self.head.uri
)?;
writeln!(f, " headers:")?;
for (key, val) in self.head.headers.iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Client;
#[test]
fn test_debug() {
let request = Client::new().ws("/").header("x-test", "111");
let repr = format!("{:?}", request);
assert!(repr.contains("WebsocketsRequest"));
assert!(repr.contains("x-test"));
}
#[test]
fn test_header_override() {
let req = Client::build()
.header(header::CONTENT_TYPE, "111")
.finish()
.ws("/")
.set_header(header::CONTENT_TYPE, "222");
assert_eq!(
req.head
.headers
.get(header::CONTENT_TYPE)
.unwrap()
.to_str()
.unwrap(),
"222"
);
}
#[test]
fn basic_auth() {
let req = Client::new()
.ws("/")
.basic_auth("username", Some("password"));
assert_eq!(
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
.unwrap(),
"Basic dXNlcm5hbWU6cGFzc3dvcmQ="
);
let req = Client::new().ws("/").basic_auth("username", None);
assert_eq!(
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
.unwrap(),
"Basic dXNlcm5hbWU="
);
}
#[test]
fn bearer_auth() {
let req = Client::new().ws("/").bearer_auth("someS3cr3tAutht0k3n");
assert_eq!(
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
.unwrap(),
"Bearer someS3cr3tAutht0k3n"
);
}
#[test]
fn basics() {
let req = Client::new()
.ws("/")
.origin("test-origin")
.max_frame_size(100)
.server_mode()
.protocols(&["v1", "v2"])
.set_header_if_none(header::CONTENT_TYPE, "json")
.set_header_if_none(header::CONTENT_TYPE, "text")
.cookie(Cookie::build("cookie1", "value1").finish());
assert_eq!(
req.origin.as_ref().unwrap().to_str().unwrap(),
"test-origin"
);
assert_eq!(req.max_size, 100);
assert_eq!(req.server_mode, true);
assert_eq!(req.protocols, Some("v1,v2".to_string()));
assert_eq!(
req.head.headers.get(header::CONTENT_TYPE).unwrap(),
header::HeaderValue::from_static("json")
);
let _ = req.connect();
}
}

View File

@@ -1,8 +1,9 @@
use std::io::Write; use std::io::{Read, Write};
use std::time::Duration; use std::time::Duration;
use brotli2::write::BrotliEncoder; use brotli2::write::BrotliEncoder;
use bytes::Bytes; use bytes::Bytes;
use flate2::read::GzDecoder;
use flate2::write::GzEncoder; use flate2::write::GzEncoder;
use flate2::Compression; use flate2::Compression;
use futures::future::Future; use futures::future::Future;
@@ -10,6 +11,8 @@ use rand::Rng;
use actix_http::HttpService; use actix_http::HttpService;
use actix_http_test::TestServer; use actix_http_test::TestServer;
use actix_web::http::Cookie;
use actix_web::middleware::{BodyEncoding, Compress};
use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse};
use awc::error::SendRequestError; use awc::error::SendRequestError;
@@ -94,11 +97,9 @@ fn test_timeout_override() {
)))) ))))
}); });
let client = srv.execute(|| { let client = awc::Client::build()
awc::Client::build()
.timeout(Duration::from_millis(50000)) .timeout(Duration::from_millis(50000))
.finish() .finish();
});
let request = client let request = client
.get(srv.url("/")) .get(srv.url("/"))
.timeout(Duration::from_millis(50)) .timeout(Duration::from_millis(50))
@@ -109,58 +110,77 @@ fn test_timeout_override() {
} }
} }
// #[test] #[test]
// fn test_connection_close() { fn test_connection_close() {
// let mut srv = let mut srv = TestServer::new(|| {
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); HttpService::new(
App::new().service(web::resource("/").to(|| HttpResponse::Ok())),
)
});
// let request = srv.get("/").header("Connection", "close").finish().unwrap(); let res = srv
// let response = srv.execute(request.send()).unwrap(); .block_on(awc::Client::new().get(srv.url("/")).force_close().send())
// assert!(response.status().is_success()); .unwrap();
// } assert!(res.status().is_success());
}
// #[test] #[test]
// fn test_with_query_parameter() { fn test_with_query_parameter() {
// let mut srv = test::TestServer::new(|app| { let mut srv = TestServer::new(|| {
// app.handler(|req: &HttpRequest| match req.query().get("qp") { HttpService::new(App::new().service(web::resource("/").to(
// Some(_) => HttpResponse::Ok().finish(), |req: HttpRequest| {
// None => HttpResponse::BadRequest().finish(), if req.query_string().contains("qp") {
// }) HttpResponse::Ok()
// }); } else {
HttpResponse::BadRequest()
}
},
)))
});
// let request = srv.get("/").uri(srv.url("/?qp=5").as_str()).finish().unwrap(); let res = srv
.block_on(awc::Client::new().get(srv.url("/?qp=5")).send())
.unwrap();
assert!(res.status().is_success());
}
// let response = srv.execute(request.send()).unwrap(); #[test]
// assert!(response.status().is_success()); fn test_no_decompress() {
// } let mut srv = TestServer::new(|| {
HttpService::new(App::new().wrap(Compress::default()).service(
web::resource("/").route(web::to(|| {
let mut res = HttpResponse::Ok().body(STR);
res.encoding(header::ContentEncoding::Gzip);
res
})),
))
});
// #[test] let mut res = srv
// fn test_no_decompress() { .block_on(awc::Client::new().get(srv.url("/")).no_decompress().send())
// let mut srv = .unwrap();
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); assert!(res.status().is_success());
// let request = srv.get("/").disable_decompress().finish().unwrap(); // read response
// let response = srv.execute(request.send()).unwrap(); let bytes = srv.block_on(res.body()).unwrap();
// assert!(response.status().is_success());
// // read response let mut e = GzDecoder::new(&bytes[..]);
// let bytes = srv.execute(response.body()).unwrap(); let mut dec = Vec::new();
e.read_to_end(&mut dec).unwrap();
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
// let mut e = GzDecoder::new(&bytes[..]); // POST
// let mut dec = Vec::new(); let mut res = srv
// e.read_to_end(&mut dec).unwrap(); .block_on(awc::Client::new().post(srv.url("/")).no_decompress().send())
// assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); .unwrap();
assert!(res.status().is_success());
// // POST let bytes = srv.block_on(res.body()).unwrap();
// let request = srv.post().disable_decompress().finish().unwrap(); let mut e = GzDecoder::new(&bytes[..]);
// let response = srv.execute(request.send()).unwrap(); let mut dec = Vec::new();
e.read_to_end(&mut dec).unwrap();
// let bytes = srv.execute(response.body()).unwrap(); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
// let mut e = GzDecoder::new(&bytes[..]); }
// let mut dec = Vec::new();
// e.read_to_end(&mut dec).unwrap();
// assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
// }
#[test] #[test]
fn test_client_gzip_encoding() { fn test_client_gzip_encoding() {
@@ -406,7 +426,6 @@ fn test_client_brotli_encoding() {
#[test] #[test]
fn test_client_cookie_handling() { fn test_client_cookie_handling() {
use actix_web::http::Cookie;
fn err() -> Error { fn err() -> Error {
use std::io::{Error as IoError, ErrorKind}; use std::io::{Error as IoError, ErrorKind};
// stub some generic error // stub some generic error
@@ -468,36 +487,6 @@ fn test_client_cookie_handling() {
assert_eq!(c2, cookie2); assert_eq!(c2, cookie2);
} }
// #[test]
// fn test_default_headers() {
// let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
// let request = srv.get("/").finish().unwrap();
// let repr = format!("{:?}", request);
// assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\""));
// assert!(repr.contains(concat!(
// "\"user-agent\": \"actix-web/",
// env!("CARGO_PKG_VERSION"),
// "\""
// )));
// let request_override = srv
// .get("/")
// .header("User-Agent", "test")
// .header("Accept-Encoding", "over_test")
// .finish()
// .unwrap();
// let repr_override = format!("{:?}", request_override);
// assert!(repr_override.contains("\"user-agent\": \"test\""));
// assert!(repr_override.contains("\"accept-encoding\": \"over_test\""));
// assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\""));
// assert!(!repr_override.contains(concat!(
// "\"user-agent\": \"Actix-web/",
// env!("CARGO_PKG_VERSION"),
// "\""
// )));
// }
// #[test] // #[test]
// fn client_read_until_eof() { // fn client_read_until_eof() {
// let addr = test::TestServer::unused_addr(); // let addr = test::TestServer::unused_addr();

View File

@@ -11,7 +11,7 @@ use futures::future::{ok, Either};
use futures::{Future, Sink, Stream}; use futures::{Future, Sink, Stream};
use tokio_tcp::TcpStream; use tokio_tcp::TcpStream;
use actix_http::{body::BodySize, h1, ws, ResponseError, ServiceConfig}; use actix_http::{body::BodySize, h1, ws, Request, ResponseError, ServiceConfig};
fn ws_service(req: ws::Frame) -> impl Future<Item = ws::Message, Error = io::Error> { fn ws_service(req: ws::Frame) -> impl Future<Item = ws::Message, Error = io::Error> {
match req { match req {
@@ -40,10 +40,11 @@ fn test_simple() {
fn_service(|io: Io<TcpStream>| Ok(io.into_parts().0)) fn_service(|io: Io<TcpStream>| Ok(io.into_parts().0))
.and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default()))) .and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())))
.and_then(TakeItem::new().map_err(|_| ())) .and_then(TakeItem::new().map_err(|_| ()))
.and_then(|(req, framed): (_, Framed<_, _>)| { .and_then(
|(req, framed): (Option<h1::Message<Request>>, Framed<_, _>)| {
// validate request // validate request
if let Some(h1::Message::Item(req)) = req { if let Some(h1::Message::Item(req)) = req {
match ws::verify_handshake(&req) { match ws::verify_handshake(req.head()) {
Err(e) => { Err(e) => {
// validation failed // validation failed
let res = e.error_response(); let res = e.error_response();
@@ -58,7 +59,7 @@ fn test_simple() {
) )
} }
Ok(_) => { Ok(_) => {
let res = ws::handshake_response(&req).finish(); let res = ws::handshake_response(req.head()).finish();
Either::B( Either::B(
// send handshake response // send handshake response
framed framed
@@ -80,7 +81,8 @@ fn test_simple() {
} else { } else {
panic!() panic!()
} }
}) },
)
}); });
// client service // client service

View File

@@ -27,7 +27,7 @@ fn main() -> std::io::Result<()> {
HttpServer::new(|| { HttpServer::new(|| {
App::new() App::new()
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
.wrap(middleware::encoding::Compress::default()) .wrap(middleware::Compress::default())
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.service(index) .service(index)
.service(no_params) .service(no_params)
@@ -36,9 +36,9 @@ fn main() -> std::io::Result<()> {
.wrap( .wrap(
middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"),
) )
.default_resource(|r| { .default_service(
r.route(web::route().to(|| HttpResponse::MethodNotAllowed())) web::route().to(|| HttpResponse::MethodNotAllowed()),
}) )
.route(web::get().to_async(index_async)), .route(web::get().to_async(index_async)),
) )
.service(web::resource("/test1.html").to(|| "Test\r\n")) .service(web::resource("/test1.html").to(|| "Test\r\n"))

View File

@@ -1,7 +1,6 @@
use actix_http::Error; use actix_http::Error;
use actix_rt::System; use actix_rt::System;
use bytes::BytesMut; use futures::{future::lazy, Future};
use futures::{future::lazy, Future, Stream};
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
std::env::set_var("RUST_LOG", "actix_http=trace"); std::env::set_var("RUST_LOG", "actix_http=trace");
@@ -13,17 +12,14 @@ fn main() -> Result<(), Error> {
.header("User-Agent", "Actix-web") .header("User-Agent", "Actix-web")
.send() // <- Send http request .send() // <- Send http request
.from_err() .from_err()
.and_then(|response| { .and_then(|mut response| {
// <- server http response // <- server http response
println!("Response: {:?}", response); println!("Response: {:?}", response);
// read response body // read response body
response response
.body()
.from_err() .from_err()
.fold(BytesMut::new(), move |mut acc, chunk| {
acc.extend_from_slice(&chunk);
Ok::<_, Error>(acc)
})
.map(|body| println!("Downloaded: {:?} bytes", body.len())) .map(|body| println!("Downloaded: {:?} bytes", body.len()))
}) })
})) }))

View File

@@ -1,24 +1,21 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc; use std::rc::Rc;
use actix_http::body::{Body, MessageBody}; use actix_http::body::{Body, MessageBody};
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
use actix_http::encoding::{Decoder, Encoder};
use actix_server_config::ServerConfig; use actix_server_config::ServerConfig;
use actix_service::boxed::{self, BoxedNewService}; use actix_service::boxed::{self, BoxedNewService};
use actix_service::{ use actix_service::{
apply_transform, IntoNewService, IntoTransform, NewService, Transform, apply_transform, IntoNewService, IntoTransform, NewService, Transform,
}; };
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] use futures::IntoFuture;
use bytes::Bytes;
use futures::{IntoFuture, Stream};
use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory}; use crate::app_service::{AppEntry, AppInit, AppRoutingFactory};
use crate::config::{AppConfig, AppConfigInner, RouterConfig}; use crate::config::{AppConfig, AppConfigInner, ServiceConfig};
use crate::data::{Data, DataFactory}; use crate::data::{Data, DataFactory};
use crate::dev::{Payload, PayloadStream, ResourceDef}; use crate::dev::ResourceDef;
use crate::error::{Error, PayloadError}; use crate::error::Error;
use crate::resource::Resource; use crate::resource::Resource;
use crate::route::Route; use crate::route::Route;
use crate::service::{ use crate::service::{
@@ -26,45 +23,49 @@ use crate::service::{
ServiceResponse, ServiceResponse,
}; };
type HttpNewService<P> = type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
/// Application builder - structure that follows the builder pattern /// Application builder - structure that follows the builder pattern
/// for building application instances. /// for building application instances.
pub struct App<In, Out, T> pub struct App<T, B> {
where endpoint: T,
T: NewService<Request = ServiceRequest<In>, Response = ServiceRequest<Out>>, services: Vec<Box<ServiceFactory>>,
{ default: Option<Rc<HttpNewService>>,
chain: T, factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
data: Vec<Box<DataFactory>>, data: Vec<Box<DataFactory>>,
config: AppConfigInner, config: AppConfigInner,
_t: PhantomData<(In, Out)>, external: Vec<ResourceDef>,
_t: PhantomData<(B)>,
} }
impl App<PayloadStream, PayloadStream, AppChain> { impl App<AppEntry, Body> {
/// Create application builder. Application can be configured with a builder-like pattern. /// Create application builder. Application can be configured with a builder-like pattern.
pub fn new() -> Self { pub fn new() -> Self {
let fref = Rc::new(RefCell::new(None));
App { App {
chain: AppChain, endpoint: AppEntry::new(fref.clone()),
data: Vec::new(), data: Vec::new(),
services: Vec::new(),
default: None,
factory_ref: fref,
config: AppConfigInner::default(), config: AppConfigInner::default(),
external: Vec::new(),
_t: PhantomData, _t: PhantomData,
} }
} }
} }
impl<In, Out, T> App<In, Out, T> impl<T, B> App<T, B>
where where
In: 'static, B: MessageBody,
Out: 'static,
T: NewService< T: NewService<
Request = ServiceRequest<In>, Request = ServiceRequest,
Response = ServiceRequest<Out>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, >,
{ {
/// Set application data. Applicatin data could be accessed /// Set application data. Application data could be accessed
/// by using `Data<T>` extractor where `T` is data type. /// by using `Data<T>` extractor where `T` is data type.
/// ///
/// **Note**: http server accepts an application factory rather than /// **Note**: http server accepts an application factory rather than
@@ -112,151 +113,6 @@ where
self self
} }
/// 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,
) -> AppRouter<
T,
Out,
B,
impl NewService<
Request = ServiceRequest<Out>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
>
where
M: Transform<
AppRouting<Out>,
Request = ServiceRequest<Out>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
F: IntoTransform<M, AppRouting<Out>>,
{
let fref = Rc::new(RefCell::new(None));
let endpoint = apply_transform(mw, AppEntry::new(fref.clone()));
AppRouter {
endpoint,
chain: self.chain,
data: self.data,
services: Vec::new(),
default: None,
factory_ref: fref,
config: self.config,
external: Vec::new(),
_t: PhantomData,
}
}
/// 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;
/// # use futures::Future;
/// use actix_web::{web, App};
/// use actix_web::http::{header::CONTENT_TYPE, HeaderValue};
///
/// fn index() -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .wrap_fn(|req, srv|
/// srv.call(req).map(|mut res| {
/// res.headers_mut().insert(
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
/// );
/// res
/// }))
/// .route("/index.html", web::get().to(index));
/// }
/// ```
pub fn wrap_fn<F, R, B>(
self,
mw: F,
) -> AppRouter<
T,
Out,
B,
impl NewService<
Request = ServiceRequest<Out>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
>
where
F: FnMut(ServiceRequest<Out>, &mut AppRouting<Out>) -> R + Clone,
R: IntoFuture<Item = ServiceResponse<B>, Error = Error>,
{
self.wrap(mw)
}
/// Register a request modifier. It can modify any request parameters
/// including request payload type.
pub fn chain<C, F, P>(
self,
chain: F,
) -> App<
In,
P,
impl NewService<
Request = ServiceRequest<In>,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
>
where
C: NewService<
Request = ServiceRequest<Out>,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
F: IntoNewService<C>,
{
let chain = self.chain.and_then(chain.into_new_service());
App {
chain,
data: self.data,
config: self.config,
_t: PhantomData,
}
}
/// Run external configuration as part of the application building /// Run external configuration as part of the application building
/// process /// process
/// ///
@@ -269,7 +125,7 @@ where
/// use actix_web::{web, middleware, App, HttpResponse}; /// use actix_web::{web, middleware, App, HttpResponse};
/// ///
/// // this function could be located in different module /// // this function could be located in different module
/// fn config<P>(cfg: &mut web::RouterConfig<P>) { /// fn config(cfg: &mut web::ServiceConfig) {
/// cfg.service(web::resource("/test") /// cfg.service(web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok())) /// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) /// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
@@ -283,27 +139,16 @@ where
/// .route("/index.html", web::get().to(|| HttpResponse::Ok())); /// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
/// } /// }
/// ``` /// ```
pub fn configure<F>(mut self, f: F) -> AppRouter<T, Out, Body, AppEntry<Out>> pub fn configure<F>(mut self, f: F) -> Self
where where
F: Fn(&mut RouterConfig<Out>), F: Fn(&mut ServiceConfig),
{ {
let mut cfg = RouterConfig::new(); let mut cfg = ServiceConfig::new();
f(&mut cfg); f(&mut cfg);
self.data.extend(cfg.data); self.data.extend(cfg.data);
self.services.extend(cfg.services);
let fref = Rc::new(RefCell::new(None)); self.external.extend(cfg.external);
self
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. /// Configure route for a specific path.
@@ -325,171 +170,7 @@ where
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())); /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()));
/// } /// }
/// ``` /// ```
pub fn route( pub fn route(self, path: &str, mut route: Route) -> Self {
self,
path: &str,
mut route: Route<Out>,
) -> AppRouter<T, Out, Body, AppEntry<Out>> {
self.service(
Resource::new(path)
.add_guards(route.take_guards())
.route(route),
)
}
/// Register http service.
///
/// Http service is any type that implements `HttpServiceFactory` trait.
///
/// Actix web provides several services implementations:
///
/// * *Resource* is an entry in resource table which corresponds to requested URL.
/// * *Scope* is a set of resources with common root path.
/// * "StaticFiles" is a service for static files support
pub fn service<F>(self, service: F) -> AppRouter<T, Out, Body, AppEntry<Out>>
where
F: HttpServiceFactory<Out> + 'static,
{
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: vec![Box::new(ServiceFactoryWrapper::new(service))],
external: Vec::new(),
_t: PhantomData,
}
}
/// Set server host name.
///
/// Host name is used by application router as a hostname for url
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
/// html#method.host) documentation for more information.
///
/// By default host name is set to a "localhost" value.
pub fn hostname(mut self, val: &str) -> Self {
self.config.host = val.to_owned();
self
}
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
/// Enable content compression and decompression.
pub fn enable_encoding(
self,
) -> AppRouter<
impl NewService<
Request = ServiceRequest<In>,
Response = ServiceRequest<Decoder<Payload<Out>>>,
Error = Error,
InitError = (),
>,
Decoder<Payload<Out>>,
Encoder<Body>,
impl NewService<
Request = ServiceRequest<Decoder<Payload<Out>>>,
Response = ServiceResponse<Encoder<Body>>,
Error = Error,
InitError = (),
>,
>
where
Out: Stream<Item = Bytes, Error = PayloadError>,
{
use crate::middleware::encoding::{Compress, Decompress};
self.chain(Decompress::new()).wrap(Compress::default())
}
}
/// Application router builder - Structure that follows the builder pattern
/// for building application instances.
pub struct AppRouter<C, P, B, T> {
chain: C,
endpoint: T,
services: Vec<Box<ServiceFactory<P>>>,
default: Option<Rc<HttpNewService<P>>>,
factory_ref: Rc<RefCell<Option<AppRoutingFactory<P>>>>,
data: Vec<Box<DataFactory>>,
config: AppConfigInner,
external: Vec<ResourceDef>,
_t: PhantomData<(P, B)>,
}
impl<C, P, B, T> AppRouter<C, P, B, T>
where
P: 'static,
B: MessageBody,
T: NewService<
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
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.
/// This method can not be could multiple times, in that case
/// multiple resources with one route would be registered for same resource path.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn index(data: web::Path<(String, String)>) -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .route("/test1", web::get().to(index))
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()));
/// }
/// ```
pub fn route(self, path: &str, mut route: Route<P>) -> Self {
self.service( self.service(
Resource::new(path) Resource::new(path)
.add_guards(route.take_guards()) .add_guards(route.take_guards())
@@ -508,102 +189,75 @@ where
/// * "StaticFiles" is a service for static files support /// * "StaticFiles" is a service for static files support
pub fn service<F>(mut self, factory: F) -> Self pub fn service<F>(mut self, factory: F) -> Self
where where
F: HttpServiceFactory<P> + 'static, F: HttpServiceFactory + 'static,
{ {
self.services self.services
.push(Box::new(ServiceFactoryWrapper::new(factory))); .push(Box::new(ServiceFactoryWrapper::new(factory)));
self self
} }
/// Registers middleware, in the form of a middleware component (type), /// Set server host name.
/// 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. /// Host name is used by application router as a hostname for url
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
/// html#method.host) documentation for more information.
/// ///
pub fn wrap<M, B1, F>( /// By default host name is set to a "localhost" value.
self, pub fn hostname(mut self, val: &str) -> Self {
mw: F, self.config.host = val.to_owned();
) -> AppRouter< self
C,
P,
B1,
impl NewService<
Request = ServiceRequest<P>,
Response = ServiceResponse<B1>,
Error = Error,
InitError = (),
>,
>
where
M: Transform<
T::Service,
Request = ServiceRequest<P>,
Response = ServiceResponse<B1>,
Error = Error,
InitError = (),
>,
B1: MessageBody,
F: IntoTransform<M, T::Service>,
{
let endpoint = apply_transform(mw, self.endpoint);
AppRouter {
endpoint,
chain: self.chain,
data: self.data,
services: self.services,
default: self.default,
factory_ref: self.factory_ref,
config: self.config,
external: self.external,
_t: PhantomData,
}
} }
/// Registers middleware, in the form of a closure, that runs during inbound /// Default service to be used if no matching resource could be found.
/// 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. /// It is possible to use services like `Resource`, `Route`.
/// ///
pub fn wrap_fn<B1, F, R>( /// ```rust
self, /// use actix_web::{web, App, HttpResponse};
mw: F, ///
) -> AppRouter< /// fn index() -> &'static str {
C, /// "Welcome!"
P, /// }
B1, ///
impl NewService< /// fn main() {
Request = ServiceRequest<P>, /// let app = App::new()
Response = ServiceResponse<B1>, /// .service(
Error = Error, /// web::resource("/index.html").route(web::get().to(index)))
InitError = (), /// .default_service(
>, /// web::route().to(|| HttpResponse::NotFound()));
> /// }
/// ```
///
/// It is also possible to use static files as default service.
///
/// ```rust
/// use actix_files::Files;
/// use actix_web::{web, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .service(
/// web::resource("/index.html").to(|| HttpResponse::Ok()))
/// .default_service(
/// Files::new("", "./static")
/// );
/// }
/// ```
pub fn default_service<F, U>(mut self, f: F) -> Self
where where
B1: MessageBody, F: IntoNewService<U>,
F: FnMut(ServiceRequest<P>, &mut T::Service) -> R + Clone,
R: IntoFuture<Item = ServiceResponse<B1>, Error = Error>,
{
self.wrap(mw)
}
/// Default resource to be used if no matching resource could be found.
pub fn default_resource<F, U>(mut self, f: F) -> Self
where
F: FnOnce(Resource<P>) -> Resource<P, U>,
U: NewService< U: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (),
> + 'static, > + 'static,
U::InitError: fmt::Debug,
{ {
// create and configure default resource // create and configure default resource
self.default = Some(Rc::new(boxed::new_service( self.default = Some(Rc::new(boxed::new_service(
f(Resource::new("")).into_new_service().map_init_err(|_| ()), f.into_new_service().map_init_err(|e| {
log::error!("Can not construct default service: {:?}", e)
}),
))); )));
self self
@@ -641,27 +295,128 @@ where
self.external.push(rdef); self.external.push(rdef);
self self
} }
/// 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, B1, F>(
self,
mw: F,
) -> App<
impl NewService<
Request = ServiceRequest,
Response = ServiceResponse<B1>,
Error = Error,
InitError = (),
>,
B1,
>
where
M: Transform<
T::Service,
Request = ServiceRequest,
Response = ServiceResponse<B1>,
Error = Error,
InitError = (),
>,
B1: MessageBody,
F: IntoTransform<M, T::Service>,
{
let endpoint = apply_transform(mw, self.endpoint);
App {
endpoint,
data: self.data,
services: self.services,
default: self.default,
factory_ref: self.factory_ref,
config: self.config,
external: self.external,
_t: PhantomData,
}
} }
impl<C, T, P: 'static, B: MessageBody> IntoNewService<AppInit<C, T, P, B>, ServerConfig> /// Registers middleware, in the form of a closure, that runs during inbound
for AppRouter<C, P, B, T> /// 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::{web, App};
/// use actix_web::http::{header::CONTENT_TYPE, HeaderValue};
///
/// fn index() -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .wrap_fn(|req, srv|
/// srv.call(req).map(|mut res| {
/// res.headers_mut().insert(
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
/// );
/// res
/// }))
/// .route("/index.html", web::get().to(index));
/// }
/// ```
pub fn wrap_fn<B1, F, R>(
self,
mw: F,
) -> App<
impl NewService<
Request = ServiceRequest,
Response = ServiceResponse<B1>,
Error = Error,
InitError = (),
>,
B1,
>
where where
B1: MessageBody,
F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone,
R: IntoFuture<Item = ServiceResponse<B1>, Error = Error>,
{
self.wrap(mw)
}
}
impl<T, B> IntoNewService<AppInit<T, B>, ServerConfig> for App<T, B>
where
B: MessageBody,
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse<B>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, >,
C: NewService<
Request = ServiceRequest,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
{ {
fn into_new_service(self) -> AppInit<C, T, P, B> { fn into_new_service(self) -> AppInit<T, B> {
AppInit { AppInit {
chain: self.chain,
data: self.data, data: self.data,
endpoint: self.endpoint, endpoint: self.endpoint,
services: RefCell::new(self.services), services: RefCell::new(self.services),
@@ -681,7 +436,7 @@ mod tests {
use super::*; use super::*;
use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::http::{header, HeaderValue, Method, StatusCode};
use crate::service::{ServiceRequest, ServiceResponse}; use crate::service::{ServiceRequest, ServiceResponse};
use crate::test::{block_on, call_success, init_service, TestRequest}; use crate::test::{block_on, call_service, init_service, TestRequest};
use crate::{web, Error, HttpResponse}; use crate::{web, Error, HttpResponse};
#[test] #[test]
@@ -702,10 +457,14 @@ mod tests {
.service(web::resource("/test").to(|| HttpResponse::Ok())) .service(web::resource("/test").to(|| HttpResponse::Ok()))
.service( .service(
web::resource("/test2") web::resource("/test2")
.default_resource(|r| r.to(|| HttpResponse::Created())) .default_service(|r: ServiceRequest| {
r.into_response(HttpResponse::Created())
})
.route(web::get().to(|| HttpResponse::Ok())), .route(web::get().to(|| HttpResponse::Ok())),
) )
.default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())), .default_service(|r: ServiceRequest| {
r.into_response(HttpResponse::MethodNotAllowed())
}),
); );
let req = TestRequest::with_uri("/blah").to_request(); let req = TestRequest::with_uri("/blah").to_request();
@@ -742,13 +501,13 @@ mod tests {
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
} }
fn md<S, P, B>( fn md<S, B>(
req: ServiceRequest<P>, req: ServiceRequest,
srv: &mut S, srv: &mut S,
) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error> ) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error>
where where
S: Service< S: Service<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse<B>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
>, >,
@@ -768,7 +527,7 @@ mod tests {
.route("/test", web::get().to(|| HttpResponse::Ok())), .route("/test", web::get().to(|| HttpResponse::Ok())),
); );
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@@ -784,7 +543,7 @@ mod tests {
.wrap(md), .wrap(md),
); );
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@@ -808,7 +567,7 @@ mod tests {
.service(web::resource("/test").to(|| HttpResponse::Ok())), .service(web::resource("/test").to(|| HttpResponse::Ok())),
); );
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@@ -832,7 +591,7 @@ mod tests {
}), }),
); );
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),

View File

@@ -6,11 +6,11 @@ use actix_http::{Request, Response};
use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url};
use actix_server_config::ServerConfig; use actix_server_config::ServerConfig;
use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::boxed::{self, BoxedNewService, BoxedService};
use actix_service::{fn_service, AndThen, NewService, Service, ServiceExt}; use actix_service::{fn_service, NewService, Service};
use futures::future::{ok, Either, FutureResult}; use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
use crate::config::{AppConfig, ServiceConfig}; use crate::config::{AppConfig, AppService};
use crate::data::{DataFactory, DataFactoryResult}; use crate::data::{DataFactory, DataFactoryResult};
use crate::error::Error; use crate::error::Error;
use crate::guard::Guard; use crate::guard::Guard;
@@ -19,9 +19,8 @@ use crate::rmap::ResourceMap;
use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse}; use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse};
type Guards = Vec<Box<Guard>>; type Guards = Vec<Box<Guard>>;
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>; type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
type HttpNewService<P> = type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
type BoxedResponse = Either< type BoxedResponse = Either<
FutureResult<ServiceResponse, Error>, FutureResult<ServiceResponse, Error>,
Box<Future<Item = ServiceResponse, Error = Error>>, Box<Future<Item = ServiceResponse, Error = Error>>,
@@ -29,36 +28,28 @@ type BoxedResponse = Either<
/// Service factory to convert `Request` to a `ServiceRequest<S>`. /// Service factory to convert `Request` to a `ServiceRequest<S>`.
/// It also executes data factories. /// It also executes data factories.
pub struct AppInit<C, T, P, B> pub struct AppInit<T, B>
where where
C: NewService<Request = ServiceRequest, Response = ServiceRequest<P>>,
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse<B>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, >,
{ {
pub(crate) chain: C,
pub(crate) endpoint: T, pub(crate) endpoint: T,
pub(crate) data: Vec<Box<DataFactory>>, pub(crate) data: Vec<Box<DataFactory>>,
pub(crate) config: RefCell<AppConfig>, pub(crate) config: RefCell<AppConfig>,
pub(crate) services: RefCell<Vec<Box<ServiceFactory<P>>>>, pub(crate) services: RefCell<Vec<Box<ServiceFactory>>>,
pub(crate) default: Option<Rc<HttpNewService<P>>>, pub(crate) default: Option<Rc<HttpNewService>>,
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory<P>>>>, pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
pub(crate) external: RefCell<Vec<ResourceDef>>, pub(crate) external: RefCell<Vec<ResourceDef>>,
} }
impl<C, T, P: 'static, B> NewService<ServerConfig> for AppInit<C, T, P, B> impl<T, B> NewService<ServerConfig> for AppInit<T, B>
where where
C: NewService<
Request = ServiceRequest,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse<B>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
InitError = (), InitError = (),
@@ -66,15 +57,15 @@ where
{ {
type Request = Request; type Request = Request;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = C::Error; type Error = T::Error;
type InitError = C::InitError; type InitError = T::InitError;
type Service = AndThen<AppInitService<C::Service, P>, T::Service>; type Service = AppInitService<T::Service, B>;
type Future = AppInitResult<C, T, P, B>; type Future = AppInitResult<T, B>;
fn new_service(&self, cfg: &ServerConfig) -> Self::Future { fn new_service(&self, cfg: &ServerConfig) -> Self::Future {
// update resource default service // update resource default service
let default = self.default.clone().unwrap_or_else(|| { let default = self.default.clone().unwrap_or_else(|| {
Rc::new(boxed::new_service(fn_service(|req: ServiceRequest<P>| { Rc::new(boxed::new_service(fn_service(|req: ServiceRequest| {
Ok(req.into_response(Response::NotFound().finish())) Ok(req.into_response(Response::NotFound().finish()))
}))) })))
}); });
@@ -86,8 +77,7 @@ where
loc_cfg.addr = cfg.local_addr(); loc_cfg.addr = cfg.local_addr();
} }
let mut config = let mut config = AppService::new(self.config.borrow().clone(), default.clone());
ServiceConfig::new(self.config.borrow().clone(), default.clone());
// register services // register services
std::mem::replace(&mut *self.services.borrow_mut(), Vec::new()) std::mem::replace(&mut *self.services.borrow_mut(), Vec::new())
@@ -121,8 +111,6 @@ where
rmap.finish(rmap.clone()); rmap.finish(rmap.clone());
AppInitResult { AppInitResult {
chain: None,
chain_fut: self.chain.new_service(&()),
endpoint: None, endpoint: None,
endpoint_fut: self.endpoint.new_service(&()), endpoint_fut: self.endpoint.new_service(&()),
data: self.data.iter().map(|s| s.construct()).collect(), data: self.data.iter().map(|s| s.construct()).collect(),
@@ -133,38 +121,29 @@ where
} }
} }
pub struct AppInitResult<C, T, P, B> pub struct AppInitResult<T, B>
where where
C: NewService,
T: NewService, T: NewService,
{ {
chain: Option<C::Service>,
endpoint: Option<T::Service>, endpoint: Option<T::Service>,
chain_fut: C::Future,
endpoint_fut: T::Future, endpoint_fut: T::Future,
rmap: Rc<ResourceMap>, rmap: Rc<ResourceMap>,
data: Vec<Box<DataFactoryResult>>, data: Vec<Box<DataFactoryResult>>,
config: AppConfig, config: AppConfig,
_t: PhantomData<(P, B)>, _t: PhantomData<B>,
} }
impl<C, T, P, B> Future for AppInitResult<C, T, P, B> impl<T, B> Future for AppInitResult<T, B>
where where
C: NewService<
Request = ServiceRequest,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse<B>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, >,
{ {
type Item = AndThen<AppInitService<C::Service, P>, T::Service>; type Item = AppInitService<T::Service, B>;
type Error = C::InitError; type Error = T::InitError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut idx = 0; let mut idx = 0;
@@ -177,28 +156,19 @@ where
} }
} }
if self.chain.is_none() {
if let Async::Ready(srv) = self.chain_fut.poll()? {
self.chain = Some(srv);
}
}
if self.endpoint.is_none() { if self.endpoint.is_none() {
if let Async::Ready(srv) = self.endpoint_fut.poll()? { if let Async::Ready(srv) = self.endpoint_fut.poll()? {
self.endpoint = Some(srv); self.endpoint = Some(srv);
} }
} }
if self.chain.is_some() && self.endpoint.is_some() { if self.endpoint.is_some() {
Ok(Async::Ready( Ok(Async::Ready(AppInitService {
AppInitService { service: self.endpoint.take().unwrap(),
chain: self.chain.take().unwrap(),
rmap: self.rmap.clone(), rmap: self.rmap.clone(),
config: self.config.clone(), config: self.config.clone(),
pool: HttpRequestPool::create(), pool: HttpRequestPool::create(),
} }))
.and_then(self.endpoint.take().unwrap()),
))
} else { } else {
Ok(Async::NotReady) Ok(Async::NotReady)
} }
@@ -206,27 +176,27 @@ where
} }
/// Service to convert `Request` to a `ServiceRequest<S>` /// Service to convert `Request` to a `ServiceRequest<S>`
pub struct AppInitService<C, P> pub struct AppInitService<T: Service, B>
where where
C: Service<Request = ServiceRequest, Response = ServiceRequest<P>, Error = Error>, T: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{ {
chain: C, service: T,
rmap: Rc<ResourceMap>, rmap: Rc<ResourceMap>,
config: AppConfig, config: AppConfig,
pool: &'static HttpRequestPool, pool: &'static HttpRequestPool,
} }
impl<C, P> Service for AppInitService<C, P> impl<T, B> Service for AppInitService<T, B>
where where
C: Service<Request = ServiceRequest, Response = ServiceRequest<P>, Error = Error>, T: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{ {
type Request = Request; type Request = Request;
type Response = ServiceRequest<P>; type Response = ServiceResponse<B>;
type Error = C::Error; type Error = T::Error;
type Future = C::Future; type Future = T::Future;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.chain.poll_ready() self.service.poll_ready()
} }
fn call(&mut self, req: Request) -> Self::Future { fn call(&mut self, req: Request) -> Self::Future {
@@ -247,22 +217,22 @@ where
self.pool, self.pool,
) )
}; };
self.chain.call(ServiceRequest::from_parts(req, payload)) self.service.call(ServiceRequest::from_parts(req, payload))
} }
} }
pub struct AppRoutingFactory<P> { pub struct AppRoutingFactory {
services: Rc<Vec<(ResourceDef, HttpNewService<P>, RefCell<Option<Guards>>)>>, services: Rc<Vec<(ResourceDef, HttpNewService, RefCell<Option<Guards>>)>>,
default: Rc<HttpNewService<P>>, default: Rc<HttpNewService>,
} }
impl<P: 'static> NewService for AppRoutingFactory<P> { impl NewService for AppRoutingFactory {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Service = AppRouting<P>; type Service = AppRouting;
type Future = AppRoutingFactoryResponse<P>; type Future = AppRoutingFactoryResponse;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
AppRoutingFactoryResponse { AppRoutingFactoryResponse {
@@ -283,23 +253,23 @@ impl<P: 'static> NewService for AppRoutingFactory<P> {
} }
} }
type HttpServiceFut<P> = Box<Future<Item = HttpService<P>, Error = ()>>; type HttpServiceFut = Box<Future<Item = HttpService, Error = ()>>;
/// Create app service /// Create app service
#[doc(hidden)] #[doc(hidden)]
pub struct AppRoutingFactoryResponse<P> { pub struct AppRoutingFactoryResponse {
fut: Vec<CreateAppRoutingItem<P>>, fut: Vec<CreateAppRoutingItem>,
default: Option<HttpService<P>>, default: Option<HttpService>,
default_fut: Option<Box<Future<Item = HttpService<P>, Error = ()>>>, default_fut: Option<Box<Future<Item = HttpService, Error = ()>>>,
} }
enum CreateAppRoutingItem<P> { enum CreateAppRoutingItem {
Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut<P>), Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut),
Service(ResourceDef, Option<Guards>, HttpService<P>), Service(ResourceDef, Option<Guards>, HttpService),
} }
impl<P> Future for AppRoutingFactoryResponse<P> { impl Future for AppRoutingFactoryResponse {
type Item = AppRouting<P>; type Item = AppRouting;
type Error = (); type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@@ -360,14 +330,14 @@ impl<P> Future for AppRoutingFactoryResponse<P> {
} }
} }
pub struct AppRouting<P> { pub struct AppRouting {
router: Router<HttpService<P>, Guards>, router: Router<HttpService, Guards>,
ready: Option<(ServiceRequest<P>, ResourceInfo)>, ready: Option<(ServiceRequest, ResourceInfo)>,
default: Option<HttpService<P>>, default: Option<HttpService>,
} }
impl<P> Service for AppRouting<P> { impl Service for AppRouting {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Future = BoxedResponse; type Future = BoxedResponse;
@@ -380,7 +350,7 @@ impl<P> Service for AppRouting<P> {
} }
} }
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let res = self.router.recognize_mut_checked(&mut req, |req, guards| { let res = self.router.recognize_mut_checked(&mut req, |req, guards| {
if let Some(ref guards) = guards { if let Some(ref guards) = guards {
for f in guards { for f in guards {
@@ -404,58 +374,25 @@ impl<P> Service for AppRouting<P> {
} }
/// Wrapper service for routing /// Wrapper service for routing
pub struct AppEntry<P> { pub struct AppEntry {
factory: Rc<RefCell<Option<AppRoutingFactory<P>>>>, factory: Rc<RefCell<Option<AppRoutingFactory>>>,
} }
impl<P> AppEntry<P> { impl AppEntry {
pub fn new(factory: Rc<RefCell<Option<AppRoutingFactory<P>>>>) -> Self { pub fn new(factory: Rc<RefCell<Option<AppRoutingFactory>>>) -> Self {
AppEntry { factory } AppEntry { factory }
} }
} }
impl<P: 'static> NewService for AppEntry<P> { impl NewService for AppEntry {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Service = AppRouting<P>; type Service = AppRouting;
type Future = AppRoutingFactoryResponse<P>; type Future = AppRoutingFactoryResponse;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
self.factory.borrow_mut().as_mut().unwrap().new_service(&()) self.factory.borrow_mut().as_mut().unwrap().new_service(&())
} }
} }
#[doc(hidden)]
pub struct AppChain;
impl NewService for AppChain {
type Request = ServiceRequest;
type Response = ServiceRequest;
type Error = Error;
type InitError = ();
type Service = AppChain;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &()) -> Self::Future {
ok(AppChain)
}
}
impl Service for AppChain {
type Request = ServiceRequest;
type Response = ServiceRequest;
type Error = Error;
type Future = FutureResult<Self::Response, Self::Error>;
#[inline]
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
#[inline]
fn call(&mut self, req: ServiceRequest) -> Self::Future {
ok(req)
}
}

View File

@@ -19,26 +19,26 @@ use crate::service::{
}; };
type Guards = Vec<Box<Guard>>; type Guards = Vec<Box<Guard>>;
type HttpNewService<P> = type HttpNewService =
boxed::BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>; boxed::BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
/// Application configuration /// Application configuration
pub struct ServiceConfig<P> { pub struct AppService {
config: AppConfig, config: AppConfig,
root: bool, root: bool,
default: Rc<HttpNewService<P>>, default: Rc<HttpNewService>,
services: Vec<( services: Vec<(
ResourceDef, ResourceDef,
HttpNewService<P>, HttpNewService,
Option<Guards>, Option<Guards>,
Option<Rc<ResourceMap>>, Option<Rc<ResourceMap>>,
)>, )>,
} }
impl<P: 'static> ServiceConfig<P> { impl AppService {
/// Crate server settings instance /// Crate server settings instance
pub(crate) fn new(config: AppConfig, default: Rc<HttpNewService<P>>) -> Self { pub(crate) fn new(config: AppConfig, default: Rc<HttpNewService>) -> Self {
ServiceConfig { AppService {
config, config,
default, default,
root: true, root: true,
@@ -55,7 +55,7 @@ impl<P: 'static> ServiceConfig<P> {
self, self,
) -> Vec<( ) -> Vec<(
ResourceDef, ResourceDef,
HttpNewService<P>, HttpNewService,
Option<Guards>, Option<Guards>,
Option<Rc<ResourceMap>>, Option<Rc<ResourceMap>>,
)> { )> {
@@ -63,7 +63,7 @@ impl<P: 'static> ServiceConfig<P> {
} }
pub(crate) fn clone_config(&self) -> Self { pub(crate) fn clone_config(&self) -> Self {
ServiceConfig { AppService {
config: self.config.clone(), config: self.config.clone(),
default: self.default.clone(), default: self.default.clone(),
services: Vec::new(), services: Vec::new(),
@@ -77,7 +77,7 @@ impl<P: 'static> ServiceConfig<P> {
} }
/// Default resource /// Default resource
pub fn default_service(&self) -> Rc<HttpNewService<P>> { pub fn default_service(&self) -> Rc<HttpNewService> {
self.default.clone() self.default.clone()
} }
@@ -90,7 +90,7 @@ impl<P: 'static> ServiceConfig<P> {
) where ) where
F: IntoNewService<S>, F: IntoNewService<S>,
S: NewService< S: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (), InitError = (),
@@ -165,17 +165,17 @@ impl Default for AppConfigInner {
} }
} }
/// Router config. It is used for external configuration. /// Service config is used for external configuration.
/// Part of application configuration could be offloaded /// Part of application configuration could be offloaded
/// to set of external methods. This could help with /// to set of external methods. This could help with
/// modularization of big application configuration. /// modularization of big application configuration.
pub struct RouterConfig<P: 'static> { pub struct ServiceConfig {
pub(crate) services: Vec<Box<ServiceFactory<P>>>, pub(crate) services: Vec<Box<ServiceFactory>>,
pub(crate) data: Vec<Box<DataFactory>>, pub(crate) data: Vec<Box<DataFactory>>,
pub(crate) external: Vec<ResourceDef>, pub(crate) external: Vec<ResourceDef>,
} }
impl<P: 'static> RouterConfig<P> { impl ServiceConfig {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Self { Self {
services: Vec::new(), services: Vec::new(),
@@ -211,7 +211,7 @@ impl<P: 'static> RouterConfig<P> {
/// Configure route for a specific path. /// Configure route for a specific path.
/// ///
/// This is same as `App::route()` method. /// This is same as `App::route()` method.
pub fn route(&mut self, path: &str, mut route: Route<P>) -> &mut Self { pub fn route(&mut self, path: &str, mut route: Route) -> &mut Self {
self.service( self.service(
Resource::new(path) Resource::new(path)
.add_guards(route.take_guards()) .add_guards(route.take_guards())
@@ -224,7 +224,7 @@ impl<P: 'static> RouterConfig<P> {
/// This is same as `App::service()` method. /// This is same as `App::service()` method.
pub fn service<F>(&mut self, factory: F) -> &mut Self pub fn service<F>(&mut self, factory: F) -> &mut Self
where where
F: HttpServiceFactory<P> + 'static, F: HttpServiceFactory + 'static,
{ {
self.services self.services
.push(Box::new(ServiceFactoryWrapper::new(factory))); .push(Box::new(ServiceFactoryWrapper::new(factory)));
@@ -255,13 +255,13 @@ mod tests {
use actix_service::Service; use actix_service::Service;
use super::*; use super::*;
use crate::http::StatusCode; use crate::http::{Method, StatusCode};
use crate::test::{block_on, init_service, TestRequest}; use crate::test::{block_on, call_service, init_service, TestRequest};
use crate::{web, App, HttpResponse}; use crate::{web, App, HttpResponse};
#[test] #[test]
fn test_data() { fn test_data() {
let cfg = |cfg: &mut RouterConfig<_>| { let cfg = |cfg: &mut ServiceConfig| {
cfg.data(10usize); cfg.data(10usize);
}; };
@@ -276,7 +276,7 @@ mod tests {
#[test] #[test]
fn test_data_factory() { fn test_data_factory() {
let cfg = |cfg: &mut RouterConfig<_>| { let cfg = |cfg: &mut ServiceConfig| {
cfg.data_factory(|| Ok::<_, ()>(10usize)); cfg.data_factory(|| Ok::<_, ()>(10usize));
}; };
@@ -288,7 +288,7 @@ mod tests {
let resp = block_on(srv.call(req)).unwrap(); let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let cfg2 = |cfg: &mut RouterConfig<_>| { let cfg2 = |cfg: &mut ServiceConfig| {
cfg.data_factory(|| Ok::<_, ()>(10u32)); cfg.data_factory(|| Ok::<_, ()>(10u32));
}; };
let mut srv = init_service( let mut srv = init_service(
@@ -300,4 +300,26 @@ mod tests {
let resp = block_on(srv.call(req)).unwrap(); let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
} }
#[test]
fn test_service() {
let mut srv = init_service(App::new().configure(|cfg| {
cfg.service(
web::resource("/test").route(web::get().to(|| HttpResponse::Created())),
)
.route("/index.html", web::get().to(|| HttpResponse::Ok()));
}));
let req = TestRequest::with_uri("/test")
.method(Method::GET)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/index.html")
.method(Method::GET)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
}
} }

View File

@@ -25,15 +25,16 @@ pub(crate) trait DataFactoryResult {
/// during application configuration process /// during application configuration process
/// with `App::data()` method. /// with `App::data()` method.
/// ///
/// Applicatin data could be accessed by using `Data<T>` /// Application data could be accessed by using `Data<T>`
/// extractor where `T` is data type. /// extractor where `T` is data type.
/// ///
/// **Note**: http server accepts an application factory rather than /// **Note**: http server accepts an application factory rather than
/// an application instance. Http server constructs an application /// an application instance. Http server constructs an application
/// instance for each thread, thus application data must be constructed /// instance for each thread, thus application data must be constructed
/// multiple times. If you want to share data between different /// multiple times. If you want to share data between different
/// threads, a shared object should be used, e.g. `Arc`. Application /// threads, a shareable object should be used, e.g. `Send + Sync`. Application
/// data does not need to be `Send` or `Sync`. /// data does not need to be `Send` or `Sync`. Internally `Data` instance
/// uses `Arc`.
/// ///
/// If route data is not set for a handler, using `Data<T>` extractor would /// If route data is not set for a handler, using `Data<T>` extractor would
/// cause *Internal Server Error* response. /// cause *Internal Server Error* response.
@@ -60,6 +61,7 @@ pub(crate) trait DataFactoryResult {
/// web::get().to(index))); /// web::get().to(index)));
/// } /// }
/// ``` /// ```
#[derive(Debug)]
pub struct Data<T>(Arc<T>); pub struct Data<T>(Arc<T>);
impl<T> Data<T> { impl<T> Data<T> {
@@ -87,15 +89,21 @@ impl<T> Clone for Data<T> {
} }
} }
impl<T: 'static, P> FromRequest<P> for Data<T> { impl<T: 'static> FromRequest for Data<T> {
type Config = ();
type Error = Error; type Error = Error;
type Future = Result<Self, Error>; type Future = Result<Self, Error>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
if let Some(st) = req.app_config().extensions().get::<Data<T>>() { if let Some(st) = req.app_config().extensions().get::<Data<T>>() {
Ok(st.clone()) Ok(st.clone())
} else { } else {
log::debug!(
"Failed to construct App-level Data extractor. \
Request path: {:?}",
req.path()
);
Err(ErrorInternalServerError( Err(ErrorInternalServerError(
"App data is not configured, to configure use App::data()", "App data is not configured, to configure use App::data()",
)) ))
@@ -226,15 +234,17 @@ impl<T> Clone for RouteData<T> {
} }
} }
impl<T: 'static, P> FromRequest<P> for RouteData<T> { impl<T: 'static> FromRequest for RouteData<T> {
type Config = ();
type Error = Error; type Error = Error;
type Future = Result<Self, Error>; type Future = Result<Self, Error>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
if let Some(st) = req.route_data::<T>() { if let Some(st) = req.route_data::<T>() {
Ok(st.clone()) Ok(st.clone())
} else { } else {
log::debug!("Failed to construct Route-level Data extractor");
Err(ErrorInternalServerError( Err(ErrorInternalServerError(
"Route data is not configured, to configure use Route::data()", "Route data is not configured, to configure use Route::data()",
)) ))

View File

@@ -31,7 +31,7 @@ pub enum UrlencodedError {
#[display(fmt = "Can not decode chunked transfer encoding")] #[display(fmt = "Can not decode chunked transfer encoding")]
Chunked, Chunked,
/// Payload size is bigger than allowed. (default: 256kB) /// Payload size is bigger than allowed. (default: 256kB)
#[display(fmt = "Urlencoded payload size is bigger than allowed. (default: 256kB)")] #[display(fmt = "Urlencoded payload size is bigger than allowed (default: 256kB)")]
Overflow, Overflow,
/// Payload size is now known /// Payload size is now known
#[display(fmt = "Payload size is now known")] #[display(fmt = "Payload size is now known")]
@@ -66,7 +66,7 @@ impl ResponseError for UrlencodedError {
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
pub enum JsonPayloadError { pub enum JsonPayloadError {
/// Payload size is bigger than allowed. (default: 32kB) /// Payload size is bigger than allowed. (default: 32kB)
#[display(fmt = "Json payload size is bigger than allowed.")] #[display(fmt = "Json payload size is bigger than allowed")]
Overflow, Overflow,
/// Content type error /// Content type error
#[display(fmt = "Content type error")] #[display(fmt = "Content type error")]

View File

@@ -10,15 +10,18 @@ use crate::request::HttpRequest;
/// Trait implemented by types that can be extracted from request. /// Trait implemented by types that can be extracted from request.
/// ///
/// Types that implement this trait can be used with `Route` handlers. /// Types that implement this trait can be used with `Route` handlers.
pub trait FromRequest<P>: Sized { pub trait FromRequest: Sized {
/// The associated error which can be returned. /// The associated error which can be returned.
type Error: Into<Error>; type Error: Into<Error>;
/// Future that resolves to a Self /// Future that resolves to a Self
type Future: IntoFuture<Item = Self, Error = Self::Error>; type Future: IntoFuture<Item = Self, Error = Self::Error>;
/// Configuration for this extractor
type Config: Default + 'static;
/// Convert request to a Self /// Convert request to a Self
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future; fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future;
/// Convert request to a Self /// Convert request to a Self
/// ///
@@ -26,6 +29,14 @@ pub trait FromRequest<P>: Sized {
fn extract(req: &HttpRequest) -> Self::Future { fn extract(req: &HttpRequest) -> Self::Future {
Self::from_request(req, &mut Payload::None) Self::from_request(req, &mut Payload::None)
} }
/// Create and configure config instance.
fn configure<F>(f: F) -> Self::Config
where
F: FnOnce(Self::Config) -> Self::Config,
{
f(Self::Config::default())
}
} }
/// Optionally extract a field from the request /// Optionally extract a field from the request
@@ -45,11 +56,12 @@ pub trait FromRequest<P>: Sized {
/// name: String /// name: String
/// } /// }
/// ///
/// impl<P> FromRequest<P> for Thing { /// impl FromRequest for Thing {
/// type Error = Error; /// type Error = Error;
/// type Future = Result<Self, Self::Error>; /// type Future = Result<Self, Self::Error>;
/// type Config = ();
/// ///
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future { /// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
/// if rand::random() { /// if rand::random() {
/// Ok(Thing { name: "thingy".into() }) /// Ok(Thing { name: "thingy".into() })
/// } else { /// } else {
@@ -75,16 +87,17 @@ pub trait FromRequest<P>: Sized {
/// ); /// );
/// } /// }
/// ``` /// ```
impl<T: 'static, P> FromRequest<P> for Option<T> impl<T: 'static> FromRequest for Option<T>
where where
T: FromRequest<P>, T: FromRequest,
T::Future: 'static, T::Future: 'static,
{ {
type Config = T::Config;
type Error = Error; type Error = Error;
type Future = Box<Future<Item = Option<T>, Error = Error>>; type Future = Box<Future<Item = Option<T>, Error = Error>>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
Box::new( Box::new(
T::from_request(req, payload) T::from_request(req, payload)
.into_future() .into_future()
@@ -116,11 +129,12 @@ where
/// name: String /// name: String
/// } /// }
/// ///
/// impl<P> FromRequest<P> for Thing { /// impl FromRequest for Thing {
/// type Error = Error; /// type Error = Error;
/// type Future = Result<Thing, Error>; /// type Future = Result<Thing, Error>;
/// type Config = ();
/// ///
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future { /// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
/// if rand::random() { /// if rand::random() {
/// Ok(Thing { name: "thingy".into() }) /// Ok(Thing { name: "thingy".into() })
/// } else { /// } else {
@@ -143,17 +157,18 @@ where
/// ); /// );
/// } /// }
/// ``` /// ```
impl<T: 'static, P> FromRequest<P> for Result<T, T::Error> impl<T: 'static> FromRequest for Result<T, T::Error>
where where
T: FromRequest<P>, T: FromRequest,
T::Future: 'static, T::Future: 'static,
T::Error: 'static, T::Error: 'static,
{ {
type Config = T::Config;
type Error = Error; type Error = Error;
type Future = Box<Future<Item = Result<T, T::Error>, Error = Error>>; type Future = Box<Future<Item = Result<T, T::Error>, Error = Error>>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
Box::new( Box::new(
T::from_request(req, payload) T::from_request(req, payload)
.into_future() .into_future()
@@ -166,11 +181,12 @@ where
} }
#[doc(hidden)] #[doc(hidden)]
impl<P> FromRequest<P> for () { impl FromRequest for () {
type Config = ();
type Error = Error; type Error = Error;
type Future = Result<(), Error>; type Future = Result<(), Error>;
fn from_request(_: &HttpRequest, _: &mut Payload<P>) -> Self::Future { fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future {
Ok(()) Ok(())
} }
} }
@@ -179,12 +195,13 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
/// FromRequest implementation for tuple /// FromRequest implementation for tuple
#[doc(hidden)] #[doc(hidden)]
impl<P, $($T: FromRequest<P> + 'static),+> FromRequest<P> for ($($T,)+) impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+)
{ {
type Error = Error; type Error = Error;
type Future = $fut_type<P, $($T),+>; type Future = $fut_type<$($T),+>;
type Config = ($($T::Config),+);
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
$fut_type { $fut_type {
items: <($(Option<$T>,)+)>::default(), items: <($(Option<$T>,)+)>::default(),
futs: ($($T::from_request(req, payload).into_future(),)+), futs: ($($T::from_request(req, payload).into_future(),)+),
@@ -193,12 +210,12 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
} }
#[doc(hidden)] #[doc(hidden)]
pub struct $fut_type<P, $($T: FromRequest<P>),+> { pub struct $fut_type<$($T: FromRequest),+> {
items: ($(Option<$T>,)+), items: ($(Option<$T>,)+),
futs: ($(<$T::Future as futures::IntoFuture>::Future,)+), futs: ($(<$T::Future as futures::IntoFuture>::Future,)+),
} }
impl<P, $($T: FromRequest<P>),+> Future for $fut_type<P, $($T),+> impl<$($T: FromRequest),+> Future for $fut_type<$($T),+>
{ {
type Item = ($($T,)+); type Item = ($($T,)+);
type Error = Error; type Error = Error;

View File

@@ -242,13 +242,13 @@ where
} }
/// Extract arguments from request /// Extract arguments from request
pub struct Extract<P, T: FromRequest<P>, S> { pub struct Extract<T: FromRequest, S> {
config: Rc<RefCell<Option<Rc<Extensions>>>>, config: Rc<RefCell<Option<Rc<Extensions>>>>,
service: S, service: S,
_t: PhantomData<(P, T)>, _t: PhantomData<T>,
} }
impl<P, T: FromRequest<P>, S> Extract<P, T, S> { impl<T: FromRequest, S> Extract<T, S> {
pub fn new(config: Rc<RefCell<Option<Rc<Extensions>>>>, service: S) -> Self { pub fn new(config: Rc<RefCell<Option<Rc<Extensions>>>>, service: S) -> Self {
Extract { Extract {
config, config,
@@ -258,16 +258,16 @@ impl<P, T: FromRequest<P>, S> Extract<P, T, S> {
} }
} }
impl<P, T: FromRequest<P>, S> NewService for Extract<P, T, S> impl<T: FromRequest, S> NewService for Extract<T, S>
where where
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void> S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>
+ Clone, + Clone,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = (Error, ServiceRequest<P>); type Error = (Error, ServiceRequest);
type InitError = (); type InitError = ();
type Service = ExtractService<P, T, S>; type Service = ExtractService<T, S>;
type Future = FutureResult<Self::Service, ()>; type Future = FutureResult<Self::Service, ()>;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
@@ -279,27 +279,27 @@ where
} }
} }
pub struct ExtractService<P, T: FromRequest<P>, S> { pub struct ExtractService<T: FromRequest, S> {
config: Option<Rc<Extensions>>, config: Option<Rc<Extensions>>,
service: S, service: S,
_t: PhantomData<(P, T)>, _t: PhantomData<T>,
} }
impl<P, T: FromRequest<P>, S> Service for ExtractService<P, T, S> impl<T: FromRequest, S> Service for ExtractService<T, S>
where where
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void> S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>
+ Clone, + Clone,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = (Error, ServiceRequest<P>); type Error = (Error, ServiceRequest);
type Future = ExtractResponse<P, T, S>; type Future = ExtractResponse<T, S>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(())) Ok(Async::Ready(()))
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
let (mut req, mut payload) = req.into_parts(); let (mut req, mut payload) = req.into_parts();
req.set_route_data(self.config.clone()); req.set_route_data(self.config.clone());
let fut = T::from_request(&req, &mut payload).into_future(); let fut = T::from_request(&req, &mut payload).into_future();
@@ -313,19 +313,19 @@ where
} }
} }
pub struct ExtractResponse<P, T: FromRequest<P>, S: Service> { pub struct ExtractResponse<T: FromRequest, S: Service> {
req: Option<(HttpRequest, Payload<P>)>, req: Option<(HttpRequest, Payload)>,
service: S, service: S,
fut: <T::Future as IntoFuture>::Future, fut: <T::Future as IntoFuture>::Future,
fut_s: Option<S::Future>, fut_s: Option<S::Future>,
} }
impl<P, T: FromRequest<P>, S> Future for ExtractResponse<P, T, S> impl<T: FromRequest, S> Future for ExtractResponse<T, S>
where where
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>, S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>,
{ {
type Item = ServiceResponse; type Item = ServiceResponse;
type Error = (Error, ServiceRequest<P>); type Error = (Error, ServiceRequest);
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut_s { if let Some(ref mut fut) = self.fut_s {

View File

@@ -30,7 +30,7 @@ impl ConnectionInfo {
let mut host = None; let mut host = None;
let mut scheme = None; let mut scheme = None;
let mut remote = None; let mut remote = None;
let peer = None; let mut peer = None;
// load forwarded header // load forwarded header
for hdr in req.headers.get_all(&header::FORWARDED) { for hdr in req.headers.get_all(&header::FORWARDED) {
@@ -116,10 +116,10 @@ impl ConnectionInfo {
remote = h.split(',').next().map(|v| v.trim()); remote = h.split(',').next().map(|v| v.trim());
} }
} }
// if remote.is_none() { if remote.is_none() {
// get peeraddr from socketaddr // get peeraddr from socketaddr
// peer = req.peer_addr().map(|addr| format!("{}", addr)); peer = req.peer_addr.map(|addr| format!("{}", addr));
// } }
} }
ConnectionInfo { ConnectionInfo {

View File

@@ -67,7 +67,6 @@
//! ## Package feature //! ## Package feature
//! //!
//! * `client` - enables http client //! * `client` - enables http client
//! * `tls` - enables ssl support via `native-tls` crate
//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2` //! * `ssl` - enables ssl support via `openssl` crate, supports `http/2`
//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2` //! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2`
//! * `secure-cookies` - enables secure cookies support, includes `ring` crate as //! * `secure-cookies` - enables secure cookies support, includes `ring` crate as
@@ -134,8 +133,7 @@ pub mod dev {
//! use actix_web::dev::*; //! use actix_web::dev::*;
//! ``` //! ```
pub use crate::app::AppRouter; pub use crate::config::{AppConfig, AppService};
pub use crate::config::{AppConfig, ServiceConfig};
pub use crate::info::ConnectionInfo; pub use crate::info::ConnectionInfo;
pub use crate::rmap::ResourceMap; pub use crate::rmap::ResourceMap;
pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse}; pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse};
@@ -144,6 +142,7 @@ pub mod dev {
pub use crate::types::readlines::Readlines; pub use crate::types::readlines::Readlines;
pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody}; pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody};
pub use actix_http::encoding::Decoder as Decompress;
pub use actix_http::ResponseBuilder as HttpResponseBuilder; pub use actix_http::ResponseBuilder as HttpResponseBuilder;
pub use actix_http::{ pub use actix_http::{
Extensions, Payload, PayloadStream, RequestHead, ResponseHead, Extensions, Payload, PayloadStream, RequestHead, ResponseHead,

View File

@@ -41,11 +41,11 @@ impl<B> BodyEncoding for Response<B> {
/// To disable compression set encoding to `ContentEncoding::Identity` value. /// To disable compression set encoding to `ContentEncoding::Identity` value.
/// ///
/// ```rust /// ```rust
/// use actix_web::{web, middleware::encoding, App, HttpResponse}; /// use actix_web::{web, middleware, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new()
/// .wrap(encoding::Compress::default()) /// .wrap(middleware::Compress::default())
/// .service( /// .service(
/// web::resource("/test") /// web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok())) /// .route(web::get().to(|| HttpResponse::Ok()))
@@ -68,12 +68,12 @@ impl Default for Compress {
} }
} }
impl<S, P, B> Transform<S> for Compress impl<S, B> Transform<S> for Compress
where where
B: MessageBody, B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<Encoder<B>>; type Response = ServiceResponse<Encoder<B>>;
type Error = S::Error; type Error = S::Error;
type InitError = (); type InitError = ();
@@ -93,21 +93,21 @@ pub struct CompressMiddleware<S> {
encoding: ContentEncoding, encoding: ContentEncoding,
} }
impl<S, P, B> Service for CompressMiddleware<S> impl<S, B> Service for CompressMiddleware<S>
where where
B: MessageBody, B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<Encoder<B>>; type Response = ServiceResponse<Encoder<B>>;
type Error = S::Error; type Error = S::Error;
type Future = CompressResponse<S, P, B>; type Future = CompressResponse<S, B>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready() self.service.poll_ready()
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
// negotiate content-encoding // 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() { if let Ok(enc) = val.to_str() {
@@ -128,20 +128,20 @@ where
} }
#[doc(hidden)] #[doc(hidden)]
pub struct CompressResponse<S, P, B> pub struct CompressResponse<S, B>
where where
S: Service, S: Service,
B: MessageBody, B: MessageBody,
{ {
fut: S::Future, fut: S::Future,
encoding: ContentEncoding, encoding: ContentEncoding,
_t: PhantomData<(P, B)>, _t: PhantomData<(B)>,
} }
impl<S, P, B> Future for CompressResponse<S, P, B> impl<S, B> Future for CompressResponse<S, B>
where where
B: MessageBody, B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
{ {
type Item = ServiceResponse<Encoder<B>>; type Item = ServiceResponse<Encoder<B>>;
type Error = S::Error; type Error = S::Error;

View File

@@ -475,9 +475,9 @@ fn cors<'a>(
parts.as_mut() parts.as_mut()
} }
impl<S, P, B> IntoTransform<CorsFactory, S> for Cors impl<S, B> IntoTransform<CorsFactory, S> for Cors
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static, S::Error: 'static,
B: 'static, B: 'static,
@@ -537,14 +537,14 @@ pub struct CorsFactory {
inner: Rc<Inner>, inner: Rc<Inner>,
} }
impl<S, P, B> Transform<S> for CorsFactory impl<S, B> Transform<S> for CorsFactory
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static, S::Error: 'static,
B: 'static, B: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = S::Error; type Error = S::Error;
type InitError = (); type InitError = ();
@@ -678,14 +678,14 @@ impl Inner {
} }
} }
impl<S, P, B> Service for CorsMiddleware<S> impl<S, B> Service for CorsMiddleware<S>
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static, S::Error: 'static,
B: 'static, B: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = S::Error; type Error = S::Error;
type Future = Either< type Future = Either<
@@ -697,7 +697,7 @@ where
self.service.poll_ready() self.service.poll_ready()
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
if self.inner.preflight && Method::OPTIONS == *req.method() { if self.inner.preflight && Method::OPTIONS == *req.method() {
if let Err(e) = self if let Err(e) = self
.inner .inner
@@ -815,13 +815,12 @@ mod tests {
use actix_service::{FnService, Transform}; use actix_service::{FnService, Transform};
use super::*; use super::*;
use crate::dev::PayloadStream;
use crate::test::{self, block_on, TestRequest}; use crate::test::{self, block_on, TestRequest};
impl Cors { impl Cors {
fn finish<S, P, B>(self, srv: S) -> CorsMiddleware<S> fn finish<S, B>(self, srv: S) -> CorsMiddleware<S>
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>
+ 'static, + 'static,
S::Future: 'static, S::Future: 'static,
S::Error: 'static, S::Error: 'static,
@@ -849,7 +848,18 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn default() {
let mut cors =
block_on(Cors::default().new_transform(test::ok_service())).unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com")
.to_srv_request();
let resp = test::call_service(&mut cors, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
@@ -869,7 +879,7 @@ mod tests {
assert!(cors.inner.validate_allowed_method(req.head()).is_err()); assert!(cors.inner.validate_allowed_method(req.head()).is_err());
assert!(cors.inner.validate_allowed_headers(req.head()).is_err()); assert!(cors.inner.validate_allowed_headers(req.head()).is_err());
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
@@ -889,7 +899,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"*"[..], &b"*"[..],
resp.headers() resp.headers()
@@ -935,7 +945,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
@@ -974,7 +984,7 @@ mod tests {
.method(Method::GET) .method(Method::GET)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
@@ -983,7 +993,7 @@ mod tests {
let mut cors = Cors::new().disable_preflight().finish(test::ok_service()); let mut cors = Cors::new().disable_preflight().finish(test::ok_service());
let req = TestRequest::default().method(Method::GET).to_srv_request(); let req = TestRequest::default().method(Method::GET).to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert!(resp assert!(resp
.headers() .headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN) .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
@@ -992,7 +1002,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"https://www.example.com"[..], &b"https://www.example.com"[..],
resp.headers() resp.headers()
@@ -1019,7 +1029,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"*"[..], &b"*"[..],
resp.headers() resp.headers()
@@ -1057,7 +1067,7 @@ mod tests {
.allowed_headers(exposed_headers.clone()) .allowed_headers(exposed_headers.clone())
.expose_headers(exposed_headers.clone()) .expose_headers(exposed_headers.clone())
.allowed_header(header::CONTENT_TYPE) .allowed_header(header::CONTENT_TYPE)
.finish(FnService::new(move |req: ServiceRequest<PayloadStream>| { .finish(FnService::new(move |req: ServiceRequest| {
req.into_response( req.into_response(
HttpResponse::Ok().header(header::VARY, "Accept").finish(), HttpResponse::Ok().header(header::VARY, "Accept").finish(),
) )
@@ -1065,7 +1075,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"Accept, Origin"[..], &b"Accept, Origin"[..],
resp.headers().get(header::VARY).unwrap().as_bytes() resp.headers().get(header::VARY).unwrap().as_bytes()
@@ -1081,7 +1091,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
let origins_str = resp let origins_str = resp
.headers() .headers()
@@ -1105,7 +1115,7 @@ mod tests {
.method(Method::GET) .method(Method::GET)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"https://example.com"[..], &b"https://example.com"[..],
resp.headers() resp.headers()
@@ -1118,7 +1128,7 @@ mod tests {
.method(Method::GET) .method(Method::GET)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"https://example.org"[..], &b"https://example.org"[..],
resp.headers() resp.headers()
@@ -1141,7 +1151,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"https://example.com"[..], &b"https://example.com"[..],
resp.headers() resp.headers()
@@ -1155,7 +1165,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"https://example.org"[..], &b"https://example.org"[..],
resp.headers() resp.headers()

View File

@@ -1,75 +0,0 @@
//! Chain service for decompressing request payload.
use std::marker::PhantomData;
use actix_http::encoding::Decoder;
use actix_service::{NewService, Service};
use bytes::Bytes;
use futures::future::{ok, FutureResult};
use futures::{Async, Poll, Stream};
use crate::dev::Payload;
use crate::error::{Error, PayloadError};
use crate::service::ServiceRequest;
/// `Middleware` for decompressing request's payload.
/// `Decompress` middleware must be added with `App::chain()` method.
///
/// ```rust
/// use actix_web::{web, middleware::encoding, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .chain(encoding::Decompress::new())
/// .service(
/// web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
pub struct Decompress<P>(PhantomData<P>);
impl<P> Decompress<P>
where
P: Stream<Item = Bytes, Error = PayloadError>,
{
pub fn new() -> Self {
Decompress(PhantomData)
}
}
impl<P> NewService for Decompress<P>
where
P: Stream<Item = Bytes, Error = PayloadError>,
{
type Request = ServiceRequest<P>;
type Response = ServiceRequest<Decoder<Payload<P>>>;
type Error = Error;
type InitError = ();
type Service = Decompress<P>;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &()) -> Self::Future {
ok(Decompress(PhantomData))
}
}
impl<P> Service for Decompress<P>
where
P: Stream<Item = Bytes, Error = PayloadError>,
{
type Request = ServiceRequest<P>;
type Response = ServiceRequest<Decoder<Payload<P>>>;
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: ServiceRequest<P>) -> Self::Future {
let (req, payload) = req.into_parts();
let payload = Decoder::from_headers(payload, req.headers());
ok(ServiceRequest::from_parts(req, Payload::Stream(payload)))
}
}

View File

@@ -85,12 +85,12 @@ impl DefaultHeaders {
} }
} }
impl<S, P, B> Transform<S> for DefaultHeaders impl<S, B> Transform<S> for DefaultHeaders
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
S::Future: 'static, S::Future: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = S::Error; type Error = S::Error;
type InitError = (); type InitError = ();
@@ -110,12 +110,12 @@ pub struct DefaultHeadersMiddleware<S> {
inner: Rc<Inner>, inner: Rc<Inner>,
} }
impl<S, P, B> Service for DefaultHeadersMiddleware<S> impl<S, B> Service for DefaultHeadersMiddleware<S>
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
S::Future: 'static, S::Future: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = S::Error; type Error = S::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>; type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
@@ -124,7 +124,7 @@ where
self.service.poll_ready() self.service.poll_ready()
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
let inner = self.inner.clone(); let inner = self.inner.clone();
Box::new(self.service.call(req).map(move |mut res| { Box::new(self.service.call(req).map(move |mut res| {
@@ -171,7 +171,7 @@ mod tests {
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let req = TestRequest::default().to_srv_request(); let req = TestRequest::default().to_srv_request();
let srv = FnService::new(|req: ServiceRequest<_>| { let srv = FnService::new(|req: ServiceRequest| {
req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish()) req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish())
}); });
let mut mw = block_on( let mut mw = block_on(
@@ -186,7 +186,7 @@ mod tests {
#[test] #[test]
fn test_content_type() { fn test_content_type() {
let srv = FnService::new(|req: ServiceRequest<_>| { let srv = FnService::new(|req: ServiceRequest| {
req.into_response(HttpResponse::Ok().finish()) req.into_response(HttpResponse::Ok().finish())
}); });
let mut mw = let mut mw =

View File

@@ -81,18 +81,14 @@ impl<B> ErrorHandlers<B> {
} }
} }
impl<S, P, B> Transform<S> for ErrorHandlers<B> impl<S, B> Transform<S> for ErrorHandlers<B>
where where
S: Service< S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static, S::Error: 'static,
B: 'static, B: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
@@ -113,18 +109,14 @@ pub struct ErrorHandlersMiddleware<S, B> {
handlers: Rc<HashMap<StatusCode, Box<ErrorHandler<B>>>>, handlers: Rc<HashMap<StatusCode, Box<ErrorHandler<B>>>>,
} }
impl<S, P, B> Service for ErrorHandlersMiddleware<S, B> impl<S, B> Service for ErrorHandlersMiddleware<S, B>
where where
S: Service< S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static, S::Error: 'static,
B: 'static, B: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = Error; type Error = Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>; type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
@@ -133,7 +125,7 @@ where
self.service.poll_ready() self.service.poll_ready()
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
let handlers = self.handlers.clone(); let handlers = self.handlers.clone();
Box::new(self.service.call(req).and_then(move |res| { Box::new(self.service.call(req).and_then(move |res| {
@@ -169,7 +161,7 @@ mod tests {
#[test] #[test]
fn test_handler() { fn test_handler() {
let srv = FnService::new(|req: ServiceRequest<_>| { let srv = FnService::new(|req: ServiceRequest| {
req.into_response(HttpResponse::InternalServerError().finish()) req.into_response(HttpResponse::InternalServerError().finish())
}); });
@@ -180,7 +172,7 @@ mod tests {
) )
.unwrap(); .unwrap();
let resp = test::call_success(&mut mw, TestRequest::default().to_srv_request()); let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
} }
@@ -195,7 +187,7 @@ mod tests {
#[test] #[test]
fn test_handler_async() { fn test_handler_async() {
let srv = FnService::new(|req: ServiceRequest<_>| { let srv = FnService::new(|req: ServiceRequest| {
req.into_response(HttpResponse::InternalServerError().finish()) req.into_response(HttpResponse::InternalServerError().finish())
}); });
@@ -206,7 +198,7 @@ mod tests {
) )
.unwrap(); .unwrap();
let resp = test::call_success(&mut mw, TestRequest::default().to_srv_request()); let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
} }
} }

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