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

Compare commits

...

54 Commits

Author SHA1 Message Date
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
116 changed files with 4925 additions and 2462 deletions

View File

@@ -42,7 +42,7 @@ script:
after_success:
- |
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 &&
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 &&

View File

@@ -1,5 +1,50 @@
# 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
### Added
@@ -18,6 +63,10 @@
* Move multipart support to actix-multipart crate
### Fixed
* Fix body propagation in Response::from_error. #760
## [1.0.0-alpha.3] - 2019-04-02

View File

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

View File

@@ -1,8 +1,100 @@
## 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
and then access it from handlers either using a Data extractor or using
HttpRequest's api.
and then access it from handlers either using a Data extractor or using
HttpRequest's api.
instead of
@@ -36,7 +128,7 @@ HttpRequest's api.
```
* AsyncResponder is deprecated.
* AsyncResponder is removed.
instead of
@@ -52,6 +144,69 @@ HttpRequest's api.
.. 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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-files"
version = "0.1.0-alpha.4"
version = "0.1.0-alpha.6"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for actix web."
readme = "README.md"
@@ -18,7 +18,7 @@ name = "actix_files"
path = "src/lib.rs"
[dependencies]
actix-web = "1.0.0-alpha.4"
actix-web = "1.0.0-alpha.6"
actix-service = "0.3.4"
bitflags = "1"
bytes = "0.4"
@@ -31,4 +31,4 @@ percent-encoding = "1.0"
v_htmlescape = "0.4"
[dev-dependencies]
actix-web = { version = "1.0.0-alpha.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::{IntoNewService, NewService, Service};
use actix_web::dev::{
HttpServiceFactory, Payload, ResourceDef, ServiceConfig, ServiceRequest,
AppService, HttpServiceFactory, Payload, ResourceDef, ServiceRequest,
ServiceResponse,
};
use actix_web::error::{BlockingError, Error, ErrorInternalServerError};
@@ -32,9 +32,8 @@ use self::error::{FilesError, UriSegmentError};
pub use crate::named::NamedFile;
pub use crate::range::HttpRange;
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>;
type HttpNewService<P> =
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
/// 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
@@ -225,18 +224,18 @@ type MimeOverride = Fn(&mime::Name) -> DispositionType;
/// .service(fs::Files::new("/static", "."));
/// }
/// ```
pub struct Files<S> {
pub struct Files {
path: String,
directory: PathBuf,
index: Option<String>,
show_index: bool,
default: Rc<RefCell<Option<Rc<HttpNewService<S>>>>>,
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags,
}
impl<S> Clone for Files<S> {
impl Clone for Files {
fn clone(&self) -> Self {
Self {
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.
///
/// `File` uses `ThreadPool` for blocking filesystem operations.
/// By default pool with 5x threads of available cpus is used.
/// 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());
if !dir.is_dir() {
log::error!("Specified path is not a directory");
@@ -335,7 +334,7 @@ impl<S: 'static> Files<S> {
where
F: IntoNewService<U>,
U: NewService<
Request = ServiceRequest<S>,
Request = ServiceRequest,
Response = ServiceResponse,
Error = Error,
> + 'static,
@@ -349,11 +348,8 @@ impl<S: 'static> Files<S> {
}
}
impl<P> HttpServiceFactory<P> for Files<P>
where
P: 'static,
{
fn register(self, config: &mut ServiceConfig<P>) {
impl HttpServiceFactory for Files {
fn register(self, config: &mut AppService) {
if self.default.borrow().is_none() {
*self.default.borrow_mut() = Some(config.default_service());
}
@@ -366,11 +362,11 @@ where
}
}
impl<P: 'static> NewService for Files<P> {
type Request = ServiceRequest<P>;
impl NewService for Files {
type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = Error;
type Service = FilesService<P>;
type Service = FilesService;
type 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,
index: Option<String>,
show_index: bool,
default: Option<HttpService<P>>,
default: Option<HttpService>,
renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags,
}
impl<P> FilesService<P> {
impl FilesService {
fn handle_err(
&mut self,
e: io::Error,
req: HttpRequest,
payload: Payload<P>,
payload: Payload,
) -> Either<
FutureResult<ServiceResponse, Error>,
Box<Future<Item = ServiceResponse, Error = Error>>,
@@ -430,8 +426,8 @@ impl<P> FilesService<P> {
}
}
impl<P> Service for FilesService<P> {
type Request = ServiceRequest<P>;
impl Service for FilesService {
type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = Error;
type Future = Either<
@@ -443,7 +439,7 @@ impl<P> Service for FilesService<P> {
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 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 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())
}
}
@@ -570,6 +567,7 @@ mod tests {
self, ContentDisposition, DispositionParam, DispositionType,
};
use actix_web::http::{Method, StatusCode};
use actix_web::middleware::Compress;
use actix_web::test::{self, TestRequest};
use actix_web::App;
@@ -777,7 +775,7 @@ mod tests {
);
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);
let content_disposition = response
@@ -801,7 +799,7 @@ mod tests {
.uri("/t%65st/Cargo.toml")
.header(header::RANGE, "bytes=10-20")
.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);
// Invalid range header
@@ -809,7 +807,7 @@ mod tests {
.uri("/t%65st/Cargo.toml")
.header(header::RANGE, "bytes=1-0")
.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);
}
@@ -826,7 +824,7 @@ mod tests {
.header(header::RANGE, "bytes=10-20")
.to_request();
let response = test::call_success(&mut srv, request);
let response = test::call_service(&mut srv, request);
let contentrange = response
.headers()
.get(header::CONTENT_RANGE)
@@ -841,7 +839,7 @@ mod tests {
.uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-5")
.to_request();
let response = test::call_success(&mut srv, request);
let response = test::call_service(&mut srv, request);
let contentrange = response
.headers()
@@ -864,7 +862,7 @@ mod tests {
.uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-20")
.to_request();
let response = test::call_success(&mut srv, request);
let response = test::call_service(&mut srv, request);
let contentlength = response
.headers()
@@ -880,7 +878,7 @@ mod tests {
.uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-8")
.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);
// Without range header
@@ -888,7 +886,7 @@ mod tests {
.uri("/t%65st/tests/test.binary")
// .no_default_headers()
.to_request();
let response = test::call_success(&mut srv, request);
let response = test::call_service(&mut srv, request);
let contentlength = response
.headers()
@@ -903,7 +901,7 @@ mod tests {
let request = TestRequest::get()
.uri("/t%65st/tests/test.binary")
.to_request();
let mut response = test::call_success(&mut srv, request);
let mut response = test::call_service(&mut srv, request);
// with enabled compression
// {
@@ -934,7 +932,7 @@ mod tests {
let request = TestRequest::get()
.uri("/tests/test%20space.binary")
.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);
let bytes =
@@ -965,7 +963,7 @@ mod tests {
#[test]
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(|| {
NamedFile::open("Cargo.toml")
.unwrap()
@@ -977,14 +975,14 @@ mod tests {
.uri("/")
.header(header::ACCEPT_ENCODING, "gzip")
.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!(!res.headers().contains_key(header::CONTENT_ENCODING));
}
#[test]
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(|| {
NamedFile::open("Cargo.toml")
.unwrap()
@@ -996,7 +994,7 @@ mod tests {
.uri("/")
.header(header::ACCEPT_ENCODING, "gzip")
.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.headers()
@@ -1023,20 +1021,20 @@ mod tests {
);
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);
let mut srv = test::init_service(App::new().service(Files::new("/", ".")));
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);
let mut srv = test::init_service(
App::new().service(Files::new("/", ".").show_files_listing()),
);
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!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/html; charset=utf-8"
@@ -1053,15 +1051,15 @@ mod tests {
#[test]
fn test_static_files_bad_directory() {
let _st: Files<()> = Files::new("/", "missing");
let _st: Files<()> = Files::new("/", "Cargo.toml");
let _st: Files = Files::new("/", "missing");
let _st: Files = Files::new("/", "Cargo.toml");
}
#[test]
fn test_default_handler_file_missing() {
let mut st = test::block_on(
Files::new("/", ".")
.default_handler(|req: ServiceRequest<_>| {
.default_handler(|req: ServiceRequest| {
Ok(req.into_response(HttpResponse::Ok().body("default content")))
})
.new_service(&()),
@@ -1069,7 +1067,7 @@ mod tests {
.unwrap();
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);
let bytes =
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,
};
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 crate::range::HttpRange;

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

@@ -0,0 +1,37 @@
[package]
name = "actix-framed"
version = "0.1.0-alpha.1"
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-alpha.5"
bytes = "0.4"
futures = "0.1.25"
log = "0.4"
[dev-dependencies]
actix-server = { version = "0.4.1", features=["ssl"] }
actix-connect = { version = "0.1.4", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.3", 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)

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

@@ -0,0 +1,5 @@
# Changes
## [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
## [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
### Added
* Allow to use custom `Expect` handler
* Add minimal `std::error::Error` impl for `Error`
### Changed

View File

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

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
@@ -8,7 +8,7 @@ Actix http
* [API Documentation](https://docs.rs/actix-http/)
* [Chat on gitter](https://gitter.im/actix/actix)
* 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

View File

@@ -30,13 +30,13 @@ impl BodySize {
/// Type that provides this trait can be streamed to a peer.
pub trait MessageBody {
fn length(&self) -> BodySize;
fn size(&self) -> BodySize;
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error>;
}
impl MessageBody for () {
fn length(&self) -> BodySize {
fn size(&self) -> BodySize {
BodySize::Empty
}
@@ -46,8 +46,8 @@ impl MessageBody for () {
}
impl<T: MessageBody> MessageBody for Box<T> {
fn length(&self) -> BodySize {
self.as_ref().length()
fn size(&self) -> BodySize {
self.as_ref().size()
}
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> {
fn length(&self) -> BodySize {
fn size(&self) -> BodySize {
match self {
ResponseBody::Body(ref body) => body.length(),
ResponseBody::Other(ref body) => body.length(),
ResponseBody::Body(ref body) => body.size(),
ResponseBody::Other(ref body) => body.size(),
}
}
@@ -135,12 +135,12 @@ impl Body {
}
impl MessageBody for Body {
fn length(&self) -> BodySize {
fn size(&self) -> BodySize {
match self {
Body::None => BodySize::None,
Body::Empty => BodySize::Empty,
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 {
match *self {
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::Message(_) => write!(f, "Body::Message(_)"),
}
@@ -235,7 +235,7 @@ impl From<BytesMut> for Body {
}
impl MessageBody for Bytes {
fn length(&self) -> BodySize {
fn size(&self) -> BodySize {
BodySize::Sized(self.len())
}
@@ -249,7 +249,7 @@ impl MessageBody for Bytes {
}
impl MessageBody for BytesMut {
fn length(&self) -> BodySize {
fn size(&self) -> BodySize {
BodySize::Sized(self.len())
}
@@ -265,7 +265,7 @@ impl MessageBody for BytesMut {
}
impl MessageBody for &'static str {
fn length(&self) -> BodySize {
fn size(&self) -> BodySize {
BodySize::Sized(self.len())
}
@@ -281,7 +281,7 @@ impl MessageBody for &'static str {
}
impl MessageBody for &'static [u8] {
fn length(&self) -> BodySize {
fn size(&self) -> BodySize {
BodySize::Sized(self.len())
}
@@ -297,7 +297,7 @@ impl MessageBody for &'static [u8] {
}
impl MessageBody for Vec<u8> {
fn length(&self) -> BodySize {
fn size(&self) -> BodySize {
BodySize::Sized(self.len())
}
@@ -314,7 +314,7 @@ impl MessageBody for Vec<u8> {
}
impl MessageBody for String {
fn length(&self) -> BodySize {
fn size(&self) -> BodySize {
BodySize::Sized(self.len())
}
@@ -354,7 +354,7 @@ where
S: Stream<Item = Bytes, Error = E>,
E: Into<Error>,
{
fn length(&self) -> BodySize {
fn size(&self) -> BodySize {
BodySize::Stream
}
@@ -383,7 +383,7 @@ impl<S> MessageBody for SizedStream<S>
where
S: Stream<Item = Bytes, Error = Error>,
{
fn length(&self) -> BodySize {
fn size(&self) -> BodySize {
BodySize::Sized(self.size)
}
@@ -416,47 +416,117 @@ mod tests {
#[test]
fn test_static_str() {
assert_eq!(Body::from("").length(), BodySize::Sized(0));
assert_eq!(Body::from("test").length(), BodySize::Sized(4));
assert_eq!(Body::from("").size(), BodySize::Sized(0));
assert_eq!(Body::from("test").size(), BodySize::Sized(4));
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]
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_slice(b"test".as_ref()).length(),
Body::from_slice(b"test".as_ref()).size(),
BodySize::Sized(4)
);
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]
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!(Vec::from("test").size(), BodySize::Sized(4));
assert_eq!(
Vec::from("test").poll_next().unwrap(),
Async::Ready(Some(Bytes::from("test")))
);
}
#[test]
fn test_bytes() {
assert_eq!(Body::from(Bytes::from("test")).length(), BodySize::Sized(4));
assert_eq!(Body::from(Bytes::from("test")).get_ref(), b"test");
}
#[test]
fn test_string() {
let b = "test".to_owned();
assert_eq!(Body::from(b.clone()).length(), BodySize::Sized(4));
let mut b = Bytes::from("test");
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).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]
fn test_bytes_mut() {
let b = BytesMut::from("test");
assert_eq!(Body::from(b.clone()).length(), BodySize::Sized(4));
assert_eq!(Body::from(b).get_ref(), b"test");
let mut b = BytesMut::from("test");
assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4));
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::marker::PhantomData;
use actix_codec::Framed;
use actix_server_config::ServerConfig as SrvConfig;
use actix_service::{IntoNewService, NewService, Service};
use crate::body::MessageBody;
use crate::config::{KeepAlive, ServiceConfig};
use crate::error::Error;
use crate::h1::{ExpectHandler, H1Service};
use crate::h1::{Codec, ExpectHandler, H1Service, UpgradeHandler};
use crate::h2::H2Service;
use crate::request::Request;
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
/// builder-like pattern.
pub struct HttpServiceBuilder<T, S, X = ExpectHandler> {
pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
keep_alive: KeepAlive,
client_timeout: u64,
client_disconnect: u64,
expect: X,
upgrade: Option<U>,
_t: PhantomData<(T, S)>,
}
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler>
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler<T>>
where
S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>,
@@ -38,12 +40,13 @@ where
client_timeout: 5000,
client_disconnect: 0,
expect: ExpectHandler,
upgrade: None,
_t: PhantomData,
}
}
}
impl<T, S, X> HttpServiceBuilder<T, S, X>
impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
where
S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>,
@@ -51,11 +54,14 @@ where
X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>,
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.
///
/// 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
}
@@ -87,27 +93,51 @@ where
self
}
// #[cfg(feature = "ssl")]
// /// Configure alpn protocols for SslAcceptorBuilder.
// pub fn configure_openssl(
// builder: &mut openssl::ssl::SslAcceptorBuilder,
// ) -> io::Result<()> {
// let protos: &[u8] = b"\x02h2";
// builder.set_alpn_select_callback(|_, protos| {
// const H2: &[u8] = b"\x02h2";
// if protos.windows(3).any(|window| window == H2) {
// Ok(b"h2")
// } else {
// Err(openssl::ssl::AlpnError::NOACK)
// }
// });
// builder.set_alpn_protos(&protos)?;
/// Provide service for `EXPECT: 100-Continue` support.
///
/// Service get called with request that contains `EXPECT` header.
/// Service must return request in case of success, in that case
/// request will be forwarded to main service.
pub fn expect<F, X1>(self, expect: F) -> HttpServiceBuilder<T, S, X1, U>
where
F: IntoNewService<X1>,
X1: NewService<Request = Request, Response = Request>,
X1::Error: Into<Error>,
X1::InitError: fmt::Debug,
{
HttpServiceBuilder {
keep_alive: self.keep_alive,
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.
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
B: MessageBody + 'static,
F: IntoNewService<S, SrvConfig>,
@@ -120,7 +150,9 @@ where
self.client_timeout,
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.
@@ -142,7 +174,7 @@ where
}
/// 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
B: MessageBody + 'static,
F: IntoNewService<S, SrvConfig>,
@@ -157,5 +189,7 @@ where
self.client_disconnect,
);
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 super::error::SendRequestError;
use super::pool::Acquired;
use super::pool::{Acquired, Protocol};
use super::{h1proto, h2proto};
pub(crate) enum ConnectionType<Io> {
@@ -24,6 +24,8 @@ pub trait Connection {
type Io: AsyncRead + AsyncWrite;
type Future: Future<Item = (ResponseHead, Payload), Error = SendRequestError>;
fn protocol(&self) -> Protocol;
/// Send request and body
fn send_request<B: MessageBody + 'static>(
self,
@@ -94,6 +96,14 @@ where
type Io = T;
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>(
mut self,
head: RequestHead,
@@ -161,6 +171,13 @@ where
type Io = EitherIo<A, B>;
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>(
self,
head: RequestHead,

View File

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

View File

@@ -1,12 +1,14 @@
use std::io::Write;
use std::{io, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use bytes::Bytes;
use bytes::{BufMut, Bytes, BytesMut};
use futures::future::{ok, Either};
use futures::{Async, Future, Poll, Sink, Stream};
use crate::error::PayloadError;
use crate::h1;
use crate::http::header::{IntoHeaderValue, HOST};
use crate::message::{RequestHead, ResponseHead};
use crate::payload::{Payload, PayloadStream};
@@ -17,7 +19,7 @@ use crate::body::{BodySize, MessageBody};
pub(crate) fn send_request<T, B>(
io: T,
head: RequestHead,
mut head: RequestHead,
body: B,
created: time::Instant,
pool: Option<Acquired<T>>,
@@ -26,20 +28,41 @@ where
T: AsyncRead + AsyncWrite + 'static,
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 {
created,
pool,
io: Some(io),
};
let len = body.length();
let len = body.size();
// create Framed and send reqest
Framed::new(io, h1::ClientCodec::default())
.send((head, len).into())
.from_err()
// send request body
.and_then(move |framed| match body.length() {
.and_then(move |framed| match body.size() {
BodySize::None | BodySize::Empty | BodySize::Sized(0) => {
Either::A(ok(framed))
}

View File

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

View File

@@ -9,3 +9,4 @@ mod pool;
pub use self::connection::Connection;
pub use self::connector::Connector;
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::error::ConnectError;
#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq)]
/// Protocol version
pub enum Protocol {
Http1,
Http2,

View File

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

View File

@@ -1,11 +1,13 @@
//! Error and Result module
use std::cell::RefCell;
use std::io::Write;
use std::str::Utf8Error;
use std::string::FromUtf8Error;
use std::{fmt, io, result};
pub use actix_threadpool::BlockingError;
use actix_utils::timeout::TimeoutError;
use bytes::BytesMut;
use derive_more::{Display, From};
use futures::Canceled;
use http::uri::InvalidUri;
@@ -17,7 +19,9 @@ use serde_urlencoded::ser::Error as FormError;
use tokio_timer::Error as TimerError;
// re-export for convinience
use crate::body::Body;
pub use crate::cookie::ParseError as CookieParseError;
use crate::helpers::Writer;
use crate::response::Response;
/// 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 {
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 {
@@ -137,6 +153,10 @@ impl ResponseError for TimerError {}
/// `InternalServerError` for `SslError`
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`
impl ResponseError for DeError {
fn error_response(&self) -> Response {
@@ -350,6 +370,9 @@ pub enum DispatchError {
/// Service error
Service(Error),
/// Upgrade service error
Upgrade,
/// An `io::Error` that occurred while trying to read or write to a network
/// stream.
#[display(fmt = "IO error: {}", _0)]
@@ -474,7 +497,16 @@ where
{
fn error_response(&self) -> Response {
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) => {
if let Some(resp) = resp.borrow_mut().take() {
resp
@@ -484,6 +516,11 @@ where
}
}
}
/// Constructs an error response
fn render_response(&self) -> Response {
self.error_response()
}
}
/// Convert Response to a Error

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
use std::fmt;
use std::marker::PhantomData;
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_server_config::{Io, ServerConfig as SrvConfig};
use actix_codec::Framed;
use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig};
use actix_service::{IntoNewService, NewService, Service};
use actix_utils::cloneable::CloneableService;
use futures::future::{ok, FutureResult};
@@ -16,13 +16,14 @@ use crate::response::Response;
use super::codec::Codec;
use super::dispatcher::Dispatcher;
use super::{ExpectHandler, Message};
use super::{ExpectHandler, Message, UpgradeHandler};
/// `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,
cfg: ServiceConfig,
expect: X,
upgrade: Option<U>,
_t: PhantomData<(T, P, B)>,
}
@@ -42,6 +43,7 @@ where
cfg,
srv: service.into_new_service(),
expect: ExpectHandler,
upgrade: None,
_t: PhantomData,
}
}
@@ -55,12 +57,13 @@ where
cfg,
srv: service.into_new_service(),
expect: ExpectHandler,
upgrade: None,
_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
S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>,
@@ -68,24 +71,40 @@ where
S::InitError: fmt::Debug,
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
U: NewService<Request = Request, Response = Request>,
U::Error: Into<Error>,
U::InitError: fmt::Debug,
X1: NewService<Request = Request, Response = Request>,
X1::Error: Into<Error>,
X1::InitError: fmt::Debug,
{
H1Service {
expect,
cfg: self.cfg,
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,
}
}
}
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
T: AsyncRead + AsyncWrite,
T: IoStream,
S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
@@ -94,19 +113,24 @@ where
X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>,
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 Response = ();
type Error = DispatchError;
type InitError = ();
type Service = H1ServiceHandler<T, P, S::Service, B, X::Service>;
type Future = H1ServiceResponse<T, P, S, B, X>;
type Service = H1ServiceHandler<T, P, S::Service, B, X::Service, U::Service>;
type Future = H1ServiceResponse<T, P, S, B, X, U>;
fn new_service(&self, cfg: &SrvConfig) -> Self::Future {
H1ServiceResponse {
fut: self.srv.new_service(cfg).into_future(),
fut_ex: Some(self.expect.new_service(&())),
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(&())),
expect: None,
upgrade: None,
cfg: Some(self.cfg.clone()),
_t: PhantomData,
}
@@ -114,7 +138,7 @@ where
}
#[doc(hidden)]
pub struct H1ServiceResponse<T, P, S, B, X>
pub struct H1ServiceResponse<T, P, S, B, X, U>
where
S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>,
@@ -122,17 +146,22 @@ where
X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: NewService<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
{
fut: S::Future,
fut_ex: Option<X::Future>,
fut_upg: Option<U::Future>,
expect: Option<X::Service>,
upgrade: Option<U::Service>,
cfg: Option<ServiceConfig>,
_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
T: AsyncRead + AsyncWrite,
T: IoStream,
S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
@@ -141,8 +170,11 @@ where
X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>,
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 = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@@ -154,6 +186,14 @@ where
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
.fut
.poll()
@@ -162,19 +202,21 @@ where
self.cfg.take().unwrap(),
service,
self.expect.take().unwrap(),
self.upgrade.take(),
)))
}
}
/// `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>,
expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>,
cfg: ServiceConfig,
_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
S: Service<Request = Request>,
S::Error: Into<Error>,
@@ -182,31 +224,41 @@ where
B: MessageBody,
X: Service<Request = Request, Response = Request>,
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 {
srv: CloneableService::new(srv),
expect: CloneableService::new(expect),
upgrade: upgrade.map(|s| CloneableService::new(s)),
cfg,
_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
T: AsyncRead + AsyncWrite,
T: IoStream,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
type Request = Io<T, P>;
type Response = ();
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> {
let ready = self
@@ -243,6 +295,7 @@ where
self.cfg.clone(),
self.srv.clone(),
self.expect.clone(),
self.upgrade.clone(),
)
}
}
@@ -256,7 +309,7 @@ pub struct OneRequest<T, P> {
impl<T, P> OneRequest<T, P>
where
T: AsyncRead + AsyncWrite,
T: IoStream,
{
/// Create new `H1SimpleService` instance.
pub fn new() -> Self {
@@ -269,7 +322,7 @@ where
impl<T, P> NewService<SrvConfig> for OneRequest<T, P>
where
T: AsyncRead + AsyncWrite,
T: IoStream,
{
type Request = Io<T, P>;
type Response = (Request, Framed<T, Codec>);
@@ -295,7 +348,7 @@ pub struct OneRequestService<T, P> {
impl<T, P> Service for OneRequestService<T, P>
where
T: AsyncRead + AsyncWrite,
T: IoStream,
{
type Request = Io<T, P>;
type Response = (Request, Framed<T, Codec>);
@@ -319,14 +372,14 @@ where
#[doc(hidden)]
pub struct OneRequestServiceResponse<T>
where
T: AsyncRead + AsyncWrite,
T: IoStream,
{
framed: Option<Framed<T, Codec>>,
}
impl<T> Future for OneRequestServiceResponse<T>
where
T: AsyncRead + AsyncWrite,
T: IoStream,
{
type Item = (Request, Framed<T, Codec>);
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::marker::PhantomData;
use std::time::Instant;
use std::{fmt, mem};
use std::{fmt, mem, net};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_server_config::IoStream;
use actix_service::Service;
use actix_utils::cloneable::CloneableService;
use bitflags::bitflags;
@@ -29,14 +30,11 @@ use crate::response::Response;
const CHUNK_SIZE: usize = 16_384;
/// Dispatcher for HTTP/2 protocol
pub struct Dispatcher<
T: AsyncRead + AsyncWrite,
S: Service<Request = Request>,
B: MessageBody,
> {
pub struct Dispatcher<T: IoStream, S: Service<Request = Request>, B: MessageBody> {
service: CloneableService<S>,
connection: Connection<T, Bytes>,
config: ServiceConfig,
peer_addr: Option<net::SocketAddr>,
ka_expire: Instant,
ka_timer: Option<Delay>,
_t: PhantomData<B>,
@@ -44,7 +42,7 @@ pub struct Dispatcher<
impl<T, S, B> Dispatcher<T, S, B>
where
T: AsyncRead + AsyncWrite,
T: IoStream,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
@@ -56,6 +54,7 @@ where
connection: Connection<T, Bytes>,
config: ServiceConfig,
timeout: Option<Delay>,
peer_addr: Option<net::SocketAddr>,
) -> Self {
// let keepalive = config.keep_alive_enabled();
// let flags = if keepalive {
@@ -76,9 +75,10 @@ where
Dispatcher {
service,
config,
peer_addr,
connection,
ka_expire,
ka_timer,
connection,
_t: PhantomData,
}
}
@@ -86,7 +86,7 @@ where
impl<T, S, B> Future for Dispatcher<T, S, B>
where
T: AsyncRead + AsyncWrite,
T: IoStream,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
@@ -117,6 +117,7 @@ where
head.method = parts.method;
head.version = parts.version;
head.headers = parts.headers.into();
head.peer_addr = self.peer_addr;
tokio_current_thread::spawn(ServiceResponse::<S::Future, B> {
state: ServiceResponseState::ServiceCall(
self.service.call(req),
@@ -153,10 +154,10 @@ where
fn prepare_response(
&self,
head: &ResponseHead,
length: &mut BodySize,
size: &mut BodySize,
) -> http::Response<()> {
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(());
*res.status_mut() = head.status;
@@ -166,14 +167,14 @@ where
match head.status {
http::StatusCode::NO_CONTENT
| http::StatusCode::CONTINUE
| http::StatusCode::PROCESSING => *length = BodySize::None,
| http::StatusCode::PROCESSING => *size = BodySize::None,
http::StatusCode::SWITCHING_PROTOCOLS => {
skip_len = true;
*length = BodySize::Stream;
*size = BodySize::Stream;
}
_ => (),
}
let _ = match length {
let _ = match size {
BodySize::None | BodySize::Stream => None,
BodySize::Empty => res
.headers_mut()
@@ -229,16 +230,15 @@ where
let (res, body) = res.into().replace_body(());
let mut send = send.take().unwrap();
let mut length = body.length();
let h2_res = self.prepare_response(res.head(), &mut length);
let mut size = body.size();
let h2_res = self.prepare_response(res.head(), &mut size);
let stream = send
.send_response(h2_res, length.is_eof())
.map_err(|e| {
let stream =
send.send_response(h2_res, size.is_eof()).map_err(|e| {
trace!("Error sending h2 response: {:?}", e);
})?;
if length.is_eof() {
if size.is_eof() {
Ok(Async::Ready(()))
} else {
self.state = ServiceResponseState::SendPayload(stream, body);
@@ -251,16 +251,15 @@ where
let (res, body) = res.replace_body(());
let mut send = send.take().unwrap();
let mut length = body.length();
let h2_res = self.prepare_response(res.head(), &mut length);
let mut size = body.size();
let h2_res = self.prepare_response(res.head(), &mut size);
let stream = send
.send_response(h2_res, length.is_eof())
.map_err(|e| {
let stream =
send.send_response(h2_res, size.is_eof()).map_err(|e| {
trace!("Error sending h2 response: {:?}", e);
})?;
if length.is_eof() {
if size.is_eof() {
Ok(Async::Ready(()))
} else {
self.state = ServiceResponseState::SendPayload(

View File

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

View File

@@ -1,6 +1,7 @@
use std::{io, mem, ptr, slice};
use bytes::{BufMut, BytesMut};
use http::Version;
use std::{mem, ptr, slice};
const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
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)]
mod tests {
use super::*;

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
use std::cell::{Ref, RefMut};
use std::fmt;
use std::{fmt, net};
use http::{header, Method, Uri, Version};
@@ -139,6 +139,7 @@ impl<P> Request<P> {
}
/// Check if request requires connection upgrade
#[inline]
pub fn upgrade(&self) -> bool {
if let Some(conn) = self.head().headers.get(header::CONNECTION) {
if let Ok(s) = conn.to_str() {
@@ -147,6 +148,15 @@ impl<P> Request<P> {
}
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> {

View File

@@ -1,7 +1,7 @@
//! Http response
use std::cell::{Ref, RefMut};
use std::io::Write;
use std::{fmt, io, str};
use std::{fmt, str};
use bytes::{BufMut, Bytes, BytesMut};
use futures::future::{ok, FutureResult, IntoFuture};
@@ -51,13 +51,9 @@ impl Response<Body> {
/// Constructs an error response
#[inline]
pub fn from_error(error: Error) -> Response {
let mut resp = error.as_response_error().error_response();
let mut buf = BytesMut::new();
let _ = write!(Writer(&mut buf), "{}", error);
resp.headers_mut()
.insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain"));
let mut resp = error.as_response_error().render_response();
resp.error = Some(error);
resp.set_body(Body::from(buf))
resp
}
/// 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
pub fn drop_body(self) -> Response<()> {
Response {
@@ -264,7 +272,7 @@ impl<B: MessageBody> fmt::Debug for Response<B> {
for (key, val) in self.head.headers.iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val);
}
let _ = writeln!(f, " body: {:?}", self.body.length());
let _ = writeln!(f, " body: {:?}", self.body.size());
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
///
/// 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::{fmt, io};
use std::{fmt, io, net};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_server_config::{Io as ServerIo, Protocol, ServerConfig as SrvConfig};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_server_config::{
Io as ServerIo, IoStream, Protocol, ServerConfig as SrvConfig,
};
use actix_service::{IntoNewService, NewService, Service};
use actix_utils::cloneable::CloneableService;
use bytes::{Buf, BufMut, Bytes, BytesMut};
@@ -18,10 +20,11 @@ use crate::response::Response;
use crate::{h1, h2::Dispatcher};
/// `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,
cfg: ServiceConfig,
expect: X,
upgrade: Option<U>,
_t: PhantomData<(T, P, B)>,
}
@@ -57,6 +60,7 @@ where
cfg,
srv: service.into_new_service(),
expect: h1::ExpectHandler,
upgrade: None,
_t: PhantomData,
}
}
@@ -70,12 +74,13 @@ where
cfg,
srv: service.into_new_service(),
expect: h1::ExpectHandler,
upgrade: None,
_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
S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>,
@@ -88,24 +93,44 @@ where
/// Service get called with request that contains `EXPECT` header.
/// Service must return request in case of success, in that case
/// request will be forwarded to main service.
pub fn expect<U>(self, expect: U) -> HttpService<T, P, S, B, U>
pub fn expect<X1>(self, expect: X1) -> HttpService<T, P, S, B, X1, U>
where
U: NewService<Request = Request, Response = Request>,
U::Error: Into<Error>,
U::InitError: fmt::Debug,
X1: NewService<Request = Request, Response = Request>,
X1::Error: Into<Error>,
X1::InitError: fmt::Debug,
{
HttpService {
expect,
cfg: self.cfg,
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,
}
}
}
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
T: AsyncRead + AsyncWrite,
T: IoStream,
S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
@@ -115,19 +140,24 @@ where
X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>,
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 Response = ();
type Error = DispatchError;
type InitError = ();
type Service = HttpServiceHandler<T, P, S::Service, B, X::Service>;
type Future = HttpServiceResponse<T, P, S, B, X>;
type Service = HttpServiceHandler<T, P, S::Service, B, X::Service, U::Service>;
type Future = HttpServiceResponse<T, P, S, B, X, U>;
fn new_service(&self, cfg: &SrvConfig) -> Self::Future {
HttpServiceResponse {
fut: self.srv.new_service(cfg).into_future(),
fut_ex: Some(self.expect.new_service(&())),
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(&())),
expect: None,
upgrade: None,
cfg: Some(self.cfg.clone()),
_t: PhantomData,
}
@@ -135,17 +165,26 @@ where
}
#[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_ex: Option<X::Future>,
fut_upg: Option<U::Future>,
expect: Option<X::Service>,
upgrade: Option<U::Service>,
cfg: Option<ServiceConfig>,
_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
T: AsyncRead + AsyncWrite,
T: IoStream,
S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
@@ -155,8 +194,11 @@ where
X: NewService<Request = Request, Response = Request>,
X::Error: Into<Error>,
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 = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@@ -168,6 +210,14 @@ where
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
.fut
.poll()
@@ -176,19 +226,21 @@ where
self.cfg.take().unwrap(),
service,
self.expect.take().unwrap(),
self.upgrade.take(),
)))
}
}
/// `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>,
expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>,
cfg: ServiceConfig,
_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
S: Service<Request = Request>,
S::Error: Into<Error>,
@@ -197,20 +249,28 @@ where
B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>,
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 {
cfg,
srv: CloneableService::new(srv),
expect: CloneableService::new(expect),
upgrade: upgrade.map(|s| CloneableService::new(s)),
_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
T: AsyncRead + AsyncWrite,
T: IoStream,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
@@ -218,11 +278,13 @@ where
B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
{
type Request = ServerIo<T, P>;
type Response = ();
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> {
let ready = self
@@ -257,6 +319,7 @@ where
let (io, _, proto) = req.into_parts();
match proto {
Protocol::Http2 => {
let peer_addr = io.peer_addr();
let io = Io {
inner: io,
unread: None,
@@ -266,6 +329,7 @@ where
server::handshake(io),
self.cfg.clone(),
self.srv.clone(),
peer_addr,
))),
}
}
@@ -275,6 +339,7 @@ where
self.cfg.clone(),
self.srv.clone(),
self.expect.clone(),
self.upgrade.clone(),
)),
},
_ => HttpServiceHandlerResponse {
@@ -284,23 +349,26 @@ where
self.cfg.clone(),
self.srv.clone(),
self.expect.clone(),
self.upgrade.clone(),
))),
},
}
}
}
enum State<T, S, B, X>
enum State<T, S, B, X, U>
where
S: Service<Request = Request>,
S::Future: 'static,
S::Error: Into<Error>,
T: AsyncRead + AsyncWrite,
T: IoStream,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
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>),
Unknown(
Option<(
@@ -309,14 +377,22 @@ where
ServiceConfig,
CloneableService<S>,
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
T: AsyncRead + AsyncWrite,
T: IoStream,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
@@ -324,15 +400,17 @@ where
B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>,
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";
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
T: AsyncRead + AsyncWrite,
T: IoStream,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Future: 'static,
@@ -340,6 +418,8 @@ where
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
{
type Item = ();
type Error = DispatchError;
@@ -366,14 +446,19 @@ where
} else {
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[..] {
let peer_addr = io.peer_addr();
let io = Io {
inner: io,
unread: Some(buf),
};
self.state =
State::Handshake(Some((server::handshake(io), cfg, srv)));
self.state = State::Handshake(Some((
server::handshake(io),
cfg,
srv,
peer_addr,
)));
} else {
self.state = State::H1(h1::Dispatcher::with_timeout(
io,
@@ -383,6 +468,7 @@ where
None,
srv,
expect,
upgrade,
))
}
self.poll()
@@ -400,8 +486,8 @@ where
} else {
panic!()
};
let (_, cfg, srv) = data.take().unwrap();
self.state = State::H2(Dispatcher::new(srv, conn, cfg, None));
let (_, cfg, srv, peer_addr) = data.take().unwrap();
self.state = State::H2(Dispatcher::new(srv, conn, cfg, None, peer_addr));
self.poll()
}
}
@@ -453,3 +539,25 @@ impl<T: AsyncWrite> AsyncWrite for Io<T> {
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.
use std::fmt::Write as FmtWrite;
use std::io;
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::{HttpTryFrom, Method, Uri, Version};
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
@@ -181,3 +185,86 @@ impl TestRequest {
fn parts(parts: &mut Option<Inner>) -> &mut Inner {
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 crate::error::ResponseError;
use crate::httpmessage::HttpMessage;
use crate::request::Request;
use crate::message::RequestHead;
use crate::response::{Response, ResponseBuilder};
mod codec;
mod frame;
mod mask;
mod proto;
mod service;
mod transport;
pub use self::codec::{Codec, Frame, Message};
pub use self::frame::Parser;
pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode};
pub use self::service::VerifyWebSockets;
pub use self::transport::Transport;
/// Websocket protocol errors
@@ -112,7 +109,7 @@ impl ResponseError for HandshakeError {
// /// `protocols` is a sequence of known protocols. On successful handshake,
// /// the returned response headers contain the first protocol in this list
// /// which the server also knows.
pub fn handshake(req: &Request) -> Result<ResponseBuilder, HandshakeError> {
pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
verify_handshake(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,
// /// the returned response headers contain the first protocol in this list
// /// 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
if *req.method() != Method::GET {
if req.method != Method::GET {
return Err(HandshakeError::GetMethodRequired);
}
@@ -171,7 +168,7 @@ pub fn verify_handshake(req: &Request) -> Result<(), HandshakeError> {
/// Create websocket's handshake response
///
/// 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 = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap();
proto::hash_key(key.as_ref())
@@ -195,13 +192,13 @@ mod tests {
let req = TestRequest::default().method(Method::POST).finish();
assert_eq!(
HandshakeError::GetMethodRequired,
verify_handshake(&req).err().unwrap()
verify_handshake(req.head()).err().unwrap()
);
let req = TestRequest::default().finish();
assert_eq!(
HandshakeError::NoWebsocketUpgrade,
verify_handshake(&req).err().unwrap()
verify_handshake(req.head()).err().unwrap()
);
let req = TestRequest::default()
@@ -209,7 +206,7 @@ mod tests {
.finish();
assert_eq!(
HandshakeError::NoWebsocketUpgrade,
verify_handshake(&req).err().unwrap()
verify_handshake(req.head()).err().unwrap()
);
let req = TestRequest::default()
@@ -220,7 +217,7 @@ mod tests {
.finish();
assert_eq!(
HandshakeError::NoConnectionUpgrade,
verify_handshake(&req).err().unwrap()
verify_handshake(req.head()).err().unwrap()
);
let req = TestRequest::default()
@@ -235,7 +232,7 @@ mod tests {
.finish();
assert_eq!(
HandshakeError::NoVersionHeader,
verify_handshake(&req).err().unwrap()
verify_handshake(req.head()).err().unwrap()
);
let req = TestRequest::default()
@@ -254,7 +251,7 @@ mod tests {
.finish();
assert_eq!(
HandshakeError::UnsupportedVersion,
verify_handshake(&req).err().unwrap()
verify_handshake(req.head()).err().unwrap()
);
let req = TestRequest::default()
@@ -273,7 +270,7 @@ mod tests {
.finish();
assert_eq!(
HandshakeError::BadWebsocketKey,
verify_handshake(&req).err().unwrap()
verify_handshake(req.head()).err().unwrap()
);
let req = TestRequest::default()
@@ -296,7 +293,7 @@ mod tests {
.finish();
assert_eq!(
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() {
env_logger::init();
let mut srv = TestServer::new(move || {
HttpService::build()
.finish(|_| future::ok::<_, ()>(Response::Ok().body(STR)))
.map(|_| ())
HttpService::build().finish(|_| future::ok::<_, ()>(Response::Ok().body(STR)))
});
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());

View File

@@ -5,10 +5,11 @@ use std::{net, thread};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_http_test::TestServer;
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 futures::future::{self, ok, Future};
use futures::stream::{once, Stream};
use tokio_timer::sleep;
use actix_http::body::Body;
use actix_http::error::PayloadError;
@@ -34,7 +35,10 @@ fn test_h1() {
.keep_alive(KeepAlive::Disabled)
.client_timeout(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();
@@ -49,6 +53,7 @@ fn test_h1_2() {
.client_timeout(1000)
.client_disconnect(1000)
.finish(|req: Request| {
assert!(req.peer_addr().is_some());
assert_eq!(req.version(), http::Version::HTTP_11);
future::ok::<_, ()>(Response::Ok().finish())
})
@@ -114,6 +119,7 @@ fn test_h2_1() -> std::io::Result<()> {
.and_then(
HttpService::build()
.finish(|req: Request| {
assert!(req.peer_addr().is_some());
assert_eq!(req.version(), http::Version::HTTP_2);
future::ok::<_, Error>(Response::Ok().finish())
})
@@ -153,6 +159,62 @@ fn test_h2_body() -> std::io::Result<()> {
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]
fn test_slow_request() {
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
* Do not support nested multipart
* Split multipart support to separate crate

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,9 +18,9 @@ name = "actix_web_actors"
path = "src/lib.rs"
[dependencies]
actix = "0.8.0-alpha.2"
actix-web = "1.0.0-alpha.3"
actix-http = "0.1.0-alpha.3"
actix = "0.8.0"
actix-web = "1.0.0-alpha.5"
actix-http = "0.1.0-alpha.5"
actix-codec = "0.1.2"
bytes = "0.4"
futures = "0.1.25"

View File

@@ -199,7 +199,7 @@ mod tests {
use actix::Actor;
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 bytes::{Bytes, BytesMut};
@@ -237,7 +237,7 @@ mod tests {
})));
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);
let body = block_on(resp.take_body().fold(

View File

@@ -1,5 +1,9 @@
# 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
* Initial impl

View File

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

View File

@@ -1,118 +1,94 @@
#![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;
mod route;
use proc_macro::TokenStream;
use quote::quote;
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]
pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
if args.is_empty() {
panic!("invalid server definition, expected: #[get(\"some path\")]");
}
// path
let path = match args[0] {
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
let fname = quote!(#fname).to_string();
fname.as_str()[1..fname.len() - 1].to_owned()
}
_ => 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()
let gen = route::Args::new(&args, input, route::GuardType::Get);
gen.generate()
}
/// #[post("path")] attribute
/// Creates route handler with `POST` method guard.
///
/// Syntax: `#[post("path"[, attributes])]`
///
/// Attributes are the same as in [get](attr.get.html)
#[proc_macro_attribute]
pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
if args.is_empty() {
panic!("invalid server definition, expected: #[post(\"some path\")]");
}
// path
let path = match args[0] {
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
let fname = quote!(#fname).to_string();
fname.as_str()[1..fname.len() - 1].to_owned()
}
_ => 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()
let gen = route::Args::new(&args, input, route::GuardType::Post);
gen.generate()
}
/// #[put("path")] attribute
/// Creates route handler with `PUT` method guard.
///
/// Syntax: `#[put("path"[, attributes])]`
///
/// Attributes are the same as in [get](attr.get.html)
#[proc_macro_attribute]
pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
if args.is_empty() {
panic!("invalid server definition, expected: #[put(\"some path\")]");
}
// path
let path = match args[0] {
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
let fname = quote!(#fname).to_string();
fname.as_str()[1..fname.len() - 1].to_owned()
}
_ => 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::Put())
.to(#name), config);
}
}
})
.into()
let gen = route::Args::new(&args, input, route::GuardType::Put);
gen.generate()
}
/// Creates route handler with `DELETE` method guard.
///
/// Syntax: `#[delete("path"[, attributes])]`
///
/// Attributes are the same as in [get](attr.get.html)
#[proc_macro_attribute]
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);
gen.generate()
}

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_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")]
fn test() -> impl Responder {
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]
fn test_body() {
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 response = srv.block_on(request.send()).unwrap();

View File

@@ -1,5 +1,23 @@
# 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
### Changed

View File

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

View File

@@ -1,4 +1,4 @@
use std::io;
use std::{fmt, io};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::body::Body;
@@ -102,6 +102,12 @@ impl<T: AsyncRead + AsyncWrite> AsyncSocket for Socket<T> {
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 {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.as_read_mut().read(buf)

View File

@@ -61,7 +61,6 @@ pub struct ClientRequest {
pub(crate) head: RequestHead,
err: Option<HttpError>,
cookies: Option<CookieJar>,
default_headers: bool,
response_decompress: bool,
timeout: Option<Duration>,
config: Rc<ClientConfig>,
@@ -79,7 +78,6 @@ impl ClientRequest {
err: None,
cookies: None,
timeout: None,
default_headers: true,
response_decompress: true,
}
.method(method)
@@ -316,13 +314,6 @@ impl ClientRequest {
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
pub fn no_decompress(mut self) -> Self {
self.response_decompress = false;
@@ -392,36 +383,6 @@ impl ClientRequest {
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
if let Some(ref mut jar) = self.cookies {
let mut cookie = String::new();
@@ -436,7 +397,7 @@ impl ClientRequest {
);
}
let slf = self;
let mut slf = self;
// enable br only for https
#[cfg(any(
@@ -444,7 +405,8 @@ impl ClientRequest {
feature = "flate2-zlib",
feature = "flate2-rust"
))]
let slf = {
{
if slf.response_decompress {
let https = slf
.head
.uri
@@ -453,16 +415,16 @@ impl ClientRequest {
.unwrap_or(true);
if https {
slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING)
slf = slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING)
} else {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
{
slf.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate")
}
#[cfg(not(any(feature = "flate2-zlib", feature = "flate2-rust")))]
slf
slf = slf
.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate")
}
};
}
}
let head = slf.head;
let config = slf.config.as_ref();
@@ -582,22 +544,36 @@ impl fmt::Debug for ClientRequest {
#[cfg(test)]
mod tests {
use std::time::SystemTime;
use super::*;
use crate::{test, Client};
use crate::Client;
#[test]
fn test_debug() {
test::run_on(|| {
let request = Client::new().get("/").header("x-test", "111");
let repr = format!("{:?}", request);
assert!(repr.contains("ClientRequest"));
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]
fn test_client_header() {
test::run_on(|| {
let req = Client::build()
.header(header::CONTENT_TYPE, "111")
.finish()
@@ -612,12 +588,10 @@ mod tests {
.unwrap(),
"111"
);
})
}
#[test]
fn test_client_header_override() {
test::run_on(|| {
let req = Client::build()
.header(header::CONTENT_TYPE, "111")
.finish()
@@ -633,12 +607,10 @@ mod tests {
.unwrap(),
"222"
);
})
}
#[test]
fn client_basic_auth() {
test::run_on(|| {
let req = Client::new()
.get("/")
.basic_auth("username", Some("password"));
@@ -662,12 +634,10 @@ mod tests {
.unwrap(),
"Basic dXNlcm5hbWU="
);
});
}
#[test]
fn client_bearer_auth() {
test::run_on(|| {
let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n");
assert_eq!(
req.head
@@ -678,6 +648,5 @@ mod tests {
.unwrap(),
"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
use std::fmt::Write as FmtWrite;
use std::io::Write;
use std::rc::Rc;
use std::{fmt, str};
use actix_codec::Framed;
use actix_http::cookie::{Cookie, CookieJar};
use actix_http::{ws, Payload, RequestHead};
use bytes::{BufMut, BytesMut};
use futures::future::{err, Either, Future};
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use tokio_timer::Timeout;
@@ -33,7 +31,6 @@ pub struct WebsocketsRequest {
protocols: Option<String>,
max_size: usize,
server_mode: bool,
default_headers: bool,
cookies: Option<CookieJar>,
config: Rc<ClientConfig>,
}
@@ -63,7 +60,6 @@ impl WebsocketsRequest {
max_size: 65_536,
server_mode: false,
cookies: None,
default_headers: true,
}
}
@@ -119,13 +115,6 @@ impl WebsocketsRequest {
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.
///
/// Header gets appended to existing header.
@@ -188,10 +177,9 @@ impl WebsocketsRequest {
}
/// 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
U: fmt::Display,
P: fmt::Display,
{
let auth = match password {
Some(password) => format!("{}:{}", username, password),
@@ -232,67 +220,36 @@ impl WebsocketsRequest {
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
if let Some(ref mut jar) = slf.cookies {
if let Some(ref mut jar) = self.cookies {
let mut cookie = String::new();
for c in jar.delta() {
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
let _ = write!(&mut cookie, "; {}={}", name, value);
}
head.headers.insert(
self.head.headers.insert(
header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
}
// origin
if let Some(origin) = slf.origin.take() {
head.headers.insert(header::ORIGIN, origin);
if let Some(origin) = self.origin.take() {
self.head.headers.insert(header::ORIGIN, origin);
}
head.set_connection_type(ConnectionType::Upgrade);
head.headers
self.head.set_connection_type(ConnectionType::Upgrade);
self.head
.headers
.insert(header::UPGRADE, HeaderValue::from_static("websocket"));
head.headers.insert(
self.head.headers.insert(
header::SEC_WEBSOCKET_VERSION,
HeaderValue::from_static("13"),
);
if let Some(protocols) = slf.protocols.take() {
head.headers.insert(
if let Some(protocols) = self.protocols.take() {
self.head.headers.insert(
header::SEC_WEBSOCKET_PROTOCOL,
HeaderValue::try_from(protocols.as_str()).unwrap(),
);
@@ -304,15 +261,16 @@ impl WebsocketsRequest {
let sec_key: [u8; 16] = rand::random();
let key = base64::encode(&sec_key);
head.headers.insert(
self.head.headers.insert(
header::SEC_WEBSOCKET_KEY,
HeaderValue::try_from(key.as_str()).unwrap(),
);
let max_size = slf.max_size;
let server_mode = slf.server_mode;
let head = self.head;
let max_size = self.max_size;
let server_mode = self.server_mode;
let fut = slf
let fut = self
.config
.connector
.borrow_mut()
@@ -387,7 +345,7 @@ impl WebsocketsRequest {
});
// 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| {
if let Some(e) = e.into_inner() {
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 brotli2::write::BrotliEncoder;
use bytes::Bytes;
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use flate2::Compression;
use futures::future::Future;
@@ -10,6 +11,8 @@ use rand::Rng;
use actix_http::HttpService;
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 awc::error::SendRequestError;
@@ -94,11 +97,9 @@ fn test_timeout_override() {
))))
});
let client = srv.execute(|| {
awc::Client::build()
let client = awc::Client::build()
.timeout(Duration::from_millis(50000))
.finish()
});
.finish();
let request = client
.get(srv.url("/"))
.timeout(Duration::from_millis(50))
@@ -109,58 +110,77 @@ fn test_timeout_override() {
}
}
// #[test]
// fn test_connection_close() {
// let mut srv =
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
#[test]
fn test_connection_close() {
let mut srv = TestServer::new(|| {
HttpService::new(
App::new().service(web::resource("/").to(|| HttpResponse::Ok())),
)
});
// let request = srv.get("/").header("Connection", "close").finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
// }
let res = srv
.block_on(awc::Client::new().get(srv.url("/")).force_close().send())
.unwrap();
assert!(res.status().is_success());
}
// #[test]
// fn test_with_query_parameter() {
// let mut srv = test::TestServer::new(|app| {
// app.handler(|req: &HttpRequest| match req.query().get("qp") {
// Some(_) => HttpResponse::Ok().finish(),
// None => HttpResponse::BadRequest().finish(),
// })
// });
#[test]
fn test_with_query_parameter() {
let mut srv = TestServer::new(|| {
HttpService::new(App::new().service(web::resource("/").to(
|req: HttpRequest| {
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();
// assert!(response.status().is_success());
// }
#[test]
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]
// fn test_no_decompress() {
// let mut srv =
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
let mut res = srv
.block_on(awc::Client::new().get(srv.url("/")).no_decompress().send())
.unwrap();
assert!(res.status().is_success());
// let request = srv.get("/").disable_decompress().finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
// read response
let bytes = srv.block_on(res.body()).unwrap();
// // read response
// let bytes = srv.execute(response.body()).unwrap();
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()));
// 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()));
// POST
let mut res = srv
.block_on(awc::Client::new().post(srv.url("/")).no_decompress().send())
.unwrap();
assert!(res.status().is_success());
// // POST
// let request = srv.post().disable_decompress().finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// let bytes = srv.execute(response.body()).unwrap();
// 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()));
// }
let bytes = srv.block_on(res.body()).unwrap();
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]
fn test_client_gzip_encoding() {
@@ -406,7 +426,6 @@ fn test_client_brotli_encoding() {
#[test]
fn test_client_cookie_handling() {
use actix_web::http::Cookie;
fn err() -> Error {
use std::io::{Error as IoError, ErrorKind};
// stub some generic error
@@ -468,36 +487,6 @@ fn test_client_cookie_handling() {
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]
// fn client_read_until_eof() {
// let addr = test::TestServer::unused_addr();

View File

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

View File

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

View File

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

View File

@@ -1,24 +1,21 @@
use std::cell::RefCell;
use std::fmt;
use std::marker::PhantomData;
use std::rc::Rc;
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_service::boxed::{self, BoxedNewService};
use actix_service::{
apply_transform, IntoNewService, IntoTransform, NewService, Transform,
};
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
use bytes::Bytes;
use futures::{IntoFuture, Stream};
use futures::IntoFuture;
use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory};
use crate::config::{AppConfig, AppConfigInner, RouterConfig};
use crate::app_service::{AppEntry, AppInit, AppRoutingFactory};
use crate::config::{AppConfig, AppConfigInner, ServiceConfig};
use crate::data::{Data, DataFactory};
use crate::dev::{Payload, PayloadStream, ResourceDef};
use crate::error::{Error, PayloadError};
use crate::dev::ResourceDef;
use crate::error::Error;
use crate::resource::Resource;
use crate::route::Route;
use crate::service::{
@@ -26,45 +23,49 @@ use crate::service::{
ServiceResponse,
};
type HttpNewService<P> =
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
/// Application builder - structure that follows the builder pattern
/// for building application instances.
pub struct App<In, Out, T>
where
T: NewService<Request = ServiceRequest<In>, Response = ServiceRequest<Out>>,
{
chain: T,
pub struct App<T, B> {
endpoint: T,
services: Vec<Box<ServiceFactory>>,
default: Option<Rc<HttpNewService>>,
factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
data: Vec<Box<DataFactory>>,
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.
pub fn new() -> Self {
let fref = Rc::new(RefCell::new(None));
App {
chain: AppChain,
endpoint: AppEntry::new(fref.clone()),
data: Vec::new(),
services: Vec::new(),
default: None,
factory_ref: fref,
config: AppConfigInner::default(),
external: Vec::new(),
_t: PhantomData,
}
}
}
impl<In, Out, T> App<In, Out, T>
impl<T, B> App<T, B>
where
In: 'static,
Out: 'static,
B: MessageBody,
T: NewService<
Request = ServiceRequest<In>,
Response = ServiceRequest<Out>,
Request = ServiceRequest,
Response = ServiceResponse<B>,
Error = Error,
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.
///
/// **Note**: http server accepts an application factory rather than
@@ -112,151 +113,6 @@ where
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
/// process
///
@@ -269,7 +125,7 @@ where
/// use actix_web::{web, middleware, App, HttpResponse};
///
/// // 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")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
@@ -283,27 +139,16 @@ where
/// .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
F: Fn(&mut RouterConfig<Out>),
F: Fn(&mut ServiceConfig),
{
let mut cfg = RouterConfig::new();
let mut cfg = ServiceConfig::new();
f(&mut cfg);
self.data.extend(cfg.data);
let fref = Rc::new(RefCell::new(None));
AppRouter {
chain: self.chain,
default: None,
endpoint: AppEntry::new(fref.clone()),
factory_ref: fref,
data: self.data,
config: self.config,
services: cfg.services,
external: cfg.external,
_t: PhantomData,
}
self.services.extend(cfg.services);
self.external.extend(cfg.external);
self
}
/// Configure route for a specific path.
@@ -325,171 +170,7 @@ where
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()));
/// }
/// ```
pub fn route(
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 {
pub fn route(self, path: &str, mut route: Route) -> Self {
self.service(
Resource::new(path)
.add_guards(route.take_guards())
@@ -508,102 +189,75 @@ where
/// * "StaticFiles" is a service for static files support
pub fn service<F>(mut self, factory: F) -> Self
where
F: HttpServiceFactory<P> + 'static,
F: HttpServiceFactory + 'static,
{
self.services
.push(Box::new(ServiceFactoryWrapper::new(factory)));
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 *Route*.
/// Set server host name.
///
/// 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>(
self,
mw: F,
) -> AppRouter<
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,
}
/// 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
}
/// Registers middleware, in the form of a closure, that runs during inbound
/// and/or outbound processing in the request lifecycle (request -> response),
/// modifying request/response as necessary, across all requests managed by
/// the *Route*.
/// Default service to be used if no matching resource could be found.
///
/// 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>(
self,
mw: F,
) -> AppRouter<
C,
P,
B1,
impl NewService<
Request = ServiceRequest<P>,
Response = ServiceResponse<B1>,
Error = Error,
InitError = (),
>,
>
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn index() -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .service(
/// web::resource("/index.html").route(web::get().to(index)))
/// .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
B1: MessageBody,
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>,
F: IntoNewService<U>,
U: NewService<
Request = ServiceRequest<P>,
Request = ServiceRequest,
Response = ServiceResponse,
Error = Error,
InitError = (),
> + 'static,
U::InitError: fmt::Debug,
{
// create and configure default resource
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
@@ -641,27 +295,128 @@ where
self.external.push(rdef);
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,
}
}
/// 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<B1, F, R>(
self,
mw: F,
) -> App<
impl NewService<
Request = ServiceRequest,
Response = ServiceResponse<B1>,
Error = Error,
InitError = (),
>,
B1,
>
where
B1: MessageBody,
F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone,
R: IntoFuture<Item = ServiceResponse<B1>, Error = Error>,
{
self.wrap(mw)
}
}
impl<C, T, P: 'static, B: MessageBody> IntoNewService<AppInit<C, T, P, B>, ServerConfig>
for AppRouter<C, P, B, T>
impl<T, B> IntoNewService<AppInit<T, B>, ServerConfig> for App<T, B>
where
B: MessageBody,
T: NewService<
Request = ServiceRequest<P>,
Request = ServiceRequest,
Response = ServiceResponse<B>,
Error = Error,
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 {
chain: self.chain,
data: self.data,
endpoint: self.endpoint,
services: RefCell::new(self.services),
@@ -681,7 +436,7 @@ mod tests {
use super::*;
use crate::http::{header, HeaderValue, Method, StatusCode};
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};
#[test]
@@ -702,10 +457,14 @@ mod tests {
.service(web::resource("/test").to(|| HttpResponse::Ok()))
.service(
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())),
)
.default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())),
.default_service(|r: ServiceRequest| {
r.into_response(HttpResponse::MethodNotAllowed())
}),
);
let req = TestRequest::with_uri("/blah").to_request();
@@ -742,13 +501,13 @@ mod tests {
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
fn md<S, P, B>(
req: ServiceRequest<P>,
fn md<S, B>(
req: ServiceRequest,
srv: &mut S,
) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error>
where
S: Service<
Request = ServiceRequest<P>,
Request = ServiceRequest,
Response = ServiceResponse<B>,
Error = Error,
>,
@@ -768,7 +527,7 @@ mod tests {
.route("/test", web::get().to(|| HttpResponse::Ok())),
);
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.headers().get(header::CONTENT_TYPE).unwrap(),
@@ -784,7 +543,7 @@ mod tests {
.wrap(md),
);
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.headers().get(header::CONTENT_TYPE).unwrap(),
@@ -808,7 +567,7 @@ mod tests {
.service(web::resource("/test").to(|| HttpResponse::Ok())),
);
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.headers().get(header::CONTENT_TYPE).unwrap(),
@@ -832,7 +591,7 @@ mod tests {
}),
);
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.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_server_config::ServerConfig;
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::{Async, Future, Poll};
use crate::config::{AppConfig, ServiceConfig};
use crate::config::{AppConfig, AppService};
use crate::data::{DataFactory, DataFactoryResult};
use crate::error::Error;
use crate::guard::Guard;
@@ -19,9 +19,8 @@ use crate::rmap::ResourceMap;
use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse};
type Guards = Vec<Box<Guard>>;
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>;
type HttpNewService<P> =
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
type BoxedResponse = Either<
FutureResult<ServiceResponse, Error>,
Box<Future<Item = ServiceResponse, Error = Error>>,
@@ -29,36 +28,28 @@ type BoxedResponse = Either<
/// Service factory to convert `Request` to a `ServiceRequest<S>`.
/// It also executes data factories.
pub struct AppInit<C, T, P, B>
pub struct AppInit<T, B>
where
C: NewService<Request = ServiceRequest, Response = ServiceRequest<P>>,
T: NewService<
Request = ServiceRequest<P>,
Request = ServiceRequest,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
{
pub(crate) chain: C,
pub(crate) endpoint: T,
pub(crate) data: Vec<Box<DataFactory>>,
pub(crate) config: RefCell<AppConfig>,
pub(crate) services: RefCell<Vec<Box<ServiceFactory<P>>>>,
pub(crate) default: Option<Rc<HttpNewService<P>>>,
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory<P>>>>,
pub(crate) services: RefCell<Vec<Box<ServiceFactory>>>,
pub(crate) default: Option<Rc<HttpNewService>>,
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
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
C: NewService<
Request = ServiceRequest,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
T: NewService<
Request = ServiceRequest<P>,
Request = ServiceRequest,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
@@ -66,15 +57,15 @@ where
{
type Request = Request;
type Response = ServiceResponse<B>;
type Error = C::Error;
type InitError = C::InitError;
type Service = AndThen<AppInitService<C::Service, P>, T::Service>;
type Future = AppInitResult<C, T, P, B>;
type Error = T::Error;
type InitError = T::InitError;
type Service = AppInitService<T::Service, B>;
type Future = AppInitResult<T, B>;
fn new_service(&self, cfg: &ServerConfig) -> Self::Future {
// update resource default service
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()))
})))
});
@@ -86,8 +77,7 @@ where
loc_cfg.addr = cfg.local_addr();
}
let mut config =
ServiceConfig::new(self.config.borrow().clone(), default.clone());
let mut config = AppService::new(self.config.borrow().clone(), default.clone());
// register services
std::mem::replace(&mut *self.services.borrow_mut(), Vec::new())
@@ -121,8 +111,6 @@ where
rmap.finish(rmap.clone());
AppInitResult {
chain: None,
chain_fut: self.chain.new_service(&()),
endpoint: None,
endpoint_fut: self.endpoint.new_service(&()),
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
C: NewService,
T: NewService,
{
chain: Option<C::Service>,
endpoint: Option<T::Service>,
chain_fut: C::Future,
endpoint_fut: T::Future,
rmap: Rc<ResourceMap>,
data: Vec<Box<DataFactoryResult>>,
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
C: NewService<
Request = ServiceRequest,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
T: NewService<
Request = ServiceRequest<P>,
Request = ServiceRequest,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
{
type Item = AndThen<AppInitService<C::Service, P>, T::Service>;
type Error = C::InitError;
type Item = AppInitService<T::Service, B>;
type Error = T::InitError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
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 let Async::Ready(srv) = self.endpoint_fut.poll()? {
self.endpoint = Some(srv);
}
}
if self.chain.is_some() && self.endpoint.is_some() {
Ok(Async::Ready(
AppInitService {
chain: self.chain.take().unwrap(),
if self.endpoint.is_some() {
Ok(Async::Ready(AppInitService {
service: self.endpoint.take().unwrap(),
rmap: self.rmap.clone(),
config: self.config.clone(),
pool: HttpRequestPool::create(),
}
.and_then(self.endpoint.take().unwrap()),
))
}))
} else {
Ok(Async::NotReady)
}
@@ -206,27 +176,27 @@ where
}
/// Service to convert `Request` to a `ServiceRequest<S>`
pub struct AppInitService<C, P>
pub struct AppInitService<T: Service, B>
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>,
config: AppConfig,
pool: &'static HttpRequestPool,
}
impl<C, P> Service for AppInitService<C, P>
impl<T, B> Service for AppInitService<T, B>
where
C: Service<Request = ServiceRequest, Response = ServiceRequest<P>, Error = Error>,
T: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{
type Request = Request;
type Response = ServiceRequest<P>;
type Error = C::Error;
type Future = C::Future;
type Response = ServiceResponse<B>;
type Error = T::Error;
type Future = T::Future;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.chain.poll_ready()
self.service.poll_ready()
}
fn call(&mut self, req: Request) -> Self::Future {
@@ -247,22 +217,22 @@ where
self.pool,
)
};
self.chain.call(ServiceRequest::from_parts(req, payload))
self.service.call(ServiceRequest::from_parts(req, payload))
}
}
pub struct AppRoutingFactory<P> {
services: Rc<Vec<(ResourceDef, HttpNewService<P>, RefCell<Option<Guards>>)>>,
default: Rc<HttpNewService<P>>,
pub struct AppRoutingFactory {
services: Rc<Vec<(ResourceDef, HttpNewService, RefCell<Option<Guards>>)>>,
default: Rc<HttpNewService>,
}
impl<P: 'static> NewService for AppRoutingFactory<P> {
type Request = ServiceRequest<P>;
impl NewService for AppRoutingFactory {
type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = Error;
type InitError = ();
type Service = AppRouting<P>;
type Future = AppRoutingFactoryResponse<P>;
type Service = AppRouting;
type Future = AppRoutingFactoryResponse;
fn new_service(&self, _: &()) -> Self::Future {
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
#[doc(hidden)]
pub struct AppRoutingFactoryResponse<P> {
fut: Vec<CreateAppRoutingItem<P>>,
default: Option<HttpService<P>>,
default_fut: Option<Box<Future<Item = HttpService<P>, Error = ()>>>,
pub struct AppRoutingFactoryResponse {
fut: Vec<CreateAppRoutingItem>,
default: Option<HttpService>,
default_fut: Option<Box<Future<Item = HttpService, Error = ()>>>,
}
enum CreateAppRoutingItem<P> {
Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut<P>),
Service(ResourceDef, Option<Guards>, HttpService<P>),
enum CreateAppRoutingItem {
Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut),
Service(ResourceDef, Option<Guards>, HttpService),
}
impl<P> Future for AppRoutingFactoryResponse<P> {
type Item = AppRouting<P>;
impl Future for AppRoutingFactoryResponse {
type Item = AppRouting;
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@@ -360,14 +330,14 @@ impl<P> Future for AppRoutingFactoryResponse<P> {
}
}
pub struct AppRouting<P> {
router: Router<HttpService<P>, Guards>,
ready: Option<(ServiceRequest<P>, ResourceInfo)>,
default: Option<HttpService<P>>,
pub struct AppRouting {
router: Router<HttpService, Guards>,
ready: Option<(ServiceRequest, ResourceInfo)>,
default: Option<HttpService>,
}
impl<P> Service for AppRouting<P> {
type Request = ServiceRequest<P>;
impl Service for AppRouting {
type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = Error;
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| {
if let Some(ref guards) = guards {
for f in guards {
@@ -404,58 +374,25 @@ impl<P> Service for AppRouting<P> {
}
/// Wrapper service for routing
pub struct AppEntry<P> {
factory: Rc<RefCell<Option<AppRoutingFactory<P>>>>,
pub struct AppEntry {
factory: Rc<RefCell<Option<AppRoutingFactory>>>,
}
impl<P> AppEntry<P> {
pub fn new(factory: Rc<RefCell<Option<AppRoutingFactory<P>>>>) -> Self {
impl AppEntry {
pub fn new(factory: Rc<RefCell<Option<AppRoutingFactory>>>) -> Self {
AppEntry { factory }
}
}
impl<P: 'static> NewService for AppEntry<P> {
type Request = ServiceRequest<P>;
impl NewService for AppEntry {
type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = Error;
type InitError = ();
type Service = AppRouting<P>;
type Future = AppRoutingFactoryResponse<P>;
type Service = AppRouting;
type Future = AppRoutingFactoryResponse;
fn new_service(&self, _: &()) -> Self::Future {
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 HttpNewService<P> =
boxed::BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
type HttpNewService =
boxed::BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
/// Application configuration
pub struct ServiceConfig<P> {
pub struct AppService {
config: AppConfig,
root: bool,
default: Rc<HttpNewService<P>>,
default: Rc<HttpNewService>,
services: Vec<(
ResourceDef,
HttpNewService<P>,
HttpNewService,
Option<Guards>,
Option<Rc<ResourceMap>>,
)>,
}
impl<P: 'static> ServiceConfig<P> {
impl AppService {
/// Crate server settings instance
pub(crate) fn new(config: AppConfig, default: Rc<HttpNewService<P>>) -> Self {
ServiceConfig {
pub(crate) fn new(config: AppConfig, default: Rc<HttpNewService>) -> Self {
AppService {
config,
default,
root: true,
@@ -55,7 +55,7 @@ impl<P: 'static> ServiceConfig<P> {
self,
) -> Vec<(
ResourceDef,
HttpNewService<P>,
HttpNewService,
Option<Guards>,
Option<Rc<ResourceMap>>,
)> {
@@ -63,7 +63,7 @@ impl<P: 'static> ServiceConfig<P> {
}
pub(crate) fn clone_config(&self) -> Self {
ServiceConfig {
AppService {
config: self.config.clone(),
default: self.default.clone(),
services: Vec::new(),
@@ -77,7 +77,7 @@ impl<P: 'static> ServiceConfig<P> {
}
/// Default resource
pub fn default_service(&self) -> Rc<HttpNewService<P>> {
pub fn default_service(&self) -> Rc<HttpNewService> {
self.default.clone()
}
@@ -90,7 +90,7 @@ impl<P: 'static> ServiceConfig<P> {
) where
F: IntoNewService<S>,
S: NewService<
Request = ServiceRequest<P>,
Request = ServiceRequest,
Response = ServiceResponse,
Error = Error,
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
/// to set of external methods. This could help with
/// modularization of big application configuration.
pub struct RouterConfig<P: 'static> {
pub(crate) services: Vec<Box<ServiceFactory<P>>>,
pub struct ServiceConfig {
pub(crate) services: Vec<Box<ServiceFactory>>,
pub(crate) data: Vec<Box<DataFactory>>,
pub(crate) external: Vec<ResourceDef>,
}
impl<P: 'static> RouterConfig<P> {
impl ServiceConfig {
pub(crate) fn new() -> Self {
Self {
services: Vec::new(),
@@ -211,7 +211,7 @@ impl<P: 'static> RouterConfig<P> {
/// Configure route for a specific path.
///
/// This is same as `App::route()` method.
pub fn route(&mut self, path: &str, mut route: Route<P>) -> &mut Self {
pub fn route(&mut self, path: &str, mut route: Route) -> &mut Self {
self.service(
Resource::new(path)
.add_guards(route.take_guards())
@@ -224,7 +224,7 @@ impl<P: 'static> RouterConfig<P> {
/// This is same as `App::service()` method.
pub fn service<F>(&mut self, factory: F) -> &mut Self
where
F: HttpServiceFactory<P> + 'static,
F: HttpServiceFactory + 'static,
{
self.services
.push(Box::new(ServiceFactoryWrapper::new(factory)));
@@ -255,13 +255,13 @@ mod tests {
use actix_service::Service;
use super::*;
use crate::http::StatusCode;
use crate::test::{block_on, init_service, TestRequest};
use crate::http::{Method, StatusCode};
use crate::test::{block_on, call_service, init_service, TestRequest};
use crate::{web, App, HttpResponse};
#[test]
fn test_data() {
let cfg = |cfg: &mut RouterConfig<_>| {
let cfg = |cfg: &mut ServiceConfig| {
cfg.data(10usize);
};
@@ -276,7 +276,7 @@ mod tests {
#[test]
fn test_data_factory() {
let cfg = |cfg: &mut RouterConfig<_>| {
let cfg = |cfg: &mut ServiceConfig| {
cfg.data_factory(|| Ok::<_, ()>(10usize));
};
@@ -288,7 +288,7 @@ mod tests {
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let cfg2 = |cfg: &mut RouterConfig<_>| {
let cfg2 = |cfg: &mut ServiceConfig| {
cfg.data_factory(|| Ok::<_, ()>(10u32));
};
let mut srv = init_service(
@@ -300,4 +300,26 @@ mod tests {
let resp = block_on(srv.call(req)).unwrap();
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
/// 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.
///
/// **Note**: http server accepts an application factory rather than
/// an application instance. Http server constructs an application
/// instance for each thread, thus application data must be constructed
/// multiple times. If you want to share data between different
/// threads, a shared object should be used, e.g. `Arc`. Application
/// data does not need to be `Send` or `Sync`.
/// threads, a shareable object should be used, e.g. `Send + Sync`. Application
/// 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
/// cause *Internal Server Error* response.
@@ -60,6 +61,7 @@ pub(crate) trait DataFactoryResult {
/// web::get().to(index)));
/// }
/// ```
#[derive(Debug)]
pub struct Data<T>(Arc<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 Future = Result<Self, Error>;
#[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>>() {
Ok(st.clone())
} else {
log::debug!(
"Failed to construct App-level Data extractor. \
Request path: {:?}",
req.path()
);
Err(ErrorInternalServerError(
"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 Future = Result<Self, Error>;
#[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>() {
Ok(st.clone())
} else {
log::debug!("Failed to construct Route-level Data extractor");
Err(ErrorInternalServerError(
"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")]
Chunked,
/// 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,
/// Payload size is now known
#[display(fmt = "Payload size is now known")]
@@ -66,7 +66,7 @@ impl ResponseError for UrlencodedError {
#[derive(Debug, Display, From)]
pub enum JsonPayloadError {
/// 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,
/// 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.
///
/// 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.
type Error: Into<Error>;
/// Future that resolves to a Self
type Future: IntoFuture<Item = Self, Error = Self::Error>;
/// Configuration for this extractor
type Config: Default + 'static;
/// 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
///
@@ -26,6 +29,14 @@ pub trait FromRequest<P>: Sized {
fn extract(req: &HttpRequest) -> Self::Future {
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
@@ -45,11 +56,12 @@ pub trait FromRequest<P>: Sized {
/// name: String
/// }
///
/// impl<P> FromRequest<P> for Thing {
/// impl FromRequest for Thing {
/// type Error = 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() {
/// Ok(Thing { name: "thingy".into() })
/// } 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
T: FromRequest<P>,
T: FromRequest,
T::Future: 'static,
{
type Config = T::Config;
type Error = Error;
type Future = Box<Future<Item = Option<T>, Error = Error>>;
#[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
Box::new(
T::from_request(req, payload)
.into_future()
@@ -116,11 +129,12 @@ where
/// name: String
/// }
///
/// impl<P> FromRequest<P> for Thing {
/// impl FromRequest for Thing {
/// type Error = 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() {
/// Ok(Thing { name: "thingy".into() })
/// } 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
T: FromRequest<P>,
T: FromRequest,
T::Future: 'static,
T::Error: 'static,
{
type Config = T::Config;
type Error = Error;
type Future = Box<Future<Item = Result<T, T::Error>, Error = Error>>;
#[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
Box::new(
T::from_request(req, payload)
.into_future()
@@ -166,11 +181,12 @@ where
}
#[doc(hidden)]
impl<P> FromRequest<P> for () {
impl FromRequest for () {
type Config = ();
type Error = Error;
type Future = Result<(), Error>;
fn from_request(_: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future {
Ok(())
}
}
@@ -179,12 +195,13 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
/// FromRequest implementation for tuple
#[doc(hidden)]
impl<P, $($T: FromRequest<P> + 'static),+> FromRequest<P> for ($($T,)+)
impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+)
{
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 {
items: <($(Option<$T>,)+)>::default(),
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)]
pub struct $fut_type<P, $($T: FromRequest<P>),+> {
pub struct $fut_type<$($T: FromRequest),+> {
items: ($(Option<$T>,)+),
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 Error = Error;

View File

@@ -242,13 +242,13 @@ where
}
/// Extract arguments from request
pub struct Extract<P, T: FromRequest<P>, S> {
pub struct Extract<T: FromRequest, S> {
config: Rc<RefCell<Option<Rc<Extensions>>>>,
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 {
Extract {
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
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>
+ Clone,
{
type Request = ServiceRequest<P>;
type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = (Error, ServiceRequest<P>);
type Error = (Error, ServiceRequest);
type InitError = ();
type Service = ExtractService<P, T, S>;
type Service = ExtractService<T, S>;
type Future = FutureResult<Self::Service, ()>;
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>>,
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
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>
+ Clone,
{
type Request = ServiceRequest<P>;
type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = (Error, ServiceRequest<P>);
type Future = ExtractResponse<P, T, S>;
type Error = (Error, ServiceRequest);
type Future = ExtractResponse<T, S>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
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();
req.set_route_data(self.config.clone());
let fut = T::from_request(&req, &mut payload).into_future();
@@ -313,19 +313,19 @@ where
}
}
pub struct ExtractResponse<P, T: FromRequest<P>, S: Service> {
req: Option<(HttpRequest, Payload<P>)>,
pub struct ExtractResponse<T: FromRequest, S: Service> {
req: Option<(HttpRequest, Payload)>,
service: S,
fut: <T::Future as IntoFuture>::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
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>,
{
type Item = ServiceResponse;
type Error = (Error, ServiceRequest<P>);
type Error = (Error, ServiceRequest);
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut_s {

View File

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

View File

@@ -67,7 +67,6 @@
//! ## Package feature
//!
//! * `client` - enables http client
//! * `tls` - enables ssl support via `native-tls` crate
//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2`
//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2`
//! * `secure-cookies` - enables secure cookies support, includes `ring` crate as
@@ -134,8 +133,7 @@ pub mod dev {
//! use actix_web::dev::*;
//! ```
pub use crate::app::AppRouter;
pub use crate::config::{AppConfig, ServiceConfig};
pub use crate::config::{AppConfig, AppService};
pub use crate::info::ConnectionInfo;
pub use crate::rmap::ResourceMap;
pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse};
@@ -144,6 +142,7 @@ pub mod dev {
pub use crate::types::readlines::Readlines;
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::{
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.
///
/// ```rust
/// use actix_web::{web, middleware::encoding, App, HttpResponse};
/// use actix_web::{web, middleware, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .wrap(encoding::Compress::default())
/// .wrap(middleware::Compress::default())
/// .service(
/// web::resource("/test")
/// .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
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 Error = S::Error;
type InitError = ();
@@ -93,21 +93,21 @@ pub struct CompressMiddleware<S> {
encoding: ContentEncoding,
}
impl<S, P, B> Service for CompressMiddleware<S>
impl<S, B> Service for CompressMiddleware<S>
where
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 Error = S::Error;
type Future = CompressResponse<S, P, B>;
type Future = CompressResponse<S, B>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
fn call(&mut self, req: ServiceRequest) -> Self::Future {
// negotiate content-encoding
let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) {
if let Ok(enc) = val.to_str() {
@@ -128,20 +128,20 @@ where
}
#[doc(hidden)]
pub struct CompressResponse<S, P, B>
pub struct CompressResponse<S, B>
where
S: Service,
B: MessageBody,
{
fut: S::Future,
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
B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
{
type Item = ServiceResponse<Encoder<B>>;
type Error = S::Error;

View File

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

View File

@@ -140,12 +140,13 @@ struct IdentityItem {
/// }
/// # fn main() {}
/// ```
impl<P> FromRequest<P> for Identity {
impl FromRequest for Identity {
type Config = ();
type Error = Error;
type Future = Result<Identity, Error>;
#[inline]
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
Ok(Identity(req.clone()))
}
}
@@ -159,7 +160,7 @@ pub trait IdentityPolicy: Sized + 'static {
type ResponseFuture: IntoFuture<Item = (), Error = Error>;
/// Parse the session from request and load data from a service identity.
fn from_request<P>(&self, request: &mut ServiceRequest<P>) -> Self::Future;
fn from_request(&self, request: &mut ServiceRequest) -> Self::Future;
/// Write changes to response
fn to_response<B>(
@@ -198,16 +199,15 @@ impl<T> IdentityService<T> {
}
}
impl<S, T, P, B> Transform<S> for IdentityService<T>
impl<S, T, B> Transform<S> for IdentityService<T>
where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>> + 'static,
S::Future: 'static,
S::Error: 'static,
T: IdentityPolicy,
P: 'static,
B: 'static,
{
type Request = ServiceRequest<P>;
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = S::Error;
type InitError = ();
@@ -228,16 +228,15 @@ pub struct IdentityServiceMiddleware<S, T> {
service: Rc<RefCell<S>>,
}
impl<S, T, P, B> Service for IdentityServiceMiddleware<S, T>
impl<S, T, B> Service for IdentityServiceMiddleware<S, T>
where
P: 'static,
B: 'static,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>> + 'static,
S::Future: 'static,
S::Error: 'static,
T: IdentityPolicy,
{
type Request = ServiceRequest<P>;
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = S::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
@@ -246,7 +245,7 @@ where
self.service.borrow_mut().poll_ready()
}
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let srv = self.service.clone();
let backend = self.backend.clone();
@@ -348,7 +347,7 @@ impl CookieIdentityInner {
Ok(())
}
fn load<T>(&self, req: &ServiceRequest<T>) -> Option<String> {
fn load(&self, req: &ServiceRequest) -> Option<String> {
if let Ok(cookies) = req.cookies() {
for cookie in cookies.iter() {
if cookie.name() == self.name {
@@ -445,7 +444,7 @@ impl IdentityPolicy for CookieIdentityPolicy {
type Future = Result<Option<String>, Error>;
type ResponseFuture = Result<(), Error>;
fn from_request<P>(&self, req: &mut ServiceRequest<P>) -> Self::Future {
fn from_request(&self, req: &mut ServiceRequest) -> Self::Future {
Ok(self.0.load(req))
}
@@ -501,15 +500,15 @@ mod tests {
})),
);
let resp =
test::call_success(&mut srv, TestRequest::with_uri("/index").to_request());
test::call_service(&mut srv, TestRequest::with_uri("/index").to_request());
assert_eq!(resp.status(), StatusCode::OK);
let resp =
test::call_success(&mut srv, TestRequest::with_uri("/login").to_request());
test::call_service(&mut srv, TestRequest::with_uri("/login").to_request());
assert_eq!(resp.status(), StatusCode::OK);
let c = resp.response().cookies().next().unwrap().to_owned();
let resp = test::call_success(
let resp = test::call_service(
&mut srv,
TestRequest::with_uri("/index")
.cookie(c.clone())
@@ -517,7 +516,7 @@ mod tests {
);
assert_eq!(resp.status(), StatusCode::CREATED);
let resp = test::call_success(
let resp = test::call_service(
&mut srv,
TestRequest::with_uri("/logout")
.cookie(c.clone())

View File

@@ -114,12 +114,12 @@ impl Default for Logger {
}
}
impl<S, P, B> Transform<S> for Logger
impl<S, B> Transform<S> for Logger
where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
B: MessageBody,
{
type Request = ServiceRequest<P>;
type Request = ServiceRequest;
type Response = ServiceResponse<StreamLog<B>>;
type Error = S::Error;
type InitError = ();
@@ -140,21 +140,21 @@ pub struct LoggerMiddleware<S> {
service: S,
}
impl<S, P, B> Service for LoggerMiddleware<S>
impl<S, B> Service for LoggerMiddleware<S>
where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
B: MessageBody,
{
type Request = ServiceRequest<P>;
type Request = ServiceRequest;
type Response = ServiceResponse<StreamLog<B>>;
type Error = S::Error;
type Future = LoggerResponse<S, P, B>;
type Future = LoggerResponse<S, B>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
fn call(&mut self, req: ServiceRequest) -> Self::Future {
if self.inner.exclude.contains(req.path()) {
LoggerResponse {
fut: self.service.call(req),
@@ -180,7 +180,7 @@ where
}
#[doc(hidden)]
pub struct LoggerResponse<S, P, B>
pub struct LoggerResponse<S, B>
where
B: MessageBody,
S: Service,
@@ -188,13 +188,13 @@ where
fut: S::Future,
time: time::Tm,
format: Option<Format>,
_t: PhantomData<(P, B)>,
_t: PhantomData<(B,)>,
}
impl<S, P, B> Future for LoggerResponse<S, P, B>
impl<S, B> Future for LoggerResponse<S, B>
where
B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
{
type Item = ServiceResponse<StreamLog<B>>;
type Error = S::Error;
@@ -241,8 +241,8 @@ impl<B> Drop for StreamLog<B> {
}
impl<B: MessageBody> MessageBody for StreamLog<B> {
fn length(&self) -> BodySize {
self.body.length()
fn size(&self) -> BodySize {
self.body.size()
}
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
@@ -363,13 +363,6 @@ impl FormatText {
let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0;
fmt.write_fmt(format_args!("{:.6}", rt))
}
// FormatText::RemoteAddr => {
// if let Some(remote) = req.connection_info().remote() {
// return remote.fmt(fmt);
// } else {
// "-".fmt(fmt)
// }
// }
FormatText::EnvironHeader(ref name) => {
if let Ok(val) = env::var(name) {
fmt.write_fmt(format_args!("{}", val))
@@ -402,7 +395,7 @@ impl FormatText {
}
}
fn render_request<P>(&mut self, now: time::Tm, req: &ServiceRequest<P>) {
fn render_request(&mut self, now: time::Tm, req: &ServiceRequest) {
match *self {
FormatText::RequestLine => {
*self = if req.query_string().is_empty() {
@@ -441,6 +434,14 @@ impl FormatText {
};
*self = FormatText::Str(s.to_string());
}
FormatText::RemoteAddr => {
let s = if let Some(remote) = req.connection_info().remote() {
FormatText::Str(remote.to_string())
} else {
FormatText::Str("-".to_string())
};
*self = s;
}
_ => (),
}
}
@@ -464,7 +465,7 @@ mod tests {
#[test]
fn test_logger() {
let srv = FnService::new(|req: ServiceRequest<_>| {
let srv = FnService::new(|req: ServiceRequest| {
req.into_response(
HttpResponse::build(StatusCode::OK)
.header("X-Test", "ttt")

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