mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-28 01:32:57 +01:00
remove actix-web artifacts
This commit is contained in:
parent
13b0ee7355
commit
6aa2de7b8d
712
CHANGES.md
712
CHANGES.md
@ -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
|
||||
|
15
Cargo.toml
15
Cargo.toml
@ -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 = [
|
||||
"./",
|
||||
]
|
||||
|
176
MIGRATION.md
176
MIGRATION.md
@ -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`
|
14
Makefile
14
Makefile
@ -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)
|
44
README.md
44
README.md
@ -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.
|
||||
|
16
build.rs
16
build.rs
@ -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 => (),
|
||||
};
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
29
src/body.rs
29
src/body.rs
@ -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
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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"));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
294
src/context.rs
294
src/context.rs
@ -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
443
src/de.rs
@ -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"))
|
||||
}
|
||||
}
|
15
src/error.rs
15
src/error.rs
@ -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)]
|
||||
|
1024
src/extractor.rs
1024
src/extractor.rs
File diff suppressed because it is too large
Load Diff
562
src/handler.rs
562
src/handler.rs
@ -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())
|
||||
}
|
||||
}
|
571
src/helpers.rs
571
src/helpers.rs
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
99
src/json.rs
99
src/json.rs
@ -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:
|
||||
|
101
src/lib.rs
101
src/lib.rs
@ -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
@ -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());
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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),
|
||||
})
|
||||
}
|
||||
}
|
@ -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"));
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
815
src/multipart.rs
815
src/multipart.rs
@ -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();
|
||||
}
|
||||
}
|
303
src/param.rs
303
src/param.rs
@ -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"]))
|
||||
);
|
||||
}
|
||||
}
|
869
src/pipeline.rs
869
src/pipeline.rs
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
328
src/pred.rs
328
src/pred.rs
@ -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()));
|
||||
}
|
||||
}
|
324
src/resource.rs
324
src/resource.rs
@ -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))
|
||||
}
|
||||
}
|
666
src/route.rs
666
src/route.rs
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
1247
src/router.rs
1247
src/router.rs
File diff suppressed because it is too large
Load Diff
1236
src/scope.rs
1236
src/scope.rs
File diff suppressed because it is too large
Load Diff
@ -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(())),
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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()?)
|
||||
}
|
||||
}
|
||||
}
|
453
src/server/h2.rs
453
src/server/h2.rs
@ -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)
|
||||
}
|
||||
}
|
@ -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(()))
|
||||
}
|
||||
}
|
@ -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)
|
||||
);
|
@ -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)?)
|
||||
}
|
@ -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(|_| ()));
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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");
|
||||
|
@ -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,
|
||||
|
@ -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;
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
383
src/with.rs
383
src/with.rs
@ -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)
|
||||
);
|
@ -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!"));
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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
1357
tests/test_server.rs
1357
tests/test_server.rs
File diff suppressed because it is too large
Load Diff
394
tests/test_ws.rs
394
tests/test_ws.rs
@ -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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user