1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-27 17:52:56 +01:00

remove actix-web artifacts

This commit is contained in:
Nikolay Kim 2018-10-04 17:00:27 -07:00
parent 13b0ee7355
commit 6aa2de7b8d
67 changed files with 51 additions and 27202 deletions

View File

@ -1,713 +1,5 @@
# Changes
## [0.7.9] - 2018-09-x
## [0.1.0] - 2018-09-x
### Added
* Added client shutdown timeout setting
* Added slow request timeout setting
* Respond with 408 response on slow request timeout #523
### Fixed
* HTTP1 decoding errors are reported to the client. #512
* Correctly compose multiple allowed origins in CORS. #517
* Websocket server finished() isn't called if client disconnects #511
* Responses with the following codes: 100, 101, 102, 204 -- are sent without Content-Length header. #521
* Correct usage of `no_http2` flag in `bind_*` methods. #519
## [0.7.8] - 2018-09-17
### Added
* Use server `Keep-Alive` setting as slow request timeout #439
### Changed
* Use 5 seconds keep-alive timer by default.
### Fixed
* Fixed wrong error message for i16 type #510
## [0.7.7] - 2018-09-11
### Fixed
* Fix linked list of HttpChannels #504
* Fix requests to TestServer fail #508
## [0.7.6] - 2018-09-07
### Fixed
* Fix system_exit in HttpServer #501
* Fix parsing of route param containin regexes with repetition #500
### Changes
* Unhide `SessionBackend` and `SessionImpl` traits #455
## [0.7.5] - 2018-09-04
### Added
* Added the ability to pass a custom `TlsConnector`.
* Allow to register handlers on scope level #465
### Fixed
* Handle socket read disconnect
* Handling scoped paths without leading slashes #460
### Changed
* Read client response until eof if connection header set to close #464
## [0.7.4] - 2018-08-23
### Added
* Added `HttpServer::maxconn()` and `HttpServer::maxconnrate()`,
accept backpressure #250
* Allow to customize connection handshake process via `HttpServer::listen_with()`
and `HttpServer::bind_with()` methods
* Support making client connections via `tokio-uds`'s `UnixStream` when "uds" feature is enabled #472
### Changed
* It is allowed to use function with up to 10 parameters for handler with `extractor parameters`.
`Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
even for handler with one parameter.
* native-tls - 0.2
* `Content-Disposition` is re-worked. Its parser is now more robust and handles quoted content better. See #461
### Fixed
* Use zlib instead of raw deflate for decoding and encoding payloads with
`Content-Encoding: deflate`.
* Fixed headers formating for CORS Middleware Access-Control-Expose-Headers #436
* Fix adding multiple response headers #446
* Client includes port in HOST header when it is not default(e.g. not 80 and 443). #448
* Panic during access without routing being set #452
* Fixed http/2 error handling
### Deprecated
* `HttpServer::no_http2()` is deprecated, use `OpensslAcceptor::with_flags()` or
`RustlsAcceptor::with_flags()` instead
* `HttpServer::listen_tls()`, `HttpServer::listen_ssl()`, `HttpServer::listen_rustls()` have been
deprecated in favor of `HttpServer::listen_with()` with specific `acceptor`.
* `HttpServer::bind_tls()`, `HttpServer::bind_ssl()`, `HttpServer::bind_rustls()` have been
deprecated in favor of `HttpServer::bind_with()` with specific `acceptor`.
## [0.7.3] - 2018-08-01
### Added
* Support HTTP/2 with rustls #36
* Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433
### Fixed
* Fixed failure 0.1.2 compatibility
* Do not override HOST header for client request #428
* Gz streaming, use `flate2::write::GzDecoder` #228
* HttpRequest::url_for is not working with scopes #429
* Fixed headers' formating for CORS Middleware `Access-Control-Expose-Headers` header value to HTTP/1.1 & HTTP/2 spec-compliant format #436
## [0.7.2] - 2018-07-26
### Added
* Add implementation of `FromRequest<S>` for `Option<T>` and `Result<T, Error>`
* Allow to handle application prefix, i.e. allow to handle `/app` path
for application with `/app` prefix.
Check [`App::prefix()`](https://actix.rs/actix-web/actix_web/struct.App.html#method.prefix)
api doc.
* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies
### Changed
* Upgrade to cookie 0.11
* Removed the timestamp from the default logger middleware
### Fixed
* Missing response header "content-encoding" #421
* Fix stream draining for http/2 connections #290
## [0.7.1] - 2018-07-21
### Fixed
* Fixed default_resource 'not yet implemented' panic #410
## [0.7.0] - 2018-07-21
### Added
* Add `fs::StaticFileConfig` to provide means of customizing static
file services. It allows to map `mime` to `Content-Disposition`,
specify whether to use `ETag` and `Last-Modified` and allowed methods.
* Add `.has_prefixed_resource()` method to `router::ResourceInfo`
for route matching with prefix awareness
* Add `HttpMessage::readlines()` for reading line by line.
* Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests.
* Add method to configure custom error handler to `Form` extractor.
* Add methods to `HttpResponse` to retrieve, add, and delete cookies
* Add `.set_content_type()` and `.set_content_disposition()` methods
to `fs::NamedFile` to allow overriding the values inferred by default
* Add `fs::file_extension_to_mime()` helper function to get the MIME
type for a file extension
* Add `.content_disposition()` method to parse Content-Disposition of
multipart fields
* Re-export `actix::prelude::*` as `actix_web::actix` module.
* `HttpRequest::url_for_static()` for a named route with no variables segments
* Propagation of the application's default resource to scopes that haven't set a default resource.
### Changed
* Min rustc version is 1.26
* Use tokio instead of tokio-core
* `CookieSessionBackend` sets percent encoded cookies for outgoing HTTP messages.
* Became possible to use enums with query extractor.
Issue [#371](https://github.com/actix/actix-web/issues/371).
[Example](https://github.com/actix/actix-web/blob/master/tests/test_handlers.rs#L94-L134)
* `HttpResponse::into_builder()` now moves cookies into the builder
instead of dropping them
* For safety and performance reasons `Handler::handle()` uses `&self` instead of `&mut self`
* `Handler::handle()` uses `&HttpRequest` instead of `HttpRequest`
* Added header `User-Agent: Actix-web/<current_version>` to default headers when building a request
* port `Extensions` type from http create, we don't need `Send + Sync`
* `HttpRequest::query()` returns `Ref<HashMap<String, String>>`
* `HttpRequest::cookies()` returns `Ref<Vec<Cookie<'static>>>`
* `StaticFiles::new()` returns `Result<StaticFiles<S>, Error>` instead of `StaticFiles<S>`
* `StaticFiles` uses the default handler if the file does not exist
### Removed
* Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead.
* Remove `HttpMessage::range()`
## [0.6.15] - 2018-07-11
### Fixed
* Fix h2 compatibility #352
* Fix duplicate tail of StaticFiles with index_file. #344
## [0.6.14] - 2018-06-21
### Added
* Allow to disable masking for websockets client
### Fixed
* SendRequest execution fails with the "internal error: entered unreachable code" #329
## [0.6.13] - 2018-06-11
* http/2 end-of-frame is not set if body is empty bytes #307
* InternalError can trigger memory unsafety #301
## [0.6.12] - 2018-06-08
### Added
* Add `Host` filter #287
* Allow to filter applications
* Improved failure interoperability with downcasting #285
* Allow to use custom resolver for `ClientConnector`
## [0.6.11] - 2018-06-05
* Support chunked encoding for UrlEncoded body #262
* `HttpRequest::url_for()` for a named route with no variables segments #265
* `Middleware::response()` is not invoked if error result was returned by another `Middleware::start()` #255
* CORS: Do not validate Origin header on non-OPTION requests #271
* Fix multipart upload "Incomplete" error #282
## [0.6.10] - 2018-05-24
### Added
* Allow to use path without trailing slashes for scope registration #241
* Allow to set encoding for exact NamedFile #239
### Fixed
* `TestServer::post()` actually sends `GET` request #240
## 0.6.9 (2018-05-22)
* Drop connection if request's payload is not fully consumed #236
* Fix streaming response with body compression
## 0.6.8 (2018-05-20)
* Fix scope resource path extractor #234
* Re-use tcp listener on pause/resume
## 0.6.7 (2018-05-17)
* Fix compilation with --no-default-features
## 0.6.6 (2018-05-17)
* Panic during middleware execution #226
* Add support for listen_tls/listen_ssl #224
* Implement extractor for `Session`
* Ranges header support for NamedFile #60
## 0.6.5 (2018-05-15)
* Fix error handling during request decoding #222
## 0.6.4 (2018-05-11)
* Fix segfault in ServerSettings::get_response_builder()
## 0.6.3 (2018-05-10)
* Add `Router::with_async()` method for async handler registration.
* Added error response functions for 501,502,503,504
* Fix client request timeout handling
## 0.6.2 (2018-05-09)
* WsWriter trait is optional.
## 0.6.1 (2018-05-08)
* Fix http/2 payload streaming #215
* Fix connector's default `keep-alive` and `lifetime` settings #212
* Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214
* Allow to exclude certain endpoints from logging #211
## 0.6.0 (2018-05-08)
* Add route scopes #202
* Allow to use ssl and non-ssl connections at the same time #206
* Websocket CloseCode Empty/Status is ambiguous #193
* Add Content-Disposition to NamedFile #204
* Allow to access Error's backtrace object
* Allow to override files listing renderer for `StaticFiles` #203
* Various extractor usability improvements #207
## 0.5.6 (2018-04-24)
* Make flate2 crate optional #200
## 0.5.5 (2018-04-24)
* Fix panic when Websocket is closed with no error code #191
* Allow to use rust backend for flate2 crate #199
## 0.5.4 (2018-04-19)
* Add identity service middleware
* Middleware response() is not invoked if there was an error in async handler #187
* Use Display formatting for InternalError Display implementation #188
## 0.5.3 (2018-04-18)
* Impossible to quote slashes in path parameters #182
## 0.5.2 (2018-04-16)
* Allow to configure StaticFiles's CpuPool, via static method or env variable
* Add support for custom handling of Json extractor errors #181
* Fix StaticFiles does not support percent encoded paths #177
* Fix Client Request with custom Body Stream halting on certain size requests #176
## 0.5.1 (2018-04-12)
* Client connector provides stats, `ClientConnector::stats()`
* Fix end-of-stream handling in parse_payload #173
* Fix StaticFiles generate a lot of threads #174
## 0.5.0 (2018-04-10)
* Type-safe path/query/form parameter handling, using serde #70
* HttpResponse builder's methods `.body()`, `.finish()`, `.json()`
return `HttpResponse` instead of `Result`
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()`
* Added `signed` and `private` `CookieSessionBackend`s
* Added `HttpRequest::resource()`, returns current matched resource
* Added `ErrorHandlers` middleware
* Fix router cannot parse Non-ASCII characters in URL #137
* Fix client connection pooling
* Fix long client urls #129
* Fix panic on invalid URL characters #130
* Fix logger request duration calculation #152
* Fix prefix and static file serving #168
## 0.4.10 (2018-03-20)
* Use `Error` instead of `InternalError` for `error::ErrorXXXX` methods
* Allow to set client request timeout
* Allow to set client websocket handshake timeout
* Refactor `TestServer` configuration
* Fix server websockets big payloads support
* Fix http/2 date header generation
## 0.4.9 (2018-03-16)
* Allow to disable http/2 support
* Wake payload reading task when data is available
* Fix server keep-alive handling
* Send Query Parameters in client requests #120
* Move brotli encoding to a feature
* Add option of default handler for `StaticFiles` handler #57
* Add basic client connection pooling
## 0.4.8 (2018-03-12)
* Allow to set read buffer capacity for server request
* Handle WouldBlock error for socket accept call
## 0.4.7 (2018-03-11)
* Fix panic on unknown content encoding
* Fix connection get closed too early
* Fix streaming response handling for http/2
* Better sleep on error support
## 0.4.6 (2018-03-10)
* Fix client cookie handling
* Fix json content type detection
* Fix CORS middleware #117
* Optimize websockets stream support
## 0.4.5 (2018-03-07)
* Fix compression #103 and #104
* Fix client cookie handling #111
* Non-blocking processing of a `NamedFile`
* Enable compression support for `NamedFile`
* Better support for `NamedFile` type
* Add `ResponseError` impl for `SendRequestError`. This improves ergonomics of the client.
* Add native-tls support for client
* Allow client connection timeout to be set #108
* Allow to use std::net::TcpListener for HttpServer
* Handle panics in worker threads
## 0.4.4 (2018-03-04)
* Allow to use Arc<Vec<u8>> as response/request body
* Fix handling of requests with an encoded body with a length > 8192 #93
## 0.4.3 (2018-03-03)
* Fix request body read bug
* Fix segmentation fault #79
* Set reuse address before bind #90
## 0.4.2 (2018-03-02)
* Better naming for websockets implementation
* Add `Pattern::with_prefix()`, make it more usable outside of actix
* Add csrf middleware for filter for cross-site request forgery #89
* Fix disconnect on idle connections
## 0.4.1 (2018-03-01)
* Rename `Route::p()` to `Route::filter()`
* Better naming for http codes
* Fix payload parse in situation when socket data is not ready.
* Fix Session mutable borrow lifetime #87
## 0.4.0 (2018-02-28)
* Actix 0.5 compatibility
* Fix request json/urlencoded loaders
* Simplify HttpServer type definition
* Added HttpRequest::encoding() method
* Added HttpRequest::mime_type() method
* Added HttpRequest::uri_mut(), allows to modify request uri
* Added StaticFiles::index_file()
* Added http client
* Added websocket client
* Added TestServer::ws(), test websockets client
* Added TestServer http client support
* Allow to override content encoding on application level
## 0.3.3 (2018-01-25)
* Stop processing any events after context stop
* Re-enable write back-pressure for h1 connections
* Refactor HttpServer::start_ssl() method
* Upgrade openssl to 0.10
## 0.3.2 (2018-01-21)
* Fix HEAD requests handling
* Log request processing errors
* Always enable content encoding if encoding explicitly selected
* Allow multiple Applications on a single server with different state #49
* CORS middleware: allowed_headers is defaulting to None #50
## 0.3.1 (2018-01-13)
* Fix directory entry path #47
* Do not enable chunked encoding for HTTP/1.0
* Allow explicitly disable chunked encoding
## 0.3.0 (2018-01-12)
* HTTP/2 Support
* Refactor streaming responses
* Refactor error handling
* Asynchronous middlewares
* Refactor logger middleware
* Content compression/decompression (br, gzip, deflate)
* Server multi-threading
* Graceful shutdown support
## 0.2.1 (2017-11-03)
* Allow to start tls server with `HttpServer::serve_tls`
* Export `Frame` enum
* Add conversion impl from `HttpResponse` and `BinaryBody` to a `Frame`
## 0.2.0 (2017-10-30)
* Do not use `http::Uri` as it can not parse some valid paths
* Refactor response `Body`
* Refactor `RouteRecognizer` usability
* Refactor `HttpContext::write`
* Refactor `Payload` stream
* Re-use `BinaryBody` for `Frame::Payload`
* Stop http actor on `write_eof`
* Fix disconnection handling.
## 0.1.0 (2017-10-23)
* First release
* Initial impl

View File

@ -1,8 +1,8 @@
[package]
name = "actix-web"
version = "0.7.9"
name = "actix-http"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
description = "Actix http"
readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs"
@ -14,7 +14,6 @@ categories = ["network-programming", "asynchronous",
"web-programming::websocket"]
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
build = "build.rs"
[package.metadata.docs.rs]
features = ["tls", "alpn", "rust-tls", "session", "brotli", "flate2-c"]
@ -25,7 +24,7 @@ appveyor = { repository = "fafhrd91/actix-web-hdy9d" }
codecov = { repository = "actix/actix-web", branch = "master", service = "github" }
[lib]
name = "actix_web"
name = "actix_http"
path = "src/lib.rs"
[features]
@ -130,6 +129,7 @@ webpki-roots = { version = "0.15", optional = true }
tokio-uds = { version="0.2", optional = true }
[dev-dependencies]
actix-web = "0.7"
env_logger = "0.5"
serde_derive = "1.0"
@ -140,8 +140,3 @@ version_check = "0.1"
lto = true
opt-level = 3
codegen-units = 1
[workspace]
members = [
"./",
]

View File

@ -1,176 +0,0 @@
## 0.7.4
* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
even for handler with one parameter.
## 0.7
* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload
use `HttpMessage::payload()` method.
instead of
```rust
fn index(req: HttpRequest) -> impl Responder {
req
.from_err()
.fold(...)
....
}
```
use `.payload()`
```rust
fn index(req: HttpRequest) -> impl Responder {
req
.payload() // <- get request payload stream
.from_err()
.fold(...)
....
}
```
* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html)
trait uses `&HttpRequest` instead of `&mut HttpRequest`.
* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead.
instead of
```rust
fn index(query: Query<..>, info: Json<MyStruct) -> impl Responder {}
```
use tuple of extractors and use `.with()` for registration:
```rust
fn index((query, json): (Query<..>, Json<MyStruct)) -> impl Responder {}
```
* `Handler::handle()` uses `&self` instead of `&mut self`
* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value
* Removed deprecated `HttpServer::threads()`, use
[HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead.
* Renamed `client::ClientConnectorError::Connector` to
`client::ClientConnectorError::Resolver`
* `Route::with()` does not return `ExtractorConfig`, to configure
extractor use `Route::with_config()`
instead of
```rust
fn main() {
let app = App::new().resource("/index.html", |r| {
r.method(http::Method::GET)
.with(index)
.limit(4096); // <- limit size of the payload
});
}
```
use
```rust
fn main() {
let app = App::new().resource("/index.html", |r| {
r.method(http::Method::GET)
.with_config(index, |cfg| { // <- register handler
cfg.limit(4096); // <- limit size of the payload
})
});
}
```
* `Route::with_async()` does not return `ExtractorConfig`, to configure
extractor use `Route::with_async_config()`
## 0.6
* `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
* `ws::Message::Close` now includes optional close reason.
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
* `HttpServer::threads()` renamed to `HttpServer::workers()`.
* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated.
Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead.
* `HttpRequest::extensions()` returns read only reference to the request's Extension
`HttpRequest::extensions_mut()` returns mutable reference.
* Instead of
`use actix_web::middleware::{
CookieSessionBackend, CookieSessionError, RequestSession,
Session, SessionBackend, SessionImpl, SessionStorage};`
use `actix_web::middleware::session`
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError,
RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
* `FromRequest::from_request()` accepts mutable reference to a request
* `FromRequest::Result` has to implement `Into<Reply<Self>>`
* [`Responder::respond_to()`](
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
is generic over `S`
* Use `Query` extractor instead of HttpRequest::query()`.
```rust
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
...
}
```
or
```rust
let q = Query::<HashMap<String, String>>::extract(req);
```
* Websocket operations are implemented as `WsWriter` trait.
you need to use `use actix_web::ws::WsWriter`
## 0.5
* `HttpResponseBuilder::body()`, `.finish()`, `.json()`
methods return `HttpResponse` instead of `Result<HttpResponse>`
* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version`
moved to `actix_web::http` module
* `actix_web::header` moved to `actix_web::http::header`
* `NormalizePath` moved to `actix_web::http` module
* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function,
shortcut for `actix_web::server::HttpServer::new()`
* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself
* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead.
* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type
* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()`
functions should be used instead
* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>`
instead of `Result<_, http::Error>`
* `Application` renamed to a `App`
* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev`

View File

@ -1,14 +0,0 @@
.PHONY: default build test doc book clean
CARGO_FLAGS := --features "$(FEATURES) alpn tls"
default: test
build:
cargo build $(CARGO_FLAGS)
test: build clippy
cargo test $(CARGO_FLAGS)
doc: build
cargo doc --no-deps $(CARGO_FLAGS)

View File

@ -1,20 +1,6 @@
# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![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-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) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![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-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 web is a simple, pragmatic and extremely fast web framework for Rust.
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols
* Streaming and pipelining
* Keep-alive and slow requests handling
* Client/server [WebSockets](https://actix.rs/docs/websockets/) support
* Transparent content compression/decompression (br, gzip, deflate)
* Configurable [request routing](https://actix.rs/docs/url-dispatch/)
* Graceful server shutdown
* Multipart streams
* Static assets
* SSL support with OpenSSL or `native-tls`
* Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/))
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
* Built on top of [Actix actor framework](https://github.com/actix/actix)
Actix http
## Documentation & community resources
@ -44,30 +30,6 @@ fn main() {
}
```
### More examples
* [Basics](https://github.com/actix/examples/tree/master/basics/)
* [Stateful](https://github.com/actix/examples/tree/master/state/)
* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/)
* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/)
* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/)
* [Tera](https://github.com/actix/examples/tree/master/template_tera/) /
[Askama](https://github.com/actix/examples/tree/master/template_askama/) templates
* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/)
* [r2d2](https://github.com/actix/examples/tree/master/r2d2/)
* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/)
* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/)
* [Json](https://github.com/actix/examples/tree/master/json/)
You may consider checking out
[this directory](https://github.com/actix/examples/tree/master/) for more examples.
## Benchmarks
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext)
* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks).
## License
This project is licensed under either of
@ -80,5 +42,5 @@ at your option.
## Code of Conduct
Contribution to the actix-web crate is organized under the terms of the
Contributor Covenant, the maintainer of actix-web, @fafhrd91, promises to
Contributor Covenant, the maintainer of actix-net, @fafhrd91, promises to
intervene to uphold that code of conduct.

View File

@ -1,16 +0,0 @@
extern crate version_check;
fn main() {
match version_check::is_min_version("1.26.0") {
Some((true, _)) => println!("cargo:rustc-cfg=actix_impl_trait"),
_ => (),
};
match version_check::is_nightly() {
Some(true) => {
println!("cargo:rustc-cfg=actix_nightly");
println!("cargo:rustc-cfg=actix_impl_trait");
}
Some(false) => (),
None => (),
};
}

View File

@ -1,879 +0,0 @@
use std::rc::Rc;
use handler::{AsyncResult, FromRequest, Handler, Responder, WrapHandler};
use header::ContentEncoding;
use http::Method;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::Middleware;
use pipeline::{Pipeline, PipelineHandler};
use pred::Predicate;
use resource::Resource;
use router::{ResourceDef, Router};
use scope::Scope;
use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request};
use with::WithFactory;
/// Application
pub struct HttpApplication<S = ()> {
state: Rc<S>,
prefix: String,
prefix_len: usize,
inner: Rc<Inner<S>>,
filters: Option<Vec<Box<Predicate<S>>>>,
middlewares: Rc<Vec<Box<Middleware<S>>>>,
}
#[doc(hidden)]
pub struct Inner<S> {
router: Router<S>,
encoding: ContentEncoding,
}
impl<S: 'static> PipelineHandler<S> for Inner<S> {
#[inline]
fn encoding(&self) -> ContentEncoding {
self.encoding
}
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
self.router.handle(req)
}
}
impl<S: 'static> HttpApplication<S> {
#[cfg(test)]
pub(crate) fn run(&self, req: Request) -> AsyncResult<HttpResponse> {
let info = self
.inner
.router
.recognize(&req, &self.state, self.prefix_len);
let req = HttpRequest::new(req, Rc::clone(&self.state), info);
self.inner.handle(&req)
}
}
impl<S: 'static> HttpHandler for HttpApplication<S> {
type Task = Pipeline<S, Inner<S>>;
fn handle(&self, msg: Request) -> Result<Pipeline<S, Inner<S>>, Request> {
let m = {
if self.prefix_len == 0 {
true
} else {
let path = msg.path();
path.starts_with(&self.prefix)
&& (path.len() == self.prefix_len
|| path.split_at(self.prefix_len).1.starts_with('/'))
}
};
if m {
if let Some(ref filters) = self.filters {
for filter in filters {
if !filter.check(&msg, &self.state) {
return Err(msg);
}
}
}
let info = self
.inner
.router
.recognize(&msg, &self.state, self.prefix_len);
let inner = Rc::clone(&self.inner);
let req = HttpRequest::new(msg, Rc::clone(&self.state), info);
Ok(Pipeline::new(req, Rc::clone(&self.middlewares), inner))
} else {
Err(msg)
}
}
}
struct ApplicationParts<S> {
state: S,
prefix: String,
router: Router<S>,
encoding: ContentEncoding,
middlewares: Vec<Box<Middleware<S>>>,
filters: Vec<Box<Predicate<S>>>,
}
/// Structure that follows the builder pattern for building application
/// instances.
pub struct App<S = ()> {
parts: Option<ApplicationParts<S>>,
}
impl App<()> {
/// Create application with empty state. Application can
/// be configured with a builder-like pattern.
pub fn new() -> App<()> {
App::with_state(())
}
}
impl Default for App<()> {
fn default() -> Self {
App::new()
}
}
impl<S> App<S>
where
S: 'static,
{
/// Create application with specified state. Application can be
/// configured with a builder-like pattern.
///
/// State is shared with all resources within same application and
/// could be accessed with `HttpRequest::state()` method.
///
/// **Note**: http server accepts an application factory rather than
/// an application instance. Http server constructs an application
/// instance for each thread, thus application state must be constructed
/// multiple times. If you want to share state between different
/// threads, a shared object should be used, e.g. `Arc`. Application
/// state does not need to be `Send` or `Sync`.
pub fn with_state(state: S) -> App<S> {
App {
parts: Some(ApplicationParts {
state,
prefix: "".to_owned(),
router: Router::new(ResourceDef::prefix("")),
middlewares: Vec::new(),
filters: Vec::new(),
encoding: ContentEncoding::Auto,
}),
}
}
/// Get reference to the application state
pub fn state(&self) -> &S {
let parts = self.parts.as_ref().expect("Use after finish");
&parts.state
}
/// Set application prefix.
///
/// Only requests that match the application's prefix get
/// processed by this application.
///
/// The application prefix always contains a leading slash (`/`).
/// If the supplied prefix does not contain leading slash, it is
/// inserted.
///
/// Prefix should consist of valid path segments. i.e for an
/// application with the prefix `/app` any request with the paths
/// `/app`, `/app/` or `/app/test` would match, but the path
/// `/application` would not.
///
/// In the following example only requests with an `/app/` path
/// prefix get handled. Requests with path `/app/test/` would be
/// handled, while requests with the paths `/application` or
/// `/other/...` would return `NOT FOUND`. It is also possible to
/// handle `/app` path, to do this you can register resource for
/// empty string `""`
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .prefix("/app")
/// .resource("", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app` path
/// .resource("/", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app/` path
/// .resource("/test", |r| {
/// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// })
/// .finish();
/// }
/// ```
pub fn prefix<P: Into<String>>(mut self, prefix: P) -> App<S> {
{
let parts = self.parts.as_mut().expect("Use after finish");
let mut prefix = prefix.into();
if !prefix.starts_with('/') {
prefix.insert(0, '/')
}
parts.router.set_prefix(&prefix);
parts.prefix = prefix;
}
self
}
/// Add match predicate to application.
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn main() {
/// App::new()
/// .filter(pred::Host("www.rust-lang.org"))
/// .resource("/path", |r| r.f(|_| HttpResponse::Ok()))
/// # .finish();
/// # }
/// ```
pub fn filter<T: Predicate<S> + 'static>(mut self, p: T) -> App<S> {
{
let parts = self.parts.as_mut().expect("Use after finish");
parts.filters.push(Box::new(p));
}
self
}
/// Configure route for a specific path.
///
/// This is a simplified version of the `App::resource()` method.
/// Handler functions need to accept one request extractor
/// argument.
///
/// This method could be called multiple times, in that case
/// multiple routes would be registered for same resource path.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpRequest, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .route("/test", http::Method::GET, |_: HttpRequest| {
/// HttpResponse::Ok()
/// })
/// .route("/test", http::Method::POST, |_: HttpRequest| {
/// HttpResponse::MethodNotAllowed()
/// });
/// }
/// ```
pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> App<S>
where
F: WithFactory<T, S, R>,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{
self.parts
.as_mut()
.expect("Use after finish")
.router
.register_route(path, method, f);
self
}
/// Configure scope for common root path.
///
/// Scopes collect multiple paths under a common path prefix.
/// Scope path can contain variable path segments as resources.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpRequest, HttpResponse};
///
/// fn main() {
/// 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()))
/// });
/// }
/// ```
///
/// In the above example, three routes get added:
/// * /{project_id}/path1
/// * /{project_id}/path2
/// * /{project_id}/path3
///
pub fn scope<F>(mut self, path: &str, f: F) -> App<S>
where
F: FnOnce(Scope<S>) -> Scope<S>,
{
let scope = f(Scope::new(path));
self.parts
.as_mut()
.expect("Use after finish")
.router
.register_scope(scope);
self
}
/// Configure resource for a specific path.
///
/// Resources may have variable path segments. For example, a
/// resource with the path `/a/{name}/c` would match all incoming
/// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`.
///
/// A variable segment is specified in the form `{identifier}`,
/// where the identifier can be used later in a request handler to
/// access the matched value for that segment. This is done by
/// looking up the identifier in the `Params` object returned by
/// `HttpRequest.match_info()` method.
///
/// By default, each segment matches the regular expression `[^{}/]+`.
///
/// You can also specify a custom regex in the form `{identifier:regex}`:
///
/// For instance, to route `GET`-requests on any route matching
/// `/users/{userid}/{friend}` and store `userid` and `friend` in
/// the exposed `Params` object:
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
///
/// fn main() {
/// let app = App::new().resource("/users/{userid}/{friend}", |r| {
/// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// });
/// }
/// ```
pub fn resource<F, R>(mut self, path: &str, f: F) -> App<S>
where
F: FnOnce(&mut Resource<S>) -> R + 'static,
{
{
let parts = self.parts.as_mut().expect("Use after finish");
// create resource
let mut resource = Resource::new(ResourceDef::new(path));
// configure
f(&mut resource);
parts.router.register_resource(resource);
}
self
}
/// Configure resource for a specific path.
#[doc(hidden)]
pub fn register_resource(&mut self, resource: Resource<S>) {
self.parts
.as_mut()
.expect("Use after finish")
.router
.register_resource(resource);
}
/// Default resource to be used if no matching route could be found.
pub fn default_resource<F, R>(mut self, f: F) -> App<S>
where
F: FnOnce(&mut Resource<S>) -> R + 'static,
{
// create and configure default resource
let mut resource = Resource::new(ResourceDef::new(""));
f(&mut resource);
self.parts
.as_mut()
.expect("Use after finish")
.router
.register_default_resource(resource.into());
self
}
/// Set default content encoding. `ContentEncoding::Auto` is set by default.
pub fn default_encoding(mut self, encoding: ContentEncoding) -> App<S> {
{
let parts = self.parts.as_mut().expect("Use after finish");
parts.encoding = encoding;
}
self
}
/// Register an external resource.
///
/// External resources are useful for URL generation purposes only
/// and are never considered for matching at request time. Calls to
/// `HttpRequest::url_for()` will work as expected.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{App, HttpRequest, HttpResponse, Result};
///
/// fn index(req: &HttpRequest) -> Result<HttpResponse> {
/// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
/// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
/// Ok(HttpResponse::Ok().into())
/// }
///
/// fn main() {
/// let app = App::new()
/// .resource("/index.html", |r| r.get().f(index))
/// .external_resource("youtube", "https://youtube.com/watch/{video_id}")
/// .finish();
/// }
/// ```
pub fn external_resource<T, U>(mut self, name: T, url: U) -> App<S>
where
T: AsRef<str>,
U: AsRef<str>,
{
self.parts
.as_mut()
.expect("Use after finish")
.router
.register_external(name.as_ref(), ResourceDef::external(url.as_ref()));
self
}
/// Configure handler for specific path prefix.
///
/// A path prefix consists of valid path segments, i.e for the
/// prefix `/app` any request with the paths `/app`, `/app/` or
/// `/app/test` would match, but the path `/application` would
/// not.
///
/// Path tail is available as `tail` parameter in request's match_dict.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpRequest, HttpResponse};
///
/// fn main() {
/// let app = App::new().handler("/app", |req: &HttpRequest| match *req.method() {
/// http::Method::GET => HttpResponse::Ok(),
/// http::Method::POST => HttpResponse::MethodNotAllowed(),
/// _ => HttpResponse::NotFound(),
/// });
/// }
/// ```
pub fn handler<H: Handler<S>>(mut self, path: &str, handler: H) -> App<S> {
{
let mut path = path.trim().trim_right_matches('/').to_owned();
if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/');
};
self.parts
.as_mut()
.expect("Use after finish")
.router
.register_handler(&path, Box::new(WrapHandler::new(handler)), None);
}
self
}
/// Register a middleware.
pub fn middleware<M: Middleware<S>>(mut self, mw: M) -> App<S> {
self.parts
.as_mut()
.expect("Use after finish")
.middlewares
.push(Box::new(mw));
self
}
/// Run external configuration as part of the application building
/// process
///
/// This function is useful for moving parts of configuration to a
/// different module or event library. For example we can move
/// some of the resources' configuration to different module.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{fs, middleware, App, HttpResponse};
///
/// // this function could be located in different module
/// fn config(app: App) -> App {
/// app.resource("/test", |r| {
/// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// })
/// }
///
/// fn main() {
/// let app = App::new()
/// .middleware(middleware::Logger::default())
/// .configure(config) // <- register resources
/// .handler("/static", fs::StaticFiles::new(".").unwrap());
/// }
/// ```
pub fn configure<F>(self, cfg: F) -> App<S>
where
F: Fn(App<S>) -> App<S>,
{
cfg(self)
}
/// Finish application configuration and create `HttpHandler` object.
pub fn finish(&mut self) -> HttpApplication<S> {
let mut parts = self.parts.take().expect("Use after finish");
let prefix = parts.prefix.trim().trim_right_matches('/');
parts.router.finish();
let inner = Rc::new(Inner {
router: parts.router,
encoding: parts.encoding,
});
let filters = if parts.filters.is_empty() {
None
} else {
Some(parts.filters)
};
HttpApplication {
inner,
filters,
state: Rc::new(parts.state),
middlewares: Rc::new(parts.middlewares),
prefix: prefix.to_owned(),
prefix_len: prefix.len(),
}
}
/// Convenience method for creating `Box<HttpHandler>` instances.
///
/// This method is useful if you need to register multiple
/// application instances with different state.
///
/// ```rust
/// # use std::thread;
/// # extern crate actix_web;
/// use actix_web::{server, App, HttpResponse};
///
/// struct State1;
///
/// struct State2;
///
/// fn main() {
/// # thread::spawn(|| {
/// server::new(|| {
/// vec![
/// App::with_state(State1)
/// .prefix("/app1")
/// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
/// .boxed(),
/// App::with_state(State2)
/// .prefix("/app2")
/// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
/// .boxed(),
/// ]
/// }).bind("127.0.0.1:8080")
/// .unwrap()
/// .run()
/// # });
/// }
/// ```
pub fn boxed(mut self) -> Box<HttpHandler<Task = Box<HttpHandlerTask>>> {
Box::new(BoxedApplication { app: self.finish() })
}
}
struct BoxedApplication<S> {
app: HttpApplication<S>,
}
impl<S: 'static> HttpHandler for BoxedApplication<S> {
type Task = Box<HttpHandlerTask>;
fn handle(&self, req: Request) -> Result<Self::Task, Request> {
self.app.handle(req).map(|t| {
let task: Self::Task = Box::new(t);
task
})
}
}
impl<S: 'static> IntoHttpHandler for App<S> {
type Handler = HttpApplication<S>;
fn into_handler(mut self) -> HttpApplication<S> {
self.finish()
}
}
impl<'a, S: 'static> IntoHttpHandler for &'a mut App<S> {
type Handler = HttpApplication<S>;
fn into_handler(self) -> HttpApplication<S> {
self.finish()
}
}
#[doc(hidden)]
impl<S: 'static> Iterator for App<S> {
type Item = HttpApplication<S>;
fn next(&mut self) -> Option<Self::Item> {
if self.parts.is_some() {
Some(self.finish())
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use body::{Binary, Body};
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use pred;
use test::{TestRequest, TestServer};
#[test]
fn test_default_resource() {
let app = App::new()
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.finish();
let req = TestRequest::with_uri("/test").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/blah").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let app = App::new()
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed()))
.finish();
let req = TestRequest::with_uri("/blah").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED);
}
#[test]
fn test_unhandled_prefix() {
let app = App::new()
.prefix("/test")
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.finish();
let ctx = TestRequest::default().request();
assert!(app.handle(ctx).is_err());
}
#[test]
fn test_state() {
let app = App::with_state(10)
.resource("/", |r| r.f(|_| HttpResponse::Ok()))
.finish();
let req = TestRequest::with_state(10).request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
#[test]
fn test_prefix() {
let app = App::new()
.prefix("/test")
.resource("/blah", |r| r.f(|_| HttpResponse::Ok()))
.finish();
let req = TestRequest::with_uri("/test").request();
let resp = app.handle(req);
assert!(resp.is_ok());
let req = TestRequest::with_uri("/test/").request();
let resp = app.handle(req);
assert!(resp.is_ok());
let req = TestRequest::with_uri("/test/blah").request();
let resp = app.handle(req);
assert!(resp.is_ok());
let req = TestRequest::with_uri("/testing").request();
let resp = app.handle(req);
assert!(resp.is_err());
}
#[test]
fn test_handler() {
let app = App::new()
.handler("/test", |_: &_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/test").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/app").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/testapp").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/blah").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_handler2() {
let app = App::new()
.handler("test", |_: &_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/test").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/app").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/testapp").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/blah").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_handler_with_prefix() {
let app = App::new()
.prefix("prefix")
.handler("/test", |_: &_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/prefix/test").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/test/").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/test/app").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/testapp").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/prefix/blah").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_route() {
let app = App::new()
.route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok())
.route("/test", Method::POST, |_: HttpRequest| {
HttpResponse::Created()
}).finish();
let req = TestRequest::with_uri("/test").method(Method::GET).request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test")
.method(Method::POST)
.request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/test")
.method(Method::HEAD)
.request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_handler_prefix() {
let app = App::new()
.prefix("/app")
.handler("/test", |_: &_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/test").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/test").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/test/").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/test/app").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/testapp").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/blah").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_option_responder() {
let app = App::new()
.resource("/none", |r| r.f(|_| -> Option<&'static str> { None }))
.resource("/some", |r| r.f(|_| Some("some")))
.finish();
let req = TestRequest::with_uri("/none").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/some").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some")));
}
#[test]
fn test_filter() {
let mut srv = TestServer::with_factory(|| {
App::new()
.filter(pred::Get())
.handler("/test", |_: &_| HttpResponse::Ok())
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let request = srv.post().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_prefix_root() {
let mut srv = TestServer::with_factory(|| {
App::new()
.prefix("/test")
.resource("/", |r| r.f(|_| HttpResponse::Ok()))
.resource("", |r| r.f(|_| HttpResponse::Created()))
});
let request = srv.get().uri(srv.url("/test/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::CREATED);
}
}

View File

@ -3,10 +3,7 @@ use futures::Stream;
use std::sync::Arc;
use std::{fmt, mem};
use context::ActorHttpContext;
use error::Error;
use handler::Responder;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
/// Type represent streaming body
@ -21,8 +18,8 @@ pub enum Body {
/// Unspecified streaming response. Developer is responsible for setting
/// right `Content-Length` or `Transfer-Encoding` headers.
Streaming(BodyStream),
/// Special body type for actor response.
Actor(Box<ActorHttpContext>),
// /// Special body type for actor response.
// Actor(Box<ActorHttpContext>),
}
/// Represents various types of binary body.
@ -45,7 +42,7 @@ impl Body {
#[inline]
pub fn is_streaming(&self) -> bool {
match *self {
Body::Streaming(_) | Body::Actor(_) => true,
Body::Streaming(_) => true,
_ => false,
}
}
@ -94,7 +91,7 @@ impl PartialEq for Body {
Body::Binary(ref b2) => b == b2,
_ => false,
},
Body::Streaming(_) | Body::Actor(_) => false,
Body::Streaming(_) => false,
}
}
}
@ -105,7 +102,6 @@ impl fmt::Debug for Body {
Body::Empty => write!(f, "Body::Empty"),
Body::Binary(ref b) => write!(f, "Body::Binary({:?})", b),
Body::Streaming(_) => write!(f, "Body::Streaming(_)"),
Body::Actor(_) => write!(f, "Body::Actor(_)"),
}
}
}
@ -119,12 +115,6 @@ where
}
}
impl From<Box<ActorHttpContext>> for Body {
fn from(ctx: Box<ActorHttpContext>) -> Body {
Body::Actor(ctx)
}
}
impl Binary {
#[inline]
/// Returns `true` if body is empty
@ -254,17 +244,6 @@ impl AsRef<[u8]> for Binary {
}
}
impl Responder for Binary {
type Item = HttpResponse;
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(HttpResponse::build_from(req)
.content_type("application/octet-stream")
.body(self))
}
}
#[cfg(test)]
mod tests {
use super::*;

File diff suppressed because it is too large Load Diff

View File

@ -1,118 +0,0 @@
//! Http client api
//!
//! ```rust
//! # extern crate actix_web;
//! # extern crate futures;
//! # extern crate tokio;
//! # use futures::Future;
//! # use std::process;
//! use actix_web::{actix, client};
//!
//! fn main() {
//! actix::run(
//! || client::get("http://www.rust-lang.org") // <- Create request builder
//! .header("User-Agent", "Actix-web")
//! .finish().unwrap()
//! .send() // <- Send http request
//! .map_err(|_| ())
//! .and_then(|response| { // <- server http response
//! println!("Response: {:?}", response);
//! # actix::System::current().stop();
//! Ok(())
//! })
//! );
//! }
//! ```
mod connector;
mod parser;
mod pipeline;
mod request;
mod response;
mod writer;
pub use self::connector::{
ClientConnector, ClientConnectorError, ClientConnectorStats, Connect, Connection,
Pause, Resume,
};
pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError};
pub(crate) use self::pipeline::Pipeline;
pub use self::pipeline::{SendRequest, SendRequestError};
pub use self::request::{ClientRequest, ClientRequestBuilder};
pub use self::response::ClientResponse;
pub(crate) use self::writer::HttpClientWriter;
use error::ResponseError;
use http::Method;
use httpresponse::HttpResponse;
/// Convert `SendRequestError` to a `HttpResponse`
impl ResponseError for SendRequestError {
fn error_response(&self) -> HttpResponse {
match *self {
SendRequestError::Timeout => HttpResponse::GatewayTimeout(),
SendRequestError::Connector(_) => HttpResponse::BadGateway(),
_ => HttpResponse::InternalServerError(),
}.into()
}
}
/// Create request builder for `GET` requests
///
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # extern crate tokio;
/// # extern crate env_logger;
/// # use futures::Future;
/// # use std::process;
/// use actix_web::{actix, client};
///
/// fn main() {
/// actix::run(
/// || client::get("http://www.rust-lang.org") // <- Create request builder
/// .header("User-Agent", "Actix-web")
/// .finish().unwrap()
/// .send() // <- Send http request
/// .map_err(|_| ())
/// .and_then(|response| { // <- server http response
/// println!("Response: {:?}", response);
/// # actix::System::current().stop();
/// Ok(())
/// }),
/// );
/// }
/// ```
pub fn get<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::GET).uri(uri);
builder
}
/// Create request builder for `HEAD` requests
pub fn head<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::HEAD).uri(uri);
builder
}
/// Create request builder for `POST` requests
pub fn post<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::POST).uri(uri);
builder
}
/// Create request builder for `PUT` requests
pub fn put<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::PUT).uri(uri);
builder
}
/// Create request builder for `DELETE` requests
pub fn delete<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::DELETE).uri(uri);
builder
}

View File

@ -1,238 +0,0 @@
use std::mem;
use bytes::{Bytes, BytesMut};
use futures::{Async, Poll};
use http::header::{self, HeaderName, HeaderValue};
use http::{HeaderMap, StatusCode, Version};
use httparse;
use error::{ParseError, PayloadError};
use server::h1decoder::{EncodingDecoder, HeaderIndex};
use server::IoStream;
use super::response::ClientMessage;
use super::ClientResponse;
const MAX_BUFFER_SIZE: usize = 131_072;
const MAX_HEADERS: usize = 96;
#[derive(Default)]
pub struct HttpResponseParser {
decoder: Option<EncodingDecoder>,
eof: bool, // indicate that we read payload until stream eof
}
#[derive(Debug, Fail)]
pub enum HttpResponseParserError {
/// Server disconnected
#[fail(display = "Server disconnected")]
Disconnect,
#[fail(display = "{}", _0)]
Error(#[cause] ParseError),
}
impl HttpResponseParser {
pub fn parse<T>(
&mut self, io: &mut T, buf: &mut BytesMut,
) -> Poll<ClientResponse, HttpResponseParserError>
where
T: IoStream,
{
loop {
// Don't call parser until we have data to parse.
if !buf.is_empty() {
match HttpResponseParser::parse_message(buf)
.map_err(HttpResponseParserError::Error)?
{
Async::Ready((msg, info)) => {
if let Some((decoder, eof)) = info {
self.eof = eof;
self.decoder = Some(decoder);
} else {
self.eof = false;
self.decoder = None;
}
return Ok(Async::Ready(msg));
}
Async::NotReady => {
if buf.capacity() >= MAX_BUFFER_SIZE {
return Err(HttpResponseParserError::Error(
ParseError::TooLarge,
));
}
// Parser needs more data.
}
}
}
// Read some more data into the buffer for the parser.
match io.read_available(buf) {
Ok(Async::Ready((false, true))) => {
return Err(HttpResponseParserError::Disconnect)
}
Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => return Err(HttpResponseParserError::Error(err.into())),
}
}
}
pub fn parse_payload<T>(
&mut self, io: &mut T, buf: &mut BytesMut,
) -> Poll<Option<Bytes>, PayloadError>
where
T: IoStream,
{
if self.decoder.is_some() {
loop {
// read payload
let (not_ready, stream_finished) = match io.read_available(buf) {
Ok(Async::Ready((_, true))) => (false, true),
Ok(Async::Ready((_, false))) => (false, false),
Ok(Async::NotReady) => (true, false),
Err(err) => return Err(err.into()),
};
match self.decoder.as_mut().unwrap().decode(buf) {
Ok(Async::Ready(Some(b))) => return Ok(Async::Ready(Some(b))),
Ok(Async::Ready(None)) => {
self.decoder.take();
return Ok(Async::Ready(None));
}
Ok(Async::NotReady) => {
if not_ready {
return Ok(Async::NotReady);
}
if stream_finished {
// read untile eof?
if self.eof {
return Ok(Async::Ready(None));
} else {
return Err(PayloadError::Incomplete);
}
}
}
Err(err) => return Err(err.into()),
}
}
} else {
Ok(Async::Ready(None))
}
}
fn parse_message(
buf: &mut BytesMut,
) -> Poll<(ClientResponse, Option<(EncodingDecoder, bool)>), ParseError> {
// Unsafe: we read only this data only after httparse parses headers into.
// performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() };
let (len, version, status, headers_len) = {
let mut parsed: [httparse::Header; MAX_HEADERS] =
unsafe { mem::uninitialized() };
let mut resp = httparse::Response::new(&mut parsed);
match resp.parse(buf)? {
httparse::Status::Complete(len) => {
let version = if resp.version.unwrap_or(1) == 1 {
Version::HTTP_11
} else {
Version::HTTP_10
};
HeaderIndex::record(buf, resp.headers, &mut headers);
let status = StatusCode::from_u16(resp.code.unwrap())
.map_err(|_| ParseError::Status)?;
(len, version, status, resp.headers.len())
}
httparse::Status::Partial => return Ok(Async::NotReady),
}
};
let slice = buf.split_to(len).freeze();
// convert headers
let mut hdrs = HeaderMap::new();
for idx in headers[..headers_len].iter() {
if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) {
// Unsafe: httparse check header value for valid utf-8
let value = unsafe {
HeaderValue::from_shared_unchecked(
slice.slice(idx.value.0, idx.value.1),
)
};
hdrs.append(name, value);
} else {
return Err(ParseError::Header);
}
}
let decoder = if status == StatusCode::SWITCHING_PROTOCOLS {
Some((EncodingDecoder::eof(), true))
} else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) {
// Content-Length
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
Some((EncodingDecoder::length(len), false))
} else {
debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header);
}
} else {
debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header);
}
} else if chunked(&hdrs)? {
// Chunked encoding
Some((EncodingDecoder::chunked(), false))
} else if let Some(value) = hdrs.get(header::CONNECTION) {
let close = if let Ok(s) = value.to_str() {
s == "close"
} else {
false
};
if close {
Some((EncodingDecoder::eof(), true))
} else {
None
}
} else {
None
};
if let Some(decoder) = decoder {
Ok(Async::Ready((
ClientResponse::new(ClientMessage {
status,
version,
headers: hdrs,
cookies: None,
}),
Some(decoder),
)))
} else {
Ok(Async::Ready((
ClientResponse::new(ClientMessage {
status,
version,
headers: hdrs,
cookies: None,
}),
None,
)))
}
}
}
/// Check if request has chunked transfer encoding
pub fn chunked(headers: &HeaderMap) -> Result<bool, ParseError> {
if let Some(encodings) = headers.get(header::TRANSFER_ENCODING) {
if let Ok(s) = encodings.to_str() {
Ok(s.to_lowercase().contains("chunked"))
} else {
Err(ParseError::Header)
}
} else {
Ok(false)
}
}

View File

@ -1,552 +0,0 @@
use bytes::{Bytes, BytesMut};
use futures::sync::oneshot;
use futures::{Async, Future, Poll, Stream};
use http::header::CONTENT_ENCODING;
use std::time::{Duration, Instant};
use std::{io, mem};
use tokio_timer::Delay;
use actix::{Addr, Request, SystemService};
use super::{
ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect,
Connection, HttpClientWriter, HttpResponseParser, HttpResponseParserError,
};
use body::{Body, BodyStream};
use context::{ActorHttpContext, Frame};
use error::Error;
use error::PayloadError;
use header::ContentEncoding;
use http::{Method, Uri};
use httpmessage::HttpMessage;
use server::input::PayloadStream;
use server::WriterState;
/// A set of errors that can occur during request sending and response reading
#[derive(Fail, Debug)]
pub enum SendRequestError {
/// Response took too long
#[fail(display = "Timeout while waiting for response")]
Timeout,
/// Failed to connect to host
#[fail(display = "Failed to connect to host: {}", _0)]
Connector(#[cause] ClientConnectorError),
/// Error parsing response
#[fail(display = "{}", _0)]
ParseError(#[cause] HttpResponseParserError),
/// Error reading response payload
#[fail(display = "Error reading response payload: {}", _0)]
Io(#[cause] io::Error),
}
impl From<io::Error> for SendRequestError {
fn from(err: io::Error) -> SendRequestError {
SendRequestError::Io(err)
}
}
impl From<ClientConnectorError> for SendRequestError {
fn from(err: ClientConnectorError) -> SendRequestError {
match err {
ClientConnectorError::Timeout => SendRequestError::Timeout,
_ => SendRequestError::Connector(err),
}
}
}
enum State {
New,
Connect(Request<ClientConnector, Connect>),
Connection(Connection),
Send(Box<Pipeline>),
None,
}
/// `SendRequest` is a `Future` which represents an asynchronous
/// request sending process.
#[must_use = "SendRequest does nothing unless polled"]
pub struct SendRequest {
req: ClientRequest,
state: State,
conn: Option<Addr<ClientConnector>>,
conn_timeout: Duration,
wait_timeout: Duration,
timeout: Option<Duration>,
}
impl SendRequest {
pub(crate) fn new(req: ClientRequest) -> SendRequest {
SendRequest {
req,
conn: None,
state: State::New,
timeout: None,
wait_timeout: Duration::from_secs(5),
conn_timeout: Duration::from_secs(1),
}
}
pub(crate) fn with_connector(
req: ClientRequest, conn: Addr<ClientConnector>,
) -> SendRequest {
SendRequest {
req,
conn: Some(conn),
state: State::New,
timeout: None,
wait_timeout: Duration::from_secs(5),
conn_timeout: Duration::from_secs(1),
}
}
pub(crate) fn with_connection(req: ClientRequest, conn: Connection) -> SendRequest {
SendRequest {
req,
state: State::Connection(conn),
conn: None,
timeout: None,
wait_timeout: Duration::from_secs(5),
conn_timeout: Duration::from_secs(1),
}
}
/// Set request timeout
///
/// Request timeout is the total time before a response must be received.
/// Default value is 5 seconds.
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
/// Set connection timeout
///
/// Connection timeout includes resolving hostname and actual connection to
/// the host.
/// Default value is 1 second.
pub fn conn_timeout(mut self, timeout: Duration) -> Self {
self.conn_timeout = timeout;
self
}
/// Set wait timeout
///
/// If connections pool limits are enabled, wait time indicates max time
/// to wait for available connection. Default value is 5 seconds.
pub fn wait_timeout(mut self, timeout: Duration) -> Self {
self.wait_timeout = timeout;
self
}
}
impl Future for SendRequest {
type Item = ClientResponse;
type Error = SendRequestError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
let state = mem::replace(&mut self.state, State::None);
match state {
State::New => {
let conn = if let Some(conn) = self.conn.take() {
conn
} else {
ClientConnector::from_registry()
};
self.state = State::Connect(conn.send(Connect {
uri: self.req.uri().clone(),
wait_timeout: self.wait_timeout,
conn_timeout: self.conn_timeout,
}))
}
State::Connect(mut conn) => match conn.poll() {
Ok(Async::NotReady) => {
self.state = State::Connect(conn);
return Ok(Async::NotReady);
}
Ok(Async::Ready(result)) => match result {
Ok(stream) => self.state = State::Connection(stream),
Err(err) => return Err(err.into()),
},
Err(_) => {
return Err(SendRequestError::Connector(
ClientConnectorError::Disconnected,
));
}
},
State::Connection(conn) => {
let mut writer = HttpClientWriter::new();
writer.start(&mut self.req)?;
let body = match self.req.replace_body(Body::Empty) {
Body::Streaming(stream) => IoBody::Payload(stream),
Body::Actor(ctx) => IoBody::Actor(ctx),
_ => IoBody::Done,
};
let timeout = self
.timeout
.take()
.unwrap_or_else(|| Duration::from_secs(5));
let pl = Box::new(Pipeline {
body,
writer,
conn: Some(conn),
parser: Some(HttpResponseParser::default()),
parser_buf: BytesMut::new(),
disconnected: false,
body_completed: false,
drain: None,
decompress: None,
should_decompress: self.req.response_decompress(),
write_state: RunningState::Running,
timeout: Some(Delay::new(Instant::now() + timeout)),
meth: self.req.method().clone(),
path: self.req.uri().clone(),
});
self.state = State::Send(pl);
}
State::Send(mut pl) => {
pl.poll_timeout()?;
pl.poll_write().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str())
})?;
match pl.parse() {
Ok(Async::Ready(mut resp)) => {
if self.req.method() == Method::HEAD {
pl.parser.take();
}
resp.set_pipeline(pl);
return Ok(Async::Ready(resp));
}
Ok(Async::NotReady) => {
self.state = State::Send(pl);
return Ok(Async::NotReady);
}
Err(err) => {
return Err(SendRequestError::ParseError(err));
}
}
}
State::None => unreachable!(),
}
}
}
}
pub struct Pipeline {
body: IoBody,
body_completed: bool,
conn: Option<Connection>,
writer: HttpClientWriter,
parser: Option<HttpResponseParser>,
parser_buf: BytesMut,
disconnected: bool,
drain: Option<oneshot::Sender<()>>,
decompress: Option<PayloadStream>,
should_decompress: bool,
write_state: RunningState,
timeout: Option<Delay>,
meth: Method,
path: Uri,
}
enum IoBody {
Payload(BodyStream),
Actor(Box<ActorHttpContext>),
Done,
}
#[derive(Debug, PartialEq)]
enum RunningState {
Running,
Paused,
Done,
}
impl RunningState {
#[inline]
fn pause(&mut self) {
if *self != RunningState::Done {
*self = RunningState::Paused
}
}
#[inline]
fn resume(&mut self) {
if *self != RunningState::Done {
*self = RunningState::Running
}
}
}
impl Pipeline {
fn release_conn(&mut self) {
if let Some(conn) = self.conn.take() {
if self.meth == Method::HEAD {
conn.close()
} else {
conn.release()
}
}
}
#[inline]
fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> {
if let Some(ref mut conn) = self.conn {
match self
.parser
.as_mut()
.unwrap()
.parse(conn, &mut self.parser_buf)
{
Ok(Async::Ready(resp)) => {
// check content-encoding
if self.should_decompress {
if let Some(enc) = resp.headers().get(CONTENT_ENCODING) {
if let Ok(enc) = enc.to_str() {
match ContentEncoding::from(enc) {
ContentEncoding::Auto
| ContentEncoding::Identity => (),
enc => {
self.decompress = Some(PayloadStream::new(enc))
}
}
}
}
}
Ok(Async::Ready(resp))
}
val => val,
}
} else {
Ok(Async::NotReady)
}
}
#[inline]
pub(crate) fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
if self.conn.is_none() {
return Ok(Async::Ready(None));
}
let mut need_run = false;
// need write?
match self
.poll_write()
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?
{
Async::NotReady => need_run = true,
Async::Ready(_) => {
self.poll_timeout().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("{}", e))
})?;
}
}
// need read?
if self.parser.is_some() {
let conn: &mut Connection = self.conn.as_mut().unwrap();
loop {
match self
.parser
.as_mut()
.unwrap()
.parse_payload(conn, &mut self.parser_buf)?
{
Async::Ready(Some(b)) => {
if let Some(ref mut decompress) = self.decompress {
match decompress.feed_data(b) {
Ok(Some(b)) => return Ok(Async::Ready(Some(b))),
Ok(None) => return Ok(Async::NotReady),
Err(ref err)
if err.kind() == io::ErrorKind::WouldBlock =>
{
continue
}
Err(err) => return Err(err.into()),
}
} else {
return Ok(Async::Ready(Some(b)));
}
}
Async::Ready(None) => {
let _ = self.parser.take();
break;
}
Async::NotReady => return Ok(Async::NotReady),
}
}
}
// eof
if let Some(mut decompress) = self.decompress.take() {
let res = decompress.feed_eof();
if let Some(b) = res? {
self.release_conn();
return Ok(Async::Ready(Some(b)));
}
}
if need_run {
Ok(Async::NotReady)
} else {
self.release_conn();
Ok(Async::Ready(None))
}
}
fn poll_timeout(&mut self) -> Result<(), SendRequestError> {
if self.timeout.is_some() {
match self.timeout.as_mut().unwrap().poll() {
Ok(Async::Ready(())) => return Err(SendRequestError::Timeout),
Ok(Async::NotReady) => (),
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e).into()),
}
}
Ok(())
}
#[inline]
fn poll_write(&mut self) -> Poll<(), Error> {
if self.write_state == RunningState::Done || self.conn.is_none() {
return Ok(Async::Ready(()));
}
let mut done = false;
if self.drain.is_none() && self.write_state != RunningState::Paused {
'outter: loop {
let result = match mem::replace(&mut self.body, IoBody::Done) {
IoBody::Payload(mut body) => match body.poll()? {
Async::Ready(None) => {
self.writer.write_eof()?;
self.body_completed = true;
break;
}
Async::Ready(Some(chunk)) => {
self.body = IoBody::Payload(body);
self.writer.write(chunk.as_ref())?
}
Async::NotReady => {
done = true;
self.body = IoBody::Payload(body);
break;
}
},
IoBody::Actor(mut ctx) => {
if self.disconnected {
ctx.disconnected();
}
match ctx.poll()? {
Async::Ready(Some(vec)) => {
if vec.is_empty() {
self.body = IoBody::Actor(ctx);
break;
}
let mut res = None;
for frame in vec {
match frame {
Frame::Chunk(None) => {
self.body_completed = true;
self.writer.write_eof()?;
break 'outter;
}
Frame::Chunk(Some(chunk)) => {
res =
Some(self.writer.write(chunk.as_ref())?)
}
Frame::Drain(fut) => self.drain = Some(fut),
}
}
self.body = IoBody::Actor(ctx);
if self.drain.is_some() {
self.write_state.resume();
break;
}
res.unwrap()
}
Async::Ready(None) => {
done = true;
break;
}
Async::NotReady => {
done = true;
self.body = IoBody::Actor(ctx);
break;
}
}
}
IoBody::Done => {
self.body_completed = true;
done = true;
break;
}
};
match result {
WriterState::Pause => {
self.write_state.pause();
break;
}
WriterState::Done => self.write_state.resume(),
}
}
}
// flush io but only if we need to
match self
.writer
.poll_completed(self.conn.as_mut().unwrap(), false)
{
Ok(Async::Ready(_)) => {
if self.disconnected
|| (self.body_completed && self.writer.is_completed())
{
self.write_state = RunningState::Done;
} else {
self.write_state.resume();
}
// resolve drain futures
if let Some(tx) = self.drain.take() {
let _ = tx.send(());
}
// restart io processing
if !done || self.write_state == RunningState::Done {
self.poll_write()
} else {
Ok(Async::NotReady)
}
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(err) => Err(err.into()),
}
}
}
impl Drop for Pipeline {
fn drop(&mut self) {
if let Some(conn) = self.conn.take() {
debug!(
"Client http transaction is not completed, dropping connection: {:?} {:?}",
self.meth,
self.path,
);
conn.close()
}
}
}
/// Future that resolves to a complete request body.
impl Stream for Box<Pipeline> {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
Pipeline::poll(self)
}
}

View File

@ -1,782 +0,0 @@
use std::fmt::Write as FmtWrite;
use std::io::Write;
use std::time::Duration;
use std::{fmt, mem};
use actix::Addr;
use bytes::{BufMut, Bytes, BytesMut};
use cookie::{Cookie, CookieJar};
use futures::Stream;
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use serde::Serialize;
use serde_json;
use serde_urlencoded;
use url::Url;
use super::connector::{ClientConnector, Connection};
use super::pipeline::SendRequest;
use body::Body;
use error::Error;
use header::{ContentEncoding, Header, IntoHeaderValue};
use http::header::{self, HeaderName, HeaderValue};
use http::{uri, Error as HttpError, HeaderMap, HttpTryFrom, Method, Uri, Version};
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
/// An HTTP Client Request
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # extern crate tokio;
/// # use futures::Future;
/// # use std::process;
/// use actix_web::{actix, client};
///
/// fn main() {
/// actix::run(
/// || client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder
/// .header("User-Agent", "Actix-web")
/// .finish().unwrap()
/// .send() // <- Send http request
/// .map_err(|_| ())
/// .and_then(|response| { // <- server http response
/// println!("Response: {:?}", response);
/// # actix::System::current().stop();
/// Ok(())
/// }),
/// );
/// }
/// ```
pub struct ClientRequest {
uri: Uri,
method: Method,
version: Version,
headers: HeaderMap,
body: Body,
chunked: bool,
upgrade: bool,
timeout: Option<Duration>,
encoding: ContentEncoding,
response_decompress: bool,
buffer_capacity: usize,
conn: ConnectionType,
}
enum ConnectionType {
Default,
Connector(Addr<ClientConnector>),
Connection(Connection),
}
impl Default for ClientRequest {
fn default() -> ClientRequest {
ClientRequest {
uri: Uri::default(),
method: Method::default(),
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
body: Body::Empty,
chunked: false,
upgrade: false,
timeout: None,
encoding: ContentEncoding::Auto,
response_decompress: true,
buffer_capacity: 32_768,
conn: ConnectionType::Default,
}
}
}
impl ClientRequest {
/// Create request builder for `GET` request
pub fn get<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::GET).uri(uri);
builder
}
/// Create request builder for `HEAD` request
pub fn head<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::HEAD).uri(uri);
builder
}
/// Create request builder for `POST` request
pub fn post<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::POST).uri(uri);
builder
}
/// Create request builder for `PUT` request
pub fn put<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::PUT).uri(uri);
builder
}
/// Create request builder for `DELETE` request
pub fn delete<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::DELETE).uri(uri);
builder
}
}
impl ClientRequest {
/// Create client request builder
pub fn build() -> ClientRequestBuilder {
ClientRequestBuilder {
request: Some(ClientRequest::default()),
err: None,
cookies: None,
default_headers: true,
}
}
/// Create client request builder
pub fn build_from<T: Into<ClientRequestBuilder>>(source: T) -> ClientRequestBuilder {
source.into()
}
/// Get the request URI
#[inline]
pub fn uri(&self) -> &Uri {
&self.uri
}
/// Set client request URI
#[inline]
pub fn set_uri(&mut self, uri: Uri) {
self.uri = uri
}
/// Get the request method
#[inline]
pub fn method(&self) -> &Method {
&self.method
}
/// Set HTTP `Method` for the request
#[inline]
pub fn set_method(&mut self, method: Method) {
self.method = method
}
/// Get HTTP version for the request
#[inline]
pub fn version(&self) -> Version {
self.version
}
/// Set http `Version` for the request
#[inline]
pub fn set_version(&mut self, version: Version) {
self.version = version
}
/// Get the headers from the request
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
/// Get a mutable reference to the headers
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
/// is chunked encoding enabled
#[inline]
pub fn chunked(&self) -> bool {
self.chunked
}
/// is upgrade request
#[inline]
pub fn upgrade(&self) -> bool {
self.upgrade
}
/// Content encoding
#[inline]
pub fn content_encoding(&self) -> ContentEncoding {
self.encoding
}
/// Decompress response payload
#[inline]
pub fn response_decompress(&self) -> bool {
self.response_decompress
}
/// Requested write buffer capacity
pub fn write_buffer_capacity(&self) -> usize {
self.buffer_capacity
}
/// Get body of this response
#[inline]
pub fn body(&self) -> &Body {
&self.body
}
/// Set a body
pub fn set_body<B: Into<Body>>(&mut self, body: B) {
self.body = body.into();
}
/// Extract body, replace it with `Empty`
pub(crate) fn replace_body(&mut self, body: Body) -> Body {
mem::replace(&mut self.body, body)
}
/// Send request
///
/// This method returns a future that resolves to a ClientResponse
pub fn send(mut self) -> SendRequest {
let timeout = self.timeout.take();
let send = match mem::replace(&mut self.conn, ConnectionType::Default) {
ConnectionType::Default => SendRequest::new(self),
ConnectionType::Connector(conn) => SendRequest::with_connector(self, conn),
ConnectionType::Connection(conn) => SendRequest::with_connection(self, conn),
};
if let Some(timeout) = timeout {
send.timeout(timeout)
} else {
send
}
}
}
impl fmt::Debug for ClientRequest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
"\nClientRequest {:?} {}:{}",
self.version, self.method, self.uri
)?;
writeln!(f, " headers:")?;
for (key, val) in self.headers.iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
}
}
/// An HTTP Client request builder
///
/// This type can be used to construct an instance of `ClientRequest` through a
/// builder-like pattern.
pub struct ClientRequestBuilder {
request: Option<ClientRequest>,
err: Option<HttpError>,
cookies: Option<CookieJar>,
default_headers: bool,
}
impl ClientRequestBuilder {
/// Set HTTP URI of request.
#[inline]
pub fn uri<U: AsRef<str>>(&mut self, uri: U) -> &mut Self {
match Url::parse(uri.as_ref()) {
Ok(url) => self._uri(url.as_str()),
Err(_) => self._uri(uri.as_ref()),
}
}
fn _uri(&mut self, url: &str) -> &mut Self {
match Uri::try_from(url) {
Ok(uri) => {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.uri = uri;
}
}
Err(e) => self.err = Some(e.into()),
}
self
}
/// Set HTTP method of this request.
#[inline]
pub fn method(&mut self, method: Method) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.method = method;
}
self
}
/// Set HTTP method of this request.
#[inline]
pub fn get_method(&mut self) -> &Method {
let parts = self.request.as_ref().expect("cannot reuse request builder");
&parts.method
}
/// Set HTTP version of this request.
///
/// By default requests's HTTP version depends on network stream
#[inline]
pub fn version(&mut self, version: Version) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.version = version;
}
self
}
/// Set a header.
///
/// ```rust
/// # extern crate mime;
/// # extern crate actix_web;
/// # use actix_web::client::*;
/// #
/// use actix_web::{client, http};
///
/// fn main() {
/// let req = client::ClientRequest::build()
/// .set(http::header::Date::now())
/// .set(http::header::ContentType(mime::TEXT_HTML))
/// .finish()
/// .unwrap();
/// }
/// ```
#[doc(hidden)]
pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
match hdr.try_into() {
Ok(value) => {
parts.headers.insert(H::name(), value);
}
Err(e) => self.err = Some(e.into()),
}
}
self
}
/// Append a header.
///
/// Header gets appended to existing header.
/// To override header use `set_header()` method.
///
/// ```rust
/// # extern crate http;
/// # extern crate actix_web;
/// # use actix_web::client::*;
/// #
/// use http::header;
///
/// fn main() {
/// let req = ClientRequest::build()
/// .header("X-TEST", "value")
/// .header(header::CONTENT_TYPE, "application/json")
/// .finish()
/// .unwrap();
/// }
/// ```
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
if let Some(parts) = parts(&mut self.request, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
parts.headers.append(key, value);
}
Err(e) => self.err = Some(e.into()),
},
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Set a header.
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
if let Some(parts) = parts(&mut self.request, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
parts.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
},
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Set a header only if it is not yet set.
pub fn set_header_if_none<K, V>(&mut self, key: K, value: V) -> &mut Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
if let Some(parts) = parts(&mut self.request, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => if !parts.headers.contains_key(&key) {
match value.try_into() {
Ok(value) => {
parts.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
}
},
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Set content encoding.
///
/// By default `ContentEncoding::Identity` is used.
#[inline]
pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.encoding = enc;
}
self
}
/// Enables automatic chunked transfer encoding
#[inline]
pub fn chunked(&mut self) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.chunked = true;
}
self
}
/// Enable connection upgrade
#[inline]
pub fn upgrade(&mut self) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.upgrade = true;
}
self
}
/// Set request's content type
#[inline]
pub fn content_type<V>(&mut self, value: V) -> &mut Self
where
HeaderValue: HttpTryFrom<V>,
{
if let Some(parts) = parts(&mut self.request, &self.err) {
match HeaderValue::try_from(value) {
Ok(value) => {
parts.headers.insert(header::CONTENT_TYPE, value);
}
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Set content length
#[inline]
pub fn content_length(&mut self, len: u64) -> &mut Self {
let mut wrt = BytesMut::new().writer();
let _ = write!(wrt, "{}", len);
self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze())
}
/// Set a cookie
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{client, http};
///
/// fn main() {
/// let req = client::ClientRequest::build()
/// .cookie(
/// http::Cookie::build("name", "value")
/// .domain("www.rust-lang.org")
/// .path("/")
/// .secure(true)
/// .http_only(true)
/// .finish(),
/// )
/// .finish()
/// .unwrap();
/// }
/// ```
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
if self.cookies.is_none() {
let mut jar = CookieJar::new();
jar.add(cookie.into_owned());
self.cookies = Some(jar)
} else {
self.cookies.as_mut().unwrap().add(cookie.into_owned());
}
self
}
/// Do not add default request headers.
/// By default `Accept-Encoding` and `User-Agent` headers are set.
pub fn no_default_headers(&mut self) -> &mut Self {
self.default_headers = false;
self
}
/// Disable automatic decompress response body
pub fn disable_decompress(&mut self) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.response_decompress = false;
}
self
}
/// Set write buffer capacity
///
/// Default buffer capacity is 32kb
pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.buffer_capacity = cap;
}
self
}
/// Set request timeout
///
/// Request timeout is a total time before response should be received.
/// Default value is 5 seconds.
pub fn timeout(&mut self, timeout: Duration) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.timeout = Some(timeout);
}
self
}
/// Send request using custom connector
pub fn with_connector(&mut self, conn: Addr<ClientConnector>) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.conn = ConnectionType::Connector(conn);
}
self
}
/// Send request using existing `Connection`
pub fn with_connection(&mut self, conn: Connection) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.conn = ConnectionType::Connection(conn);
}
self
}
/// This method calls provided closure with builder reference if
/// value is `true`.
pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self
where
F: FnOnce(&mut ClientRequestBuilder),
{
if value {
f(self);
}
self
}
/// This method calls provided closure with builder reference if
/// value is `Some`.
pub fn if_some<T, F>(&mut self, value: Option<T>, f: F) -> &mut Self
where
F: FnOnce(T, &mut ClientRequestBuilder),
{
if let Some(val) = value {
f(val, self);
}
self
}
/// Set a body and generate `ClientRequest`.
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn body<B: Into<Body>>(&mut self, body: B) -> Result<ClientRequest, Error> {
if let Some(e) = self.err.take() {
return Err(e.into());
}
if self.default_headers {
// enable br only for https
let https = if let Some(parts) = parts(&mut self.request, &self.err) {
parts
.uri
.scheme_part()
.map(|s| s == &uri::Scheme::HTTPS)
.unwrap_or(true)
} else {
true
};
if https {
self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate");
} else {
self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate");
}
// set request host header
if let Some(parts) = parts(&mut self.request, &self.err) {
if let Some(host) = parts.uri.host() {
if !parts.headers.contains_key(header::HOST) {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match parts.uri.port() {
None | Some(80) | Some(443) => write!(wrt, "{}", host),
Some(port) => write!(wrt, "{}:{}", host, port),
};
match wrt.get_mut().take().freeze().try_into() {
Ok(value) => {
parts.headers.insert(header::HOST, value);
}
Err(e) => self.err = Some(e.into()),
}
}
}
}
// user agent
self.set_header_if_none(
header::USER_AGENT,
concat!("actix-web/", env!("CARGO_PKG_VERSION")),
);
}
let mut request = self.request.take().expect("cannot reuse request builder");
// set cookies
if let Some(ref mut jar) = self.cookies {
let mut cookie = String::new();
for c in jar.delta() {
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
let _ = write!(&mut cookie, "; {}={}", name, value);
}
request.headers.insert(
header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
}
request.body = body.into();
Ok(request)
}
/// Set a JSON body and generate `ClientRequest`
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn json<T: Serialize>(&mut self, value: T) -> Result<ClientRequest, Error> {
let body = serde_json::to_string(&value)?;
let contains = if let Some(parts) = parts(&mut self.request, &self.err) {
parts.headers.contains_key(header::CONTENT_TYPE)
} else {
true
};
if !contains {
self.header(header::CONTENT_TYPE, "application/json");
}
self.body(body)
}
/// Set a urlencoded body and generate `ClientRequest`
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn form<T: Serialize>(&mut self, value: T) -> Result<ClientRequest, Error> {
let body = serde_urlencoded::to_string(&value)?;
let contains = if let Some(parts) = parts(&mut self.request, &self.err) {
parts.headers.contains_key(header::CONTENT_TYPE)
} else {
true
};
if !contains {
self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded");
}
self.body(body)
}
/// Set a streaming body and generate `ClientRequest`.
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn streaming<S, E>(&mut self, stream: S) -> Result<ClientRequest, Error>
where
S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error>,
{
self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into()))))
}
/// Set an empty body and generate `ClientRequest`
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn finish(&mut self) -> Result<ClientRequest, Error> {
self.body(Body::Empty)
}
/// This method construct new `ClientRequestBuilder`
pub fn take(&mut self) -> ClientRequestBuilder {
ClientRequestBuilder {
request: self.request.take(),
err: self.err.take(),
cookies: self.cookies.take(),
default_headers: self.default_headers,
}
}
}
#[inline]
fn parts<'a>(
parts: &'a mut Option<ClientRequest>, err: &Option<HttpError>,
) -> Option<&'a mut ClientRequest> {
if err.is_some() {
return None;
}
parts.as_mut()
}
impl fmt::Debug for ClientRequestBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(ref parts) = self.request {
writeln!(
f,
"\nClientRequestBuilder {:?} {}:{}",
parts.version, parts.method, parts.uri
)?;
writeln!(f, " headers:")?;
for (key, val) in parts.headers.iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
} else {
write!(f, "ClientRequestBuilder(Consumed)")
}
}
}
/// Create `ClientRequestBuilder` from `HttpRequest`
///
/// It is useful for proxy requests. This implementation
/// copies all request headers and the method.
impl<'a, S: 'static> From<&'a HttpRequest<S>> for ClientRequestBuilder {
fn from(req: &'a HttpRequest<S>) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
for (key, value) in req.headers() {
builder.header(key.clone(), value.clone());
}
builder.method(req.method().clone());
builder
}
}

View File

@ -1,124 +0,0 @@
use std::cell::RefCell;
use std::{fmt, str};
use cookie::Cookie;
use http::header::{self, HeaderValue};
use http::{HeaderMap, StatusCode, Version};
use error::CookieParseError;
use httpmessage::HttpMessage;
use super::pipeline::Pipeline;
pub(crate) struct ClientMessage {
pub status: StatusCode,
pub version: Version,
pub headers: HeaderMap<HeaderValue>,
pub cookies: Option<Vec<Cookie<'static>>>,
}
impl Default for ClientMessage {
fn default() -> ClientMessage {
ClientMessage {
status: StatusCode::OK,
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
cookies: None,
}
}
}
/// An HTTP Client response
pub struct ClientResponse(ClientMessage, RefCell<Option<Box<Pipeline>>>);
impl HttpMessage for ClientResponse {
type Stream = Box<Pipeline>;
/// Get the headers from the response.
#[inline]
fn headers(&self) -> &HeaderMap {
&self.0.headers
}
#[inline]
fn payload(&self) -> Box<Pipeline> {
self.1
.borrow_mut()
.take()
.expect("Payload is already consumed.")
}
}
impl ClientResponse {
pub(crate) fn new(msg: ClientMessage) -> ClientResponse {
ClientResponse(msg, RefCell::new(None))
}
pub(crate) fn set_pipeline(&mut self, pl: Box<Pipeline>) {
*self.1.borrow_mut() = Some(pl);
}
/// Get the HTTP version of this response.
#[inline]
pub fn version(&self) -> Version {
self.0.version
}
/// Get the status from the server.
#[inline]
pub fn status(&self) -> StatusCode {
self.0.status
}
/// Load response cookies.
pub fn cookies(&self) -> Result<Vec<Cookie<'static>>, CookieParseError> {
let mut cookies = Vec::new();
for val in self.0.headers.get_all(header::SET_COOKIE).iter() {
let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?;
cookies.push(Cookie::parse_encoded(s)?.into_owned());
}
Ok(cookies)
}
/// Return request cookie.
pub fn cookie(&self, name: &str) -> Option<Cookie> {
if let Ok(cookies) = self.cookies() {
for cookie in cookies {
if cookie.name() == name {
return Some(cookie);
}
}
}
None
}
}
impl fmt::Debug for ClientResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status())?;
writeln!(f, " headers:")?;
for (key, val) in self.headers().iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_debug() {
let mut resp = ClientResponse::new(ClientMessage::default());
resp.0
.headers
.insert(header::COOKIE, HeaderValue::from_static("cookie1=value1"));
resp.0
.headers
.insert(header::COOKIE, HeaderValue::from_static("cookie2=value2"));
let dbg = format!("{:?}", resp);
assert!(dbg.contains("ClientResponse"));
}
}

View File

@ -1,412 +0,0 @@
#![cfg_attr(
feature = "cargo-clippy",
allow(clippy::redundant_field_names)
)]
use std::cell::RefCell;
use std::fmt::Write as FmtWrite;
use std::io::{self, Write};
#[cfg(feature = "brotli")]
use brotli2::write::BrotliEncoder;
use bytes::{BufMut, BytesMut};
#[cfg(feature = "flate2")]
use flate2::write::{GzEncoder, ZlibEncoder};
#[cfg(feature = "flate2")]
use flate2::Compression;
use futures::{Async, Poll};
use http::header::{
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use http::{HttpTryFrom, Version};
use time::{self, Duration};
use tokio_io::AsyncWrite;
use body::{Binary, Body};
use header::ContentEncoding;
use server::output::{ContentEncoder, Output, TransferEncoding};
use server::WriterState;
use client::ClientRequest;
const AVERAGE_HEADER_SIZE: usize = 30;
bitflags! {
struct Flags: u8 {
const STARTED = 0b0000_0001;
const UPGRADE = 0b0000_0010;
const KEEPALIVE = 0b0000_0100;
const DISCONNECTED = 0b0000_1000;
}
}
pub(crate) struct HttpClientWriter {
flags: Flags,
written: u64,
headers_size: u32,
buffer: Output,
buffer_capacity: usize,
}
impl HttpClientWriter {
pub fn new() -> HttpClientWriter {
HttpClientWriter {
flags: Flags::empty(),
written: 0,
headers_size: 0,
buffer_capacity: 0,
buffer: Output::Buffer(BytesMut::new()),
}
}
pub fn disconnected(&mut self) {
self.buffer.take();
}
pub fn is_completed(&self) -> bool {
self.buffer.is_empty()
}
// pub fn keepalive(&self) -> bool {
// self.flags.contains(Flags::KEEPALIVE) &&
// !self.flags.contains(Flags::UPGRADE) }
fn write_to_stream<T: AsyncWrite>(
&mut self, stream: &mut T,
) -> io::Result<WriterState> {
while !self.buffer.is_empty() {
match stream.write(self.buffer.as_ref().as_ref()) {
Ok(0) => {
self.disconnected();
return Ok(WriterState::Done);
}
Ok(n) => {
let _ = self.buffer.split_to(n);
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
if self.buffer.len() > self.buffer_capacity {
return Ok(WriterState::Pause);
} else {
return Ok(WriterState::Done);
}
}
Err(err) => return Err(err),
}
}
Ok(WriterState::Done)
}
}
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(())
}
}
impl HttpClientWriter {
pub fn start(&mut self, msg: &mut ClientRequest) -> io::Result<()> {
// prepare task
self.buffer = content_encoder(self.buffer.take(), msg);
self.flags.insert(Flags::STARTED);
if msg.upgrade() {
self.flags.insert(Flags::UPGRADE);
}
// render message
{
// output buffer
let buffer = self.buffer.as_mut();
// status line
writeln!(
Writer(buffer),
"{} {} {:?}\r",
msg.method(),
msg.uri()
.path_and_query()
.map(|u| u.as_str())
.unwrap_or("/"),
msg.version()
).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
// write headers
if let Body::Binary(ref bytes) = *msg.body() {
buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
} else {
buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE);
}
for (key, value) in msg.headers() {
let v = value.as_ref();
let k = key.as_str().as_bytes();
buffer.reserve(k.len() + v.len() + 4);
buffer.put_slice(k);
buffer.put_slice(b": ");
buffer.put_slice(v);
buffer.put_slice(b"\r\n");
}
// set date header
if !msg.headers().contains_key(DATE) {
buffer.extend_from_slice(b"date: ");
set_date(buffer);
buffer.extend_from_slice(b"\r\n\r\n");
} else {
buffer.extend_from_slice(b"\r\n");
}
}
self.headers_size = self.buffer.len() as u32;
if msg.body().is_binary() {
if let Body::Binary(bytes) = msg.replace_body(Body::Empty) {
self.written += bytes.len() as u64;
self.buffer.write(bytes.as_ref())?;
}
} else {
self.buffer_capacity = msg.write_buffer_capacity();
}
Ok(())
}
pub fn write(&mut self, payload: &[u8]) -> io::Result<WriterState> {
self.written += payload.len() as u64;
if !self.flags.contains(Flags::DISCONNECTED) {
self.buffer.write(payload)?;
}
if self.buffer.len() > self.buffer_capacity {
Ok(WriterState::Pause)
} else {
Ok(WriterState::Done)
}
}
pub fn write_eof(&mut self) -> io::Result<()> {
if self.buffer.write_eof()? {
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::Other,
"Last payload item, but eof is not reached",
))
}
}
#[inline]
pub fn poll_completed<T: AsyncWrite>(
&mut self, stream: &mut T, shutdown: bool,
) -> Poll<(), io::Error> {
match self.write_to_stream(stream) {
Ok(WriterState::Done) => {
if shutdown {
stream.shutdown()
} else {
Ok(Async::Ready(()))
}
}
Ok(WriterState::Pause) => Ok(Async::NotReady),
Err(err) => Err(err),
}
}
}
fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output {
let version = req.version();
let mut body = req.replace_body(Body::Empty);
let mut encoding = req.content_encoding();
let transfer = match body {
Body::Empty => {
req.headers_mut().remove(CONTENT_LENGTH);
return Output::Empty(buf);
}
Body::Binary(ref mut bytes) => {
#[cfg(any(feature = "flate2", feature = "brotli"))]
{
if encoding.is_compression() {
let mut tmp = BytesMut::new();
let mut transfer = TransferEncoding::eof(tmp);
let mut enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(
ZlibEncoder::new(transfer, Compression::default()),
),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
transfer,
Compression::default(),
)),
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
ContentEncoder::Br(BrotliEncoder::new(transfer, 5))
}
ContentEncoding::Auto | ContentEncoding::Identity => {
unreachable!()
}
};
// TODO return error!
let _ = enc.write(bytes.as_ref());
let _ = enc.write_eof();
*bytes = Binary::from(enc.buf_mut().take());
req.headers_mut().insert(
CONTENT_ENCODING,
HeaderValue::from_static(encoding.as_str()),
);
encoding = ContentEncoding::Identity;
}
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
req.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
TransferEncoding::eof(buf)
}
#[cfg(not(any(feature = "flate2", feature = "brotli")))]
{
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
req.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
TransferEncoding::eof(buf)
}
}
Body::Streaming(_) | Body::Actor(_) => {
if req.upgrade() {
if version == Version::HTTP_2 {
error!("Connection upgrade is forbidden for HTTP/2");
} else {
req.headers_mut()
.insert(CONNECTION, HeaderValue::from_static("upgrade"));
}
if encoding != ContentEncoding::Identity {
encoding = ContentEncoding::Identity;
req.headers_mut().remove(CONTENT_ENCODING);
}
TransferEncoding::eof(buf)
} else {
streaming_encoding(buf, version, req)
}
}
};
if encoding.is_compression() {
req.headers_mut().insert(
CONTENT_ENCODING,
HeaderValue::from_static(encoding.as_str()),
);
}
req.replace_body(body);
let enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => {
ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::default()))
}
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => {
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default()))
}
#[cfg(feature = "brotli")]
ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 5)),
ContentEncoding::Identity | ContentEncoding::Auto => return Output::TE(transfer),
};
Output::Encoder(enc)
}
fn streaming_encoding(
buf: BytesMut, version: Version, req: &mut ClientRequest,
) -> TransferEncoding {
if req.chunked() {
// Enable transfer encoding
req.headers_mut().remove(CONTENT_LENGTH);
if version == Version::HTTP_2 {
req.headers_mut().remove(TRANSFER_ENCODING);
TransferEncoding::eof(buf)
} else {
req.headers_mut()
.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
TransferEncoding::chunked(buf)
}
} else {
// if Content-Length is specified, then use it as length hint
let (len, chunked) = if let Some(len) = req.headers().get(CONTENT_LENGTH) {
// Content-Length
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
(Some(len), false)
} else {
error!("illegal Content-Length: {:?}", len);
(None, false)
}
} else {
error!("illegal Content-Length: {:?}", len);
(None, false)
}
} else {
(None, true)
};
if !chunked {
if let Some(len) = len {
TransferEncoding::length(len, buf)
} else {
TransferEncoding::eof(buf)
}
} else {
// Enable transfer encoding
match version {
Version::HTTP_11 => {
req.headers_mut()
.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
TransferEncoding::chunked(buf)
}
_ => {
req.headers_mut().remove(TRANSFER_ENCODING);
TransferEncoding::eof(buf)
}
}
}
}
}
// "Sun, 06 Nov 1994 08:49:37 GMT".len()
pub const DATE_VALUE_LENGTH: usize = 29;
fn set_date(dst: &mut BytesMut) {
CACHED.with(|cache| {
let mut cache = cache.borrow_mut();
let now = time::get_time();
if now > cache.next_update {
cache.update(now);
}
dst.extend_from_slice(cache.buffer());
})
}
struct CachedDate {
bytes: [u8; DATE_VALUE_LENGTH],
next_update: time::Timespec,
}
thread_local!(static CACHED: RefCell<CachedDate> = RefCell::new(CachedDate {
bytes: [0; DATE_VALUE_LENGTH],
next_update: time::Timespec::new(0, 0),
}));
impl CachedDate {
fn buffer(&self) -> &[u8] {
&self.bytes[..]
}
fn update(&mut self, now: time::Timespec) {
write!(&mut self.bytes[..], "{}", time::at_utc(now).rfc822()).unwrap();
self.next_update = now + Duration::seconds(1);
self.next_update.nsec = 0;
}
}

View File

@ -1,294 +0,0 @@
extern crate actix;
use futures::sync::oneshot;
use futures::sync::oneshot::Sender;
use futures::{Async, Future, Poll};
use smallvec::SmallVec;
use std::marker::PhantomData;
use self::actix::dev::{
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope,
};
use self::actix::fut::ActorFuture;
use self::actix::{
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
};
use body::{Binary, Body};
use error::{Error, ErrorInternalServerError};
use httprequest::HttpRequest;
pub trait ActorHttpContext: 'static {
fn disconnected(&mut self);
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error>;
}
#[derive(Debug)]
pub enum Frame {
Chunk(Option<Binary>),
Drain(oneshot::Sender<()>),
}
impl Frame {
pub fn len(&self) -> usize {
match *self {
Frame::Chunk(Some(ref bin)) => bin.len(),
_ => 0,
}
}
}
/// Execution context for http actors
pub struct HttpContext<A, S = ()>
where
A: Actor<Context = HttpContext<A, S>>,
{
inner: ContextParts<A>,
stream: Option<SmallVec<[Frame; 4]>>,
request: HttpRequest<S>,
disconnected: bool,
}
impl<A, S> ActorContext for HttpContext<A, S>
where
A: Actor<Context = Self>,
{
fn stop(&mut self) {
self.inner.stop();
}
fn terminate(&mut self) {
self.inner.terminate()
}
fn state(&self) -> ActorState {
self.inner.state()
}
}
impl<A, S> AsyncContext<A> for HttpContext<A, S>
where
A: Actor<Context = Self>,
{
#[inline]
fn spawn<F>(&mut self, fut: F) -> SpawnHandle
where
F: ActorFuture<Item = (), Error = (), Actor = A> + 'static,
{
self.inner.spawn(fut)
}
#[inline]
fn wait<F>(&mut self, fut: F)
where
F: ActorFuture<Item = (), Error = (), Actor = A> + 'static,
{
self.inner.wait(fut)
}
#[doc(hidden)]
#[inline]
fn waiting(&self) -> bool {
self.inner.waiting()
|| self.inner.state() == ActorState::Stopping
|| self.inner.state() == ActorState::Stopped
}
#[inline]
fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
self.inner.cancel_future(handle)
}
#[inline]
fn address(&self) -> Addr<A> {
self.inner.address()
}
}
impl<A, S: 'static> HttpContext<A, S>
where
A: Actor<Context = Self>,
{
#[inline]
/// Create a new HTTP Context from a request and an actor
pub fn create(req: HttpRequest<S>, actor: A) -> Body {
let mb = Mailbox::default();
let ctx = HttpContext {
inner: ContextParts::new(mb.sender_producer()),
stream: None,
request: req,
disconnected: false,
};
Body::Actor(Box::new(HttpContextFut::new(ctx, actor, mb)))
}
/// Create a new HTTP Context
pub fn with_factory<F>(req: HttpRequest<S>, f: F) -> Body
where
F: FnOnce(&mut Self) -> A + 'static,
{
let mb = Mailbox::default();
let mut ctx = HttpContext {
inner: ContextParts::new(mb.sender_producer()),
stream: None,
request: req,
disconnected: false,
};
let act = f(&mut ctx);
Body::Actor(Box::new(HttpContextFut::new(ctx, act, mb)))
}
}
impl<A, S> HttpContext<A, S>
where
A: Actor<Context = Self>,
{
/// Shared application state
#[inline]
pub fn state(&self) -> &S {
self.request.state()
}
/// Incoming request
#[inline]
pub fn request(&mut self) -> &mut HttpRequest<S> {
&mut self.request
}
/// Write payload
#[inline]
pub fn write<B: Into<Binary>>(&mut self, data: B) {
if !self.disconnected {
self.add_frame(Frame::Chunk(Some(data.into())));
} else {
warn!("Trying to write to disconnected response");
}
}
/// Indicate end of streaming payload. Also this method calls `Self::close`.
#[inline]
pub fn write_eof(&mut self) {
self.add_frame(Frame::Chunk(None));
}
/// Returns drain future
pub fn drain(&mut self) -> Drain<A> {
let (tx, rx) = oneshot::channel();
self.add_frame(Frame::Drain(tx));
Drain::new(rx)
}
/// Check if connection still open
#[inline]
pub fn connected(&self) -> bool {
!self.disconnected
}
#[inline]
fn add_frame(&mut self, frame: Frame) {
if self.stream.is_none() {
self.stream = Some(SmallVec::new());
}
if let Some(s) = self.stream.as_mut() {
s.push(frame)
}
}
/// Handle of the running future
///
/// SpawnHandle is the handle returned by `AsyncContext::spawn()` method.
pub fn handle(&self) -> SpawnHandle {
self.inner.curr_handle()
}
}
impl<A, S> AsyncContextParts<A> for HttpContext<A, S>
where
A: Actor<Context = Self>,
{
fn parts(&mut self) -> &mut ContextParts<A> {
&mut self.inner
}
}
struct HttpContextFut<A, S>
where
A: Actor<Context = HttpContext<A, S>>,
{
fut: ContextFut<A, HttpContext<A, S>>,
}
impl<A, S> HttpContextFut<A, S>
where
A: Actor<Context = HttpContext<A, S>>,
{
fn new(ctx: HttpContext<A, S>, act: A, mailbox: Mailbox<A>) -> Self {
let fut = ContextFut::new(ctx, act, mailbox);
HttpContextFut { fut }
}
}
impl<A, S> ActorHttpContext for HttpContextFut<A, S>
where
A: Actor<Context = HttpContext<A, S>>,
S: 'static,
{
#[inline]
fn disconnected(&mut self) {
self.fut.ctx().disconnected = true;
self.fut.ctx().stop();
}
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error> {
if self.fut.alive() {
match self.fut.poll() {
Ok(Async::NotReady) | Ok(Async::Ready(())) => (),
Err(_) => return Err(ErrorInternalServerError("error")),
}
}
// frames
if let Some(data) = self.fut.ctx().stream.take() {
Ok(Async::Ready(Some(data)))
} else if self.fut.alive() {
Ok(Async::NotReady)
} else {
Ok(Async::Ready(None))
}
}
}
impl<A, M, S> ToEnvelope<A, M> for HttpContext<A, S>
where
A: Actor<Context = HttpContext<A, S>> + Handler<M>,
M: Message + Send + 'static,
M::Result: Send,
{
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> Envelope<A> {
Envelope::new(msg, tx)
}
}
/// Consume a future
pub struct Drain<A> {
fut: oneshot::Receiver<()>,
_a: PhantomData<A>,
}
impl<A> Drain<A> {
/// Create a drain from a future
pub fn new(fut: oneshot::Receiver<()>) -> Self {
Drain {
fut,
_a: PhantomData,
}
}
}
impl<A: Actor> ActorFuture for Drain<A> {
type Item = ();
type Error = ();
type Actor = A;
#[inline]
fn poll(
&mut self, _: &mut A, _: &mut <Self::Actor as Actor>::Context,
) -> Poll<Self::Item, Self::Error> {
self.fut.poll().map_err(|_| ())
}
}

443
src/de.rs
View File

@ -1,443 +0,0 @@
use serde::de::{self, Deserializer, Error as DeError, Visitor};
use httprequest::HttpRequest;
use param::ParamsIter;
macro_rules! unsupported_type {
($trait_fn:ident, $name:expr) => {
fn $trait_fn<V>(self, _: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
Err(de::value::Error::custom(concat!("unsupported type: ", $name)))
}
};
}
macro_rules! parse_single_value {
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
if self.req.match_info().len() != 1 {
Err(de::value::Error::custom(
format!("wrong number of parameters: {} expected 1",
self.req.match_info().len()).as_str()))
} else {
let v = self.req.match_info()[0].parse().map_err(
|_| de::value::Error::custom(
format!("can not parse {:?} to a {}",
&self.req.match_info()[0], $tp)))?;
visitor.$visit_fn(v)
}
}
}
}
pub struct PathDeserializer<'de, S: 'de> {
req: &'de HttpRequest<S>,
}
impl<'de, S: 'de> PathDeserializer<'de, S> {
pub fn new(req: &'de HttpRequest<S>) -> Self {
PathDeserializer { req }
}
}
impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
type Error = de::value::Error;
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_map(ParamsDeserializer {
params: self.req.match_info().iter(),
current: None,
})
}
fn deserialize_struct<V>(
self, _: &'static str, _: &'static [&'static str], visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.deserialize_map(visitor)
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_unit()
}
fn deserialize_unit_struct<V>(
self, _: &'static str, visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.deserialize_unit(visitor)
}
fn deserialize_newtype_struct<V>(
self, _: &'static str, visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_newtype_struct(self)
}
fn deserialize_tuple<V>(
self, len: usize, visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.req.match_info().len() < len {
Err(de::value::Error::custom(
format!(
"wrong number of parameters: {} expected {}",
self.req.match_info().len(),
len
).as_str(),
))
} else {
visitor.visit_seq(ParamsSeq {
params: self.req.match_info().iter(),
})
}
}
fn deserialize_tuple_struct<V>(
self, _: &'static str, len: usize, visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.req.match_info().len() < len {
Err(de::value::Error::custom(
format!(
"wrong number of parameters: {} expected {}",
self.req.match_info().len(),
len
).as_str(),
))
} else {
visitor.visit_seq(ParamsSeq {
params: self.req.match_info().iter(),
})
}
}
fn deserialize_enum<V>(
self, _: &'static str, _: &'static [&'static str], _: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(de::value::Error::custom("unsupported type: enum"))
}
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.req.match_info().len() != 1 {
Err(de::value::Error::custom(
format!(
"wrong number of parameters: {} expected 1",
self.req.match_info().len()
).as_str(),
))
} else {
visitor.visit_str(&self.req.match_info()[0])
}
}
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_seq(ParamsSeq {
params: self.req.match_info().iter(),
})
}
unsupported_type!(deserialize_any, "'any'");
unsupported_type!(deserialize_bytes, "bytes");
unsupported_type!(deserialize_option, "Option<T>");
unsupported_type!(deserialize_identifier, "identifier");
unsupported_type!(deserialize_ignored_any, "ignored_any");
parse_single_value!(deserialize_bool, visit_bool, "bool");
parse_single_value!(deserialize_i8, visit_i8, "i8");
parse_single_value!(deserialize_i16, visit_i16, "i16");
parse_single_value!(deserialize_i32, visit_i32, "i32");
parse_single_value!(deserialize_i64, visit_i64, "i64");
parse_single_value!(deserialize_u8, visit_u8, "u8");
parse_single_value!(deserialize_u16, visit_u16, "u16");
parse_single_value!(deserialize_u32, visit_u32, "u32");
parse_single_value!(deserialize_u64, visit_u64, "u64");
parse_single_value!(deserialize_f32, visit_f32, "f32");
parse_single_value!(deserialize_f64, visit_f64, "f64");
parse_single_value!(deserialize_string, visit_string, "String");
parse_single_value!(deserialize_byte_buf, visit_string, "String");
parse_single_value!(deserialize_char, visit_char, "char");
}
struct ParamsDeserializer<'de> {
params: ParamsIter<'de>,
current: Option<(&'de str, &'de str)>,
}
impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
type Error = de::value::Error;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
where
K: de::DeserializeSeed<'de>,
{
self.current = self.params.next().map(|ref item| (item.0, item.1));
match self.current {
Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)),
None => Ok(None),
}
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
where
V: de::DeserializeSeed<'de>,
{
if let Some((_, value)) = self.current.take() {
seed.deserialize(Value { value })
} else {
Err(de::value::Error::custom("unexpected item"))
}
}
}
struct Key<'de> {
key: &'de str,
}
impl<'de> Deserializer<'de> for Key<'de> {
type Error = de::value::Error;
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_str(self.key)
}
fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(de::value::Error::custom("Unexpected"))
}
forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
byte_buf option unit unit_struct newtype_struct seq tuple
tuple_struct map struct enum ignored_any
}
}
macro_rules! parse_value {
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
let v = self.value.parse().map_err(
|_| de::value::Error::custom(
format!("can not parse {:?} to a {}", self.value, $tp)))?;
visitor.$visit_fn(v)
}
}
}
struct Value<'de> {
value: &'de str,
}
impl<'de> Deserializer<'de> for Value<'de> {
type Error = de::value::Error;
parse_value!(deserialize_bool, visit_bool, "bool");
parse_value!(deserialize_i8, visit_i8, "i8");
parse_value!(deserialize_i16, visit_i16, "i16");
parse_value!(deserialize_i32, visit_i32, "i16");
parse_value!(deserialize_i64, visit_i64, "i64");
parse_value!(deserialize_u8, visit_u8, "u8");
parse_value!(deserialize_u16, visit_u16, "u16");
parse_value!(deserialize_u32, visit_u32, "u32");
parse_value!(deserialize_u64, visit_u64, "u64");
parse_value!(deserialize_f32, visit_f32, "f32");
parse_value!(deserialize_f64, visit_f64, "f64");
parse_value!(deserialize_string, visit_string, "String");
parse_value!(deserialize_byte_buf, visit_string, "String");
parse_value!(deserialize_char, visit_char, "char");
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_unit()
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_unit()
}
fn deserialize_unit_struct<V>(
self, _: &'static str, visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_unit()
}
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_borrowed_bytes(self.value.as_bytes())
}
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_borrowed_str(self.value)
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_some(self)
}
fn deserialize_enum<V>(
self, _: &'static str, _: &'static [&'static str], visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_enum(ValueEnum { value: self.value })
}
fn deserialize_newtype_struct<V>(
self, _: &'static str, visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_newtype_struct(self)
}
fn deserialize_tuple<V>(self, _: usize, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(de::value::Error::custom("unsupported type: tuple"))
}
fn deserialize_struct<V>(
self, _: &'static str, _: &'static [&'static str], _: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(de::value::Error::custom("unsupported type: struct"))
}
fn deserialize_tuple_struct<V>(
self, _: &'static str, _: usize, _: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(de::value::Error::custom("unsupported type: tuple struct"))
}
unsupported_type!(deserialize_any, "any");
unsupported_type!(deserialize_seq, "seq");
unsupported_type!(deserialize_map, "map");
unsupported_type!(deserialize_identifier, "identifier");
}
struct ParamsSeq<'de> {
params: ParamsIter<'de>,
}
impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
type Error = de::value::Error;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
where
T: de::DeserializeSeed<'de>,
{
match self.params.next() {
Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)),
None => Ok(None),
}
}
}
struct ValueEnum<'de> {
value: &'de str,
}
impl<'de> de::EnumAccess<'de> for ValueEnum<'de> {
type Error = de::value::Error;
type Variant = UnitVariant;
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
where
V: de::DeserializeSeed<'de>,
{
Ok((seed.deserialize(Key { key: self.value })?, UnitVariant))
}
}
struct UnitVariant;
impl<'de> de::VariantAccess<'de> for UnitVariant {
type Error = de::value::Error;
fn unit_variant(self) -> Result<(), Self::Error> {
Ok(())
}
fn newtype_variant_seed<T>(self, _seed: T) -> Result<T::Value, Self::Error>
where
T: de::DeserializeSeed<'de>,
{
Err(de::value::Error::custom("not supported"))
}
fn tuple_variant<V>(self, _len: usize, _visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(de::value::Error::custom("not supported"))
}
fn struct_variant<V>(
self, _: &'static [&'static str], _: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(de::value::Error::custom("not supported"))
}
}

View File

@ -22,8 +22,7 @@ pub use url::ParseError as UrlParseError;
// re-exports
pub use cookie::ParseError as CookieParseError;
use handler::Responder;
use httprequest::HttpRequest;
// use httprequest::HttpRequest;
use httpresponse::{HttpResponse, HttpResponseParts};
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
@ -727,18 +726,6 @@ where
}
}
impl<T> Responder for InternalError<T>
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
type Item = HttpResponse;
type Error = Error;
fn respond_to<S>(self, _: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Err(self.into())
}
}
/// Helper function that creates wrapper of any error and generate *BAD
/// REQUEST* response.
#[allow(non_snake_case)]

File diff suppressed because it is too large Load Diff

1786
src/fs.rs

File diff suppressed because it is too large Load Diff

View File

@ -1,562 +0,0 @@
use std::marker::PhantomData;
use std::ops::Deref;
use futures::future::{err, ok, Future};
use futures::{Async, Poll};
use error::Error;
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use resource::DefaultResource;
/// Trait defines object that could be registered as route handler
#[allow(unused_variables)]
pub trait Handler<S>: 'static {
/// The type of value that handler will return.
type Result: Responder;
/// Handle request
fn handle(&self, req: &HttpRequest<S>) -> Self::Result;
}
/// Trait implemented by types that generate responses for clients.
///
/// Types that implement this trait can be used as the return type of a handler.
pub trait Responder {
/// The associated item which can be returned.
type Item: Into<AsyncResult<HttpResponse>>;
/// The associated error which can be returned.
type Error: Into<Error>;
/// Convert itself to `AsyncResult` or `Error`.
fn respond_to<S: 'static>(
self, req: &HttpRequest<S>,
) -> Result<Self::Item, Self::Error>;
}
/// Trait implemented by types that can be extracted from request.
///
/// Types that implement this trait can be used with `Route::with()` method.
pub trait FromRequest<S>: Sized {
/// Configuration for conversion process
type Config: Default;
/// Future that resolves to a Self
type Result: Into<AsyncResult<Self>>;
/// Convert request to a Self
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result;
/// Convert request to a Self
///
/// This method uses default extractor configuration
fn extract(req: &HttpRequest<S>) -> Self::Result {
Self::from_request(req, &Self::Config::default())
}
}
/// Combines two different responder types into a single type
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # use futures::future::Future;
/// use actix_web::{AsyncResponder, Either, Error, HttpRequest, HttpResponse};
/// use futures::future::result;
///
/// type RegisterResult =
/// Either<HttpResponse, Box<Future<Item = HttpResponse, Error = Error>>>;
///
/// fn index(req: HttpRequest) -> RegisterResult {
/// if is_a_variant() {
/// // <- choose variant A
/// Either::A(HttpResponse::BadRequest().body("Bad data"))
/// } else {
/// Either::B(
/// // <- variant B
/// result(Ok(HttpResponse::Ok()
/// .content_type("text/html")
/// .body("Hello!")))
/// .responder(),
/// )
/// }
/// }
/// # fn is_a_variant() -> bool { true }
/// # fn main() {}
/// ```
#[derive(Debug)]
pub enum Either<A, B> {
/// First branch of the type
A(A),
/// Second branch of the type
B(B),
}
impl<A, B> Responder for Either<A, B>
where
A: Responder,
B: Responder,
{
type Item = AsyncResult<HttpResponse>;
type Error = Error;
fn respond_to<S: 'static>(
self, req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
match self {
Either::A(a) => match a.respond_to(req) {
Ok(val) => Ok(val.into()),
Err(err) => Err(err.into()),
},
Either::B(b) => match b.respond_to(req) {
Ok(val) => Ok(val.into()),
Err(err) => Err(err.into()),
},
}
}
}
impl<A, B, I, E> Future for Either<A, B>
where
A: Future<Item = I, Error = E>,
B: Future<Item = I, Error = E>,
{
type Item = I;
type Error = E;
fn poll(&mut self) -> Poll<I, E> {
match *self {
Either::A(ref mut fut) => fut.poll(),
Either::B(ref mut fut) => fut.poll(),
}
}
}
impl<T> Responder for Option<T>
where
T: Responder,
{
type Item = AsyncResult<HttpResponse>;
type Error = Error;
fn respond_to<S: 'static>(
self, req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
match self {
Some(t) => match t.respond_to(req) {
Ok(val) => Ok(val.into()),
Err(err) => Err(err.into()),
},
None => Ok(req.build_response(StatusCode::NOT_FOUND).finish().into()),
}
}
}
/// Convenience trait that converts `Future` object to a `Boxed` future
///
/// For example loading json from request's body is async operation.
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{
/// App, AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse,
/// };
/// use futures::future::Future;
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
/// Ok(HttpResponse::Ok().into())
/// })
/// // Construct boxed future by using `AsyncResponder::responder()` method
/// .responder()
/// }
/// # fn main() {}
/// ```
pub trait AsyncResponder<I, E>: Sized {
/// Convert to a boxed future
fn responder(self) -> Box<Future<Item = I, Error = E>>;
}
impl<F, I, E> AsyncResponder<I, E> for F
where
F: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
{
fn responder(self) -> Box<Future<Item = I, Error = E>> {
Box::new(self)
}
}
/// Handler<S> for Fn()
impl<F, R, S> Handler<S> for F
where
F: Fn(&HttpRequest<S>) -> R + 'static,
R: Responder + 'static,
{
type Result = R;
fn handle(&self, req: &HttpRequest<S>) -> R {
(self)(req)
}
}
/// Represents async result
///
/// Result could be in tree different forms.
/// * Ok(T) - ready item
/// * Err(E) - error happen during reply process
/// * Future<T, E> - reply process completes in the future
pub struct AsyncResult<I, E = Error>(Option<AsyncResultItem<I, E>>);
impl<I, E> Future for AsyncResult<I, E> {
type Item = I;
type Error = E;
fn poll(&mut self) -> Poll<I, E> {
let res = self.0.take().expect("use after resolve");
match res {
AsyncResultItem::Ok(msg) => Ok(Async::Ready(msg)),
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Future(mut fut) => match fut.poll() {
Ok(Async::NotReady) => {
self.0 = Some(AsyncResultItem::Future(fut));
Ok(Async::NotReady)
}
Ok(Async::Ready(msg)) => Ok(Async::Ready(msg)),
Err(err) => Err(err),
},
}
}
}
pub(crate) enum AsyncResultItem<I, E> {
Ok(I),
Err(E),
Future(Box<Future<Item = I, Error = E>>),
}
impl<I, E> AsyncResult<I, E> {
/// Create async response
#[inline]
pub fn async(fut: Box<Future<Item = I, Error = E>>) -> AsyncResult<I, E> {
AsyncResult(Some(AsyncResultItem::Future(fut)))
}
/// Send response
#[inline]
pub fn ok<R: Into<I>>(ok: R) -> AsyncResult<I, E> {
AsyncResult(Some(AsyncResultItem::Ok(ok.into())))
}
/// Send error
#[inline]
pub fn err<R: Into<E>>(err: R) -> AsyncResult<I, E> {
AsyncResult(Some(AsyncResultItem::Err(err.into())))
}
#[inline]
pub(crate) fn into(self) -> AsyncResultItem<I, E> {
self.0.expect("use after resolve")
}
#[cfg(test)]
pub(crate) fn as_msg(&self) -> &I {
match self.0.as_ref().unwrap() {
&AsyncResultItem::Ok(ref resp) => resp,
_ => panic!(),
}
}
#[cfg(test)]
pub(crate) fn as_err(&self) -> Option<&E> {
match self.0.as_ref().unwrap() {
&AsyncResultItem::Err(ref err) => Some(err),
_ => None,
}
}
}
impl Responder for AsyncResult<HttpResponse> {
type Item = AsyncResult<HttpResponse>;
type Error = Error;
fn respond_to<S>(
self, _: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
Ok(self)
}
}
impl Responder for HttpResponse {
type Item = AsyncResult<HttpResponse>;
type Error = Error;
#[inline]
fn respond_to<S>(
self, _: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
Ok(AsyncResult(Some(AsyncResultItem::Ok(self))))
}
}
impl<T> From<T> for AsyncResult<T> {
#[inline]
fn from(resp: T) -> AsyncResult<T> {
AsyncResult(Some(AsyncResultItem::Ok(resp)))
}
}
impl<T: Responder, E: Into<Error>> Responder for Result<T, E> {
type Item = <T as Responder>::Item;
type Error = Error;
fn respond_to<S: 'static>(self, req: &HttpRequest<S>) -> Result<Self::Item, Error> {
match self {
Ok(val) => match val.respond_to(req) {
Ok(val) => Ok(val),
Err(err) => Err(err.into()),
},
Err(err) => Err(err.into()),
}
}
}
impl<T, E: Into<Error>> From<Result<AsyncResult<T>, E>> for AsyncResult<T> {
#[inline]
fn from(res: Result<AsyncResult<T>, E>) -> Self {
match res {
Ok(val) => val,
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
}
}
}
impl<T, E: Into<Error>> From<Result<T, E>> for AsyncResult<T> {
#[inline]
fn from(res: Result<T, E>) -> Self {
match res {
Ok(val) => AsyncResult(Some(AsyncResultItem::Ok(val))),
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
}
}
}
impl<T, E> From<Result<Box<Future<Item = T, Error = E>>, E>> for AsyncResult<T>
where
T: 'static,
E: Into<Error> + 'static,
{
#[inline]
fn from(res: Result<Box<Future<Item = T, Error = E>>, E>) -> Self {
match res {
Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(Box::new(
fut.map_err(|e| e.into()),
)))),
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
}
}
}
impl<T> From<Box<Future<Item = T, Error = Error>>> for AsyncResult<T> {
#[inline]
fn from(fut: Box<Future<Item = T, Error = Error>>) -> AsyncResult<T> {
AsyncResult(Some(AsyncResultItem::Future(fut)))
}
}
/// Convenience type alias
pub type FutureResponse<I, E = Error> = Box<Future<Item = I, Error = E>>;
impl<I, E> Responder for Box<Future<Item = I, Error = E>>
where
I: Responder + 'static,
E: Into<Error> + 'static,
{
type Item = AsyncResult<HttpResponse>;
type Error = Error;
#[inline]
fn respond_to<S: 'static>(
self, req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
let req = req.clone();
let fut = self
.map_err(|e| e.into())
.then(move |r| match r.respond_to(&req) {
Ok(reply) => match reply.into().into() {
AsyncResultItem::Ok(resp) => ok(resp),
_ => panic!("Nested async replies are not supported"),
},
Err(e) => err(e),
});
Ok(AsyncResult::async(Box::new(fut)))
}
}
pub(crate) trait RouteHandler<S>: 'static {
fn handle(&self, &HttpRequest<S>) -> AsyncResult<HttpResponse>;
fn has_default_resource(&self) -> bool {
false
}
fn default_resource(&mut self, _: DefaultResource<S>) {}
fn finish(&mut self) {}
}
/// Route handler wrapper for Handler
pub(crate) struct WrapHandler<S, H, R>
where
H: Handler<S, Result = R>,
R: Responder,
S: 'static,
{
h: H,
s: PhantomData<S>,
}
impl<S, H, R> WrapHandler<S, H, R>
where
H: Handler<S, Result = R>,
R: Responder,
S: 'static,
{
pub fn new(h: H) -> Self {
WrapHandler { h, s: PhantomData }
}
}
impl<S, H, R> RouteHandler<S> for WrapHandler<S, H, R>
where
H: Handler<S, Result = R>,
R: Responder + 'static,
S: 'static,
{
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
match self.h.handle(req).respond_to(req) {
Ok(reply) => reply.into(),
Err(err) => AsyncResult::err(err.into()),
}
}
}
/// Async route handler
pub(crate) struct AsyncHandler<S, H, F, R, E>
where
H: Fn(&HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
S: 'static,
{
h: Box<H>,
s: PhantomData<S>,
}
impl<S, H, F, R, E> AsyncHandler<S, H, F, R, E>
where
H: Fn(&HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
S: 'static,
{
pub fn new(h: H) -> Self {
AsyncHandler {
h: Box::new(h),
s: PhantomData,
}
}
}
impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
where
H: Fn(&HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
S: 'static,
{
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
let req = req.clone();
let fut = (self.h)(&req).map_err(|e| e.into()).then(move |r| {
match r.respond_to(&req) {
Ok(reply) => match reply.into().into() {
AsyncResultItem::Ok(resp) => Either::A(ok(resp)),
AsyncResultItem::Err(e) => Either::A(err(e)),
AsyncResultItem::Future(fut) => Either::B(fut),
},
Err(e) => Either::A(err(e)),
}
});
AsyncResult::async(Box::new(fut))
}
}
/// Access an application state
///
/// `S` - application state type
///
/// ## Example
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Path, State};
///
/// /// Application state
/// struct MyApp {
/// msg: &'static str,
/// }
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(state: State<MyApp>, path: Path<Info>) -> String {
/// format!("{} {}!", state.msg, path.username)
/// }
///
/// fn main() {
/// let app = App::with_state(MyApp { msg: "Welcome" }).resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// }
/// ```
pub struct State<S>(HttpRequest<S>);
impl<S> Deref for State<S> {
type Target = S;
fn deref(&self) -> &S {
self.0.state()
}
}
impl<S> FromRequest<S> for State<S> {
type Config = ();
type Result = State<S>;
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
State(req.clone())
}
}

View File

@ -1,571 +0,0 @@
//! Various helpers
use http::{header, StatusCode};
use regex::Regex;
use handler::Handler;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
/// Path normalization helper
///
/// By normalizing it means:
///
/// - Add a trailing slash to the path.
/// - Remove a trailing slash from the path.
/// - Double slashes are replaced by one.
///
/// The handler returns as soon as it finds a path that resolves
/// correctly. The order if all enable is 1) merge, 3) both merge and append
/// and 3) append. If the path resolves with
/// at least one of those conditions, it will redirect to the new path.
///
/// If *append* is *true* append slash when needed. If a resource is
/// defined with trailing slash and the request comes without it, it will
/// append it automatically.
///
/// If *merge* is *true*, merge multiple consecutive slashes in the path into
/// one.
///
/// This handler designed to be use as a handler for application's *default
/// resource*.
///
/// ```rust
/// # extern crate actix_web;
/// # #[macro_use] extern crate serde_derive;
/// # use actix_web::*;
/// use actix_web::http::NormalizePath;
///
/// # fn index(req: &HttpRequest) -> HttpResponse {
/// # HttpResponse::Ok().into()
/// # }
/// fn main() {
/// let app = App::new()
/// .resource("/test/", |r| r.f(index))
/// .default_resource(|r| r.h(NormalizePath::default()))
/// .finish();
/// }
/// ```
/// In this example `/test`, `/test///` will be redirected to `/test/` url.
pub struct NormalizePath {
append: bool,
merge: bool,
re_merge: Regex,
redirect: StatusCode,
not_found: StatusCode,
}
impl Default for NormalizePath {
/// Create default `NormalizePath` instance, *append* is set to *true*,
/// *merge* is set to *true* and *redirect* is set to
/// `StatusCode::MOVED_PERMANENTLY`
fn default() -> NormalizePath {
NormalizePath {
append: true,
merge: true,
re_merge: Regex::new("//+").unwrap(),
redirect: StatusCode::MOVED_PERMANENTLY,
not_found: StatusCode::NOT_FOUND,
}
}
}
impl NormalizePath {
/// Create new `NormalizePath` instance
pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath {
NormalizePath {
append,
merge,
redirect,
re_merge: Regex::new("//+").unwrap(),
not_found: StatusCode::NOT_FOUND,
}
}
}
impl<S> Handler<S> for NormalizePath {
type Result = HttpResponse;
fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
let query = req.query_string();
if self.merge {
// merge slashes
let p = self.re_merge.replace_all(req.path(), "/");
if p.len() != req.path().len() {
if req.resource().has_prefixed_resource(p.as_ref()) {
let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_ref())
.finish();
}
// merge slashes and append trailing slash
if self.append && !p.ends_with('/') {
let p = p.as_ref().to_owned() + "/";
if req.resource().has_prefixed_resource(&p) {
let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.finish();
}
}
// try to remove trailing slash
if p.ends_with('/') {
let p = p.as_ref().trim_right_matches('/');
if req.resource().has_prefixed_resource(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(
header::LOCATION,
(p.to_owned() + "?" + query).as_str(),
)
} else {
req.header(header::LOCATION, p)
}.finish();
}
}
} else if p.ends_with('/') {
// try to remove trailing slash
let p = p.as_ref().trim_right_matches('/');
if req.resource().has_prefixed_resource(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(
header::LOCATION,
(p.to_owned() + "?" + query).as_str(),
)
} else {
req.header(header::LOCATION, p)
}.finish();
}
}
}
// append trailing slash
if self.append && !req.path().ends_with('/') {
let p = req.path().to_owned() + "/";
if req.resource().has_prefixed_resource(&p) {
let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.finish();
}
}
HttpResponse::new(self.not_found)
}
}
#[cfg(test)]
mod tests {
use super::*;
use application::App;
use http::{header, Method};
use test::TestRequest;
fn index(_req: &HttpRequest) -> HttpResponse {
HttpResponse::new(StatusCode::OK)
}
#[test]
fn test_normalize_path_trailing_slashes() {
let app = App::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/resource1", "", StatusCode::OK),
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY),
("/resource2/", "", StatusCode::OK),
("/resource1?p1=1&p2=2", "", StatusCode::OK),
(
"/resource1/?p1=1&p2=2",
"/resource1?p1=1&p2=2",
StatusCode::MOVED_PERMANENTLY,
),
(
"/resource2?p1=1&p2=2",
"/resource2/?p1=1&p2=2",
StatusCode::MOVED_PERMANENTLY,
),
("/resource2/?p1=1&p2=2", "", StatusCode::OK),
];
for (path, target, code) in params {
let req = TestRequest::with_uri(path).request();
let resp = app.run(req);
let r = &resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
);
}
}
}
#[test]
fn test_prefixed_normalize_path_trailing_slashes() {
let app = App::new()
.prefix("/test")
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/test/resource1", "", StatusCode::OK),
(
"/test/resource1/",
"/test/resource1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/test/resource2",
"/test/resource2/",
StatusCode::MOVED_PERMANENTLY,
),
("/test/resource2/", "", StatusCode::OK),
("/test/resource1?p1=1&p2=2", "", StatusCode::OK),
(
"/test/resource1/?p1=1&p2=2",
"/test/resource1?p1=1&p2=2",
StatusCode::MOVED_PERMANENTLY,
),
(
"/test/resource2?p1=1&p2=2",
"/test/resource2/?p1=1&p2=2",
StatusCode::MOVED_PERMANENTLY,
),
("/test/resource2/?p1=1&p2=2", "", StatusCode::OK),
];
for (path, target, code) in params {
let req = TestRequest::with_uri(path).request();
let resp = app.run(req);
let r = &resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
);
}
}
}
#[test]
fn test_normalize_path_trailing_slashes_disabled() {
let app = App::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| {
r.h(NormalizePath::new(
false,
true,
StatusCode::MOVED_PERMANENTLY,
))
}).finish();
// trailing slashes
let params = vec![
("/resource1", StatusCode::OK),
("/resource1/", StatusCode::MOVED_PERMANENTLY),
("/resource2", StatusCode::NOT_FOUND),
("/resource2/", StatusCode::OK),
("/resource1?p1=1&p2=2", StatusCode::OK),
("/resource1/?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY),
("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND),
("/resource2/?p1=1&p2=2", StatusCode::OK),
];
for (path, code) in params {
let req = TestRequest::with_uri(path).request();
let resp = app.run(req);
let r = &resp.as_msg();
assert_eq!(r.status(), code);
}
}
#[test]
fn test_normalize_path_merge_slashes() {
let app = App::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/resource1/a/b", "", StatusCode::OK),
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY),
(
"//resource1//a//b",
"/resource1/a/b",
StatusCode::MOVED_PERMANENTLY,
),
(
"//resource1//a//b/",
"/resource1/a/b",
StatusCode::MOVED_PERMANENTLY,
),
(
"//resource1//a//b//",
"/resource1/a/b",
StatusCode::MOVED_PERMANENTLY,
),
(
"///resource1//a//b",
"/resource1/a/b",
StatusCode::MOVED_PERMANENTLY,
),
(
"/////resource1/a///b",
"/resource1/a/b",
StatusCode::MOVED_PERMANENTLY,
),
(
"/////resource1/a//b/",
"/resource1/a/b",
StatusCode::MOVED_PERMANENTLY,
),
("/resource1/a/b?p=1", "", StatusCode::OK),
(
"//resource1//a//b?p=1",
"/resource1/a/b?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"//resource1//a//b/?p=1",
"/resource1/a/b?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"///resource1//a//b?p=1",
"/resource1/a/b?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/////resource1/a///b?p=1",
"/resource1/a/b?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/////resource1/a//b/?p=1",
"/resource1/a/b?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/////resource1/a//b//?p=1",
"/resource1/a/b?p=1",
StatusCode::MOVED_PERMANENTLY,
),
];
for (path, target, code) in params {
let req = TestRequest::with_uri(path).request();
let resp = app.run(req);
let r = &resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
);
}
}
}
#[test]
fn test_normalize_path_merge_and_append_slashes() {
let app = App::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
.resource("/resource2/a/b/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/resource1/a/b", "", StatusCode::OK),
(
"/resource1/a/b/",
"/resource1/a/b",
StatusCode::MOVED_PERMANENTLY,
),
(
"//resource2//a//b",
"/resource2/a/b/",
StatusCode::MOVED_PERMANENTLY,
),
(
"//resource2//a//b/",
"/resource2/a/b/",
StatusCode::MOVED_PERMANENTLY,
),
(
"//resource2//a//b//",
"/resource2/a/b/",
StatusCode::MOVED_PERMANENTLY,
),
(
"///resource1//a//b",
"/resource1/a/b",
StatusCode::MOVED_PERMANENTLY,
),
(
"///resource1//a//b/",
"/resource1/a/b",
StatusCode::MOVED_PERMANENTLY,
),
(
"/////resource1/a///b",
"/resource1/a/b",
StatusCode::MOVED_PERMANENTLY,
),
(
"/////resource1/a///b/",
"/resource1/a/b",
StatusCode::MOVED_PERMANENTLY,
),
(
"/resource2/a/b",
"/resource2/a/b/",
StatusCode::MOVED_PERMANENTLY,
),
("/resource2/a/b/", "", StatusCode::OK),
(
"//resource2//a//b",
"/resource2/a/b/",
StatusCode::MOVED_PERMANENTLY,
),
(
"//resource2//a//b/",
"/resource2/a/b/",
StatusCode::MOVED_PERMANENTLY,
),
(
"///resource2//a//b",
"/resource2/a/b/",
StatusCode::MOVED_PERMANENTLY,
),
(
"///resource2//a//b/",
"/resource2/a/b/",
StatusCode::MOVED_PERMANENTLY,
),
(
"/////resource2/a///b",
"/resource2/a/b/",
StatusCode::MOVED_PERMANENTLY,
),
(
"/////resource2/a///b/",
"/resource2/a/b/",
StatusCode::MOVED_PERMANENTLY,
),
("/resource1/a/b?p=1", "", StatusCode::OK),
(
"/resource1/a/b/?p=1",
"/resource1/a/b?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"//resource2//a//b?p=1",
"/resource2/a/b/?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"//resource2//a//b/?p=1",
"/resource2/a/b/?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"///resource1//a//b?p=1",
"/resource1/a/b?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"///resource1//a//b/?p=1",
"/resource1/a/b?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/////resource1/a///b?p=1",
"/resource1/a/b?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/////resource1/a///b/?p=1",
"/resource1/a/b?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/////resource1/a///b//?p=1",
"/resource1/a/b?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/resource2/a/b?p=1",
"/resource2/a/b/?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"//resource2//a//b?p=1",
"/resource2/a/b/?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"//resource2//a//b/?p=1",
"/resource2/a/b/?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"///resource2//a//b?p=1",
"/resource2/a/b/?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"///resource2//a//b/?p=1",
"/resource2/a/b/?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/////resource2/a///b?p=1",
"/resource2/a/b/?p=1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/////resource2/a///b/?p=1",
"/resource2/a/b/?p=1",
StatusCode::MOVED_PERMANENTLY,
),
];
for (path, target, code) in params {
let req = TestRequest::with_uri(path).request();
let resp = app.run(req);
let r = &resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
);
}
}
}
}

View File

@ -15,7 +15,6 @@ use error::{
};
use header::Header;
use json::JsonBody;
use multipart::Multipart;
/// Trait that implements general purpose operations on http messages
pub trait HttpMessage: Sized {
@ -203,46 +202,6 @@ pub trait HttpMessage: Sized {
JsonBody::new(self)
}
/// Return stream to http payload processes as multipart.
///
/// Content-type: multipart/form-data;
///
/// ## Server example
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate env_logger;
/// # extern crate futures;
/// # use std::str;
/// # use actix_web::*;
/// # use actix_web::actix::fut::FinishStream;
/// # use futures::{Future, Stream};
/// # use futures::future::{ok, result, Either};
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
/// req.multipart().from_err() // <- get multipart stream for current request
/// .and_then(|item| match item { // <- iterate over multipart items
/// multipart::MultipartItem::Field(field) => {
/// // Field in turn is stream of *Bytes* object
/// Either::A(field.from_err()
/// .map(|c| println!("-- CHUNK: \n{:?}", str::from_utf8(&c)))
/// .finish())
/// },
/// multipart::MultipartItem::Nested(mp) => {
/// // Or item could be nested Multipart stream
/// Either::B(ok(()))
/// }
/// })
/// .finish() // <- Stream::finish() combinator from actix
/// .map(|_| HttpResponse::Ok().into())
/// .responder()
/// }
/// # fn main() {}
/// ```
fn multipart(&self) -> Multipart<Self::Stream> {
let boundary = Multipart::boundary(self.headers());
Multipart::new(boundary, self.payload())
}
/// Return stream of lines.
fn readlines(&self) -> Readlines<Self> {
Readlines::new(self)

View File

@ -13,12 +13,10 @@ use serde::Serialize;
use serde_json;
use body::Body;
use client::ClientResponse;
use error::Error;
use handler::Responder;
use header::{ContentEncoding, Header, IntoHeaderValue};
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
// use httprequest::HttpRequest;
/// max write buffer size 64k
pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536;
@ -720,16 +718,6 @@ impl From<HttpResponseBuilder> for HttpResponse {
}
}
impl Responder for HttpResponseBuilder {
type Item = HttpResponse;
type Error = Error;
#[inline]
fn respond_to<S>(mut self, _: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(self.finish())
}
}
impl From<&'static str> for HttpResponse {
fn from(val: &'static str) -> Self {
HttpResponse::Ok()
@ -738,18 +726,6 @@ impl From<&'static str> for HttpResponse {
}
}
impl Responder for &'static str {
type Item = HttpResponse;
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self))
}
}
impl From<&'static [u8]> for HttpResponse {
fn from(val: &'static [u8]) -> Self {
HttpResponse::Ok()
@ -758,18 +734,6 @@ impl From<&'static [u8]> for HttpResponse {
}
}
impl Responder for &'static [u8] {
type Item = HttpResponse;
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream")
.body(self))
}
}
impl From<String> for HttpResponse {
fn from(val: String) -> Self {
HttpResponse::Ok()
@ -778,18 +742,6 @@ impl From<String> for HttpResponse {
}
}
impl Responder for String {
type Item = HttpResponse;
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self))
}
}
impl<'a> From<&'a String> for HttpResponse {
fn from(val: &'a String) -> Self {
HttpResponse::build(StatusCode::OK)
@ -798,18 +750,6 @@ impl<'a> From<&'a String> for HttpResponse {
}
}
impl<'a> Responder for &'a String {
type Item = HttpResponse;
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self))
}
}
impl From<Bytes> for HttpResponse {
fn from(val: Bytes) -> Self {
HttpResponse::Ok()
@ -818,18 +758,6 @@ impl From<Bytes> for HttpResponse {
}
}
impl Responder for Bytes {
type Item = HttpResponse;
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream")
.body(self))
}
}
impl From<BytesMut> for HttpResponse {
fn from(val: BytesMut) -> Self {
HttpResponse::Ok()
@ -838,40 +766,6 @@ impl From<BytesMut> for HttpResponse {
}
}
impl Responder for BytesMut {
type Item = HttpResponse;
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream")
.body(self))
}
}
/// Create `HttpResponseBuilder` from `ClientResponse`
///
/// It is useful for proxy response. This implementation
/// copies all responses's headers and status.
impl<'a> From<&'a ClientResponse> for HttpResponseBuilder {
fn from(resp: &'a ClientResponse) -> HttpResponseBuilder {
let mut builder = HttpResponse::build(resp.status());
for (key, value) in resp.headers() {
builder.header(key.clone(), value.clone());
}
builder
}
}
impl<'a, S> From<&'a HttpRequest<S>> for HttpResponseBuilder {
fn from(req: &'a HttpRequest<S>) -> HttpResponseBuilder {
req.request()
.server_settings()
.get_response_builder(StatusCode::OK)
}
}
#[derive(Debug)]
struct InnerHttpResponse {
version: Option<Version>,
@ -921,7 +815,7 @@ impl InnerHttpResponse {
let body = match mem::replace(&mut self.body, Body::Empty) {
Body::Empty => None,
Body::Binary(mut bin) => Some(bin.take()),
Body::Streaming(_) | Body::Actor(_) => {
Body::Streaming(_) => {
error!("Streaming or Actor body is not support by error response");
None
}

View File

@ -11,10 +11,9 @@ use serde::Serialize;
use serde_json;
use error::{Error, JsonPayloadError};
use handler::{FromRequest, Responder};
use http::StatusCode;
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
// use httprequest::HttpRequest;
use httpresponse::HttpResponse;
/// Json helper
@ -116,102 +115,6 @@ where
}
}
impl<T: Serialize> Responder for Json<T> {
type Item = HttpResponse;
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
let body = serde_json::to_string(&self.0)?;
Ok(req
.build_response(StatusCode::OK)
.content_type("application/json")
.body(body))
}
}
impl<T, S> FromRequest<S> for Json<T>
where
T: DeserializeOwned + 'static,
S: 'static,
{
type Config = JsonConfig<S>;
type Result = Box<Future<Item = Self, Error = Error>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
let req2 = req.clone();
let err = Rc::clone(&cfg.ehandler);
Box::new(
JsonBody::new(req)
.limit(cfg.limit)
.map_err(move |e| (*err)(e, &req2))
.map(Json),
)
}
}
/// Json extractor configuration
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{error, http, App, HttpResponse, Json, Result};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// deserialize `Info` from request's body, max payload size is 4kb
/// fn index(info: Json<Info>) -> Result<String> {
/// Ok(format!("Welcome {}!", info.username))
/// }
///
/// fn main() {
/// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::POST)
/// .with_config(index, |cfg| {
/// cfg.0.limit(4096) // <- change json extractor configuration
/// .error_handler(|err, req| { // <- create custom error response
/// error::InternalError::from_response(
/// err, HttpResponse::Conflict().finish()).into()
/// });
/// })
/// });
/// }
/// ```
pub struct JsonConfig<S> {
limit: usize,
ehandler: Rc<Fn(JsonPayloadError, &HttpRequest<S>) -> Error>,
}
impl<S> JsonConfig<S> {
/// Change max size of payload. By default max size is 256Kb
pub fn limit(&mut self, limit: usize) -> &mut Self {
self.limit = limit;
self
}
/// Set custom error handler
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
where
F: Fn(JsonPayloadError, &HttpRequest<S>) -> Error + 'static,
{
self.ehandler = Rc::new(f);
self
}
}
impl<S> Default for JsonConfig<S> {
fn default() -> Self {
JsonConfig {
limit: 262_144,
ehandler: Rc::new(|e, _| e.into()),
}
}
}
/// Request payload json parser that resolves to a deserialized `T` value.
///
/// Returns error:

View File

@ -77,13 +77,11 @@
//! * `flate2-rust` - experimental rust based implementation for
//! `gzip`, `deflate` compression.
//!
#![cfg_attr(actix_nightly, feature(
specialization, // for impl ErrorResponse for std::error::Error
extern_prelude,
tool_lints,
))]
#![cfg_attr(actix_nightly, feature(tool_lints))]
#![warn(missing_docs)]
#![allow(unused_imports, unused_variables, dead_code)]
extern crate actix;
#[macro_use]
extern crate log;
extern crate base64;
@ -140,109 +138,36 @@ extern crate serde_json;
extern crate smallvec;
extern crate actix_net;
#[macro_use]
extern crate actix as actix_inner;
#[cfg(test)]
#[macro_use]
extern crate serde_derive;
#[cfg(feature = "tls")]
extern crate native_tls;
#[cfg(feature = "tls")]
extern crate tokio_tls;
#[cfg(feature = "openssl")]
extern crate openssl;
#[cfg(feature = "openssl")]
extern crate tokio_openssl;
#[cfg(feature = "rust-tls")]
extern crate rustls;
#[cfg(feature = "rust-tls")]
extern crate tokio_rustls;
#[cfg(feature = "rust-tls")]
extern crate webpki;
#[cfg(feature = "rust-tls")]
extern crate webpki_roots;
mod application;
mod body;
mod context;
mod de;
mod extensions;
mod extractor;
mod handler;
mod header;
mod helpers;
mod httpcodes;
mod httpmessage;
mod httprequest;
//mod httprequest;
mod httpresponse;
mod info;
mod json;
mod param;
mod payload;
mod pipeline;
mod resource;
mod route;
mod router;
mod scope;
mod uri;
mod with;
pub mod client;
pub mod error;
pub mod fs;
pub mod middleware;
pub mod multipart;
pub mod pred;
pub mod server;
pub mod test;
pub mod ws;
pub use application::App;
//pub mod test;
//pub mod ws;
pub use body::{Binary, Body};
pub use context::HttpContext;
pub use error::{Error, ResponseError, Result};
pub use extensions::Extensions;
pub use extractor::{Form, Path, Query};
pub use handler::{
AsyncResponder, Either, FromRequest, FutureResponse, Responder, State,
};
pub use httpmessage::HttpMessage;
pub use httprequest::HttpRequest;
//pub use httprequest::HttpRequest;
pub use httpresponse::HttpResponse;
pub use json::Json;
pub use scope::Scope;
pub use server::Request;
pub mod actix {
//! Re-exports [actix's](https://docs.rs/actix/) prelude
extern crate actix;
pub use self::actix::actors::resolver;
pub use self::actix::actors::signal;
pub use self::actix::fut;
pub use self::actix::msgs;
pub use self::actix::prelude::*;
pub use self::actix::{run, spawn};
}
#[cfg(feature = "openssl")]
pub(crate) const HAS_OPENSSL: bool = true;
#[cfg(not(feature = "openssl"))]
pub(crate) const HAS_OPENSSL: bool = false;
#[cfg(feature = "tls")]
pub(crate) const HAS_TLS: bool = true;
#[cfg(not(feature = "tls"))]
pub(crate) const HAS_TLS: bool = false;
#[cfg(feature = "rust-tls")]
pub(crate) const HAS_RUSTLS: bool = true;
#[cfg(not(feature = "rust-tls"))]
pub(crate) const HAS_RUSTLS: bool = false;
pub mod dev {
//! The `actix-web` prelude for library developers
//!
@ -255,19 +180,11 @@ pub mod dev {
//! ```
pub use body::BodyStream;
pub use context::Drain;
pub use extractor::{FormConfig, PayloadConfig};
pub use handler::{AsyncResult, Handler};
pub use httpmessage::{MessageBody, Readlines, UrlEncoded};
pub use httpresponse::HttpResponseBuilder;
pub use info::ConnectionInfo;
pub use json::{JsonBody, JsonConfig};
pub use param::{FromParam, Params};
pub use json::JsonBody;
pub use payload::{Payload, PayloadBuffer};
pub use pipeline::Pipeline;
pub use resource::Resource;
pub use route::Route;
pub use router::{ResourceDef, ResourceInfo, ResourceType, Router};
}
pub mod http {
@ -281,8 +198,6 @@ pub mod http {
pub use cookie::{Cookie, CookieBuilder};
pub use helpers::NormalizePath;
/// Various http headers
pub mod header {
pub use header::*;

File diff suppressed because it is too large Load Diff

View File

@ -1,275 +0,0 @@
//! A filter for cross-site request forgery (CSRF).
//!
//! This middleware is stateless and [based on request
//! headers](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers).
//!
//! By default requests are allowed only if one of these is true:
//!
//! * The request method is safe (`GET`, `HEAD`, `OPTIONS`). It is the
//! applications responsibility to ensure these methods cannot be used to
//! execute unwanted actions. Note that upgrade requests for websockets are
//! also considered safe.
//! * The `Origin` header (added automatically by the browser) matches one
//! of the allowed origins.
//! * There is no `Origin` header but the `Referer` header matches one of
//! the allowed origins.
//!
//! Use [`CsrfFilter::allow_xhr()`](struct.CsrfFilter.html#method.allow_xhr)
//! if you want to allow requests with unprotected methods via
//! [CORS](../cors/struct.Cors.html).
//!
//! # Example
//!
//! ```
//! # extern crate actix_web;
//! use actix_web::middleware::csrf;
//! use actix_web::{http, App, HttpRequest, HttpResponse};
//!
//! fn handle_post(_: &HttpRequest) -> &'static str {
//! "This action should only be triggered with requests from the same site"
//! }
//!
//! fn main() {
//! let app = App::new()
//! .middleware(
//! csrf::CsrfFilter::new().allowed_origin("https://www.example.com"),
//! )
//! .resource("/", |r| {
//! r.method(http::Method::GET).f(|_| HttpResponse::Ok());
//! r.method(http::Method::POST).f(handle_post);
//! })
//! .finish();
//! }
//! ```
//!
//! In this example the entire application is protected from CSRF.
use std::borrow::Cow;
use std::collections::HashSet;
use bytes::Bytes;
use error::{ResponseError, Result};
use http::{header, HeaderMap, HttpTryFrom, Uri};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Started};
use server::Request;
/// Potential cross-site request forgery detected.
#[derive(Debug, Fail)]
pub enum CsrfError {
/// The HTTP request header `Origin` was required but not provided.
#[fail(display = "Origin header required")]
MissingOrigin,
/// The HTTP request header `Origin` could not be parsed correctly.
#[fail(display = "Could not parse Origin header")]
BadOrigin,
/// The cross-site request was denied.
#[fail(display = "Cross-site request denied")]
CsrDenied,
}
impl ResponseError for CsrfError {
fn error_response(&self) -> HttpResponse {
HttpResponse::Forbidden().body(self.to_string())
}
}
fn uri_origin(uri: &Uri) -> Option<String> {
match (uri.scheme_part(), uri.host(), uri.port()) {
(Some(scheme), Some(host), Some(port)) => {
Some(format!("{}://{}:{}", scheme, host, port))
}
(Some(scheme), Some(host), None) => Some(format!("{}://{}", scheme, host)),
_ => None,
}
}
fn origin(headers: &HeaderMap) -> Option<Result<Cow<str>, CsrfError>> {
headers
.get(header::ORIGIN)
.map(|origin| {
origin
.to_str()
.map_err(|_| CsrfError::BadOrigin)
.map(|o| o.into())
}).or_else(|| {
headers.get(header::REFERER).map(|referer| {
Uri::try_from(Bytes::from(referer.as_bytes()))
.ok()
.as_ref()
.and_then(uri_origin)
.ok_or(CsrfError::BadOrigin)
.map(|o| o.into())
})
})
}
/// A middleware that filters cross-site requests.
///
/// To construct a CSRF filter:
///
/// 1. Call [`CsrfFilter::build`](struct.CsrfFilter.html#method.build) to
/// start building.
/// 2. [Add](struct.CsrfFilterBuilder.html#method.allowed_origin) allowed
/// origins.
/// 3. Call [finish](struct.CsrfFilterBuilder.html#method.finish) to retrieve
/// the constructed filter.
///
/// # Example
///
/// ```
/// use actix_web::middleware::csrf;
/// use actix_web::App;
///
/// # fn main() {
/// let app = App::new()
/// .middleware(csrf::CsrfFilter::new().allowed_origin("https://www.example.com"));
/// # }
/// ```
#[derive(Default)]
pub struct CsrfFilter {
origins: HashSet<String>,
allow_xhr: bool,
allow_missing_origin: bool,
allow_upgrade: bool,
}
impl CsrfFilter {
/// Start building a `CsrfFilter`.
pub fn new() -> CsrfFilter {
CsrfFilter {
origins: HashSet::new(),
allow_xhr: false,
allow_missing_origin: false,
allow_upgrade: false,
}
}
/// Add an origin that is allowed to make requests. Will be verified
/// against the `Origin` request header.
pub fn allowed_origin<T: Into<String>>(mut self, origin: T) -> CsrfFilter {
self.origins.insert(origin.into());
self
}
/// Allow all requests with an `X-Requested-With` header.
///
/// A cross-site attacker should not be able to send requests with custom
/// headers unless a CORS policy whitelists them. Therefore it should be
/// safe to allow requests with an `X-Requested-With` header (added
/// automatically by many JavaScript libraries).
///
/// This is disabled by default, because in Safari it is possible to
/// circumvent this using redirects and Flash.
///
/// Use this method to enable more lax filtering.
pub fn allow_xhr(mut self) -> CsrfFilter {
self.allow_xhr = true;
self
}
/// Allow requests if the expected `Origin` header is missing (and
/// there is no `Referer` to fall back on).
///
/// The filter is conservative by default, but it should be safe to allow
/// missing `Origin` headers because a cross-site attacker cannot prevent
/// the browser from sending `Origin` on unprotected requests.
pub fn allow_missing_origin(mut self) -> CsrfFilter {
self.allow_missing_origin = true;
self
}
/// Allow cross-site upgrade requests (for example to open a WebSocket).
pub fn allow_upgrade(mut self) -> CsrfFilter {
self.allow_upgrade = true;
self
}
fn validate(&self, req: &Request) -> Result<(), CsrfError> {
let is_upgrade = req.headers().contains_key(header::UPGRADE);
let is_safe = req.method().is_safe() && (self.allow_upgrade || !is_upgrade);
if is_safe || (self.allow_xhr && req.headers().contains_key("x-requested-with"))
{
Ok(())
} else if let Some(header) = origin(req.headers()) {
match header {
Ok(ref origin) if self.origins.contains(origin.as_ref()) => Ok(()),
Ok(_) => Err(CsrfError::CsrDenied),
Err(err) => Err(err),
}
} else if self.allow_missing_origin {
Ok(())
} else {
Err(CsrfError::MissingOrigin)
}
}
}
impl<S> Middleware<S> for CsrfFilter {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
self.validate(req)?;
Ok(Started::Done)
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::Method;
use test::TestRequest;
#[test]
fn test_safe() {
let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
let req = TestRequest::with_header("Origin", "https://www.w3.org")
.method(Method::HEAD)
.finish();
assert!(csrf.start(&req).is_ok());
}
#[test]
fn test_csrf() {
let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
let req = TestRequest::with_header("Origin", "https://www.w3.org")
.method(Method::POST)
.finish();
assert!(csrf.start(&req).is_err());
}
#[test]
fn test_referer() {
let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
let req = TestRequest::with_header(
"Referer",
"https://www.example.com/some/path?query=param",
).method(Method::POST)
.finish();
assert!(csrf.start(&req).is_ok());
}
#[test]
fn test_upgrade() {
let strict_csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
let lax_csrf = CsrfFilter::new()
.allowed_origin("https://www.example.com")
.allow_upgrade();
let req = TestRequest::with_header("Origin", "https://cswsh.com")
.header("Connection", "Upgrade")
.header("Upgrade", "websocket")
.method(Method::GET)
.finish();
assert!(strict_csrf.start(&req).is_err());
assert!(lax_csrf.start(&req).is_ok());
}
}

View File

@ -1,120 +0,0 @@
//! Default response headers
use http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
use http::{HeaderMap, HttpTryFrom};
use error::Result;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response};
/// `Middleware` for setting default response headers.
///
/// This middleware does not set header if response headers already contains it.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, middleware, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
/// .resource("/test", |r| {
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// r.method(http::Method::HEAD)
/// .f(|_| HttpResponse::MethodNotAllowed());
/// })
/// .finish();
/// }
/// ```
pub struct DefaultHeaders {
ct: bool,
headers: HeaderMap,
}
impl Default for DefaultHeaders {
fn default() -> Self {
DefaultHeaders {
ct: false,
headers: HeaderMap::new(),
}
}
}
impl DefaultHeaders {
/// Construct `DefaultHeaders` middleware.
pub fn new() -> DefaultHeaders {
DefaultHeaders::default()
}
/// Set a header.
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(clippy::match_wild_err_arm))]
pub fn header<K, V>(mut self, key: K, value: V) -> Self
where
HeaderName: HttpTryFrom<K>,
HeaderValue: HttpTryFrom<V>,
{
match HeaderName::try_from(key) {
Ok(key) => match HeaderValue::try_from(value) {
Ok(value) => {
self.headers.append(key, value);
}
Err(_) => panic!("Can not create header value"),
},
Err(_) => panic!("Can not create header name"),
}
self
}
/// Set *CONTENT-TYPE* header if response does not contain this header.
pub fn content_type(mut self) -> Self {
self.ct = true;
self
}
}
impl<S> Middleware<S> for DefaultHeaders {
fn response(&self, _: &HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> {
for (key, value) in self.headers.iter() {
if !resp.headers().contains_key(key) {
resp.headers_mut().insert(key, value.clone());
}
}
// default content-type
if self.ct && !resp.headers().contains_key(CONTENT_TYPE) {
resp.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/octet-stream"),
);
}
Ok(Response::Done(resp))
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::header::CONTENT_TYPE;
use test::TestRequest;
#[test]
fn test_default_headers() {
let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001");
let req = TestRequest::default().finish();
let resp = HttpResponse::Ok().finish();
let resp = match mw.response(&req, resp) {
Ok(Response::Done(resp)) => resp,
_ => panic!(),
};
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish();
let resp = match mw.response(&req, resp) {
Ok(Response::Done(resp)) => resp,
_ => panic!(),
};
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002");
}
}

View File

@ -1,141 +0,0 @@
use std::collections::HashMap;
use error::Result;
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response};
type ErrorHandler<S> = Fn(&HttpRequest<S>, HttpResponse) -> Result<Response>;
/// `Middleware` for allowing custom handlers for responses.
///
/// You can use `ErrorHandlers::handler()` method to register a custom error
/// handler for specific status code. You can modify existing response or
/// create completely new one.
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::{ErrorHandlers, Response};
/// use actix_web::{http, App, HttpRequest, HttpResponse, Result};
///
/// fn render_500<S>(_: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
/// let mut builder = resp.into_builder();
/// builder.header(http::header::CONTENT_TYPE, "application/json");
/// Ok(Response::Done(builder.into()))
/// }
///
/// fn main() {
/// let app = App::new()
/// .middleware(
/// ErrorHandlers::new()
/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500),
/// )
/// .resource("/test", |r| {
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// r.method(http::Method::HEAD)
/// .f(|_| HttpResponse::MethodNotAllowed());
/// })
/// .finish();
/// }
/// ```
pub struct ErrorHandlers<S> {
handlers: HashMap<StatusCode, Box<ErrorHandler<S>>>,
}
impl<S> Default for ErrorHandlers<S> {
fn default() -> Self {
ErrorHandlers {
handlers: HashMap::new(),
}
}
}
impl<S> ErrorHandlers<S> {
/// Construct new `ErrorHandlers` instance
pub fn new() -> Self {
ErrorHandlers::default()
}
/// Register error handler for specified status code
pub fn handler<F>(mut self, status: StatusCode, handler: F) -> Self
where
F: Fn(&HttpRequest<S>, HttpResponse) -> Result<Response> + 'static,
{
self.handlers.insert(status, Box::new(handler));
self
}
}
impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
if let Some(handler) = self.handlers.get(&resp.status()) {
handler(req, resp)
} else {
Ok(Response::Done(resp))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use error::{Error, ErrorInternalServerError};
use http::header::CONTENT_TYPE;
use http::StatusCode;
use httpmessage::HttpMessage;
use middleware::Started;
use test::{self, TestRequest};
fn render_500<S>(_: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
let mut builder = resp.into_builder();
builder.header(CONTENT_TYPE, "0001");
Ok(Response::Done(builder.into()))
}
#[test]
fn test_handler() {
let mw =
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
let mut req = TestRequest::default().finish();
let resp = HttpResponse::InternalServerError().finish();
let resp = match mw.response(&mut req, resp) {
Ok(Response::Done(resp)) => resp,
_ => panic!(),
};
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let resp = HttpResponse::Ok().finish();
let resp = match mw.response(&mut req, resp) {
Ok(Response::Done(resp)) => resp,
_ => panic!(),
};
assert!(!resp.headers().contains_key(CONTENT_TYPE));
}
struct MiddlewareOne;
impl<S> Middleware<S> for MiddlewareOne {
fn start(&self, _: &HttpRequest<S>) -> Result<Started, Error> {
Err(ErrorInternalServerError("middleware error"))
}
}
#[test]
fn test_middleware_start_error() {
let mut srv = test::TestServer::new(move |app| {
app.middleware(
ErrorHandlers::new()
.handler(StatusCode::INTERNAL_SERVER_ERROR, render_500),
).middleware(MiddlewareOne)
.handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.headers().get(CONTENT_TYPE).unwrap(), "0001");
}
}

View File

@ -1,387 +0,0 @@
//! Request identity service for Actix applications.
//!
//! [**IdentityService**](struct.IdentityService.html) middleware can be
//! used with different policies types to store identity information.
//!
//! By default, only cookie identity policy is implemented. Other backend
//! implementations can be added separately.
//!
//! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html)
//! uses cookies as identity storage.
//!
//! To access current request identity
//! [**RequestIdentity**](trait.RequestIdentity.html) should be used.
//! *HttpRequest* implements *RequestIdentity* trait.
//!
//! ```rust
//! use actix_web::middleware::identity::RequestIdentity;
//! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
//! use actix_web::*;
//!
//! fn index(req: HttpRequest) -> Result<String> {
//! // access request identity
//! if let Some(id) = req.identity() {
//! Ok(format!("Welcome! {}", id))
//! } else {
//! Ok("Welcome Anonymous!".to_owned())
//! }
//! }
//!
//! fn login(mut req: HttpRequest) -> HttpResponse {
//! req.remember("User1".to_owned()); // <- remember identity
//! HttpResponse::Ok().finish()
//! }
//!
//! fn logout(mut req: HttpRequest) -> HttpResponse {
//! req.forget(); // <- remove identity
//! HttpResponse::Ok().finish()
//! }
//!
//! fn main() {
//! let app = App::new().middleware(IdentityService::new(
//! // <- create identity middleware
//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
//! .name("auth-cookie")
//! .secure(false),
//! ));
//! }
//! ```
use std::rc::Rc;
use cookie::{Cookie, CookieJar, Key};
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
use futures::Future;
use time::Duration;
use error::{Error, Result};
use http::header::{self, HeaderValue};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response, Started};
/// The helper trait to obtain your identity from a request.
///
/// ```rust
/// use actix_web::middleware::identity::RequestIdentity;
/// use actix_web::*;
///
/// fn index(req: HttpRequest) -> Result<String> {
/// // access request identity
/// if let Some(id) = req.identity() {
/// Ok(format!("Welcome! {}", id))
/// } else {
/// Ok("Welcome Anonymous!".to_owned())
/// }
/// }
///
/// fn login(mut req: HttpRequest) -> HttpResponse {
/// req.remember("User1".to_owned()); // <- remember identity
/// HttpResponse::Ok().finish()
/// }
///
/// fn logout(mut req: HttpRequest) -> HttpResponse {
/// req.forget(); // <- remove identity
/// HttpResponse::Ok().finish()
/// }
/// # fn main() {}
/// ```
pub trait RequestIdentity {
/// Return the claimed identity of the user associated request or
/// ``None`` if no identity can be found associated with the request.
fn identity(&self) -> Option<String>;
/// Remember identity.
fn remember(&self, identity: String);
/// This method is used to 'forget' the current identity on subsequent
/// requests.
fn forget(&self);
}
impl<S> RequestIdentity for HttpRequest<S> {
fn identity(&self) -> Option<String> {
if let Some(id) = self.extensions().get::<IdentityBox>() {
return id.0.identity().map(|s| s.to_owned());
}
None
}
fn remember(&self, identity: String) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.as_mut().remember(identity);
}
}
fn forget(&self) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.forget();
}
}
}
/// An identity
pub trait Identity: 'static {
/// Return the claimed identity of the user associated request or
/// ``None`` if no identity can be found associated with the request.
fn identity(&self) -> Option<&str>;
/// Remember identity.
fn remember(&mut self, key: String);
/// This method is used to 'forget' the current identity on subsequent
/// requests.
fn forget(&mut self);
/// Write session to storage backend.
fn write(&mut self, resp: HttpResponse) -> Result<Response>;
}
/// Identity policy definition.
pub trait IdentityPolicy<S>: Sized + 'static {
/// The associated identity
type Identity: Identity;
/// The return type of the middleware
type Future: Future<Item = Self::Identity, Error = Error>;
/// Parse the session from request and load data from a service identity.
fn from_request(&self, request: &HttpRequest<S>) -> Self::Future;
}
/// Request identity middleware
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
/// use actix_web::App;
///
/// fn main() {
/// let app = App::new().middleware(IdentityService::new(
/// // <- create identity middleware
/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
/// .name("auth-cookie")
/// .secure(false),
/// ));
/// }
/// ```
pub struct IdentityService<T> {
backend: T,
}
impl<T> IdentityService<T> {
/// Create new identity service with specified backend.
pub fn new(backend: T) -> Self {
IdentityService { backend }
}
}
struct IdentityBox(Box<Identity>);
impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
let req = req.clone();
let fut = self.backend.from_request(&req).then(move |res| match res {
Ok(id) => {
req.extensions_mut().insert(IdentityBox(Box::new(id)));
FutOk(None)
}
Err(err) => FutErr(err),
});
Ok(Started::Future(Box::new(fut)))
}
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
if let Some(ref mut id) = req.extensions_mut().get_mut::<IdentityBox>() {
id.0.as_mut().write(resp)
} else {
Ok(Response::Done(resp))
}
}
}
#[doc(hidden)]
/// Identity that uses private cookies as identity storage.
pub struct CookieIdentity {
changed: bool,
identity: Option<String>,
inner: Rc<CookieIdentityInner>,
}
impl Identity for CookieIdentity {
fn identity(&self) -> Option<&str> {
self.identity.as_ref().map(|s| s.as_ref())
}
fn remember(&mut self, value: String) {
self.changed = true;
self.identity = Some(value);
}
fn forget(&mut self) {
self.changed = true;
self.identity = None;
}
fn write(&mut self, mut resp: HttpResponse) -> Result<Response> {
if self.changed {
let _ = self.inner.set_cookie(&mut resp, self.identity.take());
}
Ok(Response::Done(resp))
}
}
struct CookieIdentityInner {
key: Key,
name: String,
path: String,
domain: Option<String>,
secure: bool,
max_age: Option<Duration>,
}
impl CookieIdentityInner {
fn new(key: &[u8]) -> CookieIdentityInner {
CookieIdentityInner {
key: Key::from_master(key),
name: "actix-identity".to_owned(),
path: "/".to_owned(),
domain: None,
secure: true,
max_age: None,
}
}
fn set_cookie(&self, resp: &mut HttpResponse, id: Option<String>) -> Result<()> {
let some = id.is_some();
{
let id = id.unwrap_or_else(String::new);
let mut cookie = Cookie::new(self.name.clone(), id);
cookie.set_path(self.path.clone());
cookie.set_secure(self.secure);
cookie.set_http_only(true);
if let Some(ref domain) = self.domain {
cookie.set_domain(domain.clone());
}
if let Some(max_age) = self.max_age {
cookie.set_max_age(max_age);
}
let mut jar = CookieJar::new();
if some {
jar.private(&self.key).add(cookie);
} else {
jar.add_original(cookie.clone());
jar.private(&self.key).remove(cookie);
}
for cookie in jar.delta() {
let val = HeaderValue::from_str(&cookie.to_string())?;
resp.headers_mut().append(header::SET_COOKIE, val);
}
}
Ok(())
}
fn load<S>(&self, req: &HttpRequest<S>) -> Option<String> {
if let Ok(cookies) = req.cookies() {
for cookie in cookies.iter() {
if cookie.name() == self.name {
let mut jar = CookieJar::new();
jar.add_original(cookie.clone());
let cookie_opt = jar.private(&self.key).get(&self.name);
if let Some(cookie) = cookie_opt {
return Some(cookie.value().into());
}
}
}
}
None
}
}
/// Use cookies for request identity storage.
///
/// The constructors take a key as an argument.
/// This is the private key for cookie - when this value is changed,
/// all identities are lost. The constructors will panic if the key is less
/// than 32 bytes in length.
///
/// # Example
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
/// use actix_web::App;
///
/// fn main() {
/// let app = App::new().middleware(IdentityService::new(
/// // <- create identity middleware
/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy
/// .domain("www.rust-lang.org")
/// .name("actix_auth")
/// .path("/")
/// .secure(true),
/// ));
/// }
/// ```
pub struct CookieIdentityPolicy(Rc<CookieIdentityInner>);
impl CookieIdentityPolicy {
/// Construct new `CookieIdentityPolicy` instance.
///
/// Panics if key length is less than 32 bytes.
pub fn new(key: &[u8]) -> CookieIdentityPolicy {
CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key)))
}
/// Sets the `path` field in the session cookie being built.
pub fn path<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().path = value.into();
self
}
/// Sets the `name` field in the session cookie being built.
pub fn name<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().name = value.into();
self
}
/// Sets the `domain` field in the session cookie being built.
pub fn domain<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into());
self
}
/// Sets the `secure` field in the session cookie being built.
///
/// If the `secure` field is set, a cookie will only be transmitted when the
/// connection is secure - i.e. `https`
pub fn secure(mut self, value: bool) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().secure = value;
self
}
/// Sets the `max-age` field in the session cookie being built.
pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
self
}
}
impl<S> IdentityPolicy<S> for CookieIdentityPolicy {
type Identity = CookieIdentity;
type Future = FutureResult<CookieIdentity, Error>;
fn from_request(&self, req: &HttpRequest<S>) -> Self::Future {
let identity = self.0.load(req);
FutOk(CookieIdentity {
identity,
changed: false,
inner: Rc::clone(&self.0),
})
}
}

View File

@ -1,384 +0,0 @@
//! Request logging middleware
use std::collections::HashSet;
use std::env;
use std::fmt::{self, Display, Formatter};
use regex::Regex;
use time;
use error::Result;
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Finished, Middleware, Started};
/// `Middleware` for logging request and response info to the terminal.
///
/// `Logger` middleware uses standard log crate to log information. You should
/// enable logger for `actix_web` package to see access log.
/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar)
///
/// ## Usage
///
/// Create `Logger` middleware with the specified `format`.
/// Default `Logger` could be created with `default` method, it uses the
/// default format:
///
/// ```ignore
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// ```
/// ```rust
/// # extern crate actix_web;
/// extern crate env_logger;
/// use actix_web::middleware::Logger;
/// use actix_web::App;
///
/// fn main() {
/// std::env::set_var("RUST_LOG", "actix_web=info");
/// env_logger::init();
///
/// let app = App::new()
/// .middleware(Logger::default())
/// .middleware(Logger::new("%a %{User-Agent}i"))
/// .finish();
/// }
/// ```
///
/// ## Format
///
/// `%%` The percent sign
///
/// `%a` Remote IP-address (IP-address of proxy if using reverse proxy)
///
/// `%t` Time when the request was started to process
///
/// `%r` First line of request
///
/// `%s` Response status code
///
/// `%b` Size of response in bytes, including HTTP headers
///
/// `%T` Time taken to serve the request, in seconds with floating fraction in
/// .06f format
///
/// `%D` Time taken to serve the request, in milliseconds
///
/// `%{FOO}i` request.headers['FOO']
///
/// `%{FOO}o` response.headers['FOO']
///
/// `%{FOO}e` os.environ['FOO']
///
pub struct Logger {
format: Format,
exclude: HashSet<String>,
}
impl Logger {
/// Create `Logger` middleware with the specified `format`.
pub fn new(format: &str) -> Logger {
Logger {
format: Format::new(format),
exclude: HashSet::new(),
}
}
/// Ignore and do not log access info for specified path.
pub fn exclude<T: Into<String>>(mut self, path: T) -> Self {
self.exclude.insert(path.into());
self
}
}
impl Default for Logger {
/// Create `Logger` middleware with format:
///
/// ```ignore
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// ```
fn default() -> Logger {
Logger {
format: Format::default(),
exclude: HashSet::new(),
}
}
}
struct StartTime(time::Tm);
impl Logger {
fn log<S>(&self, req: &HttpRequest<S>, resp: &HttpResponse) {
if let Some(entry_time) = req.extensions().get::<StartTime>() {
let render = |fmt: &mut Formatter| {
for unit in &self.format.0 {
unit.render(fmt, req, resp, entry_time.0)?;
}
Ok(())
};
info!("{}", FormatDisplay(&render));
}
}
}
impl<S> Middleware<S> for Logger {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
if !self.exclude.contains(req.path()) {
req.extensions_mut().insert(StartTime(time::now()));
}
Ok(Started::Done)
}
fn finish(&self, req: &HttpRequest<S>, resp: &HttpResponse) -> Finished {
self.log(req, resp);
Finished::Done
}
}
/// A formatting style for the `Logger`, consisting of multiple
/// `FormatText`s concatenated into one line.
#[derive(Clone)]
#[doc(hidden)]
struct Format(Vec<FormatText>);
impl Default for Format {
/// Return the default formatting style for the `Logger`:
fn default() -> Format {
Format::new(r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#)
}
}
impl Format {
/// Create a `Format` from a format string.
///
/// Returns `None` if the format string syntax is incorrect.
pub fn new(s: &str) -> Format {
trace!("Access log format: {}", s);
let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbTD]?)").unwrap();
let mut idx = 0;
let mut results = Vec::new();
for cap in fmt.captures_iter(s) {
let m = cap.get(0).unwrap();
let pos = m.start();
if idx != pos {
results.push(FormatText::Str(s[idx..pos].to_owned()));
}
idx = m.end();
if let Some(key) = cap.get(2) {
results.push(match cap.get(3).unwrap().as_str() {
"i" => FormatText::RequestHeader(key.as_str().to_owned()),
"o" => FormatText::ResponseHeader(key.as_str().to_owned()),
"e" => FormatText::EnvironHeader(key.as_str().to_owned()),
_ => unreachable!(),
})
} else {
let m = cap.get(1).unwrap();
results.push(match m.as_str() {
"%" => FormatText::Percent,
"a" => FormatText::RemoteAddr,
"t" => FormatText::RequestTime,
"r" => FormatText::RequestLine,
"s" => FormatText::ResponseStatus,
"b" => FormatText::ResponseSize,
"T" => FormatText::Time,
"D" => FormatText::TimeMillis,
_ => FormatText::Str(m.as_str().to_owned()),
});
}
}
if idx != s.len() {
results.push(FormatText::Str(s[idx..].to_owned()));
}
Format(results)
}
}
/// A string of text to be logged. This is either one of the data
/// fields supported by the `Logger`, or a custom `String`.
#[doc(hidden)]
#[derive(Debug, Clone)]
pub enum FormatText {
Str(String),
Percent,
RequestLine,
RequestTime,
ResponseStatus,
ResponseSize,
Time,
TimeMillis,
RemoteAddr,
RequestHeader(String),
ResponseHeader(String),
EnvironHeader(String),
}
impl FormatText {
fn render<S>(
&self, fmt: &mut Formatter, req: &HttpRequest<S>, resp: &HttpResponse,
entry_time: time::Tm,
) -> Result<(), fmt::Error> {
match *self {
FormatText::Str(ref string) => fmt.write_str(string),
FormatText::Percent => "%".fmt(fmt),
FormatText::RequestLine => {
if req.query_string().is_empty() {
fmt.write_fmt(format_args!(
"{} {} {:?}",
req.method(),
req.path(),
req.version()
))
} else {
fmt.write_fmt(format_args!(
"{} {}?{} {:?}",
req.method(),
req.path(),
req.query_string(),
req.version()
))
}
}
FormatText::ResponseStatus => resp.status().as_u16().fmt(fmt),
FormatText::ResponseSize => resp.response_size().fmt(fmt),
FormatText::Time => {
let rt = time::now() - entry_time;
let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0;
fmt.write_fmt(format_args!("{:.6}", rt))
}
FormatText::TimeMillis => {
let rt = time::now() - entry_time;
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::RequestTime => entry_time
.strftime("[%d/%b/%Y:%H:%M:%S %z]")
.unwrap()
.fmt(fmt),
FormatText::RequestHeader(ref name) => {
let s = if let Some(val) = req.headers().get(name) {
if let Ok(s) = val.to_str() {
s
} else {
"-"
}
} else {
"-"
};
fmt.write_fmt(format_args!("{}", s))
}
FormatText::ResponseHeader(ref name) => {
let s = if let Some(val) = resp.headers().get(name) {
if let Ok(s) = val.to_str() {
s
} else {
"-"
}
} else {
"-"
};
fmt.write_fmt(format_args!("{}", s))
}
FormatText::EnvironHeader(ref name) => {
if let Ok(val) = env::var(name) {
fmt.write_fmt(format_args!("{}", val))
} else {
"-".fmt(fmt)
}
}
}
}
}
pub(crate) struct FormatDisplay<'a>(&'a Fn(&mut Formatter) -> Result<(), fmt::Error>);
impl<'a> fmt::Display for FormatDisplay<'a> {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
(self.0)(fmt)
}
}
#[cfg(test)]
mod tests {
use time;
use super::*;
use http::{header, StatusCode};
use test::TestRequest;
#[test]
fn test_logger() {
let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test");
let req = TestRequest::with_header(
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
).finish();
let resp = HttpResponse::build(StatusCode::OK)
.header("X-Test", "ttt")
.force_close()
.finish();
match logger.start(&req) {
Ok(Started::Done) => (),
_ => panic!(),
};
match logger.finish(&req, &resp) {
Finished::Done => (),
_ => panic!(),
}
let entry_time = time::now();
let render = |fmt: &mut Formatter| {
for unit in &logger.format.0 {
unit.render(fmt, &req, &resp, entry_time)?;
}
Ok(())
};
let s = format!("{}", FormatDisplay(&render));
assert!(s.contains("ACTIX-WEB ttt"));
}
#[test]
fn test_default_format() {
let format = Format::default();
let req = TestRequest::with_header(
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
).finish();
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
let entry_time = time::now();
let render = |fmt: &mut Formatter| {
for unit in &format.0 {
unit.render(fmt, &req, &resp, entry_time)?;
}
Ok(())
};
let s = format!("{}", FormatDisplay(&render));
assert!(s.contains("GET / HTTP/1.1"));
assert!(s.contains("200 0"));
assert!(s.contains("ACTIX-WEB"));
let req = TestRequest::with_uri("/?test").finish();
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
let entry_time = time::now();
let render = |fmt: &mut Formatter| {
for unit in &format.0 {
unit.render(fmt, &req, &resp, entry_time)?;
}
Ok(())
};
let s = format!("{}", FormatDisplay(&render));
assert!(s.contains("GET /?test HTTP/1.1"));
}
}

View File

@ -1,68 +0,0 @@
//! Middlewares
use futures::Future;
use error::{Error, Result};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
mod logger;
pub mod cors;
pub mod csrf;
mod defaultheaders;
mod errhandlers;
#[cfg(feature = "session")]
pub mod identity;
#[cfg(feature = "session")]
pub mod session;
pub use self::defaultheaders::DefaultHeaders;
pub use self::errhandlers::ErrorHandlers;
pub use self::logger::Logger;
/// Middleware start result
pub enum Started {
/// Middleware is completed, continue to next middleware
Done,
/// New http response got generated. If middleware generates response
/// handler execution halts.
Response(HttpResponse),
/// Execution completed, runs future to completion.
Future(Box<Future<Item = Option<HttpResponse>, Error = Error>>),
}
/// Middleware execution result
pub enum Response {
/// New http response got generated
Done(HttpResponse),
/// Result is a future that resolves to a new http response
Future(Box<Future<Item = HttpResponse, Error = Error>>),
}
/// Middleware finish result
pub enum Finished {
/// Execution completed
Done,
/// Execution completed, but run future to completion
Future(Box<Future<Item = (), Error = Error>>),
}
/// Middleware definition
#[allow(unused_variables)]
pub trait Middleware<S>: 'static {
/// Method is called when request is ready. It may return
/// future, which should resolve before next middleware get called.
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
Ok(Started::Done)
}
/// Method is called when handler returns response,
/// but before sending http message to peer.
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
Ok(Response::Done(resp))
}
/// Method is called after body stream get sent to peer.
fn finish(&self, req: &HttpRequest<S>, resp: &HttpResponse) -> Finished {
Finished::Done
}
}

View File

@ -1,617 +0,0 @@
//! User sessions.
//!
//! Actix provides a general solution for session management. The
//! [**SessionStorage**](struct.SessionStorage.html)
//! middleware can be used with different backend types to store session
//! data in different backends.
//!
//! By default, only cookie session backend is implemented. Other
//! backend implementations can be added.
//!
//! [**CookieSessionBackend**](struct.CookieSessionBackend.html)
//! uses cookies as session storage. `CookieSessionBackend` creates sessions
//! which are limited to storing fewer than 4000 bytes of data, as the payload
//! must fit into a single cookie. An internal server error is generated if a
//! session contains more than 4000 bytes.
//!
//! A cookie may have a security policy of *signed* or *private*. Each has
//! a respective `CookieSessionBackend` constructor.
//!
//! A *signed* cookie may be viewed but not modified by the client. A *private*
//! cookie may neither be viewed nor modified by the client.
//!
//! The constructors take a key as an argument. This is the private key
//! for cookie session - when this value is changed, all session data is lost.
//!
//! In general, you create a `SessionStorage` middleware and initialize it
//! with specific backend implementation, such as a `CookieSessionBackend`.
//! To access session data,
//! [*HttpRequest::session()*](trait.RequestSession.html#tymethod.session)
//! must be used. This method returns a
//! [*Session*](struct.Session.html) object, which allows us to get or set
//! session data.
//!
//! ```rust
//! # extern crate actix_web;
//! use actix_web::{actix, server, App, HttpRequest, Result};
//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend};
//!
//! fn index(req: HttpRequest) -> Result<&'static str> {
//! // access session data
//! if let Some(count) = req.session().get::<i32>("counter")? {
//! println!("SESSION value: {}", count);
//! req.session().set("counter", count+1)?;
//! } else {
//! req.session().set("counter", 1)?;
//! }
//!
//! Ok("Welcome!")
//! }
//!
//! fn main() {
//! actix::System::run(|| {
//! server::new(
//! || App::new().middleware(
//! SessionStorage::new( // <- create session middleware
//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend
//! .secure(false)
//! )))
//! .bind("127.0.0.1:59880").unwrap()
//! .start();
//! # actix::System::current().stop();
//! });
//! }
//! ```
use std::cell::RefCell;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::rc::Rc;
use std::sync::Arc;
use cookie::{Cookie, CookieJar, Key, SameSite};
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
use futures::Future;
use http::header::{self, HeaderValue};
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json;
use serde_json::error::Error as JsonError;
use time::Duration;
use error::{Error, ResponseError, Result};
use handler::FromRequest;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response, Started};
/// The helper trait to obtain your session data from a request.
///
/// ```rust
/// use actix_web::middleware::session::RequestSession;
/// use actix_web::*;
///
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = req.session().get::<i32>("counter")? {
/// req.session().set("counter", count + 1)?;
/// } else {
/// req.session().set("counter", 1)?;
/// }
///
/// Ok("Welcome!")
/// }
/// # fn main() {}
/// ```
pub trait RequestSession {
/// Get the session from the request
fn session(&self) -> Session;
}
impl<S> RequestSession for HttpRequest<S> {
fn session(&self) -> Session {
if let Some(s_impl) = self.extensions().get::<Arc<SessionImplCell>>() {
return Session(SessionInner::Session(Arc::clone(&s_impl)));
}
Session(SessionInner::None)
}
}
/// The high-level interface you use to modify session data.
///
/// Session object could be obtained with
/// [`RequestSession::session`](trait.RequestSession.html#tymethod.session)
/// method. `RequestSession` trait is implemented for `HttpRequest`.
///
/// ```rust
/// use actix_web::middleware::session::RequestSession;
/// use actix_web::*;
///
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = req.session().get::<i32>("counter")? {
/// req.session().set("counter", count + 1)?;
/// } else {
/// req.session().set("counter", 1)?;
/// }
///
/// Ok("Welcome!")
/// }
/// # fn main() {}
/// ```
pub struct Session(SessionInner);
enum SessionInner {
Session(Arc<SessionImplCell>),
None,
}
impl Session {
/// Get a `value` from the session.
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
match self.0 {
SessionInner::Session(ref sess) => {
if let Some(s) = sess.as_ref().0.borrow().get(key) {
Ok(Some(serde_json::from_str(s)?))
} else {
Ok(None)
}
}
SessionInner::None => Ok(None),
}
}
/// Set a `value` from the session.
pub fn set<T: Serialize>(&self, key: &str, value: T) -> Result<()> {
match self.0 {
SessionInner::Session(ref sess) => {
sess.as_ref()
.0
.borrow_mut()
.set(key, serde_json::to_string(&value)?);
Ok(())
}
SessionInner::None => Ok(()),
}
}
/// Remove value from the session.
pub fn remove(&self, key: &str) {
match self.0 {
SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().remove(key),
SessionInner::None => (),
}
}
/// Clear the session.
pub fn clear(&self) {
match self.0 {
SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().clear(),
SessionInner::None => (),
}
}
}
/// Extractor implementation for Session type.
///
/// ```rust
/// # use actix_web::*;
/// use actix_web::middleware::session::Session;
///
/// fn index(session: Session) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = session.get::<i32>("counter")? {
/// session.set("counter", count + 1)?;
/// } else {
/// session.set("counter", 1)?;
/// }
///
/// Ok("Welcome!")
/// }
/// # fn main() {}
/// ```
impl<S> FromRequest<S> for Session {
type Config = ();
type Result = Session;
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
req.session()
}
}
struct SessionImplCell(RefCell<Box<SessionImpl>>);
/// Session storage middleware
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::session::{CookieSessionBackend, SessionStorage};
/// use actix_web::App;
///
/// fn main() {
/// let app = App::new().middleware(SessionStorage::new(
/// // <- create session middleware
/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend
/// .secure(false),
/// ));
/// }
/// ```
pub struct SessionStorage<T, S>(T, PhantomData<S>);
impl<S, T: SessionBackend<S>> SessionStorage<T, S> {
/// Create session storage
pub fn new(backend: T) -> SessionStorage<T, S> {
SessionStorage(backend, PhantomData)
}
}
impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
let mut req = req.clone();
let fut = self.0.from_request(&mut req).then(move |res| match res {
Ok(sess) => {
req.extensions_mut()
.insert(Arc::new(SessionImplCell(RefCell::new(Box::new(sess)))));
FutOk(None)
}
Err(err) => FutErr(err),
});
Ok(Started::Future(Box::new(fut)))
}
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
if let Some(s_box) = req.extensions().get::<Arc<SessionImplCell>>() {
s_box.0.borrow_mut().write(resp)
} else {
Ok(Response::Done(resp))
}
}
}
/// A simple key-value storage interface that is internally used by `Session`.
pub trait SessionImpl: 'static {
/// Get session value by key
fn get(&self, key: &str) -> Option<&str>;
/// Set session value
fn set(&mut self, key: &str, value: String);
/// Remove specific key from session
fn remove(&mut self, key: &str);
/// Remove all values from session
fn clear(&mut self);
/// Write session to storage backend.
fn write(&self, resp: HttpResponse) -> Result<Response>;
}
/// Session's storage backend trait definition.
pub trait SessionBackend<S>: Sized + 'static {
/// Session item
type Session: SessionImpl;
/// Future that reads session
type ReadFuture: Future<Item = Self::Session, Error = Error>;
/// Parse the session from request and load data from a storage backend.
fn from_request(&self, request: &mut HttpRequest<S>) -> Self::ReadFuture;
}
/// Session that uses signed cookies as session storage
pub struct CookieSession {
changed: bool,
state: HashMap<String, String>,
inner: Rc<CookieSessionInner>,
}
/// Errors that can occur during handling cookie session
#[derive(Fail, Debug)]
pub enum CookieSessionError {
/// Size of the serialized session is greater than 4000 bytes.
#[fail(display = "Size of the serialized session is greater than 4000 bytes.")]
Overflow,
/// Fail to serialize session.
#[fail(display = "Fail to serialize session")]
Serialize(JsonError),
}
impl ResponseError for CookieSessionError {}
impl SessionImpl for CookieSession {
fn get(&self, key: &str) -> Option<&str> {
if let Some(s) = self.state.get(key) {
Some(s)
} else {
None
}
}
fn set(&mut self, key: &str, value: String) {
self.changed = true;
self.state.insert(key.to_owned(), value);
}
fn remove(&mut self, key: &str) {
self.changed = true;
self.state.remove(key);
}
fn clear(&mut self) {
self.changed = true;
self.state.clear()
}
fn write(&self, mut resp: HttpResponse) -> Result<Response> {
if self.changed {
let _ = self.inner.set_cookie(&mut resp, &self.state);
}
Ok(Response::Done(resp))
}
}
enum CookieSecurity {
Signed,
Private,
}
struct CookieSessionInner {
key: Key,
security: CookieSecurity,
name: String,
path: String,
domain: Option<String>,
secure: bool,
http_only: bool,
max_age: Option<Duration>,
same_site: Option<SameSite>,
}
impl CookieSessionInner {
fn new(key: &[u8], security: CookieSecurity) -> CookieSessionInner {
CookieSessionInner {
security,
key: Key::from_master(key),
name: "actix-session".to_owned(),
path: "/".to_owned(),
domain: None,
secure: true,
http_only: true,
max_age: None,
same_site: None,
}
}
fn set_cookie(
&self, resp: &mut HttpResponse, state: &HashMap<String, String>,
) -> Result<()> {
let value =
serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?;
if value.len() > 4064 {
return Err(CookieSessionError::Overflow.into());
}
let mut cookie = Cookie::new(self.name.clone(), value);
cookie.set_path(self.path.clone());
cookie.set_secure(self.secure);
cookie.set_http_only(self.http_only);
if let Some(ref domain) = self.domain {
cookie.set_domain(domain.clone());
}
if let Some(max_age) = self.max_age {
cookie.set_max_age(max_age);
}
if let Some(same_site) = self.same_site {
cookie.set_same_site(same_site);
}
let mut jar = CookieJar::new();
match self.security {
CookieSecurity::Signed => jar.signed(&self.key).add(cookie),
CookieSecurity::Private => jar.private(&self.key).add(cookie),
}
for cookie in jar.delta() {
let val = HeaderValue::from_str(&cookie.encoded().to_string())?;
resp.headers_mut().append(header::SET_COOKIE, val);
}
Ok(())
}
fn load<S>(&self, req: &mut HttpRequest<S>) -> HashMap<String, String> {
if let Ok(cookies) = req.cookies() {
for cookie in cookies.iter() {
if cookie.name() == self.name {
let mut jar = CookieJar::new();
jar.add_original(cookie.clone());
let cookie_opt = match self.security {
CookieSecurity::Signed => jar.signed(&self.key).get(&self.name),
CookieSecurity::Private => {
jar.private(&self.key).get(&self.name)
}
};
if let Some(cookie) = cookie_opt {
if let Ok(val) = serde_json::from_str(cookie.value()) {
return val;
}
}
}
}
}
HashMap::new()
}
}
/// Use cookies for session storage.
///
/// `CookieSessionBackend` creates sessions which are limited to storing
/// fewer than 4000 bytes of data (as the payload must fit into a single
/// cookie). An Internal Server Error is generated if the session contains more
/// than 4000 bytes.
///
/// A cookie may have a security policy of *signed* or *private*. Each has a
/// respective `CookieSessionBackend` constructor.
///
/// A *signed* cookie is stored on the client as plaintext alongside
/// a signature such that the cookie may be viewed but not modified by the
/// client.
///
/// A *private* cookie is stored on the client as encrypted text
/// such that it may neither be viewed nor modified by the client.
///
/// The constructors take a key as an argument.
/// This is the private key for cookie session - when this value is changed,
/// all session data is lost. The constructors will panic if the key is less
/// than 32 bytes in length.
///
/// The backend relies on `cookie` crate to create and read cookies.
/// By default all cookies are percent encoded, but certain symbols may
/// cause troubles when reading cookie, if they are not properly percent encoded.
///
/// # Example
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::session::CookieSessionBackend;
///
/// # fn main() {
/// let backend: CookieSessionBackend = CookieSessionBackend::signed(&[0; 32])
/// .domain("www.rust-lang.org")
/// .name("actix_session")
/// .path("/")
/// .secure(true);
/// # }
/// ```
pub struct CookieSessionBackend(Rc<CookieSessionInner>);
impl CookieSessionBackend {
/// Construct new *signed* `CookieSessionBackend` instance.
///
/// Panics if key length is less than 32 bytes.
pub fn signed(key: &[u8]) -> CookieSessionBackend {
CookieSessionBackend(Rc::new(CookieSessionInner::new(
key,
CookieSecurity::Signed,
)))
}
/// Construct new *private* `CookieSessionBackend` instance.
///
/// Panics if key length is less than 32 bytes.
pub fn private(key: &[u8]) -> CookieSessionBackend {
CookieSessionBackend(Rc::new(CookieSessionInner::new(
key,
CookieSecurity::Private,
)))
}
/// Sets the `path` field in the session cookie being built.
pub fn path<S: Into<String>>(mut self, value: S) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().path = value.into();
self
}
/// Sets the `name` field in the session cookie being built.
pub fn name<S: Into<String>>(mut self, value: S) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().name = value.into();
self
}
/// Sets the `domain` field in the session cookie being built.
pub fn domain<S: Into<String>>(mut self, value: S) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into());
self
}
/// Sets the `secure` field in the session cookie being built.
///
/// If the `secure` field is set, a cookie will only be transmitted when the
/// connection is secure - i.e. `https`
pub fn secure(mut self, value: bool) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().secure = value;
self
}
/// Sets the `http_only` field in the session cookie being built.
pub fn http_only(mut self, value: bool) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().http_only = value;
self
}
/// Sets the `same_site` field in the session cookie being built.
pub fn same_site(mut self, value: SameSite) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().same_site = Some(value);
self
}
/// Sets the `max-age` field in the session cookie being built.
pub fn max_age(mut self, value: Duration) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
self
}
}
impl<S> SessionBackend<S> for CookieSessionBackend {
type Session = CookieSession;
type ReadFuture = FutureResult<CookieSession, Error>;
fn from_request(&self, req: &mut HttpRequest<S>) -> Self::ReadFuture {
let state = self.0.load(req);
FutOk(CookieSession {
changed: false,
inner: Rc::clone(&self.0),
state,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use application::App;
use test;
#[test]
fn cookie_session() {
let mut srv = test::TestServer::with_factory(|| {
App::new()
.middleware(SessionStorage::new(
CookieSessionBackend::signed(&[0; 32]).secure(false),
)).resource("/", |r| {
r.f(|req| {
let _ = req.session().set("counter", 100);
"test"
})
})
});
let request = srv.get().uri(srv.url("/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.cookie("actix-session").is_some());
}
#[test]
fn cookie_session_extractor() {
let mut srv = test::TestServer::with_factory(|| {
App::new()
.middleware(SessionStorage::new(
CookieSessionBackend::signed(&[0; 32]).secure(false),
)).resource("/", |r| {
r.with(|ses: Session| {
let _ = ses.set("counter", 100);
"test"
})
})
});
let request = srv.get().uri(srv.url("/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.cookie("actix-session").is_some());
}
}

View File

@ -1,815 +0,0 @@
//! Multipart requests support
use std::cell::{RefCell, UnsafeCell};
use std::marker::PhantomData;
use std::rc::Rc;
use std::{cmp, fmt};
use bytes::Bytes;
use futures::task::{current as current_task, Task};
use futures::{Async, Poll, Stream};
use http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue};
use http::HttpTryFrom;
use httparse;
use mime;
use error::{MultipartError, ParseError, PayloadError};
use payload::PayloadBuffer;
const MAX_HEADERS: usize = 32;
/// The server-side implementation of `multipart/form-data` requests.
///
/// This will parse the incoming stream into `MultipartItem` instances via its
/// Stream implementation.
/// `MultipartItem::Field` contains multipart field. `MultipartItem::Multipart`
/// is used for nested multipart streams.
pub struct Multipart<S> {
safety: Safety,
error: Option<MultipartError>,
inner: Option<Rc<RefCell<InnerMultipart<S>>>>,
}
///
pub enum MultipartItem<S> {
/// Multipart field
Field(Field<S>),
/// Nested multipart stream
Nested(Multipart<S>),
}
enum InnerMultipartItem<S> {
None,
Field(Rc<RefCell<InnerField<S>>>),
Multipart(Rc<RefCell<InnerMultipart<S>>>),
}
#[derive(PartialEq, Debug)]
enum InnerState {
/// Stream eof
Eof,
/// Skip data until first boundary
FirstBoundary,
/// Reading boundary
Boundary,
/// Reading Headers,
Headers,
}
struct InnerMultipart<S> {
payload: PayloadRef<S>,
boundary: String,
state: InnerState,
item: InnerMultipartItem<S>,
}
impl Multipart<()> {
/// Extract boundary info from headers.
pub fn boundary(headers: &HeaderMap) -> Result<String, MultipartError> {
if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
if let Ok(ct) = content_type.parse::<mime::Mime>() {
if let Some(boundary) = ct.get_param(mime::BOUNDARY) {
Ok(boundary.as_str().to_owned())
} else {
Err(MultipartError::Boundary)
}
} else {
Err(MultipartError::ParseContentType)
}
} else {
Err(MultipartError::ParseContentType)
}
} else {
Err(MultipartError::NoContentType)
}
}
}
impl<S> Multipart<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
/// Create multipart instance for boundary.
pub fn new(boundary: Result<String, MultipartError>, stream: S) -> Multipart<S> {
match boundary {
Ok(boundary) => Multipart {
error: None,
safety: Safety::new(),
inner: Some(Rc::new(RefCell::new(InnerMultipart {
boundary,
payload: PayloadRef::new(PayloadBuffer::new(stream)),
state: InnerState::FirstBoundary,
item: InnerMultipartItem::None,
}))),
},
Err(err) => Multipart {
error: Some(err),
safety: Safety::new(),
inner: None,
},
}
}
}
impl<S> Stream for Multipart<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
type Item = MultipartItem<S>;
type Error = MultipartError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if let Some(err) = self.error.take() {
Err(err)
} else if self.safety.current() {
self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety)
} else {
Ok(Async::NotReady)
}
}
}
impl<S> InnerMultipart<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
fn read_headers(payload: &mut PayloadBuffer<S>) -> Poll<HeaderMap, MultipartError> {
match payload.read_until(b"\r\n\r\n")? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(bytes)) => {
let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS];
match httparse::parse_headers(&bytes, &mut hdrs) {
Ok(httparse::Status::Complete((_, hdrs))) => {
// convert headers
let mut headers = HeaderMap::with_capacity(hdrs.len());
for h in hdrs {
if let Ok(name) = HeaderName::try_from(h.name) {
if let Ok(value) = HeaderValue::try_from(h.value) {
headers.append(name, value);
} else {
return Err(ParseError::Header.into());
}
} else {
return Err(ParseError::Header.into());
}
}
Ok(Async::Ready(headers))
}
Ok(httparse::Status::Partial) => Err(ParseError::Header.into()),
Err(err) => Err(ParseError::from(err).into()),
}
}
}
}
fn read_boundary(
payload: &mut PayloadBuffer<S>, boundary: &str,
) -> Poll<bool, MultipartError> {
// TODO: need to read epilogue
match payload.readline()? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(chunk)) => {
if chunk.len() == boundary.len() + 4
&& &chunk[..2] == b"--"
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes()
{
Ok(Async::Ready(false))
} else if chunk.len() == boundary.len() + 6
&& &chunk[..2] == b"--"
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes()
&& &chunk[boundary.len() + 2..boundary.len() + 4] == b"--"
{
Ok(Async::Ready(true))
} else {
Err(MultipartError::Boundary)
}
}
}
}
fn skip_until_boundary(
payload: &mut PayloadBuffer<S>, boundary: &str,
) -> Poll<bool, MultipartError> {
let mut eof = false;
loop {
match payload.readline()? {
Async::Ready(Some(chunk)) => {
if chunk.is_empty() {
//ValueError("Could not find starting boundary %r"
//% (self._boundary))
}
if chunk.len() < boundary.len() {
continue;
}
if &chunk[..2] == b"--"
&& &chunk[2..chunk.len() - 2] == boundary.as_bytes()
{
break;
} else {
if chunk.len() < boundary.len() + 2 {
continue;
}
let b: &[u8] = boundary.as_ref();
if &chunk[..boundary.len()] == b
&& &chunk[boundary.len()..boundary.len() + 2] == b"--"
{
eof = true;
break;
}
}
}
Async::NotReady => return Ok(Async::NotReady),
Async::Ready(None) => return Err(MultipartError::Incomplete),
}
}
Ok(Async::Ready(eof))
}
fn poll(
&mut self, safety: &Safety,
) -> Poll<Option<MultipartItem<S>>, MultipartError> {
if self.state == InnerState::Eof {
Ok(Async::Ready(None))
} else {
// release field
loop {
// Nested multipart streams of fields has to be consumed
// before switching to next
if safety.current() {
let stop = match self.item {
InnerMultipartItem::Field(ref mut field) => {
match field.borrow_mut().poll(safety)? {
Async::NotReady => return Ok(Async::NotReady),
Async::Ready(Some(_)) => continue,
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,
};
if stop {
self.item = InnerMultipartItem::None;
}
if let InnerMultipartItem::None = self.item {
break;
}
}
}
let headers = if let Some(payload) = self.payload.get_mut(safety) {
match self.state {
// read until first boundary
InnerState::FirstBoundary => {
match InnerMultipart::skip_until_boundary(
payload,
&self.boundary,
)? {
Async::Ready(eof) => {
if eof {
self.state = InnerState::Eof;
return Ok(Async::Ready(None));
} else {
self.state = InnerState::Headers;
}
}
Async::NotReady => return Ok(Async::NotReady),
}
}
// read boundary
InnerState::Boundary => {
match InnerMultipart::read_boundary(payload, &self.boundary)? {
Async::NotReady => return Ok(Async::NotReady),
Async::Ready(eof) => {
if eof {
self.state = InnerState::Eof;
return Ok(Async::Ready(None));
} else {
self.state = InnerState::Headers;
}
}
}
}
_ => (),
}
// read field headers for next field
if self.state == InnerState::Headers {
if let Async::Ready(headers) = InnerMultipart::read_headers(payload)?
{
self.state = InnerState::Boundary;
headers
} else {
return Ok(Async::NotReady);
}
} else {
unreachable!()
}
} else {
debug!("NotReady: field is in flight");
return Ok(Async::NotReady);
};
// content type
let mut mt = mime::APPLICATION_OCTET_STREAM;
if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
if let Ok(ct) = content_type.parse::<mime::Mime>() {
mt = ct;
}
}
}
self.state = InnerState::Boundary;
// 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(MultipartItem::Nested(Multipart {
safety: safety.clone(),
error: None,
inner: Some(inner),
}))))
} else {
let field = Rc::new(RefCell::new(InnerField::new(
self.payload.clone(),
self.boundary.clone(),
&headers,
)?));
self.item = InnerMultipartItem::Field(Rc::clone(&field));
Ok(Async::Ready(Some(MultipartItem::Field(Field::new(
safety.clone(),
headers,
mt,
field,
)))))
}
}
}
}
impl<S> Drop for InnerMultipart<S> {
fn drop(&mut self) {
// InnerMultipartItem::Field has to be dropped first because of Safety.
self.item = InnerMultipartItem::None;
}
}
/// A single field in a multipart stream
pub struct Field<S> {
ct: mime::Mime,
headers: HeaderMap,
inner: Rc<RefCell<InnerField<S>>>,
safety: Safety,
}
impl<S> Field<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
fn new(
safety: Safety, headers: HeaderMap, ct: mime::Mime,
inner: Rc<RefCell<InnerField<S>>>,
) -> Self {
Field {
ct,
headers,
inner,
safety,
}
}
/// Get a map of headers
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
/// Get the content type of the field
pub fn content_type(&self) -> &mime::Mime {
&self.ct
}
/// Get the content disposition of the field, if it exists
pub fn content_disposition(&self) -> Option<ContentDisposition> {
// RFC 7578: 'Each part MUST contain a Content-Disposition header field
// where the disposition type is "form-data".'
if let Some(content_disposition) =
self.headers.get(::http::header::CONTENT_DISPOSITION)
{
ContentDisposition::from_raw(content_disposition).ok()
} else {
None
}
}
}
impl<S> Stream for Field<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
type Item = Bytes;
type Error = MultipartError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if self.safety.current() {
self.inner.borrow_mut().poll(&self.safety)
} else {
Ok(Async::NotReady)
}
}
}
impl<S> fmt::Debug for Field<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "\nMultipartField: {}", self.ct)?;
writeln!(f, " boundary: {}", self.inner.borrow().boundary)?;
writeln!(f, " headers:")?;
for (key, val) in self.headers.iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
}
}
struct InnerField<S> {
payload: Option<PayloadRef<S>>,
boundary: String,
eof: bool,
length: Option<u64>,
}
impl<S> InnerField<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
fn new(
payload: PayloadRef<S>, boundary: String, headers: &HeaderMap,
) -> Result<InnerField<S>, PayloadError> {
let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
Some(len)
} else {
return Err(PayloadError::Incomplete);
}
} else {
return Err(PayloadError::Incomplete);
}
} else {
None
};
Ok(InnerField {
boundary,
payload: Some(payload),
eof: false,
length: len,
})
}
/// Reads body part content chunk of the specified size.
/// The body part must has `Content-Length` header with proper value.
fn read_len(
payload: &mut PayloadBuffer<S>, size: &mut u64,
) -> Poll<Option<Bytes>, MultipartError> {
if *size == 0 {
Ok(Async::Ready(None))
} else {
match payload.readany() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => Err(MultipartError::Incomplete),
Ok(Async::Ready(Some(mut chunk))) => {
let len = cmp::min(chunk.len() as u64, *size);
*size -= len;
let ch = chunk.split_to(len as usize);
if !chunk.is_empty() {
payload.unprocessed(chunk);
}
Ok(Async::Ready(Some(ch)))
}
Err(err) => Err(err.into()),
}
}
}
/// Reads content chunk of body part with unknown length.
/// The `Content-Length` header for body part is not necessary.
fn read_stream(
payload: &mut PayloadBuffer<S>, boundary: &str,
) -> Poll<Option<Bytes>, MultipartError> {
match payload.read_until(b"\r")? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(mut chunk)) => {
if chunk.len() == 1 {
payload.unprocessed(chunk);
match payload.read_exact(boundary.len() + 4)? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(mut chunk)) => {
if &chunk[..2] == b"\r\n"
&& &chunk[2..4] == b"--"
&& &chunk[4..] == boundary.as_bytes()
{
payload.unprocessed(chunk);
Ok(Async::Ready(None))
} else {
// \r might be part of data stream
let ch = chunk.split_to(1);
payload.unprocessed(chunk);
Ok(Async::Ready(Some(ch)))
}
}
}
} else {
let to = chunk.len() - 1;
let ch = chunk.split_to(to);
payload.unprocessed(chunk);
Ok(Async::Ready(Some(ch)))
}
}
}
}
fn poll(&mut self, s: &Safety) -> Poll<Option<Bytes>, MultipartError> {
if self.payload.is_none() {
return Ok(Async::Ready(None));
}
let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) {
let res = if let Some(ref mut len) = self.length {
InnerField::read_len(payload, len)?
} else {
InnerField::read_stream(payload, &self.boundary)?
};
match res {
Async::NotReady => Async::NotReady,
Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)),
Async::Ready(None) => {
self.eof = true;
match payload.readline()? {
Async::NotReady => Async::NotReady,
Async::Ready(None) => Async::Ready(None),
Async::Ready(Some(line)) => {
if line.as_ref() != b"\r\n" {
warn!("multipart field did not read all the data or it is malformed");
}
Async::Ready(None)
}
}
}
}
} else {
Async::NotReady
};
if Async::Ready(None) == result {
self.payload.take();
}
Ok(result)
}
}
struct PayloadRef<S> {
payload: Rc<UnsafeCell<PayloadBuffer<S>>>,
}
impl<S> PayloadRef<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
fn new(payload: PayloadBuffer<S>) -> PayloadRef<S> {
PayloadRef {
payload: Rc::new(payload.into()),
}
}
fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadBuffer<S>>
where
'a: 'b,
{
// Unsafe: Invariant is inforced by Safety Safety is used as ref counter,
// only top most ref can have mutable access to payload.
if s.current() {
let payload: &mut PayloadBuffer<S> = unsafe { &mut *self.payload.get() };
Some(payload)
} else {
None
}
}
}
impl<S> Clone for PayloadRef<S> {
fn clone(&self) -> PayloadRef<S> {
PayloadRef {
payload: Rc::clone(&self.payload),
}
}
}
/// Counter. It tracks of number of clones of payloads and give access to
/// payload only to top most task panics if Safety get destroyed and it not top
/// most task.
#[derive(Debug)]
struct Safety {
task: Option<Task>,
level: usize,
payload: Rc<PhantomData<bool>>,
}
impl Safety {
fn new() -> Safety {
let payload = Rc::new(PhantomData);
Safety {
task: None,
level: Rc::strong_count(&payload),
payload,
}
}
fn current(&self) -> bool {
Rc::strong_count(&self.payload) == self.level
}
}
impl Clone for Safety {
fn clone(&self) -> Safety {
let payload = Rc::clone(&self.payload);
Safety {
task: Some(current_task()),
level: Rc::strong_count(&payload),
payload,
}
}
}
impl Drop for Safety {
fn drop(&mut self) {
// parent task is dead
if Rc::strong_count(&self.payload) != self.level {
panic!("Safety get dropped but it is not from top-most task");
}
if let Some(task) = self.task.take() {
task.notify()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
use futures::future::{lazy, result};
use payload::{Payload, PayloadWriter};
use tokio::runtime::current_thread::Runtime;
#[test]
fn test_boundary() {
let headers = HeaderMap::new();
match Multipart::boundary(&headers) {
Err(MultipartError::NoContentType) => (),
_ => unreachable!("should not happen"),
}
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("test"),
);
match Multipart::boundary(&headers) {
Err(MultipartError::ParseContentType) => (),
_ => unreachable!("should not happen"),
}
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("multipart/mixed"),
);
match Multipart::boundary(&headers) {
Err(MultipartError::Boundary) => (),
_ => unreachable!("should not happen"),
}
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static(
"multipart/mixed; boundary=\"5c02368e880e436dab70ed54e1c58209\"",
),
);
assert_eq!(
Multipart::boundary(&headers).unwrap(),
"5c02368e880e436dab70ed54e1c58209"
);
}
#[test]
fn test_multipart() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
data\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n");
sender.feed_data(bytes);
let mut multipart = Multipart::new(
Ok("abbc761f78ff4d7cb7573b5a23f96ef0".to_owned()),
payload,
);
match multipart.poll() {
Ok(Async::Ready(Some(item))) => match item {
MultipartItem::Field(mut field) => {
{
use http::header::{DispositionParam, DispositionType};
let cd = field.content_disposition().unwrap();
assert_eq!(cd.disposition, DispositionType::FormData);
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);
match field.poll() {
Ok(Async::Ready(Some(chunk))) => {
assert_eq!(chunk, "test")
}
_ => unreachable!(),
}
match field.poll() {
Ok(Async::Ready(None)) => (),
_ => unreachable!(),
}
}
_ => unreachable!(),
},
_ => unreachable!(),
}
match multipart.poll() {
Ok(Async::Ready(Some(item))) => match item {
MultipartItem::Field(mut field) => {
assert_eq!(field.content_type().type_(), mime::TEXT);
assert_eq!(field.content_type().subtype(), mime::PLAIN);
match field.poll() {
Ok(Async::Ready(Some(chunk))) => {
assert_eq!(chunk, "data")
}
_ => unreachable!(),
}
match field.poll() {
Ok(Async::Ready(None)) => (),
_ => unreachable!(),
}
}
_ => unreachable!(),
},
_ => unreachable!(),
}
match multipart.poll() {
Ok(Async::Ready(None)) => (),
_ => unreachable!(),
}
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}
}

View File

@ -1,303 +0,0 @@
use std;
use std::ops::Index;
use std::path::PathBuf;
use std::rc::Rc;
use std::str::FromStr;
use http::StatusCode;
use smallvec::SmallVec;
use error::{InternalError, ResponseError, UriSegmentError};
use uri::Url;
/// A trait to abstract the idea of creating a new instance of a type from a
/// path parameter.
pub trait FromParam: Sized {
/// The associated error which can be returned from parsing.
type Err: ResponseError;
/// Parses a string `s` to return a value of this type.
fn from_param(s: &str) -> Result<Self, Self::Err>;
}
#[derive(Debug, Clone)]
pub(crate) enum ParamItem {
Static(&'static str),
UrlSegment(u16, u16),
}
/// Route match information
///
/// If resource path contains variable patterns, `Params` stores this variables.
#[derive(Debug, Clone)]
pub struct Params {
url: Url,
pub(crate) tail: u16,
pub(crate) segments: SmallVec<[(Rc<String>, ParamItem); 3]>,
}
impl Params {
pub(crate) fn new() -> Params {
Params {
url: Url::default(),
tail: 0,
segments: SmallVec::new(),
}
}
pub(crate) fn with_url(url: &Url) -> Params {
Params {
url: url.clone(),
tail: 0,
segments: SmallVec::new(),
}
}
pub(crate) fn clear(&mut self) {
self.segments.clear();
}
pub(crate) fn set_tail(&mut self, tail: u16) {
self.tail = tail;
}
pub(crate) fn set_url(&mut self, url: Url) {
self.url = url;
}
pub(crate) fn add(&mut self, name: Rc<String>, value: ParamItem) {
self.segments.push((name, value));
}
pub(crate) fn add_static(&mut self, name: &str, value: &'static str) {
self.segments
.push((Rc::new(name.to_string()), ParamItem::Static(value)));
}
/// Check if there are any matched patterns
pub fn is_empty(&self) -> bool {
self.segments.is_empty()
}
/// Check number of extracted parameters
pub fn len(&self) -> usize {
self.segments.len()
}
/// Get matched parameter by name without type conversion
pub fn get(&self, key: &str) -> Option<&str> {
for item in self.segments.iter() {
if key == item.0.as_str() {
return match item.1 {
ParamItem::Static(ref s) => Some(&s),
ParamItem::UrlSegment(s, e) => {
Some(&self.url.path()[(s as usize)..(e as usize)])
}
};
}
}
if key == "tail" {
Some(&self.url.path()[(self.tail as usize)..])
} else {
None
}
}
/// Get unprocessed part of path
pub fn unprocessed(&self) -> &str {
&self.url.path()[(self.tail as usize)..]
}
/// Get matched `FromParam` compatible parameter by name.
///
/// If keyed parameter is not available empty string is used as default
/// value.
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// fn index(req: HttpRequest) -> Result<String> {
/// let ivalue: isize = req.match_info().query("val")?;
/// Ok(format!("isuze value: {:?}", ivalue))
/// }
/// # fn main() {}
/// ```
pub fn query<T: FromParam>(&self, key: &str) -> Result<T, <T as FromParam>::Err> {
if let Some(s) = self.get(key) {
T::from_param(s)
} else {
T::from_param("")
}
}
/// Return iterator to items in parameter container
pub fn iter(&self) -> ParamsIter {
ParamsIter {
idx: 0,
params: self,
}
}
}
#[derive(Debug)]
pub struct ParamsIter<'a> {
idx: usize,
params: &'a Params,
}
impl<'a> Iterator for ParamsIter<'a> {
type Item = (&'a str, &'a str);
#[inline]
fn next(&mut self) -> Option<(&'a str, &'a str)> {
if self.idx < self.params.len() {
let idx = self.idx;
let res = match self.params.segments[idx].1 {
ParamItem::Static(ref s) => &s,
ParamItem::UrlSegment(s, e) => {
&self.params.url.path()[(s as usize)..(e as usize)]
}
};
self.idx += 1;
return Some((&self.params.segments[idx].0, res));
}
None
}
}
impl<'a> Index<&'a str> for Params {
type Output = str;
fn index(&self, name: &'a str) -> &str {
self.get(name)
.expect("Value for parameter is not available")
}
}
impl Index<usize> for Params {
type Output = str;
fn index(&self, idx: usize) -> &str {
match self.segments[idx].1 {
ParamItem::Static(ref s) => &s,
ParamItem::UrlSegment(s, e) => &self.url.path()[(s as usize)..(e as usize)],
}
}
}
/// Creates a `PathBuf` from a path parameter. The returned `PathBuf` is
/// percent-decoded. If a segment is equal to "..", the previous segment (if
/// any) is skipped.
///
/// For security purposes, if a segment meets any of the following conditions,
/// an `Err` is returned indicating the condition met:
///
/// * Decoded segment starts with any of: `.` (except `..`), `*`
/// * Decoded segment ends with any of: `:`, `>`, `<`
/// * Decoded segment contains any of: `/`
/// * On Windows, decoded segment contains any of: '\'
/// * Percent-encoding results in invalid UTF8.
///
/// As a result of these conditions, a `PathBuf` parsed from request path
/// parameter is safe to interpolate within, or use as a suffix of, a path
/// without additional checks.
impl FromParam for PathBuf {
type Err = UriSegmentError;
fn from_param(val: &str) -> Result<PathBuf, UriSegmentError> {
let mut buf = PathBuf::new();
for segment in val.split('/') {
if segment == ".." {
buf.pop();
} else if segment.starts_with('.') {
return Err(UriSegmentError::BadStart('.'));
} else if segment.starts_with('*') {
return Err(UriSegmentError::BadStart('*'));
} else if segment.ends_with(':') {
return Err(UriSegmentError::BadEnd(':'));
} else if segment.ends_with('>') {
return Err(UriSegmentError::BadEnd('>'));
} else if segment.ends_with('<') {
return Err(UriSegmentError::BadEnd('<'));
} else if segment.is_empty() {
continue;
} else if cfg!(windows) && segment.contains('\\') {
return Err(UriSegmentError::BadChar('\\'));
} else {
buf.push(segment)
}
}
Ok(buf)
}
}
macro_rules! FROM_STR {
($type:ty) => {
impl FromParam for $type {
type Err = InternalError<<$type as FromStr>::Err>;
fn from_param(val: &str) -> Result<Self, Self::Err> {
<$type as FromStr>::from_str(val)
.map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST))
}
}
};
}
FROM_STR!(u8);
FROM_STR!(u16);
FROM_STR!(u32);
FROM_STR!(u64);
FROM_STR!(usize);
FROM_STR!(i8);
FROM_STR!(i16);
FROM_STR!(i32);
FROM_STR!(i64);
FROM_STR!(isize);
FROM_STR!(f32);
FROM_STR!(f64);
FROM_STR!(String);
FROM_STR!(std::net::IpAddr);
FROM_STR!(std::net::Ipv4Addr);
FROM_STR!(std::net::Ipv6Addr);
FROM_STR!(std::net::SocketAddr);
FROM_STR!(std::net::SocketAddrV4);
FROM_STR!(std::net::SocketAddrV6);
#[cfg(test)]
mod tests {
use super::*;
use std::iter::FromIterator;
#[test]
fn test_path_buf() {
assert_eq!(
PathBuf::from_param("/test/.tt"),
Err(UriSegmentError::BadStart('.'))
);
assert_eq!(
PathBuf::from_param("/test/*tt"),
Err(UriSegmentError::BadStart('*'))
);
assert_eq!(
PathBuf::from_param("/test/tt:"),
Err(UriSegmentError::BadEnd(':'))
);
assert_eq!(
PathBuf::from_param("/test/tt<"),
Err(UriSegmentError::BadEnd('<'))
);
assert_eq!(
PathBuf::from_param("/test/tt>"),
Err(UriSegmentError::BadEnd('>'))
);
assert_eq!(
PathBuf::from_param("/seg1/seg2/"),
Ok(PathBuf::from_iter(vec!["seg1", "seg2"]))
);
assert_eq!(
PathBuf::from_param("/seg1/../seg2/"),
Ok(PathBuf::from_iter(vec!["seg2"]))
);
}
}

View File

@ -1,869 +0,0 @@
use std::marker::PhantomData;
use std::rc::Rc;
use std::{io, mem};
use futures::sync::oneshot;
use futures::{Async, Future, Poll, Stream};
use log::Level::Debug;
use body::{Body, BodyStream};
use context::{ActorHttpContext, Frame};
use error::Error;
use handler::{AsyncResult, AsyncResultItem};
use header::ContentEncoding;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Finished, Middleware, Response, Started};
use server::{HttpHandlerTask, Writer, WriterState};
#[doc(hidden)]
pub trait PipelineHandler<S> {
fn encoding(&self) -> ContentEncoding;
fn handle(&self, &HttpRequest<S>) -> AsyncResult<HttpResponse>;
}
#[doc(hidden)]
pub struct Pipeline<S: 'static, H>(
PipelineInfo<S>,
PipelineState<S, H>,
Rc<Vec<Box<Middleware<S>>>>,
);
enum PipelineState<S, H> {
None,
Error,
Starting(StartMiddlewares<S, H>),
Handler(WaitingResponse<S, H>),
RunMiddlewares(RunMiddlewares<S, H>),
Response(ProcessResponse<S, H>),
Finishing(FinishingMiddlewares<S, H>),
Completed(Completed<S, H>),
}
impl<S: 'static, H: PipelineHandler<S>> PipelineState<S, H> {
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
match *self {
PipelineState::Starting(ref mut state) => state.poll(info, mws),
PipelineState::Handler(ref mut state) => state.poll(info, mws),
PipelineState::RunMiddlewares(ref mut state) => state.poll(info, mws),
PipelineState::Finishing(ref mut state) => state.poll(info, mws),
PipelineState::Completed(ref mut state) => state.poll(info),
PipelineState::Response(ref mut state) => state.poll(info, mws),
PipelineState::None | PipelineState::Error => None,
}
}
}
struct PipelineInfo<S: 'static> {
req: HttpRequest<S>,
count: u16,
context: Option<Box<ActorHttpContext>>,
error: Option<Error>,
disconnected: Option<bool>,
encoding: ContentEncoding,
}
impl<S: 'static> PipelineInfo<S> {
fn poll_context(&mut self) -> Poll<(), Error> {
if let Some(ref mut context) = self.context {
match context.poll() {
Err(err) => Err(err),
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(_)) => Ok(Async::Ready(())),
}
} else {
Ok(Async::Ready(()))
}
}
}
impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
pub(crate) fn new(
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>, handler: Rc<H>,
) -> Pipeline<S, H> {
let mut info = PipelineInfo {
req,
count: 0,
error: None,
context: None,
disconnected: None,
encoding: handler.encoding(),
};
let state = StartMiddlewares::init(&mut info, &mws, handler);
Pipeline(info, state, mws)
}
}
impl<S: 'static, H> Pipeline<S, H> {
#[inline]
fn is_done(&self) -> bool {
match self.1 {
PipelineState::None
| PipelineState::Error
| PipelineState::Starting(_)
| PipelineState::Handler(_)
| PipelineState::RunMiddlewares(_)
| PipelineState::Response(_) => true,
PipelineState::Finishing(_) | PipelineState::Completed(_) => false,
}
}
}
impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
fn disconnected(&mut self) {
self.0.disconnected = Some(true);
}
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
let mut state = mem::replace(&mut self.1, PipelineState::None);
loop {
if let PipelineState::Response(st) = state {
match st.poll_io(io, &mut self.0, &self.2) {
Ok(state) => {
self.1 = state;
if let Some(error) = self.0.error.take() {
return Err(error);
} else {
return Ok(Async::Ready(self.is_done()));
}
}
Err(state) => {
self.1 = state;
return Ok(Async::NotReady);
}
}
}
match state {
PipelineState::None => return Ok(Async::Ready(true)),
PipelineState::Error => {
return Err(
io::Error::new(io::ErrorKind::Other, "Internal error").into()
)
}
_ => (),
}
match state.poll(&mut self.0, &self.2) {
Some(st) => state = st,
None => {
return {
self.1 = state;
Ok(Async::NotReady)
}
}
}
}
}
fn poll_completed(&mut self) -> Poll<(), Error> {
let mut state = mem::replace(&mut self.1, PipelineState::None);
loop {
match state {
PipelineState::None | PipelineState::Error => {
return Ok(Async::Ready(()))
}
_ => (),
}
if let Some(st) = state.poll(&mut self.0, &self.2) {
state = st;
} else {
self.1 = state;
return Ok(Async::NotReady);
}
}
}
}
type Fut = Box<Future<Item = Option<HttpResponse>, Error = Error>>;
/// Middlewares start executor
struct StartMiddlewares<S, H> {
hnd: Rc<H>,
fut: Option<Fut>,
_s: PhantomData<S>,
}
impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
fn init(
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>], hnd: Rc<H>,
) -> PipelineState<S, H> {
// execute middlewares, we need this stage because middlewares could be
// non-async and we can move to next state immediately
let len = mws.len() as u16;
loop {
if info.count == len {
let reply = hnd.handle(&info.req);
return WaitingResponse::init(info, mws, reply);
} else {
match mws[info.count as usize].start(&info.req) {
Ok(Started::Done) => info.count += 1,
Ok(Started::Response(resp)) => {
return RunMiddlewares::init(info, mws, resp);
}
Ok(Started::Future(fut)) => {
return PipelineState::Starting(StartMiddlewares {
hnd,
fut: Some(fut),
_s: PhantomData,
})
}
Err(err) => {
return RunMiddlewares::init(info, mws, err.into());
}
}
}
}
}
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
let len = mws.len() as u16;
'outer: loop {
match self.fut.as_mut().unwrap().poll() {
Ok(Async::NotReady) => {
return None;
}
Ok(Async::Ready(resp)) => {
info.count += 1;
if let Some(resp) = resp {
return Some(RunMiddlewares::init(info, mws, resp));
}
loop {
if info.count == len {
let reply = self.hnd.handle(&info.req);
return Some(WaitingResponse::init(info, mws, reply));
} else {
let res = mws[info.count as usize].start(&info.req);
match res {
Ok(Started::Done) => info.count += 1,
Ok(Started::Response(resp)) => {
return Some(RunMiddlewares::init(info, mws, resp));
}
Ok(Started::Future(fut)) => {
self.fut = Some(fut);
continue 'outer;
}
Err(err) => {
return Some(RunMiddlewares::init(
info,
mws,
err.into(),
));
}
}
}
}
}
Err(err) => {
return Some(RunMiddlewares::init(info, mws, err.into()));
}
}
}
}
}
// waiting for response
struct WaitingResponse<S, H> {
fut: Box<Future<Item = HttpResponse, Error = Error>>,
_s: PhantomData<S>,
_h: PhantomData<H>,
}
impl<S: 'static, H> WaitingResponse<S, H> {
#[inline]
fn init(
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
reply: AsyncResult<HttpResponse>,
) -> PipelineState<S, H> {
match reply.into() {
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, mws, resp),
AsyncResultItem::Err(err) => RunMiddlewares::init(info, mws, err.into()),
AsyncResultItem::Future(fut) => PipelineState::Handler(WaitingResponse {
fut,
_s: PhantomData,
_h: PhantomData,
}),
}
}
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
match self.fut.poll() {
Ok(Async::NotReady) => None,
Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, mws, resp)),
Err(err) => Some(RunMiddlewares::init(info, mws, err.into())),
}
}
}
/// Middlewares response executor
struct RunMiddlewares<S, H> {
curr: usize,
fut: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
_s: PhantomData<S>,
_h: PhantomData<H>,
}
impl<S: 'static, H> RunMiddlewares<S, H> {
#[inline]
fn init(
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>], mut resp: HttpResponse,
) -> PipelineState<S, H> {
if info.count == 0 {
return ProcessResponse::init(resp);
}
let mut curr = 0;
let len = mws.len();
loop {
let state = mws[curr].response(&info.req, resp);
resp = match state {
Err(err) => {
info.count = (curr + 1) as u16;
return ProcessResponse::init(err.into());
}
Ok(Response::Done(r)) => {
curr += 1;
if curr == len {
return ProcessResponse::init(r);
} else {
r
}
}
Ok(Response::Future(fut)) => {
return PipelineState::RunMiddlewares(RunMiddlewares {
curr,
fut: Some(fut),
_s: PhantomData,
_h: PhantomData,
});
}
};
}
}
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
let len = mws.len();
loop {
// poll latest fut
let mut resp = match self.fut.as_mut().unwrap().poll() {
Ok(Async::NotReady) => return None,
Ok(Async::Ready(resp)) => {
self.curr += 1;
resp
}
Err(err) => return Some(ProcessResponse::init(err.into())),
};
loop {
if self.curr == len {
return Some(ProcessResponse::init(resp));
} else {
let state = mws[self.curr].response(&info.req, resp);
match state {
Err(err) => return Some(ProcessResponse::init(err.into())),
Ok(Response::Done(r)) => {
self.curr += 1;
resp = r
}
Ok(Response::Future(fut)) => {
self.fut = Some(fut);
break;
}
}
}
}
}
}
}
struct ProcessResponse<S, H> {
resp: Option<HttpResponse>,
iostate: IOState,
running: RunningState,
drain: Option<oneshot::Sender<()>>,
_s: PhantomData<S>,
_h: PhantomData<H>,
}
#[derive(PartialEq, Debug)]
enum RunningState {
Running,
Paused,
Done,
}
impl RunningState {
#[inline]
fn pause(&mut self) {
if *self != RunningState::Done {
*self = RunningState::Paused
}
}
#[inline]
fn resume(&mut self) {
if *self != RunningState::Done {
*self = RunningState::Running
}
}
}
enum IOState {
Response,
Payload(BodyStream),
Actor(Box<ActorHttpContext>),
Done,
}
impl<S: 'static, H> ProcessResponse<S, H> {
#[inline]
fn init(resp: HttpResponse) -> PipelineState<S, H> {
PipelineState::Response(ProcessResponse {
resp: Some(resp),
iostate: IOState::Response,
running: RunningState::Running,
drain: None,
_s: PhantomData,
_h: PhantomData,
})
}
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
// connection is dead at this point
match mem::replace(&mut self.iostate, IOState::Done) {
IOState::Response => Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
)),
IOState::Payload(_) => Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
)),
IOState::Actor(mut ctx) => {
if info.disconnected.take().is_some() {
ctx.disconnected();
}
loop {
match ctx.poll() {
Ok(Async::Ready(Some(vec))) => {
if vec.is_empty() {
continue;
}
for frame in vec {
match frame {
Frame::Chunk(None) => {
info.context = Some(ctx);
return Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
Frame::Chunk(Some(_)) => (),
Frame::Drain(fut) => {
let _ = fut.send(());
}
}
}
}
Ok(Async::Ready(None)) => {
return Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
))
}
Ok(Async::NotReady) => {
self.iostate = IOState::Actor(ctx);
return None;
}
Err(err) => {
info.context = Some(ctx);
info.error = Some(err);
return Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
}
}
}
IOState::Done => Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
)),
}
}
fn poll_io(
mut self, io: &mut Writer, info: &mut PipelineInfo<S>,
mws: &[Box<Middleware<S>>],
) -> Result<PipelineState<S, H>, PipelineState<S, H>> {
loop {
if self.drain.is_none() && self.running != RunningState::Paused {
// if task is paused, write buffer is probably full
'inner: loop {
let result = match mem::replace(&mut self.iostate, IOState::Done) {
IOState::Response => {
let encoding = self
.resp
.as_ref()
.unwrap()
.content_encoding()
.unwrap_or(info.encoding);
let result = match io.start(
&info.req,
self.resp.as_mut().unwrap(),
encoding,
) {
Ok(res) => res,
Err(err) => {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
};
if let Some(err) = self.resp.as_ref().unwrap().error() {
if self.resp.as_ref().unwrap().status().is_server_error()
{
error!(
"Error occured during request handling, status: {} {}",
self.resp.as_ref().unwrap().status(), err
);
} else {
warn!(
"Error occured during request handling: {}",
err
);
}
if log_enabled!(Debug) {
debug!("{:?}", err);
}
}
// always poll stream or actor for the first time
match self.resp.as_mut().unwrap().replace_body(Body::Empty) {
Body::Streaming(stream) => {
self.iostate = IOState::Payload(stream);
continue 'inner;
}
Body::Actor(ctx) => {
self.iostate = IOState::Actor(ctx);
continue 'inner;
}
_ => (),
}
result
}
IOState::Payload(mut body) => match body.poll() {
Ok(Async::Ready(None)) => {
if let Err(err) = io.write_eof() {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
break;
}
Ok(Async::Ready(Some(chunk))) => {
self.iostate = IOState::Payload(body);
match io.write(&chunk.into()) {
Err(err) => {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
Ok(result) => result,
}
}
Ok(Async::NotReady) => {
self.iostate = IOState::Payload(body);
break;
}
Err(err) => {
info.error = Some(err);
return Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
},
IOState::Actor(mut ctx) => {
if info.disconnected.take().is_some() {
ctx.disconnected();
}
match ctx.poll() {
Ok(Async::Ready(Some(vec))) => {
if vec.is_empty() {
self.iostate = IOState::Actor(ctx);
break;
}
let mut res = None;
for frame in vec {
match frame {
Frame::Chunk(None) => {
info.context = Some(ctx);
if let Err(err) = io.write_eof() {
info.error = Some(err.into());
return Ok(
FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
),
);
}
break 'inner;
}
Frame::Chunk(Some(chunk)) => match io
.write(&chunk)
{
Err(err) => {
info.context = Some(ctx);
info.error = Some(err.into());
return Ok(
FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
),
);
}
Ok(result) => res = Some(result),
},
Frame::Drain(fut) => self.drain = Some(fut),
}
}
self.iostate = IOState::Actor(ctx);
if self.drain.is_some() {
self.running.resume();
break 'inner;
}
res.unwrap()
}
Ok(Async::Ready(None)) => break,
Ok(Async::NotReady) => {
self.iostate = IOState::Actor(ctx);
break;
}
Err(err) => {
info.context = Some(ctx);
info.error = Some(err);
return Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
}
}
IOState::Done => break,
};
match result {
WriterState::Pause => {
self.running.pause();
break;
}
WriterState::Done => self.running.resume(),
}
}
}
// flush io but only if we need to
if self.running == RunningState::Paused || self.drain.is_some() {
match io.poll_completed(false) {
Ok(Async::Ready(_)) => {
self.running.resume();
// resolve drain futures
if let Some(tx) = self.drain.take() {
let _ = tx.send(());
}
// restart io processing
continue;
}
Ok(Async::NotReady) => return Err(PipelineState::Response(self)),
Err(err) => {
if let IOState::Actor(mut ctx) =
mem::replace(&mut self.iostate, IOState::Done)
{
ctx.disconnected();
info.context = Some(ctx);
}
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
}
}
break;
}
// response is completed
match self.iostate {
IOState::Done => {
match io.write_eof() {
Ok(_) => (),
Err(err) => {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
}
self.resp.as_mut().unwrap().set_response_size(io.written());
Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
))
}
_ => Err(PipelineState::Response(self)),
}
}
}
/// Middlewares start executor
struct FinishingMiddlewares<S, H> {
resp: Option<HttpResponse>,
fut: Option<Box<Future<Item = (), Error = Error>>>,
_s: PhantomData<S>,
_h: PhantomData<H>,
}
impl<S: 'static, H> FinishingMiddlewares<S, H> {
#[inline]
fn init(
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>], resp: HttpResponse,
) -> PipelineState<S, H> {
if info.count == 0 {
resp.release();
Completed::init(info)
} else {
let mut state = FinishingMiddlewares {
resp: Some(resp),
fut: None,
_s: PhantomData,
_h: PhantomData,
};
if let Some(st) = state.poll(info, mws) {
st
} else {
PipelineState::Finishing(state)
}
}
}
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
loop {
// poll latest fut
let not_ready = if let Some(ref mut fut) = self.fut {
match fut.poll() {
Ok(Async::NotReady) => true,
Ok(Async::Ready(())) => false,
Err(err) => {
error!("Middleware finish error: {}", err);
false
}
}
} else {
false
};
if not_ready {
return None;
}
self.fut = None;
if info.count == 0 {
self.resp.take().unwrap().release();
return Some(Completed::init(info));
}
info.count -= 1;
let state =
mws[info.count as usize].finish(&info.req, self.resp.as_ref().unwrap());
match state {
Finished::Done => {
if info.count == 0 {
self.resp.take().unwrap().release();
return Some(Completed::init(info));
}
}
Finished::Future(fut) => {
self.fut = Some(fut);
}
}
}
}
}
#[derive(Debug)]
struct Completed<S, H>(PhantomData<S>, PhantomData<H>);
impl<S, H> Completed<S, H> {
#[inline]
fn init(info: &mut PipelineInfo<S>) -> PipelineState<S, H> {
if let Some(ref err) = info.error {
error!("Error occurred during request handling: {}", err);
}
if info.context.is_none() {
PipelineState::None
} else {
match info.poll_context() {
Ok(Async::NotReady) => {
PipelineState::Completed(Completed(PhantomData, PhantomData))
}
Ok(Async::Ready(())) => PipelineState::None,
Err(_) => PipelineState::Error,
}
}
}
#[inline]
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
match info.poll_context() {
Ok(Async::NotReady) => None,
Ok(Async::Ready(())) => Some(PipelineState::None),
Err(_) => Some(PipelineState::Error),
}
}
}

View File

@ -1,328 +0,0 @@
//! Route match predicates
#![allow(non_snake_case)]
use std::marker::PhantomData;
use http;
use http::{header, HttpTryFrom};
use server::message::Request;
/// Trait defines resource route predicate.
/// Predicate can modify request object. It is also possible to
/// to store extra attributes on request by using `Extensions` container,
/// Extensions container available via `HttpRequest::extensions()` method.
pub trait Predicate<S> {
/// Check if request matches predicate
fn check(&self, &Request, &S) -> bool;
}
/// Return predicate that matches if any of supplied predicate matches.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{pred, App, HttpResponse};
///
/// fn main() {
/// App::new().resource("/index.html", |r| {
/// r.route()
/// .filter(pred::Any(pred::Get()).or(pred::Post()))
/// .f(|r| HttpResponse::MethodNotAllowed())
/// });
/// }
/// ```
pub fn Any<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AnyPredicate<S> {
AnyPredicate(vec![Box::new(pred)])
}
/// Matches if any of supplied predicate matches.
pub struct AnyPredicate<S>(Vec<Box<Predicate<S>>>);
impl<S> AnyPredicate<S> {
/// Add new predicate to list of predicates to check
pub fn or<P: Predicate<S> + 'static>(mut self, pred: P) -> Self {
self.0.push(Box::new(pred));
self
}
}
impl<S: 'static> Predicate<S> for AnyPredicate<S> {
fn check(&self, req: &Request, state: &S) -> bool {
for p in &self.0 {
if p.check(req, state) {
return true;
}
}
false
}
}
/// Return predicate that matches if all of supplied predicate matches.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{pred, App, HttpResponse};
///
/// fn main() {
/// App::new().resource("/index.html", |r| {
/// r.route()
/// .filter(
/// pred::All(pred::Get())
/// .and(pred::Header("content-type", "text/plain")),
/// )
/// .f(|_| HttpResponse::MethodNotAllowed())
/// });
/// }
/// ```
pub fn All<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AllPredicate<S> {
AllPredicate(vec![Box::new(pred)])
}
/// Matches if all of supplied predicate matches.
pub struct AllPredicate<S>(Vec<Box<Predicate<S>>>);
impl<S> AllPredicate<S> {
/// Add new predicate to list of predicates to check
pub fn and<P: Predicate<S> + 'static>(mut self, pred: P) -> Self {
self.0.push(Box::new(pred));
self
}
}
impl<S: 'static> Predicate<S> for AllPredicate<S> {
fn check(&self, req: &Request, state: &S) -> bool {
for p in &self.0 {
if !p.check(req, state) {
return false;
}
}
true
}
}
/// Return predicate that matches if supplied predicate does not match.
pub fn Not<S: 'static, P: Predicate<S> + 'static>(pred: P) -> NotPredicate<S> {
NotPredicate(Box::new(pred))
}
#[doc(hidden)]
pub struct NotPredicate<S>(Box<Predicate<S>>);
impl<S: 'static> Predicate<S> for NotPredicate<S> {
fn check(&self, req: &Request, state: &S) -> bool {
!self.0.check(req, state)
}
}
/// Http method predicate
#[doc(hidden)]
pub struct MethodPredicate<S>(http::Method, PhantomData<S>);
impl<S: 'static> Predicate<S> for MethodPredicate<S> {
fn check(&self, req: &Request, _: &S) -> bool {
*req.method() == self.0
}
}
/// Predicate to match *GET* http method
pub fn Get<S: 'static>() -> MethodPredicate<S> {
MethodPredicate(http::Method::GET, PhantomData)
}
/// Predicate to match *POST* http method
pub fn Post<S: 'static>() -> MethodPredicate<S> {
MethodPredicate(http::Method::POST, PhantomData)
}
/// Predicate to match *PUT* http method
pub fn Put<S: 'static>() -> MethodPredicate<S> {
MethodPredicate(http::Method::PUT, PhantomData)
}
/// Predicate to match *DELETE* http method
pub fn Delete<S: 'static>() -> MethodPredicate<S> {
MethodPredicate(http::Method::DELETE, PhantomData)
}
/// Predicate to match *HEAD* http method
pub fn Head<S: 'static>() -> MethodPredicate<S> {
MethodPredicate(http::Method::HEAD, PhantomData)
}
/// Predicate to match *OPTIONS* http method
pub fn Options<S: 'static>() -> MethodPredicate<S> {
MethodPredicate(http::Method::OPTIONS, PhantomData)
}
/// Predicate to match *CONNECT* http method
pub fn Connect<S: 'static>() -> MethodPredicate<S> {
MethodPredicate(http::Method::CONNECT, PhantomData)
}
/// Predicate to match *PATCH* http method
pub fn Patch<S: 'static>() -> MethodPredicate<S> {
MethodPredicate(http::Method::PATCH, PhantomData)
}
/// Predicate to match *TRACE* http method
pub fn Trace<S: 'static>() -> MethodPredicate<S> {
MethodPredicate(http::Method::TRACE, PhantomData)
}
/// Predicate to match specified http method
pub fn Method<S: 'static>(method: http::Method) -> MethodPredicate<S> {
MethodPredicate(method, PhantomData)
}
/// Return predicate that matches if request contains specified header and
/// value.
pub fn Header<S: 'static>(
name: &'static str, value: &'static str,
) -> HeaderPredicate<S> {
HeaderPredicate(
header::HeaderName::try_from(name).unwrap(),
header::HeaderValue::from_static(value),
PhantomData,
)
}
#[doc(hidden)]
pub struct HeaderPredicate<S>(header::HeaderName, header::HeaderValue, PhantomData<S>);
impl<S: 'static> Predicate<S> for HeaderPredicate<S> {
fn check(&self, req: &Request, _: &S) -> bool {
if let Some(val) = req.headers().get(&self.0) {
return val == self.1;
}
false
}
}
/// Return predicate that matches if request contains specified Host name.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{pred, App, HttpResponse};
///
/// fn main() {
/// App::new().resource("/index.html", |r| {
/// r.route()
/// .filter(pred::Host("www.rust-lang.org"))
/// .f(|_| HttpResponse::MethodNotAllowed())
/// });
/// }
/// ```
pub fn Host<S: 'static, H: AsRef<str>>(host: H) -> HostPredicate<S> {
HostPredicate(host.as_ref().to_string(), None, PhantomData)
}
#[doc(hidden)]
pub struct HostPredicate<S>(String, Option<String>, PhantomData<S>);
impl<S> HostPredicate<S> {
/// Set reuest scheme to match
pub fn scheme<H: AsRef<str>>(&mut self, scheme: H) {
self.1 = Some(scheme.as_ref().to_string())
}
}
impl<S: 'static> Predicate<S> for HostPredicate<S> {
fn check(&self, req: &Request, _: &S) -> bool {
let info = req.connection_info();
if let Some(ref scheme) = self.1 {
self.0 == info.host() && scheme == info.scheme()
} else {
self.0 == info.host()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::{header, Method};
use test::TestRequest;
#[test]
fn test_header() {
let req = TestRequest::with_header(
header::TRANSFER_ENCODING,
header::HeaderValue::from_static("chunked"),
).finish();
let pred = Header("transfer-encoding", "chunked");
assert!(pred.check(&req, req.state()));
let pred = Header("transfer-encoding", "other");
assert!(!pred.check(&req, req.state()));
let pred = Header("content-type", "other");
assert!(!pred.check(&req, req.state()));
}
#[test]
fn test_host() {
let req = TestRequest::default()
.header(
header::HOST,
header::HeaderValue::from_static("www.rust-lang.org"),
).finish();
let pred = Host("www.rust-lang.org");
assert!(pred.check(&req, req.state()));
let pred = Host("localhost");
assert!(!pred.check(&req, req.state()));
}
#[test]
fn test_methods() {
let req = TestRequest::default().finish();
let req2 = TestRequest::default().method(Method::POST).finish();
assert!(Get().check(&req, req.state()));
assert!(!Get().check(&req2, req2.state()));
assert!(Post().check(&req2, req2.state()));
assert!(!Post().check(&req, req.state()));
let r = TestRequest::default().method(Method::PUT).finish();
assert!(Put().check(&r, r.state()));
assert!(!Put().check(&req, req.state()));
let r = TestRequest::default().method(Method::DELETE).finish();
assert!(Delete().check(&r, r.state()));
assert!(!Delete().check(&req, req.state()));
let r = TestRequest::default().method(Method::HEAD).finish();
assert!(Head().check(&r, r.state()));
assert!(!Head().check(&req, req.state()));
let r = TestRequest::default().method(Method::OPTIONS).finish();
assert!(Options().check(&r, r.state()));
assert!(!Options().check(&req, req.state()));
let r = TestRequest::default().method(Method::CONNECT).finish();
assert!(Connect().check(&r, r.state()));
assert!(!Connect().check(&req, req.state()));
let r = TestRequest::default().method(Method::PATCH).finish();
assert!(Patch().check(&r, r.state()));
assert!(!Patch().check(&req, req.state()));
let r = TestRequest::default().method(Method::TRACE).finish();
assert!(Trace().check(&r, r.state()));
assert!(!Trace().check(&req, req.state()));
}
#[test]
fn test_preds() {
let r = TestRequest::default().method(Method::TRACE).finish();
assert!(Not(Get()).check(&r, r.state()));
assert!(!Not(Trace()).check(&r, r.state()));
assert!(All(Trace()).and(Trace()).check(&r, r.state()));
assert!(!All(Get()).and(Trace()).check(&r, r.state()));
assert!(Any(Get()).or(Trace()).check(&r, r.state()));
assert!(!Any(Get()).or(Get()).check(&r, r.state()));
}
}

View File

@ -1,324 +0,0 @@
use std::ops::Deref;
use std::rc::Rc;
use futures::Future;
use http::Method;
use smallvec::SmallVec;
use error::Error;
use handler::{AsyncResult, FromRequest, Handler, Responder};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::Middleware;
use pred;
use route::Route;
use router::ResourceDef;
use with::WithFactory;
#[derive(Copy, Clone)]
pub(crate) struct RouteId(usize);
/// *Resource* is an entry in route table which corresponds to requested URL.
///
/// Resource in turn has at least one route.
/// Route consists of an object that implements `Handler` trait (handler)
/// and list of predicates (objects that implement `Predicate` trait).
/// Route uses builder-like pattern for configuration.
/// During request handling, resource object iterate through all routes
/// and check all predicates for specific route, if request matches all
/// predicates route route considered matched and route handler get called.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{App, HttpResponse, http};
///
/// fn main() {
/// let app = App::new()
/// .resource(
/// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok()))
/// .finish();
/// }
pub struct Resource<S = ()> {
rdef: ResourceDef,
routes: SmallVec<[Route<S>; 3]>,
middlewares: Rc<Vec<Box<Middleware<S>>>>,
}
impl<S> Resource<S> {
/// Create new resource with specified resource definition
pub fn new(rdef: ResourceDef) -> Self {
Resource {
rdef,
routes: SmallVec::new(),
middlewares: Rc::new(Vec::new()),
}
}
/// Name of the resource
pub(crate) fn get_name(&self) -> &str {
self.rdef.name()
}
/// Set resource name
pub fn name(&mut self, name: &str) {
self.rdef.set_name(name);
}
/// Resource definition
pub fn rdef(&self) -> &ResourceDef {
&self.rdef
}
}
impl<S: 'static> Resource<S> {
/// Register a new route and return mutable reference to *Route* object.
/// *Route* is used for route configuration, i.e. adding predicates,
/// setting up handler.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
///
/// fn main() {
/// let app = App::new()
/// .resource("/", |r| {
/// r.route()
/// .filter(pred::Any(pred::Get()).or(pred::Put()))
/// .filter(pred::Header("Content-Type", "text/plain"))
/// .f(|r| HttpResponse::Ok())
/// })
/// .finish();
/// }
/// ```
pub fn route(&mut self) -> &mut Route<S> {
self.routes.push(Route::default());
self.routes.last_mut().unwrap()
}
/// Register a new `GET` route.
pub fn get(&mut self) -> &mut Route<S> {
self.routes.push(Route::default());
self.routes.last_mut().unwrap().filter(pred::Get())
}
/// Register a new `POST` route.
pub fn post(&mut self) -> &mut Route<S> {
self.routes.push(Route::default());
self.routes.last_mut().unwrap().filter(pred::Post())
}
/// Register a new `PUT` route.
pub fn put(&mut self) -> &mut Route<S> {
self.routes.push(Route::default());
self.routes.last_mut().unwrap().filter(pred::Put())
}
/// Register a new `DELETE` route.
pub fn delete(&mut self) -> &mut Route<S> {
self.routes.push(Route::default());
self.routes.last_mut().unwrap().filter(pred::Delete())
}
/// Register a new `HEAD` route.
pub fn head(&mut self) -> &mut Route<S> {
self.routes.push(Route::default());
self.routes.last_mut().unwrap().filter(pred::Head())
}
/// Register a new route and add method check to route.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
/// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
///
/// App::new().resource("/", |r| r.method(http::Method::GET).f(index));
/// ```
///
/// This is shortcut for:
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().resource("/", |r| r.route().filter(pred::Get()).f(index));
/// ```
pub fn method(&mut self, method: Method) -> &mut Route<S> {
self.routes.push(Route::default());
self.routes.last_mut().unwrap().filter(pred::Method(method))
}
/// Register a new route and add handler object.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
/// fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() }
///
/// App::new().resource("/", |r| r.h(handler));
/// ```
///
/// This is shortcut for:
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().resource("/", |r| r.route().h(handler));
/// ```
pub fn h<H: Handler<S>>(&mut self, handler: H) {
self.routes.push(Route::default());
self.routes.last_mut().unwrap().h(handler)
}
/// Register a new route and add handler function.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
/// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
///
/// App::new().resource("/", |r| r.f(index));
/// ```
///
/// This is shortcut for:
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().resource("/", |r| r.route().f(index));
/// ```
pub fn f<F, R>(&mut self, handler: F)
where
F: Fn(&HttpRequest<S>) -> R + 'static,
R: Responder + 'static,
{
self.routes.push(Route::default());
self.routes.last_mut().unwrap().f(handler)
}
/// Register a new route and add handler.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
/// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
///
/// App::new().resource("/", |r| r.with(index));
/// ```
///
/// This is shortcut for:
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().resource("/", |r| r.route().with(index));
/// ```
pub fn with<T, F, R>(&mut self, handler: F)
where
F: WithFactory<T, S, R>,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{
self.routes.push(Route::default());
self.routes.last_mut().unwrap().with(handler);
}
/// Register a new route and add async handler.
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// use actix_web::*;
/// use futures::future::Future;
///
/// fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// unimplemented!()
/// }
///
/// App::new().resource("/", |r| r.with_async(index));
/// ```
///
/// This is shortcut for:
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # use actix_web::*;
/// # use futures::future::Future;
/// # fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// # unimplemented!()
/// # }
/// App::new().resource("/", |r| r.route().with_async(index));
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
where
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
{
self.routes.push(Route::default());
self.routes.last_mut().unwrap().with_async(handler);
}
/// Register a resource middleware
///
/// This is similar to `App's` middlewares, but
/// middlewares get invoked on resource level.
///
/// *Note* `Middleware::finish()` fires right after response get
/// prepared. It does not wait until body get sent to peer.
pub fn middleware<M: Middleware<S>>(&mut self, mw: M) {
Rc::get_mut(&mut self.middlewares)
.unwrap()
.push(Box::new(mw));
}
#[inline]
pub(crate) fn get_route_id(&self, req: &HttpRequest<S>) -> Option<RouteId> {
for idx in 0..self.routes.len() {
if (&self.routes[idx]).check(req) {
return Some(RouteId(idx));
}
}
None
}
#[inline]
pub(crate) fn handle(
&self, id: RouteId, req: &HttpRequest<S>,
) -> AsyncResult<HttpResponse> {
if self.middlewares.is_empty() {
(&self.routes[id.0]).handle(req)
} else {
(&self.routes[id.0]).compose(req.clone(), Rc::clone(&self.middlewares))
}
}
}
/// Default resource
pub struct DefaultResource<S>(Rc<Resource<S>>);
impl<S> Deref for DefaultResource<S> {
type Target = Resource<S>;
fn deref(&self) -> &Resource<S> {
self.0.as_ref()
}
}
impl<S> Clone for DefaultResource<S> {
fn clone(&self) -> Self {
DefaultResource(self.0.clone())
}
}
impl<S> From<Resource<S>> for DefaultResource<S> {
fn from(res: Resource<S>) -> Self {
DefaultResource(Rc::new(res))
}
}

View File

@ -1,666 +0,0 @@
use std::marker::PhantomData;
use std::rc::Rc;
use futures::{Async, Future, Poll};
use error::Error;
use handler::{
AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler, Responder,
RouteHandler, WrapHandler,
};
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{
Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse,
Started as MiddlewareStarted,
};
use pred::Predicate;
use with::{WithAsyncFactory, WithFactory};
/// 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 Route<S> {
preds: Vec<Box<Predicate<S>>>,
handler: InnerHandler<S>,
}
impl<S: 'static> Default for Route<S> {
fn default() -> Route<S> {
Route {
preds: Vec::new(),
handler: InnerHandler::new(|_: &_| HttpResponse::new(StatusCode::NOT_FOUND)),
}
}
}
impl<S: 'static> Route<S> {
#[inline]
pub(crate) fn check(&self, req: &HttpRequest<S>) -> bool {
let state = req.state();
for pred in &self.preds {
if !pred.check(req, state) {
return false;
}
}
true
}
#[inline]
pub(crate) fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
self.handler.handle(req)
}
#[inline]
pub(crate) fn compose(
&self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
) -> AsyncResult<HttpResponse> {
AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone())))
}
/// Add match predicate to route.
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn main() {
/// App::new().resource("/path", |r| {
/// r.route()
/// .filter(pred::Get())
/// .filter(pred::Header("content-type", "text/plain"))
/// .f(|req| HttpResponse::Ok())
/// })
/// # .finish();
/// # }
/// ```
pub fn filter<T: Predicate<S> + 'static>(&mut self, p: T) -> &mut Self {
self.preds.push(Box::new(p));
self
}
/// Set handler object. Usually call to this method is last call
/// during route configuration, so it does not return reference to self.
pub fn h<H: Handler<S>>(&mut self, handler: H) {
self.handler = InnerHandler::new(handler);
}
/// Set handler function. Usually call to this method is last call
/// during route configuration, so it does not return reference to self.
pub fn f<F, R>(&mut self, handler: F)
where
F: Fn(&HttpRequest<S>) -> R + 'static,
R: Responder + 'static,
{
self.handler = InnerHandler::new(handler);
}
/// Set async handler function.
pub fn a<H, R, F, E>(&mut self, handler: H)
where
H: Fn(&HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
{
self.handler = InnerHandler::async(handler);
}
/// Set handler function, use request extractor for parameters.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Path, Result};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: Path<Info>) -> Result<String> {
/// Ok(format!("Welcome {}!", info.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// }
/// ```
///
/// It is possible to use multiple extractors for one handler function.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// # use std::collections::HashMap;
/// use actix_web::{http, App, Json, Path, Query, Result};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(
/// path: Path<Info>, query: Query<HashMap<String, String>>, body: Json<Info>,
/// ) -> Result<String> {
/// Ok(format!("Welcome {}!", path.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with<T, F, R>(&mut self, handler: F)
where
F: WithFactory<T, S, R> + 'static,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{
self.h(handler.create());
}
/// Set handler function. Same as `.with()` but it allows to configure
/// extractor. Configuration closure accepts config objects as tuple.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Path, Result};
///
/// /// extract text data from request
/// fn index(body: String) -> Result<String> {
/// Ok(format!("Body {}!", body))
/// }
///
/// fn main() {
/// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::GET)
/// .with_config(index, |cfg| { // <- register handler
/// cfg.0.limit(4096); // <- limit size of the payload
/// })
/// });
/// }
/// ```
pub fn with_config<T, F, R, C>(&mut self, handler: F, cfg_f: C)
where
F: WithFactory<T, S, R>,
R: Responder + 'static,
T: FromRequest<S> + 'static,
C: FnOnce(&mut T::Config),
{
let mut cfg = <T::Config as Default>::default();
cfg_f(&mut cfg);
self.h(handler.create_with_config(cfg));
}
/// Set async handler function, use request extractor for parameters.
/// Also this method needs to be used if your handler function returns
/// `impl Future<>`
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Error, Path};
/// use futures::Future;
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: Path<Info>) -> Box<Future<Item = &'static str, Error = Error>> {
/// unimplemented!()
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with_async(index),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
where
F: WithAsyncFactory<T, S, R, I, E>,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
{
self.h(handler.create());
}
/// Set async handler function, use request extractor for parameters.
/// This method allows to configure extractor. Configuration closure
/// accepts config objects as tuple.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Error, Form};
/// use futures::Future;
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: Form<Info>) -> Box<Future<Item = &'static str, Error = Error>> {
/// unimplemented!()
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET)
/// .with_async_config(index, |cfg| {
/// cfg.0.limit(4096);
/// }),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with_async_config<T, F, R, I, E, C>(&mut self, handler: F, cfg: C)
where
F: WithAsyncFactory<T, S, R, I, E>,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
C: FnOnce(&mut T::Config),
{
let mut extractor_cfg = <T::Config as Default>::default();
cfg(&mut extractor_cfg);
self.h(handler.create_with_config(extractor_cfg));
}
}
/// `RouteHandler` wrapper. This struct is required because it needs to be
/// shared for resource level middlewares.
struct InnerHandler<S>(Rc<Box<RouteHandler<S>>>);
impl<S: 'static> InnerHandler<S> {
#[inline]
fn new<H: Handler<S>>(h: H) -> Self {
InnerHandler(Rc::new(Box::new(WrapHandler::new(h))))
}
#[inline]
fn async<H, R, F, E>(h: H) -> Self
where
H: Fn(&HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
{
InnerHandler(Rc::new(Box::new(AsyncHandler::new(h))))
}
#[inline]
pub fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
self.0.handle(req)
}
}
impl<S> Clone for InnerHandler<S> {
#[inline]
fn clone(&self) -> Self {
InnerHandler(Rc::clone(&self.0))
}
}
/// Compose resource level middlewares with route handler.
struct Compose<S: 'static> {
info: ComposeInfo<S>,
state: ComposeState<S>,
}
struct ComposeInfo<S: 'static> {
count: usize,
req: HttpRequest<S>,
mws: Rc<Vec<Box<Middleware<S>>>>,
handler: InnerHandler<S>,
}
enum ComposeState<S: 'static> {
Starting(StartMiddlewares<S>),
Handler(WaitingResponse<S>),
RunMiddlewares(RunMiddlewares<S>),
Finishing(FinishingMiddlewares<S>),
Completed(Response<S>),
}
impl<S: 'static> ComposeState<S> {
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
match *self {
ComposeState::Starting(ref mut state) => state.poll(info),
ComposeState::Handler(ref mut state) => state.poll(info),
ComposeState::RunMiddlewares(ref mut state) => state.poll(info),
ComposeState::Finishing(ref mut state) => state.poll(info),
ComposeState::Completed(_) => None,
}
}
}
impl<S: 'static> Compose<S> {
fn new(
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>, handler: InnerHandler<S>,
) -> Self {
let mut info = ComposeInfo {
count: 0,
req,
mws,
handler,
};
let state = StartMiddlewares::init(&mut info);
Compose { state, info }
}
}
impl<S> Future for Compose<S> {
type Item = HttpResponse;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
if let ComposeState::Completed(ref mut resp) = self.state {
let resp = resp.resp.take().unwrap();
return Ok(Async::Ready(resp));
}
if let Some(state) = self.state.poll(&mut self.info) {
self.state = state;
} else {
return Ok(Async::NotReady);
}
}
}
}
/// Middlewares start executor
struct StartMiddlewares<S> {
fut: Option<Fut>,
_s: PhantomData<S>,
}
type Fut = Box<Future<Item = Option<HttpResponse>, Error = Error>>;
impl<S: 'static> StartMiddlewares<S> {
fn init(info: &mut ComposeInfo<S>) -> ComposeState<S> {
let len = info.mws.len();
loop {
if info.count == len {
let reply = info.handler.handle(&info.req);
return WaitingResponse::init(info, reply);
} else {
let result = info.mws[info.count].start(&info.req);
match result {
Ok(MiddlewareStarted::Done) => info.count += 1,
Ok(MiddlewareStarted::Response(resp)) => {
return RunMiddlewares::init(info, resp);
}
Ok(MiddlewareStarted::Future(fut)) => {
return ComposeState::Starting(StartMiddlewares {
fut: Some(fut),
_s: PhantomData,
});
}
Err(err) => {
return RunMiddlewares::init(info, err.into());
}
}
}
}
}
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
let len = info.mws.len();
'outer: loop {
match self.fut.as_mut().unwrap().poll() {
Ok(Async::NotReady) => {
return None;
}
Ok(Async::Ready(resp)) => {
info.count += 1;
if let Some(resp) = resp {
return Some(RunMiddlewares::init(info, resp));
}
loop {
if info.count == len {
let reply = info.handler.handle(&info.req);
return Some(WaitingResponse::init(info, reply));
} else {
let result = info.mws[info.count].start(&info.req);
match result {
Ok(MiddlewareStarted::Done) => info.count += 1,
Ok(MiddlewareStarted::Response(resp)) => {
return Some(RunMiddlewares::init(info, resp));
}
Ok(MiddlewareStarted::Future(fut)) => {
self.fut = Some(fut);
continue 'outer;
}
Err(err) => {
return Some(RunMiddlewares::init(info, err.into()));
}
}
}
}
}
Err(err) => {
return Some(RunMiddlewares::init(info, err.into()));
}
}
}
}
}
type HandlerFuture = Future<Item = HttpResponse, Error = Error>;
// waiting for response
struct WaitingResponse<S> {
fut: Box<HandlerFuture>,
_s: PhantomData<S>,
}
impl<S: 'static> WaitingResponse<S> {
#[inline]
fn init(
info: &mut ComposeInfo<S>, reply: AsyncResult<HttpResponse>,
) -> ComposeState<S> {
match reply.into() {
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse {
fut,
_s: PhantomData,
}),
}
}
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
match self.fut.poll() {
Ok(Async::NotReady) => None,
Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)),
Err(err) => Some(RunMiddlewares::init(info, err.into())),
}
}
}
/// Middlewares response executor
struct RunMiddlewares<S> {
curr: usize,
fut: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
_s: PhantomData<S>,
}
impl<S: 'static> RunMiddlewares<S> {
fn init(info: &mut ComposeInfo<S>, mut resp: HttpResponse) -> ComposeState<S> {
let mut curr = 0;
let len = info.mws.len();
loop {
let state = info.mws[curr].response(&info.req, resp);
resp = match state {
Err(err) => {
info.count = curr + 1;
return FinishingMiddlewares::init(info, err.into());
}
Ok(MiddlewareResponse::Done(r)) => {
curr += 1;
if curr == len {
return FinishingMiddlewares::init(info, r);
} else {
r
}
}
Ok(MiddlewareResponse::Future(fut)) => {
return ComposeState::RunMiddlewares(RunMiddlewares {
curr,
fut: Some(fut),
_s: PhantomData,
});
}
};
}
}
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
let len = info.mws.len();
loop {
// poll latest fut
let mut resp = match self.fut.as_mut().unwrap().poll() {
Ok(Async::NotReady) => return None,
Ok(Async::Ready(resp)) => {
self.curr += 1;
resp
}
Err(err) => return Some(FinishingMiddlewares::init(info, err.into())),
};
loop {
if self.curr == len {
return Some(FinishingMiddlewares::init(info, resp));
} else {
let state = info.mws[self.curr].response(&info.req, resp);
match state {
Err(err) => {
return Some(FinishingMiddlewares::init(info, err.into()))
}
Ok(MiddlewareResponse::Done(r)) => {
self.curr += 1;
resp = r
}
Ok(MiddlewareResponse::Future(fut)) => {
self.fut = Some(fut);
break;
}
}
}
}
}
}
}
/// Middlewares start executor
struct FinishingMiddlewares<S> {
resp: Option<HttpResponse>,
fut: Option<Box<Future<Item = (), Error = Error>>>,
_s: PhantomData<S>,
}
impl<S: 'static> FinishingMiddlewares<S> {
fn init(info: &mut ComposeInfo<S>, resp: HttpResponse) -> ComposeState<S> {
if info.count == 0 {
Response::init(resp)
} else {
let mut state = FinishingMiddlewares {
resp: Some(resp),
fut: None,
_s: PhantomData,
};
if let Some(st) = state.poll(info) {
st
} else {
ComposeState::Finishing(state)
}
}
}
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
loop {
// poll latest fut
let not_ready = if let Some(ref mut fut) = self.fut {
match fut.poll() {
Ok(Async::NotReady) => true,
Ok(Async::Ready(())) => false,
Err(err) => {
error!("Middleware finish error: {}", err);
false
}
}
} else {
false
};
if not_ready {
return None;
}
self.fut = None;
if info.count == 0 {
return Some(Response::init(self.resp.take().unwrap()));
}
info.count -= 1;
let state = info.mws[info.count as usize]
.finish(&info.req, self.resp.as_ref().unwrap());
match state {
MiddlewareFinished::Done => {
if info.count == 0 {
return Some(Response::init(self.resp.take().unwrap()));
}
}
MiddlewareFinished::Future(fut) => {
self.fut = Some(fut);
}
}
}
}
}
struct Response<S> {
resp: Option<HttpResponse>,
_s: PhantomData<S>,
}
impl<S: 'static> Response<S> {
fn init(resp: HttpResponse) -> ComposeState<S> {
ComposeState::Completed(Response {
resp: Some(resp),
_s: PhantomData,
})
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,396 +0,0 @@
use std::time::Duration;
use std::{fmt, net};
use actix_net::server::ServerMessage;
use actix_net::service::{NewService, Service};
use futures::future::{err, ok, Either, FutureResult};
use futures::{Async, Future, Poll};
use tokio_reactor::Handle;
use tokio_tcp::TcpStream;
use tokio_timer::{sleep, Delay};
use super::channel::HttpProtocol;
use super::error::AcceptorError;
use super::handler::HttpHandler;
use super::settings::ServiceConfig;
use super::IoStream;
/// This trait indicates types that can create acceptor service for http server.
pub trait AcceptorServiceFactory: Send + Clone + 'static {
type Io: IoStream + Send;
type NewService: NewService<Request = TcpStream, Response = Self::Io>;
fn create(&self) -> Self::NewService;
}
impl<F, T> AcceptorServiceFactory for F
where
F: Fn() -> T + Send + Clone + 'static,
T::Response: IoStream + Send,
T: NewService<Request = TcpStream>,
T::InitError: fmt::Debug,
{
type Io = T::Response;
type NewService = T;
fn create(&self) -> T {
(self)()
}
}
#[derive(Clone)]
/// Default acceptor service convert `TcpStream` to a `tokio_tcp::TcpStream`
pub(crate) struct DefaultAcceptor;
impl AcceptorServiceFactory for DefaultAcceptor {
type Io = TcpStream;
type NewService = DefaultAcceptor;
fn create(&self) -> Self::NewService {
DefaultAcceptor
}
}
impl NewService for DefaultAcceptor {
type Request = TcpStream;
type Response = TcpStream;
type Error = ();
type InitError = ();
type Service = DefaultAcceptor;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self) -> Self::Future {
ok(DefaultAcceptor)
}
}
impl Service for DefaultAcceptor {
type Request = TcpStream;
type Response = TcpStream;
type Error = ();
type Future = FutureResult<Self::Response, Self::Error>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, req: Self::Request) -> Self::Future {
ok(req)
}
}
pub(crate) struct TcpAcceptor<T> {
inner: T,
}
impl<T, E> TcpAcceptor<T>
where
T: NewService<Request = TcpStream, Error = AcceptorError<E>>,
T::InitError: fmt::Debug,
{
pub(crate) fn new(inner: T) -> Self {
TcpAcceptor { inner }
}
}
impl<T, E> NewService for TcpAcceptor<T>
where
T: NewService<Request = TcpStream, Error = AcceptorError<E>>,
T::InitError: fmt::Debug,
{
type Request = net::TcpStream;
type Response = T::Response;
type Error = AcceptorError<E>;
type InitError = T::InitError;
type Service = TcpAcceptorService<T::Service>;
type Future = TcpAcceptorResponse<T>;
fn new_service(&self) -> Self::Future {
TcpAcceptorResponse {
fut: self.inner.new_service(),
}
}
}
pub(crate) struct TcpAcceptorResponse<T>
where
T: NewService<Request = TcpStream>,
T::InitError: fmt::Debug,
{
fut: T::Future,
}
impl<T> Future for TcpAcceptorResponse<T>
where
T: NewService<Request = TcpStream>,
T::InitError: fmt::Debug,
{
type Item = TcpAcceptorService<T::Service>;
type Error = T::InitError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.fut.poll() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(service)) => {
Ok(Async::Ready(TcpAcceptorService { inner: service }))
}
Err(e) => {
error!("Can not create accetor service: {:?}", e);
Err(e)
}
}
}
}
pub(crate) struct TcpAcceptorService<T> {
inner: T,
}
impl<T, E> Service for TcpAcceptorService<T>
where
T: Service<Request = TcpStream, Error = AcceptorError<E>>,
{
type Request = net::TcpStream;
type Response = T::Response;
type Error = AcceptorError<E>;
type Future = Either<T::Future, FutureResult<Self::Response, Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.inner.poll_ready()
}
fn call(&mut self, req: Self::Request) -> Self::Future {
let stream = TcpStream::from_std(req, &Handle::default()).map_err(|e| {
error!("Can not convert to an async tcp stream: {}", e);
AcceptorError::Io(e)
});
match stream {
Ok(stream) => Either::A(self.inner.call(stream)),
Err(e) => Either::B(err(e)),
}
}
}
#[doc(hidden)]
/// Acceptor timeout middleware
///
/// Applies timeout to request prcoessing.
pub struct AcceptorTimeout<T> {
inner: T,
timeout: Duration,
}
impl<T: NewService> AcceptorTimeout<T> {
/// Create new `AcceptorTimeout` instance. timeout is in milliseconds.
pub fn new(timeout: u64, inner: T) -> Self {
Self {
inner,
timeout: Duration::from_millis(timeout),
}
}
}
impl<T: NewService> NewService for AcceptorTimeout<T> {
type Request = T::Request;
type Response = T::Response;
type Error = AcceptorError<T::Error>;
type InitError = T::InitError;
type Service = AcceptorTimeoutService<T::Service>;
type Future = AcceptorTimeoutFut<T>;
fn new_service(&self) -> Self::Future {
AcceptorTimeoutFut {
fut: self.inner.new_service(),
timeout: self.timeout,
}
}
}
#[doc(hidden)]
pub struct AcceptorTimeoutFut<T: NewService> {
fut: T::Future,
timeout: Duration,
}
impl<T: NewService> Future for AcceptorTimeoutFut<T> {
type Item = AcceptorTimeoutService<T::Service>;
type Error = T::InitError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let inner = try_ready!(self.fut.poll());
Ok(Async::Ready(AcceptorTimeoutService {
inner,
timeout: self.timeout,
}))
}
}
#[doc(hidden)]
/// Acceptor timeout service
///
/// Applies timeout to request prcoessing.
pub struct AcceptorTimeoutService<T> {
inner: T,
timeout: Duration,
}
impl<T: Service> Service for AcceptorTimeoutService<T> {
type Request = T::Request;
type Response = T::Response;
type Error = AcceptorError<T::Error>;
type Future = AcceptorTimeoutResponse<T>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.inner.poll_ready().map_err(AcceptorError::Service)
}
fn call(&mut self, req: Self::Request) -> Self::Future {
AcceptorTimeoutResponse {
fut: self.inner.call(req),
sleep: sleep(self.timeout),
}
}
}
#[doc(hidden)]
pub struct AcceptorTimeoutResponse<T: Service> {
fut: T::Future,
sleep: Delay,
}
impl<T: Service> Future for AcceptorTimeoutResponse<T> {
type Item = T::Response;
type Error = AcceptorError<T::Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.fut.poll().map_err(AcceptorError::Service)? {
Async::NotReady => match self.sleep.poll() {
Err(_) => Err(AcceptorError::Timeout),
Ok(Async::Ready(_)) => Err(AcceptorError::Timeout),
Ok(Async::NotReady) => Ok(Async::NotReady),
},
Async::Ready(resp) => Ok(Async::Ready(resp)),
}
}
}
pub(crate) struct ServerMessageAcceptor<T, H: HttpHandler> {
inner: T,
settings: ServiceConfig<H>,
}
impl<T, H> ServerMessageAcceptor<T, H>
where
H: HttpHandler,
T: NewService<Request = net::TcpStream>,
{
pub(crate) fn new(settings: ServiceConfig<H>, inner: T) -> Self {
ServerMessageAcceptor { inner, settings }
}
}
impl<T, H> NewService for ServerMessageAcceptor<T, H>
where
H: HttpHandler,
T: NewService<Request = net::TcpStream>,
{
type Request = ServerMessage;
type Response = ();
type Error = T::Error;
type InitError = T::InitError;
type Service = ServerMessageAcceptorService<T::Service, H>;
type Future = ServerMessageAcceptorResponse<T, H>;
fn new_service(&self) -> Self::Future {
ServerMessageAcceptorResponse {
fut: self.inner.new_service(),
settings: self.settings.clone(),
}
}
}
pub(crate) struct ServerMessageAcceptorResponse<T, H>
where
H: HttpHandler,
T: NewService<Request = net::TcpStream>,
{
fut: T::Future,
settings: ServiceConfig<H>,
}
impl<T, H> Future for ServerMessageAcceptorResponse<T, H>
where
H: HttpHandler,
T: NewService<Request = net::TcpStream>,
{
type Item = ServerMessageAcceptorService<T::Service, H>;
type Error = T::InitError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.fut.poll()? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(service) => Ok(Async::Ready(ServerMessageAcceptorService {
inner: service,
settings: self.settings.clone(),
})),
}
}
}
pub(crate) struct ServerMessageAcceptorService<T, H: HttpHandler> {
inner: T,
settings: ServiceConfig<H>,
}
impl<T, H> Service for ServerMessageAcceptorService<T, H>
where
H: HttpHandler,
T: Service<Request = net::TcpStream>,
{
type Request = ServerMessage;
type Response = ();
type Error = T::Error;
type Future =
Either<ServerMessageAcceptorServiceFut<T>, FutureResult<(), Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.inner.poll_ready()
}
fn call(&mut self, req: Self::Request) -> Self::Future {
match req {
ServerMessage::Connect(stream) => {
Either::A(ServerMessageAcceptorServiceFut {
fut: self.inner.call(stream),
})
}
ServerMessage::Shutdown(_) => Either::B(ok(())),
ServerMessage::ForceShutdown => {
self.settings
.head()
.traverse(|proto: &mut HttpProtocol<TcpStream, H>| proto.shutdown());
Either::B(ok(()))
}
}
}
}
pub(crate) struct ServerMessageAcceptorServiceFut<T: Service> {
fut: T::Future,
}
impl<T> Future for ServerMessageAcceptorServiceFut<T>
where
T: Service,
{
type Item = ();
type Error = T::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.fut.poll()? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(_) => Ok(Async::Ready(())),
}
}
}

View File

@ -1,117 +0,0 @@
use std::{fmt, net};
use actix_net::either::Either;
use actix_net::server::{Server, ServiceFactory};
use actix_net::service::{NewService, NewServiceExt};
use super::acceptor::{
AcceptorServiceFactory, AcceptorTimeout, ServerMessageAcceptor, TcpAcceptor,
};
use super::error::AcceptorError;
use super::handler::IntoHttpHandler;
use super::service::HttpService;
use super::settings::{ServerSettings, ServiceConfig};
use super::KeepAlive;
pub(crate) trait ServiceProvider {
fn register(
&self, server: Server, lst: net::TcpListener, host: String,
addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64,
client_shutdown: u64,
) -> Server;
}
/// Utility type that builds complete http pipeline
pub(crate) struct HttpServiceBuilder<F, H, A>
where
F: Fn() -> H + Send + Clone,
{
factory: F,
acceptor: A,
}
impl<F, H, A> HttpServiceBuilder<F, H, A>
where
F: Fn() -> H + Send + Clone + 'static,
H: IntoHttpHandler,
A: AcceptorServiceFactory,
<A::NewService as NewService>::InitError: fmt::Debug,
{
/// Create http service builder
pub fn new(factory: F, acceptor: A) -> Self {
Self { factory, acceptor }
}
fn finish(
&self, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool,
client_timeout: u64, client_shutdown: u64,
) -> impl ServiceFactory {
let factory = self.factory.clone();
let acceptor = self.acceptor.clone();
move || {
let app = (factory)().into_handler();
let settings = ServiceConfig::new(
app,
keep_alive,
client_timeout,
client_shutdown,
ServerSettings::new(addr, &host, false),
);
if secure {
Either::B(ServerMessageAcceptor::new(
settings.clone(),
TcpAcceptor::new(AcceptorTimeout::new(
client_timeout,
acceptor.create(),
)).map_err(|_| ())
.map_init_err(|_| ())
.and_then(
HttpService::new(settings)
.map_init_err(|_| ())
.map_err(|_| ()),
),
))
} else {
Either::A(ServerMessageAcceptor::new(
settings.clone(),
TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service))
.map_err(|_| ())
.map_init_err(|_| ())
.and_then(
HttpService::new(settings)
.map_init_err(|_| ())
.map_err(|_| ()),
),
))
}
}
}
}
impl<F, H, A> ServiceProvider for HttpServiceBuilder<F, H, A>
where
F: Fn() -> H + Send + Clone + 'static,
A: AcceptorServiceFactory,
<A::NewService as NewService>::InitError: fmt::Debug,
H: IntoHttpHandler,
{
fn register(
&self, server: Server, lst: net::TcpListener, host: String,
addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64,
client_shutdown: u64,
) -> Server {
server.listen2(
"actix-web",
lst,
self.finish(
host,
addr,
keep_alive,
secure,
client_timeout,
client_shutdown,
),
)
}
}

View File

@ -1,436 +0,0 @@
use std::net::Shutdown;
use std::{io, mem, time};
use bytes::{Buf, BufMut, BytesMut};
use futures::{Async, Future, Poll};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_timer::Delay;
use super::error::HttpDispatchError;
use super::settings::ServiceConfig;
use super::{h1, h2, HttpHandler, IoStream};
use error::Error;
use http::StatusCode;
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
pub(crate) enum HttpProtocol<T: IoStream, H: HttpHandler + 'static> {
H1(h1::Http1Dispatcher<T, H>),
H2(h2::Http2<T, H>),
Unknown(ServiceConfig<H>, T, BytesMut),
None,
}
impl<T: IoStream, H: HttpHandler + 'static> HttpProtocol<T, H> {
pub(crate) fn shutdown(&mut self) {
match self {
HttpProtocol::H1(ref mut h1) => {
let io = h1.io();
let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0)));
let _ = IoStream::shutdown(io, Shutdown::Both);
}
HttpProtocol::H2(ref mut h2) => h2.shutdown(),
HttpProtocol::Unknown(_, io, _) => {
let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0)));
let _ = IoStream::shutdown(io, Shutdown::Both);
}
HttpProtocol::None => (),
}
}
}
enum ProtocolKind {
Http1,
Http2,
}
#[doc(hidden)]
pub struct HttpChannel<T, H>
where
T: IoStream,
H: HttpHandler + 'static,
{
node: Node<HttpProtocol<T, H>>,
node_reg: bool,
ka_timeout: Option<Delay>,
}
impl<T, H> HttpChannel<T, H>
where
T: IoStream,
H: HttpHandler + 'static,
{
pub(crate) fn new(settings: ServiceConfig<H>, io: T) -> HttpChannel<T, H> {
let ka_timeout = settings.client_timer();
HttpChannel {
ka_timeout,
node_reg: false,
node: Node::new(HttpProtocol::Unknown(
settings,
io,
BytesMut::with_capacity(8192),
)),
}
}
}
impl<T, H> Drop for HttpChannel<T, H>
where
T: IoStream,
H: HttpHandler + 'static,
{
fn drop(&mut self) {
self.node.remove();
}
}
impl<T, H> Future for HttpChannel<T, H>
where
T: IoStream,
H: HttpHandler + 'static,
{
type Item = ();
type Error = HttpDispatchError<Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
// keep-alive timer
if self.ka_timeout.is_some() {
match self.ka_timeout.as_mut().unwrap().poll() {
Ok(Async::Ready(_)) => {
trace!("Slow request timed out, close connection");
let proto = mem::replace(self.node.get_mut(), HttpProtocol::None);
if let HttpProtocol::Unknown(settings, io, buf) = proto {
*self.node.get_mut() =
HttpProtocol::H1(h1::Http1Dispatcher::for_error(
settings,
io,
StatusCode::REQUEST_TIMEOUT,
self.ka_timeout.take(),
buf,
));
return self.poll();
}
return Ok(Async::Ready(()));
}
Ok(Async::NotReady) => (),
Err(_) => panic!("Something is really wrong"),
}
}
if !self.node_reg {
self.node_reg = true;
let settings = match self.node.get_mut() {
HttpProtocol::H1(ref mut h1) => h1.settings().clone(),
HttpProtocol::H2(ref mut h2) => h2.settings().clone(),
HttpProtocol::Unknown(ref mut settings, _, _) => settings.clone(),
HttpProtocol::None => unreachable!(),
};
settings.head().insert(&mut self.node);
}
let mut is_eof = false;
let kind = match self.node.get_mut() {
HttpProtocol::H1(ref mut h1) => return h1.poll(),
HttpProtocol::H2(ref mut h2) => return h2.poll(),
HttpProtocol::Unknown(_, ref mut io, ref mut buf) => {
let mut err = None;
let mut disconnect = false;
match io.read_available(buf) {
Ok(Async::Ready((read_some, stream_closed))) => {
is_eof = stream_closed;
// Only disconnect if no data was read.
if is_eof && !read_some {
disconnect = true;
}
}
Err(e) => {
err = Some(e.into());
}
_ => (),
}
if disconnect {
debug!("Ignored premature client disconnection");
return Ok(Async::Ready(()));
} else if let Some(e) = err {
return Err(e);
}
if buf.len() >= 14 {
if buf[..14] == HTTP2_PREFACE[..] {
ProtocolKind::Http2
} else {
ProtocolKind::Http1
}
} else {
return Ok(Async::NotReady);
}
}
HttpProtocol::None => unreachable!(),
};
// upgrade to specific http protocol
let proto = mem::replace(self.node.get_mut(), HttpProtocol::None);
if let HttpProtocol::Unknown(settings, io, buf) = proto {
match kind {
ProtocolKind::Http1 => {
*self.node.get_mut() = HttpProtocol::H1(h1::Http1Dispatcher::new(
settings,
io,
buf,
is_eof,
self.ka_timeout.take(),
));
return self.poll();
}
ProtocolKind::Http2 => {
*self.node.get_mut() = HttpProtocol::H2(h2::Http2::new(
settings,
io,
buf.freeze(),
self.ka_timeout.take(),
));
return self.poll();
}
}
}
unreachable!()
}
}
#[doc(hidden)]
pub struct H1Channel<T, H>
where
T: IoStream,
H: HttpHandler + 'static,
{
node: Node<HttpProtocol<T, H>>,
node_reg: bool,
}
impl<T, H> H1Channel<T, H>
where
T: IoStream,
H: HttpHandler + 'static,
{
pub(crate) fn new(settings: ServiceConfig<H>, io: T) -> H1Channel<T, H> {
H1Channel {
node_reg: false,
node: Node::new(HttpProtocol::H1(h1::Http1Dispatcher::new(
settings,
io,
BytesMut::with_capacity(8192),
false,
None,
))),
}
}
}
impl<T, H> Drop for H1Channel<T, H>
where
T: IoStream,
H: HttpHandler + 'static,
{
fn drop(&mut self) {
self.node.remove();
}
}
impl<T, H> Future for H1Channel<T, H>
where
T: IoStream,
H: HttpHandler + 'static,
{
type Item = ();
type Error = HttpDispatchError<Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if !self.node_reg {
self.node_reg = true;
let settings = match self.node.get_mut() {
HttpProtocol::H1(ref mut h1) => h1.settings().clone(),
_ => unreachable!(),
};
settings.head().insert(&mut self.node);
}
match self.node.get_mut() {
HttpProtocol::H1(ref mut h1) => h1.poll(),
_ => unreachable!(),
}
}
}
pub(crate) struct Node<T> {
next: Option<*mut Node<T>>,
prev: Option<*mut Node<T>>,
element: T,
}
impl<T> Node<T> {
fn new(element: T) -> Self {
Node {
element,
next: None,
prev: None,
}
}
fn get_mut(&mut self) -> &mut T {
&mut self.element
}
fn insert<I>(&mut self, next_el: &mut Node<I>) {
let next: *mut Node<T> = next_el as *const _ as *mut _;
if let Some(next2) = self.next {
unsafe {
let n = next2.as_mut().unwrap();
n.prev = Some(next);
}
next_el.next = Some(next2 as *mut _);
}
self.next = Some(next);
unsafe {
let next: &mut Node<T> = &mut *next;
next.prev = Some(self as *mut _);
}
}
fn remove(&mut self) {
let next = self.next.take();
let prev = self.prev.take();
if let Some(prev) = prev {
unsafe {
prev.as_mut().unwrap().next = next;
}
}
if let Some(next) = next {
unsafe {
next.as_mut().unwrap().prev = prev;
}
}
}
}
impl Node<()> {
pub(crate) fn head() -> Self {
Node {
next: None,
prev: None,
element: (),
}
}
pub(crate) fn traverse<T, H, F: Fn(&mut HttpProtocol<T, H>)>(&self, f: F)
where
T: IoStream,
H: HttpHandler + 'static,
{
if let Some(n) = self.next.as_ref() {
unsafe {
let mut next: &mut Node<HttpProtocol<T, H>> =
&mut *(n.as_ref().unwrap() as *const _ as *mut _);
loop {
f(&mut next.element);
next = if let Some(n) = next.next.as_ref() {
&mut **n
} else {
return;
}
}
}
}
}
}
/// Wrapper for `AsyncRead + AsyncWrite` types
pub(crate) struct WrapperStream<T>
where
T: AsyncRead + AsyncWrite + 'static,
{
io: T,
}
impl<T> WrapperStream<T>
where
T: AsyncRead + AsyncWrite + 'static,
{
pub fn new(io: T) -> Self {
WrapperStream { io }
}
}
impl<T> IoStream for WrapperStream<T>
where
T: AsyncRead + AsyncWrite + 'static,
{
#[inline]
fn shutdown(&mut self, _: Shutdown) -> io::Result<()> {
Ok(())
}
#[inline]
fn set_nodelay(&mut self, _: bool) -> io::Result<()> {
Ok(())
}
#[inline]
fn set_linger(&mut self, _: Option<time::Duration>) -> io::Result<()> {
Ok(())
}
#[inline]
fn set_keepalive(&mut self, _: Option<time::Duration>) -> io::Result<()> {
Ok(())
}
}
impl<T> io::Read for WrapperStream<T>
where
T: AsyncRead + AsyncWrite + 'static,
{
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.io.read(buf)
}
}
impl<T> io::Write for WrapperStream<T>
where
T: AsyncRead + AsyncWrite + 'static,
{
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.io.write(buf)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
self.io.flush()
}
}
impl<T> AsyncRead for WrapperStream<T>
where
T: AsyncRead + AsyncWrite + 'static,
{
#[inline]
fn read_buf<B: BufMut>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
self.io.read_buf(buf)
}
}
impl<T> AsyncWrite for WrapperStream<T>
where
T: AsyncRead + AsyncWrite + 'static,
{
#[inline]
fn shutdown(&mut self) -> Poll<(), io::Error> {
self.io.shutdown()
}
#[inline]
fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
self.io.write_buf(buf)
}
}

View File

@ -4,7 +4,6 @@ use std::io;
use futures::{Async, Poll};
use http2;
use super::{helpers, HttpHandlerTask, Writer};
use error::{Error, ParseError};
use http::{StatusCode, Version};
@ -89,31 +88,3 @@ impl<E: Debug + Display> From<http2::Error> for HttpDispatchError<E> {
HttpDispatchError::Http2(err)
}
}
pub(crate) struct ServerError(Version, StatusCode);
impl ServerError {
pub fn err(ver: Version, status: StatusCode) -> Box<HttpHandlerTask> {
Box::new(ServerError(ver, status))
}
}
impl HttpHandlerTask for ServerError {
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
{
let bytes = io.buffer();
// Buffer should have sufficient capacity for status line
// and extra space
bytes.reserve(helpers::STATUS_LINE_BUF_SIZE + 1);
helpers::write_status_line(self.0, self.1.as_u16(), bytes);
}
// Convert Status Code to Reason.
let reason = self.1.canonical_reason().unwrap_or("");
io.buffer().extend_from_slice(reason.as_bytes());
// No response body.
io.buffer().extend_from_slice(b"\r\ncontent-length: 0\r\n");
// date header
io.set_date();
Ok(Async::Ready(true))
}
}

View File

@ -1,356 +0,0 @@
// #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use std::io::{self, Write};
use bytes::{BufMut, BytesMut};
use futures::{Async, Poll};
use tokio_io::AsyncWrite;
use super::helpers;
use super::output::{Output, ResponseInfo, ResponseLength};
use super::settings::ServiceConfig;
use super::Request;
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
use body::{Binary, Body};
use header::ContentEncoding;
use http::header::{
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use http::{Method, Version};
use httpresponse::HttpResponse;
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
bitflags! {
struct Flags: u8 {
const STARTED = 0b0000_0001;
const UPGRADE = 0b0000_0010;
const KEEPALIVE = 0b0000_0100;
const DISCONNECTED = 0b0000_1000;
}
}
pub(crate) struct H1Writer<T: AsyncWrite, H: 'static> {
flags: Flags,
stream: T,
written: u64,
headers_size: u32,
buffer: Output,
buffer_capacity: usize,
settings: ServiceConfig<H>,
}
impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
pub fn new(stream: T, settings: ServiceConfig<H>) -> H1Writer<T, H> {
H1Writer {
flags: Flags::KEEPALIVE,
written: 0,
headers_size: 0,
buffer: Output::Buffer(settings.get_bytes()),
buffer_capacity: 0,
stream,
settings,
}
}
pub fn get_mut(&mut self) -> &mut T {
&mut self.stream
}
pub fn reset(&mut self) {
self.written = 0;
self.flags = Flags::KEEPALIVE;
}
pub fn flushed(&mut self) -> bool {
self.buffer.is_empty()
}
pub fn disconnected(&mut self) {
self.flags.insert(Flags::DISCONNECTED);
}
pub fn upgrade(&self) -> bool {
self.flags.contains(Flags::UPGRADE)
}
pub fn keepalive(&self) -> bool {
self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE)
}
fn write_data(stream: &mut T, data: &[u8]) -> io::Result<usize> {
let mut written = 0;
while written < data.len() {
match stream.write(&data[written..]) {
Ok(0) => {
return Err(io::Error::new(io::ErrorKind::WriteZero, ""));
}
Ok(n) => {
written += n;
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
return Ok(written)
}
Err(err) => return Err(err),
}
}
Ok(written)
}
}
impl<T: AsyncWrite, H: 'static> Drop for H1Writer<T, H> {
fn drop(&mut self) {
if let Some(bytes) = self.buffer.take_option() {
self.settings.release_bytes(bytes);
}
}
}
impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
#[inline]
fn written(&self) -> u64 {
self.written
}
#[inline]
fn set_date(&mut self) {
self.settings.set_date(self.buffer.as_mut(), true)
}
#[inline]
fn buffer(&mut self) -> &mut BytesMut {
self.buffer.as_mut()
}
fn start(
&mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding,
) -> io::Result<WriterState> {
// prepare task
let mut info = ResponseInfo::new(req.inner.method == Method::HEAD);
self.buffer.for_server(&mut info, &req.inner, msg, encoding);
if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) {
self.flags = Flags::STARTED | Flags::KEEPALIVE;
} else {
self.flags = Flags::STARTED;
}
// Connection upgrade
let version = msg.version().unwrap_or_else(|| req.inner.version);
if msg.upgrade() {
self.flags.insert(Flags::UPGRADE);
msg.headers_mut()
.insert(CONNECTION, HeaderValue::from_static("upgrade"));
}
// keep-alive
else if self.flags.contains(Flags::KEEPALIVE) {
if version < Version::HTTP_11 {
msg.headers_mut()
.insert(CONNECTION, HeaderValue::from_static("keep-alive"));
}
} else if version >= Version::HTTP_11 {
msg.headers_mut()
.insert(CONNECTION, HeaderValue::from_static("close"));
}
let body = msg.replace_body(Body::Empty);
// render message
{
// output buffer
let mut buffer = self.buffer.as_mut();
let reason = msg.reason().as_bytes();
if let Body::Binary(ref bytes) = body {
buffer.reserve(
256 + msg.headers().len() * AVERAGE_HEADER_SIZE
+ bytes.len()
+ reason.len(),
);
} else {
buffer.reserve(
256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len(),
);
}
// status line
helpers::write_status_line(version, msg.status().as_u16(), &mut buffer);
buffer.extend_from_slice(reason);
// content length
match info.length {
ResponseLength::Chunked => {
buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n")
}
ResponseLength::Zero => {
buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n")
}
ResponseLength::Length(len) => {
helpers::write_content_length(len, &mut buffer)
}
ResponseLength::Length64(len) => {
buffer.extend_from_slice(b"\r\ncontent-length: ");
write!(buffer.writer(), "{}", len)?;
buffer.extend_from_slice(b"\r\n");
}
ResponseLength::None => buffer.extend_from_slice(b"\r\n"),
}
if let Some(ce) = info.content_encoding {
buffer.extend_from_slice(b"content-encoding: ");
buffer.extend_from_slice(ce.as_ref());
buffer.extend_from_slice(b"\r\n");
}
// write headers
let mut pos = 0;
let mut has_date = false;
let mut remaining = buffer.remaining_mut();
let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) };
for (key, value) in msg.headers() {
match *key {
TRANSFER_ENCODING => continue,
CONTENT_ENCODING => if encoding != ContentEncoding::Identity {
continue;
},
CONTENT_LENGTH => match info.length {
ResponseLength::None => (),
_ => continue,
},
DATE => {
has_date = true;
}
_ => (),
}
let v = value.as_ref();
let k = key.as_str().as_bytes();
let len = k.len() + v.len() + 4;
if len > remaining {
unsafe {
buffer.advance_mut(pos);
}
pos = 0;
buffer.reserve(len);
remaining = buffer.remaining_mut();
unsafe {
buf = &mut *(buffer.bytes_mut() as *mut _);
}
}
buf[pos..pos + k.len()].copy_from_slice(k);
pos += k.len();
buf[pos..pos + 2].copy_from_slice(b": ");
pos += 2;
buf[pos..pos + v.len()].copy_from_slice(v);
pos += v.len();
buf[pos..pos + 2].copy_from_slice(b"\r\n");
pos += 2;
remaining -= len;
}
unsafe {
buffer.advance_mut(pos);
}
// optimized date header, set_date writes \r\n
if !has_date {
self.settings.set_date(&mut buffer, true);
} else {
// msg eof
buffer.extend_from_slice(b"\r\n");
}
self.headers_size = buffer.len() as u32;
}
if let Body::Binary(bytes) = body {
self.written = bytes.len() as u64;
self.buffer.write(bytes.as_ref())?;
} else {
// capacity, makes sense only for streaming or actor
self.buffer_capacity = msg.write_buffer_capacity();
msg.replace_body(body);
}
Ok(WriterState::Done)
}
fn write(&mut self, payload: &Binary) -> io::Result<WriterState> {
self.written += payload.len() as u64;
if !self.flags.contains(Flags::DISCONNECTED) {
if self.flags.contains(Flags::STARTED) {
// shortcut for upgraded connection
if self.flags.contains(Flags::UPGRADE) {
if self.buffer.is_empty() {
let pl: &[u8] = payload.as_ref();
let n = match Self::write_data(&mut self.stream, pl) {
Err(err) => {
self.disconnected();
return Err(err);
}
Ok(val) => val,
};
if n < pl.len() {
self.buffer.write(&pl[n..])?;
return Ok(WriterState::Done);
}
} else {
self.buffer.write(payload.as_ref())?;
}
} else {
// TODO: add warning, write after EOF
self.buffer.write(payload.as_ref())?;
}
} else {
// could be response to EXCEPT header
self.buffer.write(payload.as_ref())?;
}
}
if self.buffer.len() > self.buffer_capacity {
Ok(WriterState::Pause)
} else {
Ok(WriterState::Done)
}
}
fn write_eof(&mut self) -> io::Result<WriterState> {
if !self.buffer.write_eof()? {
Err(io::Error::new(
io::ErrorKind::Other,
"Last payload item, but eof is not reached",
))
} else if self.buffer.len() > MAX_WRITE_BUFFER_SIZE {
Ok(WriterState::Pause)
} else {
Ok(WriterState::Done)
}
}
#[inline]
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> {
if self.flags.contains(Flags::DISCONNECTED) {
return Err(io::Error::new(io::ErrorKind::Other, "disconnected"));
}
if !self.buffer.is_empty() {
let written = {
match Self::write_data(&mut self.stream, self.buffer.as_ref().as_ref()) {
Err(err) => {
self.disconnected();
return Err(err);
}
Ok(val) => val,
}
};
let _ = self.buffer.split_to(written);
if shutdown && !self.buffer.is_empty()
|| (self.buffer.len() > self.buffer_capacity)
{
return Ok(Async::NotReady);
}
}
if shutdown {
self.stream.poll_flush()?;
self.stream.shutdown()
} else {
Ok(self.stream.poll_flush()?)
}
}
}

View File

@ -1,453 +0,0 @@
use std::collections::VecDeque;
use std::io::{Read, Write};
use std::net::SocketAddr;
use std::rc::Rc;
use std::time::Instant;
use std::{cmp, io, mem};
use bytes::{Buf, Bytes};
use futures::{Async, Future, Poll, Stream};
use http2::server::{self, Connection, Handshake, SendResponse};
use http2::{Reason, RecvStream};
use modhttp::request::Parts;
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_timer::Delay;
use error::{Error, PayloadError};
use extensions::Extensions;
use http::{StatusCode, Version};
use payload::{Payload, PayloadStatus, PayloadWriter};
use uri::Url;
use super::error::{HttpDispatchError, ServerError};
use super::h2writer::H2Writer;
use super::input::PayloadType;
use super::settings::ServiceConfig;
use super::{HttpHandler, HttpHandlerTask, IoStream, Writer};
bitflags! {
struct Flags: u8 {
const DISCONNECTED = 0b0000_0010;
}
}
/// HTTP/2 Transport
pub(crate) struct Http2<T, H>
where
T: AsyncRead + AsyncWrite + 'static,
H: HttpHandler + 'static,
{
flags: Flags,
settings: ServiceConfig<H>,
addr: Option<SocketAddr>,
state: State<IoWrapper<T>>,
tasks: VecDeque<Entry<H>>,
keepalive_timer: Option<Delay>,
extensions: Option<Rc<Extensions>>,
}
enum State<T: AsyncRead + AsyncWrite> {
Handshake(Handshake<T, Bytes>),
Connection(Connection<T, Bytes>),
Empty,
}
impl<T, H> Http2<T, H>
where
T: IoStream + 'static,
H: HttpHandler + 'static,
{
pub fn new(
settings: ServiceConfig<H>, io: T, buf: Bytes, keepalive_timer: Option<Delay>,
) -> Self {
let addr = io.peer_addr();
let extensions = io.extensions();
Http2 {
flags: Flags::empty(),
tasks: VecDeque::new(),
state: State::Handshake(server::handshake(IoWrapper {
unread: if buf.is_empty() { None } else { Some(buf) },
inner: io,
})),
addr,
settings,
extensions,
keepalive_timer,
}
}
pub(crate) fn shutdown(&mut self) {
self.state = State::Empty;
self.tasks.clear();
self.keepalive_timer.take();
}
pub fn settings(&self) -> &ServiceConfig<H> {
&self.settings
}
pub fn poll(&mut self) -> Poll<(), HttpDispatchError<Error>> {
// server
if let State::Connection(ref mut conn) = self.state {
// keep-alive timer
if let Some(ref mut timeout) = self.keepalive_timer {
match timeout.poll() {
Ok(Async::Ready(_)) => {
trace!("Keep-alive timeout, close connection");
return Ok(Async::Ready(()));
}
Ok(Async::NotReady) => (),
Err(_) => unreachable!(),
}
}
loop {
let mut not_ready = true;
let disconnected = self.flags.contains(Flags::DISCONNECTED);
// check in-flight connections
for item in &mut self.tasks {
// read payload
if !disconnected {
item.poll_payload();
}
if !item.flags.contains(EntryFlags::EOF) {
if disconnected {
item.flags.insert(EntryFlags::EOF);
} else {
let retry = item.payload.need_read() == PayloadStatus::Read;
loop {
match item.task.poll_io(&mut item.stream) {
Ok(Async::Ready(ready)) => {
if ready {
item.flags.insert(
EntryFlags::EOF | EntryFlags::FINISHED,
);
} else {
item.flags.insert(EntryFlags::EOF);
}
not_ready = false;
}
Ok(Async::NotReady) => {
if item.payload.need_read()
== PayloadStatus::Read
&& !retry
{
continue;
}
}
Err(err) => {
error!("Unhandled error: {}", err);
item.flags.insert(
EntryFlags::EOF
| EntryFlags::ERROR
| EntryFlags::WRITE_DONE,
);
item.stream.reset(Reason::INTERNAL_ERROR);
}
}
break;
}
}
}
if item.flags.contains(EntryFlags::EOF)
&& !item.flags.contains(EntryFlags::FINISHED)
{
match item.task.poll_completed() {
Ok(Async::NotReady) => (),
Ok(Async::Ready(_)) => {
item.flags.insert(
EntryFlags::FINISHED | EntryFlags::WRITE_DONE,
);
}
Err(err) => {
item.flags.insert(
EntryFlags::ERROR
| EntryFlags::WRITE_DONE
| EntryFlags::FINISHED,
);
error!("Unhandled error: {}", err);
}
}
}
if item.flags.contains(EntryFlags::FINISHED)
&& !item.flags.contains(EntryFlags::WRITE_DONE)
&& !disconnected
{
match item.stream.poll_completed(false) {
Ok(Async::NotReady) => (),
Ok(Async::Ready(_)) => {
not_ready = false;
item.flags.insert(EntryFlags::WRITE_DONE);
}
Err(_) => {
item.flags.insert(EntryFlags::ERROR);
}
}
}
}
// cleanup finished tasks
while !self.tasks.is_empty() {
if self.tasks[0].flags.contains(EntryFlags::FINISHED)
&& self.tasks[0].flags.contains(EntryFlags::WRITE_DONE)
|| self.tasks[0].flags.contains(EntryFlags::ERROR)
{
self.tasks.pop_front();
} else {
break;
}
}
// get request
if !self.flags.contains(Flags::DISCONNECTED) {
match conn.poll() {
Ok(Async::Ready(None)) => {
not_ready = false;
self.flags.insert(Flags::DISCONNECTED);
for entry in &mut self.tasks {
entry.task.disconnected()
}
}
Ok(Async::Ready(Some((req, resp)))) => {
not_ready = false;
let (parts, body) = req.into_parts();
// stop keepalive timer
self.keepalive_timer.take();
self.tasks.push_back(Entry::new(
parts,
body,
resp,
self.addr,
self.settings.clone(),
self.extensions.clone(),
));
}
Ok(Async::NotReady) => {
// start keep-alive timer
if self.tasks.is_empty() {
if self.settings.keep_alive_enabled() {
if self.keepalive_timer.is_none() {
if let Some(ka) = self.settings.keep_alive() {
trace!("Start keep-alive timer");
let mut timeout =
Delay::new(Instant::now() + ka);
// register timeout
let _ = timeout.poll();
self.keepalive_timer = Some(timeout);
}
}
} else {
// keep-alive disable, drop connection
return conn.poll_close().map_err(|e| e.into());
}
} else {
// keep-alive unset, rely on operating system
return Ok(Async::NotReady);
}
}
Err(err) => {
trace!("Connection error: {}", err);
self.flags.insert(Flags::DISCONNECTED);
for entry in &mut self.tasks {
entry.task.disconnected()
}
self.keepalive_timer.take();
}
}
}
if not_ready {
if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED)
{
return conn.poll_close().map_err(|e| e.into());
} else {
return Ok(Async::NotReady);
}
}
}
}
// handshake
self.state = if let State::Handshake(ref mut handshake) = self.state {
match handshake.poll() {
Ok(Async::Ready(conn)) => State::Connection(conn),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => {
trace!("Error handling connection: {}", err);
return Err(err.into());
}
}
} else {
mem::replace(&mut self.state, State::Empty)
};
self.poll()
}
}
bitflags! {
struct EntryFlags: u8 {
const EOF = 0b0000_0001;
const REOF = 0b0000_0010;
const ERROR = 0b0000_0100;
const FINISHED = 0b0000_1000;
const WRITE_DONE = 0b0001_0000;
}
}
enum EntryPipe<H: HttpHandler> {
Task(H::Task),
Error(Box<HttpHandlerTask>),
}
impl<H: HttpHandler> EntryPipe<H> {
fn disconnected(&mut self) {
match *self {
EntryPipe::Task(ref mut task) => task.disconnected(),
EntryPipe::Error(ref mut task) => task.disconnected(),
}
}
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
match *self {
EntryPipe::Task(ref mut task) => task.poll_io(io),
EntryPipe::Error(ref mut task) => task.poll_io(io),
}
}
fn poll_completed(&mut self) -> Poll<(), Error> {
match *self {
EntryPipe::Task(ref mut task) => task.poll_completed(),
EntryPipe::Error(ref mut task) => task.poll_completed(),
}
}
}
struct Entry<H: HttpHandler + 'static> {
task: EntryPipe<H>,
payload: PayloadType,
recv: RecvStream,
stream: H2Writer<H>,
flags: EntryFlags,
}
impl<H: HttpHandler + 'static> Entry<H> {
fn new(
parts: Parts, recv: RecvStream, resp: SendResponse<Bytes>,
addr: Option<SocketAddr>, settings: ServiceConfig<H>,
extensions: Option<Rc<Extensions>>,
) -> Entry<H>
where
H: HttpHandler + 'static,
{
// Payload and Content-Encoding
let (psender, payload) = Payload::new(false);
let mut msg = settings.get_request();
{
let inner = msg.inner_mut();
inner.url = Url::new(parts.uri);
inner.method = parts.method;
inner.version = parts.version;
inner.headers = parts.headers;
inner.stream_extensions = extensions;
*inner.payload.borrow_mut() = Some(payload);
inner.addr = addr;
}
// Payload sender
let psender = PayloadType::new(msg.headers(), psender);
// start request processing
let task = match settings.handler().handle(msg) {
Ok(task) => EntryPipe::Task(task),
Err(_) => EntryPipe::Error(ServerError::err(
Version::HTTP_2,
StatusCode::NOT_FOUND,
)),
};
Entry {
task,
recv,
payload: psender,
stream: H2Writer::new(resp, settings),
flags: EntryFlags::empty(),
}
}
fn poll_payload(&mut self) {
while !self.flags.contains(EntryFlags::REOF)
&& self.payload.need_read() == PayloadStatus::Read
{
match self.recv.poll() {
Ok(Async::Ready(Some(chunk))) => {
let l = chunk.len();
self.payload.feed_data(chunk);
if let Err(err) = self.recv.release_capacity().release_capacity(l) {
self.payload.set_error(PayloadError::Http2(err));
break;
}
}
Ok(Async::Ready(None)) => {
self.flags.insert(EntryFlags::REOF);
self.payload.feed_eof();
}
Ok(Async::NotReady) => break,
Err(err) => {
self.payload.set_error(PayloadError::Http2(err));
break;
}
}
}
}
}
struct IoWrapper<T> {
unread: Option<Bytes>,
inner: T,
}
impl<T: Read> Read for IoWrapper<T> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if let Some(mut bytes) = self.unread.take() {
let size = cmp::min(buf.len(), bytes.len());
buf[..size].copy_from_slice(&bytes[..size]);
if bytes.len() > size {
bytes.split_to(size);
self.unread = Some(bytes);
}
Ok(size)
} else {
self.inner.read(buf)
}
}
}
impl<T: Write> Write for IoWrapper<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl<T: AsyncRead + 'static> AsyncRead for IoWrapper<T> {
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
self.inner.prepare_uninitialized_buffer(buf)
}
}
impl<T: AsyncWrite + 'static> AsyncWrite for IoWrapper<T> {
fn shutdown(&mut self) -> Poll<(), io::Error> {
self.inner.shutdown()
}
fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
self.inner.write_buf(buf)
}
}

View File

@ -1,259 +0,0 @@
#![cfg_attr(
feature = "cargo-clippy",
allow(clippy::redundant_field_names)
)]
use std::{cmp, io};
use bytes::{Bytes, BytesMut};
use futures::{Async, Poll};
use http2::server::SendResponse;
use http2::{Reason, SendStream};
use modhttp::Response;
use super::helpers;
use super::message::Request;
use super::output::{Output, ResponseInfo, ResponseLength};
use super::settings::ServiceConfig;
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
use body::{Binary, Body};
use header::ContentEncoding;
use http::header::{
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use http::{HttpTryFrom, Method, Version};
use httpresponse::HttpResponse;
const CHUNK_SIZE: usize = 16_384;
bitflags! {
struct Flags: u8 {
const STARTED = 0b0000_0001;
const DISCONNECTED = 0b0000_0010;
const EOF = 0b0000_0100;
const RESERVED = 0b0000_1000;
}
}
pub(crate) struct H2Writer<H: 'static> {
respond: SendResponse<Bytes>,
stream: Option<SendStream<Bytes>>,
flags: Flags,
written: u64,
buffer: Output,
buffer_capacity: usize,
settings: ServiceConfig<H>,
}
impl<H: 'static> H2Writer<H> {
pub fn new(respond: SendResponse<Bytes>, settings: ServiceConfig<H>) -> H2Writer<H> {
H2Writer {
stream: None,
flags: Flags::empty(),
written: 0,
buffer: Output::Buffer(settings.get_bytes()),
buffer_capacity: 0,
respond,
settings,
}
}
pub fn reset(&mut self, reason: Reason) {
if let Some(mut stream) = self.stream.take() {
stream.send_reset(reason)
}
}
}
impl<H: 'static> Drop for H2Writer<H> {
fn drop(&mut self) {
self.settings.release_bytes(self.buffer.take());
}
}
impl<H: 'static> Writer for H2Writer<H> {
fn written(&self) -> u64 {
self.written
}
#[inline]
fn set_date(&mut self) {
self.settings.set_date(self.buffer.as_mut(), true)
}
#[inline]
fn buffer(&mut self) -> &mut BytesMut {
self.buffer.as_mut()
}
fn start(
&mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding,
) -> io::Result<WriterState> {
// prepare response
self.flags.insert(Flags::STARTED);
let mut info = ResponseInfo::new(req.inner.method == Method::HEAD);
self.buffer.for_server(&mut info, &req.inner, msg, encoding);
let mut has_date = false;
let mut resp = Response::new(());
*resp.status_mut() = msg.status();
*resp.version_mut() = Version::HTTP_2;
for (key, value) in msg.headers().iter() {
match *key {
// http2 specific
CONNECTION | TRANSFER_ENCODING => continue,
CONTENT_ENCODING => if encoding != ContentEncoding::Identity {
continue;
},
CONTENT_LENGTH => match info.length {
ResponseLength::None => (),
_ => continue,
},
DATE => has_date = true,
_ => (),
}
resp.headers_mut().append(key, value.clone());
}
// set date header
if !has_date {
let mut bytes = BytesMut::with_capacity(29);
self.settings.set_date(&mut bytes, false);
resp.headers_mut()
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap());
}
// content length
match info.length {
ResponseLength::Zero => {
resp.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
self.flags.insert(Flags::EOF);
}
ResponseLength::Length(len) => {
let mut val = BytesMut::new();
helpers::convert_usize(len, &mut val);
let l = val.len();
resp.headers_mut().insert(
CONTENT_LENGTH,
HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(),
);
}
ResponseLength::Length64(len) => {
let l = format!("{}", len);
resp.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap());
}
_ => (),
}
if let Some(ce) = info.content_encoding {
resp.headers_mut()
.insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap());
}
trace!("Response: {:?}", resp);
match self
.respond
.send_response(resp, self.flags.contains(Flags::EOF))
{
Ok(stream) => self.stream = Some(stream),
Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")),
}
let body = msg.replace_body(Body::Empty);
if let Body::Binary(bytes) = body {
if bytes.is_empty() {
Ok(WriterState::Done)
} else {
self.flags.insert(Flags::EOF);
self.buffer.write(bytes.as_ref())?;
if let Some(ref mut stream) = self.stream {
self.flags.insert(Flags::RESERVED);
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
}
Ok(WriterState::Pause)
}
} else {
msg.replace_body(body);
self.buffer_capacity = msg.write_buffer_capacity();
Ok(WriterState::Done)
}
}
fn write(&mut self, payload: &Binary) -> io::Result<WriterState> {
if !self.flags.contains(Flags::DISCONNECTED) {
if self.flags.contains(Flags::STARTED) {
// TODO: add warning, write after EOF
self.buffer.write(payload.as_ref())?;
} else {
// might be response for EXCEPT
error!("Not supported");
}
}
if self.buffer.len() > self.buffer_capacity {
Ok(WriterState::Pause)
} else {
Ok(WriterState::Done)
}
}
fn write_eof(&mut self) -> io::Result<WriterState> {
self.flags.insert(Flags::EOF);
if !self.buffer.write_eof()? {
Err(io::Error::new(
io::ErrorKind::Other,
"Last payload item, but eof is not reached",
))
} else if self.buffer.len() > MAX_WRITE_BUFFER_SIZE {
Ok(WriterState::Pause)
} else {
Ok(WriterState::Done)
}
}
fn poll_completed(&mut self, _shutdown: bool) -> Poll<(), io::Error> {
if !self.flags.contains(Flags::STARTED) {
return Ok(Async::NotReady);
}
if let Some(ref mut stream) = self.stream {
// reserve capacity
if !self.flags.contains(Flags::RESERVED) && !self.buffer.is_empty() {
self.flags.insert(Flags::RESERVED);
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
}
loop {
match stream.poll_capacity() {
Ok(Async::NotReady) => return Ok(Async::NotReady),
Ok(Async::Ready(None)) => return Ok(Async::Ready(())),
Ok(Async::Ready(Some(cap))) => {
let len = self.buffer.len();
let bytes = self.buffer.split_to(cmp::min(cap, len));
let eof =
self.buffer.is_empty() && self.flags.contains(Flags::EOF);
self.written += bytes.len() as u64;
if let Err(e) = stream.send_data(bytes.freeze(), eof) {
return Err(io::Error::new(io::ErrorKind::Other, e));
} else if !self.buffer.is_empty() {
let cap = cmp::min(self.buffer.len(), CHUNK_SIZE);
stream.reserve_capacity(cap);
} else {
if eof {
stream.reserve_capacity(0);
continue;
}
self.flags.remove(Flags::RESERVED);
return Ok(Async::Ready(()));
}
}
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
}
}
}
Ok(Async::Ready(()))
}
}

View File

@ -1,208 +0,0 @@
use futures::{Async, Future, Poll};
use super::message::Request;
use super::Writer;
use error::Error;
/// Low level http request handler
#[allow(unused_variables)]
pub trait HttpHandler: 'static {
/// Request handling task
type Task: HttpHandlerTask;
/// Handle request
fn handle(&self, req: Request) -> Result<Self::Task, Request>;
}
impl HttpHandler for Box<HttpHandler<Task = Box<HttpHandlerTask>>> {
type Task = Box<HttpHandlerTask>;
fn handle(&self, req: Request) -> Result<Box<HttpHandlerTask>, Request> {
self.as_ref().handle(req)
}
}
/// Low level http request handler
pub trait HttpHandlerTask {
/// Poll task, this method is used before or after *io* object is available
fn poll_completed(&mut self) -> Poll<(), Error> {
Ok(Async::Ready(()))
}
/// Poll task when *io* object is available
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error>;
/// Connection is disconnected
fn disconnected(&mut self) {}
}
impl HttpHandlerTask for Box<HttpHandlerTask> {
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
self.as_mut().poll_io(io)
}
}
pub(super) struct HttpHandlerTaskFut<T: HttpHandlerTask> {
task: T,
}
impl<T: HttpHandlerTask> HttpHandlerTaskFut<T> {
pub(crate) fn new(task: T) -> Self {
Self { task }
}
}
impl<T: HttpHandlerTask> Future for HttpHandlerTaskFut<T> {
type Item = ();
type Error = ();
fn poll(&mut self) -> Poll<(), ()> {
self.task.poll_completed().map_err(|_| ())
}
}
/// Conversion helper trait
pub trait IntoHttpHandler {
/// The associated type which is result of conversion.
type Handler: HttpHandler;
/// Convert into `HttpHandler` object.
fn into_handler(self) -> Self::Handler;
}
impl<T: HttpHandler> IntoHttpHandler for T {
type Handler = T;
fn into_handler(self) -> Self::Handler {
self
}
}
impl<T: IntoHttpHandler> IntoHttpHandler for Vec<T> {
type Handler = VecHttpHandler<T::Handler>;
fn into_handler(self) -> Self::Handler {
VecHttpHandler(self.into_iter().map(|item| item.into_handler()).collect())
}
}
#[doc(hidden)]
pub struct VecHttpHandler<H: HttpHandler>(Vec<H>);
impl<H: HttpHandler> HttpHandler for VecHttpHandler<H> {
type Task = H::Task;
fn handle(&self, mut req: Request) -> Result<Self::Task, Request> {
for h in &self.0 {
req = match h.handle(req) {
Ok(task) => return Ok(task),
Err(e) => e,
};
}
Err(req)
}
}
macro_rules! http_handler ({$EN:ident, $(($n:tt, $T:ident)),+} => {
impl<$($T: HttpHandler,)+> HttpHandler for ($($T,)+) {
type Task = $EN<$($T,)+>;
fn handle(&self, mut req: Request) -> Result<Self::Task, Request> {
$(
req = match self.$n.handle(req) {
Ok(task) => return Ok($EN::$T(task)),
Err(e) => e,
};
)+
Err(req)
}
}
#[doc(hidden)]
pub enum $EN<$($T: HttpHandler,)+> {
$($T ($T::Task),)+
}
impl<$($T: HttpHandler,)+> HttpHandlerTask for $EN<$($T,)+>
{
fn poll_completed(&mut self) -> Poll<(), Error> {
match self {
$($EN :: $T(ref mut task) => task.poll_completed(),)+
}
}
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
match self {
$($EN::$T(ref mut task) => task.poll_io(io),)+
}
}
/// Connection is disconnected
fn disconnected(&mut self) {
match self {
$($EN::$T(ref mut task) => task.disconnected(),)+
}
}
}
});
http_handler!(HttpHandlerTask1, (0, A));
http_handler!(HttpHandlerTask2, (0, A), (1, B));
http_handler!(HttpHandlerTask3, (0, A), (1, B), (2, C));
http_handler!(HttpHandlerTask4, (0, A), (1, B), (2, C), (3, D));
http_handler!(HttpHandlerTask5, (0, A), (1, B), (2, C), (3, D), (4, E));
http_handler!(
HttpHandlerTask6,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E),
(5, F)
);
http_handler!(
HttpHandlerTask7,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E),
(5, F),
(6, G)
);
http_handler!(
HttpHandlerTask8,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E),
(5, F),
(6, G),
(7, H)
);
http_handler!(
HttpHandlerTask9,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E),
(5, F),
(6, G),
(7, H),
(8, I)
);
http_handler!(
HttpHandlerTask10,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E),
(5, F),
(6, G),
(7, H),
(8, I),
(9, J)
);

View File

@ -1,584 +0,0 @@
use std::{fmt, io, mem, net};
use actix::{Addr, System};
use actix_net::server::Server;
use actix_net::service::NewService;
use actix_net::ssl;
use net2::TcpBuilder;
use num_cpus;
#[cfg(feature = "tls")]
use native_tls::TlsAcceptor;
#[cfg(any(feature = "alpn", feature = "ssl"))]
use openssl::ssl::SslAcceptorBuilder;
#[cfg(feature = "rust-tls")]
use rustls::ServerConfig;
use super::acceptor::{AcceptorServiceFactory, DefaultAcceptor};
use super::builder::{HttpServiceBuilder, ServiceProvider};
use super::{IntoHttpHandler, KeepAlive};
struct Socket {
scheme: &'static str,
lst: net::TcpListener,
addr: net::SocketAddr,
handler: Box<ServiceProvider>,
}
/// An HTTP Server
///
/// By default it serves HTTP2 when HTTPs is enabled,
/// in order to change it, use `ServerFlags` that can be provided
/// to acceptor service.
pub struct HttpServer<H, F>
where
H: IntoHttpHandler + 'static,
F: Fn() -> H + Send + Clone,
{
pub(super) factory: F,
pub(super) host: Option<String>,
pub(super) keep_alive: KeepAlive,
pub(super) client_timeout: u64,
pub(super) client_shutdown: u64,
backlog: i32,
threads: usize,
exit: bool,
shutdown_timeout: u16,
no_http2: bool,
no_signals: bool,
maxconn: usize,
maxconnrate: usize,
sockets: Vec<Socket>,
}
impl<H, F> HttpServer<H, F>
where
H: IntoHttpHandler + 'static,
F: Fn() -> H + Send + Clone + 'static,
{
/// Create new http server with application factory
pub fn new(factory: F) -> HttpServer<H, F> {
HttpServer {
factory,
threads: num_cpus::get(),
host: None,
backlog: 2048,
keep_alive: KeepAlive::Timeout(5),
shutdown_timeout: 30,
exit: false,
no_http2: false,
no_signals: false,
maxconn: 25_600,
maxconnrate: 256,
client_timeout: 5000,
client_shutdown: 5000,
sockets: Vec::new(),
}
}
/// Set number of workers to start.
///
/// By default http server uses number of available logical cpu as threads
/// count.
pub fn workers(mut self, num: usize) -> Self {
self.threads = num;
self
}
/// Set the maximum number of pending connections.
///
/// This refers to the number of clients that can be waiting to be served.
/// Exceeding this number results in the client getting an error when
/// attempting to connect. It should only affect servers under significant
/// load.
///
/// Generally set in the 64-2048 range. Default value is 2048.
///
/// This method should be called before `bind()` method call.
pub fn backlog(mut self, num: i32) -> Self {
self.backlog = num;
self
}
/// Sets the maximum per-worker number of concurrent connections.
///
/// All socket listeners will stop accepting connections when this limit is reached
/// for each worker.
///
/// By default max connections is set to a 25k.
pub fn maxconn(mut self, num: usize) -> Self {
self.maxconn = num;
self
}
/// Sets the maximum per-worker concurrent connection establish process.
///
/// All listeners will stop accepting connections when this limit is reached. It
/// can be used to limit the global SSL CPU usage.
///
/// By default max connections is set to a 256.
pub fn maxconnrate(mut self, num: usize) -> Self {
self.maxconnrate = num;
self
}
/// Set server keep-alive setting.
///
/// By default keep alive is set to a 5 seconds.
pub fn keep_alive<T: Into<KeepAlive>>(mut self, val: T) -> Self {
self.keep_alive = val.into();
self
}
/// Set server client timeout in milliseconds for first request.
///
/// Defines a timeout for reading client request header. If a client does not transmit
/// the entire set headers within this time, the request is terminated with
/// the 408 (Request Time-out) error.
///
/// To disable timeout set value to 0.
///
/// By default client timeout is set to 5000 milliseconds.
pub fn client_timeout(mut self, val: u64) -> Self {
self.client_timeout = val;
self
}
/// Set server connection shutdown timeout in milliseconds.
///
/// Defines a timeout for shutdown connection. If a shutdown procedure does not complete
/// within this time, the request is dropped.
///
/// To disable timeout set value to 0.
///
/// By default client timeout is set to 5000 milliseconds.
pub fn client_shutdown(mut self, val: u64) -> Self {
self.client_shutdown = val;
self
}
/// Set server host name.
///
/// Host name is used by application router aa a hostname for url
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
/// html#method.host) documentation for more information.
pub fn server_hostname(mut self, val: String) -> Self {
self.host = Some(val);
self
}
/// Stop actix system.
///
/// `SystemExit` message stops currently running system.
pub fn system_exit(mut self) -> Self {
self.exit = true;
self
}
/// Disable signal handling
pub fn disable_signals(mut self) -> Self {
self.no_signals = true;
self
}
/// Timeout for graceful workers shutdown.
///
/// After receiving a stop signal, workers have this much time to finish
/// serving requests. Workers still alive after the timeout are force
/// dropped.
///
/// By default shutdown timeout sets to 30 seconds.
pub fn shutdown_timeout(mut self, sec: u16) -> Self {
self.shutdown_timeout = sec;
self
}
/// Disable `HTTP/2` support
// #[doc(hidden)]
// #[deprecated(
// since = "0.7.4",
// note = "please use acceptor service with proper ServerFlags parama"
// )]
pub fn no_http2(mut self) -> Self {
self.no_http2 = true;
self
}
/// Get addresses of bound sockets.
pub fn addrs(&self) -> Vec<net::SocketAddr> {
self.sockets.iter().map(|s| s.addr).collect()
}
/// Get addresses of bound sockets and the scheme for it.
///
/// This is useful when the server is bound from different sources
/// with some sockets listening on http and some listening on https
/// and the user should be presented with an enumeration of which
/// socket requires which protocol.
pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> {
self.sockets.iter().map(|s| (s.addr, s.scheme)).collect()
}
/// Use listener for accepting incoming connection requests
///
/// HttpServer does not change any configuration for TcpListener,
/// it needs to be configured before passing it to listen() method.
pub fn listen(mut self, lst: net::TcpListener) -> Self {
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
lst,
addr,
scheme: "http",
handler: Box::new(HttpServiceBuilder::new(
self.factory.clone(),
DefaultAcceptor,
)),
});
self
}
#[doc(hidden)]
/// Use listener for accepting incoming connection requests
pub fn listen_with<A>(mut self, lst: net::TcpListener, acceptor: A) -> Self
where
A: AcceptorServiceFactory,
<A::NewService as NewService>::InitError: fmt::Debug,
{
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
lst,
addr,
scheme: "https",
handler: Box::new(HttpServiceBuilder::new(self.factory.clone(), acceptor)),
});
self
}
#[cfg(feature = "tls")]
/// Use listener for accepting incoming tls connection requests
///
/// HttpServer does not change any configuration for TcpListener,
/// it needs to be configured before passing it to listen() method.
pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self {
use actix_net::service::NewServiceExt;
self.listen_with(lst, move || {
ssl::NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ())
})
}
#[cfg(any(feature = "alpn", feature = "ssl"))]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn listen_ssl(
self, lst: net::TcpListener, builder: SslAcceptorBuilder,
) -> io::Result<Self> {
use super::{openssl_acceptor_with_flags, ServerFlags};
use actix_net::service::NewServiceExt;
let flags = if self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
let acceptor = openssl_acceptor_with_flags(builder, flags)?;
Ok(self.listen_with(lst, move || {
ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ())
}))
}
#[cfg(feature = "rust-tls")]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn listen_rustls(self, lst: net::TcpListener, config: ServerConfig) -> Self {
use super::{RustlsAcceptor, ServerFlags};
use actix_net::service::NewServiceExt;
// alpn support
let flags = if self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
self.listen_with(lst, move || {
RustlsAcceptor::with_flags(config.clone(), flags).map_err(|_| ())
})
}
/// The socket address to bind
///
/// To bind multiple addresses this method can be called multiple times.
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> {
let sockets = self.bind2(addr)?;
for lst in sockets {
self = self.listen(lst);
}
Ok(self)
}
/// Start listening for incoming connections with supplied acceptor.
#[doc(hidden)]
#[cfg_attr(
feature = "cargo-clippy",
allow(clippy::needless_pass_by_value)
)]
pub fn bind_with<S, A>(mut self, addr: S, acceptor: A) -> io::Result<Self>
where
S: net::ToSocketAddrs,
A: AcceptorServiceFactory,
<A::NewService as NewService>::InitError: fmt::Debug,
{
let sockets = self.bind2(addr)?;
for lst in sockets {
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
lst,
addr,
scheme: "https",
handler: Box::new(HttpServiceBuilder::new(
self.factory.clone(),
acceptor.clone(),
)),
});
}
Ok(self)
}
fn bind2<S: net::ToSocketAddrs>(
&self, addr: S,
) -> io::Result<Vec<net::TcpListener>> {
let mut err = None;
let mut succ = false;
let mut sockets = Vec::new();
for addr in addr.to_socket_addrs()? {
match create_tcp_listener(addr, self.backlog) {
Ok(lst) => {
succ = true;
sockets.push(lst);
}
Err(e) => err = Some(e),
}
}
if !succ {
if let Some(e) = err.take() {
Err(e)
} else {
Err(io::Error::new(
io::ErrorKind::Other,
"Can not bind to address.",
))
}
} else {
Ok(sockets)
}
}
#[cfg(feature = "tls")]
/// The ssl socket address to bind
///
/// To bind multiple addresses this method can be called multiple times.
pub fn bind_tls<S: net::ToSocketAddrs>(
self, addr: S, acceptor: TlsAcceptor,
) -> io::Result<Self> {
use actix_net::service::NewServiceExt;
use actix_net::ssl::NativeTlsAcceptor;
self.bind_with(addr, move || {
NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ())
})
}
#[cfg(any(feature = "alpn", feature = "ssl"))]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_ssl<S>(self, addr: S, builder: SslAcceptorBuilder) -> io::Result<Self>
where
S: net::ToSocketAddrs,
{
use super::{openssl_acceptor_with_flags, ServerFlags};
use actix_net::service::NewServiceExt;
// alpn support
let flags = if self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
let acceptor = openssl_acceptor_with_flags(builder, flags)?;
self.bind_with(addr, move || {
ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ())
})
}
#[cfg(feature = "rust-tls")]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_rustls<S: net::ToSocketAddrs>(
self, addr: S, builder: ServerConfig,
) -> io::Result<Self> {
use super::{RustlsAcceptor, ServerFlags};
use actix_net::service::NewServiceExt;
// alpn support
let flags = if self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
self.bind_with(addr, move || {
RustlsAcceptor::with_flags(builder.clone(), flags).map_err(|_| ())
})
}
}
impl<H: IntoHttpHandler, F: Fn() -> H + Send + Clone> HttpServer<H, F> {
/// Start listening for incoming connections.
///
/// This method starts number of http workers in separate threads.
/// For each address this method starts separate thread which does
/// `accept()` in a loop.
///
/// This methods panics if no socket addresses get bound.
///
/// This method requires to run within properly configured `Actix` system.
///
/// ```rust
/// extern crate actix_web;
/// use actix_web::{actix, server, App, HttpResponse};
///
/// fn main() {
/// let sys = actix::System::new("example"); // <- create Actix system
///
/// server::new(|| App::new().resource("/", |r| r.h(|_: &_| HttpResponse::Ok())))
/// .bind("127.0.0.1:0")
/// .expect("Can not bind to 127.0.0.1:0")
/// .start();
/// # actix::System::current().stop();
/// sys.run(); // <- Run actix system, this method starts all async processes
/// }
/// ```
pub fn start(mut self) -> Addr<Server> {
ssl::max_concurrent_ssl_connect(self.maxconnrate);
let mut srv = Server::new()
.workers(self.threads)
.maxconn(self.maxconn)
.shutdown_timeout(self.shutdown_timeout);
srv = if self.exit { srv.system_exit() } else { srv };
srv = if self.no_signals {
srv.disable_signals()
} else {
srv
};
let sockets = mem::replace(&mut self.sockets, Vec::new());
for socket in sockets {
let host = self
.host
.as_ref()
.map(|h| h.to_owned())
.unwrap_or_else(|| format!("{}", socket.addr));
let (secure, client_shutdown) = if socket.scheme == "https" {
(true, self.client_shutdown)
} else {
(false, 0)
};
srv = socket.handler.register(
srv,
socket.lst,
host,
socket.addr,
self.keep_alive,
secure,
self.client_timeout,
client_shutdown,
);
}
srv.start()
}
/// Spawn new thread and start listening for incoming connections.
///
/// This method spawns new thread and starts new actix system. Other than
/// that it is similar to `start()` method. This method blocks.
///
/// This methods panics if no socket addresses get bound.
///
/// ```rust,ignore
/// # extern crate futures;
/// # extern crate actix_web;
/// # use futures::Future;
/// use actix_web::*;
///
/// fn main() {
/// HttpServer::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok())))
/// .bind("127.0.0.1:0")
/// .expect("Can not bind to 127.0.0.1:0")
/// .run();
/// }
/// ```
pub fn run(self) {
let sys = System::new("http-server");
self.start();
sys.run();
}
/// Register current http server as actix-net's server service
pub fn register(self, mut srv: Server) -> Server {
for socket in self.sockets {
let host = self
.host
.as_ref()
.map(|h| h.to_owned())
.unwrap_or_else(|| format!("{}", socket.addr));
let (secure, client_shutdown) = if socket.scheme == "https" {
(true, self.client_shutdown)
} else {
(false, 0)
};
srv = socket.handler.register(
srv,
socket.lst,
host,
socket.addr,
self.keep_alive,
secure,
self.client_timeout,
client_shutdown,
);
}
srv
}
}
fn create_tcp_listener(
addr: net::SocketAddr, backlog: i32,
) -> io::Result<net::TcpListener> {
let builder = match addr {
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,
net::SocketAddr::V6(_) => TcpBuilder::new_v6()?,
};
builder.reuse_address(true)?;
builder.bind(addr)?;
Ok(builder.listen(backlog)?)
}

View File

@ -1,69 +0,0 @@
//! Support for `Stream<Item=T::AsyncReady+AsyncWrite>`, deprecated!
use std::{io, net};
use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message};
use futures::{Future, Stream};
use tokio_io::{AsyncRead, AsyncWrite};
use super::channel::{HttpChannel, WrapperStream};
use super::handler::{HttpHandler, IntoHttpHandler};
use super::http::HttpServer;
use super::settings::{ServerSettings, ServiceConfig};
impl<T: AsyncRead + AsyncWrite + 'static> Message for WrapperStream<T> {
type Result = ();
}
impl<H, F> HttpServer<H, F>
where
H: IntoHttpHandler,
F: Fn() -> H + Send + Clone,
{
#[doc(hidden)]
#[deprecated(since = "0.7.8")]
/// Start listening for incoming connections from a stream.
///
/// This method uses only one thread for handling incoming connections.
pub fn start_incoming<T, S>(self, stream: S, secure: bool)
where
S: Stream<Item = T, Error = io::Error> + 'static,
T: AsyncRead + AsyncWrite + 'static,
{
// set server settings
let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
let apps = (self.factory)().into_handler();
let settings = ServiceConfig::new(
apps,
self.keep_alive,
self.client_timeout,
self.client_shutdown,
ServerSettings::new(addr, "127.0.0.1:8080", secure),
);
// start server
HttpIncoming::create(move |ctx| {
ctx.add_message_stream(stream.map_err(|_| ()).map(WrapperStream::new));
HttpIncoming { settings }
});
}
}
struct HttpIncoming<H: HttpHandler> {
settings: ServiceConfig<H>,
}
impl<H: HttpHandler> Actor for HttpIncoming<H> {
type Context = Context<Self>;
}
impl<T, H> Handler<WrapperStream<T>> for HttpIncoming<H>
where
T: AsyncRead + AsyncWrite,
H: HttpHandler,
{
type Result = ();
fn handle(&mut self, msg: WrapperStream<T>, _: &mut Context<Self>) -> Self::Result {
Arbiter::spawn(HttpChannel::new(self.settings.clone(), msg).map_err(|_| ()));
}
}

View File

@ -117,33 +117,20 @@ use tokio_tcp::TcpStream;
pub use actix_net::server::{PauseServer, ResumeServer, StopServer};
pub(crate) mod acceptor;
pub(crate) mod builder;
mod channel;
mod error;
pub(crate) mod h1;
// pub(crate) mod h1;
#[doc(hidden)]
pub mod h1codec;
#[doc(hidden)]
pub mod h1decoder;
mod h1writer;
mod h2;
mod h2writer;
mod handler;
pub(crate) mod helpers;
mod http;
pub(crate) mod incoming;
pub(crate) mod input;
pub(crate) mod message;
pub(crate) mod output;
pub(crate) mod service;
// pub(crate) mod service;
pub(crate) mod settings;
mod ssl;
pub use self::handler::*;
pub use self::http::HttpServer;
pub use self::message::Request;
pub use self::ssl::*;
pub use self::error::{AcceptorError, HttpDispatchError};
pub use self::settings::ServerSettings;
@ -151,14 +138,11 @@ pub use self::settings::ServerSettings;
#[doc(hidden)]
pub mod h1disp;
#[doc(hidden)]
pub use self::acceptor::AcceptorTimeout;
#[doc(hidden)]
pub use self::settings::{ServiceConfig, ServiceConfigBuilder};
#[doc(hidden)]
pub use self::service::{H1Service, HttpService, StreamConfiguration};
//#[doc(hidden)]
//pub use self::service::{H1Service, HttpService, StreamConfiguration};
#[doc(hidden)]
pub use self::helpers::write_content_length;
@ -174,53 +158,11 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536;
const LW_BUFFER_SIZE: usize = 4096;
const HW_BUFFER_SIZE: usize = 32_768;
/// Create new http server with application factory.
///
/// This is shortcut for `server::HttpServer::new()` method.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{actix, server, App, HttpResponse};
///
/// fn main() {
/// let sys = actix::System::new("example"); // <- create Actix system
///
/// server::new(
/// || App::new()
/// .resource("/", |r| r.f(|_| HttpResponse::Ok())))
/// .bind("127.0.0.1:59090").unwrap()
/// .start();
///
/// # actix::System::current().stop();
/// sys.run();
/// }
/// ```
pub fn new<F, H>(factory: F) -> HttpServer<H, F>
where
F: Fn() -> H + Send + Clone + 'static,
H: IntoHttpHandler + 'static,
{
HttpServer::new(factory)
}
#[doc(hidden)]
bitflags! {
///Flags that can be used to configure HTTP Server.
pub struct ServerFlags: u8 {
///Use HTTP1 protocol
const HTTP1 = 0b0000_0001;
///Use HTTP2 protocol
const HTTP2 = 0b0000_0010;
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
/// Server keep-alive setting
pub enum KeepAlive {
/// Keep alive in seconds
Timeout(usize),
/// Use `SO_KEEPALIVE` socket option, value in seconds
Tcp(usize),
/// Relay on OS to shutdown tcp connection
Os,
/// Disabled
@ -243,41 +185,9 @@ impl From<Option<usize>> for KeepAlive {
}
}
#[doc(hidden)]
#[derive(Debug)]
pub enum WriterState {
Done,
Pause,
}
#[doc(hidden)]
/// Stream writer
pub trait Writer {
/// number of bytes written to the stream
fn written(&self) -> u64;
#[doc(hidden)]
fn set_date(&mut self);
#[doc(hidden)]
fn buffer(&mut self) -> &mut BytesMut;
fn start(
&mut self, req: &Request, resp: &mut HttpResponse, encoding: ContentEncoding,
) -> io::Result<WriterState>;
fn write(&mut self, payload: &Binary) -> io::Result<WriterState>;
fn write_eof(&mut self) -> io::Result<WriterState>;
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error>;
}
#[doc(hidden)]
/// Low-level io stream operations
pub trait IoStream: AsyncRead + AsyncWrite + 'static {
fn shutdown(&mut self, how: Shutdown) -> io::Result<()>;
/// Returns the socket address of the remote peer of this TCP connection.
fn peer_addr(&self) -> Option<SocketAddr> {
None
@ -289,54 +199,10 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static {
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
fn read_available(&mut self, buf: &mut BytesMut) -> Poll<(bool, bool), io::Error> {
let mut read_some = false;
loop {
if buf.remaining_mut() < LW_BUFFER_SIZE {
buf.reserve(HW_BUFFER_SIZE);
}
let read = unsafe { self.read(buf.bytes_mut()) };
match read {
Ok(n) => {
if n == 0 {
return Ok(Async::Ready((read_some, true)));
} else {
read_some = true;
unsafe {
buf.advance_mut(n);
}
}
}
Err(e) => {
return if e.kind() == io::ErrorKind::WouldBlock {
if read_some {
Ok(Async::Ready((read_some, false)))
} else {
Ok(Async::NotReady)
}
} else {
Err(e)
};
}
}
}
}
/// Extra io stream extensions
fn extensions(&self) -> Option<Rc<Extensions>> {
None
}
}
#[cfg(all(unix, feature = "uds"))]
impl IoStream for ::tokio_uds::UnixStream {
#[inline]
fn shutdown(&mut self, how: Shutdown) -> io::Result<()> {
::tokio_uds::UnixStream::shutdown(self, how)
}
#[inline]
fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> {
Ok(())
@ -354,11 +220,6 @@ impl IoStream for ::tokio_uds::UnixStream {
}
impl IoStream for TcpStream {
#[inline]
fn shutdown(&mut self, how: Shutdown) -> io::Result<()> {
TcpStream::shutdown(self, how)
}
#[inline]
fn peer_addr(&self) -> Option<SocketAddr> {
TcpStream::peer_addr(self).ok()

View File

@ -254,7 +254,7 @@ impl Output {
}
return;
}
Body::Streaming(_) | Body::Actor(_) => {
Body::Streaming(_) => {
if resp.upgrade() {
if version == Version::HTTP_2 {
error!("Connection upgrade is forbidden for HTTP/2");

View File

@ -15,7 +15,6 @@ use time;
use tokio_current_thread::spawn;
use tokio_timer::{sleep, Delay};
use super::channel::Node;
use super::message::{Request, RequestPool};
use super::KeepAlive;
use body::Body;
@ -128,35 +127,33 @@ impl ServerSettings {
const DATE_VALUE_LENGTH: usize = 29;
/// Http service configuration
pub struct ServiceConfig<H>(Rc<Inner<H>>);
pub struct ServiceConfig(Rc<Inner>);
struct Inner<H> {
handler: H,
struct Inner {
keep_alive: Option<Duration>,
client_timeout: u64,
client_shutdown: u64,
ka_enabled: bool,
bytes: Rc<SharedBytesPool>,
messages: &'static RequestPool,
node: RefCell<Node<()>>,
date: UnsafeCell<(bool, Date)>,
}
impl<H> Clone for ServiceConfig<H> {
impl Clone for ServiceConfig {
fn clone(&self) -> Self {
ServiceConfig(self.0.clone())
}
}
impl<H> ServiceConfig<H> {
impl ServiceConfig {
/// Create instance of `ServiceConfig`
pub(crate) fn new(
handler: H, keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64,
keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64,
settings: ServerSettings,
) -> ServiceConfig<H> {
) -> ServiceConfig {
let (keep_alive, ka_enabled) = match keep_alive {
KeepAlive::Timeout(val) => (val as u64, true),
KeepAlive::Os | KeepAlive::Tcp(_) => (0, true),
KeepAlive::Os => (0, true),
KeepAlive::Disabled => (0, false),
};
let keep_alive = if ka_enabled && keep_alive > 0 {
@ -166,29 +163,19 @@ impl<H> ServiceConfig<H> {
};
ServiceConfig(Rc::new(Inner {
handler,
keep_alive,
ka_enabled,
client_timeout,
client_shutdown,
bytes: Rc::new(SharedBytesPool::new()),
messages: RequestPool::pool(settings),
node: RefCell::new(Node::head()),
date: UnsafeCell::new((false, Date::new())),
}))
}
/// Create worker settings builder.
pub fn build(handler: H) -> ServiceConfigBuilder<H> {
ServiceConfigBuilder::new(handler)
}
pub(crate) fn head(&self) -> RefMut<Node<()>> {
self.0.node.borrow_mut()
}
pub(crate) fn handler(&self) -> &H {
&self.0.handler
pub fn build() -> ServiceConfigBuilder {
ServiceConfigBuilder::new()
}
#[inline]
@ -226,7 +213,7 @@ impl<H> ServiceConfig<H> {
}
}
impl<H: 'static> ServiceConfig<H> {
impl ServiceConfig {
#[inline]
/// Client timeout for first request.
pub fn client_timer(&self) -> Option<Delay> {
@ -329,8 +316,7 @@ impl<H: 'static> ServiceConfig<H> {
///
/// This type can be used to construct an instance of `ServiceConfig` through a
/// builder-like pattern.
pub struct ServiceConfigBuilder<H> {
handler: H,
pub struct ServiceConfigBuilder {
keep_alive: KeepAlive,
client_timeout: u64,
client_shutdown: u64,
@ -339,11 +325,10 @@ pub struct ServiceConfigBuilder<H> {
secure: bool,
}
impl<H> ServiceConfigBuilder<H> {
impl ServiceConfigBuilder {
/// Create instance of `ServiceConfigBuilder`
pub fn new(handler: H) -> ServiceConfigBuilder<H> {
pub fn new() -> ServiceConfigBuilder {
ServiceConfigBuilder {
handler,
keep_alive: KeepAlive::Timeout(5),
client_timeout: 5000,
client_shutdown: 5000,
@ -426,12 +411,11 @@ impl<H> ServiceConfigBuilder<H> {
}
/// Finish service configuration and create `ServiceConfig` object.
pub fn finish(self) -> ServiceConfig<H> {
pub fn finish(self) -> ServiceConfig {
let settings = ServerSettings::new(self.addr, &self.host, self.secure);
let client_shutdown = if self.secure { self.client_shutdown } else { 0 };
ServiceConfig::new(
self.handler,
self.keep_alive,
self.client_timeout,
client_shutdown,

View File

@ -1,12 +0,0 @@
#[cfg(any(feature = "alpn", feature = "ssl"))]
mod openssl;
#[cfg(any(feature = "alpn", feature = "ssl"))]
pub use self::openssl::{openssl_acceptor_with_flags, OpensslAcceptor};
#[cfg(feature = "tls")]
mod nativetls;
#[cfg(feature = "rust-tls")]
mod rustls;
#[cfg(feature = "rust-tls")]
pub use self::rustls::RustlsAcceptor;

View File

@ -1,34 +0,0 @@
use std::net::{Shutdown, SocketAddr};
use std::{io, time};
use actix_net::ssl::TlsStream;
use server::IoStream;
impl<Io: IoStream> IoStream for TlsStream<Io> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = self.get_mut().shutdown();
Ok(())
}
#[inline]
fn peer_addr(&self) -> Option<SocketAddr> {
self.get_ref().get_ref().peer_addr()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_keepalive(dur)
}
}

View File

@ -1,87 +0,0 @@
use std::net::{Shutdown, SocketAddr};
use std::{io, time};
use actix_net::ssl;
use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_openssl::SslStream;
use server::{IoStream, ServerFlags};
/// Support `SSL` connections via openssl package
///
/// `ssl` feature enables `OpensslAcceptor` type
pub struct OpensslAcceptor<T> {
_t: ssl::OpensslAcceptor<T>,
}
impl<T: AsyncRead + AsyncWrite> OpensslAcceptor<T> {
/// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support.
pub fn new(builder: SslAcceptorBuilder) -> io::Result<ssl::OpensslAcceptor<T>> {
OpensslAcceptor::with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2)
}
/// Create `OpensslAcceptor` with custom server flags.
pub fn with_flags(
builder: SslAcceptorBuilder, flags: ServerFlags,
) -> io::Result<ssl::OpensslAcceptor<T>> {
let acceptor = openssl_acceptor_with_flags(builder, flags)?;
Ok(ssl::OpensslAcceptor::new(acceptor))
}
}
/// Configure `SslAcceptorBuilder` with custom server flags.
pub fn openssl_acceptor_with_flags(
mut builder: SslAcceptorBuilder, flags: ServerFlags,
) -> io::Result<SslAcceptor> {
let mut protos = Vec::new();
if flags.contains(ServerFlags::HTTP1) {
protos.extend(b"\x08http/1.1");
}
if flags.contains(ServerFlags::HTTP2) {
protos.extend(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(AlpnError::NOACK)
}
});
}
if !protos.is_empty() {
builder.set_alpn_protos(&protos)?;
}
Ok(builder.build())
}
impl<T: IoStream> IoStream for SslStream<T> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = self.get_mut().shutdown();
Ok(())
}
#[inline]
fn peer_addr(&self) -> Option<SocketAddr> {
self.get_ref().get_ref().peer_addr()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_keepalive(dur)
}
}

View File

@ -1,87 +0,0 @@
use std::net::{Shutdown, SocketAddr};
use std::{io, time};
use actix_net::ssl; //::RustlsAcceptor;
use rustls::{ClientSession, ServerConfig, ServerSession};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_rustls::TlsStream;
use server::{IoStream, ServerFlags};
/// Support `SSL` connections via rustls package
///
/// `rust-tls` feature enables `RustlsAcceptor` type
pub struct RustlsAcceptor<T> {
_t: ssl::RustlsAcceptor<T>,
}
impl<T: AsyncRead + AsyncWrite> RustlsAcceptor<T> {
/// Create `RustlsAcceptor` with custom server flags.
pub fn with_flags(
mut config: ServerConfig, flags: ServerFlags,
) -> ssl::RustlsAcceptor<T> {
let mut protos = Vec::new();
if flags.contains(ServerFlags::HTTP2) {
protos.push("h2".to_string());
}
if flags.contains(ServerFlags::HTTP1) {
protos.push("http/1.1".to_string());
}
if !protos.is_empty() {
config.set_protocols(&protos);
}
ssl::RustlsAcceptor::new(config)
}
}
impl<Io: IoStream> IoStream for TlsStream<Io, ClientSession> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = <Self as AsyncWrite>::shutdown(self);
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().0.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_keepalive(dur)
}
}
impl<Io: IoStream> IoStream for TlsStream<Io, ServerSession> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = <Self as AsyncWrite>::shutdown(self);
Ok(())
}
#[inline]
fn peer_addr(&self) -> Option<SocketAddr> {
self.get_ref().0.peer_addr()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().0.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_keepalive(dur)
}
}

View File

@ -1,383 +0,0 @@
use futures::{Async, Future, Poll};
use std::marker::PhantomData;
use std::rc::Rc;
use error::Error;
use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
trait FnWith<T, R>: 'static {
fn call_with(self: &Self, T) -> R;
}
impl<T, R, F: Fn(T) -> R + 'static> FnWith<T, R> for F {
fn call_with(self: &Self, arg: T) -> R {
(*self)(arg)
}
}
#[doc(hidden)]
pub trait WithFactory<T, S, R>: 'static
where
T: FromRequest<S>,
R: Responder,
{
fn create(self) -> With<T, S, R>;
fn create_with_config(self, T::Config) -> With<T, S, R>;
}
#[doc(hidden)]
pub trait WithAsyncFactory<T, S, R, I, E>: 'static
where
T: FromRequest<S>,
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<Error>,
{
fn create(self) -> WithAsync<T, S, R, I, E>;
fn create_with_config(self, T::Config) -> WithAsync<T, S, R, I, E>;
}
#[doc(hidden)]
pub struct With<T, S, R>
where
T: FromRequest<S>,
S: 'static,
{
hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>,
_s: PhantomData<S>,
}
impl<T, S, R> With<T, S, R>
where
T: FromRequest<S>,
S: 'static,
{
pub fn new<F: Fn(T) -> R + 'static>(f: F, cfg: T::Config) -> Self {
With {
cfg: Rc::new(cfg),
hnd: Rc::new(f),
_s: PhantomData,
}
}
}
impl<T, S, R> Handler<S> for With<T, S, R>
where
R: Responder + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
type Result = AsyncResult<HttpResponse>;
fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut {
req: req.clone(),
started: false,
hnd: Rc::clone(&self.hnd),
cfg: self.cfg.clone(),
fut1: None,
fut2: None,
};
match fut.poll() {
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => AsyncResult::err(e),
}
}
}
struct WithHandlerFut<T, S, R>
where
R: Responder,
T: FromRequest<S> + 'static,
S: 'static,
{
started: bool,
hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>,
req: HttpRequest<S>,
fut1: Option<Box<Future<Item = T, Error = Error>>>,
fut2: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
}
impl<T, S, R> Future for WithHandlerFut<T, S, R>
where
R: Responder + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
type Item = HttpResponse;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut2 {
return fut.poll();
}
let item = if !self.started {
self.started = true;
let reply = T::from_request(&self.req, self.cfg.as_ref()).into();
match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut1 = Some(fut);
return self.poll();
}
}
} else {
match self.fut1.as_mut().unwrap().poll()? {
Async::Ready(item) => item,
Async::NotReady => return Ok(Async::NotReady),
}
};
let item = match self.hnd.as_ref().call_with(item).respond_to(&self.req) {
Ok(item) => item.into(),
Err(e) => return Err(e.into()),
};
match item.into() {
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut2 = Some(fut);
self.poll()
}
}
}
}
#[doc(hidden)]
pub struct WithAsync<T, S, R, I, E>
where
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<E>,
T: FromRequest<S>,
S: 'static,
{
hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>,
_s: PhantomData<S>,
}
impl<T, S, R, I, E> WithAsync<T, S, R, I, E>
where
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<Error>,
T: FromRequest<S>,
S: 'static,
{
pub fn new<F: Fn(T) -> R + 'static>(f: F, cfg: T::Config) -> Self {
WithAsync {
cfg: Rc::new(cfg),
hnd: Rc::new(f),
_s: PhantomData,
}
}
}
impl<T, S, R, I, E> Handler<S> for WithAsync<T, S, R, I, E>
where
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
type Result = AsyncResult<HttpResponse>;
fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
let mut fut = WithAsyncHandlerFut {
req: req.clone(),
started: false,
hnd: Rc::clone(&self.hnd),
cfg: Rc::clone(&self.cfg),
fut1: None,
fut2: None,
fut3: None,
};
match fut.poll() {
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => AsyncResult::err(e),
}
}
}
struct WithAsyncHandlerFut<T, S, R, I, E>
where
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
started: bool,
hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>,
req: HttpRequest<S>,
fut1: Option<Box<Future<Item = T, Error = Error>>>,
fut2: Option<R>,
fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
}
impl<T, S, R, I, E> Future for WithAsyncHandlerFut<T, S, R, I, E>
where
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
type Item = HttpResponse;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut3 {
return fut.poll();
}
if self.fut2.is_some() {
return match self.fut2.as_mut().unwrap().poll() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(r)) => match r.respond_to(&self.req) {
Ok(r) => match r.into().into() {
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut3 = Some(fut);
self.poll()
}
},
Err(e) => Err(e.into()),
},
Err(e) => Err(e.into()),
};
}
let item = if !self.started {
self.started = true;
let reply = T::from_request(&self.req, self.cfg.as_ref()).into();
match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut1 = Some(fut);
return self.poll();
}
}
} else {
match self.fut1.as_mut().unwrap().poll()? {
Async::Ready(item) => item,
Async::NotReady => return Ok(Async::NotReady),
}
};
self.fut2 = Some(self.hnd.as_ref().call_with(item));
self.poll()
}
}
macro_rules! with_factory_tuple ({$(($n:tt, $T:ident)),+} => {
impl<$($T,)+ State, Func, Res> WithFactory<($($T,)+), State, Res> for Func
where Func: Fn($($T,)+) -> Res + 'static,
$($T: FromRequest<State> + 'static,)+
Res: Responder + 'static,
State: 'static,
{
fn create(self) -> With<($($T,)+), State, Res> {
With::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+))
}
fn create_with_config(self, cfg: ($($T::Config,)+)) -> With<($($T,)+), State, Res> {
With::new(move |($($n,)+)| (self)($($n,)+), cfg)
}
}
});
macro_rules! with_async_factory_tuple ({$(($n:tt, $T:ident)),+} => {
impl<$($T,)+ State, Func, Res, Item, Err> WithAsyncFactory<($($T,)+), State, Res, Item, Err> for Func
where Func: Fn($($T,)+) -> Res + 'static,
$($T: FromRequest<State> + 'static,)+
Res: Future<Item=Item, Error=Err>,
Item: Responder + 'static,
Err: Into<Error>,
State: 'static,
{
fn create(self) -> WithAsync<($($T,)+), State, Res, Item, Err> {
WithAsync::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+))
}
fn create_with_config(self, cfg: ($($T::Config,)+)) -> WithAsync<($($T,)+), State, Res, Item, Err> {
WithAsync::new(move |($($n,)+)| (self)($($n,)+), cfg)
}
}
});
with_factory_tuple!((a, A));
with_factory_tuple!((a, A), (b, B));
with_factory_tuple!((a, A), (b, B), (c, C));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G));
with_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H)
);
with_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H),
(i, I)
);
with_async_factory_tuple!((a, A));
with_async_factory_tuple!((a, A), (b, B));
with_async_factory_tuple!((a, A), (b, B), (c, C));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G));
with_async_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H)
);
with_async_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H),
(i, I)
);

View File

@ -1,508 +0,0 @@
#![allow(deprecated)]
extern crate actix;
extern crate actix_web;
extern crate bytes;
extern crate flate2;
extern crate futures;
extern crate rand;
#[cfg(all(unix, feature = "uds"))]
extern crate tokio_uds;
use std::io::{Read, Write};
use std::{net, thread};
use bytes::Bytes;
use flate2::read::GzDecoder;
use futures::stream::once;
use futures::Future;
use rand::Rng;
use actix_web::*;
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World";
#[test]
fn test_simple() {
let mut srv =
test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
let request = srv.get().header("x-test", "111").finish().unwrap();
let repr = format!("{:?}", request);
assert!(repr.contains("ClientRequest"));
assert!(repr.contains("x-test"));
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
let request = srv.post().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[test]
fn test_connection_close() {
let mut srv =
test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
let request = srv.get().header("Connection", "close").finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.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(),
})
});
let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
}
#[test]
fn test_no_decompress() {
let mut srv =
test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
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.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()));
// 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()));
}
#[test]
fn test_client_gzip_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Deflate)
.body(bytes))
}).responder()
})
});
// client request
let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip)
.body(STR)
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[test]
fn test_client_gzip_encoding_large() {
let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Deflate)
.body(bytes))
}).responder()
})
});
// client request
let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip)
.body(data.clone())
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from(data));
}
#[test]
fn test_client_gzip_encoding_large_random() {
let data = rand::thread_rng()
.gen_ascii_chars()
.take(100_000)
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Deflate)
.body(bytes))
}).responder()
})
});
// client request
let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip)
.body(data.clone())
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from(data));
}
#[cfg(all(unix, feature = "uds"))]
#[test]
fn test_compatible_with_unix_socket_stream() {
let (stream, _) = tokio_uds::UnixStream::pair().unwrap();
let _ = client::Connection::from_stream(stream);
}
#[cfg(feature = "brotli")]
#[test]
fn test_client_brotli_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Gzip)
.body(bytes))
}).responder()
})
});
// client request
let request = srv
.client(http::Method::POST, "/")
.content_encoding(http::ContentEncoding::Br)
.body(STR)
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[cfg(feature = "brotli")]
#[test]
fn test_client_brotli_encoding_large_random() {
let data = rand::thread_rng()
.gen_ascii_chars()
.take(70_000)
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(move |bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Gzip)
.body(bytes))
}).responder()
})
});
// client request
let request = srv
.client(http::Method::POST, "/")
.content_encoding(http::ContentEncoding::Br)
.body(data.clone())
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes.len(), data.len());
assert_eq!(bytes, Bytes::from(data));
}
#[cfg(feature = "brotli")]
#[test]
fn test_client_deflate_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Br)
.body(bytes))
}).responder()
})
});
// client request
let request = srv
.post()
.content_encoding(http::ContentEncoding::Deflate)
.body(STR)
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[cfg(feature = "brotli")]
#[test]
fn test_client_deflate_encoding_large_random() {
let data = rand::thread_rng()
.gen_ascii_chars()
.take(70_000)
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Br)
.body(bytes))
}).responder()
})
});
// client request
let request = srv
.post()
.content_encoding(http::ContentEncoding::Deflate)
.body(data.clone())
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from(data));
}
#[test]
fn test_client_streaming_explicit() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
req.body()
.map_err(Error::from)
.and_then(|body| {
Ok(HttpResponse::Ok()
.chunked()
.content_encoding(http::ContentEncoding::Identity)
.body(body))
}).responder()
})
});
let body = once(Ok(Bytes::from_static(STR.as_ref())));
let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[test]
fn test_body_streaming_implicit() {
let mut srv = test::TestServer::new(|app| {
app.handler(|_| {
let body = once(Ok(Bytes::from_static(STR.as_ref())));
HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Gzip)
.body(Body::Streaming(Box::new(body)))
})
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[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
Error::from(IoError::from(ErrorKind::NotFound))
}
let cookie1 = Cookie::build("cookie1", "value1").finish();
let cookie2 = Cookie::build("cookie2", "value2")
.domain("www.example.org")
.path("/")
.secure(true)
.http_only(true)
.finish();
// Q: are all these clones really necessary? A: Yes, possibly
let cookie1b = cookie1.clone();
let cookie2b = cookie2.clone();
let mut srv = test::TestServer::new(move |app| {
let cookie1 = cookie1b.clone();
let cookie2 = cookie2b.clone();
app.handler(move |req: &HttpRequest| {
// Check cookies were sent correctly
req.cookie("cookie1")
.ok_or_else(err)
.and_then(|c1| {
if c1.value() == "value1" {
Ok(())
} else {
Err(err())
}
}).and_then(|()| req.cookie("cookie2").ok_or_else(err))
.and_then(|c2| {
if c2.value() == "value2" {
Ok(())
} else {
Err(err())
}
})
// Send some cookies back
.map(|_| {
HttpResponse::Ok()
.cookie(cookie1.clone())
.cookie(cookie2.clone())
.finish()
})
})
});
let request = srv
.get()
.cookie(cookie1.clone())
.cookie(cookie2.clone())
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
let c1 = response.cookie("cookie1").expect("Missing cookie1");
assert_eq!(c1, cookie1);
let c2 = response.cookie("cookie2").expect("Missing cookie2");
assert_eq!(c2, cookie2);
}
#[test]
fn test_default_headers() {
let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
let request = srv.get().finish().unwrap();
let repr = format!("{:?}", request);
assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\""));
assert!(repr.contains(concat!(
"\"user-agent\": \"actix-web/",
env!("CARGO_PKG_VERSION"),
"\""
)));
let request_override = srv
.get()
.header("User-Agent", "test")
.header("Accept-Encoding", "over_test")
.finish()
.unwrap();
let repr_override = format!("{:?}", request_override);
assert!(repr_override.contains("\"user-agent\": \"test\""));
assert!(repr_override.contains("\"accept-encoding\": \"over_test\""));
assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\""));
assert!(!repr_override.contains(concat!(
"\"user-agent\": \"Actix-web/",
env!("CARGO_PKG_VERSION"),
"\""
)));
}
#[test]
fn client_read_until_eof() {
let addr = test::TestServer::unused_addr();
thread::spawn(move || {
let lst = net::TcpListener::bind(addr).unwrap();
for stream in lst.incoming() {
let mut stream = stream.unwrap();
let mut b = [0; 1000];
let _ = stream.read(&mut b).unwrap();
let _ = stream
.write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!");
}
});
let mut sys = actix::System::new("test");
// client request
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = sys.block_on(req.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = sys.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"welcome!"));
}

View File

@ -1,81 +0,0 @@
extern crate actix;
extern crate actix_net;
extern crate actix_web;
use std::{thread, time};
use actix::System;
use actix_net::server::Server;
use actix_net::service::NewServiceExt;
use actix_web::server::{HttpService, KeepAlive, ServiceConfig, StreamConfiguration};
use actix_web::{client, http, test, App, HttpRequest};
#[test]
fn test_custom_pipeline() {
let addr = test::TestServer::unused_addr();
thread::spawn(move || {
Server::new()
.bind("test", addr, move || {
let app = App::new()
.route("/", http::Method::GET, |_: HttpRequest| "OK")
.finish();
let settings = ServiceConfig::build(app)
.keep_alive(KeepAlive::Disabled)
.client_timeout(1000)
.client_shutdown(1000)
.server_hostname("localhost")
.server_address(addr)
.finish();
StreamConfiguration::new()
.nodelay(true)
.tcp_keepalive(Some(time::Duration::from_secs(10)))
.and_then(HttpService::new(settings))
}).unwrap()
.run();
});
let mut sys = System::new("test");
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = sys.block_on(req.send()).unwrap();
assert!(response.status().is_success());
}
}
#[test]
fn test_h1() {
use actix_web::server::H1Service;
let addr = test::TestServer::unused_addr();
thread::spawn(move || {
Server::new()
.bind("test", addr, move || {
let app = App::new()
.route("/", http::Method::GET, |_: HttpRequest| "OK")
.finish();
let settings = ServiceConfig::build(app)
.keep_alive(KeepAlive::Disabled)
.client_timeout(1000)
.client_shutdown(1000)
.server_hostname("localhost")
.server_address(addr)
.finish();
H1Service::new(settings)
}).unwrap()
.run();
});
let mut sys = System::new("test");
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = sys.block_on(req.send()).unwrap();
assert!(response.status().is_success());
}
}

View File

@ -1,4 +1,5 @@
extern crate actix;
extern crate actix_http;
extern crate actix_net;
extern crate actix_web;
extern crate futures;
@ -8,12 +9,12 @@ use std::thread;
use actix::System;
use actix_net::server::Server;
use actix_net::service::{IntoNewService, IntoService};
use actix_web::{client, test};
use futures::future;
use actix_web::server::h1disp::Http1Dispatcher;
use actix_web::server::KeepAlive;
use actix_web::server::ServiceConfig;
use actix_web::{client, test, App, Error, HttpRequest, HttpResponse};
use actix_http::server::h1disp::Http1Dispatcher;
use actix_http::server::{KeepAlive, ServiceConfig};
use actix_http::{Error, HttpResponse};
#[test]
fn test_h1_v2() {
@ -21,10 +22,7 @@ fn test_h1_v2() {
thread::spawn(move || {
Server::new()
.bind("test", addr, move || {
let app = App::new()
.route("/", http::Method::GET, |_: HttpRequest| "OK")
.finish();
let settings = ServiceConfig::build(app)
let settings = ServiceConfig::build()
.keep_alive(KeepAlive::Disabled)
.client_timeout(1000)
.client_shutdown(1000)

View File

@ -1,677 +0,0 @@
extern crate actix;
extern crate actix_web;
extern crate bytes;
extern crate futures;
extern crate h2;
extern crate http;
extern crate tokio_timer;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
use std::io;
use std::time::{Duration, Instant};
use actix_web::*;
use bytes::Bytes;
use futures::Future;
use http::StatusCode;
use serde_json::Value;
use tokio_timer::Delay;
#[derive(Deserialize)]
struct PParam {
username: String,
}
#[test]
fn test_path_extractor() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.with(|p: Path<PParam>| format!("Welcome {}!", p.username))
});
});
// client request
let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome test!"));
}
#[test]
fn test_async_handler() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(|p: Path<PParam>| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| Ok(format!("Welcome {}!", p.username)))
.responder()
})
});
});
// client request
let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome test!"));
}
#[test]
fn test_query_extractor() {
let mut srv = test::TestServer::new(|app| {
app.resource("/index.html", |r| {
r.with(|p: Query<PParam>| format!("Welcome {}!", p.username))
});
});
// client request
let request = srv
.get()
.uri(srv.url("/index.html?username=test"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome test!"));
// client request
let request = srv.get().uri(srv.url("/index.html")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[derive(Deserialize, Debug)]
pub enum ResponseType {
Token,
Code,
}
#[derive(Debug, Deserialize)]
pub struct AuthRequest {
id: u64,
response_type: ResponseType,
}
#[test]
fn test_query_enum_extractor() {
let mut srv = test::TestServer::new(|app| {
app.resource("/index.html", |r| {
r.with(|p: Query<AuthRequest>| format!("{:?}", p.into_inner()))
});
});
// client request
let request = srv
.get()
.uri(srv.url("/index.html?id=64&response_type=Code"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(
bytes,
Bytes::from_static(b"AuthRequest { id: 64, response_type: Code }")
);
let request = srv
.get()
.uri(srv.url("/index.html?id=64&response_type=Co"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let request = srv
.get()
.uri(srv.url("/index.html?response_type=Code"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_async_extractor_async() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(|data: Json<Value>| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| Ok(format!("{}", data.0)))
.responder()
})
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}"));
}
#[derive(Deserialize, Serialize)]
struct FormData {
username: String,
}
#[test]
fn test_form_extractor() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route()
.with(|form: Form<FormData>| format!("{}", form.username))
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html"))
.form(FormData {
username: "test".to_string(),
}).unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"test"));
}
#[test]
fn test_form_extractor2() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with_config(
|form: Form<FormData>| format!("{}", form.username),
|cfg| {
cfg.0.error_handler(|err, _| {
error::InternalError::from_response(
err,
HttpResponse::Conflict().finish(),
).into()
});
},
);
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html"))
.header("content-type", "application/x-www-form-urlencoded")
.body("918237129hdk:D:D:D:D:D:DjASHDKJhaswkjeq")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_client_error());
}
#[test]
fn test_path_and_query_extractor() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(|(p, q): (Path<PParam>, Query<PParam>)| {
format!("Welcome {} - {}!", p.username, q.username)
})
});
});
// client request
let request = srv
.get()
.uri(srv.url("/test1/index.html?username=test2"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!"));
// client request
let request = srv
.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_path_and_query_extractor2() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route()
.with(|(_r, p, q): (HttpRequest, Path<PParam>, Query<PParam>)| {
format!("Welcome {} - {}!", p.username, q.username)
})
});
});
// client request
let request = srv
.get()
.uri(srv.url("/test1/index.html?username=test2"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!"));
// client request
let request = srv
.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_path_and_query_extractor2_async() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(
|(p, _q, data): (Path<PParam>, Query<PParam>, Json<Value>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
}).responder()
},
)
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
}
#[test]
fn test_path_and_query_extractor3_async() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(|(p, data): (Path<PParam>, Json<Value>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
}).responder()
})
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
}
#[test]
fn test_path_and_query_extractor4_async() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(|(data, p): (Json<Value>, Path<PParam>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
}).responder()
})
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
}
#[test]
fn test_path_and_query_extractor2_async2() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(
|(p, data, _q): (Path<PParam>, Json<Value>, Query<PParam>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
}).responder()
},
)
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
// client request
let request = srv
.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_path_and_query_extractor2_async3() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route()
.with(|data: Json<Value>, p: Path<PParam>, _: Query<PParam>| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
}).responder()
})
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
// client request
let request = srv
.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_path_and_query_extractor2_async4() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route()
.with(|data: (Json<Value>, Path<PParam>, Query<PParam>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0))
}).responder()
})
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
// client request
let request = srv
.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_scope_and_path_extractor() {
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/sc", |scope| {
scope.resource("/{num}/index.html", |r| {
r.route()
.with(|p: Path<(usize,)>| format!("Welcome {}!", p.0))
})
})
});
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome 10!"));
// client request
let request = srv
.get()
.uri(srv.url("/sc/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_nested_scope_and_path_extractor() {
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/sc", |scope| {
scope.nested("/{num}", |scope| {
scope.resource("/{num}/index.html", |r| {
r.route().with(|p: Path<(usize, usize)>| {
format!("Welcome {} {}!", p.0, p.1)
})
})
})
})
});
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/12/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome 10 12!"));
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[cfg(actix_impl_trait)]
fn test_impl_trait(
data: (Json<Value>, Path<PParam>, Query<PParam>),
) -> impl Future<Item = String, Error = io::Error> {
Delay::new(Instant::now() + Duration::from_millis(10))
.map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout"))
.and_then(move |_| Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)))
}
#[cfg(actix_impl_trait)]
fn test_impl_trait_err(
_data: (Json<Value>, Path<PParam>, Query<PParam>),
) -> impl Future<Item = String, Error = io::Error> {
Delay::new(Instant::now() + Duration::from_millis(10))
.map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout"))
.and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other")))
}
#[cfg(actix_impl_trait)]
#[test]
fn test_path_and_query_extractor2_async4_impl_trait() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with_async(test_impl_trait)
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
// client request
let request = srv
.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[cfg(actix_impl_trait)]
#[test]
fn test_path_and_query_extractor2_async4_impl_trait_err() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with_async(test_impl_trait_err)
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn test_non_ascii_route() {
let mut srv = test::TestServer::new(|app| {
app.resource("/中文/index.html", |r| r.f(|_| "success"));
});
// client request
let request = srv
.get()
.uri(srv.url("/中文/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"success"));
}
#[test]
fn test_unsafe_path_route() {
let mut srv = test::TestServer::new(|app| {
app.resource("/test/{url}", |r| {
r.f(|r| format!("success: {}", &r.match_info()["url"]))
});
});
// client request
let request = srv
.get()
.uri(srv.url("/test/http%3A%2F%2Fexample.com"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(
bytes,
Bytes::from_static(b"success: http:%2F%2Fexample.com")
);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,394 +0,0 @@
extern crate actix;
extern crate actix_web;
extern crate bytes;
extern crate futures;
extern crate http;
extern crate rand;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::{thread, time};
use bytes::Bytes;
use futures::Stream;
use rand::distributions::Alphanumeric;
use rand::Rng;
#[cfg(feature = "ssl")]
extern crate openssl;
#[cfg(feature = "rust-tls")]
extern crate rustls;
use actix::prelude::*;
use actix_web::*;
struct Ws;
impl Actor for Ws {
type Context = ws::WebsocketContext<Self>;
}
impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
match msg {
ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Close(reason) => ctx.close(reason),
_ => (),
}
}
}
#[test]
fn test_simple() {
let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws)));
let (reader, mut writer) = srv.ws().unwrap();
writer.text("text");
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
writer.binary(b"text".as_ref());
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(
item,
Some(ws::Message::Binary(Bytes::from_static(b"text").into()))
);
writer.ping("ping");
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Pong("ping".to_owned())));
writer.close(Some(ws::CloseCode::Normal.into()));
let (item, _) = srv.execute(reader.into_future()).unwrap();
assert_eq!(
item,
Some(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
);
}
// websocket resource helper function
fn start_ws_resource(req: &HttpRequest) -> Result<HttpResponse, Error> {
ws::start(req, Ws)
}
#[test]
fn test_simple_path() {
const PATH: &str = "/v1/ws/";
// Create a websocket at a specific path.
let mut srv = test::TestServer::new(|app| {
app.resource(PATH, |r| r.route().f(start_ws_resource));
});
// fetch the sockets for the resource at a given path.
let (reader, mut writer) = srv.ws_at(PATH).unwrap();
writer.text("text");
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
writer.binary(b"text".as_ref());
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(
item,
Some(ws::Message::Binary(Bytes::from_static(b"text").into()))
);
writer.ping("ping");
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Pong("ping".to_owned())));
writer.close(Some(ws::CloseCode::Normal.into()));
let (item, _) = srv.execute(reader.into_future()).unwrap();
assert_eq!(
item,
Some(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
);
}
#[test]
fn test_empty_close_code() {
let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws)));
let (reader, mut writer) = srv.ws().unwrap();
writer.close(None);
let (item, _) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Close(None)));
}
#[test]
fn test_close_description() {
let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws)));
let (reader, mut writer) = srv.ws().unwrap();
let close_reason: ws::CloseReason =
(ws::CloseCode::Normal, "close description").into();
writer.close(Some(close_reason.clone()));
let (item, _) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Close(Some(close_reason))));
}
#[test]
fn test_large_text() {
let data = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(65_536)
.collect::<String>();
let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws)));
let (mut reader, mut writer) = srv.ws().unwrap();
for _ in 0..100 {
writer.text(data.clone());
let (item, r) = srv.execute(reader.into_future()).unwrap();
reader = r;
assert_eq!(item, Some(ws::Message::Text(data.clone())));
}
}
#[test]
fn test_large_bin() {
let data = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(65_536)
.collect::<String>();
let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws)));
let (mut reader, mut writer) = srv.ws().unwrap();
for _ in 0..100 {
writer.binary(data.clone());
let (item, r) = srv.execute(reader.into_future()).unwrap();
reader = r;
assert_eq!(item, Some(ws::Message::Binary(Binary::from(data.clone()))));
}
}
#[test]
fn test_client_frame_size() {
let data = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(131_072)
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req| -> Result<HttpResponse> {
let mut resp = ws::handshake(req)?;
let stream = ws::WsStream::new(req.payload()).max_size(131_072);
let body = ws::WebsocketContext::create(req.clone(), Ws, stream);
Ok(resp.body(body))
})
});
let (reader, mut writer) = srv.ws().unwrap();
writer.binary(data.clone());
match srv.execute(reader.into_future()).err().unwrap().0 {
ws::ProtocolError::Overflow => (),
_ => panic!(),
}
}
struct Ws2 {
count: usize,
bin: bool,
}
impl Actor for Ws2 {
type Context = ws::WebsocketContext<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
self.send(ctx);
}
}
impl Ws2 {
fn send(&mut self, ctx: &mut ws::WebsocketContext<Self>) {
if self.bin {
ctx.binary(Vec::from("0".repeat(65_536)));
} else {
ctx.text("0".repeat(65_536));
}
ctx.drain()
.and_then(|_, act, ctx| {
act.count += 1;
if act.count != 10_000 {
act.send(ctx);
}
actix::fut::ok(())
}).wait(ctx);
}
}
impl StreamHandler<ws::Message, ws::ProtocolError> for Ws2 {
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
match msg {
ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Close(reason) => ctx.close(reason),
_ => (),
}
}
}
#[test]
fn test_server_send_text() {
let data = Some(ws::Message::Text("0".repeat(65_536)));
let mut srv = test::TestServer::new(|app| {
app.handler(|req| {
ws::start(
req,
Ws2 {
count: 0,
bin: false,
},
)
})
});
let (mut reader, _writer) = srv.ws().unwrap();
for _ in 0..10_000 {
let (item, r) = srv.execute(reader.into_future()).unwrap();
reader = r;
assert_eq!(item, data);
}
}
#[test]
fn test_server_send_bin() {
let data = Some(ws::Message::Binary(Binary::from("0".repeat(65_536))));
let mut srv = test::TestServer::new(|app| {
app.handler(|req| {
ws::start(
req,
Ws2 {
count: 0,
bin: true,
},
)
})
});
let (mut reader, _writer) = srv.ws().unwrap();
for _ in 0..10_000 {
let (item, r) = srv.execute(reader.into_future()).unwrap();
reader = r;
assert_eq!(item, data);
}
}
#[test]
#[cfg(feature = "ssl")]
fn test_ws_server_ssl() {
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
// load ssl keys
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder
.set_private_key_file("tests/key.pem", SslFiletype::PEM)
.unwrap();
builder
.set_certificate_chain_file("tests/cert.pem")
.unwrap();
let mut srv = test::TestServer::build().ssl(builder).start(|app| {
app.handler(|req| {
ws::start(
req,
Ws2 {
count: 0,
bin: false,
},
)
})
});
let (mut reader, _writer) = srv.ws().unwrap();
let data = Some(ws::Message::Text("0".repeat(65_536)));
for _ in 0..10_000 {
let (item, r) = srv.execute(reader.into_future()).unwrap();
reader = r;
assert_eq!(item, data);
}
}
#[test]
#[cfg(feature = "rust-tls")]
fn test_ws_server_rust_tls() {
use rustls::internal::pemfile::{certs, rsa_private_keys};
use rustls::{NoClientAuth, ServerConfig};
use std::fs::File;
use std::io::BufReader;
// load ssl keys
let mut config = ServerConfig::new(NoClientAuth::new());
let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap());
let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap());
let cert_chain = certs(cert_file).unwrap();
let mut keys = rsa_private_keys(key_file).unwrap();
config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
let mut srv = test::TestServer::build().rustls(config).start(|app| {
app.handler(|req| {
ws::start(
req,
Ws2 {
count: 0,
bin: false,
},
)
})
});
let (mut reader, _writer) = srv.ws().unwrap();
let data = Some(ws::Message::Text("0".repeat(65_536)));
for _ in 0..10_000 {
let (item, r) = srv.execute(reader.into_future()).unwrap();
reader = r;
assert_eq!(item, data);
}
}
struct WsStopped(Arc<AtomicUsize>);
impl Actor for WsStopped {
type Context = ws::WebsocketContext<Self>;
fn stopped(&mut self, _: &mut Self::Context) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
impl StreamHandler<ws::Message, ws::ProtocolError> for WsStopped {
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
match msg {
ws::Message::Text(text) => ctx.text(text),
_ => (),
}
}
}
#[test]
fn test_ws_stopped() {
let num = Arc::new(AtomicUsize::new(0));
let num2 = num.clone();
let mut srv = test::TestServer::new(move |app| {
let num3 = num2.clone();
app.handler(move |req| ws::start(req, WsStopped(num3.clone())))
});
{
let (reader, mut writer) = srv.ws().unwrap();
writer.text("text");
let (item, _) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
}
thread::sleep(time::Duration::from_millis(1000));
assert_eq!(num.load(Ordering::Relaxed), 1);
}