mirror of
https://github.com/fafhrd91/actix-web
synced 2024-11-23 16:21:06 +01:00
copy actix-web2
This commit is contained in:
parent
6d11ee683f
commit
2d7293aaf8
832
CHANGES.md
832
CHANGES.md
@ -1,833 +1,5 @@
|
||||
# Changes
|
||||
|
||||
## [x.x.xx] - xxxx-xx-xx
|
||||
## [0.1.0] - 2018-10-x
|
||||
|
||||
### Added
|
||||
|
||||
* Add `from_file` and `from_file_with_config` to `NamedFile` to allow sending files without a known path. #670
|
||||
|
||||
* Add `insert` and `remove` methods to `HttpResponseBuilder`
|
||||
|
||||
### Fixed
|
||||
|
||||
* Ignored the `If-Modified-Since` if `If-None-Match` is specified. #680
|
||||
|
||||
## [0.7.18] - 2019-01-10
|
||||
|
||||
### Added
|
||||
|
||||
* Add `with_cookie` for `TestRequest` to allow users to customize request cookie. #647
|
||||
|
||||
* Add `cookie` method for `TestRequest` to allow users to add cookie dynamically.
|
||||
|
||||
### Fixed
|
||||
|
||||
* StaticFiles decode special characters in request's path
|
||||
|
||||
* Fix test server listener leak #654
|
||||
|
||||
## [0.7.17] - 2018-12-25
|
||||
|
||||
### Added
|
||||
|
||||
* Support for custom content types in `JsonConfig`. #637
|
||||
|
||||
* Send `HTTP/1.1 100 Continue` if request contains `expect: continue` header #634
|
||||
|
||||
### Fixed
|
||||
|
||||
* HTTP1 decoder should perform case-insentive comparison for client requests (e.g. `Keep-Alive`). #631
|
||||
|
||||
* Access-Control-Allow-Origin header should only a return a single, matching origin. #603
|
||||
|
||||
## [0.7.16] - 2018-12-11
|
||||
|
||||
### Added
|
||||
|
||||
* Implement `FromRequest` extractor for `Either<A,B>`
|
||||
|
||||
* Implement `ResponseError` for `SendError`
|
||||
|
||||
|
||||
## [0.7.15] - 2018-12-05
|
||||
|
||||
### Changed
|
||||
|
||||
* `ClientConnector::resolver` now accepts `Into<Recipient>` instead of `Addr`. It enables user to implement own resolver.
|
||||
|
||||
* `QueryConfig` and `PathConfig` are made public.
|
||||
|
||||
* `AsyncResult::async` is changed to `AsyncResult::future` as `async` is reserved keyword in 2018 edition.
|
||||
|
||||
### Added
|
||||
|
||||
* By default, `Path` extractor now percent decode all characters. This behaviour can be disabled
|
||||
with `PathConfig::default().disable_decoding()`
|
||||
|
||||
|
||||
## [0.7.14] - 2018-11-14
|
||||
|
||||
### Added
|
||||
|
||||
* Add method to configure custom error handler to `Query` and `Path` extractors.
|
||||
|
||||
* Add method to configure `SameSite` option in `CookieIdentityPolicy`.
|
||||
|
||||
* By default, `Path` extractor now percent decode all characters. This behaviour can be disabled
|
||||
with `PathConfig::default().disable_decoding()`
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix websockets connection drop if request contains "content-length" header #567
|
||||
|
||||
* Fix keep-alive timer reset
|
||||
|
||||
* HttpServer now treats streaming bodies the same for HTTP/1.x protocols. #549
|
||||
|
||||
* Set nodelay for socket #560
|
||||
|
||||
|
||||
## [0.7.13] - 2018-10-14
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed rustls support
|
||||
|
||||
* HttpServer not sending streamed request body on HTTP/2 requests #544
|
||||
|
||||
|
||||
## [0.7.12] - 2018-10-10
|
||||
|
||||
### Changed
|
||||
|
||||
* Set min version for actix
|
||||
|
||||
* Set min version for actix-net
|
||||
|
||||
|
||||
## [0.7.11] - 2018-10-09
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed 204 responses for http/2
|
||||
|
||||
|
||||
## [0.7.10] - 2018-10-09
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed panic during graceful shutdown
|
||||
|
||||
|
||||
## [0.7.9] - 2018-10-09
|
||||
|
||||
### 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
|
||||
|
122
Cargo.toml
122
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "0.7.18"
|
||||
version = "0.1.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||
readme = "README.md"
|
||||
@ -10,44 +10,21 @@ repository = "https://github.com/actix/actix-web.git"
|
||||
documentation = "https://actix.rs/api/actix-web/stable/actix_web/"
|
||||
categories = ["network-programming", "asynchronous",
|
||||
"web-programming::http-server",
|
||||
"web-programming::http-client",
|
||||
"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", "ssl", "rust-tls", "session", "brotli", "flate2-c"]
|
||||
edition = "2018"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "actix/actix-web", branch = "master" }
|
||||
appveyor = { repository = "fafhrd91/actix-web-hdy9d" }
|
||||
codecov = { repository = "actix/actix-web", branch = "master", service = "github" }
|
||||
travis-ci = { repository = "actix/actix-web2", branch = "master" }
|
||||
codecov = { repository = "actix/actix-web2", branch = "master", service = "github" }
|
||||
|
||||
[lib]
|
||||
name = "actix_web"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["session", "brotli", "flate2-c", "cell"]
|
||||
|
||||
# tls
|
||||
tls = ["native-tls", "tokio-tls", "actix-net/tls"]
|
||||
|
||||
# openssl
|
||||
ssl = ["openssl", "tokio-openssl", "actix-net/ssl"]
|
||||
|
||||
# deprecated, use "ssl"
|
||||
alpn = ["openssl", "tokio-openssl", "actix-net/ssl"]
|
||||
|
||||
# rustls
|
||||
rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots", "actix-net/rust-tls"]
|
||||
|
||||
# unix sockets
|
||||
uds = ["tokio-uds"]
|
||||
|
||||
# sessions feature, session require "ring" crate and c compiler
|
||||
session = ["cookie/secure"]
|
||||
default = ["brotli", "flate2-c"]
|
||||
|
||||
# brotli encoding, requires c compiler
|
||||
brotli = ["brotli2"]
|
||||
@ -58,81 +35,54 @@ flate2-c = ["flate2/miniz-sys"]
|
||||
# rust backend for flate2 crate
|
||||
flate2-rust = ["flate2/rust_backend"]
|
||||
|
||||
cell = ["actix-net/cell"]
|
||||
|
||||
[dependencies]
|
||||
actix = "0.7.9"
|
||||
actix-net = "0.2.6"
|
||||
actix-codec = "0.1.0"
|
||||
#actix-service = "0.2.1"
|
||||
#actix-server = "0.2.1"
|
||||
#actix-utils = "0.2.1"
|
||||
actix-service = { git = "https://github.com/actix/actix-net.git" }
|
||||
actix-server = { git = "https://github.com/actix/actix-net.git" }
|
||||
actix-utils = { git = "https://github.com/actix/actix-net.git" }
|
||||
|
||||
v_htmlescape = "0.4"
|
||||
base64 = "0.10"
|
||||
bitflags = "1.0"
|
||||
failure = "^0.1.2"
|
||||
h2 = "0.1"
|
||||
http = "^0.1.14"
|
||||
httparse = "1.3"
|
||||
actix-rt = "0.1.0"
|
||||
actix-http = { git = "https://github.com/actix/actix-http.git" }
|
||||
actix-router = { git = "https://github.com/actix/actix-net.git" }
|
||||
|
||||
bytes = "0.4"
|
||||
futures = "0.1"
|
||||
derive_more = "0.14"
|
||||
log = "0.4"
|
||||
lazy_static = "1.2"
|
||||
mime = "0.3"
|
||||
mime_guess = "2.0.0-alpha"
|
||||
num_cpus = "1.0"
|
||||
num_cpus = "1.10"
|
||||
percent-encoding = "1.0"
|
||||
rand = "0.6"
|
||||
regex = "1.0"
|
||||
cookie = { version="0.11", features=["percent-encode"] }
|
||||
v_htmlescape = "0.4"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
sha1 = "0.6"
|
||||
smallvec = "0.6"
|
||||
time = "0.1"
|
||||
encoding = "0.2"
|
||||
language-tags = "0.2"
|
||||
lazy_static = "1.0"
|
||||
lazycell = "1.0.0"
|
||||
parking_lot = "0.7"
|
||||
serde_urlencoded = "^0.5.3"
|
||||
url = { version="1.7", features=["query_encoding"] }
|
||||
cookie = { version="0.11", features=["percent-encode"] }
|
||||
parking_lot = "0.7"
|
||||
hashbrown = "0.1"
|
||||
regex = "1"
|
||||
time = "0.1"
|
||||
threadpool = "1.7"
|
||||
|
||||
# compression
|
||||
brotli2 = { version="^0.3.2", optional = true }
|
||||
flate2 = { version="^1.0.2", optional = true, default-features = false }
|
||||
|
||||
# io
|
||||
mio = "^0.6.13"
|
||||
net2 = "0.2"
|
||||
bytes = "0.4"
|
||||
byteorder = "1.2"
|
||||
futures = "0.1"
|
||||
futures-cpupool = "0.1"
|
||||
slab = "0.4"
|
||||
tokio = "0.1"
|
||||
tokio-io = "0.1"
|
||||
tokio-tcp = "0.1"
|
||||
tokio-timer = "0.2.8"
|
||||
tokio-reactor = "0.1"
|
||||
tokio-current-thread = "0.1"
|
||||
|
||||
# native-tls
|
||||
native-tls = { version="0.2", optional = true }
|
||||
tokio-tls = { version="0.2", optional = true }
|
||||
|
||||
# openssl
|
||||
openssl = { version="0.10", optional = true }
|
||||
tokio-openssl = { version="0.2", optional = true }
|
||||
|
||||
#rustls
|
||||
rustls = { version = "0.14", optional = true }
|
||||
tokio-rustls = { version = "0.8", optional = true }
|
||||
webpki = { version = "0.18", optional = true }
|
||||
webpki-roots = { version = "0.15", optional = true }
|
||||
|
||||
# unix sockets
|
||||
tokio-uds = { version="0.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "0.1.0"
|
||||
#actix-server = { version="0.2", features=["ssl"] }
|
||||
actix-server = { git = "https://github.com/actix/actix-net.git", features=["ssl"] }
|
||||
actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] }
|
||||
actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] }
|
||||
rand = "0.6"
|
||||
env_logger = "0.6"
|
||||
serde_derive = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
version_check = "0.1"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 3
|
||||
|
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 => (),
|
||||
};
|
||||
}
|
54
examples/basic.rs
Normal file
54
examples/basic.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use futures::IntoFuture;
|
||||
|
||||
use actix_http::{h1, http::Method, Response};
|
||||
use actix_server::Server;
|
||||
use actix_web2::{middleware, App, Error, HttpRequest, Resource};
|
||||
|
||||
fn index(req: HttpRequest) -> &'static str {
|
||||
println!("REQ: {:?}", req);
|
||||
"Hello world!\r\n"
|
||||
}
|
||||
|
||||
fn index_async(req: HttpRequest) -> impl IntoFuture<Item = &'static str, Error = Error> {
|
||||
println!("REQ: {:?}", req);
|
||||
Ok("Hello world!\r\n")
|
||||
}
|
||||
|
||||
fn no_params() -> &'static str {
|
||||
"Hello world!\r\n"
|
||||
}
|
||||
|
||||
fn main() {
|
||||
::std::env::set_var("RUST_LOG", "actix_server=info,actix_web2=info");
|
||||
env_logger::init();
|
||||
let sys = actix_rt::System::new("hello-world");
|
||||
|
||||
Server::build()
|
||||
.bind("test", "127.0.0.1:8080", || {
|
||||
h1::H1Service::new(
|
||||
App::new()
|
||||
.middleware(
|
||||
middleware::DefaultHeaders::new().header("X-Version", "0.2"),
|
||||
)
|
||||
.middleware(middleware::Compress::default())
|
||||
.resource("/resource1/index.html", |r| r.get(index))
|
||||
.service(
|
||||
"/resource2/index.html",
|
||||
Resource::new()
|
||||
.middleware(
|
||||
middleware::DefaultHeaders::new()
|
||||
.header("X-Version-R2", "0.3"),
|
||||
)
|
||||
.default_resource(|r| r.to(|| Response::MethodNotAllowed()))
|
||||
.method(Method::GET, |r| r.to_async(index_async)),
|
||||
)
|
||||
.service("/test1.html", Resource::new().to(|| "Test\r\n"))
|
||||
.service("/", Resource::new().to(no_params)),
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.workers(1)
|
||||
.start();
|
||||
|
||||
let _ = sys.run();
|
||||
}
|
@ -1,5 +1,2 @@
|
||||
max_width = 89
|
||||
reorder_imports = true
|
||||
#wrap_comments = true
|
||||
fn_args_density = "Compressed"
|
||||
#use_small_heuristics = false
|
||||
|
648
src/app.rs
Normal file
648
src/app.rs
Normal file
@ -0,0 +1,648 @@
|
||||
use std::cell::RefCell;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_http::body::{Body, MessageBody};
|
||||
use actix_http::{Extensions, PayloadStream, Request, Response};
|
||||
use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url};
|
||||
use actix_service::{
|
||||
AndThenNewService, ApplyNewService, IntoNewService, IntoNewTransform, NewService,
|
||||
NewTransform, Service,
|
||||
};
|
||||
use futures::future::{ok, Either, FutureResult};
|
||||
use futures::{Async, Future, IntoFuture, Poll};
|
||||
|
||||
use crate::helpers::{
|
||||
BoxedHttpNewService, BoxedHttpService, DefaultNewService, HttpDefaultNewService,
|
||||
};
|
||||
use crate::resource::Resource;
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
use crate::state::{State, StateFactory, StateFactoryResult};
|
||||
|
||||
type BoxedResponse = Box<Future<Item = ServiceResponse, Error = ()>>;
|
||||
|
||||
pub trait HttpServiceFactory<Request> {
|
||||
type Factory: NewService<Request = Request>;
|
||||
|
||||
fn rdef(&self) -> &ResourceDef;
|
||||
|
||||
fn create(self) -> Self::Factory;
|
||||
}
|
||||
|
||||
/// Application builder
|
||||
pub struct App<P, B, T> {
|
||||
services: Vec<(
|
||||
ResourceDef,
|
||||
BoxedHttpNewService<ServiceRequest<P>, ServiceResponse>,
|
||||
)>,
|
||||
default: Option<Rc<HttpDefaultNewService<ServiceRequest<P>, ServiceResponse>>>,
|
||||
defaults: Vec<
|
||||
Rc<
|
||||
RefCell<
|
||||
Option<Rc<HttpDefaultNewService<ServiceRequest<P>, ServiceResponse>>>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
endpoint: T,
|
||||
factory_ref: Rc<RefCell<Option<AppFactory<P>>>>,
|
||||
extensions: Extensions,
|
||||
state: Vec<Box<StateFactory>>,
|
||||
_t: PhantomData<(P, B)>,
|
||||
}
|
||||
|
||||
impl App<PayloadStream, Body, AppEntry<PayloadStream>> {
|
||||
/// Create application with empty state. Application can
|
||||
/// be configured with a builder-like pattern.
|
||||
pub fn new() -> Self {
|
||||
App::create()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for App<PayloadStream, Body, AppEntry<PayloadStream>> {
|
||||
fn default() -> Self {
|
||||
App::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl App<PayloadStream, Body, AppEntry<PayloadStream>> {
|
||||
/// 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 state<S: 'static>(mut self, state: S) -> Self {
|
||||
self.state.push(Box::new(State::new(state)));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set application state. This function is
|
||||
/// similar to `.state()` but it accepts state factory. State get
|
||||
/// constructed asynchronously during application initialization.
|
||||
pub fn state_factory<S, F, Out>(mut self, state: F) -> Self
|
||||
where
|
||||
F: Fn() -> Out + 'static,
|
||||
Out: IntoFuture + 'static,
|
||||
Out::Error: std::fmt::Debug,
|
||||
{
|
||||
self.state.push(Box::new(State::new(state)));
|
||||
self
|
||||
}
|
||||
|
||||
fn create() -> Self {
|
||||
let fref = Rc::new(RefCell::new(None));
|
||||
App {
|
||||
services: Vec::new(),
|
||||
default: None,
|
||||
defaults: Vec::new(),
|
||||
endpoint: AppEntry::new(fref.clone()),
|
||||
factory_ref: fref,
|
||||
extensions: Extensions::new(),
|
||||
state: Vec::new(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /// Application router builder
|
||||
// pub struct AppRouter<S, T, P> {
|
||||
// services: Vec<(
|
||||
// ResourceDef,
|
||||
// BoxedHttpNewService<ServiceRequest<P>, Response>,
|
||||
// )>,
|
||||
// default: Option<Rc<HttpDefaultNewService<ServiceRequest<P>, Response>>>,
|
||||
// defaults:
|
||||
// Vec<Rc<RefCell<Option<Rc<HttpDefaultNewService<ServiceRequest<P>, Response>>>>>>,
|
||||
// state: AppState<S>,
|
||||
// endpoint: T,
|
||||
// factory_ref: Rc<RefCell<Option<AppFactory<P>>>>,
|
||||
// extensions: Extensions,
|
||||
// _t: PhantomData<P>,
|
||||
// }
|
||||
|
||||
impl<P, B, T> App<P, B, T>
|
||||
where
|
||||
P: 'static,
|
||||
B: MessageBody,
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = (),
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
/// 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,ignore
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{http, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource("/users/{userid}/{friend}", |r| {
|
||||
/// r.get(|r| r.to(|_| HttpResponse::Ok()));
|
||||
/// r.head(|r| r.to(|_| HttpResponse::MethodNotAllowed()))
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
pub fn resource<F, U>(mut self, path: &str, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(Resource<P>) -> Resource<P, U>,
|
||||
U: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse,
|
||||
Error = (),
|
||||
InitError = (),
|
||||
> + 'static,
|
||||
{
|
||||
let rdef = ResourceDef::new(path);
|
||||
let resource = f(Resource::new());
|
||||
self.defaults.push(resource.get_default());
|
||||
self.services.push((
|
||||
rdef,
|
||||
Box::new(HttpNewService::new(resource.into_new_service())),
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
/// Default resource to be used if no matching route could be found.
|
||||
///
|
||||
/// Default resource works with resources only and does not work with
|
||||
/// custom services.
|
||||
pub fn default_resource<F, R, U>(mut self, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(Resource<P>) -> R,
|
||||
R: IntoNewService<U>,
|
||||
U: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse,
|
||||
Error = (),
|
||||
> + 'static,
|
||||
{
|
||||
// create and configure default resource
|
||||
self.default = Some(Rc::new(Box::new(DefaultNewService::new(
|
||||
f(Resource::new()).into_new_service(),
|
||||
))));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Register resource handler service.
|
||||
pub fn service<R, F, U>(mut self, rdef: R, factory: F) -> Self
|
||||
where
|
||||
R: Into<ResourceDef>,
|
||||
F: IntoNewService<U>,
|
||||
U: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse,
|
||||
Error = (),
|
||||
> + 'static,
|
||||
{
|
||||
self.services.push((
|
||||
rdef.into(),
|
||||
Box::new(HttpNewService::new(factory.into_new_service())),
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
/// Register a middleware.
|
||||
pub fn middleware<M, B1, F>(
|
||||
self,
|
||||
mw: F,
|
||||
) -> App<
|
||||
P,
|
||||
B1,
|
||||
impl NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse<B1>,
|
||||
Error = (),
|
||||
InitError = (),
|
||||
>,
|
||||
>
|
||||
where
|
||||
M: NewTransform<
|
||||
T::Service,
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse<B1>,
|
||||
Error = (),
|
||||
InitError = (),
|
||||
>,
|
||||
B1: MessageBody,
|
||||
F: IntoNewTransform<M, T::Service>,
|
||||
{
|
||||
let endpoint = ApplyNewService::new(mw, self.endpoint);
|
||||
App {
|
||||
endpoint,
|
||||
state: self.state,
|
||||
services: self.services,
|
||||
default: self.default,
|
||||
defaults: Vec::new(),
|
||||
factory_ref: self.factory_ref,
|
||||
extensions: Extensions::new(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,ignore
|
||||
/// # 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<N, U>(self, _name: N, _url: U) -> Self
|
||||
where
|
||||
N: 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
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P: 'static, B: MessageBody>
|
||||
IntoNewService<AndThenNewService<AppStateFactory<P>, T, ()>> for App<P, B, T>
|
||||
where
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = (),
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
fn into_new_service(self) -> AndThenNewService<AppStateFactory<P>, T, ()> {
|
||||
// update resource default service
|
||||
if self.default.is_some() {
|
||||
for default in &self.defaults {
|
||||
if default.borrow_mut().is_none() {
|
||||
*default.borrow_mut() = self.default.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set factory
|
||||
*self.factory_ref.borrow_mut() = Some(AppFactory {
|
||||
services: Rc::new(self.services),
|
||||
});
|
||||
|
||||
AppStateFactory {
|
||||
state: self.state,
|
||||
extensions: Rc::new(RefCell::new(Rc::new(self.extensions))),
|
||||
_t: PhantomData,
|
||||
}
|
||||
.and_then(self.endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
/// Service factory to convert `Request` to a `ServiceRequest<S>`
|
||||
pub struct AppStateFactory<P> {
|
||||
state: Vec<Box<StateFactory>>,
|
||||
extensions: Rc<RefCell<Rc<Extensions>>>,
|
||||
_t: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: 'static> NewService for AppStateFactory<P> {
|
||||
type Request = Request<P>;
|
||||
type Response = ServiceRequest<P>;
|
||||
type Error = ();
|
||||
type InitError = ();
|
||||
type Service = AppStateService<P>;
|
||||
type Future = AppStateFactoryResult<P>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
AppStateFactoryResult {
|
||||
state: self.state.iter().map(|s| s.construct()).collect(),
|
||||
extensions: self.extensions.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct AppStateFactoryResult<P> {
|
||||
state: Vec<Box<StateFactoryResult>>,
|
||||
extensions: Rc<RefCell<Rc<Extensions>>>,
|
||||
_t: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P> Future for AppStateFactoryResult<P> {
|
||||
type Item = AppStateService<P>;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(extensions) = Rc::get_mut(&mut *self.extensions.borrow_mut()) {
|
||||
let mut idx = 0;
|
||||
while idx < self.state.len() {
|
||||
if let Async::Ready(_) = self.state[idx].poll_result(extensions)? {
|
||||
self.state.remove(idx);
|
||||
} else {
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
if !self.state.is_empty() {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
} else {
|
||||
log::warn!("Multiple copies of app extensions exists");
|
||||
}
|
||||
|
||||
Ok(Async::Ready(AppStateService {
|
||||
extensions: self.extensions.borrow().clone(),
|
||||
_t: PhantomData,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Service to convert `Request` to a `ServiceRequest<S>`
|
||||
pub struct AppStateService<P> {
|
||||
extensions: Rc<Extensions>,
|
||||
_t: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P> Service for AppStateService<P> {
|
||||
type Request = Request<P>;
|
||||
type Response = ServiceRequest<P>;
|
||||
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: Request<P>) -> Self::Future {
|
||||
ok(ServiceRequest::new(
|
||||
Path::new(Url::new(req.uri().clone())),
|
||||
req,
|
||||
self.extensions.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppFactory<P> {
|
||||
services: Rc<
|
||||
Vec<(
|
||||
ResourceDef,
|
||||
BoxedHttpNewService<ServiceRequest<P>, ServiceResponse>,
|
||||
)>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl<P> NewService for AppFactory<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = ();
|
||||
type InitError = ();
|
||||
type Service = AppService<P>;
|
||||
type Future = CreateAppService<P>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
CreateAppService {
|
||||
fut: self
|
||||
.services
|
||||
.iter()
|
||||
.map(|(path, service)| {
|
||||
CreateAppServiceItem::Future(
|
||||
Some(path.clone()),
|
||||
service.new_service(&()),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type HttpServiceFut<P> =
|
||||
Box<Future<Item = BoxedHttpService<ServiceRequest<P>, ServiceResponse>, Error = ()>>;
|
||||
|
||||
/// Create app service
|
||||
#[doc(hidden)]
|
||||
pub struct CreateAppService<P> {
|
||||
fut: Vec<CreateAppServiceItem<P>>,
|
||||
}
|
||||
|
||||
enum CreateAppServiceItem<P> {
|
||||
Future(Option<ResourceDef>, HttpServiceFut<P>),
|
||||
Service(
|
||||
ResourceDef,
|
||||
BoxedHttpService<ServiceRequest<P>, ServiceResponse>,
|
||||
),
|
||||
}
|
||||
|
||||
impl<P> Future for CreateAppService<P> {
|
||||
type Item = AppService<P>;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let mut done = true;
|
||||
|
||||
// poll http services
|
||||
for item in &mut self.fut {
|
||||
let res = match item {
|
||||
CreateAppServiceItem::Future(ref mut path, ref mut fut) => {
|
||||
match fut.poll()? {
|
||||
Async::Ready(service) => Some((path.take().unwrap(), service)),
|
||||
Async::NotReady => {
|
||||
done = false;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
CreateAppServiceItem::Service(_, _) => continue,
|
||||
};
|
||||
|
||||
if let Some((path, service)) = res {
|
||||
*item = CreateAppServiceItem::Service(path, service);
|
||||
}
|
||||
}
|
||||
|
||||
if done {
|
||||
let router = self
|
||||
.fut
|
||||
.drain(..)
|
||||
.fold(Router::build(), |mut router, item| {
|
||||
match item {
|
||||
CreateAppServiceItem::Service(path, service) => {
|
||||
router.rdef(path, service)
|
||||
}
|
||||
CreateAppServiceItem::Future(_, _) => unreachable!(),
|
||||
}
|
||||
router
|
||||
});
|
||||
Ok(Async::Ready(AppService {
|
||||
router: router.finish(),
|
||||
ready: None,
|
||||
}))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppService<P> {
|
||||
router: Router<BoxedHttpService<ServiceRequest<P>, ServiceResponse>>,
|
||||
ready: Option<(ServiceRequest<P>, ResourceInfo)>,
|
||||
}
|
||||
|
||||
impl<P> Service for AppService<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = ();
|
||||
type Future = Either<BoxedResponse, FutureResult<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
if self.ready.is_none() {
|
||||
Ok(Async::Ready(()))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
|
||||
if let Some((srv, _info)) = self.router.recognize_mut(req.match_info_mut()) {
|
||||
Either::A(srv.call(req))
|
||||
} else {
|
||||
let req = req.into_request();
|
||||
Either::B(ok(ServiceResponse::new(req, Response::NotFound().finish())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppServiceResponse(Box<Future<Item = ServiceResponse, Error = ()>>);
|
||||
|
||||
impl Future for AppServiceResponse {
|
||||
type Item = ServiceResponse;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
self.0.poll().map_err(|_| panic!())
|
||||
}
|
||||
}
|
||||
|
||||
struct HttpNewService<P: 'static, T: NewService<Request = ServiceRequest<P>>>(T);
|
||||
|
||||
impl<P, T> HttpNewService<P, T>
|
||||
where
|
||||
T: NewService<Request = ServiceRequest<P>, Response = ServiceResponse, Error = ()>,
|
||||
T::Future: 'static,
|
||||
<T::Service as Service>::Future: 'static,
|
||||
{
|
||||
pub fn new(service: T) -> Self {
|
||||
HttpNewService(service)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: 'static, T> NewService for HttpNewService<P, T>
|
||||
where
|
||||
T: NewService<Request = ServiceRequest<P>, Response = ServiceResponse, Error = ()>,
|
||||
T::Future: 'static,
|
||||
T::Service: 'static,
|
||||
<T::Service as Service>::Future: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = ();
|
||||
type InitError = ();
|
||||
type Service = BoxedHttpService<ServiceRequest<P>, Self::Response>;
|
||||
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| {
|
||||
let service: BoxedHttpService<_, _> = Box::new(HttpServiceWrapper {
|
||||
service,
|
||||
_t: PhantomData,
|
||||
});
|
||||
Ok(service)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
struct HttpServiceWrapper<T: Service, P> {
|
||||
service: T,
|
||||
_t: PhantomData<(P,)>,
|
||||
}
|
||||
|
||||
impl<T, P> Service for HttpServiceWrapper<T, P>
|
||||
where
|
||||
T::Future: 'static,
|
||||
T: Service<Request = ServiceRequest<P>, Response = ServiceResponse, Error = ()>,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = ();
|
||||
type Future = BoxedResponse;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready().map_err(|_| ())
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
Box::new(self.service.call(req))
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct AppEntry<P> {
|
||||
factory: Rc<RefCell<Option<AppFactory<P>>>>,
|
||||
}
|
||||
|
||||
impl<P> AppEntry<P> {
|
||||
fn new(factory: Rc<RefCell<Option<AppFactory<P>>>>) -> Self {
|
||||
AppEntry { factory }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> NewService for AppEntry<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = ();
|
||||
type InitError = ();
|
||||
type Service = AppService<P>;
|
||||
type Future = CreateAppService<P>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
self.factory.borrow_mut().as_mut().unwrap().new_service(&())
|
||||
}
|
||||
}
|
@ -1,17 +1,21 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_http::http::ContentEncoding;
|
||||
use actix_http::{Error, Request, Response};
|
||||
use actix_service::Service;
|
||||
use futures::{Async, Future, Poll};
|
||||
|
||||
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 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 scope::Scope;
|
||||
// use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request};
|
||||
use with::WithFactory;
|
||||
|
||||
/// Application
|
||||
@ -21,7 +25,7 @@ pub struct HttpApplication<S = ()> {
|
||||
prefix_len: usize,
|
||||
inner: Rc<Inner<S>>,
|
||||
filters: Option<Vec<Box<Predicate<S>>>>,
|
||||
middlewares: Rc<Vec<Box<Middleware<S>>>>,
|
||||
// middlewares: Rc<Vec<Box<Middleware<S>>>>,
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -30,16 +34,16 @@ pub struct Inner<S> {
|
||||
encoding: ContentEncoding,
|
||||
}
|
||||
|
||||
impl<S: 'static> PipelineHandler<S> for Inner<S> {
|
||||
#[inline]
|
||||
fn encoding(&self) -> ContentEncoding {
|
||||
self.encoding
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
// fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
// self.router.handle(req)
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<S: 'static> HttpApplication<S> {
|
||||
#[cfg(test)]
|
||||
@ -54,10 +58,18 @@ impl<S: 'static> HttpApplication<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> HttpHandler for HttpApplication<S> {
|
||||
type Task = Pipeline<S, Inner<S>>;
|
||||
impl<S: 'static> Service for HttpApplication<S> {
|
||||
// type Task = Pipeline<S, Inner<S>>;
|
||||
type Request = actix_http::Request;
|
||||
type Response = actix_http::Response;
|
||||
type Error = Error;
|
||||
type Future = Box<Future<Item = Response, Error = Error>>;
|
||||
|
||||
fn handle(&self, msg: Request) -> Result<Pipeline<S, Inner<S>>, Request> {
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, msg: actix_http::Request) -> Self::Future {
|
||||
let m = {
|
||||
if self.prefix_len == 0 {
|
||||
true
|
||||
@ -70,11 +82,12 @@ impl<S: 'static> HttpHandler for HttpApplication<S> {
|
||||
};
|
||||
if m {
|
||||
if let Some(ref filters) = self.filters {
|
||||
for filter in filters {
|
||||
if !filter.check(&msg, &self.state) {
|
||||
return Err(msg);
|
||||
}
|
||||
}
|
||||
//for filter in filters {
|
||||
// if !filter.check(&msg, &self.state) {
|
||||
//return Err(msg);
|
||||
unimplemented!()
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
let info = self
|
||||
@ -83,10 +96,12 @@ impl<S: 'static> HttpHandler for HttpApplication<S> {
|
||||
.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))
|
||||
// let req = HttpRequest::new(msg, Rc::clone(&self.state), info);
|
||||
// Ok(Pipeline::new(req, inner))
|
||||
unimplemented!()
|
||||
} else {
|
||||
Err(msg)
|
||||
// Err(msg)
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,7 +111,7 @@ struct ApplicationParts<S> {
|
||||
prefix: String,
|
||||
router: Router<S>,
|
||||
encoding: ContentEncoding,
|
||||
middlewares: Vec<Box<Middleware<S>>>,
|
||||
// middlewares: Vec<Box<Middleware<S>>>,
|
||||
filters: Vec<Box<Predicate<S>>>,
|
||||
}
|
||||
|
||||
@ -106,55 +121,10 @@ 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
|
||||
@ -205,26 +175,6 @@ where
|
||||
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.
|
||||
@ -263,42 +213,42 @@ where
|
||||
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 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.
|
||||
///
|
||||
@ -377,51 +327,6 @@ where
|
||||
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
|
||||
@ -458,15 +363,15 @@ where
|
||||
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
|
||||
}
|
||||
// /// 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
|
||||
@ -521,93 +426,93 @@ where
|
||||
inner,
|
||||
filters,
|
||||
state: Rc::new(parts.state),
|
||||
middlewares: Rc::new(parts.middlewares),
|
||||
// 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() })
|
||||
}
|
||||
// /// 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>,
|
||||
}
|
||||
// struct BoxedApplication<S> {
|
||||
// app: HttpApplication<S>,
|
||||
// }
|
||||
|
||||
impl<S: 'static> HttpHandler for BoxedApplication<S> {
|
||||
type Task = Box<HttpHandlerTask>;
|
||||
// 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
|
||||
})
|
||||
}
|
||||
}
|
||||
// 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>;
|
||||
// impl<S: 'static> IntoHttpHandler for App<S> {
|
||||
// type Handler = HttpApplication<S>;
|
||||
|
||||
fn into_handler(mut self) -> HttpApplication<S> {
|
||||
self.finish()
|
||||
}
|
||||
}
|
||||
// fn into_handler(mut self) -> HttpApplication<S> {
|
||||
// self.finish()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<'a, S: 'static> IntoHttpHandler for &'a mut App<S> {
|
||||
type Handler = HttpApplication<S>;
|
||||
// impl<'a, S: 'static> IntoHttpHandler for &'a mut App<S> {
|
||||
// type Handler = HttpApplication<S>;
|
||||
|
||||
fn into_handler(self) -> HttpApplication<S> {
|
||||
self.finish()
|
||||
}
|
||||
}
|
||||
// fn into_handler(self) -> HttpApplication<S> {
|
||||
// self.finish()
|
||||
// }
|
||||
// }
|
||||
|
||||
#[doc(hidden)]
|
||||
impl<S: 'static> Iterator for App<S> {
|
||||
type Item = HttpApplication<S>;
|
||||
// #[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
|
||||
}
|
||||
}
|
||||
}
|
||||
// fn next(&mut self) -> Option<Self::Item> {
|
||||
// if self.parts.is_some() {
|
||||
// Some(self.finish())
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@ -773,7 +678,8 @@ mod tests {
|
||||
.route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok())
|
||||
.route("/test", Method::POST, |_: HttpRequest| {
|
||||
HttpResponse::Created()
|
||||
}).finish();
|
||||
})
|
||||
.finish();
|
||||
|
||||
let req = TestRequest::with_uri("/test").method(Method::GET).request();
|
||||
let resp = app.run(req);
|
||||
|
74
src/blocking.rs
Normal file
74
src/blocking.rs
Normal file
@ -0,0 +1,74 @@
|
||||
//! Thread pool for blocking operations
|
||||
|
||||
use futures::sync::oneshot;
|
||||
use futures::{Async, Future, Poll};
|
||||
use parking_lot::Mutex;
|
||||
use threadpool::ThreadPool;
|
||||
|
||||
/// Env variable for default cpu pool size
|
||||
const ENV_CPU_POOL_VAR: &str = "ACTIX_CPU_POOL";
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub(crate) static ref DEFAULT_POOL: Mutex<ThreadPool> = {
|
||||
let default = match std::env::var(ENV_CPU_POOL_VAR) {
|
||||
Ok(val) => {
|
||||
if let Ok(val) = val.parse() {
|
||||
val
|
||||
} else {
|
||||
log::error!("Can not parse ACTIX_CPU_POOL value");
|
||||
num_cpus::get() * 5
|
||||
}
|
||||
}
|
||||
Err(_) => num_cpus::get() * 5,
|
||||
};
|
||||
Mutex::new(
|
||||
threadpool::Builder::new()
|
||||
.thread_name("actix-web".to_owned())
|
||||
.num_threads(8)
|
||||
.build(),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static POOL: ThreadPool = {
|
||||
DEFAULT_POOL.lock().clone()
|
||||
};
|
||||
}
|
||||
|
||||
pub enum BlockingError<E> {
|
||||
Error(E),
|
||||
Canceled,
|
||||
}
|
||||
|
||||
/// Execute blocking function on a thread pool, returns future that resolves
|
||||
/// to result of the function execution.
|
||||
pub fn run<F, I, E>(f: F) -> CpuFuture<I, E>
|
||||
where
|
||||
F: FnOnce() -> Result<I, E>,
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
POOL.with(move |pool| {
|
||||
let _ = tx.send(f());
|
||||
});
|
||||
|
||||
CpuFuture { rx }
|
||||
}
|
||||
|
||||
pub struct CpuFuture<I, E> {
|
||||
rx: oneshot::Receiver<Result<I, E>>,
|
||||
}
|
||||
|
||||
impl<I, E> Future for CpuFuture<I, E> {
|
||||
type Item = I;
|
||||
type Error = BlockingError<E>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let res =
|
||||
futures::try_ready!(self.rx.poll().map_err(|_| BlockingError::Canceled));
|
||||
match res {
|
||||
Ok(val) => Ok(Async::Ready(val)),
|
||||
Err(err) => Err(BlockingError::Error(err)),
|
||||
}
|
||||
}
|
||||
}
|
391
src/body.rs
391
src/body.rs
@ -1,391 +0,0 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::Stream;
|
||||
use std::borrow::Cow;
|
||||
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
|
||||
pub type BodyStream = Box<Stream<Item = Bytes, Error = Error>>;
|
||||
|
||||
/// Represents various types of http message body.
|
||||
pub enum Body {
|
||||
/// Empty response. `Content-Length` header is set to `0`
|
||||
Empty,
|
||||
/// Specific response body.
|
||||
Binary(Binary),
|
||||
/// 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>),
|
||||
}
|
||||
|
||||
/// Represents various types of binary body.
|
||||
/// `Content-Length` header is set to length of the body.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Binary {
|
||||
/// Bytes body
|
||||
Bytes(Bytes),
|
||||
/// Static slice
|
||||
Slice(&'static [u8]),
|
||||
/// Shared string body
|
||||
#[doc(hidden)]
|
||||
SharedString(Arc<String>),
|
||||
/// Shared vec body
|
||||
SharedVec(Arc<Vec<u8>>),
|
||||
}
|
||||
|
||||
impl Body {
|
||||
/// Does this body streaming.
|
||||
#[inline]
|
||||
pub fn is_streaming(&self) -> bool {
|
||||
match *self {
|
||||
Body::Streaming(_) | Body::Actor(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this binary body.
|
||||
#[inline]
|
||||
pub fn is_binary(&self) -> bool {
|
||||
match *self {
|
||||
Body::Binary(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this binary empy.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match *self {
|
||||
Body::Empty => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create body from slice (copy)
|
||||
pub fn from_slice(s: &[u8]) -> Body {
|
||||
Body::Binary(Binary::Bytes(Bytes::from(s)))
|
||||
}
|
||||
|
||||
/// Is this binary body.
|
||||
#[inline]
|
||||
pub(crate) fn binary(self) -> Binary {
|
||||
match self {
|
||||
Body::Binary(b) => b,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Body {
|
||||
fn eq(&self, other: &Body) -> bool {
|
||||
match *self {
|
||||
Body::Empty => match *other {
|
||||
Body::Empty => true,
|
||||
_ => false,
|
||||
},
|
||||
Body::Binary(ref b) => match *other {
|
||||
Body::Binary(ref b2) => b == b2,
|
||||
_ => false,
|
||||
},
|
||||
Body::Streaming(_) | Body::Actor(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Body {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
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(_)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Body
|
||||
where
|
||||
T: Into<Binary>,
|
||||
{
|
||||
fn from(b: T) -> Body {
|
||||
Body::Binary(b.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<ActorHttpContext>> for Body {
|
||||
fn from(ctx: Box<ActorHttpContext>) -> Body {
|
||||
Body::Actor(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Binary {
|
||||
#[inline]
|
||||
/// Returns `true` if body is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Length of body in bytes
|
||||
pub fn len(&self) -> usize {
|
||||
match *self {
|
||||
Binary::Bytes(ref bytes) => bytes.len(),
|
||||
Binary::Slice(slice) => slice.len(),
|
||||
Binary::SharedString(ref s) => s.len(),
|
||||
Binary::SharedVec(ref s) => s.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create binary body from slice
|
||||
pub fn from_slice(s: &[u8]) -> Binary {
|
||||
Binary::Bytes(Bytes::from(s))
|
||||
}
|
||||
|
||||
/// Convert Binary to a Bytes instance
|
||||
pub fn take(&mut self) -> Bytes {
|
||||
mem::replace(self, Binary::Slice(b"")).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Binary {
|
||||
fn clone(&self) -> Binary {
|
||||
match *self {
|
||||
Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()),
|
||||
Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)),
|
||||
Binary::SharedString(ref s) => Binary::SharedString(s.clone()),
|
||||
Binary::SharedVec(ref s) => Binary::SharedVec(s.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Bytes> for Binary {
|
||||
fn into(self) -> Bytes {
|
||||
match self {
|
||||
Binary::Bytes(bytes) => bytes,
|
||||
Binary::Slice(slice) => Bytes::from(slice),
|
||||
Binary::SharedString(s) => Bytes::from(s.as_str()),
|
||||
Binary::SharedVec(s) => Bytes::from(AsRef::<[u8]>::as_ref(s.as_ref())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for Binary {
|
||||
fn from(s: &'static str) -> Binary {
|
||||
Binary::Slice(s.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static [u8]> for Binary {
|
||||
fn from(s: &'static [u8]) -> Binary {
|
||||
Binary::Slice(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Binary {
|
||||
fn from(vec: Vec<u8>) -> Binary {
|
||||
Binary::Bytes(Bytes::from(vec))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, [u8]>> for Binary {
|
||||
fn from(b: Cow<'static, [u8]>) -> Binary {
|
||||
match b {
|
||||
Cow::Borrowed(s) => Binary::Slice(s),
|
||||
Cow::Owned(vec) => Binary::Bytes(Bytes::from(vec)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Binary {
|
||||
fn from(s: String) -> Binary {
|
||||
Binary::Bytes(Bytes::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, str>> for Binary {
|
||||
fn from(s: Cow<'static, str>) -> Binary {
|
||||
match s {
|
||||
Cow::Borrowed(s) => Binary::Slice(s.as_ref()),
|
||||
Cow::Owned(s) => Binary::Bytes(Bytes::from(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a String> for Binary {
|
||||
fn from(s: &'a String) -> Binary {
|
||||
Binary::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bytes> for Binary {
|
||||
fn from(s: Bytes) -> Binary {
|
||||
Binary::Bytes(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BytesMut> for Binary {
|
||||
fn from(s: BytesMut) -> Binary {
|
||||
Binary::Bytes(s.freeze())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<String>> for Binary {
|
||||
fn from(body: Arc<String>) -> Binary {
|
||||
Binary::SharedString(body)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Arc<String>> for Binary {
|
||||
fn from(body: &'a Arc<String>) -> Binary {
|
||||
Binary::SharedString(Arc::clone(body))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<Vec<u8>>> for Binary {
|
||||
fn from(body: Arc<Vec<u8>>) -> Binary {
|
||||
Binary::SharedVec(body)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Arc<Vec<u8>>> for Binary {
|
||||
fn from(body: &'a Arc<Vec<u8>>) -> Binary {
|
||||
Binary::SharedVec(Arc::clone(body))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Binary {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
match *self {
|
||||
Binary::Bytes(ref bytes) => bytes.as_ref(),
|
||||
Binary::Slice(slice) => slice,
|
||||
Binary::SharedString(ref s) => s.as_bytes(),
|
||||
Binary::SharedVec(ref s) => s.as_ref().as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::*;
|
||||
|
||||
#[test]
|
||||
fn test_body_is_streaming() {
|
||||
assert_eq!(Body::Empty.is_streaming(), false);
|
||||
assert_eq!(Body::Binary(Binary::from("")).is_streaming(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_empty() {
|
||||
assert_eq!(Binary::from("").is_empty(), true);
|
||||
assert_eq!(Binary::from("test").is_empty(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_str() {
|
||||
assert_eq!(Binary::from("test").len(), 4);
|
||||
assert_eq!(Binary::from("test").as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cow_str() {
|
||||
let cow: Cow<'static, str> = Cow::Borrowed("test");
|
||||
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||
let cow: Cow<'static, str> = Cow::Owned("test".to_owned());
|
||||
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_bytes() {
|
||||
assert_eq!(Binary::from(b"test".as_ref()).len(), 4);
|
||||
assert_eq!(Binary::from(b"test".as_ref()).as_ref(), b"test");
|
||||
assert_eq!(Binary::from_slice(b"test".as_ref()).len(), 4);
|
||||
assert_eq!(Binary::from_slice(b"test".as_ref()).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vec() {
|
||||
assert_eq!(Binary::from(Vec::from("test")).len(), 4);
|
||||
assert_eq!(Binary::from(Vec::from("test")).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes() {
|
||||
assert_eq!(Binary::from(Bytes::from("test")).len(), 4);
|
||||
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cow_bytes() {
|
||||
let cow: Cow<'static, [u8]> = Cow::Borrowed(b"test");
|
||||
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||
let cow: Cow<'static, [u8]> = Cow::Owned(Vec::from("test"));
|
||||
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arc_string() {
|
||||
let b = Arc::new("test".to_owned());
|
||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
|
||||
assert_eq!(Binary::from(&b).len(), 4);
|
||||
assert_eq!(Binary::from(&b).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string() {
|
||||
let b = "test".to_owned();
|
||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
|
||||
assert_eq!(Binary::from(&b).len(), 4);
|
||||
assert_eq!(Binary::from(&b).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shared_vec() {
|
||||
let b = Arc::new(Vec::from(&b"test"[..]));
|
||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(b.clone()).as_ref(), &b"test"[..]);
|
||||
assert_eq!(Binary::from(&b).len(), 4);
|
||||
assert_eq!(Binary::from(&b).as_ref(), &b"test"[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes_mut() {
|
||||
let b = BytesMut::from("test");
|
||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(b).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_into() {
|
||||
let bytes = Bytes::from_static(b"test");
|
||||
let b: Bytes = Binary::from("test").into();
|
||||
assert_eq!(b, bytes);
|
||||
let b: Bytes = Binary::from(bytes.clone()).into();
|
||||
assert_eq!(b, bytes);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,120 +0,0 @@
|
||||
//! Http client api
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate actix_web;
|
||||
//! # extern crate actix;
|
||||
//! # extern crate futures;
|
||||
//! # extern crate tokio;
|
||||
//! # use std::process;
|
||||
//! use actix_web::client;
|
||||
//! use futures::Future;
|
||||
//!
|
||||
//! 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 actix;
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio;
|
||||
/// # extern crate env_logger;
|
||||
/// # use std::process;
|
||||
/// use actix_web::client;
|
||||
/// use futures::Future;
|
||||
///
|
||||
/// 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.len() >= 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,553 +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_inner::dev::Request;
|
||||
use actix::{Addr, 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,783 +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 actix;
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio;
|
||||
/// # use futures::Future;
|
||||
/// # use std::process;
|
||||
/// use actix_web::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_part().map(|port| port.as_u16()) {
|
||||
None | Some(80) | Some(443) => write!(wrt, "{}", host),
|
||||
Some(port) => write!(wrt, "{}:{}", host, port),
|
||||
};
|
||||
|
||||
match wrt.get_mut().take().freeze().try_into() {
|
||||
Ok(value) => {
|
||||
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(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(|_| ())
|
||||
}
|
||||
}
|
455
src/de.rs
455
src/de.rs
@ -1,455 +0,0 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use serde::de::{self, Deserializer, Error as DeError, Visitor};
|
||||
|
||||
use httprequest::HttpRequest;
|
||||
use param::ParamsIter;
|
||||
use uri::RESERVED_QUOTER;
|
||||
|
||||
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! percent_decode_if_needed {
|
||||
($value:expr, $decode:expr) => {
|
||||
if $decode {
|
||||
if let Some(ref mut value) = RESERVED_QUOTER.requote($value.as_bytes()) {
|
||||
Rc::make_mut(value).parse()
|
||||
} else {
|
||||
$value.parse()
|
||||
}
|
||||
} else {
|
||||
$value.parse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_parsed = percent_decode_if_needed!(&self.req.match_info()[0], self.decode)
|
||||
.map_err(|_| de::value::Error::custom(
|
||||
format!("can not parse {:?} to a {}", &self.req.match_info()[0], $tp)
|
||||
))?;
|
||||
visitor.$visit_fn(v_parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PathDeserializer<'de, S: 'de> {
|
||||
req: &'de HttpRequest<S>,
|
||||
decode: bool,
|
||||
}
|
||||
|
||||
impl<'de, S: 'de> PathDeserializer<'de, S> {
|
||||
pub fn new(req: &'de HttpRequest<S>, decode: bool) -> Self {
|
||||
PathDeserializer { req, decode }
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
decode: self.decode,
|
||||
})
|
||||
}
|
||||
|
||||
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(),
|
||||
decode: self.decode,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
decode: self.decode,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_seq(ParamsSeq {
|
||||
params: self.req.match_info().iter(),
|
||||
decode: self.decode,
|
||||
})
|
||||
}
|
||||
|
||||
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_str, 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)>,
|
||||
decode: bool,
|
||||
}
|
||||
|
||||
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, decode: self.decode })
|
||||
} 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_parsed = percent_decode_if_needed!(&self.value, self.decode)
|
||||
.map_err(|_| de::value::Error::custom(
|
||||
format!("can not parse {:?} to a {}", &self.value, $tp)
|
||||
))?;
|
||||
visitor.$visit_fn(v_parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Value<'de> {
|
||||
value: &'de str,
|
||||
decode: bool,
|
||||
}
|
||||
|
||||
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>,
|
||||
decode: bool,
|
||||
}
|
||||
|
||||
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, decode: self.decode })?)),
|
||||
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"))
|
||||
}
|
||||
}
|
1426
src/error.rs
1426
src/error.rs
File diff suppressed because it is too large
Load Diff
@ -1,114 +0,0 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::hash::{BuildHasherDefault, Hasher};
|
||||
|
||||
struct IdHasher {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl Default for IdHasher {
|
||||
fn default() -> IdHasher {
|
||||
IdHasher { id: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Hasher for IdHasher {
|
||||
fn write(&mut self, bytes: &[u8]) {
|
||||
for &x in bytes {
|
||||
self.id.wrapping_add(u64::from(x));
|
||||
}
|
||||
}
|
||||
|
||||
fn write_u64(&mut self, u: u64) {
|
||||
self.id = u;
|
||||
}
|
||||
|
||||
fn finish(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
type AnyMap = HashMap<TypeId, Box<Any>, BuildHasherDefault<IdHasher>>;
|
||||
|
||||
#[derive(Default)]
|
||||
/// A type map of request extensions.
|
||||
pub struct Extensions {
|
||||
map: AnyMap,
|
||||
}
|
||||
|
||||
impl Extensions {
|
||||
/// Create an empty `Extensions`.
|
||||
#[inline]
|
||||
pub fn new() -> Extensions {
|
||||
Extensions {
|
||||
map: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a type into this `Extensions`.
|
||||
///
|
||||
/// If a extension of this type already existed, it will
|
||||
/// be returned.
|
||||
pub fn insert<T: 'static>(&mut self, val: T) {
|
||||
self.map.insert(TypeId::of::<T>(), Box::new(val));
|
||||
}
|
||||
|
||||
/// Get a reference to a type previously inserted on this `Extensions`.
|
||||
pub fn get<T: 'static>(&self) -> Option<&T> {
|
||||
self.map
|
||||
.get(&TypeId::of::<T>())
|
||||
.and_then(|boxed| (&**boxed as &(Any + 'static)).downcast_ref())
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a type previously inserted on this `Extensions`.
|
||||
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||
self.map
|
||||
.get_mut(&TypeId::of::<T>())
|
||||
.and_then(|boxed| (&mut **boxed as &mut (Any + 'static)).downcast_mut())
|
||||
}
|
||||
|
||||
/// Remove a type from this `Extensions`.
|
||||
///
|
||||
/// If a extension of this type existed, it will be returned.
|
||||
pub fn remove<T: 'static>(&mut self) -> Option<T> {
|
||||
self.map.remove(&TypeId::of::<T>()).and_then(|boxed| {
|
||||
(boxed as Box<Any + 'static>)
|
||||
.downcast()
|
||||
.ok()
|
||||
.map(|boxed| *boxed)
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear the `Extensions` of all inserted extensions.
|
||||
#[inline]
|
||||
pub fn clear(&mut self) {
|
||||
self.map.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Extensions {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Extensions").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extensions() {
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct MyType(i32);
|
||||
|
||||
let mut extensions = Extensions::new();
|
||||
|
||||
extensions.insert(5i32);
|
||||
extensions.insert(MyType(10));
|
||||
|
||||
assert_eq!(extensions.get(), Some(&5i32));
|
||||
assert_eq!(extensions.get_mut(), Some(&mut 5i32));
|
||||
|
||||
assert_eq!(extensions.remove::<i32>(), Some(5i32));
|
||||
assert!(extensions.get::<i32>().is_none());
|
||||
|
||||
assert_eq!(extensions.get::<bool>(), None);
|
||||
assert_eq!(extensions.get(), Some(&MyType(10)));
|
||||
}
|
1280
src/extractor.rs
1280
src/extractor.rs
File diff suppressed because it is too large
Load Diff
327
src/filter.rs
Normal file
327
src/filter.rs
Normal file
@ -0,0 +1,327 @@
|
||||
//! Route match predicates
|
||||
#![allow(non_snake_case)]
|
||||
use actix_http::http::{self, header, HttpTryFrom};
|
||||
|
||||
use crate::request::HttpRequest;
|
||||
|
||||
/// Trait defines resource 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 Filter {
|
||||
/// Check if request matches predicate
|
||||
fn check(&self, request: &HttpRequest) -> bool;
|
||||
}
|
||||
|
||||
/// Return filter that matches if any of supplied filters.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web2::{filter, 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<F: Filter + 'static>(filter: F) -> AnyFilter {
|
||||
AnyFilter(vec![Box::new(filter)])
|
||||
}
|
||||
|
||||
/// Matches if any of supplied filters matche.
|
||||
pub struct AnyFilter(Vec<Box<Filter>>);
|
||||
|
||||
impl AnyFilter {
|
||||
/// Add filter to a list of filters to check
|
||||
pub fn or<F: Filter + 'static>(mut self, filter: F) -> Self {
|
||||
self.0.push(Box::new(filter));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter for AnyFilter {
|
||||
fn check(&self, req: &HttpRequest) -> bool {
|
||||
for p in &self.0 {
|
||||
if p.check(req) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Return filter that matches if all of supplied filters match.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # 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<F: Filter + 'static>(filter: F) -> AllFilter {
|
||||
AllFilter(vec![Box::new(filter)])
|
||||
}
|
||||
|
||||
/// Matches if all of supplied filters matche.
|
||||
pub struct AllFilter(Vec<Box<Filter>>);
|
||||
|
||||
impl AllFilter {
|
||||
/// Add new predicate to list of predicates to check
|
||||
pub fn and<F: Filter + 'static>(mut self, filter: F) -> Self {
|
||||
self.0.push(Box::new(filter));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter for AllFilter {
|
||||
fn check(&self, request: &HttpRequest) -> bool {
|
||||
for p in &self.0 {
|
||||
if !p.check(request) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Return predicate that matches if supplied predicate does not match.
|
||||
pub fn Not<F: Filter + 'static>(filter: F) -> NotFilter {
|
||||
NotFilter(Box::new(filter))
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct NotFilter(Box<Filter>);
|
||||
|
||||
impl Filter for NotFilter {
|
||||
fn check(&self, request: &HttpRequest) -> bool {
|
||||
!self.0.check(request)
|
||||
}
|
||||
}
|
||||
|
||||
/// Http method predicate
|
||||
#[doc(hidden)]
|
||||
pub struct MethodFilter(http::Method);
|
||||
|
||||
impl Filter for MethodFilter {
|
||||
fn check(&self, request: &HttpRequest) -> bool {
|
||||
request.method() == self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Predicate to match *GET* http method
|
||||
pub fn Get() -> MethodFilter {
|
||||
MethodFilter(http::Method::GET)
|
||||
}
|
||||
|
||||
/// Predicate to match *POST* http method
|
||||
pub fn Post() -> MethodFilter {
|
||||
MethodFilter(http::Method::POST)
|
||||
}
|
||||
|
||||
/// Predicate to match *PUT* http method
|
||||
pub fn Put() -> MethodFilter {
|
||||
MethodFilter(http::Method::PUT)
|
||||
}
|
||||
|
||||
/// Predicate to match *DELETE* http method
|
||||
pub fn Delete() -> MethodFilter {
|
||||
MethodFilter(http::Method::DELETE)
|
||||
}
|
||||
|
||||
/// Predicate to match *HEAD* http method
|
||||
pub fn Head() -> MethodFilter {
|
||||
MethodFilter(http::Method::HEAD)
|
||||
}
|
||||
|
||||
/// Predicate to match *OPTIONS* http method
|
||||
pub fn Options() -> MethodFilter {
|
||||
MethodFilter(http::Method::OPTIONS)
|
||||
}
|
||||
|
||||
/// Predicate to match *CONNECT* http method
|
||||
pub fn Connect() -> MethodFilter {
|
||||
MethodFilter(http::Method::CONNECT)
|
||||
}
|
||||
|
||||
/// Predicate to match *PATCH* http method
|
||||
pub fn Patch() -> MethodFilter {
|
||||
MethodFilter(http::Method::PATCH)
|
||||
}
|
||||
|
||||
/// Predicate to match *TRACE* http method
|
||||
pub fn Trace() -> MethodFilter {
|
||||
MethodFilter(http::Method::TRACE)
|
||||
}
|
||||
|
||||
/// Predicate to match specified http method
|
||||
pub fn Method(method: http::Method) -> MethodFilter {
|
||||
MethodFilter(method)
|
||||
}
|
||||
|
||||
/// Return predicate that matches if request contains specified header and
|
||||
/// value.
|
||||
pub fn Header(name: &'static str, value: &'static str) -> HeaderFilter {
|
||||
HeaderFilter(
|
||||
header::HeaderName::try_from(name).unwrap(),
|
||||
header::HeaderValue::from_static(value),
|
||||
)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct HeaderFilter(header::HeaderName, header::HeaderValue);
|
||||
|
||||
impl Filter for HeaderFilter {
|
||||
fn check(&self, req: &HttpRequest) -> 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,ignore
|
||||
/// # 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<H: AsRef<str>>(host: H) -> HostFilter {
|
||||
HostFilter(host.as_ref().to_string(), None)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct HostFilter(String, Option<String>);
|
||||
|
||||
impl HostFilter {
|
||||
/// Set reuest scheme to match
|
||||
pub fn scheme<H: AsRef<str>>(&mut self, scheme: H) {
|
||||
self.1 = Some(scheme.as_ref().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter for HostFilter {
|
||||
fn check(&self, _req: &HttpRequest) -> 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()
|
||||
// }
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use actix_http::http::{header, Method};
|
||||
// use actix_http::test::TestRequest;
|
||||
|
||||
// use super::*;
|
||||
|
||||
// #[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()));
|
||||
// }
|
||||
// }
|
240
src/framed_app.rs
Normal file
240
src/framed_app.rs
Normal file
@ -0,0 +1,240 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_http::h1::Codec;
|
||||
use actix_http::{Request, Response, SendResponse};
|
||||
use actix_router::{Path, Router, Url};
|
||||
use actix_service::{IntoNewService, NewService, Service};
|
||||
use actix_utils::cloneable::CloneableService;
|
||||
use futures::{Async, Future, Poll};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use crate::app::{HttpServiceFactory, State};
|
||||
use crate::framed_handler::FramedRequest;
|
||||
use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService};
|
||||
use crate::request::Request as WebRequest;
|
||||
|
||||
pub type FRequest<T> = (Request, Framed<T, Codec>);
|
||||
type BoxedResponse = Box<Future<Item = (), Error = ()>>;
|
||||
|
||||
/// Application builder
|
||||
pub struct FramedApp<T, S = ()> {
|
||||
services: Vec<(String, BoxedHttpNewService<FramedRequest<S, T>, ()>)>,
|
||||
state: State<S>,
|
||||
}
|
||||
|
||||
impl<T: 'static> FramedApp<T, ()> {
|
||||
pub fn new() -> Self {
|
||||
FramedApp {
|
||||
services: Vec::new(),
|
||||
state: State::new(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, S: 'static> FramedApp<T, S> {
|
||||
pub fn with(state: S) -> FramedApp<T, S> {
|
||||
FramedApp {
|
||||
services: Vec::new(),
|
||||
state: State::new(state),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn service<U>(mut self, factory: U) -> Self
|
||||
where
|
||||
U: HttpServiceFactory<S>,
|
||||
U::Factory: NewService<Request = FramedRequest<S, T>, Response = ()> + 'static,
|
||||
<U::Factory as NewService>::Future: 'static,
|
||||
<U::Factory as NewService>::Service: Service<Request = FramedRequest<S, T>>,
|
||||
<<U::Factory as NewService>::Service as Service>::Future: 'static,
|
||||
{
|
||||
let path = factory.path().to_string();
|
||||
self.services.push((
|
||||
path,
|
||||
Box::new(HttpNewService::new(factory.create(self.state.clone()))),
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn register_service<U>(&mut self, factory: U)
|
||||
where
|
||||
U: HttpServiceFactory<S>,
|
||||
U::Factory: NewService<Request = FramedRequest<S, T>, Response = ()> + 'static,
|
||||
<U::Factory as NewService>::Future: 'static,
|
||||
<U::Factory as NewService>::Service: Service<Request = FramedRequest<S, T>>,
|
||||
<<U::Factory as NewService>::Service as Service>::Future: 'static,
|
||||
{
|
||||
let path = factory.path().to_string();
|
||||
self.services.push((
|
||||
path,
|
||||
Box::new(HttpNewService::new(factory.create(self.state.clone()))),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, S: 'static> IntoNewService<FramedAppFactory<S, T>> for FramedApp<T, S>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
{
|
||||
fn into_new_service(self) -> FramedAppFactory<S, T> {
|
||||
FramedAppFactory {
|
||||
state: self.state,
|
||||
services: Rc::new(self.services),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FramedAppFactory<S, T> {
|
||||
state: State<S>,
|
||||
services: Rc<Vec<(String, BoxedHttpNewService<FramedRequest<S, T>, ()>)>>,
|
||||
_t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<S: 'static, T: 'static> NewService for FramedAppFactory<S, T>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type Request = FRequest<T>;
|
||||
type Response = ();
|
||||
type Error = ();
|
||||
type InitError = ();
|
||||
type Service = CloneableService<FramedAppService<S, T>>;
|
||||
type Future = CreateService<S, T>;
|
||||
|
||||
fn new_service(&self) -> Self::Future {
|
||||
CreateService {
|
||||
fut: self
|
||||
.services
|
||||
.iter()
|
||||
.map(|(path, service)| {
|
||||
CreateServiceItem::Future(Some(path.clone()), service.new_service())
|
||||
})
|
||||
.collect(),
|
||||
state: self.state.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct CreateService<S, T> {
|
||||
fut: Vec<CreateServiceItem<S, T>>,
|
||||
state: State<S>,
|
||||
}
|
||||
|
||||
enum CreateServiceItem<S, T> {
|
||||
Future(
|
||||
Option<String>,
|
||||
Box<Future<Item = BoxedHttpService<FramedRequest<S, T>, ()>, Error = ()>>,
|
||||
),
|
||||
Service(String, BoxedHttpService<FramedRequest<S, T>, ()>),
|
||||
}
|
||||
|
||||
impl<S: 'static, T: 'static> Future for CreateService<S, T>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type Item = CloneableService<FramedAppService<S, T>>;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let mut done = true;
|
||||
|
||||
// poll http services
|
||||
for item in &mut self.fut {
|
||||
let res = match item {
|
||||
CreateServiceItem::Future(ref mut path, ref mut fut) => {
|
||||
match fut.poll()? {
|
||||
Async::Ready(service) => Some((path.take().unwrap(), service)),
|
||||
Async::NotReady => {
|
||||
done = false;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
CreateServiceItem::Service(_, _) => continue,
|
||||
};
|
||||
|
||||
if let Some((path, service)) = res {
|
||||
*item = CreateServiceItem::Service(path, service);
|
||||
}
|
||||
}
|
||||
|
||||
if done {
|
||||
let router = self
|
||||
.fut
|
||||
.drain(..)
|
||||
.fold(Router::build(), |mut router, item| {
|
||||
match item {
|
||||
CreateServiceItem::Service(path, service) => {
|
||||
router.path(&path, service)
|
||||
}
|
||||
CreateServiceItem::Future(_, _) => unreachable!(),
|
||||
}
|
||||
router
|
||||
});
|
||||
Ok(Async::Ready(CloneableService::new(FramedAppService {
|
||||
router: router.finish(),
|
||||
state: self.state.clone(),
|
||||
// default: self.default.take().expect("something is wrong"),
|
||||
})))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FramedAppService<S, T> {
|
||||
state: State<S>,
|
||||
router: Router<BoxedHttpService<FramedRequest<S, T>, ()>>,
|
||||
}
|
||||
|
||||
impl<S: 'static, T: 'static> Service for FramedAppService<S, T>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type Request = FRequest<T>;
|
||||
type Response = ();
|
||||
type Error = ();
|
||||
type Future = BoxedResponse;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
// let mut ready = true;
|
||||
// for service in &mut self.services {
|
||||
// if let Async::NotReady = service.poll_ready()? {
|
||||
// ready = false;
|
||||
// }
|
||||
// }
|
||||
// if ready {
|
||||
// Ok(Async::Ready(()))
|
||||
// } else {
|
||||
// Ok(Async::NotReady)
|
||||
// }
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, (req, framed): (Request, Framed<T, Codec>)) -> Self::Future {
|
||||
let mut path = Path::new(Url::new(req.uri().clone()));
|
||||
|
||||
if let Some((srv, _info)) = self.router.recognize_mut(&mut path) {
|
||||
return srv.call(FramedRequest::new(
|
||||
WebRequest::new(self.state.clone(), req, path),
|
||||
framed,
|
||||
));
|
||||
}
|
||||
// for item in &mut self.services {
|
||||
// req = match item.handle(req) {
|
||||
// Ok(fut) => return fut,
|
||||
// Err(req) => req,
|
||||
// };
|
||||
// }
|
||||
// self.default.call(req)
|
||||
Box::new(
|
||||
SendResponse::send(framed, Response::NotFound().finish().into())
|
||||
.map(|_| ())
|
||||
.map_err(|_| ()),
|
||||
)
|
||||
}
|
||||
}
|
379
src/framed_handler.rs
Normal file
379
src/framed_handler.rs
Normal file
@ -0,0 +1,379 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_http::{h1::Codec, Error};
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::{ok, FutureResult};
|
||||
use futures::{Async, Future, IntoFuture, Poll};
|
||||
use log::error;
|
||||
|
||||
use crate::handler::FromRequest;
|
||||
use crate::request::Request;
|
||||
|
||||
pub struct FramedError<Io> {
|
||||
pub err: Error,
|
||||
pub framed: Framed<Io, Codec>,
|
||||
}
|
||||
|
||||
pub struct FramedRequest<S, Io, Ex = ()> {
|
||||
req: Request<S>,
|
||||
framed: Framed<Io, Codec>,
|
||||
param: Ex,
|
||||
}
|
||||
|
||||
impl<S, Io> FramedRequest<S, Io, ()> {
|
||||
pub fn new(req: Request<S>, framed: Framed<Io, Codec>) -> Self {
|
||||
Self {
|
||||
req,
|
||||
framed,
|
||||
param: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Io, Ex> FramedRequest<S, Io, Ex> {
|
||||
pub fn request(&self) -> &Request<S> {
|
||||
&self.req
|
||||
}
|
||||
|
||||
pub fn request_mut(&mut self) -> &mut Request<S> {
|
||||
&mut self.req
|
||||
}
|
||||
|
||||
pub fn into_parts(self) -> (Request<S>, Framed<Io, Codec>, Ex) {
|
||||
(self.req, self.framed, self.param)
|
||||
}
|
||||
|
||||
pub fn map<Ex2, F>(self, op: F) -> FramedRequest<S, Io, Ex2>
|
||||
where
|
||||
F: FnOnce(Ex) -> Ex2,
|
||||
{
|
||||
FramedRequest {
|
||||
req: self.req,
|
||||
framed: self.framed,
|
||||
param: op(self.param),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// T handler converter factory
|
||||
pub trait FramedFactory<S, Io, Ex, T, R, E>: Clone + 'static
|
||||
where
|
||||
R: IntoFuture<Item = (), Error = E>,
|
||||
E: Into<Error>,
|
||||
{
|
||||
fn call(&self, framed: Framed<Io, Codec>, param: T, extra: Ex) -> R;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct FramedHandle<F, S, Io, Ex, T, R, E>
|
||||
where
|
||||
F: FramedFactory<S, Io, Ex, T, R, E>,
|
||||
R: IntoFuture<Item = (), Error = E>,
|
||||
E: Into<Error>,
|
||||
{
|
||||
hnd: F,
|
||||
_t: PhantomData<(S, Io, Ex, T, R, E)>,
|
||||
}
|
||||
|
||||
impl<F, S, Io, Ex, T, R, E> FramedHandle<F, S, Io, Ex, T, R, E>
|
||||
where
|
||||
F: FramedFactory<S, Io, Ex, T, R, E>,
|
||||
R: IntoFuture<Item = (), Error = E>,
|
||||
E: Into<Error>,
|
||||
{
|
||||
pub fn new(hnd: F) -> Self {
|
||||
FramedHandle {
|
||||
hnd,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<F, S, Io, Ex, T, R, E> NewService for FramedHandle<F, S, Io, Ex, T, R, E>
|
||||
where
|
||||
F: FramedFactory<S, Io, Ex, T, R, E>,
|
||||
R: IntoFuture<Item = (), Error = E>,
|
||||
E: Into<Error>,
|
||||
{
|
||||
type Request = (T, FramedRequest<S, Io, Ex>);
|
||||
type Response = ();
|
||||
type Error = FramedError<Io>;
|
||||
type InitError = ();
|
||||
type Service = FramedHandleService<F, S, Io, Ex, T, R, E>;
|
||||
type Future = FutureResult<Self::Service, ()>;
|
||||
|
||||
fn new_service(&self) -> Self::Future {
|
||||
ok(FramedHandleService {
|
||||
hnd: self.hnd.clone(),
|
||||
_t: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct FramedHandleService<F, S, Io, Ex, T, R, E>
|
||||
where
|
||||
F: FramedFactory<S, Io, Ex, T, R, E>,
|
||||
R: IntoFuture<Item = (), Error = E>,
|
||||
E: Into<Error>,
|
||||
{
|
||||
hnd: F,
|
||||
_t: PhantomData<(S, Io, Ex, T, R, E)>,
|
||||
}
|
||||
|
||||
impl<F, S, Io, Ex, T, R, E> Service for FramedHandleService<F, S, Io, Ex, T, R, E>
|
||||
where
|
||||
F: FramedFactory<S, Io, Ex, T, R, E>,
|
||||
R: IntoFuture<Item = (), Error = E>,
|
||||
E: Into<Error>,
|
||||
{
|
||||
type Request = (T, FramedRequest<S, Io, Ex>);
|
||||
type Response = ();
|
||||
type Error = FramedError<Io>;
|
||||
type Future = FramedHandleServiceResponse<Io, R::Future>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, (param, framed): (T, FramedRequest<S, Io, Ex>)) -> Self::Future {
|
||||
let (_, framed, ex) = framed.into_parts();
|
||||
FramedHandleServiceResponse {
|
||||
fut: self.hnd.call(framed, param, ex).into_future(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct FramedHandleServiceResponse<Io, F> {
|
||||
fut: F,
|
||||
_t: PhantomData<Io>,
|
||||
}
|
||||
|
||||
impl<Io, F> Future for FramedHandleServiceResponse<Io, F>
|
||||
where
|
||||
F: Future<Item = ()>,
|
||||
F::Error: Into<Error>,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = FramedError<Io>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll() {
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Ok(Async::Ready(res)) => Ok(Async::Ready(res.into())),
|
||||
Err(e) => {
|
||||
let e: Error = e.into();
|
||||
error!("Error in handler: {:?}", e);
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FramedExtract<S, Io, Ex, T>
|
||||
where
|
||||
T: FromRequest<S>,
|
||||
{
|
||||
cfg: Rc<T::Config>,
|
||||
_t: PhantomData<(Io, Ex)>,
|
||||
}
|
||||
|
||||
impl<S, Io, Ex, T> FramedExtract<S, Io, Ex, T>
|
||||
where
|
||||
T: FromRequest<S> + 'static,
|
||||
{
|
||||
pub fn new(cfg: T::Config) -> FramedExtract<S, Io, Ex, T> {
|
||||
FramedExtract {
|
||||
cfg: Rc::new(cfg),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<S, Io, Ex, T> NewService for FramedExtract<S, Io, Ex, T>
|
||||
where
|
||||
T: FromRequest<S> + 'static,
|
||||
{
|
||||
type Request = FramedRequest<S, Io, Ex>;
|
||||
type Response = (T, FramedRequest<S, Io, Ex>);
|
||||
type Error = FramedError<Io>;
|
||||
type InitError = ();
|
||||
type Service = FramedExtractService<S, Io, Ex, T>;
|
||||
type Future = FutureResult<Self::Service, ()>;
|
||||
|
||||
fn new_service(&self) -> Self::Future {
|
||||
ok(FramedExtractService {
|
||||
cfg: self.cfg.clone(),
|
||||
_t: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FramedExtractService<S, Io, Ex, T>
|
||||
where
|
||||
T: FromRequest<S>,
|
||||
{
|
||||
cfg: Rc<T::Config>,
|
||||
_t: PhantomData<(Io, Ex)>,
|
||||
}
|
||||
|
||||
impl<S, Io, Ex, T> Service for FramedExtractService<S, Io, Ex, T>
|
||||
where
|
||||
T: FromRequest<S> + 'static,
|
||||
{
|
||||
type Request = FramedRequest<S, Io, Ex>;
|
||||
type Response = (T, FramedRequest<S, Io, Ex>);
|
||||
type Error = FramedError<Io>;
|
||||
type Future = FramedExtractResponse<S, Io, Ex, T>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: FramedRequest<S, Io, Ex>) -> Self::Future {
|
||||
FramedExtractResponse {
|
||||
fut: T::from_request(&req.request(), self.cfg.as_ref()),
|
||||
req: Some(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FramedExtractResponse<S, Io, Ex, T>
|
||||
where
|
||||
T: FromRequest<S> + 'static,
|
||||
{
|
||||
req: Option<FramedRequest<S, Io, Ex>>,
|
||||
fut: T::Future,
|
||||
}
|
||||
|
||||
impl<S, Io, Ex, T> Future for FramedExtractResponse<S, Io, Ex, T>
|
||||
where
|
||||
T: FromRequest<S> + 'static,
|
||||
{
|
||||
type Item = (T, FramedRequest<S, Io, Ex>);
|
||||
type Error = FramedError<Io>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll() {
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Ok(Async::Ready(item)) => Ok(Async::Ready((item, self.req.take().unwrap()))),
|
||||
Err(err) => Err(FramedError {
|
||||
err: err.into(),
|
||||
framed: self.req.take().unwrap().into_parts().1,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! factory_tuple ({ ($(($nex:tt, $Ex:ident)),+), $(($n:tt, $T:ident)),+} => {
|
||||
impl<Func, S, Io, $($Ex,)+ $($T,)+ Res, Err> FramedFactory<S, Io, ($($Ex,)+), ($($T,)+), Res, Err> for Func
|
||||
where Func: Fn(Framed<Io, Codec>, $($Ex,)+ $($T,)+) -> Res + Clone + 'static,
|
||||
$($T: FromRequest<S> + 'static,)+
|
||||
Res: IntoFuture<Item=(), Error=Err> + 'static,
|
||||
Err: Into<Error>,
|
||||
{
|
||||
fn call(&self, framed: Framed<Io, Codec>, param: ($($T,)+), extra: ($($Ex,)+)) -> Res {
|
||||
(self)(framed, $(extra.$nex,)+ $(param.$n,)+)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
macro_rules! factory_tuple_unit ({$(($n:tt, $T:ident)),+} => {
|
||||
impl<Func, S, Io, $($T,)+ Res, Err> FramedFactory<S, Io, (), ($($T,)+), Res, Err> for Func
|
||||
where Func: Fn(Framed<Io, Codec>, $($T,)+) -> Res + Clone + 'static,
|
||||
$($T: FromRequest<S> + 'static,)+
|
||||
Res: IntoFuture<Item=(), Error=Err> + 'static,
|
||||
Err: Into<Error>,
|
||||
{
|
||||
fn call(&self, framed: Framed<Io, Codec>, param: ($($T,)+), _extra: () ) -> Res {
|
||||
(self)(framed, $(param.$n,)+)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
mod m {
|
||||
use super::*;
|
||||
|
||||
factory_tuple_unit!((0, A));
|
||||
factory_tuple!(((0, Aex)), (0, A));
|
||||
factory_tuple!(((0, Aex), (1, Bex)), (0, A));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A));
|
||||
|
||||
factory_tuple_unit!((0, A), (1, B));
|
||||
factory_tuple!(((0, Aex)), (0, A), (1, B));
|
||||
factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B));
|
||||
|
||||
factory_tuple_unit!((0, A), (1, B), (2, C));
|
||||
factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C));
|
||||
factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C));
|
||||
|
||||
factory_tuple_unit!((0, A), (1, B), (2, C), (3, D));
|
||||
factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D));
|
||||
factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D));
|
||||
|
||||
factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E));
|
||||
factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E));
|
||||
factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E));
|
||||
|
||||
factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F));
|
||||
factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F));
|
||||
factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F));
|
||||
|
||||
factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G));
|
||||
factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G));
|
||||
factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G));
|
||||
|
||||
factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H));
|
||||
factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H));
|
||||
factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H));
|
||||
|
||||
factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I));
|
||||
factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I));
|
||||
factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I));
|
||||
|
||||
factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J));
|
||||
factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J));
|
||||
factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J));
|
||||
factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J));
|
||||
}
|
448
src/framed_route.rs
Normal file
448
src/framed_route.rs
Normal file
@ -0,0 +1,448 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use actix_http::http::{HeaderName, HeaderValue, Method};
|
||||
use actix_http::Error;
|
||||
use actix_service::{IntoNewService, NewService, NewServiceExt, Service};
|
||||
use futures::{try_ready, Async, Future, IntoFuture, Poll};
|
||||
use log::{debug, error};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use crate::app::{HttpServiceFactory, State};
|
||||
use crate::framed_handler::{
|
||||
FramedError, FramedExtract, FramedFactory, FramedHandle, FramedRequest,
|
||||
};
|
||||
use crate::handler::FromRequest;
|
||||
|
||||
/// Resource route definition
|
||||
///
|
||||
/// Route uses builder-like pattern for configuration.
|
||||
/// If handler is not explicitly set, default *404 Not Found* handler is used.
|
||||
pub struct FramedRoute<Io, T, S = ()> {
|
||||
service: T,
|
||||
pattern: String,
|
||||
methods: Vec<Method>,
|
||||
headers: Vec<(HeaderName, HeaderValue)>,
|
||||
state: PhantomData<(S, Io)>,
|
||||
}
|
||||
|
||||
impl<Io, S> FramedRoute<Io, (), S> {
|
||||
pub fn build(path: &str) -> FramedRoutePatternBuilder<Io, S> {
|
||||
FramedRoutePatternBuilder::new(path)
|
||||
}
|
||||
|
||||
pub fn get(path: &str) -> FramedRoutePatternBuilder<Io, S> {
|
||||
FramedRoutePatternBuilder::new(path).method(Method::GET)
|
||||
}
|
||||
|
||||
pub fn post(path: &str) -> FramedRoutePatternBuilder<Io, S> {
|
||||
FramedRoutePatternBuilder::new(path).method(Method::POST)
|
||||
}
|
||||
|
||||
pub fn put(path: &str) -> FramedRoutePatternBuilder<Io, S> {
|
||||
FramedRoutePatternBuilder::new(path).method(Method::PUT)
|
||||
}
|
||||
|
||||
pub fn delete(path: &str) -> FramedRoutePatternBuilder<Io, S> {
|
||||
FramedRoutePatternBuilder::new(path).method(Method::DELETE)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io, T, S> FramedRoute<Io, T, S>
|
||||
where
|
||||
T: NewService<
|
||||
Request = FramedRequest<S, Io>,
|
||||
Response = (),
|
||||
Error = FramedError<Io>,
|
||||
> + 'static,
|
||||
{
|
||||
pub fn new<F: IntoNewService<T>>(pattern: &str, factory: F) -> Self {
|
||||
FramedRoute {
|
||||
pattern: pattern.to_string(),
|
||||
service: factory.into_new_service(),
|
||||
headers: Vec::new(),
|
||||
methods: Vec::new(),
|
||||
state: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn method(mut self, method: Method) -> Self {
|
||||
self.methods.push(method);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn header(mut self, name: HeaderName, value: HeaderValue) -> Self {
|
||||
self.headers.push((name, value));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io, T, S> HttpServiceFactory<S> for FramedRoute<Io, T, S>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite + 'static,
|
||||
T: NewService<
|
||||
Request = FramedRequest<S, Io>,
|
||||
Response = (),
|
||||
Error = FramedError<Io>,
|
||||
> + 'static,
|
||||
T::Service: 'static,
|
||||
{
|
||||
type Factory = FramedRouteFactory<Io, T, S>;
|
||||
|
||||
fn path(&self) -> &str {
|
||||
&self.pattern
|
||||
}
|
||||
|
||||
fn create(self, state: State<S>) -> Self::Factory {
|
||||
FramedRouteFactory {
|
||||
state,
|
||||
service: self.service,
|
||||
pattern: self.pattern,
|
||||
methods: self.methods,
|
||||
headers: self.headers,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FramedRouteFactory<Io, T, S> {
|
||||
service: T,
|
||||
pattern: String,
|
||||
methods: Vec<Method>,
|
||||
headers: Vec<(HeaderName, HeaderValue)>,
|
||||
state: State<S>,
|
||||
_t: PhantomData<Io>,
|
||||
}
|
||||
|
||||
impl<Io, T, S> NewService for FramedRouteFactory<Io, T, S>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite + 'static,
|
||||
T: NewService<
|
||||
Request = FramedRequest<S, Io>,
|
||||
Response = (),
|
||||
Error = FramedError<Io>,
|
||||
> + 'static,
|
||||
T::Service: 'static,
|
||||
{
|
||||
type Request = FramedRequest<S, Io>;
|
||||
type Response = T::Response;
|
||||
type Error = ();
|
||||
type InitError = T::InitError;
|
||||
type Service = FramedRouteService<Io, T::Service, S>;
|
||||
type Future = CreateRouteService<Io, T, S>;
|
||||
|
||||
fn new_service(&self) -> Self::Future {
|
||||
CreateRouteService {
|
||||
fut: self.service.new_service(),
|
||||
pattern: self.pattern.clone(),
|
||||
methods: self.methods.clone(),
|
||||
headers: self.headers.clone(),
|
||||
state: self.state.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CreateRouteService<Io, T: NewService, S> {
|
||||
fut: T::Future,
|
||||
pattern: String,
|
||||
methods: Vec<Method>,
|
||||
headers: Vec<(HeaderName, HeaderValue)>,
|
||||
state: State<S>,
|
||||
_t: PhantomData<Io>,
|
||||
}
|
||||
|
||||
impl<Io, T, S> Future for CreateRouteService<Io, T, S>
|
||||
where
|
||||
T: NewService<
|
||||
Request = FramedRequest<S, Io>,
|
||||
Response = (),
|
||||
Error = FramedError<Io>,
|
||||
>,
|
||||
{
|
||||
type Item = FramedRouteService<Io, T::Service, S>;
|
||||
type Error = T::InitError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let service = try_ready!(self.fut.poll());
|
||||
|
||||
Ok(Async::Ready(FramedRouteService {
|
||||
service,
|
||||
state: self.state.clone(),
|
||||
pattern: self.pattern.clone(),
|
||||
methods: self.methods.clone(),
|
||||
headers: self.headers.clone(),
|
||||
_t: PhantomData,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FramedRouteService<Io, T, S> {
|
||||
service: T,
|
||||
pattern: String,
|
||||
methods: Vec<Method>,
|
||||
headers: Vec<(HeaderName, HeaderValue)>,
|
||||
state: State<S>,
|
||||
_t: PhantomData<Io>,
|
||||
}
|
||||
|
||||
impl<Io, T, S> Service for FramedRouteService<Io, T, S>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite + 'static,
|
||||
T: Service<Request = FramedRequest<S, Io>, Response = (), Error = FramedError<Io>>
|
||||
+ 'static,
|
||||
{
|
||||
type Request = FramedRequest<S, Io>;
|
||||
type Response = ();
|
||||
type Error = ();
|
||||
type Future = FramedRouteServiceResponse<Io, T::Future>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready().map_err(|e| {
|
||||
debug!("Service not available: {}", e.err);
|
||||
()
|
||||
})
|
||||
}
|
||||
|
||||
fn call(&mut self, req: FramedRequest<S, Io>) -> Self::Future {
|
||||
FramedRouteServiceResponse {
|
||||
fut: self.service.call(req),
|
||||
send: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl<Io, T, S> HttpService<(Request, Framed<Io, Codec>)> for FramedRouteService<Io, T, S>
|
||||
// where
|
||||
// Io: AsyncRead + AsyncWrite + 'static,
|
||||
// S: 'static,
|
||||
// T: Service<FramedRequest<S, Io>, Response = (), Error = FramedError<Io>> + 'static,
|
||||
// {
|
||||
// fn handle(
|
||||
// &mut self,
|
||||
// (req, framed): (Request, Framed<Io, Codec>),
|
||||
// ) -> Result<Self::Future, (Request, Framed<Io, Codec>)> {
|
||||
// if self.methods.is_empty()
|
||||
// || !self.methods.is_empty() && self.methods.contains(req.method())
|
||||
// {
|
||||
// if let Some(params) = self.pattern.match_with_params(&req, 0) {
|
||||
// return Ok(FramedRouteServiceResponse {
|
||||
// fut: self.service.call(FramedRequest::new(
|
||||
// WebRequest::new(self.state.clone(), req, params),
|
||||
// framed,
|
||||
// )),
|
||||
// send: None,
|
||||
// _t: PhantomData,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// Err((req, framed))
|
||||
// }
|
||||
// }
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct FramedRouteServiceResponse<Io, F> {
|
||||
fut: F,
|
||||
send: Option<Box<Future<Item = (), Error = Error>>>,
|
||||
_t: PhantomData<Io>,
|
||||
}
|
||||
|
||||
impl<Io, F> Future for FramedRouteServiceResponse<Io, F>
|
||||
where
|
||||
F: Future<Error = FramedError<Io>>,
|
||||
Io: AsyncRead + AsyncWrite + 'static,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(ref mut fut) = self.send {
|
||||
return match fut.poll() {
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Ok(Async::Ready(_)) => Ok(Async::Ready(())),
|
||||
Err(e) => {
|
||||
debug!("Error during error response send: {}", e);
|
||||
Err(())
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
match self.fut.poll() {
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Ok(Async::Ready(_)) => Ok(Async::Ready(())),
|
||||
Err(e) => {
|
||||
error!("Error occurred during request handling: {}", e.err);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FramedRoutePatternBuilder<Io, S> {
|
||||
pattern: String,
|
||||
methods: Vec<Method>,
|
||||
headers: Vec<(HeaderName, HeaderValue)>,
|
||||
state: PhantomData<(Io, S)>,
|
||||
}
|
||||
|
||||
impl<Io, S> FramedRoutePatternBuilder<Io, S> {
|
||||
fn new(path: &str) -> FramedRoutePatternBuilder<Io, S> {
|
||||
FramedRoutePatternBuilder {
|
||||
pattern: path.to_string(),
|
||||
methods: Vec::new(),
|
||||
headers: Vec::new(),
|
||||
state: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn method(mut self, method: Method) -> Self {
|
||||
self.methods.push(method);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn map<T, U, F: IntoNewService<T>>(
|
||||
self,
|
||||
md: F,
|
||||
) -> FramedRouteBuilder<Io, S, T, (), U>
|
||||
where
|
||||
T: NewService<
|
||||
Request = FramedRequest<S, Io>,
|
||||
Response = FramedRequest<S, Io, U>,
|
||||
Error = FramedError<Io>,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
FramedRouteBuilder {
|
||||
service: md.into_new_service(),
|
||||
pattern: self.pattern,
|
||||
methods: self.methods,
|
||||
headers: self.headers,
|
||||
state: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with<F, P, R, E>(
|
||||
self,
|
||||
handler: F,
|
||||
) -> FramedRoute<
|
||||
Io,
|
||||
impl NewService<
|
||||
Request = FramedRequest<S, Io>,
|
||||
Response = (),
|
||||
Error = FramedError<Io>,
|
||||
InitError = (),
|
||||
>,
|
||||
S,
|
||||
>
|
||||
where
|
||||
F: FramedFactory<S, Io, (), P, R, E>,
|
||||
P: FromRequest<S> + 'static,
|
||||
R: IntoFuture<Item = (), Error = E>,
|
||||
E: Into<Error>,
|
||||
{
|
||||
FramedRoute {
|
||||
service: FramedExtract::new(P::Config::default())
|
||||
.and_then(FramedHandle::new(handler)),
|
||||
pattern: self.pattern,
|
||||
methods: self.methods,
|
||||
headers: self.headers,
|
||||
state: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FramedRouteBuilder<Io, S, T, U1, U2> {
|
||||
service: T,
|
||||
pattern: String,
|
||||
methods: Vec<Method>,
|
||||
headers: Vec<(HeaderName, HeaderValue)>,
|
||||
state: PhantomData<(Io, S, U1, U2)>,
|
||||
}
|
||||
|
||||
impl<Io, S, T, U1, U2> FramedRouteBuilder<Io, S, T, U1, U2>
|
||||
where
|
||||
T: NewService<
|
||||
Request = FramedRequest<S, Io, U1>,
|
||||
Response = FramedRequest<S, Io, U2>,
|
||||
Error = FramedError<Io>,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
pub fn new<F: IntoNewService<T>>(path: &str, factory: F) -> Self {
|
||||
FramedRouteBuilder {
|
||||
service: factory.into_new_service(),
|
||||
pattern: path.to_string(),
|
||||
methods: Vec::new(),
|
||||
headers: Vec::new(),
|
||||
state: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn method(mut self, method: Method) -> Self {
|
||||
self.methods.push(method);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn map<K, U3, F: IntoNewService<K>>(
|
||||
self,
|
||||
md: F,
|
||||
) -> FramedRouteBuilder<
|
||||
Io,
|
||||
S,
|
||||
impl NewService<
|
||||
Request = FramedRequest<S, Io, U1>,
|
||||
Response = FramedRequest<S, Io, U3>,
|
||||
Error = FramedError<Io>,
|
||||
InitError = (),
|
||||
>,
|
||||
U1,
|
||||
U3,
|
||||
>
|
||||
where
|
||||
K: NewService<
|
||||
Request = FramedRequest<S, Io, U2>,
|
||||
Response = FramedRequest<S, Io, U3>,
|
||||
Error = FramedError<Io>,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
FramedRouteBuilder {
|
||||
service: self.service.from_err().and_then(md.into_new_service()),
|
||||
pattern: self.pattern,
|
||||
methods: self.methods,
|
||||
headers: self.headers,
|
||||
state: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with<F, P, R, E>(
|
||||
self,
|
||||
handler: F,
|
||||
) -> FramedRoute<
|
||||
Io,
|
||||
impl NewService<
|
||||
Request = FramedRequest<S, Io, U1>,
|
||||
Response = (),
|
||||
Error = FramedError<Io>,
|
||||
InitError = (),
|
||||
>,
|
||||
S,
|
||||
>
|
||||
where
|
||||
F: FramedFactory<S, Io, U2, P, R, E>,
|
||||
P: FromRequest<S> + 'static,
|
||||
R: IntoFuture<Item = (), Error = E>,
|
||||
E: Into<Error>,
|
||||
{
|
||||
FramedRoute {
|
||||
service: self
|
||||
.service
|
||||
.and_then(FramedExtract::new(P::Config::default()))
|
||||
.and_then(FramedHandle::new(handler)),
|
||||
pattern: self.pattern,
|
||||
methods: self.methods,
|
||||
headers: self.headers,
|
||||
state: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
872
src/handler.rs
872
src/handler.rs
@ -1,562 +1,402 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
|
||||
use futures::future::{err, ok, Future};
|
||||
use futures::{Async, Poll};
|
||||
use actix_http::{Error, Response};
|
||||
use actix_service::{NewService, Service};
|
||||
use actix_utils::Never;
|
||||
use futures::future::{ok, FutureResult};
|
||||
use futures::{try_ready, Async, Future, IntoFuture, 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>;
|
||||
}
|
||||
use crate::request::HttpRequest;
|
||||
use crate::responder::Responder;
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
|
||||
/// 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;
|
||||
/// Types that implement this trait can be used with `Route` handlers.
|
||||
pub trait FromRequest<P>: Sized {
|
||||
/// The associated error which can be returned.
|
||||
type Error: Into<Error>;
|
||||
|
||||
/// Future that resolves to a Self
|
||||
type Result: Into<AsyncResult<Self>>;
|
||||
type Future: Future<Item = Self, Error = Self::Error>;
|
||||
|
||||
/// 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())
|
||||
}
|
||||
fn from_request(req: &mut ServiceRequest<P>) -> Self::Future;
|
||||
}
|
||||
|
||||
/// 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, PartialEq)]
|
||||
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>
|
||||
/// Handler converter factory
|
||||
pub trait Factory<T, R>: Clone
|
||||
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 future(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::future(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>,
|
||||
fn call(&self, param: T) -> R;
|
||||
}
|
||||
|
||||
impl<S, H, R> WrapHandler<S, H, R>
|
||||
impl<F, R> Factory<(), R> for F
|
||||
where
|
||||
H: Handler<S, Result = R>,
|
||||
F: Fn() -> R + Clone + 'static,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
fn call(&self, _: ()) -> R {
|
||||
(self)()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct Handle<F, T, R>
|
||||
where
|
||||
F: Factory<T, R>,
|
||||
R: Responder,
|
||||
S: 'static,
|
||||
{
|
||||
pub fn new(h: H) -> Self {
|
||||
WrapHandler { h, s: PhantomData }
|
||||
hnd: F,
|
||||
_t: PhantomData<(T, R)>,
|
||||
}
|
||||
|
||||
impl<F, T, R> Handle<F, T, R>
|
||||
where
|
||||
F: Factory<T, R>,
|
||||
R: Responder,
|
||||
{
|
||||
pub fn new(hnd: F) -> Self {
|
||||
Handle {
|
||||
hnd,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<F, T, R> NewService for Handle<F, T, R>
|
||||
where
|
||||
F: Factory<T, R>,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
type Request = (T, HttpRequest);
|
||||
type Response = ServiceResponse;
|
||||
type Error = Never;
|
||||
type InitError = ();
|
||||
type Service = HandleService<F, T, R>;
|
||||
type Future = FutureResult<Self::Service, ()>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(HandleService {
|
||||
hnd: self.hnd.clone(),
|
||||
_t: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, H, R> RouteHandler<S> for WrapHandler<S, H, R>
|
||||
#[doc(hidden)]
|
||||
pub struct HandleService<F, T, R>
|
||||
where
|
||||
H: Handler<S, Result = R>,
|
||||
F: Factory<T, 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()),
|
||||
hnd: F,
|
||||
_t: PhantomData<(T, R)>,
|
||||
}
|
||||
|
||||
impl<F, T, R> Service for HandleService<F, T, R>
|
||||
where
|
||||
F: Factory<T, R>,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
type Request = (T, HttpRequest);
|
||||
type Response = ServiceResponse;
|
||||
type Error = Never;
|
||||
type Future = HandleServiceResponse<R::Future>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future {
|
||||
let fut = self.hnd.call(param).respond_to(&req);
|
||||
HandleServiceResponse {
|
||||
fut,
|
||||
req: Some(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
pub struct HandleServiceResponse<T> {
|
||||
fut: T,
|
||||
req: Option<HttpRequest>,
|
||||
}
|
||||
|
||||
impl<S, H, F, R, E> AsyncHandler<S, H, F, R, E>
|
||||
impl<T> Future for HandleServiceResponse<T>
|
||||
where
|
||||
H: Fn(&HttpRequest<S>) -> F + 'static,
|
||||
F: Future<Item = R, Error = E> + 'static,
|
||||
R: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
S: 'static,
|
||||
T: Future<Item = Response>,
|
||||
T::Error: Into<Error>,
|
||||
{
|
||||
pub fn new(h: H) -> Self {
|
||||
AsyncHandler {
|
||||
h: Box::new(h),
|
||||
s: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
type Item = ServiceResponse;
|
||||
type Error = Never;
|
||||
|
||||
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)),
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll() {
|
||||
Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new(
|
||||
self.req.take().unwrap(),
|
||||
res,
|
||||
))),
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(e) => {
|
||||
let res: Response = e.into().into();
|
||||
Ok(Async::Ready(ServiceResponse::new(
|
||||
self.req.take().unwrap(),
|
||||
res,
|
||||
)))
|
||||
}
|
||||
});
|
||||
AsyncResult::future(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>);
|
||||
/// Async handler converter factory
|
||||
pub trait AsyncFactory<T, R>: Clone + 'static
|
||||
where
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
fn call(&self, param: T) -> R;
|
||||
}
|
||||
|
||||
impl<S> Deref for State<S> {
|
||||
type Target = S;
|
||||
|
||||
fn deref(&self) -> &S {
|
||||
self.0.state()
|
||||
impl<F, R> AsyncFactory<(), R> for F
|
||||
where
|
||||
F: Fn() -> R + Clone + 'static,
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
fn call(&self, _: ()) -> R {
|
||||
(self)()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> FromRequest<S> for State<S> {
|
||||
type Config = ();
|
||||
type Result = State<S>;
|
||||
#[doc(hidden)]
|
||||
pub struct AsyncHandle<F, T, R>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
hnd: F,
|
||||
_t: PhantomData<(T, R)>,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
|
||||
State(req.clone())
|
||||
impl<F, T, R> AsyncHandle<F, T, R>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
pub fn new(hnd: F) -> Self {
|
||||
AsyncHandle {
|
||||
hnd,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<F, T, R> NewService for AsyncHandle<F, T, R>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
type Request = (T, HttpRequest);
|
||||
type Response = ServiceResponse;
|
||||
type Error = ();
|
||||
type InitError = ();
|
||||
type Service = AsyncHandleService<F, T, R>;
|
||||
type Future = FutureResult<Self::Service, ()>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(AsyncHandleService {
|
||||
hnd: self.hnd.clone(),
|
||||
_t: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct AsyncHandleService<F, T, R>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
hnd: F,
|
||||
_t: PhantomData<(T, R)>,
|
||||
}
|
||||
|
||||
impl<F, T, R> Service for AsyncHandleService<F, T, R>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
type Request = (T, HttpRequest);
|
||||
type Response = ServiceResponse;
|
||||
type Error = ();
|
||||
type Future = AsyncHandleServiceResponse<R::Future>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future {
|
||||
AsyncHandleServiceResponse {
|
||||
fut: self.hnd.call(param).into_future(),
|
||||
req: Some(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct AsyncHandleServiceResponse<T> {
|
||||
fut: T,
|
||||
req: Option<HttpRequest>,
|
||||
}
|
||||
|
||||
impl<T> Future for AsyncHandleServiceResponse<T>
|
||||
where
|
||||
T: Future,
|
||||
T::Item: Into<Response>,
|
||||
T::Error: Into<Error>,
|
||||
{
|
||||
type Item = ServiceResponse;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll() {
|
||||
Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new(
|
||||
self.req.take().unwrap(),
|
||||
res.into(),
|
||||
))),
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(e) => {
|
||||
let res: Response = e.into().into();
|
||||
Ok(Async::Ready(ServiceResponse::new(
|
||||
self.req.take().unwrap(),
|
||||
res,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract arguments from request
|
||||
pub struct Extract<P, T: FromRequest<P>> {
|
||||
_t: PhantomData<(P, T)>,
|
||||
}
|
||||
|
||||
impl<P, T: FromRequest<P>> Extract<P, T> {
|
||||
pub fn new() -> Self {
|
||||
Extract { _t: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T: FromRequest<P>> Default for Extract<P, T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T: FromRequest<P>> NewService for Extract<P, T> {
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = (T, HttpRequest);
|
||||
type Error = (Error, ServiceRequest<P>);
|
||||
type InitError = ();
|
||||
type Service = ExtractService<P, T>;
|
||||
type Future = FutureResult<Self::Service, ()>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(ExtractService { _t: PhantomData })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtractService<P, T: FromRequest<P>> {
|
||||
_t: PhantomData<(P, T)>,
|
||||
}
|
||||
|
||||
impl<P, T: FromRequest<P>> Service for ExtractService<P, T> {
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = (T, HttpRequest);
|
||||
type Error = (Error, ServiceRequest<P>);
|
||||
type Future = ExtractResponse<P, T>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
|
||||
ExtractResponse {
|
||||
fut: T::from_request(&mut req),
|
||||
req: Some(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtractResponse<P, T: FromRequest<P>> {
|
||||
req: Option<ServiceRequest<P>>,
|
||||
fut: T::Future,
|
||||
}
|
||||
|
||||
impl<P, T: FromRequest<P>> Future for ExtractResponse<P, T> {
|
||||
type Item = (T, HttpRequest);
|
||||
type Error = (Error, ServiceRequest<P>);
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let item = try_ready!(self
|
||||
.fut
|
||||
.poll()
|
||||
.map_err(|e| (e.into(), self.req.take().unwrap())));
|
||||
|
||||
let req = self.req.take().unwrap();
|
||||
let req = req.into_request();
|
||||
|
||||
Ok(Async::Ready((item, req)))
|
||||
}
|
||||
}
|
||||
|
||||
/// FromRequest trait impl for tuples
|
||||
macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => {
|
||||
impl<Func, $($T,)+ Res> Factory<($($T,)+), Res> for Func
|
||||
where Func: Fn($($T,)+) -> Res + Clone + 'static,
|
||||
//$($T,)+
|
||||
Res: Responder + 'static,
|
||||
{
|
||||
fn call(&self, param: ($($T,)+)) -> Res {
|
||||
(self)($(param.$n,)+)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Func, $($T,)+ Res> AsyncFactory<($($T,)+), Res> for Func
|
||||
where Func: Fn($($T,)+) -> Res + Clone + 'static,
|
||||
Res: IntoFuture + 'static,
|
||||
Res::Item: Into<Response>,
|
||||
Res::Error: Into<Error>,
|
||||
{
|
||||
fn call(&self, param: ($($T,)+)) -> Res {
|
||||
(self)($(param.$n,)+)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod m {
|
||||
use super::*;
|
||||
|
||||
factory_tuple!((0, A));
|
||||
factory_tuple!((0, A), (1, B));
|
||||
factory_tuple!((0, A), (1, B), (2, C));
|
||||
factory_tuple!((0, A), (1, B), (2, C), (3, D));
|
||||
factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E));
|
||||
factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F));
|
||||
factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G));
|
||||
factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H));
|
||||
factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I));
|
||||
factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J));
|
||||
}
|
||||
|
@ -1,159 +0,0 @@
|
||||
use header::{qitem, QualityItem};
|
||||
use http::header as http;
|
||||
use mime::{self, Mime};
|
||||
|
||||
header! {
|
||||
/// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2)
|
||||
///
|
||||
/// The `Accept` header field can be used by user agents to specify
|
||||
/// response media types that are acceptable. Accept header fields can
|
||||
/// be used to indicate that the request is specifically limited to a
|
||||
/// small set of desired types, as in the case of a request for an
|
||||
/// in-line image
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// Accept = #( media-range [ accept-params ] )
|
||||
///
|
||||
/// media-range = ( "*/*"
|
||||
/// / ( type "/" "*" )
|
||||
/// / ( type "/" subtype )
|
||||
/// ) *( OWS ";" OWS parameter )
|
||||
/// accept-params = weight *( accept-ext )
|
||||
/// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ]
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
/// * `audio/*; q=0.2, audio/basic`
|
||||
/// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c`
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// extern crate mime;
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{Accept, qitem};
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
///
|
||||
/// builder.set(
|
||||
/// Accept(vec![
|
||||
/// qitem(mime::TEXT_HTML),
|
||||
/// ])
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// extern crate mime;
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{Accept, qitem};
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
///
|
||||
/// builder.set(
|
||||
/// Accept(vec![
|
||||
/// qitem(mime::APPLICATION_JSON),
|
||||
/// ])
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// extern crate mime;
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{Accept, QualityItem, q, qitem};
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
///
|
||||
/// builder.set(
|
||||
/// Accept(vec![
|
||||
/// qitem(mime::TEXT_HTML),
|
||||
/// qitem("application/xhtml+xml".parse().unwrap()),
|
||||
/// QualityItem::new(
|
||||
/// mime::TEXT_XML,
|
||||
/// q(900)
|
||||
/// ),
|
||||
/// qitem("image/webp".parse().unwrap()),
|
||||
/// QualityItem::new(
|
||||
/// mime::STAR_STAR,
|
||||
/// q(800)
|
||||
/// ),
|
||||
/// ])
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
(Accept, http::ACCEPT) => (QualityItem<Mime>)+
|
||||
|
||||
test_accept {
|
||||
// Tests from the RFC
|
||||
test_header!(
|
||||
test1,
|
||||
vec![b"audio/*; q=0.2, audio/basic"],
|
||||
Some(HeaderField(vec![
|
||||
QualityItem::new("audio/*".parse().unwrap(), q(200)),
|
||||
qitem("audio/basic".parse().unwrap()),
|
||||
])));
|
||||
test_header!(
|
||||
test2,
|
||||
vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
|
||||
Some(HeaderField(vec![
|
||||
QualityItem::new(TEXT_PLAIN, q(500)),
|
||||
qitem(TEXT_HTML),
|
||||
QualityItem::new(
|
||||
"text/x-dvi".parse().unwrap(),
|
||||
q(800)),
|
||||
qitem("text/x-c".parse().unwrap()),
|
||||
])));
|
||||
// Custom tests
|
||||
test_header!(
|
||||
test3,
|
||||
vec![b"text/plain; charset=utf-8"],
|
||||
Some(Accept(vec![
|
||||
qitem(TEXT_PLAIN_UTF_8),
|
||||
])));
|
||||
test_header!(
|
||||
test4,
|
||||
vec![b"text/plain; charset=utf-8; q=0.5"],
|
||||
Some(Accept(vec![
|
||||
QualityItem::new(TEXT_PLAIN_UTF_8,
|
||||
q(500)),
|
||||
])));
|
||||
|
||||
#[test]
|
||||
fn test_fuzzing1() {
|
||||
use test::TestRequest;
|
||||
let req = TestRequest::with_header(super::http::ACCEPT, "chunk#;e").finish();
|
||||
let header = Accept::parse(&req);
|
||||
assert!(header.is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Accept {
|
||||
/// A constructor to easily create `Accept: */*`.
|
||||
pub fn star() -> Accept {
|
||||
Accept(vec![qitem(mime::STAR_STAR)])
|
||||
}
|
||||
|
||||
/// A constructor to easily create `Accept: application/json`.
|
||||
pub fn json() -> Accept {
|
||||
Accept(vec![qitem(mime::APPLICATION_JSON)])
|
||||
}
|
||||
|
||||
/// A constructor to easily create `Accept: text/*`.
|
||||
pub fn text() -> Accept {
|
||||
Accept(vec![qitem(mime::TEXT_STAR)])
|
||||
}
|
||||
|
||||
/// A constructor to easily create `Accept: image/*`.
|
||||
pub fn image() -> Accept {
|
||||
Accept(vec![qitem(mime::IMAGE_STAR)])
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
use header::{Charset, QualityItem, ACCEPT_CHARSET};
|
||||
|
||||
header! {
|
||||
/// `Accept-Charset` header, defined in
|
||||
/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3)
|
||||
///
|
||||
/// The `Accept-Charset` header field can be sent by a user agent to
|
||||
/// indicate what charsets are acceptable in textual response content.
|
||||
/// This field allows user agents capable of understanding more
|
||||
/// comprehensive or special-purpose charsets to signal that capability
|
||||
/// to an origin server that is capable of representing information in
|
||||
/// those charsets.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// Accept-Charset = 1#( ( charset / "*" ) [ weight ] )
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
/// * `iso-8859-5, unicode-1-1;q=0.8`
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{AcceptCharset, Charset, qitem};
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(
|
||||
/// AcceptCharset(vec![qitem(Charset::Us_Ascii)])
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{AcceptCharset, Charset, q, QualityItem};
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(
|
||||
/// AcceptCharset(vec![
|
||||
/// QualityItem::new(Charset::Us_Ascii, q(900)),
|
||||
/// QualityItem::new(Charset::Iso_8859_10, q(200)),
|
||||
/// ])
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{AcceptCharset, Charset, qitem};
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(
|
||||
/// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))])
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
(AcceptCharset, ACCEPT_CHARSET) => (QualityItem<Charset>)+
|
||||
|
||||
test_accept_charset {
|
||||
/// Test case from RFC
|
||||
test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]);
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
use header::{Encoding, QualityItem};
|
||||
|
||||
header! {
|
||||
/// `Accept-Encoding` header, defined in
|
||||
/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4)
|
||||
///
|
||||
/// The `Accept-Encoding` header field can be used by user agents to
|
||||
/// indicate what response content-codings are
|
||||
/// acceptable in the response. An `identity` token is used as a synonym
|
||||
/// for "no encoding" in order to communicate when no encoding is
|
||||
/// preferred.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// Accept-Encoding = #( codings [ weight ] )
|
||||
/// codings = content-coding / "identity" / "*"
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
/// * `compress, gzip`
|
||||
/// * ``
|
||||
/// * `*`
|
||||
/// * `compress;q=0.5, gzip;q=1`
|
||||
/// * `gzip;q=1.0, identity; q=0.5, *;q=0`
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem};
|
||||
///
|
||||
/// let mut headers = Headers::new();
|
||||
/// headers.set(
|
||||
/// AcceptEncoding(vec![qitem(Encoding::Chunked)])
|
||||
/// );
|
||||
/// ```
|
||||
/// ```
|
||||
/// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem};
|
||||
///
|
||||
/// let mut headers = Headers::new();
|
||||
/// headers.set(
|
||||
/// AcceptEncoding(vec![
|
||||
/// qitem(Encoding::Chunked),
|
||||
/// qitem(Encoding::Gzip),
|
||||
/// qitem(Encoding::Deflate),
|
||||
/// ])
|
||||
/// );
|
||||
/// ```
|
||||
/// ```
|
||||
/// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem};
|
||||
///
|
||||
/// let mut headers = Headers::new();
|
||||
/// headers.set(
|
||||
/// AcceptEncoding(vec![
|
||||
/// qitem(Encoding::Chunked),
|
||||
/// QualityItem::new(Encoding::Gzip, q(600)),
|
||||
/// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)),
|
||||
/// ])
|
||||
/// );
|
||||
/// ```
|
||||
(AcceptEncoding, "Accept-Encoding") => (QualityItem<Encoding>)*
|
||||
|
||||
test_accept_encoding {
|
||||
// From the RFC
|
||||
test_header!(test1, vec![b"compress, gzip"]);
|
||||
test_header!(test2, vec![b""], Some(AcceptEncoding(vec![])));
|
||||
test_header!(test3, vec![b"*"]);
|
||||
// Note: Removed quality 1 from gzip
|
||||
test_header!(test4, vec![b"compress;q=0.5, gzip"]);
|
||||
// Note: Removed quality 1 from gzip
|
||||
test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]);
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
use header::{QualityItem, ACCEPT_LANGUAGE};
|
||||
use language_tags::LanguageTag;
|
||||
|
||||
header! {
|
||||
/// `Accept-Language` header, defined in
|
||||
/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5)
|
||||
///
|
||||
/// The `Accept-Language` header field can be used by user agents to
|
||||
/// indicate the set of natural languages that are preferred in the
|
||||
/// response.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// Accept-Language = 1#( language-range [ weight ] )
|
||||
/// language-range = <language-range, see [RFC4647], Section 2.1>
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
/// * `da, en-gb;q=0.8, en;q=0.7`
|
||||
/// * `en-us;q=1.0, en;q=0.5, fr`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate language_tags;
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem};
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// let mut langtag: LanguageTag = Default::default();
|
||||
/// langtag.language = Some("en".to_owned());
|
||||
/// langtag.region = Some("US".to_owned());
|
||||
/// builder.set(
|
||||
/// AcceptLanguage(vec![
|
||||
/// qitem(langtag),
|
||||
/// ])
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # #[macro_use] extern crate language_tags;
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem};
|
||||
/// #
|
||||
/// # fn main() {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(
|
||||
/// AcceptLanguage(vec![
|
||||
/// qitem(langtag!(da)),
|
||||
/// QualityItem::new(langtag!(en;;;GB), q(800)),
|
||||
/// QualityItem::new(langtag!(en), q(700)),
|
||||
/// ])
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
(AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem<LanguageTag>)+
|
||||
|
||||
test_accept_language {
|
||||
// From the RFC
|
||||
test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]);
|
||||
// Own test
|
||||
test_header!(
|
||||
test2, vec![b"en-US, en; q=0.5, fr"],
|
||||
Some(AcceptLanguage(vec![
|
||||
qitem("en-US".parse().unwrap()),
|
||||
QualityItem::new("en".parse().unwrap(), q(500)),
|
||||
qitem("fr".parse().unwrap()),
|
||||
])));
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
use http::Method;
|
||||
use http::header;
|
||||
|
||||
header! {
|
||||
/// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1)
|
||||
///
|
||||
/// The `Allow` header field lists the set of methods advertised as
|
||||
/// supported by the target resource. The purpose of this field is
|
||||
/// strictly to inform the recipient of valid request methods associated
|
||||
/// with the resource.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// Allow = #method
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
/// * `GET, HEAD, PUT`
|
||||
/// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr`
|
||||
/// * ``
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate http;
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::Allow;
|
||||
/// use http::Method;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(
|
||||
/// Allow(vec![Method::GET])
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate http;
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::Allow;
|
||||
/// use http::Method;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(
|
||||
/// Allow(vec![
|
||||
/// Method::GET,
|
||||
/// Method::POST,
|
||||
/// Method::PATCH,
|
||||
/// ])
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
(Allow, header::ALLOW) => (Method)*
|
||||
|
||||
test_allow {
|
||||
// From the RFC
|
||||
test_header!(
|
||||
test1,
|
||||
vec![b"GET, HEAD, PUT"],
|
||||
Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT])));
|
||||
// Own tests
|
||||
test_header!(
|
||||
test2,
|
||||
vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"],
|
||||
Some(HeaderField(vec![
|
||||
Method::OPTIONS,
|
||||
Method::GET,
|
||||
Method::PUT,
|
||||
Method::POST,
|
||||
Method::DELETE,
|
||||
Method::HEAD,
|
||||
Method::TRACE,
|
||||
Method::CONNECT,
|
||||
Method::PATCH])));
|
||||
test_header!(
|
||||
test3,
|
||||
vec![b""],
|
||||
Some(HeaderField(Vec::<Method>::new())));
|
||||
}
|
||||
}
|
@ -1,254 +0,0 @@
|
||||
use header::{Header, IntoHeaderValue, Writer};
|
||||
use header::{fmt_comma_delimited, from_comma_delimited};
|
||||
use http::header;
|
||||
use std::fmt::{self, Write};
|
||||
use std::str::FromStr;
|
||||
|
||||
/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2)
|
||||
///
|
||||
/// The `Cache-Control` header field is used to specify directives for
|
||||
/// caches along the request/response chain. Such cache directives are
|
||||
/// unidirectional in that the presence of a directive in a request does
|
||||
/// not imply that the same directive is to be given in the response.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// Cache-Control = 1#cache-directive
|
||||
/// cache-directive = token [ "=" ( token / quoted-string ) ]
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
///
|
||||
/// * `no-cache`
|
||||
/// * `private, community="UCI"`
|
||||
/// * `max-age=30`
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{CacheControl, CacheDirective};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)]));
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{CacheControl, CacheDirective};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(CacheControl(vec![
|
||||
/// CacheDirective::NoCache,
|
||||
/// CacheDirective::Private,
|
||||
/// CacheDirective::MaxAge(360u32),
|
||||
/// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())),
|
||||
/// ]));
|
||||
/// ```
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub struct CacheControl(pub Vec<CacheDirective>);
|
||||
|
||||
__hyper__deref!(CacheControl => Vec<CacheDirective>);
|
||||
|
||||
//TODO: this could just be the header! macro
|
||||
impl Header for CacheControl {
|
||||
fn name() -> header::HeaderName {
|
||||
header::CACHE_CONTROL
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn parse<T>(msg: &T) -> Result<Self, ::error::ParseError>
|
||||
where
|
||||
T: ::HttpMessage,
|
||||
{
|
||||
let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?;
|
||||
if !directives.is_empty() {
|
||||
Ok(CacheControl(directives))
|
||||
} else {
|
||||
Err(::error::ParseError::Header)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CacheControl {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt_comma_delimited(f, &self[..])
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoHeaderValue for CacheControl {
|
||||
type Error = header::InvalidHeaderValueBytes;
|
||||
|
||||
fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
|
||||
let mut writer = Writer::new();
|
||||
let _ = write!(&mut writer, "{}", self);
|
||||
header::HeaderValue::from_shared(writer.take())
|
||||
}
|
||||
}
|
||||
|
||||
/// `CacheControl` contains a list of these directives.
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum CacheDirective {
|
||||
/// "no-cache"
|
||||
NoCache,
|
||||
/// "no-store"
|
||||
NoStore,
|
||||
/// "no-transform"
|
||||
NoTransform,
|
||||
/// "only-if-cached"
|
||||
OnlyIfCached,
|
||||
|
||||
// request directives
|
||||
/// "max-age=delta"
|
||||
MaxAge(u32),
|
||||
/// "max-stale=delta"
|
||||
MaxStale(u32),
|
||||
/// "min-fresh=delta"
|
||||
MinFresh(u32),
|
||||
|
||||
// response directives
|
||||
/// "must-revalidate"
|
||||
MustRevalidate,
|
||||
/// "public"
|
||||
Public,
|
||||
/// "private"
|
||||
Private,
|
||||
/// "proxy-revalidate"
|
||||
ProxyRevalidate,
|
||||
/// "s-maxage=delta"
|
||||
SMaxAge(u32),
|
||||
|
||||
/// Extension directives. Optionally include an argument.
|
||||
Extension(String, Option<String>),
|
||||
}
|
||||
|
||||
impl fmt::Display for CacheDirective {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::CacheDirective::*;
|
||||
fmt::Display::fmt(
|
||||
match *self {
|
||||
NoCache => "no-cache",
|
||||
NoStore => "no-store",
|
||||
NoTransform => "no-transform",
|
||||
OnlyIfCached => "only-if-cached",
|
||||
|
||||
MaxAge(secs) => return write!(f, "max-age={}", secs),
|
||||
MaxStale(secs) => return write!(f, "max-stale={}", secs),
|
||||
MinFresh(secs) => return write!(f, "min-fresh={}", secs),
|
||||
|
||||
MustRevalidate => "must-revalidate",
|
||||
Public => "public",
|
||||
Private => "private",
|
||||
ProxyRevalidate => "proxy-revalidate",
|
||||
SMaxAge(secs) => return write!(f, "s-maxage={}", secs),
|
||||
|
||||
Extension(ref name, None) => &name[..],
|
||||
Extension(ref name, Some(ref arg)) => {
|
||||
return write!(f, "{}={}", name, arg)
|
||||
}
|
||||
},
|
||||
f,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for CacheDirective {
|
||||
type Err = Option<<u32 as FromStr>::Err>;
|
||||
fn from_str(s: &str) -> Result<CacheDirective, Option<<u32 as FromStr>::Err>> {
|
||||
use self::CacheDirective::*;
|
||||
match s {
|
||||
"no-cache" => Ok(NoCache),
|
||||
"no-store" => Ok(NoStore),
|
||||
"no-transform" => Ok(NoTransform),
|
||||
"only-if-cached" => Ok(OnlyIfCached),
|
||||
"must-revalidate" => Ok(MustRevalidate),
|
||||
"public" => Ok(Public),
|
||||
"private" => Ok(Private),
|
||||
"proxy-revalidate" => Ok(ProxyRevalidate),
|
||||
"" => Err(None),
|
||||
_ => match s.find('=') {
|
||||
Some(idx) if idx + 1 < s.len() => {
|
||||
match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) {
|
||||
("max-age", secs) => secs.parse().map(MaxAge).map_err(Some),
|
||||
("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some),
|
||||
("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some),
|
||||
("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some),
|
||||
(left, right) => {
|
||||
Ok(Extension(left.to_owned(), Some(right.to_owned())))
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(_) => Err(None),
|
||||
None => Ok(Extension(s.to_owned(), None)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use header::Header;
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_parse_multiple_headers() {
|
||||
let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private")
|
||||
.finish();
|
||||
let cache = Header::parse(&req);
|
||||
assert_eq!(
|
||||
cache.ok(),
|
||||
Some(CacheControl(vec![
|
||||
CacheDirective::NoCache,
|
||||
CacheDirective::Private,
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_argument() {
|
||||
let req =
|
||||
TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private")
|
||||
.finish();
|
||||
let cache = Header::parse(&req);
|
||||
assert_eq!(
|
||||
cache.ok(),
|
||||
Some(CacheControl(vec![
|
||||
CacheDirective::MaxAge(100),
|
||||
CacheDirective::Private,
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_quote_form() {
|
||||
let req =
|
||||
TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish();
|
||||
let cache = Header::parse(&req);
|
||||
assert_eq!(
|
||||
cache.ok(),
|
||||
Some(CacheControl(vec![CacheDirective::MaxAge(200)]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_extension() {
|
||||
let req =
|
||||
TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish();
|
||||
let cache = Header::parse(&req);
|
||||
assert_eq!(
|
||||
cache.ok(),
|
||||
Some(CacheControl(vec![
|
||||
CacheDirective::Extension("foo".to_owned(), None),
|
||||
CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())),
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_bad_syntax() {
|
||||
let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish();
|
||||
let cache: Result<CacheControl, _> = Header::parse(&req);
|
||||
assert_eq!(cache.ok(), None)
|
||||
}
|
||||
}
|
@ -1,914 +0,0 @@
|
||||
// # References
|
||||
//
|
||||
// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt
|
||||
// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt
|
||||
// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt
|
||||
// Browser conformance tests at: http://greenbytes.de/tech/tc2231/
|
||||
// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml
|
||||
|
||||
use header;
|
||||
use header::ExtendedValue;
|
||||
use header::{Header, IntoHeaderValue, Writer};
|
||||
use regex::Regex;
|
||||
|
||||
use std::fmt::{self, Write};
|
||||
|
||||
/// Split at the index of the first `needle` if it exists or at the end.
|
||||
fn split_once(haystack: &str, needle: char) -> (&str, &str) {
|
||||
haystack.find(needle).map_or_else(
|
||||
|| (haystack, ""),
|
||||
|sc| {
|
||||
let (first, last) = haystack.split_at(sc);
|
||||
(first, last.split_at(1).1)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Split at the index of the first `needle` if it exists or at the end, trim the right of the
|
||||
/// first part and the left of the last part.
|
||||
fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) {
|
||||
let (first, last) = split_once(haystack, needle);
|
||||
(first.trim_right(), last.trim_left())
|
||||
}
|
||||
|
||||
/// The implied disposition of the content of the HTTP body.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum DispositionType {
|
||||
/// Inline implies default processing
|
||||
Inline,
|
||||
/// Attachment implies that the recipient should prompt the user to save the response locally,
|
||||
/// rather than process it normally (as per its media type).
|
||||
Attachment,
|
||||
/// Used in *multipart/form-data* as defined in
|
||||
/// [RFC7578](https://tools.ietf.org/html/rfc7578) to carry the field name and the file name.
|
||||
FormData,
|
||||
/// Extension type. Should be handled by recipients the same way as Attachment
|
||||
Ext(String),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for DispositionType {
|
||||
fn from(origin: &'a str) -> DispositionType {
|
||||
if origin.eq_ignore_ascii_case("inline") {
|
||||
DispositionType::Inline
|
||||
} else if origin.eq_ignore_ascii_case("attachment") {
|
||||
DispositionType::Attachment
|
||||
} else if origin.eq_ignore_ascii_case("form-data") {
|
||||
DispositionType::FormData
|
||||
} else {
|
||||
DispositionType::Ext(origin.to_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameter in [`ContentDisposition`].
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::http::header::DispositionParam;
|
||||
///
|
||||
/// let param = DispositionParam::Filename(String::from("sample.txt"));
|
||||
/// assert!(param.is_filename());
|
||||
/// assert_eq!(param.as_filename().unwrap(), "sample.txt");
|
||||
/// ```
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum DispositionParam {
|
||||
/// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from
|
||||
/// the form.
|
||||
Name(String),
|
||||
/// A plain file name.
|
||||
Filename(String),
|
||||
/// An extended file name. It must not exist for `ContentType::Formdata` according to
|
||||
/// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2).
|
||||
FilenameExt(ExtendedValue),
|
||||
/// An unrecognized regular parameter as defined in
|
||||
/// [RFC5987](https://tools.ietf.org/html/rfc5987) as *reg-parameter*, in
|
||||
/// [RFC6266](https://tools.ietf.org/html/rfc6266) as *token "=" value*. Recipients should
|
||||
/// ignore unrecognizable parameters.
|
||||
Unknown(String, String),
|
||||
/// An unrecognized extended paramater as defined in
|
||||
/// [RFC5987](https://tools.ietf.org/html/rfc5987) as *ext-parameter*, in
|
||||
/// [RFC6266](https://tools.ietf.org/html/rfc6266) as *ext-token "=" ext-value*. The single
|
||||
/// trailling asterisk is not included. Recipients should ignore unrecognizable parameters.
|
||||
UnknownExt(String, ExtendedValue),
|
||||
}
|
||||
|
||||
impl DispositionParam {
|
||||
/// Returns `true` if the paramater is [`Name`](DispositionParam::Name).
|
||||
#[inline]
|
||||
pub fn is_name(&self) -> bool {
|
||||
self.as_name().is_some()
|
||||
}
|
||||
|
||||
/// Returns `true` if the paramater is [`Filename`](DispositionParam::Filename).
|
||||
#[inline]
|
||||
pub fn is_filename(&self) -> bool {
|
||||
self.as_filename().is_some()
|
||||
}
|
||||
|
||||
/// Returns `true` if the paramater is [`FilenameExt`](DispositionParam::FilenameExt).
|
||||
#[inline]
|
||||
pub fn is_filename_ext(&self) -> bool {
|
||||
self.as_filename_ext().is_some()
|
||||
}
|
||||
|
||||
/// Returns `true` if the paramater is [`Unknown`](DispositionParam::Unknown) and the `name`
|
||||
#[inline]
|
||||
/// matches.
|
||||
pub fn is_unknown<T: AsRef<str>>(&self, name: T) -> bool {
|
||||
self.as_unknown(name).is_some()
|
||||
}
|
||||
|
||||
/// Returns `true` if the paramater is [`UnknownExt`](DispositionParam::UnknownExt) and the
|
||||
/// `name` matches.
|
||||
#[inline]
|
||||
pub fn is_unknown_ext<T: AsRef<str>>(&self, name: T) -> bool {
|
||||
self.as_unknown_ext(name).is_some()
|
||||
}
|
||||
|
||||
/// Returns the name if applicable.
|
||||
#[inline]
|
||||
pub fn as_name(&self) -> Option<&str> {
|
||||
match self {
|
||||
DispositionParam::Name(ref name) => Some(name.as_str()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the filename if applicable.
|
||||
#[inline]
|
||||
pub fn as_filename(&self) -> Option<&str> {
|
||||
match self {
|
||||
DispositionParam::Filename(ref filename) => Some(filename.as_str()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the filename* if applicable.
|
||||
#[inline]
|
||||
pub fn as_filename_ext(&self) -> Option<&ExtendedValue> {
|
||||
match self {
|
||||
DispositionParam::FilenameExt(ref value) => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of the unrecognized regular parameter if it is
|
||||
/// [`Unknown`](DispositionParam::Unknown) and the `name` matches.
|
||||
#[inline]
|
||||
pub fn as_unknown<T: AsRef<str>>(&self, name: T) -> Option<&str> {
|
||||
match self {
|
||||
DispositionParam::Unknown(ref ext_name, ref value)
|
||||
if ext_name.eq_ignore_ascii_case(name.as_ref()) =>
|
||||
{
|
||||
Some(value.as_str())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of the unrecognized extended parameter if it is
|
||||
/// [`Unknown`](DispositionParam::Unknown) and the `name` matches.
|
||||
#[inline]
|
||||
pub fn as_unknown_ext<T: AsRef<str>>(&self, name: T) -> Option<&ExtendedValue> {
|
||||
match self {
|
||||
DispositionParam::UnknownExt(ref ext_name, ref value)
|
||||
if ext_name.eq_ignore_ascii_case(name.as_ref()) =>
|
||||
{
|
||||
Some(value)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A *Content-Disposition* header. It is compatible to be used either as
|
||||
/// [a response header for the main body](https://mdn.io/Content-Disposition#As_a_response_header_for_the_main_body)
|
||||
/// as (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266), or as
|
||||
/// [a header for a multipart body](https://mdn.io/Content-Disposition#As_a_header_for_a_multipart_body)
|
||||
/// as (re)defined in [RFC7587](https://tools.ietf.org/html/rfc7578).
|
||||
///
|
||||
/// In a regular HTTP response, the *Content-Disposition* response header is a header indicating if
|
||||
/// the content is expected to be displayed *inline* in the browser, that is, as a Web page or as
|
||||
/// part of a Web page, or as an attachment, that is downloaded and saved locally, and also can be
|
||||
/// used to attach additional metadata, such as the filename to use when saving the response payload
|
||||
/// locally.
|
||||
///
|
||||
/// In a *multipart/form-data* body, the HTTP *Content-Disposition* general header is a header that
|
||||
/// can be used on the subpart of a multipart body to give information about the field it applies to.
|
||||
/// The subpart is delimited by the boundary defined in the *Content-Type* header. Used on the body
|
||||
/// itself, *Content-Disposition* has no effect.
|
||||
///
|
||||
/// # ABNF
|
||||
|
||||
/// ```text
|
||||
/// content-disposition = "Content-Disposition" ":"
|
||||
/// disposition-type *( ";" disposition-parm )
|
||||
///
|
||||
/// disposition-type = "inline" | "attachment" | disp-ext-type
|
||||
/// ; case-insensitive
|
||||
///
|
||||
/// disp-ext-type = token
|
||||
///
|
||||
/// disposition-parm = filename-parm | disp-ext-parm
|
||||
///
|
||||
/// filename-parm = "filename" "=" value
|
||||
/// | "filename*" "=" ext-value
|
||||
///
|
||||
/// disp-ext-parm = token "=" value
|
||||
/// | ext-token "=" ext-value
|
||||
///
|
||||
/// ext-token = <the characters in token, followed by "*">
|
||||
/// ```
|
||||
///
|
||||
/// **Note**: filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within
|
||||
/// *multipart/form-data*.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::http::header::{
|
||||
/// Charset, ContentDisposition, DispositionParam, DispositionType,
|
||||
/// ExtendedValue,
|
||||
/// };
|
||||
///
|
||||
/// let cd1 = ContentDisposition {
|
||||
/// disposition: DispositionType::Attachment,
|
||||
/// parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
|
||||
/// charset: Charset::Iso_8859_1, // The character set for the bytes of the filename
|
||||
/// language_tag: None, // The optional language tag (see `language-tag` crate)
|
||||
/// value: b"\xa9 Copyright 1989.txt".to_vec(), // the actual bytes of the filename
|
||||
/// })],
|
||||
/// };
|
||||
/// assert!(cd1.is_attachment());
|
||||
/// assert!(cd1.get_filename_ext().is_some());
|
||||
///
|
||||
/// let cd2 = ContentDisposition {
|
||||
/// disposition: DispositionType::FormData,
|
||||
/// parameters: vec![
|
||||
/// DispositionParam::Name(String::from("file")),
|
||||
/// DispositionParam::Filename(String::from("bill.odt")),
|
||||
/// ],
|
||||
/// };
|
||||
/// assert_eq!(cd2.get_name(), Some("file")); // field name
|
||||
/// assert_eq!(cd2.get_filename(), Some("bill.odt"));
|
||||
/// ```
|
||||
///
|
||||
/// # WARN
|
||||
/// If "filename" parameter is supplied, do not use the file name blindly, check and possibly
|
||||
/// change to match local file system conventions if applicable, and do not use directory path
|
||||
/// information that may be present. See [RFC2183](https://tools.ietf.org/html/rfc2183#section-2.3)
|
||||
/// .
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ContentDisposition {
|
||||
/// The disposition type
|
||||
pub disposition: DispositionType,
|
||||
/// Disposition parameters
|
||||
pub parameters: Vec<DispositionParam>,
|
||||
}
|
||||
|
||||
impl ContentDisposition {
|
||||
/// Parse a raw Content-Disposition header value.
|
||||
pub fn from_raw(hv: &header::HeaderValue) -> Result<Self, ::error::ParseError> {
|
||||
// `header::from_one_raw_str` invokes `hv.to_str` which assumes `hv` contains only visible
|
||||
// ASCII characters. So `hv.as_bytes` is necessary here.
|
||||
let hv = String::from_utf8(hv.as_bytes().to_vec())
|
||||
.map_err(|_| ::error::ParseError::Header)?;
|
||||
let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';');
|
||||
if disp_type.is_empty() {
|
||||
return Err(::error::ParseError::Header);
|
||||
}
|
||||
let mut cd = ContentDisposition {
|
||||
disposition: disp_type.into(),
|
||||
parameters: Vec::new(),
|
||||
};
|
||||
|
||||
while !left.is_empty() {
|
||||
let (param_name, new_left) = split_once_and_trim(left, '=');
|
||||
if param_name.is_empty() || param_name == "*" || new_left.is_empty() {
|
||||
return Err(::error::ParseError::Header);
|
||||
}
|
||||
left = new_left;
|
||||
if param_name.ends_with('*') {
|
||||
// extended parameters
|
||||
let param_name = ¶m_name[..param_name.len() - 1]; // trim asterisk
|
||||
let (ext_value, new_left) = split_once_and_trim(left, ';');
|
||||
left = new_left;
|
||||
let ext_value = header::parse_extended_value(ext_value)?;
|
||||
|
||||
let param = if param_name.eq_ignore_ascii_case("filename") {
|
||||
DispositionParam::FilenameExt(ext_value)
|
||||
} else {
|
||||
DispositionParam::UnknownExt(param_name.to_owned(), ext_value)
|
||||
};
|
||||
cd.parameters.push(param);
|
||||
} else {
|
||||
// regular parameters
|
||||
let value = if left.starts_with('\"') {
|
||||
// quoted-string: defined in RFC6266 -> RFC2616 Section 3.6
|
||||
let mut escaping = false;
|
||||
let mut quoted_string = vec![];
|
||||
let mut end = None;
|
||||
// search for closing quote
|
||||
for (i, &c) in left.as_bytes().iter().skip(1).enumerate() {
|
||||
if escaping {
|
||||
escaping = false;
|
||||
quoted_string.push(c);
|
||||
} else if c == 0x5c {
|
||||
// backslash
|
||||
escaping = true;
|
||||
} else if c == 0x22 {
|
||||
// double quote
|
||||
end = Some(i + 1); // cuz skipped 1 for the leading quote
|
||||
break;
|
||||
} else {
|
||||
quoted_string.push(c);
|
||||
}
|
||||
}
|
||||
left = &left[end.ok_or(::error::ParseError::Header)? + 1..];
|
||||
left = split_once(left, ';').1.trim_left();
|
||||
// In fact, it should not be Err if the above code is correct.
|
||||
String::from_utf8(quoted_string).map_err(|_| ::error::ParseError::Header)?
|
||||
} else {
|
||||
// token: won't contains semicolon according to RFC 2616 Section 2.2
|
||||
let (token, new_left) = split_once_and_trim(left, ';');
|
||||
left = new_left;
|
||||
token.to_owned()
|
||||
};
|
||||
if value.is_empty() {
|
||||
return Err(::error::ParseError::Header);
|
||||
}
|
||||
|
||||
let param = if param_name.eq_ignore_ascii_case("name") {
|
||||
DispositionParam::Name(value)
|
||||
} else if param_name.eq_ignore_ascii_case("filename") {
|
||||
DispositionParam::Filename(value)
|
||||
} else {
|
||||
DispositionParam::Unknown(param_name.to_owned(), value)
|
||||
};
|
||||
cd.parameters.push(param);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(cd)
|
||||
}
|
||||
|
||||
/// Returns `true` if it is [`Inline`](DispositionType::Inline).
|
||||
pub fn is_inline(&self) -> bool {
|
||||
match self.disposition {
|
||||
DispositionType::Inline => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if it is [`Attachment`](DispositionType::Attachment).
|
||||
pub fn is_attachment(&self) -> bool {
|
||||
match self.disposition {
|
||||
DispositionType::Attachment => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if it is [`FormData`](DispositionType::FormData).
|
||||
pub fn is_form_data(&self) -> bool {
|
||||
match self.disposition {
|
||||
DispositionType::FormData => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches.
|
||||
pub fn is_ext<T: AsRef<str>>(&self, disp_type: T) -> bool {
|
||||
match self.disposition {
|
||||
DispositionType::Ext(ref t)
|
||||
if t.eq_ignore_ascii_case(disp_type.as_ref()) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the value of *name* if exists.
|
||||
pub fn get_name(&self) -> Option<&str> {
|
||||
self.parameters.iter().filter_map(|p| p.as_name()).nth(0)
|
||||
}
|
||||
|
||||
/// Return the value of *filename* if exists.
|
||||
pub fn get_filename(&self) -> Option<&str> {
|
||||
self.parameters
|
||||
.iter()
|
||||
.filter_map(|p| p.as_filename())
|
||||
.nth(0)
|
||||
}
|
||||
|
||||
/// Return the value of *filename\** if exists.
|
||||
pub fn get_filename_ext(&self) -> Option<&ExtendedValue> {
|
||||
self.parameters
|
||||
.iter()
|
||||
.filter_map(|p| p.as_filename_ext())
|
||||
.nth(0)
|
||||
}
|
||||
|
||||
/// Return the value of the parameter which the `name` matches.
|
||||
pub fn get_unknown<T: AsRef<str>>(&self, name: T) -> Option<&str> {
|
||||
let name = name.as_ref();
|
||||
self.parameters
|
||||
.iter()
|
||||
.filter_map(|p| p.as_unknown(name))
|
||||
.nth(0)
|
||||
}
|
||||
|
||||
/// Return the value of the extended parameter which the `name` matches.
|
||||
pub fn get_unknown_ext<T: AsRef<str>>(&self, name: T) -> Option<&ExtendedValue> {
|
||||
let name = name.as_ref();
|
||||
self.parameters
|
||||
.iter()
|
||||
.filter_map(|p| p.as_unknown_ext(name))
|
||||
.nth(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoHeaderValue for ContentDisposition {
|
||||
type Error = header::InvalidHeaderValueBytes;
|
||||
|
||||
fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
|
||||
let mut writer = Writer::new();
|
||||
let _ = write!(&mut writer, "{}", self);
|
||||
header::HeaderValue::from_shared(writer.take())
|
||||
}
|
||||
}
|
||||
|
||||
impl Header for ContentDisposition {
|
||||
fn name() -> header::HeaderName {
|
||||
header::CONTENT_DISPOSITION
|
||||
}
|
||||
|
||||
fn parse<T: ::HttpMessage>(msg: &T) -> Result<Self, ::error::ParseError> {
|
||||
if let Some(h) = msg.headers().get(Self::name()) {
|
||||
Self::from_raw(&h)
|
||||
} else {
|
||||
Err(::error::ParseError::Header)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DispositionType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
DispositionType::Inline => write!(f, "inline"),
|
||||
DispositionType::Attachment => write!(f, "attachment"),
|
||||
DispositionType::FormData => write!(f, "form-data"),
|
||||
DispositionType::Ext(ref s) => write!(f, "{}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DispositionParam {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// All ASCII control charaters (0-30, 127) excepting horizontal tab, double quote, and
|
||||
// backslash should be escaped in quoted-string (i.e. "foobar").
|
||||
// Ref: RFC6266 S4.1 -> RFC2616 S2.2; RFC 7578 S4.2 -> RFC2183 S2 -> ... .
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new("[\x01-\x08\x10\x1F\x7F\"\\\\]").unwrap();
|
||||
}
|
||||
match self {
|
||||
DispositionParam::Name(ref value) => write!(f, "name={}", value),
|
||||
DispositionParam::Filename(ref value) => {
|
||||
write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref())
|
||||
}
|
||||
DispositionParam::Unknown(ref name, ref value) => write!(
|
||||
f,
|
||||
"{}=\"{}\"",
|
||||
name,
|
||||
&RE.replace_all(value, "\\$0").as_ref()
|
||||
),
|
||||
DispositionParam::FilenameExt(ref ext_value) => {
|
||||
write!(f, "filename*={}", ext_value)
|
||||
}
|
||||
DispositionParam::UnknownExt(ref name, ref ext_value) => {
|
||||
write!(f, "{}*={}", name, ext_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ContentDisposition {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.disposition)?;
|
||||
self.parameters
|
||||
.iter()
|
||||
.map(|param| write!(f, "; {}", param))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ContentDisposition, DispositionParam, DispositionType};
|
||||
use header::shared::Charset;
|
||||
use header::{ExtendedValue, HeaderValue};
|
||||
#[test]
|
||||
fn test_from_raw_basic() {
|
||||
assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err());
|
||||
|
||||
let a = HeaderValue::from_static(
|
||||
"form-data; dummy=3; name=upload; filename=\"sample.png\"",
|
||||
);
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::FormData,
|
||||
parameters: vec![
|
||||
DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
|
||||
DispositionParam::Name("upload".to_owned()),
|
||||
DispositionParam::Filename("sample.png".to_owned()),
|
||||
],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
|
||||
let a = HeaderValue::from_static("attachment; filename=\"image.jpg\"");
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::Attachment,
|
||||
parameters: vec![DispositionParam::Filename("image.jpg".to_owned())],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
|
||||
let a = HeaderValue::from_static("inline; filename=image.jpg");
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::Inline,
|
||||
parameters: vec![DispositionParam::Filename("image.jpg".to_owned())],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
|
||||
let a = HeaderValue::from_static(
|
||||
"attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"",
|
||||
);
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::Attachment,
|
||||
parameters: vec![DispositionParam::Unknown(
|
||||
String::from("creation-date"),
|
||||
"Wed, 12 Feb 1997 16:29:51 -0500".to_owned(),
|
||||
)],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_raw_extended() {
|
||||
let a = HeaderValue::from_static(
|
||||
"attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
|
||||
);
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::Attachment,
|
||||
parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
|
||||
charset: Charset::Ext(String::from("UTF-8")),
|
||||
language_tag: None,
|
||||
value: vec![
|
||||
0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20,
|
||||
b'r', b'a', b't', b'e', b's',
|
||||
],
|
||||
})],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
|
||||
let a = HeaderValue::from_static(
|
||||
"attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
|
||||
);
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::Attachment,
|
||||
parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
|
||||
charset: Charset::Ext(String::from("UTF-8")),
|
||||
language_tag: None,
|
||||
value: vec![
|
||||
0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20,
|
||||
b'r', b'a', b't', b'e', b's',
|
||||
],
|
||||
})],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_raw_extra_whitespace() {
|
||||
let a = HeaderValue::from_static(
|
||||
"form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ",
|
||||
);
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::FormData,
|
||||
parameters: vec![
|
||||
DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()),
|
||||
DispositionParam::Name("upload".to_owned()),
|
||||
DispositionParam::Filename("sample.png".to_owned()),
|
||||
],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_raw_unordered() {
|
||||
let a = HeaderValue::from_static(
|
||||
"form-data; dummy=3; filename=\"sample.png\" ; name=upload;",
|
||||
// Actually, a trailling semolocon is not compliant. But it is fine to accept.
|
||||
);
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::FormData,
|
||||
parameters: vec![
|
||||
DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
|
||||
DispositionParam::Filename("sample.png".to_owned()),
|
||||
DispositionParam::Name("upload".to_owned()),
|
||||
],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
|
||||
let a = HeaderValue::from_str(
|
||||
"attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"",
|
||||
).unwrap();
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::Attachment,
|
||||
parameters: vec![
|
||||
DispositionParam::FilenameExt(ExtendedValue {
|
||||
charset: Charset::Iso_8859_1,
|
||||
language_tag: None,
|
||||
value: b"foo-\xe4.html".to_vec(),
|
||||
}),
|
||||
DispositionParam::Filename("foo-ä.html".to_owned()),
|
||||
],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_raw_only_disp() {
|
||||
let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment"))
|
||||
.unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::Attachment,
|
||||
parameters: vec![],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
|
||||
let a =
|
||||
ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::Inline,
|
||||
parameters: vec![],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
|
||||
let a = ContentDisposition::from_raw(&HeaderValue::from_static(
|
||||
"unknown-disp-param",
|
||||
)).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::Ext(String::from("unknown-disp-param")),
|
||||
parameters: vec![],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_raw_with_mixed_case() {
|
||||
let a = HeaderValue::from_str(
|
||||
"InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"",
|
||||
).unwrap();
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::Inline,
|
||||
parameters: vec![
|
||||
DispositionParam::FilenameExt(ExtendedValue {
|
||||
charset: Charset::Iso_8859_1,
|
||||
language_tag: None,
|
||||
value: b"foo-\xe4.html".to_vec(),
|
||||
}),
|
||||
DispositionParam::Filename("foo-ä.html".to_owned()),
|
||||
],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_raw_with_unicode() {
|
||||
/* RFC7578 Section 4.2:
|
||||
Some commonly deployed systems use multipart/form-data with file names directly encoded
|
||||
including octets outside the US-ASCII range. The encoding used for the file names is
|
||||
typically UTF-8, although HTML forms will use the charset associated with the form.
|
||||
|
||||
Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above.
|
||||
(And now, only UTF-8 is handled by this implementation.)
|
||||
*/
|
||||
let a =
|
||||
HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"")
|
||||
.unwrap();
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::FormData,
|
||||
parameters: vec![
|
||||
DispositionParam::Name(String::from("upload")),
|
||||
DispositionParam::Filename(String::from("文件.webp")),
|
||||
],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
|
||||
let a =
|
||||
HeaderValue::from_str("form-data; name=upload; filename=\"余固知謇謇之為患兮,忍而不能舍也.pptx\"").unwrap();
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::FormData,
|
||||
parameters: vec![
|
||||
DispositionParam::Name(String::from("upload")),
|
||||
DispositionParam::Filename(String::from(
|
||||
"余固知謇謇之為患兮,忍而不能舍也.pptx",
|
||||
)),
|
||||
],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_raw_escape() {
|
||||
let a = HeaderValue::from_static(
|
||||
"form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"",
|
||||
);
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::FormData,
|
||||
parameters: vec![
|
||||
DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
|
||||
DispositionParam::Name("upload".to_owned()),
|
||||
DispositionParam::Filename(
|
||||
['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g']
|
||||
.iter()
|
||||
.collect(),
|
||||
),
|
||||
],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_raw_semicolon() {
|
||||
let a =
|
||||
HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\"");
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::FormData,
|
||||
parameters: vec![DispositionParam::Filename(String::from(
|
||||
"A semicolon here;.pdf",
|
||||
))],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_raw_uncessary_percent_decode() {
|
||||
let a = HeaderValue::from_static(
|
||||
"form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", // Should not be decoded!
|
||||
);
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::FormData,
|
||||
parameters: vec![
|
||||
DispositionParam::Name("photo".to_owned()),
|
||||
DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")),
|
||||
],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
|
||||
let a = HeaderValue::from_static(
|
||||
"form-data; name=photo; filename=\"%74%65%73%74.png\"",
|
||||
);
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let b = ContentDisposition {
|
||||
disposition: DispositionType::FormData,
|
||||
parameters: vec![
|
||||
DispositionParam::Name("photo".to_owned()),
|
||||
DispositionParam::Filename(String::from("%74%65%73%74.png")),
|
||||
],
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_raw_param_value_missing() {
|
||||
let a = HeaderValue::from_static("form-data; name=upload ; filename=");
|
||||
assert!(ContentDisposition::from_raw(&a).is_err());
|
||||
|
||||
let a = HeaderValue::from_static("attachment; dummy=; filename=invoice.pdf");
|
||||
assert!(ContentDisposition::from_raw(&a).is_err());
|
||||
|
||||
let a = HeaderValue::from_static("inline; filename= ");
|
||||
assert!(ContentDisposition::from_raw(&a).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_raw_param_name_missing() {
|
||||
let a = HeaderValue::from_static("inline; =\"test.txt\"");
|
||||
assert!(ContentDisposition::from_raw(&a).is_err());
|
||||
|
||||
let a = HeaderValue::from_static("inline; =diary.odt");
|
||||
assert!(ContentDisposition::from_raw(&a).is_err());
|
||||
|
||||
let a = HeaderValue::from_static("inline; =");
|
||||
assert!(ContentDisposition::from_raw(&a).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_extended() {
|
||||
let as_string =
|
||||
"attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates";
|
||||
let a = HeaderValue::from_static(as_string);
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let display_rendered = format!("{}", a);
|
||||
assert_eq!(as_string, display_rendered);
|
||||
|
||||
let a = HeaderValue::from_static("attachment; filename=colourful.csv");
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let display_rendered = format!("{}", a);
|
||||
assert_eq!(
|
||||
"attachment; filename=\"colourful.csv\"".to_owned(),
|
||||
display_rendered
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_quote() {
|
||||
let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\"";
|
||||
as_string
|
||||
.find(['\\', '\"'].iter().collect::<String>().as_str())
|
||||
.unwrap(); // ensure `\"` is there
|
||||
let a = HeaderValue::from_static(as_string);
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let display_rendered = format!("{}", a);
|
||||
assert_eq!(as_string, display_rendered);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_space_tab() {
|
||||
let as_string = "form-data; name=upload; filename=\"Space here.png\"";
|
||||
let a = HeaderValue::from_static(as_string);
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let display_rendered = format!("{}", a);
|
||||
assert_eq!(as_string, display_rendered);
|
||||
|
||||
let a: ContentDisposition = ContentDisposition {
|
||||
disposition: DispositionType::Inline,
|
||||
parameters: vec![DispositionParam::Filename(String::from("Tab\there.png"))],
|
||||
};
|
||||
let display_rendered = format!("{}", a);
|
||||
assert_eq!("inline; filename=\"Tab\x09here.png\"", display_rendered);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_control_characters() {
|
||||
/* let a = "attachment; filename=\"carriage\rreturn.png\"";
|
||||
let a = HeaderValue::from_static(a);
|
||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||
let display_rendered = format!("{}", a);
|
||||
assert_eq!(
|
||||
"attachment; filename=\"carriage\\\rreturn.png\"",
|
||||
display_rendered
|
||||
);*/
|
||||
// No way to create a HeaderValue containing a carriage return.
|
||||
|
||||
let a: ContentDisposition = ContentDisposition {
|
||||
disposition: DispositionType::Inline,
|
||||
parameters: vec![DispositionParam::Filename(String::from("bell\x07.png"))],
|
||||
};
|
||||
let display_rendered = format!("{}", a);
|
||||
assert_eq!("inline; filename=\"bell\\\x07.png\"", display_rendered);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_param_methods() {
|
||||
let param = DispositionParam::Filename(String::from("sample.txt"));
|
||||
assert!(param.is_filename());
|
||||
assert_eq!(param.as_filename().unwrap(), "sample.txt");
|
||||
|
||||
let param = DispositionParam::Unknown(String::from("foo"), String::from("bar"));
|
||||
assert!(param.is_unknown("foo"));
|
||||
assert_eq!(param.as_unknown("fOo"), Some("bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disposition_methods() {
|
||||
let cd = ContentDisposition {
|
||||
disposition: DispositionType::FormData,
|
||||
parameters: vec![
|
||||
DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
|
||||
DispositionParam::Name("upload".to_owned()),
|
||||
DispositionParam::Filename("sample.png".to_owned()),
|
||||
],
|
||||
};
|
||||
assert_eq!(cd.get_name(), Some("upload"));
|
||||
assert_eq!(cd.get_unknown("dummy"), Some("3"));
|
||||
assert_eq!(cd.get_filename(), Some("sample.png"));
|
||||
assert_eq!(cd.get_unknown_ext("dummy"), None);
|
||||
assert_eq!(cd.get_unknown("duMMy"), Some("3"));
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
use header::{QualityItem, CONTENT_LANGUAGE};
|
||||
use language_tags::LanguageTag;
|
||||
|
||||
header! {
|
||||
/// `Content-Language` header, defined in
|
||||
/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2)
|
||||
///
|
||||
/// The `Content-Language` header field describes the natural language(s)
|
||||
/// of the intended audience for the representation. Note that this
|
||||
/// might not be equivalent to all the languages used within the
|
||||
/// representation.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// Content-Language = 1#language-tag
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
///
|
||||
/// * `da`
|
||||
/// * `mi, en`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # #[macro_use] extern crate language_tags;
|
||||
/// use actix_web::HttpResponse;
|
||||
/// # use actix_web::http::header::{ContentLanguage, qitem};
|
||||
/// #
|
||||
/// # fn main() {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(
|
||||
/// ContentLanguage(vec![
|
||||
/// qitem(langtag!(en)),
|
||||
/// ])
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # #[macro_use] extern crate language_tags;
|
||||
/// use actix_web::HttpResponse;
|
||||
/// # use actix_web::http::header::{ContentLanguage, qitem};
|
||||
/// #
|
||||
/// # fn main() {
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(
|
||||
/// ContentLanguage(vec![
|
||||
/// qitem(langtag!(da)),
|
||||
/// qitem(langtag!(en;;;GB)),
|
||||
/// ])
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
(ContentLanguage, CONTENT_LANGUAGE) => (QualityItem<LanguageTag>)+
|
||||
|
||||
test_content_language {
|
||||
test_header!(test1, vec![b"da"]);
|
||||
test_header!(test2, vec![b"mi, en"]);
|
||||
}
|
||||
}
|
@ -1,210 +0,0 @@
|
||||
use error::ParseError;
|
||||
use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer,
|
||||
CONTENT_RANGE};
|
||||
use std::fmt::{self, Display, Write};
|
||||
use std::str::FromStr;
|
||||
|
||||
header! {
|
||||
/// `Content-Range` header, defined in
|
||||
/// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2)
|
||||
(ContentRange, CONTENT_RANGE) => [ContentRangeSpec]
|
||||
|
||||
test_content_range {
|
||||
test_header!(test_bytes,
|
||||
vec![b"bytes 0-499/500"],
|
||||
Some(ContentRange(ContentRangeSpec::Bytes {
|
||||
range: Some((0, 499)),
|
||||
instance_length: Some(500)
|
||||
})));
|
||||
|
||||
test_header!(test_bytes_unknown_len,
|
||||
vec![b"bytes 0-499/*"],
|
||||
Some(ContentRange(ContentRangeSpec::Bytes {
|
||||
range: Some((0, 499)),
|
||||
instance_length: None
|
||||
})));
|
||||
|
||||
test_header!(test_bytes_unknown_range,
|
||||
vec![b"bytes */500"],
|
||||
Some(ContentRange(ContentRangeSpec::Bytes {
|
||||
range: None,
|
||||
instance_length: Some(500)
|
||||
})));
|
||||
|
||||
test_header!(test_unregistered,
|
||||
vec![b"seconds 1-2"],
|
||||
Some(ContentRange(ContentRangeSpec::Unregistered {
|
||||
unit: "seconds".to_owned(),
|
||||
resp: "1-2".to_owned()
|
||||
})));
|
||||
|
||||
test_header!(test_no_len,
|
||||
vec![b"bytes 0-499"],
|
||||
None::<ContentRange>);
|
||||
|
||||
test_header!(test_only_unit,
|
||||
vec![b"bytes"],
|
||||
None::<ContentRange>);
|
||||
|
||||
test_header!(test_end_less_than_start,
|
||||
vec![b"bytes 499-0/500"],
|
||||
None::<ContentRange>);
|
||||
|
||||
test_header!(test_blank,
|
||||
vec![b""],
|
||||
None::<ContentRange>);
|
||||
|
||||
test_header!(test_bytes_many_spaces,
|
||||
vec![b"bytes 1-2/500 3"],
|
||||
None::<ContentRange>);
|
||||
|
||||
test_header!(test_bytes_many_slashes,
|
||||
vec![b"bytes 1-2/500/600"],
|
||||
None::<ContentRange>);
|
||||
|
||||
test_header!(test_bytes_many_dashes,
|
||||
vec![b"bytes 1-2-3/500"],
|
||||
None::<ContentRange>);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2)
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// Content-Range = byte-content-range
|
||||
/// / other-content-range
|
||||
///
|
||||
/// byte-content-range = bytes-unit SP
|
||||
/// ( byte-range-resp / unsatisfied-range )
|
||||
///
|
||||
/// byte-range-resp = byte-range "/" ( complete-length / "*" )
|
||||
/// byte-range = first-byte-pos "-" last-byte-pos
|
||||
/// unsatisfied-range = "*/" complete-length
|
||||
///
|
||||
/// complete-length = 1*DIGIT
|
||||
///
|
||||
/// other-content-range = other-range-unit SP other-range-resp
|
||||
/// other-range-resp = *CHAR
|
||||
/// ```
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum ContentRangeSpec {
|
||||
/// Byte range
|
||||
Bytes {
|
||||
/// First and last bytes of the range, omitted if request could not be
|
||||
/// satisfied
|
||||
range: Option<(u64, u64)>,
|
||||
|
||||
/// Total length of the instance, can be omitted if unknown
|
||||
instance_length: Option<u64>,
|
||||
},
|
||||
|
||||
/// Custom range, with unit not registered at IANA
|
||||
Unregistered {
|
||||
/// other-range-unit
|
||||
unit: String,
|
||||
|
||||
/// other-range-resp
|
||||
resp: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> {
|
||||
let mut iter = s.splitn(2, separator);
|
||||
match (iter.next(), iter.next()) {
|
||||
(Some(a), Some(b)) => Some((a, b)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ContentRangeSpec {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, ParseError> {
|
||||
let res = match split_in_two(s, ' ') {
|
||||
Some(("bytes", resp)) => {
|
||||
let (range, instance_length) =
|
||||
split_in_two(resp, '/').ok_or(ParseError::Header)?;
|
||||
|
||||
let instance_length = if instance_length == "*" {
|
||||
None
|
||||
} else {
|
||||
Some(instance_length
|
||||
.parse()
|
||||
.map_err(|_| ParseError::Header)?)
|
||||
};
|
||||
|
||||
let range = if range == "*" {
|
||||
None
|
||||
} else {
|
||||
let (first_byte, last_byte) =
|
||||
split_in_two(range, '-').ok_or(ParseError::Header)?;
|
||||
let first_byte = first_byte.parse().map_err(|_| ParseError::Header)?;
|
||||
let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?;
|
||||
if last_byte < first_byte {
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
Some((first_byte, last_byte))
|
||||
};
|
||||
|
||||
ContentRangeSpec::Bytes {
|
||||
range,
|
||||
instance_length,
|
||||
}
|
||||
}
|
||||
Some((unit, resp)) => ContentRangeSpec::Unregistered {
|
||||
unit: unit.to_owned(),
|
||||
resp: resp.to_owned(),
|
||||
},
|
||||
_ => return Err(ParseError::Header),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ContentRangeSpec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ContentRangeSpec::Bytes {
|
||||
range,
|
||||
instance_length,
|
||||
} => {
|
||||
f.write_str("bytes ")?;
|
||||
match range {
|
||||
Some((first_byte, last_byte)) => {
|
||||
write!(f, "{}-{}", first_byte, last_byte)?;
|
||||
}
|
||||
None => {
|
||||
f.write_str("*")?;
|
||||
}
|
||||
};
|
||||
f.write_str("/")?;
|
||||
if let Some(v) = instance_length {
|
||||
write!(f, "{}", v)
|
||||
} else {
|
||||
f.write_str("*")
|
||||
}
|
||||
}
|
||||
ContentRangeSpec::Unregistered {
|
||||
ref unit,
|
||||
ref resp,
|
||||
} => {
|
||||
f.write_str(unit)?;
|
||||
f.write_str(" ")?;
|
||||
f.write_str(resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoHeaderValue for ContentRangeSpec {
|
||||
type Error = InvalidHeaderValueBytes;
|
||||
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
let mut writer = Writer::new();
|
||||
let _ = write!(&mut writer, "{}", self);
|
||||
HeaderValue::from_shared(writer.take())
|
||||
}
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
use header::CONTENT_TYPE;
|
||||
use mime::{self, Mime};
|
||||
|
||||
header! {
|
||||
/// `Content-Type` header, defined in
|
||||
/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5)
|
||||
///
|
||||
/// The `Content-Type` header field indicates the media type of the
|
||||
/// associated representation: either the representation enclosed in the
|
||||
/// message payload or the selected representation, as determined by the
|
||||
/// message semantics. The indicated media type defines both the data
|
||||
/// format and how that data is intended to be processed by a recipient,
|
||||
/// within the scope of the received message semantics, after any content
|
||||
/// codings indicated by Content-Encoding are decoded.
|
||||
///
|
||||
/// Although the `mime` crate allows the mime options to be any slice, this crate
|
||||
/// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If
|
||||
/// this is an issue, it's possible to implement `Header` on a custom struct.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// Content-Type = media-type
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
///
|
||||
/// * `text/html; charset=utf-8`
|
||||
/// * `application/json`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::ContentType;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(
|
||||
/// ContentType::json()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate mime;
|
||||
/// # extern crate actix_web;
|
||||
/// use mime::TEXT_HTML;
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::ContentType;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(
|
||||
/// ContentType(TEXT_HTML)
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
(ContentType, CONTENT_TYPE) => [Mime]
|
||||
|
||||
test_content_type {
|
||||
test_header!(
|
||||
test1,
|
||||
vec![b"text/html"],
|
||||
Some(HeaderField(TEXT_HTML)));
|
||||
}
|
||||
}
|
||||
|
||||
impl ContentType {
|
||||
/// A constructor to easily create a `Content-Type: application/json`
|
||||
/// header.
|
||||
#[inline]
|
||||
pub fn json() -> ContentType {
|
||||
ContentType(mime::APPLICATION_JSON)
|
||||
}
|
||||
|
||||
/// A constructor to easily create a `Content-Type: text/plain;
|
||||
/// charset=utf-8` header.
|
||||
#[inline]
|
||||
pub fn plaintext() -> ContentType {
|
||||
ContentType(mime::TEXT_PLAIN_UTF_8)
|
||||
}
|
||||
|
||||
/// A constructor to easily create a `Content-Type: text/html` header.
|
||||
#[inline]
|
||||
pub fn html() -> ContentType {
|
||||
ContentType(mime::TEXT_HTML)
|
||||
}
|
||||
|
||||
/// A constructor to easily create a `Content-Type: text/xml` header.
|
||||
#[inline]
|
||||
pub fn xml() -> ContentType {
|
||||
ContentType(mime::TEXT_XML)
|
||||
}
|
||||
|
||||
/// A constructor to easily create a `Content-Type:
|
||||
/// application/www-form-url-encoded` header.
|
||||
#[inline]
|
||||
pub fn form_url_encoded() -> ContentType {
|
||||
ContentType(mime::APPLICATION_WWW_FORM_URLENCODED)
|
||||
}
|
||||
/// A constructor to easily create a `Content-Type: image/jpeg` header.
|
||||
#[inline]
|
||||
pub fn jpeg() -> ContentType {
|
||||
ContentType(mime::IMAGE_JPEG)
|
||||
}
|
||||
|
||||
/// A constructor to easily create a `Content-Type: image/png` header.
|
||||
#[inline]
|
||||
pub fn png() -> ContentType {
|
||||
ContentType(mime::IMAGE_PNG)
|
||||
}
|
||||
|
||||
/// A constructor to easily create a `Content-Type:
|
||||
/// application/octet-stream` header.
|
||||
#[inline]
|
||||
pub fn octet_stream() -> ContentType {
|
||||
ContentType(mime::APPLICATION_OCTET_STREAM)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ContentType {}
|
@ -1,42 +0,0 @@
|
||||
use header::{HttpDate, DATE};
|
||||
use std::time::SystemTime;
|
||||
|
||||
header! {
|
||||
/// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2)
|
||||
///
|
||||
/// The `Date` header field represents the date and time at which the
|
||||
/// message was originated.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// Date = HTTP-date
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
///
|
||||
/// * `Tue, 15 Nov 1994 08:12:31 GMT`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::Date;
|
||||
/// use std::time::SystemTime;
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(Date(SystemTime::now().into()));
|
||||
/// ```
|
||||
(Date, DATE) => [HttpDate]
|
||||
|
||||
test_date {
|
||||
test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]);
|
||||
}
|
||||
}
|
||||
|
||||
impl Date {
|
||||
/// Create a date instance set to the current system time
|
||||
pub fn now() -> Date {
|
||||
Date(SystemTime::now().into())
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
use header::{EntityTag, ETAG};
|
||||
|
||||
header! {
|
||||
/// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3)
|
||||
///
|
||||
/// The `ETag` header field in a response provides the current entity-tag
|
||||
/// for the selected representation, as determined at the conclusion of
|
||||
/// handling the request. An entity-tag is an opaque validator for
|
||||
/// differentiating between multiple representations of the same
|
||||
/// resource, regardless of whether those multiple representations are
|
||||
/// due to resource state changes over time, content negotiation
|
||||
/// resulting in multiple representations being valid at the same time,
|
||||
/// or both. An entity-tag consists of an opaque quoted string, possibly
|
||||
/// prefixed by a weakness indicator.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// ETag = entity-tag
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
///
|
||||
/// * `"xyzzy"`
|
||||
/// * `W/"xyzzy"`
|
||||
/// * `""`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{ETag, EntityTag};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned())));
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{ETag, EntityTag};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned())));
|
||||
/// ```
|
||||
(ETag, ETAG) => [EntityTag]
|
||||
|
||||
test_etag {
|
||||
// From the RFC
|
||||
test_header!(test1,
|
||||
vec![b"\"xyzzy\""],
|
||||
Some(ETag(EntityTag::new(false, "xyzzy".to_owned()))));
|
||||
test_header!(test2,
|
||||
vec![b"W/\"xyzzy\""],
|
||||
Some(ETag(EntityTag::new(true, "xyzzy".to_owned()))));
|
||||
test_header!(test3,
|
||||
vec![b"\"\""],
|
||||
Some(ETag(EntityTag::new(false, "".to_owned()))));
|
||||
// Own tests
|
||||
test_header!(test4,
|
||||
vec![b"\"foobar\""],
|
||||
Some(ETag(EntityTag::new(false, "foobar".to_owned()))));
|
||||
test_header!(test5,
|
||||
vec![b"\"\""],
|
||||
Some(ETag(EntityTag::new(false, "".to_owned()))));
|
||||
test_header!(test6,
|
||||
vec![b"W/\"weak-etag\""],
|
||||
Some(ETag(EntityTag::new(true, "weak-etag".to_owned()))));
|
||||
test_header!(test7,
|
||||
vec![b"W/\"\x65\x62\""],
|
||||
Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned()))));
|
||||
test_header!(test8,
|
||||
vec![b"W/\"\""],
|
||||
Some(ETag(EntityTag::new(true, "".to_owned()))));
|
||||
test_header!(test9,
|
||||
vec![b"no-dquotes"],
|
||||
None::<ETag>);
|
||||
test_header!(test10,
|
||||
vec![b"w/\"the-first-w-is-case-sensitive\""],
|
||||
None::<ETag>);
|
||||
test_header!(test11,
|
||||
vec![b""],
|
||||
None::<ETag>);
|
||||
test_header!(test12,
|
||||
vec![b"\"unmatched-dquotes1"],
|
||||
None::<ETag>);
|
||||
test_header!(test13,
|
||||
vec![b"unmatched-dquotes2\""],
|
||||
None::<ETag>);
|
||||
test_header!(test14,
|
||||
vec![b"matched-\"dquotes\""],
|
||||
None::<ETag>);
|
||||
test_header!(test15,
|
||||
vec![b"\""],
|
||||
None::<ETag>);
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
use header::{HttpDate, EXPIRES};
|
||||
|
||||
header! {
|
||||
/// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3)
|
||||
///
|
||||
/// The `Expires` header field gives the date/time after which the
|
||||
/// response is considered stale.
|
||||
///
|
||||
/// The presence of an Expires field does not imply that the original
|
||||
/// resource will change or cease to exist at, before, or after that
|
||||
/// time.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// Expires = HTTP-date
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
/// * `Thu, 01 Dec 1994 16:00:00 GMT`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::Expires;
|
||||
/// use std::time::{SystemTime, Duration};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24);
|
||||
/// builder.set(Expires(expiration.into()));
|
||||
/// ```
|
||||
(Expires, EXPIRES) => [HttpDate]
|
||||
|
||||
test_expires {
|
||||
// Test case from RFC
|
||||
test_header!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]);
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
use header::{EntityTag, IF_MATCH};
|
||||
|
||||
header! {
|
||||
/// `If-Match` header, defined in
|
||||
/// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1)
|
||||
///
|
||||
/// The `If-Match` header field makes the request method conditional on
|
||||
/// the recipient origin server either having at least one current
|
||||
/// representation of the target resource, when the field-value is "*",
|
||||
/// or having a current representation of the target resource that has an
|
||||
/// entity-tag matching a member of the list of entity-tags provided in
|
||||
/// the field-value.
|
||||
///
|
||||
/// An origin server MUST use the strong comparison function when
|
||||
/// comparing entity-tags for `If-Match`, since the client
|
||||
/// intends this precondition to prevent the method from being applied if
|
||||
/// there have been any changes to the representation data.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// If-Match = "*" / 1#entity-tag
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
///
|
||||
/// * `"xyzzy"`
|
||||
/// * "xyzzy", "r2d2xxxx", "c3piozzzz"
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::IfMatch;
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(IfMatch::Any);
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{IfMatch, EntityTag};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(
|
||||
/// IfMatch::Items(vec![
|
||||
/// EntityTag::new(false, "xyzzy".to_owned()),
|
||||
/// EntityTag::new(false, "foobar".to_owned()),
|
||||
/// EntityTag::new(false, "bazquux".to_owned()),
|
||||
/// ])
|
||||
/// );
|
||||
/// ```
|
||||
(IfMatch, IF_MATCH) => {Any / (EntityTag)+}
|
||||
|
||||
test_if_match {
|
||||
test_header!(
|
||||
test1,
|
||||
vec![b"\"xyzzy\""],
|
||||
Some(HeaderField::Items(
|
||||
vec![EntityTag::new(false, "xyzzy".to_owned())])));
|
||||
test_header!(
|
||||
test2,
|
||||
vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""],
|
||||
Some(HeaderField::Items(
|
||||
vec![EntityTag::new(false, "xyzzy".to_owned()),
|
||||
EntityTag::new(false, "r2d2xxxx".to_owned()),
|
||||
EntityTag::new(false, "c3piozzzz".to_owned())])));
|
||||
test_header!(test3, vec![b"*"], Some(IfMatch::Any));
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
use header::{HttpDate, IF_MODIFIED_SINCE};
|
||||
|
||||
header! {
|
||||
/// `If-Modified-Since` header, defined in
|
||||
/// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3)
|
||||
///
|
||||
/// The `If-Modified-Since` header field makes a GET or HEAD request
|
||||
/// method conditional on the selected representation's modification date
|
||||
/// being more recent than the date provided in the field-value.
|
||||
/// Transfer of the selected representation's data is avoided if that
|
||||
/// data has not changed.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// If-Unmodified-Since = HTTP-date
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::IfModifiedSince;
|
||||
/// use std::time::{SystemTime, Duration};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||
/// builder.set(IfModifiedSince(modified.into()));
|
||||
/// ```
|
||||
(IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate]
|
||||
|
||||
test_if_modified_since {
|
||||
// Test case from RFC
|
||||
test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
use header::{EntityTag, IF_NONE_MATCH};
|
||||
|
||||
header! {
|
||||
/// `If-None-Match` header, defined in
|
||||
/// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2)
|
||||
///
|
||||
/// The `If-None-Match` header field makes the request method conditional
|
||||
/// on a recipient cache or origin server either not having any current
|
||||
/// representation of the target resource, when the field-value is "*",
|
||||
/// or having a selected representation with an entity-tag that does not
|
||||
/// match any of those listed in the field-value.
|
||||
///
|
||||
/// A recipient MUST use the weak comparison function when comparing
|
||||
/// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags
|
||||
/// can be used for cache validation even if there have been changes to
|
||||
/// the representation data.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// If-None-Match = "*" / 1#entity-tag
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
///
|
||||
/// * `"xyzzy"`
|
||||
/// * `W/"xyzzy"`
|
||||
/// * `"xyzzy", "r2d2xxxx", "c3piozzzz"`
|
||||
/// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"`
|
||||
/// * `*`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::IfNoneMatch;
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(IfNoneMatch::Any);
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{IfNoneMatch, EntityTag};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(
|
||||
/// IfNoneMatch::Items(vec![
|
||||
/// EntityTag::new(false, "xyzzy".to_owned()),
|
||||
/// EntityTag::new(false, "foobar".to_owned()),
|
||||
/// EntityTag::new(false, "bazquux".to_owned()),
|
||||
/// ])
|
||||
/// );
|
||||
/// ```
|
||||
(IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+}
|
||||
|
||||
test_if_none_match {
|
||||
test_header!(test1, vec![b"\"xyzzy\""]);
|
||||
test_header!(test2, vec![b"W/\"xyzzy\""]);
|
||||
test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]);
|
||||
test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]);
|
||||
test_header!(test5, vec![b"*"]);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::IfNoneMatch;
|
||||
use header::{EntityTag, Header, IF_NONE_MATCH};
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_if_none_match() {
|
||||
let mut if_none_match: Result<IfNoneMatch, _>;
|
||||
|
||||
let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish();
|
||||
if_none_match = Header::parse(&req);
|
||||
assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any));
|
||||
|
||||
let req =
|
||||
TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..])
|
||||
.finish();
|
||||
|
||||
if_none_match = Header::parse(&req);
|
||||
let mut entities: Vec<EntityTag> = Vec::new();
|
||||
let foobar_etag = EntityTag::new(false, "foobar".to_owned());
|
||||
let weak_etag = EntityTag::new(true, "weak-etag".to_owned());
|
||||
entities.push(foobar_etag);
|
||||
entities.push(weak_etag);
|
||||
assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities)));
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
use error::ParseError;
|
||||
use header::from_one_raw_str;
|
||||
use header::{EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue,
|
||||
InvalidHeaderValueBytes, Writer};
|
||||
use http::header;
|
||||
use httpmessage::HttpMessage;
|
||||
use std::fmt::{self, Display, Write};
|
||||
|
||||
/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2)
|
||||
///
|
||||
/// If a client has a partial copy of a representation and wishes to have
|
||||
/// an up-to-date copy of the entire representation, it could use the
|
||||
/// Range header field with a conditional GET (using either or both of
|
||||
/// If-Unmodified-Since and If-Match.) However, if the precondition
|
||||
/// fails because the representation has been modified, the client would
|
||||
/// then have to make a second request to obtain the entire current
|
||||
/// representation.
|
||||
///
|
||||
/// The `If-Range` header field allows a client to \"short-circuit\" the
|
||||
/// second request. Informally, its meaning is as follows: if the
|
||||
/// representation is unchanged, send me the part(s) that I am requesting
|
||||
/// in Range; otherwise, send me the entire representation.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// If-Range = entity-tag / HTTP-date
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
///
|
||||
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
|
||||
/// * `\"xyzzy\"`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{EntityTag, IfRange};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.set(IfRange::EntityTag(EntityTag::new(
|
||||
/// false,
|
||||
/// "xyzzy".to_owned(),
|
||||
/// )));
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::IfRange;
|
||||
/// use std::time::{Duration, SystemTime};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||
/// builder.set(IfRange::Date(fetched.into()));
|
||||
/// ```
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum IfRange {
|
||||
/// The entity-tag the client has of the resource
|
||||
EntityTag(EntityTag),
|
||||
/// The date when the client retrieved the resource
|
||||
Date(HttpDate),
|
||||
}
|
||||
|
||||
impl Header for IfRange {
|
||||
fn name() -> HeaderName {
|
||||
header::IF_RANGE
|
||||
}
|
||||
#[inline]
|
||||
fn parse<T>(msg: &T) -> Result<Self, ParseError>
|
||||
where
|
||||
T: HttpMessage,
|
||||
{
|
||||
let etag: Result<EntityTag, _> =
|
||||
from_one_raw_str(msg.headers().get(header::IF_RANGE));
|
||||
if let Ok(etag) = etag {
|
||||
return Ok(IfRange::EntityTag(etag));
|
||||
}
|
||||
let date: Result<HttpDate, _> =
|
||||
from_one_raw_str(msg.headers().get(header::IF_RANGE));
|
||||
if let Ok(date) = date {
|
||||
return Ok(IfRange::Date(date));
|
||||
}
|
||||
Err(ParseError::Header)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IfRange {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
IfRange::EntityTag(ref x) => Display::fmt(x, f),
|
||||
IfRange::Date(ref x) => Display::fmt(x, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoHeaderValue for IfRange {
|
||||
type Error = InvalidHeaderValueBytes;
|
||||
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
let mut writer = Writer::new();
|
||||
let _ = write!(&mut writer, "{}", self);
|
||||
HeaderValue::from_shared(writer.take())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_if_range {
|
||||
use super::IfRange as HeaderField;
|
||||
use header::*;
|
||||
use std::str;
|
||||
test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
|
||||
test_header!(test2, vec![b"\"xyzzy\""]);
|
||||
test_header!(test3, vec![b"this-is-invalid"], None::<IfRange>);
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
use header::{HttpDate, IF_UNMODIFIED_SINCE};
|
||||
|
||||
header! {
|
||||
/// `If-Unmodified-Since` header, defined in
|
||||
/// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4)
|
||||
///
|
||||
/// The `If-Unmodified-Since` header field makes the request method
|
||||
/// conditional on the selected representation's last modification date
|
||||
/// being earlier than or equal to the date provided in the field-value.
|
||||
/// This field accomplishes the same purpose as If-Match for cases where
|
||||
/// the user agent does not have an entity-tag for the representation.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// If-Unmodified-Since = HTTP-date
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
///
|
||||
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::IfUnmodifiedSince;
|
||||
/// use std::time::{SystemTime, Duration};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||
/// builder.set(IfUnmodifiedSince(modified.into()));
|
||||
/// ```
|
||||
(IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate]
|
||||
|
||||
test_if_unmodified_since {
|
||||
// Test case from RFC
|
||||
test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
use header::{HttpDate, LAST_MODIFIED};
|
||||
|
||||
header! {
|
||||
/// `Last-Modified` header, defined in
|
||||
/// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2)
|
||||
///
|
||||
/// The `Last-Modified` header field in a response provides a timestamp
|
||||
/// indicating the date and time at which the origin server believes the
|
||||
/// selected representation was last modified, as determined at the
|
||||
/// conclusion of handling the request.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// Expires = HTTP-date
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
///
|
||||
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::LastModified;
|
||||
/// use std::time::{SystemTime, Duration};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||
/// builder.set(LastModified(modified.into()));
|
||||
/// ```
|
||||
(LastModified, LAST_MODIFIED) => [HttpDate]
|
||||
|
||||
test_last_modified {
|
||||
// Test case from RFC
|
||||
test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);}
|
||||
}
|
@ -1,350 +0,0 @@
|
||||
//! A Collection of Header implementations for common HTTP Headers.
|
||||
//!
|
||||
//! ## Mime
|
||||
//!
|
||||
//! Several header fields use MIME values for their contents. Keeping with the
|
||||
//! strongly-typed theme, the [mime](https://docs.rs/mime) crate
|
||||
//! is used, such as `ContentType(pub Mime)`.
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
|
||||
pub use self::accept_charset::AcceptCharset;
|
||||
//pub use self::accept_encoding::AcceptEncoding;
|
||||
pub use self::accept_language::AcceptLanguage;
|
||||
pub use self::accept::Accept;
|
||||
pub use self::allow::Allow;
|
||||
pub use self::cache_control::{CacheControl, CacheDirective};
|
||||
pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam};
|
||||
pub use self::content_language::ContentLanguage;
|
||||
pub use self::content_range::{ContentRange, ContentRangeSpec};
|
||||
pub use self::content_type::ContentType;
|
||||
pub use self::date::Date;
|
||||
pub use self::etag::ETag;
|
||||
pub use self::expires::Expires;
|
||||
pub use self::if_match::IfMatch;
|
||||
pub use self::if_modified_since::IfModifiedSince;
|
||||
pub use self::if_none_match::IfNoneMatch;
|
||||
pub use self::if_range::IfRange;
|
||||
pub use self::if_unmodified_since::IfUnmodifiedSince;
|
||||
pub use self::last_modified::LastModified;
|
||||
//pub use self::range::{Range, ByteRangeSpec};
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __hyper__deref {
|
||||
($from:ty => $to:ty) => {
|
||||
impl ::std::ops::Deref for $from {
|
||||
type Target = $to;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &$to {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::DerefMut for $from {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut $to {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __hyper__tm {
|
||||
($id:ident, $tm:ident{$($tf:item)*}) => {
|
||||
#[allow(unused_imports)]
|
||||
#[cfg(test)]
|
||||
mod $tm{
|
||||
use std::str;
|
||||
use http::Method;
|
||||
use $crate::header::*;
|
||||
use $crate::mime::*;
|
||||
use super::$id as HeaderField;
|
||||
$($tf)*
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! test_header {
|
||||
($id:ident, $raw:expr) => {
|
||||
#[test]
|
||||
fn $id() {
|
||||
use test;
|
||||
let raw = $raw;
|
||||
let a: Vec<Vec<u8>> = raw.iter().map(|x| x.to_vec()).collect();
|
||||
let mut req = test::TestRequest::default();
|
||||
for item in a {
|
||||
req = req.header(HeaderField::name(), item);
|
||||
}
|
||||
let req = req.finish();
|
||||
let value = HeaderField::parse(&req);
|
||||
let result = format!("{}", value.unwrap());
|
||||
let expected = String::from_utf8(raw[0].to_vec()).unwrap();
|
||||
let result_cmp: Vec<String> = result
|
||||
.to_ascii_lowercase()
|
||||
.split(' ')
|
||||
.map(|x| x.to_owned())
|
||||
.collect();
|
||||
let expected_cmp: Vec<String> = expected
|
||||
.to_ascii_lowercase()
|
||||
.split(' ')
|
||||
.map(|x| x.to_owned())
|
||||
.collect();
|
||||
assert_eq!(result_cmp.concat(), expected_cmp.concat());
|
||||
}
|
||||
};
|
||||
($id:ident, $raw:expr, $typed:expr) => {
|
||||
#[test]
|
||||
fn $id() {
|
||||
use $crate::test;
|
||||
let a: Vec<Vec<u8>> = $raw.iter().map(|x| x.to_vec()).collect();
|
||||
let mut req = test::TestRequest::default();
|
||||
for item in a {
|
||||
req = req.header(HeaderField::name(), item);
|
||||
}
|
||||
let req = req.finish();
|
||||
let val = HeaderField::parse(&req);
|
||||
let typed: Option<HeaderField> = $typed;
|
||||
// Test parsing
|
||||
assert_eq!(val.ok(), typed);
|
||||
// Test formatting
|
||||
if typed.is_some() {
|
||||
let raw = &($raw)[..];
|
||||
let mut iter = raw.iter().map(|b|str::from_utf8(&b[..]).unwrap());
|
||||
let mut joined = String::new();
|
||||
joined.push_str(iter.next().unwrap());
|
||||
for s in iter {
|
||||
joined.push_str(", ");
|
||||
joined.push_str(s);
|
||||
}
|
||||
assert_eq!(format!("{}", typed.unwrap()), joined);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! header {
|
||||
// $a:meta: Attributes associated with the header item (usually docs)
|
||||
// $id:ident: Identifier of the header
|
||||
// $n:expr: Lowercase name of the header
|
||||
// $nn:expr: Nice name of the header
|
||||
|
||||
// List header, zero or more items
|
||||
($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)*) => {
|
||||
$(#[$a])*
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct $id(pub Vec<$item>);
|
||||
__hyper__deref!($id => Vec<$item>);
|
||||
impl $crate::header::Header for $id {
|
||||
#[inline]
|
||||
fn name() -> $crate::header::HeaderName {
|
||||
$name
|
||||
}
|
||||
#[inline]
|
||||
fn parse<T>(msg: &T) -> Result<Self, $crate::error::ParseError>
|
||||
where T: $crate::HttpMessage
|
||||
{
|
||||
$crate::header::from_comma_delimited(
|
||||
msg.headers().get_all(Self::name())).map($id)
|
||||
}
|
||||
}
|
||||
impl ::std::fmt::Display for $id {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
$crate::header::fmt_comma_delimited(f, &self.0[..])
|
||||
}
|
||||
}
|
||||
impl $crate::header::IntoHeaderValue for $id {
|
||||
type Error = $crate::header::InvalidHeaderValueBytes;
|
||||
|
||||
fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> {
|
||||
use std::fmt::Write;
|
||||
let mut writer = $crate::header::Writer::new();
|
||||
let _ = write!(&mut writer, "{}", self);
|
||||
$crate::header::HeaderValue::from_shared(writer.take())
|
||||
}
|
||||
}
|
||||
};
|
||||
// List header, one or more items
|
||||
($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)+) => {
|
||||
$(#[$a])*
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct $id(pub Vec<$item>);
|
||||
__hyper__deref!($id => Vec<$item>);
|
||||
impl $crate::header::Header for $id {
|
||||
#[inline]
|
||||
fn name() -> $crate::header::HeaderName {
|
||||
$name
|
||||
}
|
||||
#[inline]
|
||||
fn parse<T>(msg: &T) -> Result<Self, $crate::error::ParseError>
|
||||
where T: $crate::HttpMessage
|
||||
{
|
||||
$crate::header::from_comma_delimited(
|
||||
msg.headers().get_all(Self::name())).map($id)
|
||||
}
|
||||
}
|
||||
impl ::std::fmt::Display for $id {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
$crate::header::fmt_comma_delimited(f, &self.0[..])
|
||||
}
|
||||
}
|
||||
impl $crate::header::IntoHeaderValue for $id {
|
||||
type Error = $crate::header::InvalidHeaderValueBytes;
|
||||
|
||||
fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> {
|
||||
use std::fmt::Write;
|
||||
let mut writer = $crate::header::Writer::new();
|
||||
let _ = write!(&mut writer, "{}", self);
|
||||
$crate::header::HeaderValue::from_shared(writer.take())
|
||||
}
|
||||
}
|
||||
};
|
||||
// Single value header
|
||||
($(#[$a:meta])*($id:ident, $name:expr) => [$value:ty]) => {
|
||||
$(#[$a])*
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct $id(pub $value);
|
||||
__hyper__deref!($id => $value);
|
||||
impl $crate::header::Header for $id {
|
||||
#[inline]
|
||||
fn name() -> $crate::header::HeaderName {
|
||||
$name
|
||||
}
|
||||
#[inline]
|
||||
fn parse<T>(msg: &T) -> Result<Self, $crate::error::ParseError>
|
||||
where T: $crate::HttpMessage
|
||||
{
|
||||
$crate::header::from_one_raw_str(
|
||||
msg.headers().get(Self::name())).map($id)
|
||||
}
|
||||
}
|
||||
impl ::std::fmt::Display for $id {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
::std::fmt::Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
impl $crate::header::IntoHeaderValue for $id {
|
||||
type Error = $crate::header::InvalidHeaderValueBytes;
|
||||
|
||||
fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> {
|
||||
self.0.try_into()
|
||||
}
|
||||
}
|
||||
};
|
||||
// List header, one or more items with "*" option
|
||||
($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => {
|
||||
$(#[$a])*
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum $id {
|
||||
/// Any value is a match
|
||||
Any,
|
||||
/// Only the listed items are a match
|
||||
Items(Vec<$item>),
|
||||
}
|
||||
impl $crate::header::Header for $id {
|
||||
#[inline]
|
||||
fn name() -> $crate::header::HeaderName {
|
||||
$name
|
||||
}
|
||||
#[inline]
|
||||
fn parse<T>(msg: &T) -> Result<Self, $crate::error::ParseError>
|
||||
where T: $crate::HttpMessage
|
||||
{
|
||||
let any = msg.headers().get(Self::name()).and_then(|hdr| {
|
||||
hdr.to_str().ok().and_then(|hdr| Some(hdr.trim() == "*"))});
|
||||
|
||||
if let Some(true) = any {
|
||||
Ok($id::Any)
|
||||
} else {
|
||||
Ok($id::Items(
|
||||
$crate::header::from_comma_delimited(
|
||||
msg.headers().get_all(Self::name()))?))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ::std::fmt::Display for $id {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
match *self {
|
||||
$id::Any => f.write_str("*"),
|
||||
$id::Items(ref fields) => $crate::header::fmt_comma_delimited(
|
||||
f, &fields[..])
|
||||
}
|
||||
}
|
||||
}
|
||||
impl $crate::header::IntoHeaderValue for $id {
|
||||
type Error = $crate::header::InvalidHeaderValueBytes;
|
||||
|
||||
fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> {
|
||||
use std::fmt::Write;
|
||||
let mut writer = $crate::header::Writer::new();
|
||||
let _ = write!(&mut writer, "{}", self);
|
||||
$crate::header::HeaderValue::from_shared(writer.take())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// optional test module
|
||||
($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => {
|
||||
header! {
|
||||
$(#[$a])*
|
||||
($id, $name) => ($item)*
|
||||
}
|
||||
|
||||
__hyper__tm! { $id, $tm { $($tf)* }}
|
||||
};
|
||||
($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => {
|
||||
header! {
|
||||
$(#[$a])*
|
||||
($id, $n) => ($item)+
|
||||
}
|
||||
|
||||
__hyper__tm! { $id, $tm { $($tf)* }}
|
||||
};
|
||||
($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => {
|
||||
header! {
|
||||
$(#[$a])* ($id, $name) => [$item]
|
||||
}
|
||||
|
||||
__hyper__tm! { $id, $tm { $($tf)* }}
|
||||
};
|
||||
($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => {
|
||||
header! {
|
||||
$(#[$a])*
|
||||
($id, $name) => {Any / ($item)+}
|
||||
}
|
||||
|
||||
__hyper__tm! { $id, $tm { $($tf)* }}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
mod accept_charset;
|
||||
//mod accept_encoding;
|
||||
mod accept_language;
|
||||
mod accept;
|
||||
mod allow;
|
||||
mod cache_control;
|
||||
mod content_disposition;
|
||||
mod content_language;
|
||||
mod content_range;
|
||||
mod content_type;
|
||||
mod date;
|
||||
mod etag;
|
||||
mod expires;
|
||||
mod if_match;
|
||||
mod if_modified_since;
|
||||
mod if_none_match;
|
||||
mod if_range;
|
||||
mod if_unmodified_since;
|
||||
mod last_modified;
|
||||
//mod range;
|
@ -1,434 +0,0 @@
|
||||
use std::fmt::{self, Display};
|
||||
use std::str::FromStr;
|
||||
|
||||
use header::parsing::from_one_raw_str;
|
||||
use header::{Header, Raw};
|
||||
|
||||
/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1)
|
||||
///
|
||||
/// The "Range" header field on a GET request modifies the method
|
||||
/// semantics to request transfer of only one or more subranges of the
|
||||
/// selected representation data, rather than the entire selected
|
||||
/// representation data.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// Range = byte-ranges-specifier / other-ranges-specifier
|
||||
/// other-ranges-specifier = other-range-unit "=" other-range-set
|
||||
/// other-range-set = 1*VCHAR
|
||||
///
|
||||
/// bytes-unit = "bytes"
|
||||
///
|
||||
/// byte-ranges-specifier = bytes-unit "=" byte-range-set
|
||||
/// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec)
|
||||
/// byte-range-spec = first-byte-pos "-" [last-byte-pos]
|
||||
/// first-byte-pos = 1*DIGIT
|
||||
/// last-byte-pos = 1*DIGIT
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
///
|
||||
/// * `bytes=1000-`
|
||||
/// * `bytes=-2000`
|
||||
/// * `bytes=0-1,30-40`
|
||||
/// * `bytes=0-10,20-90,-100`
|
||||
/// * `custom_unit=0-123`
|
||||
/// * `custom_unit=xxx-yyy`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hyper::header::{Headers, Range, ByteRangeSpec};
|
||||
///
|
||||
/// let mut headers = Headers::new();
|
||||
/// headers.set(Range::Bytes(
|
||||
/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)]
|
||||
/// ));
|
||||
///
|
||||
/// headers.clear();
|
||||
/// headers.set(Range::Unregistered("letters".to_owned(), "a-f".to_owned()));
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use hyper::header::{Headers, Range};
|
||||
///
|
||||
/// let mut headers = Headers::new();
|
||||
/// headers.set(Range::bytes(1, 100));
|
||||
///
|
||||
/// headers.clear();
|
||||
/// headers.set(Range::bytes_multi(vec![(1, 100), (200, 300)]));
|
||||
/// ```
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum Range {
|
||||
/// Byte range
|
||||
Bytes(Vec<ByteRangeSpec>),
|
||||
/// Custom range, with unit not registered at IANA
|
||||
/// (`other-range-unit`: String , `other-range-set`: String)
|
||||
Unregistered(String, String),
|
||||
}
|
||||
|
||||
/// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`.
|
||||
/// Each `ByteRangeSpec` defines a range of bytes to fetch
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum ByteRangeSpec {
|
||||
/// Get all bytes between x and y ("x-y")
|
||||
FromTo(u64, u64),
|
||||
/// Get all bytes starting from x ("x-")
|
||||
AllFrom(u64),
|
||||
/// Get last x bytes ("-x")
|
||||
Last(u64),
|
||||
}
|
||||
|
||||
impl ByteRangeSpec {
|
||||
/// Given the full length of the entity, attempt to normalize the byte range
|
||||
/// into an satisfiable end-inclusive (from, to) range.
|
||||
///
|
||||
/// The resulting range is guaranteed to be a satisfiable range within the
|
||||
/// bounds of `0 <= from <= to < full_length`.
|
||||
///
|
||||
/// If the byte range is deemed unsatisfiable, `None` is returned.
|
||||
/// An unsatisfiable range is generally cause for a server to either reject
|
||||
/// the client request with a `416 Range Not Satisfiable` status code, or to
|
||||
/// simply ignore the range header and serve the full entity using a `200
|
||||
/// OK` status code.
|
||||
///
|
||||
/// This function closely follows [RFC 7233][1] section 2.1.
|
||||
/// As such, it considers ranges to be satisfiable if they meet the
|
||||
/// following conditions:
|
||||
///
|
||||
/// > If a valid byte-range-set includes at least one byte-range-spec with
|
||||
/// a first-byte-pos that is less than the current length of the
|
||||
/// representation, or at least one suffix-byte-range-spec with a
|
||||
/// non-zero suffix-length, then the byte-range-set is satisfiable.
|
||||
/// Otherwise, the byte-range-set is unsatisfiable.
|
||||
///
|
||||
/// The function also computes remainder ranges based on the RFC:
|
||||
///
|
||||
/// > If the last-byte-pos value is
|
||||
/// absent, or if the value is greater than or equal to the current
|
||||
/// length of the representation data, the byte range is interpreted as
|
||||
/// the remainder of the representation (i.e., the server replaces the
|
||||
/// value of last-byte-pos with a value that is one less than the current
|
||||
/// length of the selected representation).
|
||||
///
|
||||
/// [1]: https://tools.ietf.org/html/rfc7233
|
||||
pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> {
|
||||
// If the full length is zero, there is no satisfiable end-inclusive range.
|
||||
if full_length == 0 {
|
||||
return None;
|
||||
}
|
||||
match self {
|
||||
&ByteRangeSpec::FromTo(from, to) => {
|
||||
if from < full_length && from <= to {
|
||||
Some((from, ::std::cmp::min(to, full_length - 1)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
&ByteRangeSpec::AllFrom(from) => {
|
||||
if from < full_length {
|
||||
Some((from, full_length - 1))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
&ByteRangeSpec::Last(last) => {
|
||||
if last > 0 {
|
||||
// From the RFC: If the selected representation is shorter
|
||||
// than the specified suffix-length,
|
||||
// the entire representation is used.
|
||||
if last > full_length {
|
||||
Some((0, full_length - 1))
|
||||
} else {
|
||||
Some((full_length - last, full_length - 1))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Range {
|
||||
/// Get the most common byte range header ("bytes=from-to")
|
||||
pub fn bytes(from: u64, to: u64) -> Range {
|
||||
Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)])
|
||||
}
|
||||
|
||||
/// Get byte range header with multiple subranges
|
||||
/// ("bytes=from1-to1,from2-to2,fromX-toX")
|
||||
pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range {
|
||||
Range::Bytes(
|
||||
ranges
|
||||
.iter()
|
||||
.map(|r| ByteRangeSpec::FromTo(r.0, r.1))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ByteRangeSpec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to),
|
||||
ByteRangeSpec::Last(pos) => write!(f, "-{}", pos),
|
||||
ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Range {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Range::Bytes(ref ranges) => {
|
||||
try!(write!(f, "bytes="));
|
||||
|
||||
for (i, range) in ranges.iter().enumerate() {
|
||||
if i != 0 {
|
||||
try!(f.write_str(","));
|
||||
}
|
||||
try!(Display::fmt(range, f));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Range::Unregistered(ref unit, ref range_str) => {
|
||||
write!(f, "{}={}", unit, range_str)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Range {
|
||||
type Err = ::Error;
|
||||
|
||||
fn from_str(s: &str) -> ::Result<Range> {
|
||||
let mut iter = s.splitn(2, '=');
|
||||
|
||||
match (iter.next(), iter.next()) {
|
||||
(Some("bytes"), Some(ranges)) => {
|
||||
let ranges = from_comma_delimited(ranges);
|
||||
if ranges.is_empty() {
|
||||
return Err(::Error::Header);
|
||||
}
|
||||
Ok(Range::Bytes(ranges))
|
||||
}
|
||||
(Some(unit), Some(range_str)) if unit != "" && range_str != "" => Ok(
|
||||
Range::Unregistered(unit.to_owned(), range_str.to_owned()),
|
||||
),
|
||||
_ => Err(::Error::Header),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ByteRangeSpec {
|
||||
type Err = ::Error;
|
||||
|
||||
fn from_str(s: &str) -> ::Result<ByteRangeSpec> {
|
||||
let mut parts = s.splitn(2, '-');
|
||||
|
||||
match (parts.next(), parts.next()) {
|
||||
(Some(""), Some(end)) => end.parse()
|
||||
.or(Err(::Error::Header))
|
||||
.map(ByteRangeSpec::Last),
|
||||
(Some(start), Some("")) => start
|
||||
.parse()
|
||||
.or(Err(::Error::Header))
|
||||
.map(ByteRangeSpec::AllFrom),
|
||||
(Some(start), Some(end)) => match (start.parse(), end.parse()) {
|
||||
(Ok(start), Ok(end)) if start <= end => {
|
||||
Ok(ByteRangeSpec::FromTo(start, end))
|
||||
}
|
||||
_ => Err(::Error::Header),
|
||||
},
|
||||
_ => Err(::Error::Header),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn from_comma_delimited<T: FromStr>(s: &str) -> Vec<T> {
|
||||
s.split(',')
|
||||
.filter_map(|x| match x.trim() {
|
||||
"" => None,
|
||||
y => Some(y),
|
||||
})
|
||||
.filter_map(|x| x.parse().ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl Header for Range {
|
||||
fn header_name() -> &'static str {
|
||||
static NAME: &'static str = "Range";
|
||||
NAME
|
||||
}
|
||||
|
||||
fn parse_header(raw: &Raw) -> ::Result<Range> {
|
||||
from_one_raw_str(raw)
|
||||
}
|
||||
|
||||
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
|
||||
f.fmt_line(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_bytes_range_valid() {
|
||||
let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap();
|
||||
let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap();
|
||||
let r3 = Range::bytes(1, 100);
|
||||
assert_eq!(r, r2);
|
||||
assert_eq!(r2, r3);
|
||||
|
||||
let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap();
|
||||
let r2: Range =
|
||||
Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap();
|
||||
let r3 = Range::Bytes(vec![
|
||||
ByteRangeSpec::FromTo(1, 100),
|
||||
ByteRangeSpec::AllFrom(200),
|
||||
]);
|
||||
assert_eq!(r, r2);
|
||||
assert_eq!(r2, r3);
|
||||
|
||||
let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap();
|
||||
let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap();
|
||||
let r3 = Range::Bytes(vec![
|
||||
ByteRangeSpec::FromTo(1, 100),
|
||||
ByteRangeSpec::Last(100),
|
||||
]);
|
||||
assert_eq!(r, r2);
|
||||
assert_eq!(r2, r3);
|
||||
|
||||
let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap();
|
||||
let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned());
|
||||
assert_eq!(r, r2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_unregistered_range_valid() {
|
||||
let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap();
|
||||
let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned());
|
||||
assert_eq!(r, r2);
|
||||
|
||||
let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap();
|
||||
let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned());
|
||||
assert_eq!(r, r2);
|
||||
|
||||
let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap();
|
||||
let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned());
|
||||
assert_eq!(r, r2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_invalid() {
|
||||
let r: ::Result<Range> = Header::parse_header(&"bytes=1-a,-".into());
|
||||
assert_eq!(r.ok(), None);
|
||||
|
||||
let r: ::Result<Range> = Header::parse_header(&"bytes=1-2-3".into());
|
||||
assert_eq!(r.ok(), None);
|
||||
|
||||
let r: ::Result<Range> = Header::parse_header(&"abc".into());
|
||||
assert_eq!(r.ok(), None);
|
||||
|
||||
let r: ::Result<Range> = Header::parse_header(&"bytes=1-100=".into());
|
||||
assert_eq!(r.ok(), None);
|
||||
|
||||
let r: ::Result<Range> = Header::parse_header(&"bytes=".into());
|
||||
assert_eq!(r.ok(), None);
|
||||
|
||||
let r: ::Result<Range> = Header::parse_header(&"custom=".into());
|
||||
assert_eq!(r.ok(), None);
|
||||
|
||||
let r: ::Result<Range> = Header::parse_header(&"=1-100".into());
|
||||
assert_eq!(r.ok(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fmt() {
|
||||
use header::Headers;
|
||||
|
||||
let mut headers = Headers::new();
|
||||
|
||||
headers.set(Range::Bytes(vec![
|
||||
ByteRangeSpec::FromTo(0, 1000),
|
||||
ByteRangeSpec::AllFrom(2000),
|
||||
]));
|
||||
assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n");
|
||||
|
||||
headers.clear();
|
||||
headers.set(Range::Bytes(vec![]));
|
||||
|
||||
assert_eq!(&headers.to_string(), "Range: bytes=\r\n");
|
||||
|
||||
headers.clear();
|
||||
headers.set(Range::Unregistered(
|
||||
"custom".to_owned(),
|
||||
"1-xxx".to_owned(),
|
||||
));
|
||||
|
||||
assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_byte_range_spec_to_satisfiable_range() {
|
||||
assert_eq!(
|
||||
Some((0, 0)),
|
||||
ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3)
|
||||
);
|
||||
assert_eq!(
|
||||
Some((1, 2)),
|
||||
ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3)
|
||||
);
|
||||
assert_eq!(
|
||||
Some((1, 2)),
|
||||
ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3)
|
||||
);
|
||||
assert_eq!(
|
||||
None,
|
||||
ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3)
|
||||
);
|
||||
assert_eq!(
|
||||
None,
|
||||
ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3)
|
||||
);
|
||||
assert_eq!(
|
||||
None,
|
||||
ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Some((0, 2)),
|
||||
ByteRangeSpec::AllFrom(0).to_satisfiable_range(3)
|
||||
);
|
||||
assert_eq!(
|
||||
Some((2, 2)),
|
||||
ByteRangeSpec::AllFrom(2).to_satisfiable_range(3)
|
||||
);
|
||||
assert_eq!(
|
||||
None,
|
||||
ByteRangeSpec::AllFrom(3).to_satisfiable_range(3)
|
||||
);
|
||||
assert_eq!(
|
||||
None,
|
||||
ByteRangeSpec::AllFrom(5).to_satisfiable_range(3)
|
||||
);
|
||||
assert_eq!(
|
||||
None,
|
||||
ByteRangeSpec::AllFrom(0).to_satisfiable_range(0)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Some((1, 2)),
|
||||
ByteRangeSpec::Last(2).to_satisfiable_range(3)
|
||||
);
|
||||
assert_eq!(
|
||||
Some((2, 2)),
|
||||
ByteRangeSpec::Last(1).to_satisfiable_range(3)
|
||||
);
|
||||
assert_eq!(
|
||||
Some((0, 2)),
|
||||
ByteRangeSpec::Last(5).to_satisfiable_range(3)
|
||||
);
|
||||
assert_eq!(None, ByteRangeSpec::Last(0).to_satisfiable_range(3));
|
||||
assert_eq!(None, ByteRangeSpec::Last(2).to_satisfiable_range(0));
|
||||
}
|
@ -1,471 +0,0 @@
|
||||
//! Various http headers
|
||||
// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header)
|
||||
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use mime::Mime;
|
||||
use modhttp::header::GetAll;
|
||||
use modhttp::Error as HttpError;
|
||||
use percent_encoding;
|
||||
|
||||
pub use modhttp::header::*;
|
||||
|
||||
use error::ParseError;
|
||||
use httpmessage::HttpMessage;
|
||||
|
||||
mod common;
|
||||
mod shared;
|
||||
#[doc(hidden)]
|
||||
pub use self::common::*;
|
||||
#[doc(hidden)]
|
||||
pub use self::shared::*;
|
||||
|
||||
#[doc(hidden)]
|
||||
/// A trait for any object that will represent a header field and value.
|
||||
pub trait Header
|
||||
where
|
||||
Self: IntoHeaderValue,
|
||||
{
|
||||
/// Returns the name of the header field
|
||||
fn name() -> HeaderName;
|
||||
|
||||
/// Parse a header
|
||||
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// A trait for any object that can be Converted to a `HeaderValue`
|
||||
pub trait IntoHeaderValue: Sized {
|
||||
/// The type returned in the event of a conversion error.
|
||||
type Error: Into<HttpError>;
|
||||
|
||||
/// Try to convert value to a Header value.
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error>;
|
||||
}
|
||||
|
||||
impl IntoHeaderValue for HeaderValue {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoHeaderValue for &'a str {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
self.parse()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoHeaderValue for &'a [u8] {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
HeaderValue::from_bytes(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoHeaderValue for Bytes {
|
||||
type Error = InvalidHeaderValueBytes;
|
||||
|
||||
#[inline]
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
HeaderValue::from_shared(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoHeaderValue for Vec<u8> {
|
||||
type Error = InvalidHeaderValueBytes;
|
||||
|
||||
#[inline]
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
HeaderValue::from_shared(Bytes::from(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoHeaderValue for String {
|
||||
type Error = InvalidHeaderValueBytes;
|
||||
|
||||
#[inline]
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
HeaderValue::from_shared(Bytes::from(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoHeaderValue for Mime {
|
||||
type Error = InvalidHeaderValueBytes;
|
||||
|
||||
#[inline]
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
HeaderValue::from_shared(Bytes::from(format!("{}", self)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents supported types of content encodings
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum ContentEncoding {
|
||||
/// Automatically select encoding based on encoding negotiation
|
||||
Auto,
|
||||
/// A format using the Brotli algorithm
|
||||
#[cfg(feature = "brotli")]
|
||||
Br,
|
||||
/// A format using the zlib structure with deflate algorithm
|
||||
#[cfg(feature = "flate2")]
|
||||
Deflate,
|
||||
/// Gzip algorithm
|
||||
#[cfg(feature = "flate2")]
|
||||
Gzip,
|
||||
/// Indicates the identity function (i.e. no compression, nor modification)
|
||||
Identity,
|
||||
}
|
||||
|
||||
impl ContentEncoding {
|
||||
#[inline]
|
||||
/// Is the content compressed?
|
||||
pub fn is_compression(self) -> bool {
|
||||
match self {
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Convert content encoding to string
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => "br",
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => "gzip",
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => "deflate",
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// default quality value
|
||||
pub fn quality(self) -> f64 {
|
||||
match self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => 1.1,
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => 1.0,
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => 0.9,
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => 0.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove memory allocation
|
||||
impl<'a> From<&'a str> for ContentEncoding {
|
||||
fn from(s: &'a str) -> ContentEncoding {
|
||||
match AsRef::<str>::as_ref(&s.trim().to_lowercase()) {
|
||||
#[cfg(feature = "brotli")]
|
||||
"br" => ContentEncoding::Br,
|
||||
#[cfg(feature = "flate2")]
|
||||
"gzip" => ContentEncoding::Gzip,
|
||||
#[cfg(feature = "flate2")]
|
||||
"deflate" => ContentEncoding::Deflate,
|
||||
_ => ContentEncoding::Identity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(crate) struct Writer {
|
||||
buf: BytesMut,
|
||||
}
|
||||
|
||||
impl Writer {
|
||||
fn new() -> Writer {
|
||||
Writer {
|
||||
buf: BytesMut::new(),
|
||||
}
|
||||
}
|
||||
fn take(&mut self) -> Bytes {
|
||||
self.buf.take().freeze()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Write for Writer {
|
||||
#[inline]
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
self.buf.extend_from_slice(s.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
|
||||
fmt::write(self, args)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
/// Reads a comma-delimited raw header into a Vec.
|
||||
pub fn from_comma_delimited<T: FromStr>(
|
||||
all: GetAll<HeaderValue>,
|
||||
) -> Result<Vec<T>, ParseError> {
|
||||
let mut result = Vec::new();
|
||||
for h in all {
|
||||
let s = h.to_str().map_err(|_| ParseError::Header)?;
|
||||
result.extend(
|
||||
s.split(',')
|
||||
.filter_map(|x| match x.trim() {
|
||||
"" => None,
|
||||
y => Some(y),
|
||||
}).filter_map(|x| x.trim().parse().ok()),
|
||||
)
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
/// Reads a single string when parsing a header.
|
||||
pub fn from_one_raw_str<T: FromStr>(val: Option<&HeaderValue>) -> Result<T, ParseError> {
|
||||
if let Some(line) = val {
|
||||
let line = line.to_str().map_err(|_| ParseError::Header)?;
|
||||
if !line.is_empty() {
|
||||
return T::from_str(line).or(Err(ParseError::Header));
|
||||
}
|
||||
}
|
||||
Err(ParseError::Header)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
/// Format an array into a comma-delimited string.
|
||||
pub fn fmt_comma_delimited<T>(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result
|
||||
where
|
||||
T: fmt::Display,
|
||||
{
|
||||
let mut iter = parts.iter();
|
||||
if let Some(part) = iter.next() {
|
||||
fmt::Display::fmt(part, f)?;
|
||||
}
|
||||
for part in iter {
|
||||
f.write_str(", ")?;
|
||||
fmt::Display::fmt(part, f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// From hyper v0.11.27 src/header/parsing.rs
|
||||
|
||||
/// The value part of an extended parameter consisting of three parts:
|
||||
/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`),
|
||||
/// and a character sequence representing the actual value (`value`), separated by single quote
|
||||
/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ExtendedValue {
|
||||
/// The character set that is used to encode the `value` to a string.
|
||||
pub charset: Charset,
|
||||
/// The human language details of the `value`, if available.
|
||||
pub language_tag: Option<LanguageTag>,
|
||||
/// The parameter value, as expressed in octets.
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Parses extended header parameter values (`ext-value`), as defined in
|
||||
/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
|
||||
///
|
||||
/// Extended values are denoted by parameter names that end with `*`.
|
||||
///
|
||||
/// ## ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// ext-value = charset "'" [ language ] "'" value-chars
|
||||
/// ; like RFC 2231's <extended-initial-value>
|
||||
/// ; (see [RFC2231], Section 7)
|
||||
///
|
||||
/// charset = "UTF-8" / "ISO-8859-1" / mime-charset
|
||||
///
|
||||
/// mime-charset = 1*mime-charsetc
|
||||
/// mime-charsetc = ALPHA / DIGIT
|
||||
/// / "!" / "#" / "$" / "%" / "&"
|
||||
/// / "+" / "-" / "^" / "_" / "`"
|
||||
/// / "{" / "}" / "~"
|
||||
/// ; as <mime-charset> in Section 2.3 of [RFC2978]
|
||||
/// ; except that the single quote is not included
|
||||
/// ; SHOULD be registered in the IANA charset registry
|
||||
///
|
||||
/// language = <Language-Tag, defined in [RFC5646], Section 2.1>
|
||||
///
|
||||
/// value-chars = *( pct-encoded / attr-char )
|
||||
///
|
||||
/// pct-encoded = "%" HEXDIG HEXDIG
|
||||
/// ; see [RFC3986], Section 2.1
|
||||
///
|
||||
/// attr-char = ALPHA / DIGIT
|
||||
/// / "!" / "#" / "$" / "&" / "+" / "-" / "."
|
||||
/// / "^" / "_" / "`" / "|" / "~"
|
||||
/// ; token except ( "*" / "'" / "%" )
|
||||
/// ```
|
||||
pub fn parse_extended_value(val: &str) -> Result<ExtendedValue, ::error::ParseError> {
|
||||
// Break into three pieces separated by the single-quote character
|
||||
let mut parts = val.splitn(3, '\'');
|
||||
|
||||
// Interpret the first piece as a Charset
|
||||
let charset: Charset = match parts.next() {
|
||||
None => return Err(::error::ParseError::Header),
|
||||
Some(n) => FromStr::from_str(n).map_err(|_| ::error::ParseError::Header)?,
|
||||
};
|
||||
|
||||
// Interpret the second piece as a language tag
|
||||
let language_tag: Option<LanguageTag> = match parts.next() {
|
||||
None => return Err(::error::ParseError::Header),
|
||||
Some("") => None,
|
||||
Some(s) => match s.parse() {
|
||||
Ok(lt) => Some(lt),
|
||||
Err(_) => return Err(::error::ParseError::Header),
|
||||
},
|
||||
};
|
||||
|
||||
// Interpret the third piece as a sequence of value characters
|
||||
let value: Vec<u8> = match parts.next() {
|
||||
None => return Err(::error::ParseError::Header),
|
||||
Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(),
|
||||
};
|
||||
|
||||
Ok(ExtendedValue {
|
||||
value,
|
||||
charset,
|
||||
language_tag,
|
||||
})
|
||||
}
|
||||
|
||||
impl fmt::Display for ExtendedValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let encoded_value = percent_encoding::percent_encode(
|
||||
&self.value[..],
|
||||
self::percent_encoding_http::HTTP_VALUE,
|
||||
);
|
||||
if let Some(ref lang) = self.language_tag {
|
||||
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
|
||||
} else {
|
||||
write!(f, "{}''{}", self.charset, encoded_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Percent encode a sequence of bytes with a character set defined in
|
||||
/// [https://tools.ietf.org/html/rfc5987#section-3.2][url]
|
||||
///
|
||||
/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2
|
||||
pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result {
|
||||
let encoded =
|
||||
percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE);
|
||||
fmt::Display::fmt(&encoded, f)
|
||||
}
|
||||
mod percent_encoding_http {
|
||||
use percent_encoding;
|
||||
|
||||
// internal module because macro is hard-coded to make a public item
|
||||
// but we don't want to public export this item
|
||||
define_encode_set! {
|
||||
// This encode set is used for HTTP header values and is defined at
|
||||
// https://tools.ietf.org/html/rfc5987#section-3.2
|
||||
pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | {
|
||||
' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?',
|
||||
'[', '\\', ']', '{', '}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{parse_extended_value, ExtendedValue};
|
||||
use header::shared::Charset;
|
||||
use language_tags::LanguageTag;
|
||||
|
||||
#[test]
|
||||
fn test_parse_extended_value_with_encoding_and_language_tag() {
|
||||
let expected_language_tag = "en".parse::<LanguageTag>().unwrap();
|
||||
// RFC 5987, Section 3.2.2
|
||||
// Extended notation, using the Unicode character U+00A3 (POUND SIGN)
|
||||
let result = parse_extended_value("iso-8859-1'en'%A3%20rates");
|
||||
assert!(result.is_ok());
|
||||
let extended_value = result.unwrap();
|
||||
assert_eq!(Charset::Iso_8859_1, extended_value.charset);
|
||||
assert!(extended_value.language_tag.is_some());
|
||||
assert_eq!(expected_language_tag, extended_value.language_tag.unwrap());
|
||||
assert_eq!(
|
||||
vec![163, b' ', b'r', b'a', b't', b'e', b's'],
|
||||
extended_value.value
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_extended_value_with_encoding() {
|
||||
// RFC 5987, Section 3.2.2
|
||||
// Extended notation, using the Unicode characters U+00A3 (POUND SIGN)
|
||||
// and U+20AC (EURO SIGN)
|
||||
let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
|
||||
assert!(result.is_ok());
|
||||
let extended_value = result.unwrap();
|
||||
assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset);
|
||||
assert!(extended_value.language_tag.is_none());
|
||||
assert_eq!(
|
||||
vec![
|
||||
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
||||
b't', b'e', b's',
|
||||
],
|
||||
extended_value.value
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_extended_value_missing_language_tag_and_encoding() {
|
||||
// From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2
|
||||
let result = parse_extended_value("foo%20bar.html");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_extended_value_partially_formatted() {
|
||||
let result = parse_extended_value("UTF-8'missing third part");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_extended_value_partially_formatted_blank() {
|
||||
let result = parse_extended_value("blank second part'");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fmt_extended_value_with_encoding_and_language_tag() {
|
||||
let extended_value = ExtendedValue {
|
||||
charset: Charset::Iso_8859_1,
|
||||
language_tag: Some("en".parse().expect("Could not parse language tag")),
|
||||
value: vec![163, b' ', b'r', b'a', b't', b'e', b's'],
|
||||
};
|
||||
assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fmt_extended_value_with_encoding() {
|
||||
let extended_value = ExtendedValue {
|
||||
charset: Charset::Ext("UTF-8".to_string()),
|
||||
language_tag: None,
|
||||
value: vec![
|
||||
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
||||
b't', b'e', b's',
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
"UTF-8''%C2%A3%20and%20%E2%82%AC%20rates",
|
||||
format!("{}", extended_value)
|
||||
);
|
||||
}
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
use std::fmt::{self, Display};
|
||||
use std::str::FromStr;
|
||||
|
||||
use self::Charset::*;
|
||||
|
||||
/// A Mime charset.
|
||||
///
|
||||
/// The string representation is normalized to upper case.
|
||||
///
|
||||
/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url].
|
||||
///
|
||||
/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum Charset {
|
||||
/// US ASCII
|
||||
Us_Ascii,
|
||||
/// ISO-8859-1
|
||||
Iso_8859_1,
|
||||
/// ISO-8859-2
|
||||
Iso_8859_2,
|
||||
/// ISO-8859-3
|
||||
Iso_8859_3,
|
||||
/// ISO-8859-4
|
||||
Iso_8859_4,
|
||||
/// ISO-8859-5
|
||||
Iso_8859_5,
|
||||
/// ISO-8859-6
|
||||
Iso_8859_6,
|
||||
/// ISO-8859-7
|
||||
Iso_8859_7,
|
||||
/// ISO-8859-8
|
||||
Iso_8859_8,
|
||||
/// ISO-8859-9
|
||||
Iso_8859_9,
|
||||
/// ISO-8859-10
|
||||
Iso_8859_10,
|
||||
/// Shift_JIS
|
||||
Shift_Jis,
|
||||
/// EUC-JP
|
||||
Euc_Jp,
|
||||
/// ISO-2022-KR
|
||||
Iso_2022_Kr,
|
||||
/// EUC-KR
|
||||
Euc_Kr,
|
||||
/// ISO-2022-JP
|
||||
Iso_2022_Jp,
|
||||
/// ISO-2022-JP-2
|
||||
Iso_2022_Jp_2,
|
||||
/// ISO-8859-6-E
|
||||
Iso_8859_6_E,
|
||||
/// ISO-8859-6-I
|
||||
Iso_8859_6_I,
|
||||
/// ISO-8859-8-E
|
||||
Iso_8859_8_E,
|
||||
/// ISO-8859-8-I
|
||||
Iso_8859_8_I,
|
||||
/// GB2312
|
||||
Gb2312,
|
||||
/// Big5
|
||||
Big5,
|
||||
/// KOI8-R
|
||||
Koi8_R,
|
||||
/// An arbitrary charset specified as a string
|
||||
Ext(String),
|
||||
}
|
||||
|
||||
impl Charset {
|
||||
fn label(&self) -> &str {
|
||||
match *self {
|
||||
Us_Ascii => "US-ASCII",
|
||||
Iso_8859_1 => "ISO-8859-1",
|
||||
Iso_8859_2 => "ISO-8859-2",
|
||||
Iso_8859_3 => "ISO-8859-3",
|
||||
Iso_8859_4 => "ISO-8859-4",
|
||||
Iso_8859_5 => "ISO-8859-5",
|
||||
Iso_8859_6 => "ISO-8859-6",
|
||||
Iso_8859_7 => "ISO-8859-7",
|
||||
Iso_8859_8 => "ISO-8859-8",
|
||||
Iso_8859_9 => "ISO-8859-9",
|
||||
Iso_8859_10 => "ISO-8859-10",
|
||||
Shift_Jis => "Shift-JIS",
|
||||
Euc_Jp => "EUC-JP",
|
||||
Iso_2022_Kr => "ISO-2022-KR",
|
||||
Euc_Kr => "EUC-KR",
|
||||
Iso_2022_Jp => "ISO-2022-JP",
|
||||
Iso_2022_Jp_2 => "ISO-2022-JP-2",
|
||||
Iso_8859_6_E => "ISO-8859-6-E",
|
||||
Iso_8859_6_I => "ISO-8859-6-I",
|
||||
Iso_8859_8_E => "ISO-8859-8-E",
|
||||
Iso_8859_8_I => "ISO-8859-8-I",
|
||||
Gb2312 => "GB2312",
|
||||
Big5 => "big5",
|
||||
Koi8_R => "KOI8-R",
|
||||
Ext(ref s) => s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Charset {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(self.label())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Charset {
|
||||
type Err = ::Error;
|
||||
fn from_str(s: &str) -> ::Result<Charset> {
|
||||
Ok(match s.to_ascii_uppercase().as_ref() {
|
||||
"US-ASCII" => Us_Ascii,
|
||||
"ISO-8859-1" => Iso_8859_1,
|
||||
"ISO-8859-2" => Iso_8859_2,
|
||||
"ISO-8859-3" => Iso_8859_3,
|
||||
"ISO-8859-4" => Iso_8859_4,
|
||||
"ISO-8859-5" => Iso_8859_5,
|
||||
"ISO-8859-6" => Iso_8859_6,
|
||||
"ISO-8859-7" => Iso_8859_7,
|
||||
"ISO-8859-8" => Iso_8859_8,
|
||||
"ISO-8859-9" => Iso_8859_9,
|
||||
"ISO-8859-10" => Iso_8859_10,
|
||||
"SHIFT-JIS" => Shift_Jis,
|
||||
"EUC-JP" => Euc_Jp,
|
||||
"ISO-2022-KR" => Iso_2022_Kr,
|
||||
"EUC-KR" => Euc_Kr,
|
||||
"ISO-2022-JP" => Iso_2022_Jp,
|
||||
"ISO-2022-JP-2" => Iso_2022_Jp_2,
|
||||
"ISO-8859-6-E" => Iso_8859_6_E,
|
||||
"ISO-8859-6-I" => Iso_8859_6_I,
|
||||
"ISO-8859-8-E" => Iso_8859_8_E,
|
||||
"ISO-8859-8-I" => Iso_8859_8_I,
|
||||
"GB2312" => Gb2312,
|
||||
"big5" => Big5,
|
||||
"KOI8-R" => Koi8_R,
|
||||
s => Ext(s.to_owned()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
assert_eq!(Us_Ascii, "us-ascii".parse().unwrap());
|
||||
assert_eq!(Us_Ascii, "US-Ascii".parse().unwrap());
|
||||
assert_eq!(Us_Ascii, "US-ASCII".parse().unwrap());
|
||||
assert_eq!(Shift_Jis, "Shift-JIS".parse().unwrap());
|
||||
assert_eq!(Ext("ABCD".to_owned()), "abcd".parse().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
assert_eq!("US-ASCII", format!("{}", Us_Ascii));
|
||||
assert_eq!("ABCD", format!("{}", Ext("ABCD".to_owned())));
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::str;
|
||||
|
||||
pub use self::Encoding::{
|
||||
Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers,
|
||||
};
|
||||
|
||||
/// A value to represent an encoding used in `Transfer-Encoding`
|
||||
/// or `Accept-Encoding` header.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum Encoding {
|
||||
/// The `chunked` encoding.
|
||||
Chunked,
|
||||
/// The `br` encoding.
|
||||
Brotli,
|
||||
/// The `gzip` encoding.
|
||||
Gzip,
|
||||
/// The `deflate` encoding.
|
||||
Deflate,
|
||||
/// The `compress` encoding.
|
||||
Compress,
|
||||
/// The `identity` encoding.
|
||||
Identity,
|
||||
/// The `trailers` encoding.
|
||||
Trailers,
|
||||
/// Some other encoding that is less common, can be any String.
|
||||
EncodingExt(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for Encoding {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(match *self {
|
||||
Chunked => "chunked",
|
||||
Brotli => "br",
|
||||
Gzip => "gzip",
|
||||
Deflate => "deflate",
|
||||
Compress => "compress",
|
||||
Identity => "identity",
|
||||
Trailers => "trailers",
|
||||
EncodingExt(ref s) => s.as_ref(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for Encoding {
|
||||
type Err = ::error::ParseError;
|
||||
fn from_str(s: &str) -> Result<Encoding, ::error::ParseError> {
|
||||
match s {
|
||||
"chunked" => Ok(Chunked),
|
||||
"br" => Ok(Brotli),
|
||||
"deflate" => Ok(Deflate),
|
||||
"gzip" => Ok(Gzip),
|
||||
"compress" => Ok(Compress),
|
||||
"identity" => Ok(Identity),
|
||||
"trailers" => Ok(Trailers),
|
||||
_ => Ok(EncodingExt(s.to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,266 +0,0 @@
|
||||
use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer};
|
||||
use std::fmt::{self, Display, Write};
|
||||
use std::str::FromStr;
|
||||
|
||||
/// check that each char in the slice is either:
|
||||
/// 1. `%x21`, or
|
||||
/// 2. in the range `%x23` to `%x7E`, or
|
||||
/// 3. above `%x80`
|
||||
fn check_slice_validity(slice: &str) -> bool {
|
||||
slice
|
||||
.bytes()
|
||||
.all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80'))
|
||||
}
|
||||
|
||||
/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3)
|
||||
///
|
||||
/// An entity tag consists of a string enclosed by two literal double quotes.
|
||||
/// Preceding the first double quote is an optional weakness indicator,
|
||||
/// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and
|
||||
/// `W/"xyzzy"`.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// entity-tag = [ weak ] opaque-tag
|
||||
/// weak = %x57.2F ; "W/", case-sensitive
|
||||
/// opaque-tag = DQUOTE *etagc DQUOTE
|
||||
/// etagc = %x21 / %x23-7E / obs-text
|
||||
/// ; VCHAR except double quotes, plus obs-text
|
||||
/// ```
|
||||
///
|
||||
/// # Comparison
|
||||
/// To check if two entity tags are equivalent in an application always use the
|
||||
/// `strong_eq` or `weak_eq` methods based on the context of the Tag. Only use
|
||||
/// `==` to check if two tags are identical.
|
||||
///
|
||||
/// The example below shows the results for a set of entity-tag pairs and
|
||||
/// both the weak and strong comparison function results:
|
||||
///
|
||||
/// | `ETag 1`| `ETag 2`| Strong Comparison | Weak Comparison |
|
||||
/// |---------|---------|-------------------|-----------------|
|
||||
/// | `W/"1"` | `W/"1"` | no match | match |
|
||||
/// | `W/"1"` | `W/"2"` | no match | no match |
|
||||
/// | `W/"1"` | `"1"` | no match | match |
|
||||
/// | `"1"` | `"1"` | match | match |
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct EntityTag {
|
||||
/// Weakness indicator for the tag
|
||||
pub weak: bool,
|
||||
/// The opaque string in between the DQUOTEs
|
||||
tag: String,
|
||||
}
|
||||
|
||||
impl EntityTag {
|
||||
/// Constructs a new EntityTag.
|
||||
/// # Panics
|
||||
/// If the tag contains invalid characters.
|
||||
pub fn new(weak: bool, tag: String) -> EntityTag {
|
||||
assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag);
|
||||
EntityTag { weak, tag }
|
||||
}
|
||||
|
||||
/// Constructs a new weak EntityTag.
|
||||
/// # Panics
|
||||
/// If the tag contains invalid characters.
|
||||
pub fn weak(tag: String) -> EntityTag {
|
||||
EntityTag::new(true, tag)
|
||||
}
|
||||
|
||||
/// Constructs a new strong EntityTag.
|
||||
/// # Panics
|
||||
/// If the tag contains invalid characters.
|
||||
pub fn strong(tag: String) -> EntityTag {
|
||||
EntityTag::new(false, tag)
|
||||
}
|
||||
|
||||
/// Get the tag.
|
||||
pub fn tag(&self) -> &str {
|
||||
self.tag.as_ref()
|
||||
}
|
||||
|
||||
/// Set the tag.
|
||||
/// # Panics
|
||||
/// If the tag contains invalid characters.
|
||||
pub fn set_tag(&mut self, tag: String) {
|
||||
assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag);
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
/// For strong comparison two entity-tags are equivalent if both are not
|
||||
/// weak and their opaque-tags match character-by-character.
|
||||
pub fn strong_eq(&self, other: &EntityTag) -> bool {
|
||||
!self.weak && !other.weak && self.tag == other.tag
|
||||
}
|
||||
|
||||
/// For weak comparison two entity-tags are equivalent if their
|
||||
/// opaque-tags match character-by-character, regardless of either or
|
||||
/// both being tagged as "weak".
|
||||
pub fn weak_eq(&self, other: &EntityTag) -> bool {
|
||||
self.tag == other.tag
|
||||
}
|
||||
|
||||
/// The inverse of `EntityTag.strong_eq()`.
|
||||
pub fn strong_ne(&self, other: &EntityTag) -> bool {
|
||||
!self.strong_eq(other)
|
||||
}
|
||||
|
||||
/// The inverse of `EntityTag.weak_eq()`.
|
||||
pub fn weak_ne(&self, other: &EntityTag) -> bool {
|
||||
!self.weak_eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EntityTag {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.weak {
|
||||
write!(f, "W/\"{}\"", self.tag)
|
||||
} else {
|
||||
write!(f, "\"{}\"", self.tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for EntityTag {
|
||||
type Err = ::error::ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<EntityTag, ::error::ParseError> {
|
||||
let length: usize = s.len();
|
||||
let slice = &s[..];
|
||||
// Early exits if it doesn't terminate in a DQUOTE.
|
||||
if !slice.ends_with('"') || slice.len() < 2 {
|
||||
return Err(::error::ParseError::Header);
|
||||
}
|
||||
// The etag is weak if its first char is not a DQUOTE.
|
||||
if slice.len() >= 2
|
||||
&& slice.starts_with('"')
|
||||
&& check_slice_validity(&slice[1..length - 1])
|
||||
{
|
||||
// No need to check if the last char is a DQUOTE,
|
||||
// we already did that above.
|
||||
return Ok(EntityTag {
|
||||
weak: false,
|
||||
tag: slice[1..length - 1].to_owned(),
|
||||
});
|
||||
} else if slice.len() >= 4
|
||||
&& slice.starts_with("W/\"")
|
||||
&& check_slice_validity(&slice[3..length - 1])
|
||||
{
|
||||
return Ok(EntityTag {
|
||||
weak: true,
|
||||
tag: slice[3..length - 1].to_owned(),
|
||||
});
|
||||
}
|
||||
Err(::error::ParseError::Header)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoHeaderValue for EntityTag {
|
||||
type Error = InvalidHeaderValueBytes;
|
||||
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
let mut wrt = Writer::new();
|
||||
write!(wrt, "{}", self).unwrap();
|
||||
HeaderValue::from_shared(wrt.take())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EntityTag;
|
||||
|
||||
#[test]
|
||||
fn test_etag_parse_success() {
|
||||
// Expected success
|
||||
assert_eq!(
|
||||
"\"foobar\"".parse::<EntityTag>().unwrap(),
|
||||
EntityTag::strong("foobar".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
"\"\"".parse::<EntityTag>().unwrap(),
|
||||
EntityTag::strong("".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
"W/\"weaktag\"".parse::<EntityTag>().unwrap(),
|
||||
EntityTag::weak("weaktag".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
"W/\"\x65\x62\"".parse::<EntityTag>().unwrap(),
|
||||
EntityTag::weak("\x65\x62".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
"W/\"\"".parse::<EntityTag>().unwrap(),
|
||||
EntityTag::weak("".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_etag_parse_failures() {
|
||||
// Expected failures
|
||||
assert!("no-dquotes".parse::<EntityTag>().is_err());
|
||||
assert!(
|
||||
"w/\"the-first-w-is-case-sensitive\""
|
||||
.parse::<EntityTag>()
|
||||
.is_err()
|
||||
);
|
||||
assert!("".parse::<EntityTag>().is_err());
|
||||
assert!("\"unmatched-dquotes1".parse::<EntityTag>().is_err());
|
||||
assert!("unmatched-dquotes2\"".parse::<EntityTag>().is_err());
|
||||
assert!("matched-\"dquotes\"".parse::<EntityTag>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_etag_fmt() {
|
||||
assert_eq!(
|
||||
format!("{}", EntityTag::strong("foobar".to_owned())),
|
||||
"\"foobar\""
|
||||
);
|
||||
assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\"");
|
||||
assert_eq!(
|
||||
format!("{}", EntityTag::weak("weak-etag".to_owned())),
|
||||
"W/\"weak-etag\""
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", EntityTag::weak("\u{0065}".to_owned())),
|
||||
"W/\"\x65\""
|
||||
);
|
||||
assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmp() {
|
||||
// | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison |
|
||||
// |---------|---------|-------------------|-----------------|
|
||||
// | `W/"1"` | `W/"1"` | no match | match |
|
||||
// | `W/"1"` | `W/"2"` | no match | no match |
|
||||
// | `W/"1"` | `"1"` | no match | match |
|
||||
// | `"1"` | `"1"` | match | match |
|
||||
let mut etag1 = EntityTag::weak("1".to_owned());
|
||||
let mut etag2 = EntityTag::weak("1".to_owned());
|
||||
assert!(!etag1.strong_eq(&etag2));
|
||||
assert!(etag1.weak_eq(&etag2));
|
||||
assert!(etag1.strong_ne(&etag2));
|
||||
assert!(!etag1.weak_ne(&etag2));
|
||||
|
||||
etag1 = EntityTag::weak("1".to_owned());
|
||||
etag2 = EntityTag::weak("2".to_owned());
|
||||
assert!(!etag1.strong_eq(&etag2));
|
||||
assert!(!etag1.weak_eq(&etag2));
|
||||
assert!(etag1.strong_ne(&etag2));
|
||||
assert!(etag1.weak_ne(&etag2));
|
||||
|
||||
etag1 = EntityTag::weak("1".to_owned());
|
||||
etag2 = EntityTag::strong("1".to_owned());
|
||||
assert!(!etag1.strong_eq(&etag2));
|
||||
assert!(etag1.weak_eq(&etag2));
|
||||
assert!(etag1.strong_ne(&etag2));
|
||||
assert!(!etag1.weak_ne(&etag2));
|
||||
|
||||
etag1 = EntityTag::strong("1".to_owned());
|
||||
etag2 = EntityTag::strong("1".to_owned());
|
||||
assert!(etag1.strong_eq(&etag2));
|
||||
assert!(etag1.weak_eq(&etag2));
|
||||
assert!(!etag1.strong_ne(&etag2));
|
||||
assert!(!etag1.weak_ne(&etag2));
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
use std::fmt::{self, Display};
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use http::header::{HeaderValue, InvalidHeaderValueBytes};
|
||||
use time;
|
||||
|
||||
use error::ParseError;
|
||||
use header::IntoHeaderValue;
|
||||
|
||||
/// A timestamp with HTTP formatting and parsing
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct HttpDate(time::Tm);
|
||||
|
||||
impl FromStr for HttpDate {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<HttpDate, ParseError> {
|
||||
match time::strptime(s, "%a, %d %b %Y %T %Z")
|
||||
.or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z"))
|
||||
.or_else(|_| time::strptime(s, "%c"))
|
||||
{
|
||||
Ok(t) => Ok(HttpDate(t)),
|
||||
Err(_) => Err(ParseError::Header),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for HttpDate {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.0.to_utc().rfc822(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<time::Tm> for HttpDate {
|
||||
fn from(tm: time::Tm) -> HttpDate {
|
||||
HttpDate(tm)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SystemTime> for HttpDate {
|
||||
fn from(sys: SystemTime) -> HttpDate {
|
||||
let tmspec = match sys.duration_since(UNIX_EPOCH) {
|
||||
Ok(dur) => {
|
||||
time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32)
|
||||
}
|
||||
Err(err) => {
|
||||
let neg = err.duration();
|
||||
time::Timespec::new(
|
||||
-(neg.as_secs() as i64),
|
||||
-(neg.subsec_nanos() as i32),
|
||||
)
|
||||
}
|
||||
};
|
||||
HttpDate(time::at_utc(tmspec))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoHeaderValue for HttpDate {
|
||||
type Error = InvalidHeaderValueBytes;
|
||||
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
let mut wrt = BytesMut::with_capacity(29).writer();
|
||||
write!(wrt, "{}", self.0.rfc822()).unwrap();
|
||||
HeaderValue::from_shared(wrt.get_mut().take().freeze())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpDate> for SystemTime {
|
||||
fn from(date: HttpDate) -> SystemTime {
|
||||
let spec = date.0.to_timespec();
|
||||
if spec.sec >= 0 {
|
||||
UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32)
|
||||
} else {
|
||||
UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::HttpDate;
|
||||
use time::Tm;
|
||||
|
||||
const NOV_07: HttpDate = HttpDate(Tm {
|
||||
tm_nsec: 0,
|
||||
tm_sec: 37,
|
||||
tm_min: 48,
|
||||
tm_hour: 8,
|
||||
tm_mday: 7,
|
||||
tm_mon: 10,
|
||||
tm_year: 94,
|
||||
tm_wday: 0,
|
||||
tm_isdst: 0,
|
||||
tm_yday: 0,
|
||||
tm_utcoff: 0,
|
||||
});
|
||||
|
||||
#[test]
|
||||
fn test_date() {
|
||||
assert_eq!(
|
||||
"Sun, 07 Nov 1994 08:48:37 GMT".parse::<HttpDate>().unwrap(),
|
||||
NOV_07
|
||||
);
|
||||
assert_eq!(
|
||||
"Sunday, 07-Nov-94 08:48:37 GMT"
|
||||
.parse::<HttpDate>()
|
||||
.unwrap(),
|
||||
NOV_07
|
||||
);
|
||||
assert_eq!(
|
||||
"Sun Nov 7 08:48:37 1994".parse::<HttpDate>().unwrap(),
|
||||
NOV_07
|
||||
);
|
||||
assert!("this-is-no-date".parse::<HttpDate>().is_err());
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
//! Copied for `hyper::header::shared`;
|
||||
|
||||
pub use self::charset::Charset;
|
||||
pub use self::encoding::Encoding;
|
||||
pub use self::entity::EntityTag;
|
||||
pub use self::httpdate::HttpDate;
|
||||
pub use self::quality_item::{q, qitem, Quality, QualityItem};
|
||||
pub use language_tags::LanguageTag;
|
||||
|
||||
mod charset;
|
||||
mod encoding;
|
||||
mod entity;
|
||||
mod httpdate;
|
||||
mod quality_item;
|
@ -1,294 +0,0 @@
|
||||
use std::cmp;
|
||||
use std::default::Default;
|
||||
use std::fmt;
|
||||
use std::str;
|
||||
|
||||
use self::internal::IntoQuality;
|
||||
|
||||
/// Represents a quality used in quality values.
|
||||
///
|
||||
/// Can be created with the `q` function.
|
||||
///
|
||||
/// # Implementation notes
|
||||
///
|
||||
/// The quality value is defined as a number between 0 and 1 with three decimal
|
||||
/// places. This means there are 1001 possible values. Since floating point
|
||||
/// numbers are not exact and the smallest floating point data type (`f32`)
|
||||
/// consumes four bytes, hyper uses an `u16` value to store the
|
||||
/// quality internally. For performance reasons you may set quality directly to
|
||||
/// a value between 0 and 1000 e.g. `Quality(532)` matches the quality
|
||||
/// `q=0.532`.
|
||||
///
|
||||
/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1)
|
||||
/// gives more information on quality values in HTTP header fields.
|
||||
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct Quality(u16);
|
||||
|
||||
impl Default for Quality {
|
||||
fn default() -> Quality {
|
||||
Quality(1000)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an item with a quality value as defined in
|
||||
/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1).
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct QualityItem<T> {
|
||||
/// The actual contents of the field.
|
||||
pub item: T,
|
||||
/// The quality (client or server preference) for the value.
|
||||
pub quality: Quality,
|
||||
}
|
||||
|
||||
impl<T> QualityItem<T> {
|
||||
/// Creates a new `QualityItem` from an item and a quality.
|
||||
/// The item can be of any type.
|
||||
/// The quality should be a value in the range [0, 1].
|
||||
pub fn new(item: T, quality: Quality) -> QualityItem<T> {
|
||||
QualityItem { item, quality }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> cmp::PartialOrd for QualityItem<T> {
|
||||
fn partial_cmp(&self, other: &QualityItem<T>) -> Option<cmp::Ordering> {
|
||||
self.quality.partial_cmp(&other.quality)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Display> fmt::Display for QualityItem<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.item, f)?;
|
||||
match self.quality.0 {
|
||||
1000 => Ok(()),
|
||||
0 => f.write_str("; q=0"),
|
||||
x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: str::FromStr> str::FromStr for QualityItem<T> {
|
||||
type Err = ::error::ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<QualityItem<T>, ::error::ParseError> {
|
||||
if !s.is_ascii() {
|
||||
return Err(::error::ParseError::Header);
|
||||
}
|
||||
// Set defaults used if parsing fails.
|
||||
let mut raw_item = s;
|
||||
let mut quality = 1f32;
|
||||
|
||||
let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect();
|
||||
if parts.len() == 2 {
|
||||
if parts[0].len() < 2 {
|
||||
return Err(::error::ParseError::Header);
|
||||
}
|
||||
let start = &parts[0][0..2];
|
||||
if start == "q=" || start == "Q=" {
|
||||
let q_part = &parts[0][2..parts[0].len()];
|
||||
if q_part.len() > 5 {
|
||||
return Err(::error::ParseError::Header);
|
||||
}
|
||||
match q_part.parse::<f32>() {
|
||||
Ok(q_value) => {
|
||||
if 0f32 <= q_value && q_value <= 1f32 {
|
||||
quality = q_value;
|
||||
raw_item = parts[1];
|
||||
} else {
|
||||
return Err(::error::ParseError::Header);
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(::error::ParseError::Header),
|
||||
}
|
||||
}
|
||||
}
|
||||
match raw_item.parse::<T>() {
|
||||
// we already checked above that the quality is within range
|
||||
Ok(item) => Ok(QualityItem::new(item, from_f32(quality))),
|
||||
Err(_) => Err(::error::ParseError::Header),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_f32(f: f32) -> Quality {
|
||||
// this function is only used internally. A check that `f` is within range
|
||||
// should be done before calling this method. Just in case, this
|
||||
// debug_assert should catch if we were forgetful
|
||||
debug_assert!(
|
||||
f >= 0f32 && f <= 1f32,
|
||||
"q value must be between 0.0 and 1.0"
|
||||
);
|
||||
Quality((f * 1000f32) as u16)
|
||||
}
|
||||
|
||||
/// Convenience function to wrap a value in a `QualityItem`
|
||||
/// Sets `q` to the default 1.0
|
||||
pub fn qitem<T>(item: T) -> QualityItem<T> {
|
||||
QualityItem::new(item, Default::default())
|
||||
}
|
||||
|
||||
/// Convenience function to create a `Quality` from a float or integer.
|
||||
///
|
||||
/// Implemented for `u16` and `f32`. Panics if value is out of range.
|
||||
pub fn q<T: IntoQuality>(val: T) -> Quality {
|
||||
val.into_quality()
|
||||
}
|
||||
|
||||
mod internal {
|
||||
use super::Quality;
|
||||
|
||||
// TryFrom is probably better, but it's not stable. For now, we want to
|
||||
// keep the functionality of the `q` function, while allowing it to be
|
||||
// generic over `f32` and `u16`.
|
||||
//
|
||||
// `q` would panic before, so keep that behavior. `TryFrom` can be
|
||||
// introduced later for a non-panicking conversion.
|
||||
|
||||
pub trait IntoQuality: Sealed + Sized {
|
||||
fn into_quality(self) -> Quality;
|
||||
}
|
||||
|
||||
impl IntoQuality for f32 {
|
||||
fn into_quality(self) -> Quality {
|
||||
assert!(
|
||||
self >= 0f32 && self <= 1f32,
|
||||
"float must be between 0.0 and 1.0"
|
||||
);
|
||||
super::from_f32(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoQuality for u16 {
|
||||
fn into_quality(self) -> Quality {
|
||||
assert!(self <= 1000, "u16 must be between 0 and 1000");
|
||||
Quality(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Sealed {}
|
||||
impl Sealed for u16 {}
|
||||
impl Sealed for f32 {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::encoding::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_quality_item_fmt_q_1() {
|
||||
let x = qitem(Chunked);
|
||||
assert_eq!(format!("{}", x), "chunked");
|
||||
}
|
||||
#[test]
|
||||
fn test_quality_item_fmt_q_0001() {
|
||||
let x = QualityItem::new(Chunked, Quality(1));
|
||||
assert_eq!(format!("{}", x), "chunked; q=0.001");
|
||||
}
|
||||
#[test]
|
||||
fn test_quality_item_fmt_q_05() {
|
||||
// Custom value
|
||||
let x = QualityItem {
|
||||
item: EncodingExt("identity".to_owned()),
|
||||
quality: Quality(500),
|
||||
};
|
||||
assert_eq!(format!("{}", x), "identity; q=0.5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quality_item_fmt_q_0() {
|
||||
// Custom value
|
||||
let x = QualityItem {
|
||||
item: EncodingExt("identity".to_owned()),
|
||||
quality: Quality(0),
|
||||
};
|
||||
assert_eq!(x.to_string(), "identity; q=0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quality_item_from_str1() {
|
||||
let x: Result<QualityItem<Encoding>, _> = "chunked".parse();
|
||||
assert_eq!(
|
||||
x.unwrap(),
|
||||
QualityItem {
|
||||
item: Chunked,
|
||||
quality: Quality(1000),
|
||||
}
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_quality_item_from_str2() {
|
||||
let x: Result<QualityItem<Encoding>, _> = "chunked; q=1".parse();
|
||||
assert_eq!(
|
||||
x.unwrap(),
|
||||
QualityItem {
|
||||
item: Chunked,
|
||||
quality: Quality(1000),
|
||||
}
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_quality_item_from_str3() {
|
||||
let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.5".parse();
|
||||
assert_eq!(
|
||||
x.unwrap(),
|
||||
QualityItem {
|
||||
item: Gzip,
|
||||
quality: Quality(500),
|
||||
}
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_quality_item_from_str4() {
|
||||
let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.273".parse();
|
||||
assert_eq!(
|
||||
x.unwrap(),
|
||||
QualityItem {
|
||||
item: Gzip,
|
||||
quality: Quality(273),
|
||||
}
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_quality_item_from_str5() {
|
||||
let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.2739999".parse();
|
||||
assert!(x.is_err());
|
||||
}
|
||||
#[test]
|
||||
fn test_quality_item_from_str6() {
|
||||
let x: Result<QualityItem<Encoding>, _> = "gzip; q=2".parse();
|
||||
assert!(x.is_err());
|
||||
}
|
||||
#[test]
|
||||
fn test_quality_item_ordering() {
|
||||
let x: QualityItem<Encoding> = "gzip; q=0.5".parse().ok().unwrap();
|
||||
let y: QualityItem<Encoding> = "gzip; q=0.273".parse().ok().unwrap();
|
||||
let comparision_result: bool = x.gt(&y);
|
||||
assert!(comparision_result)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quality() {
|
||||
assert_eq!(q(0.5), Quality(500));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic] // FIXME - 32-bit msvc unwinding broken
|
||||
#[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
|
||||
fn test_quality_invalid() {
|
||||
q(-1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic] // FIXME - 32-bit msvc unwinding broken
|
||||
#[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
|
||||
fn test_quality_invalid2() {
|
||||
q(2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzing_bugs() {
|
||||
assert!("99999;".parse::<QualityItem<String>>().is_err());
|
||||
assert!("\x0d;;;=\u{d6aa}==".parse::<QualityItem<String>>().is_err())
|
||||
}
|
||||
}
|
709
src/helpers.rs
709
src/helpers.rs
@ -1,571 +1,180 @@
|
||||
//! Various helpers
|
||||
use actix_http::Response;
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::{ok, FutureResult};
|
||||
use futures::{Future, Poll};
|
||||
|
||||
use http::{header, StatusCode};
|
||||
use regex::Regex;
|
||||
pub(crate) type BoxedHttpService<Req, Res> = Box<
|
||||
Service<
|
||||
Request = Req,
|
||||
Response = Res,
|
||||
Error = (),
|
||||
Future = Box<Future<Item = Res, Error = ()>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
use handler::Handler;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
pub(crate) type BoxedHttpNewService<Req, Res> = Box<
|
||||
NewService<
|
||||
Request = Req,
|
||||
Response = Res,
|
||||
Error = (),
|
||||
InitError = (),
|
||||
Service = BoxedHttpService<Req, Res>,
|
||||
Future = Box<Future<Item = BoxedHttpService<Req, Res>, Error = ()>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
/// 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,
|
||||
}
|
||||
pub(crate) struct HttpNewService<T: NewService>(T);
|
||||
|
||||
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<T> HttpNewService<T>
|
||||
where
|
||||
T: NewService,
|
||||
T::Response: 'static,
|
||||
T::Future: 'static,
|
||||
T::Service: Service,
|
||||
<T::Service as Service>::Future: 'static,
|
||||
{
|
||||
pub fn new(service: T) -> Self {
|
||||
HttpNewService(service)
|
||||
}
|
||||
}
|
||||
|
||||
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<T> NewService for HttpNewService<T>
|
||||
where
|
||||
T: NewService,
|
||||
T::Request: 'static,
|
||||
T::Response: 'static,
|
||||
T::Future: 'static,
|
||||
T::Service: Service + 'static,
|
||||
<T::Service as Service>::Future: 'static,
|
||||
{
|
||||
type Request = T::Request;
|
||||
type Response = T::Response;
|
||||
type Error = ();
|
||||
type InitError = ();
|
||||
type Service = BoxedHttpService<T::Request, T::Response>;
|
||||
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| {
|
||||
let service: BoxedHttpService<_, _> =
|
||||
Box::new(HttpServiceWrapper { service });
|
||||
Ok(service)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Handler<S> for NormalizePath {
|
||||
type Result = HttpResponse;
|
||||
struct HttpServiceWrapper<T: Service> {
|
||||
service: T,
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
impl<T> Service for HttpServiceWrapper<T>
|
||||
where
|
||||
T: Service,
|
||||
T::Request: 'static,
|
||||
T::Response: 'static,
|
||||
T::Future: 'static,
|
||||
{
|
||||
type Request = T::Request;
|
||||
type Response = T::Response;
|
||||
type Error = ();
|
||||
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
|
||||
|
||||
// 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)
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready().map_err(|_| ())
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||
Box::new(self.service.call(req).map_err(|_| ()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use application::App;
|
||||
use http::{header, Method};
|
||||
use test::TestRequest;
|
||||
pub(crate) fn not_found<Req>(_: Req) -> FutureResult<Response, ()> {
|
||||
ok(Response::NotFound().finish())
|
||||
}
|
||||
|
||||
fn index(_req: &HttpRequest) -> HttpResponse {
|
||||
HttpResponse::new(StatusCode::OK)
|
||||
}
|
||||
pub(crate) type HttpDefaultService<Req, Res> = Box<
|
||||
Service<
|
||||
Request = Req,
|
||||
Response = Res,
|
||||
Error = (),
|
||||
Future = Box<Future<Item = Res, Error = ()>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
#[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();
|
||||
pub(crate) type HttpDefaultNewService<Req, Res> = Box<
|
||||
NewService<
|
||||
Request = Req,
|
||||
Response = Res,
|
||||
Error = (),
|
||||
InitError = (),
|
||||
Service = HttpDefaultService<Req, Res>,
|
||||
Future = Box<Future<Item = HttpDefaultService<Req, Res>, Error = ()>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
// 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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) struct DefaultNewService<T: NewService> {
|
||||
service: T,
|
||||
}
|
||||
|
||||
#[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()
|
||||
);
|
||||
}
|
||||
}
|
||||
impl<T> DefaultNewService<T>
|
||||
where
|
||||
T: NewService + 'static,
|
||||
T::Future: 'static,
|
||||
<T::Service as Service>::Future: 'static,
|
||||
{
|
||||
pub fn new(service: T) -> Self {
|
||||
DefaultNewService { service }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> NewService for DefaultNewService<T>
|
||||
where
|
||||
T: NewService + 'static,
|
||||
T::Request: 'static,
|
||||
T::Future: 'static,
|
||||
T::Service: 'static,
|
||||
<T::Service as Service>::Future: 'static,
|
||||
{
|
||||
type Request = T::Request;
|
||||
type Response = T::Response;
|
||||
type Error = ();
|
||||
type InitError = ();
|
||||
type Service = HttpDefaultService<T::Request, T::Response>;
|
||||
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
Box::new(
|
||||
self.service
|
||||
.new_service(&())
|
||||
.map_err(|_| ())
|
||||
.and_then(|service| {
|
||||
let service: HttpDefaultService<_, _> =
|
||||
Box::new(DefaultServiceWrapper { service });
|
||||
Ok(service)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct DefaultServiceWrapper<T: Service> {
|
||||
service: T,
|
||||
}
|
||||
|
||||
impl<T> Service for DefaultServiceWrapper<T>
|
||||
where
|
||||
T: Service + 'static,
|
||||
T::Future: 'static,
|
||||
{
|
||||
type Request = T::Request;
|
||||
type Response = T::Response;
|
||||
type Error = ();
|
||||
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready().map_err(|_| ())
|
||||
}
|
||||
|
||||
fn call(&mut self, req: T::Request) -> Self::Future {
|
||||
Box::new(self.service.call(req).map_err(|_| ()))
|
||||
}
|
||||
}
|
||||
|
@ -1,84 +0,0 @@
|
||||
//! Basic http responses
|
||||
#![allow(non_upper_case_globals)]
|
||||
use http::StatusCode;
|
||||
use httpresponse::{HttpResponse, HttpResponseBuilder};
|
||||
|
||||
macro_rules! STATIC_RESP {
|
||||
($name:ident, $status:expr) => {
|
||||
#[allow(non_snake_case, missing_docs)]
|
||||
pub fn $name() -> HttpResponseBuilder {
|
||||
HttpResponse::build($status)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl HttpResponse {
|
||||
STATIC_RESP!(Ok, StatusCode::OK);
|
||||
STATIC_RESP!(Created, StatusCode::CREATED);
|
||||
STATIC_RESP!(Accepted, StatusCode::ACCEPTED);
|
||||
STATIC_RESP!(
|
||||
NonAuthoritativeInformation,
|
||||
StatusCode::NON_AUTHORITATIVE_INFORMATION
|
||||
);
|
||||
|
||||
STATIC_RESP!(NoContent, StatusCode::NO_CONTENT);
|
||||
STATIC_RESP!(ResetContent, StatusCode::RESET_CONTENT);
|
||||
STATIC_RESP!(PartialContent, StatusCode::PARTIAL_CONTENT);
|
||||
STATIC_RESP!(MultiStatus, StatusCode::MULTI_STATUS);
|
||||
STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED);
|
||||
|
||||
STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES);
|
||||
STATIC_RESP!(MovedPermanenty, StatusCode::MOVED_PERMANENTLY);
|
||||
STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY);
|
||||
STATIC_RESP!(Found, StatusCode::FOUND);
|
||||
STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER);
|
||||
STATIC_RESP!(NotModified, StatusCode::NOT_MODIFIED);
|
||||
STATIC_RESP!(UseProxy, StatusCode::USE_PROXY);
|
||||
STATIC_RESP!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT);
|
||||
STATIC_RESP!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT);
|
||||
|
||||
STATIC_RESP!(BadRequest, StatusCode::BAD_REQUEST);
|
||||
STATIC_RESP!(NotFound, StatusCode::NOT_FOUND);
|
||||
STATIC_RESP!(Unauthorized, StatusCode::UNAUTHORIZED);
|
||||
STATIC_RESP!(PaymentRequired, StatusCode::PAYMENT_REQUIRED);
|
||||
STATIC_RESP!(Forbidden, StatusCode::FORBIDDEN);
|
||||
STATIC_RESP!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED);
|
||||
STATIC_RESP!(NotAcceptable, StatusCode::NOT_ACCEPTABLE);
|
||||
STATIC_RESP!(
|
||||
ProxyAuthenticationRequired,
|
||||
StatusCode::PROXY_AUTHENTICATION_REQUIRED
|
||||
);
|
||||
STATIC_RESP!(RequestTimeout, StatusCode::REQUEST_TIMEOUT);
|
||||
STATIC_RESP!(Conflict, StatusCode::CONFLICT);
|
||||
STATIC_RESP!(Gone, StatusCode::GONE);
|
||||
STATIC_RESP!(LengthRequired, StatusCode::LENGTH_REQUIRED);
|
||||
STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED);
|
||||
STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE);
|
||||
STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG);
|
||||
STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
|
||||
STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
|
||||
STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
|
||||
|
||||
STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR);
|
||||
STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED);
|
||||
STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY);
|
||||
STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE);
|
||||
STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT);
|
||||
STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED);
|
||||
STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES);
|
||||
STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE);
|
||||
STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use body::Body;
|
||||
use http::StatusCode;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
#[test]
|
||||
fn test_build() {
|
||||
let resp = HttpResponse::Ok().body(Body::Empty);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
}
|
@ -1,855 +0,0 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use encoding::all::UTF_8;
|
||||
use encoding::label::encoding_from_whatwg_label;
|
||||
use encoding::types::{DecoderTrap, Encoding};
|
||||
use encoding::EncodingRef;
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use http::{header, HeaderMap};
|
||||
use mime::Mime;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_urlencoded;
|
||||
use std::str;
|
||||
|
||||
use error::{
|
||||
ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError,
|
||||
};
|
||||
use header::Header;
|
||||
use json::JsonBody;
|
||||
use multipart::Multipart;
|
||||
|
||||
/// Trait that implements general purpose operations on http messages
|
||||
pub trait HttpMessage: Sized {
|
||||
/// Type of message payload stream
|
||||
type Stream: Stream<Item = Bytes, Error = PayloadError> + Sized;
|
||||
|
||||
/// Read the message headers.
|
||||
fn headers(&self) -> &HeaderMap;
|
||||
|
||||
/// Message payload stream
|
||||
fn payload(&self) -> Self::Stream;
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Get a header
|
||||
fn get_header<H: Header>(&self) -> Option<H>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if self.headers().contains_key(H::name()) {
|
||||
H::parse(self).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the request content type. If request does not contain
|
||||
/// *Content-Type* header, empty str get returned.
|
||||
fn content_type(&self) -> &str {
|
||||
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
|
||||
if let Ok(content_type) = content_type.to_str() {
|
||||
return content_type.split(';').next().unwrap().trim();
|
||||
}
|
||||
}
|
||||
""
|
||||
}
|
||||
|
||||
/// Get content type encoding
|
||||
///
|
||||
/// UTF-8 is used by default, If request charset is not set.
|
||||
fn encoding(&self) -> Result<EncodingRef, ContentTypeError> {
|
||||
if let Some(mime_type) = self.mime_type()? {
|
||||
if let Some(charset) = mime_type.get_param("charset") {
|
||||
if let Some(enc) = encoding_from_whatwg_label(charset.as_str()) {
|
||||
Ok(enc)
|
||||
} else {
|
||||
Err(ContentTypeError::UnknownEncoding)
|
||||
}
|
||||
} else {
|
||||
Ok(UTF_8)
|
||||
}
|
||||
} else {
|
||||
Ok(UTF_8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the request content type to a known mime type.
|
||||
fn mime_type(&self) -> Result<Option<Mime>, ContentTypeError> {
|
||||
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
|
||||
if let Ok(content_type) = content_type.to_str() {
|
||||
return match content_type.parse() {
|
||||
Ok(mt) => Ok(Some(mt)),
|
||||
Err(_) => Err(ContentTypeError::ParseError),
|
||||
};
|
||||
} else {
|
||||
return Err(ContentTypeError::ParseError);
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Check if request has chunked transfer encoding
|
||||
fn chunked(&self) -> Result<bool, ParseError> {
|
||||
if let Some(encodings) = self.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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Load http message body.
|
||||
///
|
||||
/// By default only 256Kb payload reads to a memory, then
|
||||
/// `PayloadError::Overflow` get returned. Use `MessageBody::limit()`
|
||||
/// method to change upper limit.
|
||||
///
|
||||
/// ## Server example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{
|
||||
/// AsyncResponder, FutureResponse, HttpMessage, HttpRequest, HttpResponse,
|
||||
/// };
|
||||
/// use bytes::Bytes;
|
||||
/// use futures::future::Future;
|
||||
///
|
||||
/// fn index(mut req: HttpRequest) -> FutureResponse<HttpResponse> {
|
||||
/// req.body() // <- get Body future
|
||||
/// .limit(1024) // <- change max size of the body to a 1kb
|
||||
/// .from_err()
|
||||
/// .and_then(|bytes: Bytes| { // <- complete body
|
||||
/// println!("==== BODY ==== {:?}", bytes);
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// }).responder()
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
fn body(&self) -> MessageBody<Self> {
|
||||
MessageBody::new(self)
|
||||
}
|
||||
|
||||
/// Parse `application/x-www-form-urlencoded` encoded request's body.
|
||||
/// Return `UrlEncoded` future. Form can be deserialized to any type that
|
||||
/// implements `Deserialize` trait from *serde*.
|
||||
///
|
||||
/// Returns error:
|
||||
///
|
||||
/// * content type is not `application/x-www-form-urlencoded`
|
||||
/// * content-length is greater than 256k
|
||||
///
|
||||
/// ## Server example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// # use futures::Future;
|
||||
/// # use std::collections::HashMap;
|
||||
/// use actix_web::{FutureResponse, HttpMessage, HttpRequest, HttpResponse};
|
||||
///
|
||||
/// fn index(mut req: HttpRequest) -> FutureResponse<HttpResponse> {
|
||||
/// Box::new(
|
||||
/// req.urlencoded::<HashMap<String, String>>() // <- get UrlEncoded future
|
||||
/// .from_err()
|
||||
/// .and_then(|params| { // <- url encoded parameters
|
||||
/// println!("==== BODY ==== {:?}", params);
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// }),
|
||||
/// )
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
fn urlencoded<T: DeserializeOwned>(&self) -> UrlEncoded<Self, T> {
|
||||
UrlEncoded::new(self)
|
||||
}
|
||||
|
||||
/// Parse `application/json` encoded body.
|
||||
/// Return `JsonBody<T>` future. It resolves to a `T` value.
|
||||
///
|
||||
/// Returns error:
|
||||
///
|
||||
/// * content type is not `application/json`
|
||||
/// * content length is greater than 256k
|
||||
///
|
||||
/// ## Server example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::*;
|
||||
/// use futures::future::{ok, 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
|
||||
/// println!("==== BODY ==== {:?}", val);
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// }).responder()
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
fn json<T: DeserializeOwned>(&self) -> JsonBody<Self, T> {
|
||||
JsonBody::new::<()>(self, None)
|
||||
}
|
||||
|
||||
/// 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;
|
||||
/// # extern crate actix;
|
||||
/// # use std::str;
|
||||
/// # use actix_web::*;
|
||||
/// # use actix::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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stream to read request line by line.
|
||||
pub struct Readlines<T: HttpMessage> {
|
||||
stream: T::Stream,
|
||||
buff: BytesMut,
|
||||
limit: usize,
|
||||
checked_buff: bool,
|
||||
encoding: EncodingRef,
|
||||
err: Option<ReadlinesError>,
|
||||
}
|
||||
|
||||
impl<T: HttpMessage> Readlines<T> {
|
||||
/// Create a new stream to read request line by line.
|
||||
fn new(req: &T) -> Self {
|
||||
let encoding = match req.encoding() {
|
||||
Ok(enc) => enc,
|
||||
Err(err) => return Self::err(req, err.into()),
|
||||
};
|
||||
|
||||
Readlines {
|
||||
stream: req.payload(),
|
||||
buff: BytesMut::with_capacity(262_144),
|
||||
limit: 262_144,
|
||||
checked_buff: true,
|
||||
err: None,
|
||||
encoding,
|
||||
}
|
||||
}
|
||||
|
||||
/// Change max line size. By default max size is 256Kb
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
self.limit = limit;
|
||||
self
|
||||
}
|
||||
|
||||
fn err(req: &T, err: ReadlinesError) -> Self {
|
||||
Readlines {
|
||||
stream: req.payload(),
|
||||
buff: BytesMut::new(),
|
||||
limit: 262_144,
|
||||
checked_buff: true,
|
||||
encoding: UTF_8,
|
||||
err: Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HttpMessage + 'static> Stream for Readlines<T> {
|
||||
type Item = String;
|
||||
type Error = ReadlinesError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if let Some(err) = self.err.take() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// check if there is a newline in the buffer
|
||||
if !self.checked_buff {
|
||||
let mut found: Option<usize> = None;
|
||||
for (ind, b) in self.buff.iter().enumerate() {
|
||||
if *b == b'\n' {
|
||||
found = Some(ind);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(ind) = found {
|
||||
// check if line is longer than limit
|
||||
if ind + 1 > self.limit {
|
||||
return Err(ReadlinesError::LimitOverflow);
|
||||
}
|
||||
let enc: *const Encoding = self.encoding as *const Encoding;
|
||||
let line = if enc == UTF_8 {
|
||||
str::from_utf8(&self.buff.split_to(ind + 1))
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
.to_owned()
|
||||
} else {
|
||||
self.encoding
|
||||
.decode(&self.buff.split_to(ind + 1), DecoderTrap::Strict)
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
};
|
||||
return Ok(Async::Ready(Some(line)));
|
||||
}
|
||||
self.checked_buff = true;
|
||||
}
|
||||
// poll req for more bytes
|
||||
match self.stream.poll() {
|
||||
Ok(Async::Ready(Some(mut bytes))) => {
|
||||
// check if there is a newline in bytes
|
||||
let mut found: Option<usize> = None;
|
||||
for (ind, b) in bytes.iter().enumerate() {
|
||||
if *b == b'\n' {
|
||||
found = Some(ind);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(ind) = found {
|
||||
// check if line is longer than limit
|
||||
if ind + 1 > self.limit {
|
||||
return Err(ReadlinesError::LimitOverflow);
|
||||
}
|
||||
let enc: *const Encoding = self.encoding as *const Encoding;
|
||||
let line = if enc == UTF_8 {
|
||||
str::from_utf8(&bytes.split_to(ind + 1))
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
.to_owned()
|
||||
} else {
|
||||
self.encoding
|
||||
.decode(&bytes.split_to(ind + 1), DecoderTrap::Strict)
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
};
|
||||
// extend buffer with rest of the bytes;
|
||||
self.buff.extend_from_slice(&bytes);
|
||||
self.checked_buff = false;
|
||||
return Ok(Async::Ready(Some(line)));
|
||||
}
|
||||
self.buff.extend_from_slice(&bytes);
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Ok(Async::Ready(None)) => {
|
||||
if self.buff.is_empty() {
|
||||
return Ok(Async::Ready(None));
|
||||
}
|
||||
if self.buff.len() > self.limit {
|
||||
return Err(ReadlinesError::LimitOverflow);
|
||||
}
|
||||
let enc: *const Encoding = self.encoding as *const Encoding;
|
||||
let line = if enc == UTF_8 {
|
||||
str::from_utf8(&self.buff)
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
.to_owned()
|
||||
} else {
|
||||
self.encoding
|
||||
.decode(&self.buff, DecoderTrap::Strict)
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
};
|
||||
self.buff.clear();
|
||||
Ok(Async::Ready(Some(line)))
|
||||
}
|
||||
Err(e) => Err(ReadlinesError::from(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Future that resolves to a complete http message body.
|
||||
pub struct MessageBody<T: HttpMessage> {
|
||||
limit: usize,
|
||||
length: Option<usize>,
|
||||
stream: Option<T::Stream>,
|
||||
err: Option<PayloadError>,
|
||||
fut: Option<Box<Future<Item = Bytes, Error = PayloadError>>>,
|
||||
}
|
||||
|
||||
impl<T: HttpMessage> MessageBody<T> {
|
||||
/// Create `MessageBody` for request.
|
||||
pub fn new(req: &T) -> MessageBody<T> {
|
||||
let mut len = None;
|
||||
if let Some(l) = req.headers().get(header::CONTENT_LENGTH) {
|
||||
if let Ok(s) = l.to_str() {
|
||||
if let Ok(l) = s.parse::<usize>() {
|
||||
len = Some(l)
|
||||
} else {
|
||||
return Self::err(PayloadError::UnknownLength);
|
||||
}
|
||||
} else {
|
||||
return Self::err(PayloadError::UnknownLength);
|
||||
}
|
||||
}
|
||||
|
||||
MessageBody {
|
||||
limit: 262_144,
|
||||
length: len,
|
||||
stream: Some(req.payload()),
|
||||
fut: None,
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Change max size of payload. By default max size is 256Kb
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
self.limit = limit;
|
||||
self
|
||||
}
|
||||
|
||||
fn err(e: PayloadError) -> Self {
|
||||
MessageBody {
|
||||
stream: None,
|
||||
limit: 262_144,
|
||||
fut: None,
|
||||
err: Some(e),
|
||||
length: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Future for MessageBody<T>
|
||||
where
|
||||
T: HttpMessage + 'static,
|
||||
{
|
||||
type Item = Bytes;
|
||||
type Error = PayloadError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(ref mut fut) = self.fut {
|
||||
return fut.poll();
|
||||
}
|
||||
|
||||
if let Some(err) = self.err.take() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
if let Some(len) = self.length.take() {
|
||||
if len > self.limit {
|
||||
return Err(PayloadError::Overflow);
|
||||
}
|
||||
}
|
||||
|
||||
// future
|
||||
let limit = self.limit;
|
||||
self.fut = Some(Box::new(
|
||||
self.stream
|
||||
.take()
|
||||
.expect("Can not be used second time")
|
||||
.from_err()
|
||||
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(PayloadError::Overflow)
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok(body)
|
||||
}
|
||||
}).map(|body| body.freeze()),
|
||||
));
|
||||
self.poll()
|
||||
}
|
||||
}
|
||||
|
||||
/// Future that resolves to a parsed urlencoded values.
|
||||
pub struct UrlEncoded<T: HttpMessage, U> {
|
||||
stream: Option<T::Stream>,
|
||||
limit: usize,
|
||||
length: Option<usize>,
|
||||
encoding: EncodingRef,
|
||||
err: Option<UrlencodedError>,
|
||||
fut: Option<Box<Future<Item = U, Error = UrlencodedError>>>,
|
||||
}
|
||||
|
||||
impl<T: HttpMessage, U> UrlEncoded<T, U> {
|
||||
/// Create a new future to URL encode a request
|
||||
pub fn new(req: &T) -> UrlEncoded<T, U> {
|
||||
// check content type
|
||||
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
|
||||
return Self::err(UrlencodedError::ContentType);
|
||||
}
|
||||
let encoding = match req.encoding() {
|
||||
Ok(enc) => enc,
|
||||
Err(_) => return Self::err(UrlencodedError::ContentType),
|
||||
};
|
||||
|
||||
let mut len = None;
|
||||
if let Some(l) = req.headers().get(header::CONTENT_LENGTH) {
|
||||
if let Ok(s) = l.to_str() {
|
||||
if let Ok(l) = s.parse::<usize>() {
|
||||
len = Some(l)
|
||||
} else {
|
||||
return Self::err(UrlencodedError::UnknownLength);
|
||||
}
|
||||
} else {
|
||||
return Self::err(UrlencodedError::UnknownLength);
|
||||
}
|
||||
};
|
||||
|
||||
UrlEncoded {
|
||||
encoding,
|
||||
stream: Some(req.payload()),
|
||||
limit: 262_144,
|
||||
length: len,
|
||||
fut: None,
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn err(e: UrlencodedError) -> Self {
|
||||
UrlEncoded {
|
||||
stream: None,
|
||||
limit: 262_144,
|
||||
fut: None,
|
||||
err: Some(e),
|
||||
length: None,
|
||||
encoding: UTF_8,
|
||||
}
|
||||
}
|
||||
|
||||
/// Change max size of payload. By default max size is 256Kb
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
self.limit = limit;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Future for UrlEncoded<T, U>
|
||||
where
|
||||
T: HttpMessage + 'static,
|
||||
U: DeserializeOwned + 'static,
|
||||
{
|
||||
type Item = U;
|
||||
type Error = UrlencodedError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(ref mut fut) = self.fut {
|
||||
return fut.poll();
|
||||
}
|
||||
|
||||
if let Some(err) = self.err.take() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// payload size
|
||||
let limit = self.limit;
|
||||
if let Some(len) = self.length.take() {
|
||||
if len > limit {
|
||||
return Err(UrlencodedError::Overflow);
|
||||
}
|
||||
}
|
||||
|
||||
// future
|
||||
let encoding = self.encoding;
|
||||
let fut = self
|
||||
.stream
|
||||
.take()
|
||||
.expect("UrlEncoded could not be used second time")
|
||||
.from_err()
|
||||
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(UrlencodedError::Overflow)
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok(body)
|
||||
}
|
||||
}).and_then(move |body| {
|
||||
if (encoding as *const Encoding) == UTF_8 {
|
||||
serde_urlencoded::from_bytes::<U>(&body)
|
||||
.map_err(|_| UrlencodedError::Parse)
|
||||
} else {
|
||||
let body = encoding
|
||||
.decode(&body, DecoderTrap::Strict)
|
||||
.map_err(|_| UrlencodedError::Parse)?;
|
||||
serde_urlencoded::from_str::<U>(&body)
|
||||
.map_err(|_| UrlencodedError::Parse)
|
||||
}
|
||||
});
|
||||
self.fut = Some(Box::new(fut));
|
||||
self.poll()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use encoding::all::ISO_8859_2;
|
||||
use encoding::Encoding;
|
||||
use futures::Async;
|
||||
use mime;
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_content_type() {
|
||||
let req = TestRequest::with_header("content-type", "text/plain").finish();
|
||||
assert_eq!(req.content_type(), "text/plain");
|
||||
let req =
|
||||
TestRequest::with_header("content-type", "application/json; charset=utf=8")
|
||||
.finish();
|
||||
assert_eq!(req.content_type(), "application/json");
|
||||
let req = TestRequest::default().finish();
|
||||
assert_eq!(req.content_type(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mime_type() {
|
||||
let req = TestRequest::with_header("content-type", "application/json").finish();
|
||||
assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON));
|
||||
let req = TestRequest::default().finish();
|
||||
assert_eq!(req.mime_type().unwrap(), None);
|
||||
let req =
|
||||
TestRequest::with_header("content-type", "application/json; charset=utf-8")
|
||||
.finish();
|
||||
let mt = req.mime_type().unwrap().unwrap();
|
||||
assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8));
|
||||
assert_eq!(mt.type_(), mime::APPLICATION);
|
||||
assert_eq!(mt.subtype(), mime::JSON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mime_type_error() {
|
||||
let req = TestRequest::with_header(
|
||||
"content-type",
|
||||
"applicationadfadsfasdflknadsfklnadsfjson",
|
||||
).finish();
|
||||
assert_eq!(Err(ContentTypeError::ParseError), req.mime_type());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encoding() {
|
||||
let req = TestRequest::default().finish();
|
||||
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
|
||||
|
||||
let req = TestRequest::with_header("content-type", "application/json").finish();
|
||||
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
"content-type",
|
||||
"application/json; charset=ISO-8859-2",
|
||||
).finish();
|
||||
assert_eq!(ISO_8859_2.name(), req.encoding().unwrap().name());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encoding_error() {
|
||||
let req = TestRequest::with_header("content-type", "applicatjson").finish();
|
||||
assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
"content-type",
|
||||
"application/json; charset=kkkttktk",
|
||||
).finish();
|
||||
assert_eq!(
|
||||
Some(ContentTypeError::UnknownEncoding),
|
||||
req.encoding().err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunked() {
|
||||
let req = TestRequest::default().finish();
|
||||
assert!(!req.chunked().unwrap());
|
||||
|
||||
let req =
|
||||
TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
|
||||
assert!(req.chunked().unwrap());
|
||||
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::TRANSFER_ENCODING,
|
||||
Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"),
|
||||
).finish();
|
||||
assert!(req.chunked().is_err());
|
||||
}
|
||||
|
||||
impl PartialEq for UrlencodedError {
|
||||
fn eq(&self, other: &UrlencodedError) -> bool {
|
||||
match *self {
|
||||
UrlencodedError::Chunked => match *other {
|
||||
UrlencodedError::Chunked => true,
|
||||
_ => false,
|
||||
},
|
||||
UrlencodedError::Overflow => match *other {
|
||||
UrlencodedError::Overflow => true,
|
||||
_ => false,
|
||||
},
|
||||
UrlencodedError::UnknownLength => match *other {
|
||||
UrlencodedError::UnknownLength => true,
|
||||
_ => false,
|
||||
},
|
||||
UrlencodedError::ContentType => match *other {
|
||||
UrlencodedError::ContentType => true,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq)]
|
||||
struct Info {
|
||||
hello: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_urlencoded_error() {
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
).header(header::CONTENT_LENGTH, "xxxx")
|
||||
.finish();
|
||||
assert_eq!(
|
||||
req.urlencoded::<Info>().poll().err().unwrap(),
|
||||
UrlencodedError::UnknownLength
|
||||
);
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
).header(header::CONTENT_LENGTH, "1000000")
|
||||
.finish();
|
||||
assert_eq!(
|
||||
req.urlencoded::<Info>().poll().err().unwrap(),
|
||||
UrlencodedError::Overflow
|
||||
);
|
||||
|
||||
let req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain")
|
||||
.header(header::CONTENT_LENGTH, "10")
|
||||
.finish();
|
||||
assert_eq!(
|
||||
req.urlencoded::<Info>().poll().err().unwrap(),
|
||||
UrlencodedError::ContentType
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_urlencoded() {
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
).header(header::CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.finish();
|
||||
|
||||
let result = req.urlencoded::<Info>().poll().ok().unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Async::Ready(Info {
|
||||
hello: "world".to_owned()
|
||||
})
|
||||
);
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded; charset=utf-8",
|
||||
).header(header::CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.finish();
|
||||
|
||||
let result = req.urlencoded().poll().ok().unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Async::Ready(Info {
|
||||
hello: "world".to_owned()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_body() {
|
||||
let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish();
|
||||
match req.body().poll().err().unwrap() {
|
||||
PayloadError::UnknownLength => (),
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
|
||||
let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish();
|
||||
match req.body().poll().err().unwrap() {
|
||||
PayloadError::Overflow => (),
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
|
||||
let req = TestRequest::default()
|
||||
.set_payload(Bytes::from_static(b"test"))
|
||||
.finish();
|
||||
match req.body().poll().ok().unwrap() {
|
||||
Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")),
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
|
||||
let req = TestRequest::default()
|
||||
.set_payload(Bytes::from_static(b"11111111111111"))
|
||||
.finish();
|
||||
match req.body().limit(5).poll().err().unwrap() {
|
||||
PayloadError::Overflow => (),
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readlines() {
|
||||
let req = TestRequest::default()
|
||||
.set_payload(Bytes::from_static(
|
||||
b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\
|
||||
industry. Lorem Ipsum has been the industry's standard dummy\n\
|
||||
Contrary to popular belief, Lorem Ipsum is not simply random text.",
|
||||
)).finish();
|
||||
let mut r = Readlines::new(&req);
|
||||
match r.poll().ok().unwrap() {
|
||||
Async::Ready(Some(s)) => assert_eq!(
|
||||
s,
|
||||
"Lorem Ipsum is simply dummy text of the printing and typesetting\n"
|
||||
),
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
match r.poll().ok().unwrap() {
|
||||
Async::Ready(Some(s)) => assert_eq!(
|
||||
s,
|
||||
"industry. Lorem Ipsum has been the industry's standard dummy\n"
|
||||
),
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
match r.poll().ok().unwrap() {
|
||||
Async::Ready(Some(s)) => assert_eq!(
|
||||
s,
|
||||
"Contrary to popular belief, Lorem Ipsum is not simply random text."
|
||||
),
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,545 +0,0 @@
|
||||
//! HTTP Request message related code.
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::{fmt, str};
|
||||
|
||||
use cookie::Cookie;
|
||||
use futures_cpupool::CpuPool;
|
||||
use http::{header, HeaderMap, Method, StatusCode, Uri, Version};
|
||||
use url::{form_urlencoded, Url};
|
||||
|
||||
use body::Body;
|
||||
use error::{CookieParseError, UrlGenerationError};
|
||||
use extensions::Extensions;
|
||||
use handler::FromRequest;
|
||||
use httpmessage::HttpMessage;
|
||||
use httpresponse::{HttpResponse, HttpResponseBuilder};
|
||||
use info::ConnectionInfo;
|
||||
use param::Params;
|
||||
use payload::Payload;
|
||||
use router::ResourceInfo;
|
||||
use server::Request;
|
||||
|
||||
struct Query(HashMap<String, String>);
|
||||
struct Cookies(Vec<Cookie<'static>>);
|
||||
|
||||
/// An HTTP Request
|
||||
pub struct HttpRequest<S = ()> {
|
||||
req: Option<Request>,
|
||||
state: Rc<S>,
|
||||
resource: ResourceInfo,
|
||||
}
|
||||
|
||||
impl<S> HttpMessage for HttpRequest<S> {
|
||||
type Stream = Payload;
|
||||
|
||||
#[inline]
|
||||
fn headers(&self) -> &HeaderMap {
|
||||
self.request().headers()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn payload(&self) -> Payload {
|
||||
if let Some(payload) = self.request().inner.payload.borrow_mut().take() {
|
||||
payload
|
||||
} else {
|
||||
Payload::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Deref for HttpRequest<S> {
|
||||
type Target = Request;
|
||||
|
||||
fn deref(&self) -> &Request {
|
||||
self.request()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> HttpRequest<S> {
|
||||
#[inline]
|
||||
pub(crate) fn new(
|
||||
req: Request, state: Rc<S>, resource: ResourceInfo,
|
||||
) -> HttpRequest<S> {
|
||||
HttpRequest {
|
||||
state,
|
||||
resource,
|
||||
req: Some(req),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Construct new http request with state.
|
||||
pub(crate) fn with_state<NS>(&self, state: Rc<NS>) -> HttpRequest<NS> {
|
||||
HttpRequest {
|
||||
state,
|
||||
req: self.req.as_ref().map(|r| r.clone()),
|
||||
resource: self.resource.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct new http request with empty state.
|
||||
pub fn drop_state(&self) -> HttpRequest {
|
||||
HttpRequest {
|
||||
state: Rc::new(()),
|
||||
req: self.req.as_ref().map(|r| r.clone()),
|
||||
resource: self.resource.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Construct new http request with new RouteInfo.
|
||||
pub(crate) fn with_route_info(&self, mut resource: ResourceInfo) -> HttpRequest<S> {
|
||||
resource.merge(&self.resource);
|
||||
|
||||
HttpRequest {
|
||||
resource,
|
||||
req: self.req.as_ref().map(|r| r.clone()),
|
||||
state: self.state.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared application state
|
||||
#[inline]
|
||||
pub fn state(&self) -> &S {
|
||||
&self.state
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Server request
|
||||
pub fn request(&self) -> &Request {
|
||||
self.req.as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// Request extensions
|
||||
#[inline]
|
||||
pub fn extensions(&self) -> Ref<Extensions> {
|
||||
self.request().extensions()
|
||||
}
|
||||
|
||||
/// Mutable reference to a the request's extensions
|
||||
#[inline]
|
||||
pub fn extensions_mut(&self) -> RefMut<Extensions> {
|
||||
self.request().extensions_mut()
|
||||
}
|
||||
|
||||
/// Default `CpuPool`
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
pub fn cpu_pool(&self) -> &CpuPool {
|
||||
self.request().server_settings().cpu_pool()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Create http response
|
||||
pub fn response(&self, status: StatusCode, body: Body) -> HttpResponse {
|
||||
self.request().server_settings().get_response(status, body)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Create http response builder
|
||||
pub fn build_response(&self, status: StatusCode) -> HttpResponseBuilder {
|
||||
self.request()
|
||||
.server_settings()
|
||||
.get_response_builder(status)
|
||||
}
|
||||
|
||||
/// Read the Request Uri.
|
||||
#[inline]
|
||||
pub fn uri(&self) -> &Uri {
|
||||
self.request().inner.url.uri()
|
||||
}
|
||||
|
||||
/// Read the Request method.
|
||||
#[inline]
|
||||
pub fn method(&self) -> &Method {
|
||||
&self.request().inner.method
|
||||
}
|
||||
|
||||
/// Read the Request Version.
|
||||
#[inline]
|
||||
pub fn version(&self) -> Version {
|
||||
self.request().inner.version
|
||||
}
|
||||
|
||||
/// The target path of this Request.
|
||||
#[inline]
|
||||
pub fn path(&self) -> &str {
|
||||
self.request().inner.url.path()
|
||||
}
|
||||
|
||||
/// Get *ConnectionInfo* for the correct request.
|
||||
#[inline]
|
||||
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
|
||||
self.request().connection_info()
|
||||
}
|
||||
|
||||
/// Generate url for named resource
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # use actix_web::{App, HttpRequest, HttpResponse, http};
|
||||
/// #
|
||||
/// fn index(req: HttpRequest) -> HttpResponse {
|
||||
/// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
|
||||
/// HttpResponse::Ok().into()
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .resource("/test/{one}/{two}/{three}", |r| {
|
||||
/// r.name("foo"); // <- set resource name, then it could be used in `url_for`
|
||||
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
||||
/// })
|
||||
/// .finish();
|
||||
/// }
|
||||
/// ```
|
||||
pub fn url_for<U, I>(
|
||||
&self, name: &str, elements: U,
|
||||
) -> Result<Url, UrlGenerationError>
|
||||
where
|
||||
U: IntoIterator<Item = I>,
|
||||
I: AsRef<str>,
|
||||
{
|
||||
self.resource.url_for(&self, name, elements)
|
||||
}
|
||||
|
||||
/// Generate url for named resource
|
||||
///
|
||||
/// This method is similar to `HttpRequest::url_for()` but it can be used
|
||||
/// for urls that do not contain variable parts.
|
||||
pub fn url_for_static(&self, name: &str) -> Result<Url, UrlGenerationError> {
|
||||
const NO_PARAMS: [&str; 0] = [];
|
||||
self.url_for(name, &NO_PARAMS)
|
||||
}
|
||||
|
||||
/// This method returns reference to current `ResourceInfo` object.
|
||||
#[inline]
|
||||
pub fn resource(&self) -> &ResourceInfo {
|
||||
&self.resource
|
||||
}
|
||||
|
||||
/// Peer socket address
|
||||
///
|
||||
/// Peer address is actual socket address, if proxy is used in front of
|
||||
/// actix http server, then peer address would be address of this proxy.
|
||||
///
|
||||
/// To get client connection information `connection_info()` method should
|
||||
/// be used.
|
||||
#[inline]
|
||||
pub fn peer_addr(&self) -> Option<SocketAddr> {
|
||||
self.request().inner.addr
|
||||
}
|
||||
|
||||
/// url query parameters.
|
||||
pub fn query(&self) -> Ref<HashMap<String, String>> {
|
||||
if self.extensions().get::<Query>().is_none() {
|
||||
let mut query = HashMap::new();
|
||||
for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) {
|
||||
query.insert(key.as_ref().to_string(), val.to_string());
|
||||
}
|
||||
self.extensions_mut().insert(Query(query));
|
||||
}
|
||||
Ref::map(self.extensions(), |ext| &ext.get::<Query>().unwrap().0)
|
||||
}
|
||||
|
||||
/// The query string in the URL.
|
||||
///
|
||||
/// E.g., id=10
|
||||
#[inline]
|
||||
pub fn query_string(&self) -> &str {
|
||||
if let Some(query) = self.uri().query().as_ref() {
|
||||
query
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/// Load request cookies.
|
||||
#[inline]
|
||||
pub fn cookies(&self) -> Result<Ref<Vec<Cookie<'static>>>, CookieParseError> {
|
||||
if self.extensions().get::<Cookies>().is_none() {
|
||||
let mut cookies = Vec::new();
|
||||
for hdr in self.request().inner.headers.get_all(header::COOKIE) {
|
||||
let s =
|
||||
str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
|
||||
for cookie_str in s.split(';').map(|s| s.trim()) {
|
||||
if !cookie_str.is_empty() {
|
||||
cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
self.extensions_mut().insert(Cookies(cookies));
|
||||
}
|
||||
Ok(Ref::map(self.extensions(), |ext| {
|
||||
&ext.get::<Cookies>().unwrap().0
|
||||
}))
|
||||
}
|
||||
|
||||
/// Return request cookie.
|
||||
#[inline]
|
||||
pub fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
|
||||
if let Ok(cookies) = self.cookies() {
|
||||
for cookie in cookies.iter() {
|
||||
if cookie.name() == name {
|
||||
return Some(cookie.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn set_cookies(&mut self, cookies: Option<Vec<Cookie<'static>>>) {
|
||||
if let Some(cookies) = cookies {
|
||||
self.extensions_mut().insert(Cookies(cookies));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the Params object.
|
||||
///
|
||||
/// Params is a container for url parameters.
|
||||
/// A variable segment is specified in the form `{identifier}`,
|
||||
/// where the identifier can be used later in a request handler to
|
||||
/// access the matched value for that segment.
|
||||
#[inline]
|
||||
pub fn match_info(&self) -> &Params {
|
||||
&self.resource.match_info()
|
||||
}
|
||||
|
||||
/// Check if request requires connection upgrade
|
||||
pub(crate) fn upgrade(&self) -> bool {
|
||||
self.request().upgrade()
|
||||
}
|
||||
|
||||
/// Set read buffer capacity
|
||||
///
|
||||
/// Default buffer capacity is 32Kb.
|
||||
pub fn set_read_buffer_capacity(&mut self, cap: usize) {
|
||||
if let Some(payload) = self.request().inner.payload.borrow_mut().as_mut() {
|
||||
payload.set_read_buffer_capacity(cap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Drop for HttpRequest<S> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(req) = self.req.take() {
|
||||
req.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Clone for HttpRequest<S> {
|
||||
fn clone(&self) -> HttpRequest<S> {
|
||||
HttpRequest {
|
||||
req: self.req.as_ref().map(|r| r.clone()),
|
||||
state: self.state.clone(),
|
||||
resource: self.resource.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> FromRequest<S> for HttpRequest<S> {
|
||||
type Config = ();
|
||||
type Result = Self;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
|
||||
req.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> fmt::Debug for HttpRequest<S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"\nHttpRequest {:?} {}:{}",
|
||||
self.version(),
|
||||
self.method(),
|
||||
self.path()
|
||||
)?;
|
||||
if !self.query_string().is_empty() {
|
||||
writeln!(f, " query: ?{:?}", self.query_string())?;
|
||||
}
|
||||
if !self.match_info().is_empty() {
|
||||
writeln!(f, " params: {:?}", self.match_info())?;
|
||||
}
|
||||
writeln!(f, " headers:")?;
|
||||
for (key, val) in self.headers().iter() {
|
||||
writeln!(f, " {:?}: {:?}", key, val)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use resource::Resource;
|
||||
use router::{ResourceDef, Router};
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let req = TestRequest::with_header("content-type", "text/plain").finish();
|
||||
let dbg = format!("{:?}", req);
|
||||
assert!(dbg.contains("HttpRequest"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_request_cookies() {
|
||||
let req = TestRequest::default().finish();
|
||||
assert!(req.cookies().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_cookies() {
|
||||
let req = TestRequest::default()
|
||||
.header(header::COOKIE, "cookie1=value1")
|
||||
.header(header::COOKIE, "cookie2=value2")
|
||||
.finish();
|
||||
{
|
||||
let cookies = req.cookies().unwrap();
|
||||
assert_eq!(cookies.len(), 2);
|
||||
assert_eq!(cookies[0].name(), "cookie1");
|
||||
assert_eq!(cookies[0].value(), "value1");
|
||||
assert_eq!(cookies[1].name(), "cookie2");
|
||||
assert_eq!(cookies[1].value(), "value2");
|
||||
}
|
||||
|
||||
let cookie = req.cookie("cookie1");
|
||||
assert!(cookie.is_some());
|
||||
let cookie = cookie.unwrap();
|
||||
assert_eq!(cookie.name(), "cookie1");
|
||||
assert_eq!(cookie.value(), "value1");
|
||||
|
||||
let cookie = req.cookie("cookie-unknown");
|
||||
assert!(cookie.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_query() {
|
||||
let req = TestRequest::with_uri("/?id=test").finish();
|
||||
assert_eq!(req.query_string(), "id=test");
|
||||
let query = req.query();
|
||||
assert_eq!(&query["id"], "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_match_info() {
|
||||
let mut router = Router::<()>::default();
|
||||
router.register_resource(Resource::new(ResourceDef::new("/{key}/")));
|
||||
|
||||
let req = TestRequest::with_uri("/value/?id=test").finish();
|
||||
let info = router.recognize(&req, &(), 0);
|
||||
assert_eq!(info.match_info().get("key"), Some("value"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_url_for() {
|
||||
let mut router = Router::<()>::default();
|
||||
let mut resource = Resource::new(ResourceDef::new("/user/{name}.{ext}"));
|
||||
resource.name("index");
|
||||
router.register_resource(resource);
|
||||
|
||||
let info = router.default_route_info();
|
||||
assert!(!info.has_prefixed_resource("/use/"));
|
||||
assert!(info.has_resource("/user/test.html"));
|
||||
assert!(info.has_prefixed_resource("/user/test.html"));
|
||||
assert!(!info.has_resource("/test/unknown"));
|
||||
assert!(!info.has_prefixed_resource("/test/unknown"));
|
||||
|
||||
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org")
|
||||
.finish_with_router(router);
|
||||
|
||||
assert_eq!(
|
||||
req.url_for("unknown", &["test"]),
|
||||
Err(UrlGenerationError::ResourceNotFound)
|
||||
);
|
||||
assert_eq!(
|
||||
req.url_for("index", &["test"]),
|
||||
Err(UrlGenerationError::NotEnoughElements)
|
||||
);
|
||||
let url = req.url_for("index", &["test", "html"]);
|
||||
assert_eq!(
|
||||
url.ok().unwrap().as_str(),
|
||||
"http://www.rust-lang.org/user/test.html"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_url_for_with_prefix() {
|
||||
let mut resource = Resource::new(ResourceDef::new("/user/{name}.html"));
|
||||
resource.name("index");
|
||||
let mut router = Router::<()>::default();
|
||||
router.set_prefix("/prefix");
|
||||
router.register_resource(resource);
|
||||
|
||||
let mut info = router.default_route_info();
|
||||
info.set_prefix(7);
|
||||
assert!(!info.has_prefixed_resource("/use/"));
|
||||
assert!(info.has_resource("/user/test.html"));
|
||||
assert!(!info.has_prefixed_resource("/user/test.html"));
|
||||
assert!(!info.has_resource("/prefix/user/test.html"));
|
||||
assert!(info.has_prefixed_resource("/prefix/user/test.html"));
|
||||
|
||||
let req = TestRequest::with_uri("/prefix/test")
|
||||
.prefix(7)
|
||||
.header(header::HOST, "www.rust-lang.org")
|
||||
.finish_with_router(router);
|
||||
let url = req.url_for("index", &["test"]);
|
||||
assert_eq!(
|
||||
url.ok().unwrap().as_str(),
|
||||
"http://www.rust-lang.org/prefix/user/test.html"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_url_for_static() {
|
||||
let mut resource = Resource::new(ResourceDef::new("/index.html"));
|
||||
resource.name("index");
|
||||
let mut router = Router::<()>::default();
|
||||
router.set_prefix("/prefix");
|
||||
router.register_resource(resource);
|
||||
|
||||
let mut info = router.default_route_info();
|
||||
info.set_prefix(7);
|
||||
assert!(info.has_resource("/index.html"));
|
||||
assert!(!info.has_prefixed_resource("/index.html"));
|
||||
assert!(!info.has_resource("/prefix/index.html"));
|
||||
assert!(info.has_prefixed_resource("/prefix/index.html"));
|
||||
|
||||
let req = TestRequest::with_uri("/prefix/test")
|
||||
.prefix(7)
|
||||
.header(header::HOST, "www.rust-lang.org")
|
||||
.finish_with_router(router);
|
||||
let url = req.url_for_static("index");
|
||||
assert_eq!(
|
||||
url.ok().unwrap().as_str(),
|
||||
"http://www.rust-lang.org/prefix/index.html"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_url_for_external() {
|
||||
let mut router = Router::<()>::default();
|
||||
router.register_external(
|
||||
"youtube",
|
||||
ResourceDef::external("https://youtube.com/watch/{video_id}"),
|
||||
);
|
||||
|
||||
let info = router.default_route_info();
|
||||
assert!(!info.has_resource("https://youtube.com/watch/unknown"));
|
||||
assert!(!info.has_prefixed_resource("https://youtube.com/watch/unknown"));
|
||||
|
||||
let req = TestRequest::default().finish_with_router(router);
|
||||
let url = req.url_for("youtube", &["oHg5SJYRHA0"]);
|
||||
assert_eq!(
|
||||
url.ok().unwrap().as_str(),
|
||||
"https://youtube.com/watch/oHg5SJYRHA0"
|
||||
);
|
||||
}
|
||||
}
|
1458
src/httpresponse.rs
1458
src/httpresponse.rs
File diff suppressed because it is too large
Load Diff
78
src/info.rs
78
src/info.rs
@ -1,10 +1,17 @@
|
||||
use http::header::{self, HeaderName};
|
||||
use server::Request;
|
||||
use std::cell::Ref;
|
||||
|
||||
use actix_http::http::header::{self, HeaderName};
|
||||
use actix_http::RequestHead;
|
||||
|
||||
const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for";
|
||||
const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host";
|
||||
const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto";
|
||||
|
||||
pub enum ConnectionInfoError {
|
||||
UnknownHost,
|
||||
UnknownScheme,
|
||||
}
|
||||
|
||||
/// `HttpRequest` connection information
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ConnectionInfo {
|
||||
@ -16,18 +23,22 @@ pub struct ConnectionInfo {
|
||||
|
||||
impl ConnectionInfo {
|
||||
/// Create *ConnectionInfo* instance for a request.
|
||||
#[cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(cyclomatic_complexity)
|
||||
)]
|
||||
pub fn update(&mut self, req: &Request) {
|
||||
pub fn get(req: &RequestHead) -> Ref<Self> {
|
||||
if !req.extensions().contains::<ConnectionInfo>() {
|
||||
req.extensions_mut().insert(ConnectionInfo::new(req));
|
||||
}
|
||||
Ref::map(req.extensions(), |e| e.get().unwrap())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
|
||||
fn new(req: &RequestHead) -> ConnectionInfo {
|
||||
let mut host = None;
|
||||
let mut scheme = None;
|
||||
let mut remote = None;
|
||||
let mut peer = None;
|
||||
|
||||
// load forwarded header
|
||||
for hdr in req.headers().get_all(header::FORWARDED) {
|
||||
for hdr in req.headers.get_all(header::FORWARDED) {
|
||||
if let Ok(val) = hdr.to_str() {
|
||||
for pair in val.split(';') {
|
||||
for el in pair.split(',') {
|
||||
@ -35,15 +46,21 @@ impl ConnectionInfo {
|
||||
if let Some(name) = items.next() {
|
||||
if let Some(val) = items.next() {
|
||||
match &name.to_lowercase() as &str {
|
||||
"for" => if remote.is_none() {
|
||||
remote = Some(val.trim());
|
||||
},
|
||||
"proto" => if scheme.is_none() {
|
||||
scheme = Some(val.trim());
|
||||
},
|
||||
"host" => if host.is_none() {
|
||||
host = Some(val.trim());
|
||||
},
|
||||
"for" => {
|
||||
if remote.is_none() {
|
||||
remote = Some(val.trim());
|
||||
}
|
||||
}
|
||||
"proto" => {
|
||||
if scheme.is_none() {
|
||||
scheme = Some(val.trim());
|
||||
}
|
||||
}
|
||||
"host" => {
|
||||
if host.is_none() {
|
||||
host = Some(val.trim());
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@ -56,7 +73,7 @@ impl ConnectionInfo {
|
||||
// scheme
|
||||
if scheme.is_none() {
|
||||
if let Some(h) = req
|
||||
.headers()
|
||||
.headers
|
||||
.get(HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap())
|
||||
{
|
||||
if let Ok(h) = h.to_str() {
|
||||
@ -64,7 +81,7 @@ impl ConnectionInfo {
|
||||
}
|
||||
}
|
||||
if scheme.is_none() {
|
||||
scheme = req.uri().scheme_part().map(|a| a.as_str());
|
||||
scheme = req.uri.scheme_part().map(|a| a.as_str());
|
||||
if scheme.is_none() && req.server_settings().secure() {
|
||||
scheme = Some("https")
|
||||
}
|
||||
@ -74,7 +91,7 @@ impl ConnectionInfo {
|
||||
// host
|
||||
if host.is_none() {
|
||||
if let Some(h) = req
|
||||
.headers()
|
||||
.headers
|
||||
.get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap())
|
||||
{
|
||||
if let Ok(h) = h.to_str() {
|
||||
@ -82,11 +99,11 @@ impl ConnectionInfo {
|
||||
}
|
||||
}
|
||||
if host.is_none() {
|
||||
if let Some(h) = req.headers().get(header::HOST) {
|
||||
if let Some(h) = req.headers.get(header::HOST) {
|
||||
host = h.to_str().ok();
|
||||
}
|
||||
if host.is_none() {
|
||||
host = req.uri().authority_part().map(|a| a.as_str());
|
||||
host = req.uri.authority_part().map(|a| a.as_str());
|
||||
if host.is_none() {
|
||||
host = Some(req.server_settings().host());
|
||||
}
|
||||
@ -97,7 +114,7 @@ impl ConnectionInfo {
|
||||
// remote addr
|
||||
if remote.is_none() {
|
||||
if let Some(h) = req
|
||||
.headers()
|
||||
.headers
|
||||
.get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap())
|
||||
{
|
||||
if let Ok(h) = h.to_str() {
|
||||
@ -110,10 +127,12 @@ impl ConnectionInfo {
|
||||
}
|
||||
}
|
||||
|
||||
self.scheme = scheme.unwrap_or("http").to_owned();
|
||||
self.host = host.unwrap_or("localhost").to_owned();
|
||||
self.remote = remote.map(|s| s.to_owned());
|
||||
self.peer = peer;
|
||||
ConnectionInfo {
|
||||
scheme: scheme.unwrap_or("http").to_owned(),
|
||||
host: host.unwrap_or("localhost").to_owned(),
|
||||
remote: remote.map(|s| s.to_owned()),
|
||||
peer: peer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Scheme of the request.
|
||||
@ -163,7 +182,7 @@ impl ConnectionInfo {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use test::TestRequest;
|
||||
use crate::test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_forwarded() {
|
||||
@ -177,7 +196,8 @@ mod tests {
|
||||
.header(
|
||||
header::FORWARDED,
|
||||
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
|
||||
).request();
|
||||
)
|
||||
.request();
|
||||
|
||||
let mut info = ConnectionInfo::default();
|
||||
info.update(&req);
|
||||
|
519
src/json.rs
519
src/json.rs
@ -1,519 +0,0 @@
|
||||
use bytes::BytesMut;
|
||||
use futures::{Future, Poll, Stream};
|
||||
use http::header::CONTENT_LENGTH;
|
||||
use std::fmt;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::rc::Rc;
|
||||
|
||||
use mime;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
|
||||
use error::{Error, JsonPayloadError};
|
||||
use handler::{FromRequest, Responder};
|
||||
use http::StatusCode;
|
||||
use httpmessage::HttpMessage;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
/// Json helper
|
||||
///
|
||||
/// Json can be used for two different purpose. First is for json response
|
||||
/// generation and second is for extracting typed information from request's
|
||||
/// payload.
|
||||
///
|
||||
/// To extract typed information from request's body, the type `T` must
|
||||
/// implement the `Deserialize` trait from *serde*.
|
||||
///
|
||||
/// [**JsonConfig**](dev/struct.JsonConfig.html) allows to configure extraction
|
||||
/// process.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Json, Result, http};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// /// deserialize `Info` from request's body
|
||||
/// 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(index)); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The `Json` type allows you to respond with well-formed JSON data: simply
|
||||
/// return a value of type Json<T> where T is the type of a structure
|
||||
/// to serialize into *JSON*. The type `T` must implement the `Serialize`
|
||||
/// trait from *serde*.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// # use actix_web::*;
|
||||
/// #
|
||||
/// #[derive(Serialize)]
|
||||
/// struct MyObj {
|
||||
/// name: String,
|
||||
/// }
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> Result<Json<MyObj>> {
|
||||
/// Ok(Json(MyObj {
|
||||
/// name: req.match_info().query("name")?,
|
||||
/// }))
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub struct Json<T>(pub T);
|
||||
|
||||
impl<T> Json<T> {
|
||||
/// Deconstruct to an inner value
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Json<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Json<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Json<T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Json: {:?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Display for Json<T>
|
||||
where
|
||||
T: fmt::Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
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, Some(cfg))
|
||||
.limit(cfg.limit)
|
||||
.map_err(move |e| (*err)(e, &req2))
|
||||
.map(Json),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Json extractor configuration
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// extern crate mime;
|
||||
/// #[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
|
||||
/// .content_type(|mime| { // <- accept text/plain content type
|
||||
/// mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
||||
/// })
|
||||
/// .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>,
|
||||
content_type: Option<Box<Fn(mime::Mime) -> bool>>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/// Set predicate for allowed content types
|
||||
pub fn content_type<F>(&mut self, predicate: F) -> &mut Self
|
||||
where
|
||||
F: Fn(mime::Mime) -> bool + 'static,
|
||||
{
|
||||
self.content_type = Some(Box::new(predicate));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Default for JsonConfig<S> {
|
||||
fn default() -> Self {
|
||||
JsonConfig {
|
||||
limit: 262_144,
|
||||
ehandler: Rc::new(|e, _| e.into()),
|
||||
content_type: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Request payload json parser that resolves to a deserialized `T` value.
|
||||
///
|
||||
/// Returns error:
|
||||
///
|
||||
/// * content type is not `application/json`
|
||||
/// (unless specified in [`JsonConfig`](struct.JsonConfig.html))
|
||||
/// * content length is greater than 256k
|
||||
///
|
||||
/// # Server example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{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
|
||||
/// println!("==== BODY ==== {:?}", val);
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// }).responder()
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub struct JsonBody<T: HttpMessage, U: DeserializeOwned> {
|
||||
limit: usize,
|
||||
length: Option<usize>,
|
||||
stream: Option<T::Stream>,
|
||||
err: Option<JsonPayloadError>,
|
||||
fut: Option<Box<Future<Item = U, Error = JsonPayloadError>>>,
|
||||
}
|
||||
|
||||
impl<T: HttpMessage, U: DeserializeOwned> JsonBody<T, U> {
|
||||
/// Create `JsonBody` for request.
|
||||
pub fn new<S>(req: &T, cfg: Option<&JsonConfig<S>>) -> Self {
|
||||
// check content-type
|
||||
let json = if let Ok(Some(mime)) = req.mime_type() {
|
||||
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) ||
|
||||
cfg.map_or(false, |cfg| {
|
||||
cfg.content_type.as_ref().map_or(false, |predicate| predicate(mime))
|
||||
})
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !json {
|
||||
return JsonBody {
|
||||
limit: 262_144,
|
||||
length: None,
|
||||
stream: None,
|
||||
fut: None,
|
||||
err: Some(JsonPayloadError::ContentType),
|
||||
};
|
||||
}
|
||||
|
||||
let mut len = None;
|
||||
if let Some(l) = req.headers().get(CONTENT_LENGTH) {
|
||||
if let Ok(s) = l.to_str() {
|
||||
if let Ok(l) = s.parse::<usize>() {
|
||||
len = Some(l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonBody {
|
||||
limit: 262_144,
|
||||
length: len,
|
||||
stream: Some(req.payload()),
|
||||
fut: None,
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Change max size of payload. By default max size is 256Kb
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
self.limit = limit;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HttpMessage + 'static, U: DeserializeOwned + 'static> Future for JsonBody<T, U> {
|
||||
type Item = U;
|
||||
type Error = JsonPayloadError;
|
||||
|
||||
fn poll(&mut self) -> Poll<U, JsonPayloadError> {
|
||||
if let Some(ref mut fut) = self.fut {
|
||||
return fut.poll();
|
||||
}
|
||||
|
||||
if let Some(err) = self.err.take() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let limit = self.limit;
|
||||
if let Some(len) = self.length.take() {
|
||||
if len > limit {
|
||||
return Err(JsonPayloadError::Overflow);
|
||||
}
|
||||
}
|
||||
|
||||
let fut = self
|
||||
.stream
|
||||
.take()
|
||||
.expect("JsonBody could not be used second time")
|
||||
.from_err()
|
||||
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(JsonPayloadError::Overflow)
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok(body)
|
||||
}
|
||||
}).and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
|
||||
self.fut = Some(Box::new(fut));
|
||||
self.poll()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bytes::Bytes;
|
||||
use futures::Async;
|
||||
use http::header;
|
||||
|
||||
use handler::Handler;
|
||||
use test::TestRequest;
|
||||
use with::With;
|
||||
|
||||
impl PartialEq for JsonPayloadError {
|
||||
fn eq(&self, other: &JsonPayloadError) -> bool {
|
||||
match *self {
|
||||
JsonPayloadError::Overflow => match *other {
|
||||
JsonPayloadError::Overflow => true,
|
||||
_ => false,
|
||||
},
|
||||
JsonPayloadError::ContentType => match *other {
|
||||
JsonPayloadError::ContentType => true,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct MyObject {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json() {
|
||||
let json = Json(MyObject {
|
||||
name: "test".to_owned(),
|
||||
});
|
||||
let resp = json.respond_to(&TestRequest::default().finish()).unwrap();
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
"application/json"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_body() {
|
||||
let req = TestRequest::default().finish();
|
||||
let mut json = req.json::<MyObject>();
|
||||
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
|
||||
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/text"),
|
||||
).finish();
|
||||
let mut json = req.json::<MyObject>();
|
||||
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
|
||||
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
).header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("10000"),
|
||||
).finish();
|
||||
let mut json = req.json::<MyObject>().limit(100);
|
||||
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow);
|
||||
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
).header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.finish();
|
||||
|
||||
let mut json = req.json::<MyObject>();
|
||||
assert_eq!(
|
||||
json.poll().ok().unwrap(),
|
||||
Async::Ready(MyObject {
|
||||
name: "test".to_owned()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_json() {
|
||||
let mut cfg = JsonConfig::default();
|
||||
cfg.limit(4096);
|
||||
let handler = With::new(|data: Json<MyObject>| data, cfg);
|
||||
|
||||
let req = TestRequest::default().finish();
|
||||
assert!(handler.handle(&req).as_err().is_some());
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
).header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.finish();
|
||||
assert!(handler.handle(&req).as_err().is_none())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_json_and_bad_content_type() {
|
||||
let mut cfg = JsonConfig::default();
|
||||
cfg.limit(4096);
|
||||
let handler = With::new(|data: Json<MyObject>| data, cfg);
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/plain"),
|
||||
).header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.finish();
|
||||
assert!(handler.handle(&req).as_err().is_some())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_json_and_good_custom_content_type() {
|
||||
let mut cfg = JsonConfig::default();
|
||||
cfg.limit(4096);
|
||||
cfg.content_type(|mime: mime::Mime| {
|
||||
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
||||
});
|
||||
let handler = With::new(|data: Json<MyObject>| data, cfg);
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/plain"),
|
||||
).header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.finish();
|
||||
assert!(handler.handle(&req).as_err().is_none())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_json_and_bad_custom_content_type() {
|
||||
let mut cfg = JsonConfig::default();
|
||||
cfg.limit(4096);
|
||||
cfg.content_type(|mime: mime::Mime| {
|
||||
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
||||
});
|
||||
let handler = With::new(|data: Json<MyObject>| data, cfg);
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/html"),
|
||||
).header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.finish();
|
||||
assert!(handler.handle(&req).as_err().is_some())
|
||||
}
|
||||
}
|
309
src/lib.rs
309
src/lib.rs
@ -1,292 +1,37 @@
|
||||
//! Actix web is a small, pragmatic, and extremely fast web framework
|
||||
//! for Rust.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use actix_web::{server, App, Path, Responder};
|
||||
//! # use std::thread;
|
||||
//!
|
||||
//! fn index(info: Path<(String, u32)>) -> impl Responder {
|
||||
//! format!("Hello {}! id:{}", info.0, info.1)
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! # thread::spawn(|| {
|
||||
//! server::new(|| {
|
||||
//! App::new().resource("/{name}/{id}/index.html", |r| r.with(index))
|
||||
//! }).bind("127.0.0.1:8080")
|
||||
//! .unwrap()
|
||||
//! .run();
|
||||
//! # });
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Documentation & community resources
|
||||
//!
|
||||
//! Besides the API documentation (which you are currently looking
|
||||
//! at!), several other resources are available:
|
||||
//!
|
||||
//! * [User Guide](https://actix.rs/docs/)
|
||||
//! * [Chat on gitter](https://gitter.im/actix/actix)
|
||||
//! * [GitHub repository](https://github.com/actix/actix-web)
|
||||
//! * [Cargo package](https://crates.io/crates/actix-web)
|
||||
//!
|
||||
//! To get started navigating the API documentation you may want to
|
||||
//! consider looking at the following pages:
|
||||
//!
|
||||
//! * [App](struct.App.html): This struct represents an actix-web
|
||||
//! application and is used to configure routes and other common
|
||||
//! settings.
|
||||
//!
|
||||
//! * [HttpServer](server/struct.HttpServer.html): This struct
|
||||
//! represents an HTTP server instance and is used to instantiate and
|
||||
//! configure servers.
|
||||
//!
|
||||
//! * [HttpRequest](struct.HttpRequest.html) and
|
||||
//! [HttpResponse](struct.HttpResponse.html): These structs
|
||||
//! represent HTTP requests and responses and expose various methods
|
||||
//! for inspecting, creating and otherwise utilizing them.
|
||||
//!
|
||||
//! ## Features
|
||||
//!
|
||||
//! * Supported *HTTP/1.x* and *HTTP/2.0* protocols
|
||||
//! * Streaming and pipelining
|
||||
//! * Keep-alive and slow requests handling
|
||||
//! * `WebSockets` server/client
|
||||
//! * Transparent content compression/decompression (br, gzip, deflate)
|
||||
//! * Configurable request routing
|
||||
//! * Graceful server shutdown
|
||||
//! * Multipart streams
|
||||
//! * SSL support with OpenSSL or `native-tls`
|
||||
//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`)
|
||||
//! * Built on top of [Actix actor framework](https://github.com/actix/actix)
|
||||
//! * Supported Rust version: 1.26 or later
|
||||
//!
|
||||
//! ## Package feature
|
||||
//!
|
||||
//! * `tls` - enables ssl support via `native-tls` crate
|
||||
//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2`
|
||||
//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2`
|
||||
//! * `uds` - enables support for making client requests via Unix Domain Sockets.
|
||||
//! Unix only. Not necessary for *serving* requests.
|
||||
//! * `session` - enables session support, includes `ring` crate as
|
||||
//! dependency
|
||||
//! * `brotli` - enables `brotli` compression support, requires `c`
|
||||
//! compiler
|
||||
//! * `flate2-c` - enables `gzip`, `deflate` compression support, requires
|
||||
//! `c` compiler
|
||||
//! * `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,
|
||||
))]
|
||||
#![warn(missing_docs)]
|
||||
#![allow(clippy::type_complexity, dead_code, unused_variables)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate base64;
|
||||
extern crate byteorder;
|
||||
extern crate bytes;
|
||||
extern crate regex;
|
||||
extern crate sha1;
|
||||
extern crate time;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
#[macro_use]
|
||||
extern crate failure;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
extern crate cookie;
|
||||
extern crate futures_cpupool;
|
||||
extern crate http as modhttp;
|
||||
extern crate httparse;
|
||||
extern crate language_tags;
|
||||
extern crate lazycell;
|
||||
extern crate mime;
|
||||
extern crate mime_guess;
|
||||
extern crate mio;
|
||||
extern crate net2;
|
||||
extern crate parking_lot;
|
||||
extern crate rand;
|
||||
extern crate slab;
|
||||
extern crate tokio;
|
||||
extern crate tokio_current_thread;
|
||||
extern crate tokio_io;
|
||||
extern crate tokio_reactor;
|
||||
extern crate tokio_tcp;
|
||||
extern crate tokio_timer;
|
||||
#[cfg(all(unix, feature = "uds"))]
|
||||
extern crate tokio_uds;
|
||||
extern crate url;
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
#[cfg(feature = "brotli")]
|
||||
extern crate brotli2;
|
||||
extern crate encoding;
|
||||
#[cfg(feature = "flate2")]
|
||||
extern crate flate2;
|
||||
extern crate h2 as http2;
|
||||
extern crate num_cpus;
|
||||
extern crate serde_urlencoded;
|
||||
#[macro_use]
|
||||
extern crate percent_encoding;
|
||||
extern crate serde_json;
|
||||
extern crate smallvec;
|
||||
extern crate v_htmlescape;
|
||||
|
||||
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 app;
|
||||
mod extractor;
|
||||
mod handler;
|
||||
mod header;
|
||||
pub mod handler;
|
||||
mod helpers;
|
||||
mod httpcodes;
|
||||
mod httpmessage;
|
||||
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;
|
||||
// mod info;
|
||||
pub mod blocking;
|
||||
pub mod filter;
|
||||
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 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 httpresponse::HttpResponse;
|
||||
pub use json::Json;
|
||||
pub use scope::Scope;
|
||||
pub use server::Request;
|
||||
mod request;
|
||||
mod resource;
|
||||
mod responder;
|
||||
mod route;
|
||||
mod service;
|
||||
mod state;
|
||||
|
||||
pub mod actix {
|
||||
//! Re-exports [actix's](https://docs.rs/actix/) prelude
|
||||
pub use super::actix_inner::actors::resolver;
|
||||
pub use super::actix_inner::actors::signal;
|
||||
pub use super::actix_inner::fut;
|
||||
pub use super::actix_inner::msgs;
|
||||
pub use super::actix_inner::prelude::*;
|
||||
pub use super::actix_inner::{run, spawn};
|
||||
}
|
||||
// re-export for convenience
|
||||
pub use actix_http::Response as HttpResponse;
|
||||
pub use actix_http::{http, Error, HttpMessage, ResponseError};
|
||||
|
||||
#[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 use crate::app::App;
|
||||
pub use crate::extractor::{Form, Json, Path, Query};
|
||||
pub use crate::handler::FromRequest;
|
||||
pub use crate::request::HttpRequest;
|
||||
pub use crate::resource::Resource;
|
||||
pub use crate::responder::{Either, Responder};
|
||||
pub use crate::service::{ServiceRequest, ServiceResponse};
|
||||
pub use crate::state::State;
|
||||
|
||||
pub mod dev {
|
||||
//! The `actix-web` prelude for library developers
|
||||
//!
|
||||
//! The purpose of this module is to alleviate imports of many common actix
|
||||
//! traits by adding a glob import to the top of actix heavy modules:
|
||||
//!
|
||||
//! ```
|
||||
//! # #![allow(unused_imports)]
|
||||
//! use actix_web::dev::*;
|
||||
//! ```
|
||||
|
||||
pub use body::BodyStream;
|
||||
pub use context::Drain;
|
||||
pub use extractor::{FormConfig, PayloadConfig, QueryConfig, PathConfig, EitherConfig, EitherCollisionStrategy};
|
||||
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 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 {
|
||||
//! Various HTTP related types
|
||||
|
||||
// re-exports
|
||||
pub use modhttp::{Method, StatusCode, Version};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use modhttp::{uri, Error, Extensions, HeaderMap, HttpTryFrom, Uri};
|
||||
|
||||
pub use cookie::{Cookie, CookieBuilder};
|
||||
|
||||
pub use helpers::NormalizePath;
|
||||
|
||||
/// Various http headers
|
||||
pub mod header {
|
||||
pub use header::*;
|
||||
pub use header::{
|
||||
Charset, ContentDisposition, DispositionParam, DispositionType, LanguageTag,
|
||||
};
|
||||
}
|
||||
pub use header::ContentEncoding;
|
||||
pub use httpresponse::ConnectionType;
|
||||
pub use crate::app::AppService;
|
||||
pub use crate::handler::{AsyncFactory, Extract, Factory, Handle};
|
||||
pub use crate::route::{Route, RouteBuilder};
|
||||
// pub use crate::info::ConnectionInfo;
|
||||
}
|
||||
|
443
src/middleware/compress.rs
Normal file
443
src/middleware/compress.rs
Normal file
@ -0,0 +1,443 @@
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
use std::{cmp, fmt, io};
|
||||
|
||||
use actix_http::body::{Body, BodyLength, MessageBody, ResponseBody};
|
||||
use actix_http::http::header::{
|
||||
ContentEncoding, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING,
|
||||
};
|
||||
use actix_http::http::{HttpTryFrom, StatusCode};
|
||||
use actix_http::{Error, Head, ResponseHead};
|
||||
use actix_service::{IntoNewTransform, Service, Transform};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{Async, Future, Poll};
|
||||
use log::trace;
|
||||
|
||||
#[cfg(feature = "brotli")]
|
||||
use brotli2::write::BrotliEncoder;
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||
|
||||
use crate::middleware::MiddlewareFactory;
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Compress(ContentEncoding);
|
||||
|
||||
impl Compress {
|
||||
pub fn new(encoding: ContentEncoding) -> Self {
|
||||
Compress(encoding)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Compress {
|
||||
fn default() -> Self {
|
||||
Compress::new(ContentEncoding::Auto)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, P, B> Transform<S> for Compress
|
||||
where
|
||||
P: 'static,
|
||||
B: MessageBody,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse<Encoder<B>>;
|
||||
type Error = S::Error;
|
||||
type Future = CompressResponse<S, P, B>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>, srv: &mut S) -> Self::Future {
|
||||
// negotiate content-encoding
|
||||
let encoding = if let Some(val) = req.headers.get(ACCEPT_ENCODING) {
|
||||
if let Ok(enc) = val.to_str() {
|
||||
AcceptEncoding::parse(enc, self.0)
|
||||
} else {
|
||||
ContentEncoding::Identity
|
||||
}
|
||||
} else {
|
||||
ContentEncoding::Identity
|
||||
};
|
||||
|
||||
CompressResponse {
|
||||
encoding,
|
||||
fut: srv.call(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct CompressResponse<S, P, B>
|
||||
where
|
||||
P: 'static,
|
||||
B: MessageBody,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
fut: S::Future,
|
||||
encoding: ContentEncoding,
|
||||
}
|
||||
|
||||
impl<S, P, B> Future for CompressResponse<S, P, B>
|
||||
where
|
||||
P: 'static,
|
||||
B: MessageBody,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Item = ServiceResponse<Encoder<B>>;
|
||||
type Error = S::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let resp = futures::try_ready!(self.fut.poll());
|
||||
|
||||
Ok(Async::Ready(resp.map_body(move |head, body| {
|
||||
Encoder::body(self.encoding, head, body)
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, P, B> IntoNewTransform<MiddlewareFactory<Compress, S>, S> for Compress
|
||||
where
|
||||
P: 'static,
|
||||
B: MessageBody,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
fn into_new_transform(self) -> MiddlewareFactory<Compress, S> {
|
||||
MiddlewareFactory::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
enum EncoderBody<B> {
|
||||
Body(B),
|
||||
Other(Box<dyn MessageBody>),
|
||||
None,
|
||||
}
|
||||
|
||||
pub struct Encoder<B> {
|
||||
body: EncoderBody<B>,
|
||||
encoder: Option<ContentEncoder>,
|
||||
}
|
||||
|
||||
impl<B: MessageBody> MessageBody for Encoder<B> {
|
||||
fn length(&self) -> BodyLength {
|
||||
if self.encoder.is_none() {
|
||||
match self.body {
|
||||
EncoderBody::Body(ref b) => b.length(),
|
||||
EncoderBody::Other(ref b) => b.length(),
|
||||
EncoderBody::None => BodyLength::Empty,
|
||||
}
|
||||
} else {
|
||||
BodyLength::Stream
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
|
||||
loop {
|
||||
let result = match self.body {
|
||||
EncoderBody::Body(ref mut b) => b.poll_next()?,
|
||||
EncoderBody::Other(ref mut b) => b.poll_next()?,
|
||||
EncoderBody::None => return Ok(Async::Ready(None)),
|
||||
};
|
||||
match result {
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
Async::Ready(Some(chunk)) => {
|
||||
if let Some(ref mut encoder) = self.encoder {
|
||||
if encoder.write(&chunk)? {
|
||||
return Ok(Async::Ready(Some(encoder.take())));
|
||||
}
|
||||
} else {
|
||||
return Ok(Async::Ready(Some(chunk)));
|
||||
}
|
||||
}
|
||||
Async::Ready(None) => {
|
||||
if let Some(encoder) = self.encoder.take() {
|
||||
let chunk = encoder.finish()?;
|
||||
if chunk.is_empty() {
|
||||
return Ok(Async::Ready(None));
|
||||
} else {
|
||||
return Ok(Async::Ready(Some(chunk)));
|
||||
}
|
||||
} else {
|
||||
return Ok(Async::Ready(None));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
|
||||
head.headers_mut().insert(
|
||||
CONTENT_ENCODING,
|
||||
HeaderValue::try_from(Bytes::from_static(encoding.as_str().as_bytes())).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
impl<B: MessageBody> Encoder<B> {
|
||||
fn body(
|
||||
encoding: ContentEncoding,
|
||||
head: &mut ResponseHead,
|
||||
body: ResponseBody<B>,
|
||||
) -> ResponseBody<Encoder<B>> {
|
||||
let has_ce = head.headers().contains_key(CONTENT_ENCODING);
|
||||
match body {
|
||||
ResponseBody::Other(b) => match b {
|
||||
Body::None => ResponseBody::Other(Body::None),
|
||||
Body::Empty => ResponseBody::Other(Body::Empty),
|
||||
Body::Bytes(buf) => {
|
||||
if !(has_ce
|
||||
|| encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto)
|
||||
{
|
||||
let mut enc = ContentEncoder::encoder(encoding).unwrap();
|
||||
|
||||
// TODO return error!
|
||||
let _ = enc.write(buf.as_ref());
|
||||
let body = enc.finish().unwrap();
|
||||
update_head(encoding, head);
|
||||
ResponseBody::Other(Body::Bytes(body))
|
||||
} else {
|
||||
ResponseBody::Other(Body::Bytes(buf))
|
||||
}
|
||||
}
|
||||
Body::Message(stream) => {
|
||||
if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS {
|
||||
ResponseBody::Body(Encoder {
|
||||
body: EncoderBody::Other(stream),
|
||||
encoder: None,
|
||||
})
|
||||
} else {
|
||||
update_head(encoding, head);
|
||||
head.no_chunking = false;
|
||||
ResponseBody::Body(Encoder {
|
||||
body: EncoderBody::Other(stream),
|
||||
encoder: ContentEncoder::encoder(encoding),
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
ResponseBody::Body(stream) => {
|
||||
if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS {
|
||||
ResponseBody::Body(Encoder {
|
||||
body: EncoderBody::Body(stream),
|
||||
encoder: None,
|
||||
})
|
||||
} else {
|
||||
update_head(encoding, head);
|
||||
head.no_chunking = false;
|
||||
ResponseBody::Body(Encoder {
|
||||
body: EncoderBody::Body(stream),
|
||||
encoder: ContentEncoder::encoder(encoding),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Writer {
|
||||
buf: BytesMut,
|
||||
}
|
||||
|
||||
impl Writer {
|
||||
fn new() -> Writer {
|
||||
Writer {
|
||||
buf: BytesMut::with_capacity(8192),
|
||||
}
|
||||
}
|
||||
fn take(&mut self) -> Bytes {
|
||||
self.buf.take().freeze()
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for Writer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.buf.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum ContentEncoder {
|
||||
#[cfg(feature = "flate2")]
|
||||
Deflate(ZlibEncoder<Writer>),
|
||||
#[cfg(feature = "flate2")]
|
||||
Gzip(GzEncoder<Writer>),
|
||||
#[cfg(feature = "brotli")]
|
||||
Br(BrotliEncoder<Writer>),
|
||||
}
|
||||
|
||||
impl fmt::Debug for ContentEncoder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContentEncoder {
|
||||
fn encoder(encoding: ContentEncoding) -> Option<Self> {
|
||||
match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new(
|
||||
Writer::new(),
|
||||
flate2::Compression::fast(),
|
||||
))),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new(
|
||||
Writer::new(),
|
||||
flate2::Compression::fast(),
|
||||
))),
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => {
|
||||
Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3)))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn take(&mut self) -> Bytes {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(self) -> Result<Bytes, io::Error> {
|
||||
match self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(encoder) => match encoder.finish() {
|
||||
Ok(writer) => Ok(writer.buf.freeze()),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(encoder) => match encoder.finish() {
|
||||
Ok(writer) => Ok(writer.buf.freeze()),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(encoder) => match encoder.finish() {
|
||||
Ok(writer) => Ok(writer.buf.freeze()),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, data: &[u8]) -> Result<bool, io::Error> {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(!encoder.get_ref().buf.is_empty()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding br encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(!encoder.get_ref().buf.is_empty()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding gzip encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(!encoder.get_ref().buf.is_empty()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding deflate encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AcceptEncoding {
|
||||
encoding: ContentEncoding,
|
||||
quality: f64,
|
||||
}
|
||||
|
||||
impl Eq for AcceptEncoding {}
|
||||
|
||||
impl Ord for AcceptEncoding {
|
||||
fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering {
|
||||
if self.quality > other.quality {
|
||||
cmp::Ordering::Less
|
||||
} else if self.quality < other.quality {
|
||||
cmp::Ordering::Greater
|
||||
} else {
|
||||
cmp::Ordering::Equal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for AcceptEncoding {
|
||||
fn partial_cmp(&self, other: &AcceptEncoding) -> Option<cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AcceptEncoding {
|
||||
fn eq(&self, other: &AcceptEncoding) -> bool {
|
||||
self.quality == other.quality
|
||||
}
|
||||
}
|
||||
|
||||
impl AcceptEncoding {
|
||||
fn new(tag: &str) -> Option<AcceptEncoding> {
|
||||
let parts: Vec<&str> = tag.split(';').collect();
|
||||
let encoding = match parts.len() {
|
||||
0 => return None,
|
||||
_ => ContentEncoding::from(parts[0]),
|
||||
};
|
||||
let quality = match parts.len() {
|
||||
1 => encoding.quality(),
|
||||
_ => match f64::from_str(parts[1]) {
|
||||
Ok(q) => q,
|
||||
Err(_) => 0.0,
|
||||
},
|
||||
};
|
||||
Some(AcceptEncoding { encoding, quality })
|
||||
}
|
||||
|
||||
/// Parse a raw Accept-Encoding header value into an ordered list.
|
||||
pub fn parse(raw: &str, encoding: ContentEncoding) -> ContentEncoding {
|
||||
let mut encodings: Vec<_> = raw
|
||||
.replace(' ', "")
|
||||
.split(',')
|
||||
.map(|l| AcceptEncoding::new(l))
|
||||
.collect();
|
||||
encodings.sort();
|
||||
|
||||
for enc in encodings {
|
||||
if let Some(enc) = enc {
|
||||
if encoding == ContentEncoding::Auto {
|
||||
return enc.encoding;
|
||||
} else if encoding == enc.encoding {
|
||||
return encoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
ContentEncoding::Identity
|
||||
}
|
||||
}
|
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_part().map(|port| port.as_u16())) {
|
||||
(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,17 +1,19 @@
|
||||
//! Default response headers
|
||||
use http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
|
||||
use http::{HeaderMap, HttpTryFrom};
|
||||
//! Middleware for setting default response headers
|
||||
use std::rc::Rc;
|
||||
|
||||
use error::Result;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use middleware::{Middleware, Response};
|
||||
use actix_http::http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
|
||||
use actix_http::http::{HeaderMap, HttpTryFrom};
|
||||
use actix_service::{IntoNewTransform, Service, Transform};
|
||||
use futures::{Async, Future, Poll};
|
||||
|
||||
use crate::middleware::MiddlewareFactory;
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
|
||||
/// `Middleware` for setting default response headers.
|
||||
///
|
||||
/// This middleware does not set header if response headers already contains it.
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{http, middleware, App, HttpResponse};
|
||||
///
|
||||
@ -22,11 +24,15 @@ use middleware::{Middleware, Response};
|
||||
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
||||
/// r.method(http::Method::HEAD)
|
||||
/// .f(|_| HttpResponse::MethodNotAllowed());
|
||||
/// })
|
||||
/// .finish();
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct DefaultHeaders {
|
||||
inner: Rc<Inner>,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
ct: bool,
|
||||
headers: HeaderMap,
|
||||
}
|
||||
@ -34,8 +40,10 @@ pub struct DefaultHeaders {
|
||||
impl Default for DefaultHeaders {
|
||||
fn default() -> Self {
|
||||
DefaultHeaders {
|
||||
ct: false,
|
||||
headers: HeaderMap::new(),
|
||||
inner: Rc::new(Inner {
|
||||
ct: false,
|
||||
headers: HeaderMap::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -48,16 +56,19 @@ impl DefaultHeaders {
|
||||
|
||||
/// Set a header.
|
||||
#[inline]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))]
|
||||
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
||||
where
|
||||
HeaderName: HttpTryFrom<K>,
|
||||
HeaderValue: HttpTryFrom<V>,
|
||||
{
|
||||
#[allow(clippy::match_wild_err_arm)]
|
||||
match HeaderName::try_from(key) {
|
||||
Ok(key) => match HeaderValue::try_from(value) {
|
||||
Ok(value) => {
|
||||
self.headers.append(key, value);
|
||||
Rc::get_mut(&mut self.inner)
|
||||
.expect("Multiple copies exist")
|
||||
.headers
|
||||
.append(key, value);
|
||||
}
|
||||
Err(_) => panic!("Can not create header value"),
|
||||
},
|
||||
@ -68,53 +79,85 @@ impl DefaultHeaders {
|
||||
|
||||
/// Set *CONTENT-TYPE* header if response does not contain this header.
|
||||
pub fn content_type(mut self) -> Self {
|
||||
self.ct = true;
|
||||
Rc::get_mut(&mut self.inner)
|
||||
.expect("Multiple copies exist")
|
||||
.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());
|
||||
impl<S, State, B> IntoNewTransform<MiddlewareFactory<DefaultHeaders, S>, S>
|
||||
for DefaultHeaders
|
||||
where
|
||||
S: Service<Request = ServiceRequest<State>, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
fn into_new_transform(self) -> MiddlewareFactory<DefaultHeaders, S> {
|
||||
MiddlewareFactory::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, State, B> Transform<S> for DefaultHeaders
|
||||
where
|
||||
S: Service<Request = ServiceRequest<State>, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<State>;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = S::Error;
|
||||
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<State>, srv: &mut S) -> Self::Future {
|
||||
let inner = self.inner.clone();
|
||||
|
||||
Box::new(srv.call(req).map(move |mut res| {
|
||||
// set response headers
|
||||
for (key, value) in inner.headers.iter() {
|
||||
if !res.headers().contains_key(key) {
|
||||
res.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))
|
||||
// default content-type
|
||||
if inner.ct && !res.headers().contains_key(CONTENT_TYPE) {
|
||||
res.headers_mut().insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/octet-stream"),
|
||||
);
|
||||
}
|
||||
|
||||
res
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::header::CONTENT_TYPE;
|
||||
use test::TestRequest;
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use actix_http::http::header::CONTENT_TYPE;
|
||||
// use actix_http::test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_default_headers() {
|
||||
let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001");
|
||||
// #[test]
|
||||
// fn test_default_headers() {
|
||||
// let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001");
|
||||
|
||||
let req = TestRequest::default().finish();
|
||||
// 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 = Response::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");
|
||||
}
|
||||
}
|
||||
// let resp = Response::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,399 +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, SameSite};
|
||||
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>,
|
||||
same_site: Option<SameSite>,
|
||||
}
|
||||
|
||||
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,
|
||||
same_site: 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);
|
||||
}
|
||||
|
||||
if let Some(same_site) = self.same_site {
|
||||
cookie.set_same_site(same_site);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/// Sets the `same_site` field in the session cookie being built.
|
||||
pub fn same_site(mut self, same_site: SameSite) -> Self {
|
||||
Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site);
|
||||
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 +1,65 @@
|
||||
//! Middlewares
|
||||
use futures::Future;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use error::{Error, Result};
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use actix_service::{NewTransform, Service, Transform};
|
||||
use futures::future::{ok, FutureResult};
|
||||
|
||||
mod logger;
|
||||
#[cfg(any(feature = "brotli", feature = "flate2"))]
|
||||
mod compress;
|
||||
#[cfg(any(feature = "brotli", feature = "flate2"))]
|
||||
pub use self::compress::Compress;
|
||||
|
||||
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>>),
|
||||
/// Helper for middleware service factory
|
||||
pub struct MiddlewareFactory<T, S>
|
||||
where
|
||||
T: Transform<S> + Clone,
|
||||
S: Service,
|
||||
{
|
||||
tr: T,
|
||||
_t: PhantomData<S>,
|
||||
}
|
||||
|
||||
/// 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
|
||||
impl<T, S> MiddlewareFactory<T, S>
|
||||
where
|
||||
T: Transform<S> + Clone,
|
||||
S: Service,
|
||||
{
|
||||
pub fn new(tr: T) -> Self {
|
||||
MiddlewareFactory {
|
||||
tr,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Clone for MiddlewareFactory<T, S>
|
||||
where
|
||||
T: Transform<S> + Clone,
|
||||
S: Service,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
tr: self.tr.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> NewTransform<S> for MiddlewareFactory<T, S>
|
||||
where
|
||||
T: Transform<S> + Clone,
|
||||
S: Service,
|
||||
{
|
||||
type Request = T::Request;
|
||||
type Response = T::Response;
|
||||
type Error = T::Error;
|
||||
type Transform = T;
|
||||
type InitError = ();
|
||||
type Future = FutureResult<Self::Transform, Self::InitError>;
|
||||
|
||||
fn new_transform(&self) -> Self::Future {
|
||||
ok(self.tr.clone())
|
||||
}
|
||||
}
|
||||
|
@ -1,618 +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;
|
||||
//! # extern crate actix;
|
||||
//! use actix_web::{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();
|
||||
}
|
||||
}
|
334
src/param.rs
334
src/param.rs
@ -1,334 +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, RESERVED_QUOTER};
|
||||
|
||||
/// 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 URL-decoded matched parameter by name without type conversion
|
||||
pub fn get_decoded(&self, key: &str) -> Option<String> {
|
||||
self.get(key).map(|value| {
|
||||
if let Some(ref mut value) = RESERVED_QUOTER.requote(value.as_bytes()) {
|
||||
Rc::make_mut(value).to_string()
|
||||
} else {
|
||||
value.to_string()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// 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"]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_param_by_name() {
|
||||
let mut params = Params::new();
|
||||
params.add_static("item1", "path");
|
||||
params.add_static("item2", "http%3A%2F%2Flocalhost%3A80%2Ffoo");
|
||||
|
||||
assert_eq!(params.get("item0"), None);
|
||||
assert_eq!(params.get_decoded("item0"), None);
|
||||
assert_eq!(params.get("item1"), Some("path"));
|
||||
assert_eq!(params.get_decoded("item1"), Some("path".to_string()));
|
||||
assert_eq!(
|
||||
params.get("item2"),
|
||||
Some("http%3A%2F%2Flocalhost%3A80%2Ffoo")
|
||||
);
|
||||
assert_eq!(
|
||||
params.get_decoded("item2"),
|
||||
Some("http://localhost:80/foo".to_string())
|
||||
);
|
||||
}
|
||||
}
|
715
src/payload.rs
715
src/payload.rs
@ -1,715 +0,0 @@
|
||||
//! Payload stream
|
||||
use bytes::{Bytes, BytesMut};
|
||||
#[cfg(not(test))]
|
||||
use futures::task::current as current_task;
|
||||
use futures::task::Task;
|
||||
use futures::{Async, Poll, Stream};
|
||||
use std::cell::RefCell;
|
||||
use std::cmp;
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
use error::PayloadError;
|
||||
|
||||
/// max buffer size 32k
|
||||
pub(crate) const MAX_BUFFER_SIZE: usize = 32_768;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum PayloadStatus {
|
||||
Read,
|
||||
Pause,
|
||||
Dropped,
|
||||
}
|
||||
|
||||
/// Buffered stream of bytes chunks
|
||||
///
|
||||
/// Payload stores chunks in a vector. First chunk can be received with
|
||||
/// `.readany()` method. Payload stream is not thread safe. Payload does not
|
||||
/// notify current task when new data is available.
|
||||
///
|
||||
/// Payload stream can be used as `HttpResponse` body stream.
|
||||
#[derive(Debug)]
|
||||
pub struct Payload {
|
||||
inner: Rc<RefCell<Inner>>,
|
||||
}
|
||||
|
||||
impl Payload {
|
||||
/// Create payload stream.
|
||||
///
|
||||
/// This method construct two objects responsible for bytes stream
|
||||
/// generation.
|
||||
///
|
||||
/// * `PayloadSender` - *Sender* side of the stream
|
||||
///
|
||||
/// * `Payload` - *Receiver* side of the stream
|
||||
pub fn new(eof: bool) -> (PayloadSender, Payload) {
|
||||
let shared = Rc::new(RefCell::new(Inner::new(eof)));
|
||||
|
||||
(
|
||||
PayloadSender {
|
||||
inner: Rc::downgrade(&shared),
|
||||
},
|
||||
Payload { inner: shared },
|
||||
)
|
||||
}
|
||||
|
||||
/// Create empty payload
|
||||
#[doc(hidden)]
|
||||
pub fn empty() -> Payload {
|
||||
Payload {
|
||||
inner: Rc::new(RefCell::new(Inner::new(true))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Length of the data in this payload
|
||||
#[cfg(test)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.borrow().len()
|
||||
}
|
||||
|
||||
/// Is payload empty
|
||||
#[cfg(test)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.borrow().len() == 0
|
||||
}
|
||||
|
||||
/// Put unused data back to payload
|
||||
#[inline]
|
||||
pub fn unread_data(&mut self, data: Bytes) {
|
||||
self.inner.borrow_mut().unread_data(data);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn readall(&self) -> Option<Bytes> {
|
||||
self.inner.borrow_mut().readall()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Set read buffer capacity
|
||||
///
|
||||
/// Default buffer capacity is 32Kb.
|
||||
pub fn set_read_buffer_capacity(&mut self, cap: usize) {
|
||||
self.inner.borrow_mut().capacity = cap;
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for Payload {
|
||||
type Item = Bytes;
|
||||
type Error = PayloadError;
|
||||
|
||||
#[inline]
|
||||
fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
self.inner.borrow_mut().readany()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Payload {
|
||||
fn clone(&self) -> Payload {
|
||||
Payload {
|
||||
inner: Rc::clone(&self.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload writer interface.
|
||||
pub(crate) trait PayloadWriter {
|
||||
/// Set stream error.
|
||||
fn set_error(&mut self, err: PayloadError);
|
||||
|
||||
/// Write eof into a stream which closes reading side of a stream.
|
||||
fn feed_eof(&mut self);
|
||||
|
||||
/// Feed bytes into a payload stream
|
||||
fn feed_data(&mut self, data: Bytes);
|
||||
|
||||
/// Need read data
|
||||
fn need_read(&self) -> PayloadStatus;
|
||||
}
|
||||
|
||||
/// Sender part of the payload stream
|
||||
pub struct PayloadSender {
|
||||
inner: Weak<RefCell<Inner>>,
|
||||
}
|
||||
|
||||
impl PayloadWriter for PayloadSender {
|
||||
#[inline]
|
||||
fn set_error(&mut self, err: PayloadError) {
|
||||
if let Some(shared) = self.inner.upgrade() {
|
||||
shared.borrow_mut().set_error(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn feed_eof(&mut self) {
|
||||
if let Some(shared) = self.inner.upgrade() {
|
||||
shared.borrow_mut().feed_eof()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn feed_data(&mut self, data: Bytes) {
|
||||
if let Some(shared) = self.inner.upgrade() {
|
||||
shared.borrow_mut().feed_data(data)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn need_read(&self) -> PayloadStatus {
|
||||
// we check need_read only if Payload (other side) is alive,
|
||||
// otherwise always return true (consume payload)
|
||||
if let Some(shared) = self.inner.upgrade() {
|
||||
if shared.borrow().need_read {
|
||||
PayloadStatus::Read
|
||||
} else {
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
if shared.borrow_mut().io_task.is_none() {
|
||||
shared.borrow_mut().io_task = Some(current_task());
|
||||
}
|
||||
}
|
||||
PayloadStatus::Pause
|
||||
}
|
||||
} else {
|
||||
PayloadStatus::Dropped
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
len: usize,
|
||||
eof: bool,
|
||||
err: Option<PayloadError>,
|
||||
need_read: bool,
|
||||
items: VecDeque<Bytes>,
|
||||
capacity: usize,
|
||||
task: Option<Task>,
|
||||
io_task: Option<Task>,
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn new(eof: bool) -> Self {
|
||||
Inner {
|
||||
eof,
|
||||
len: 0,
|
||||
err: None,
|
||||
items: VecDeque::new(),
|
||||
need_read: true,
|
||||
capacity: MAX_BUFFER_SIZE,
|
||||
task: None,
|
||||
io_task: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_error(&mut self, err: PayloadError) {
|
||||
self.err = Some(err);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn feed_eof(&mut self) {
|
||||
self.eof = true;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn feed_data(&mut self, data: Bytes) {
|
||||
self.len += data.len();
|
||||
self.items.push_back(data);
|
||||
self.need_read = self.len < self.capacity;
|
||||
if let Some(task) = self.task.take() {
|
||||
task.notify()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn readall(&mut self) -> Option<Bytes> {
|
||||
let len = self.items.iter().map(|b| b.len()).sum();
|
||||
if len > 0 {
|
||||
let mut buf = BytesMut::with_capacity(len);
|
||||
for item in &self.items {
|
||||
buf.extend_from_slice(item);
|
||||
}
|
||||
self.items = VecDeque::new();
|
||||
self.len = 0;
|
||||
Some(buf.take().freeze())
|
||||
} else {
|
||||
self.need_read = true;
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
if let Some(data) = self.items.pop_front() {
|
||||
self.len -= data.len();
|
||||
self.need_read = self.len < self.capacity;
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
if self.need_read && self.task.is_none() {
|
||||
self.task = Some(current_task());
|
||||
}
|
||||
if let Some(task) = self.io_task.take() {
|
||||
task.notify()
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(Some(data)))
|
||||
} else if let Some(err) = self.err.take() {
|
||||
Err(err)
|
||||
} else if self.eof {
|
||||
Ok(Async::Ready(None))
|
||||
} else {
|
||||
self.need_read = true;
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
if self.task.is_none() {
|
||||
self.task = Some(current_task());
|
||||
}
|
||||
if let Some(task) = self.io_task.take() {
|
||||
task.notify()
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
fn unread_data(&mut self, data: Bytes) {
|
||||
self.len += data.len();
|
||||
self.items.push_front(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload buffer
|
||||
pub struct PayloadBuffer<S> {
|
||||
len: usize,
|
||||
items: VecDeque<Bytes>,
|
||||
stream: S,
|
||||
}
|
||||
|
||||
impl<S> PayloadBuffer<S>
|
||||
where
|
||||
S: Stream<Item = Bytes, Error = PayloadError>,
|
||||
{
|
||||
/// Create new `PayloadBuffer` instance
|
||||
pub fn new(stream: S) -> Self {
|
||||
PayloadBuffer {
|
||||
len: 0,
|
||||
items: VecDeque::new(),
|
||||
stream,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get mutable reference to an inner stream.
|
||||
pub fn get_mut(&mut self) -> &mut S {
|
||||
&mut self.stream
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_stream(&mut self) -> Poll<bool, PayloadError> {
|
||||
self.stream.poll().map(|res| match res {
|
||||
Async::Ready(Some(data)) => {
|
||||
self.len += data.len();
|
||||
self.items.push_back(data);
|
||||
Async::Ready(true)
|
||||
}
|
||||
Async::Ready(None) => Async::Ready(false),
|
||||
Async::NotReady => Async::NotReady,
|
||||
})
|
||||
}
|
||||
|
||||
/// Read first available chunk of bytes
|
||||
#[inline]
|
||||
pub fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
if let Some(data) = self.items.pop_front() {
|
||||
self.len -= data.len();
|
||||
Ok(Async::Ready(Some(data)))
|
||||
} else {
|
||||
match self.poll_stream()? {
|
||||
Async::Ready(true) => self.readany(),
|
||||
Async::Ready(false) => Ok(Async::Ready(None)),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if buffer contains enough bytes
|
||||
#[inline]
|
||||
pub fn can_read(&mut self, size: usize) -> Poll<Option<bool>, PayloadError> {
|
||||
if size <= self.len {
|
||||
Ok(Async::Ready(Some(true)))
|
||||
} else {
|
||||
match self.poll_stream()? {
|
||||
Async::Ready(true) => self.can_read(size),
|
||||
Async::Ready(false) => Ok(Async::Ready(None)),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return reference to the first chunk of data
|
||||
#[inline]
|
||||
pub fn get_chunk(&mut self) -> Poll<Option<&[u8]>, PayloadError> {
|
||||
if self.items.is_empty() {
|
||||
match self.poll_stream()? {
|
||||
Async::Ready(true) => (),
|
||||
Async::Ready(false) => return Ok(Async::Ready(None)),
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
match self.items.front().map(|c| c.as_ref()) {
|
||||
Some(chunk) => Ok(Async::Ready(Some(chunk))),
|
||||
None => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read exact number of bytes
|
||||
#[inline]
|
||||
pub fn read_exact(&mut self, size: usize) -> Poll<Option<Bytes>, PayloadError> {
|
||||
if size <= self.len {
|
||||
self.len -= size;
|
||||
let mut chunk = self.items.pop_front().unwrap();
|
||||
if size < chunk.len() {
|
||||
let buf = chunk.split_to(size);
|
||||
self.items.push_front(chunk);
|
||||
Ok(Async::Ready(Some(buf)))
|
||||
} else if size == chunk.len() {
|
||||
Ok(Async::Ready(Some(chunk)))
|
||||
} else {
|
||||
let mut buf = BytesMut::with_capacity(size);
|
||||
buf.extend_from_slice(&chunk);
|
||||
|
||||
while buf.len() < size {
|
||||
let mut chunk = self.items.pop_front().unwrap();
|
||||
let rem = cmp::min(size - buf.len(), chunk.len());
|
||||
buf.extend_from_slice(&chunk.split_to(rem));
|
||||
if !chunk.is_empty() {
|
||||
self.items.push_front(chunk);
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(Some(buf.freeze())))
|
||||
}
|
||||
} else {
|
||||
match self.poll_stream()? {
|
||||
Async::Ready(true) => self.read_exact(size),
|
||||
Async::Ready(false) => Ok(Async::Ready(None)),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove specified amount if bytes from buffer
|
||||
#[inline]
|
||||
pub fn drop_bytes(&mut self, size: usize) {
|
||||
if size <= self.len {
|
||||
self.len -= size;
|
||||
|
||||
let mut len = 0;
|
||||
while len < size {
|
||||
let mut chunk = self.items.pop_front().unwrap();
|
||||
let rem = cmp::min(size - len, chunk.len());
|
||||
len += rem;
|
||||
if rem < chunk.len() {
|
||||
chunk.split_to(rem);
|
||||
self.items.push_front(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy buffered data
|
||||
pub fn copy(&mut self, size: usize) -> Poll<Option<BytesMut>, PayloadError> {
|
||||
if size <= self.len {
|
||||
let mut buf = BytesMut::with_capacity(size);
|
||||
for chunk in &self.items {
|
||||
if buf.len() < size {
|
||||
let rem = cmp::min(size - buf.len(), chunk.len());
|
||||
buf.extend_from_slice(&chunk[..rem]);
|
||||
}
|
||||
if buf.len() == size {
|
||||
return Ok(Async::Ready(Some(buf)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match self.poll_stream()? {
|
||||
Async::Ready(true) => self.copy(size),
|
||||
Async::Ready(false) => Ok(Async::Ready(None)),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read until specified ending
|
||||
pub fn read_until(&mut self, line: &[u8]) -> Poll<Option<Bytes>, PayloadError> {
|
||||
let mut idx = 0;
|
||||
let mut num = 0;
|
||||
let mut offset = 0;
|
||||
let mut found = false;
|
||||
let mut length = 0;
|
||||
|
||||
for no in 0..self.items.len() {
|
||||
{
|
||||
let chunk = &self.items[no];
|
||||
for (pos, ch) in chunk.iter().enumerate() {
|
||||
if *ch == line[idx] {
|
||||
idx += 1;
|
||||
if idx == line.len() {
|
||||
num = no;
|
||||
offset = pos + 1;
|
||||
length += pos + 1;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
idx = 0
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
length += chunk.len()
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
let mut buf = BytesMut::with_capacity(length);
|
||||
if num > 0 {
|
||||
for _ in 0..num {
|
||||
buf.extend_from_slice(&self.items.pop_front().unwrap());
|
||||
}
|
||||
}
|
||||
if offset > 0 {
|
||||
let mut chunk = self.items.pop_front().unwrap();
|
||||
buf.extend_from_slice(&chunk.split_to(offset));
|
||||
if !chunk.is_empty() {
|
||||
self.items.push_front(chunk)
|
||||
}
|
||||
}
|
||||
self.len -= length;
|
||||
return Ok(Async::Ready(Some(buf.freeze())));
|
||||
}
|
||||
}
|
||||
|
||||
match self.poll_stream()? {
|
||||
Async::Ready(true) => self.read_until(line),
|
||||
Async::Ready(false) => Ok(Async::Ready(None)),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read bytes until new line delimiter
|
||||
pub fn readline(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
self.read_until(b"\n")
|
||||
}
|
||||
|
||||
/// Put unprocessed data back to the buffer
|
||||
pub fn unprocessed(&mut self, data: Bytes) {
|
||||
self.len += data.len();
|
||||
self.items.push_front(data);
|
||||
}
|
||||
|
||||
/// Get remaining data from the buffer
|
||||
pub fn remaining(&mut self) -> Bytes {
|
||||
self.items
|
||||
.iter_mut()
|
||||
.fold(BytesMut::new(), |mut b, c| {
|
||||
b.extend_from_slice(c);
|
||||
b
|
||||
}).freeze()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use failure::Fail;
|
||||
use futures::future::{lazy, result};
|
||||
use std::io;
|
||||
use tokio::runtime::current_thread::Runtime;
|
||||
|
||||
#[test]
|
||||
fn test_error() {
|
||||
let err: PayloadError =
|
||||
io::Error::new(io::ErrorKind::Other, "ParseError").into();
|
||||
assert_eq!(format!("{}", err), "ParseError");
|
||||
assert_eq!(format!("{}", err.cause().unwrap()), "ParseError");
|
||||
|
||||
let err = PayloadError::Incomplete;
|
||||
assert_eq!(
|
||||
format!("{}", err),
|
||||
"A payload reached EOF, but is not complete."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(lazy(|| {
|
||||
let (_, payload) = Payload::new(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(payload.len, 0);
|
||||
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eof() {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::new(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
|
||||
sender.feed_data(Bytes::from("data"));
|
||||
sender.feed_eof();
|
||||
|
||||
assert_eq!(
|
||||
Async::Ready(Some(Bytes::from("data"))),
|
||||
payload.readany().ok().unwrap()
|
||||
);
|
||||
assert_eq!(payload.len, 0);
|
||||
assert_eq!(Async::Ready(None), payload.readany().ok().unwrap());
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_err() {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::new(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
|
||||
|
||||
sender.set_error(PayloadError::Incomplete);
|
||||
payload.readany().err().unwrap();
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readany() {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::new(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
sender.feed_data(Bytes::from("line1"));
|
||||
sender.feed_data(Bytes::from("line2"));
|
||||
|
||||
assert_eq!(
|
||||
Async::Ready(Some(Bytes::from("line1"))),
|
||||
payload.readany().ok().unwrap()
|
||||
);
|
||||
assert_eq!(payload.len, 0);
|
||||
|
||||
assert_eq!(
|
||||
Async::Ready(Some(Bytes::from("line2"))),
|
||||
payload.readany().ok().unwrap()
|
||||
);
|
||||
assert_eq!(payload.len, 0);
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readexactly() {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::new(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap());
|
||||
|
||||
sender.feed_data(Bytes::from("line1"));
|
||||
sender.feed_data(Bytes::from("line2"));
|
||||
|
||||
assert_eq!(
|
||||
Async::Ready(Some(Bytes::from_static(b"li"))),
|
||||
payload.read_exact(2).ok().unwrap()
|
||||
);
|
||||
assert_eq!(payload.len, 3);
|
||||
|
||||
assert_eq!(
|
||||
Async::Ready(Some(Bytes::from_static(b"ne1l"))),
|
||||
payload.read_exact(4).ok().unwrap()
|
||||
);
|
||||
assert_eq!(payload.len, 4);
|
||||
|
||||
sender.set_error(PayloadError::Incomplete);
|
||||
payload.read_exact(10).err().unwrap();
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readuntil() {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::new(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap());
|
||||
|
||||
sender.feed_data(Bytes::from("line1"));
|
||||
sender.feed_data(Bytes::from("line2"));
|
||||
|
||||
assert_eq!(
|
||||
Async::Ready(Some(Bytes::from("line"))),
|
||||
payload.read_until(b"ne").ok().unwrap()
|
||||
);
|
||||
assert_eq!(payload.len, 1);
|
||||
|
||||
assert_eq!(
|
||||
Async::Ready(Some(Bytes::from("1line2"))),
|
||||
payload.read_until(b"2").ok().unwrap()
|
||||
);
|
||||
assert_eq!(payload.len, 0);
|
||||
|
||||
sender.set_error(PayloadError::Incomplete);
|
||||
payload.read_until(b"b").err().unwrap();
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unread_data() {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(lazy(|| {
|
||||
let (_, mut payload) = Payload::new(false);
|
||||
|
||||
payload.unread_data(Bytes::from("data"));
|
||||
assert!(!payload.is_empty());
|
||||
assert_eq!(payload.len(), 4);
|
||||
|
||||
assert_eq!(
|
||||
Async::Ready(Some(Bytes::from("data"))),
|
||||
payload.poll().ok().unwrap()
|
||||
);
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
})).unwrap();
|
||||
}
|
||||
}
|
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 occurred during request handling, status: {} {}",
|
||||
self.resp.as_ref().unwrap().status(), err
|
||||
);
|
||||
} else {
|
||||
warn!(
|
||||
"Error occurred 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()));
|
||||
}
|
||||
}
|
174
src/request.rs
Normal file
174
src/request.rs
Normal file
@ -0,0 +1,174 @@
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_http::http::{HeaderMap, Method, Uri, Version};
|
||||
use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead};
|
||||
use actix_router::{Path, Url};
|
||||
use futures::future::{ok, FutureResult};
|
||||
|
||||
use crate::handler::FromRequest;
|
||||
use crate::service::ServiceRequest;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HttpRequest {
|
||||
head: Message<RequestHead>,
|
||||
pub(crate) path: Path<Url>,
|
||||
extensions: Rc<Extensions>,
|
||||
}
|
||||
|
||||
impl HttpRequest {
|
||||
#[inline]
|
||||
pub fn new(
|
||||
head: Message<RequestHead>,
|
||||
path: Path<Url>,
|
||||
extensions: Rc<Extensions>,
|
||||
) -> HttpRequest {
|
||||
HttpRequest {
|
||||
head,
|
||||
path,
|
||||
extensions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpRequest {
|
||||
/// This method returns reference to the request head
|
||||
#[inline]
|
||||
pub fn head(&self) -> &RequestHead {
|
||||
&self.head
|
||||
}
|
||||
|
||||
/// Request's uri.
|
||||
#[inline]
|
||||
pub fn uri(&self) -> &Uri {
|
||||
&self.head().uri
|
||||
}
|
||||
|
||||
/// Read the Request method.
|
||||
#[inline]
|
||||
pub fn method(&self) -> &Method {
|
||||
&self.head().method
|
||||
}
|
||||
|
||||
/// Read the Request Version.
|
||||
#[inline]
|
||||
pub fn version(&self) -> Version {
|
||||
self.head().version
|
||||
}
|
||||
|
||||
/// The target path of this Request.
|
||||
#[inline]
|
||||
pub fn path(&self) -> &str {
|
||||
self.head().uri.path()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Returns Request's headers.
|
||||
pub fn headers(&self) -> &HeaderMap {
|
||||
&self.head().headers
|
||||
}
|
||||
|
||||
/// The query string in the URL.
|
||||
///
|
||||
/// E.g., id=10
|
||||
#[inline]
|
||||
pub fn query_string(&self) -> &str {
|
||||
if let Some(query) = self.uri().query().as_ref() {
|
||||
query
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the Path parameters.
|
||||
///
|
||||
/// Params is a container for url parameters.
|
||||
/// A variable segment is specified in the form `{identifier}`,
|
||||
/// where the identifier can be used later in a request handler to
|
||||
/// access the matched value for that segment.
|
||||
#[inline]
|
||||
pub fn match_info(&self) -> &Path<Url> {
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// Request extensions
|
||||
#[inline]
|
||||
pub fn extensions(&self) -> Ref<Extensions> {
|
||||
self.head.extensions()
|
||||
}
|
||||
|
||||
/// Mutable reference to a the request's extensions
|
||||
#[inline]
|
||||
pub fn extensions_mut(&self) -> RefMut<Extensions> {
|
||||
self.head.extensions_mut()
|
||||
}
|
||||
|
||||
/// Application extensions
|
||||
#[inline]
|
||||
pub fn app_extensions(&self) -> &Extensions {
|
||||
&self.extensions
|
||||
}
|
||||
|
||||
// /// Get *ConnectionInfo* for the correct request.
|
||||
// #[inline]
|
||||
// pub fn connection_info(&self) -> Ref<ConnectionInfo> {
|
||||
// ConnectionInfo::get(&*self)
|
||||
// }
|
||||
}
|
||||
|
||||
impl Deref for HttpRequest {
|
||||
type Target = RequestHead;
|
||||
|
||||
fn deref(&self) -> &RequestHead {
|
||||
self.head()
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpMessage for HttpRequest {
|
||||
type Stream = ();
|
||||
|
||||
#[inline]
|
||||
fn headers(&self) -> &HeaderMap {
|
||||
self.headers()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn take_payload(&mut self) -> Payload<Self::Stream> {
|
||||
Payload::None
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> FromRequest<P> for HttpRequest {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceRequest<P>) -> Self::Future {
|
||||
ok(req.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for HttpRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"\nHttpRequest {:?} {}:{}",
|
||||
self.head.version,
|
||||
self.head.method,
|
||||
self.path()
|
||||
)?;
|
||||
if !self.query_string().is_empty() {
|
||||
writeln!(f, " query: ?{:?}", self.query_string())?;
|
||||
}
|
||||
if !self.match_info().is_empty() {
|
||||
writeln!(f, " params: {:?}", self.match_info())?;
|
||||
}
|
||||
writeln!(f, " headers:")?;
|
||||
for (key, val) in self.headers().iter() {
|
||||
writeln!(f, " {:?}: {:?}", key, val)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
513
src/resource.rs
513
src/resource.rs
@ -1,82 +1,65 @@
|
||||
use std::ops::Deref;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use futures::Future;
|
||||
use http::Method;
|
||||
use smallvec::SmallVec;
|
||||
use actix_http::{http::Method, Error, Response};
|
||||
use actix_service::{
|
||||
ApplyNewService, IntoNewService, IntoNewTransform, NewService, NewTransform, Service,
|
||||
};
|
||||
use futures::future::{ok, Either, FutureResult};
|
||||
use futures::{Async, Future, IntoFuture, Poll};
|
||||
|
||||
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;
|
||||
use crate::handler::{AsyncFactory, Factory, FromRequest};
|
||||
use crate::helpers::{DefaultNewService, HttpDefaultNewService, HttpDefaultService};
|
||||
use crate::responder::Responder;
|
||||
use crate::route::{CreateRouteService, Route, RouteBuilder, RouteService};
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct RouteId(usize);
|
||||
|
||||
/// *Resource* is an entry in route table which corresponds to requested URL.
|
||||
/// Resource route definition
|
||||
///
|
||||
/// 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>>>>,
|
||||
/// If handler is not explicitly set, default *404 Not Found* handler is used.
|
||||
pub struct Resource<P, T = ResourceEndpoint<P>> {
|
||||
routes: Vec<Route<P>>,
|
||||
endpoint: T,
|
||||
default: Rc<
|
||||
RefCell<Option<Rc<HttpDefaultNewService<ServiceRequest<P>, ServiceResponse>>>>,
|
||||
>,
|
||||
factory_ref: Rc<RefCell<Option<ResourceFactory<P>>>>,
|
||||
}
|
||||
|
||||
impl<S> Resource<S> {
|
||||
/// Create new resource with specified resource definition
|
||||
pub fn new(rdef: ResourceDef) -> Self {
|
||||
impl<P> Resource<P> {
|
||||
pub fn new() -> Resource<P> {
|
||||
let fref = Rc::new(RefCell::new(None));
|
||||
|
||||
Resource {
|
||||
rdef,
|
||||
routes: SmallVec::new(),
|
||||
middlewares: Rc::new(Vec::new()),
|
||||
routes: Vec::new(),
|
||||
endpoint: ResourceEndpoint::new(fref.clone()),
|
||||
factory_ref: fref,
|
||||
default: Rc::new(RefCell::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<P> Default for Resource<P> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Resource<S> {
|
||||
impl<P: 'static, T> Resource<P, T>
|
||||
where
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse,
|
||||
Error = (),
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
/// 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;
|
||||
/// ```rust,ignore
|
||||
/// use actix_web::*;
|
||||
///
|
||||
/// fn main() {
|
||||
@ -90,44 +73,72 @@ impl<S: 'static> Resource<S> {
|
||||
/// .finish();
|
||||
/// }
|
||||
/// ```
|
||||
pub fn route(&mut self) -> &mut Route<S> {
|
||||
self.routes.push(Route::default());
|
||||
self.routes.last_mut().unwrap()
|
||||
pub fn route<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(RouteBuilder<P>) -> Route<P>,
|
||||
{
|
||||
self.routes.push(f(Route::build()));
|
||||
self
|
||||
}
|
||||
|
||||
/// 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())
|
||||
pub fn get<F, I, R>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Factory<I, R> + 'static,
|
||||
I: FromRequest<P> + 'static,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
self.routes.push(Route::get().to(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// 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())
|
||||
pub fn post<F, I, R>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Factory<I, R> + 'static,
|
||||
I: FromRequest<P> + 'static,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
self.routes.push(Route::post().to(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// 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())
|
||||
pub fn put<F, I, R>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Factory<I, R> + 'static,
|
||||
I: FromRequest<P> + 'static,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
self.routes.push(Route::put().to(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// 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())
|
||||
pub fn delete<F, I, R>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Factory<I, R> + 'static,
|
||||
I: FromRequest<P> + 'static,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
self.routes.push(Route::delete().to(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// 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())
|
||||
pub fn head<F, I, R>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Factory<I, R> + 'static,
|
||||
I: FromRequest<P> + 'static,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
self.routes.push(Route::build().method(Method::HEAD).to(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// Register a new route and add method check to route.
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::*;
|
||||
/// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
|
||||
@ -137,70 +148,23 @@ impl<S: 'static> Resource<S> {
|
||||
///
|
||||
/// This is shortcut for:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// # 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)
|
||||
pub fn method<F>(mut self, method: Method, f: F) -> Self
|
||||
where
|
||||
F: Fn(&HttpRequest<S>) -> R + 'static,
|
||||
R: Responder + 'static,
|
||||
F: FnOnce(RouteBuilder<P>) -> Route<P>,
|
||||
{
|
||||
self.routes.push(Route::default());
|
||||
self.routes.last_mut().unwrap().f(handler)
|
||||
self.routes.push(f(Route::build().method(method)));
|
||||
self
|
||||
}
|
||||
|
||||
/// Register a new route and add handler.
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::*;
|
||||
/// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
|
||||
@ -210,25 +174,25 @@ impl<S: 'static> Resource<S> {
|
||||
///
|
||||
/// This is shortcut for:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// # 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)
|
||||
pub fn to<F, I, R>(mut self, handler: F) -> Self
|
||||
where
|
||||
F: WithFactory<T, S, R>,
|
||||
F: Factory<I, R> + 'static,
|
||||
I: FromRequest<P> + 'static,
|
||||
R: Responder + 'static,
|
||||
T: FromRequest<S> + 'static,
|
||||
{
|
||||
self.routes.push(Route::default());
|
||||
self.routes.last_mut().unwrap().with(handler);
|
||||
self.routes.push(Route::build().to(handler));
|
||||
self
|
||||
}
|
||||
|
||||
/// Register a new route and add async handler.
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// use actix_web::*;
|
||||
@ -243,7 +207,7 @@ impl<S: 'static> Resource<S> {
|
||||
///
|
||||
/// This is shortcut for:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// # use actix_web::*;
|
||||
@ -253,72 +217,259 @@ impl<S: 'static> Resource<S> {
|
||||
/// # }
|
||||
/// App::new().resource("/", |r| r.route().with_async(index));
|
||||
/// ```
|
||||
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn to_async<F, I, R>(mut self, handler: F) -> Self
|
||||
where
|
||||
F: Fn(T) -> R + 'static,
|
||||
R: Future<Item = I, Error = E> + 'static,
|
||||
I: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
T: FromRequest<S> + 'static,
|
||||
F: AsyncFactory<I, R>,
|
||||
I: FromRequest<P> + 'static,
|
||||
R: IntoFuture + 'static,
|
||||
R::Item: Into<Response>,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
self.routes.push(Route::default());
|
||||
self.routes.last_mut().unwrap().with_async(handler);
|
||||
self.routes.push(Route::build().to_async(handler));
|
||||
self
|
||||
}
|
||||
|
||||
/// 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));
|
||||
pub fn middleware<M, F>(
|
||||
self,
|
||||
mw: F,
|
||||
) -> Resource<
|
||||
P,
|
||||
impl NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse,
|
||||
Error = (),
|
||||
InitError = (),
|
||||
>,
|
||||
>
|
||||
where
|
||||
M: NewTransform<
|
||||
T::Service,
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse,
|
||||
Error = (),
|
||||
InitError = (),
|
||||
>,
|
||||
F: IntoNewTransform<M, T::Service>,
|
||||
{
|
||||
let endpoint = ApplyNewService::new(mw, self.endpoint);
|
||||
Resource {
|
||||
endpoint,
|
||||
routes: self.routes,
|
||||
default: self.default,
|
||||
factory_ref: self.factory_ref,
|
||||
}
|
||||
}
|
||||
|
||||
#[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));
|
||||
/// Default resource to be used if no matching route could be found.
|
||||
pub fn default_resource<F, R, U>(mut self, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(Resource<P>) -> R,
|
||||
R: IntoNewService<U>,
|
||||
U: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse,
|
||||
Error = (),
|
||||
> + 'static,
|
||||
{
|
||||
// create and configure default resource
|
||||
self.default = Rc::new(RefCell::new(Some(Rc::new(Box::new(
|
||||
DefaultNewService::new(f(Resource::new()).into_new_service()),
|
||||
)))));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn get_default(
|
||||
&self,
|
||||
) -> Rc<RefCell<Option<Rc<HttpDefaultNewService<ServiceRequest<P>, ServiceResponse>>>>>
|
||||
{
|
||||
self.default.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T> IntoNewService<T> for Resource<P, T>
|
||||
where
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse,
|
||||
Error = (),
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
fn into_new_service(self) -> T {
|
||||
*self.factory_ref.borrow_mut() = Some(ResourceFactory {
|
||||
routes: self.routes,
|
||||
default: self.default,
|
||||
});
|
||||
|
||||
self.endpoint
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResourceFactory<P> {
|
||||
routes: Vec<Route<P>>,
|
||||
default: Rc<
|
||||
RefCell<Option<Rc<HttpDefaultNewService<ServiceRequest<P>, ServiceResponse>>>>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl<P> NewService for ResourceFactory<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = ();
|
||||
type InitError = ();
|
||||
type Service = ResourceService<P>;
|
||||
type Future = CreateResourceService<P>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
let default_fut = if let Some(ref default) = *self.default.borrow() {
|
||||
Some(default.new_service(&()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
CreateResourceService {
|
||||
fut: self
|
||||
.routes
|
||||
.iter()
|
||||
.map(|route| CreateRouteServiceItem::Future(route.new_service(&())))
|
||||
.collect(),
|
||||
default: None,
|
||||
default_fut,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum CreateRouteServiceItem<P> {
|
||||
Future(CreateRouteService<P>),
|
||||
Service(RouteService<P>),
|
||||
}
|
||||
|
||||
pub struct CreateResourceService<P> {
|
||||
fut: Vec<CreateRouteServiceItem<P>>,
|
||||
default: Option<HttpDefaultService<ServiceRequest<P>, ServiceResponse>>,
|
||||
default_fut: Option<
|
||||
Box<
|
||||
Future<
|
||||
Item = HttpDefaultService<ServiceRequest<P>, ServiceResponse>,
|
||||
Error = (),
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl<P> Future for CreateResourceService<P> {
|
||||
type Item = ResourceService<P>;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let mut done = true;
|
||||
|
||||
if let Some(ref mut fut) = self.default_fut {
|
||||
match fut.poll()? {
|
||||
Async::Ready(default) => self.default = Some(default),
|
||||
Async::NotReady => done = false,
|
||||
}
|
||||
}
|
||||
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)
|
||||
// poll http services
|
||||
for item in &mut self.fut {
|
||||
match item {
|
||||
CreateRouteServiceItem::Future(ref mut fut) => match fut.poll()? {
|
||||
Async::Ready(route) => {
|
||||
*item = CreateRouteServiceItem::Service(route)
|
||||
}
|
||||
Async::NotReady => {
|
||||
done = false;
|
||||
}
|
||||
},
|
||||
CreateRouteServiceItem::Service(_) => continue,
|
||||
};
|
||||
}
|
||||
|
||||
if done {
|
||||
let routes = self
|
||||
.fut
|
||||
.drain(..)
|
||||
.map(|item| match item {
|
||||
CreateRouteServiceItem::Service(service) => service,
|
||||
CreateRouteServiceItem::Future(_) => unreachable!(),
|
||||
})
|
||||
.collect();
|
||||
Ok(Async::Ready(ResourceService {
|
||||
routes,
|
||||
default: self.default.take(),
|
||||
}))
|
||||
} else {
|
||||
(&self.routes[id.0]).compose(req.clone(), Rc::clone(&self.middlewares))
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Default resource
|
||||
pub struct DefaultResource<S>(Rc<Resource<S>>);
|
||||
pub struct ResourceService<P> {
|
||||
routes: Vec<RouteService<P>>,
|
||||
default: Option<HttpDefaultService<ServiceRequest<P>, ServiceResponse>>,
|
||||
}
|
||||
|
||||
impl<S> Deref for DefaultResource<S> {
|
||||
type Target = Resource<S>;
|
||||
impl<P> Service for ResourceService<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = ();
|
||||
type Future = Either<
|
||||
Box<Future<Item = ServiceResponse, Error = ()>>,
|
||||
Either<
|
||||
Box<Future<Item = Self::Response, Error = Self::Error>>,
|
||||
FutureResult<Self::Response, Self::Error>,
|
||||
>,
|
||||
>;
|
||||
|
||||
fn deref(&self) -> &Resource<S> {
|
||||
self.0.as_ref()
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
|
||||
for route in self.routes.iter_mut() {
|
||||
if route.check(&mut req) {
|
||||
return Either::A(route.call(req));
|
||||
}
|
||||
}
|
||||
if let Some(ref mut default) = self.default {
|
||||
Either::B(Either::A(default.call(req)))
|
||||
} else {
|
||||
let req = req.into_request();
|
||||
Either::B(Either::B(ok(ServiceResponse::new(
|
||||
req,
|
||||
Response::NotFound().finish(),
|
||||
))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Clone for DefaultResource<S> {
|
||||
fn clone(&self) -> Self {
|
||||
DefaultResource(self.0.clone())
|
||||
#[doc(hidden)]
|
||||
pub struct ResourceEndpoint<P> {
|
||||
factory: Rc<RefCell<Option<ResourceFactory<P>>>>,
|
||||
}
|
||||
|
||||
impl<P> ResourceEndpoint<P> {
|
||||
fn new(factory: Rc<RefCell<Option<ResourceFactory<P>>>>) -> Self {
|
||||
ResourceEndpoint { factory }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> From<Resource<S>> for DefaultResource<S> {
|
||||
fn from(res: Resource<S>) -> Self {
|
||||
DefaultResource(Rc::new(res))
|
||||
impl<P> NewService for ResourceEndpoint<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = ();
|
||||
type InitError = ();
|
||||
type Service = ResourceService<P>;
|
||||
type Future = CreateResourceService<P>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
self.factory.borrow_mut().as_mut().unwrap().new_service(&())
|
||||
}
|
||||
}
|
||||
|
259
src/responder.rs
Normal file
259
src/responder.rs
Normal file
@ -0,0 +1,259 @@
|
||||
use actix_http::dev::ResponseBuilder;
|
||||
use actix_http::http::StatusCode;
|
||||
use actix_http::{Error, Response};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::future::{err, ok, Either as EitherFuture, FutureResult};
|
||||
use futures::{Future, Poll};
|
||||
|
||||
use crate::request::HttpRequest;
|
||||
|
||||
/// Trait implemented by types that generate http responses.
|
||||
///
|
||||
/// Types that implement this trait can be used as the return type of a handler.
|
||||
pub trait Responder {
|
||||
/// The associated error which can be returned.
|
||||
type Error: Into<Error>;
|
||||
|
||||
/// The future response value.
|
||||
type Future: Future<Item = Response, Error = Self::Error>;
|
||||
|
||||
/// Convert itself to `AsyncResult` or `Error`.
|
||||
fn respond_to(self, req: &HttpRequest) -> Self::Future;
|
||||
}
|
||||
|
||||
impl Responder for Response {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
|
||||
#[inline]
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Responder for Option<T>
|
||||
where
|
||||
T: Responder,
|
||||
{
|
||||
type Error = T::Error;
|
||||
type Future = EitherFuture<T::Future, FutureResult<Response, T::Error>>;
|
||||
|
||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
||||
match self {
|
||||
Some(t) => EitherFuture::A(t.respond_to(req)),
|
||||
None => EitherFuture::B(ok(Response::build(StatusCode::NOT_FOUND).finish())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Responder for Result<T, E>
|
||||
where
|
||||
T: Responder,
|
||||
E: Into<Error>,
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = EitherFuture<ResponseFuture<T::Future>, FutureResult<Response, Error>>;
|
||||
|
||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
||||
match self {
|
||||
Ok(val) => EitherFuture::A(ResponseFuture::new(val.respond_to(req))),
|
||||
Err(e) => EitherFuture::B(err(e.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for ResponseBuilder {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
|
||||
#[inline]
|
||||
fn respond_to(mut self, _: &HttpRequest) -> Self::Future {
|
||||
ok(self.finish())
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for &'static str {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
ok(Response::build(StatusCode::OK)
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
.body(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for &'static [u8] {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
ok(Response::build(StatusCode::OK)
|
||||
.content_type("application/octet-stream")
|
||||
.body(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for String {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
ok(Response::build(StatusCode::OK)
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
.body(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Responder for &'a String {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
ok(Response::build(StatusCode::OK)
|
||||
.content_type("text/plain; charset=utf-8")
|
||||
.body(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for Bytes {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
ok(Response::build(StatusCode::OK)
|
||||
.content_type("application/octet-stream")
|
||||
.body(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for BytesMut {
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Response, Error>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> Self::Future {
|
||||
ok(Response::build(StatusCode::OK)
|
||||
.content_type("application/octet-stream")
|
||||
.body(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// Combines two different responder types into a single type
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// # use futures::future::Future;
|
||||
/// use actix_web::{AsyncResponder, Either, Error, Request, Response};
|
||||
/// use futures::future::result;
|
||||
///
|
||||
/// type RegisterResult =
|
||||
/// Either<Response, Box<Future<Item = Response, Error = Error>>>;
|
||||
///
|
||||
/// fn index(req: Request) -> RegisterResult {
|
||||
/// if is_a_variant() {
|
||||
/// // <- choose variant A
|
||||
/// Either::A(Response::BadRequest().body("Bad data"))
|
||||
/// } else {
|
||||
/// Either::B(
|
||||
/// // <- variant B
|
||||
/// result(Ok(Response::Ok()
|
||||
/// .content_type("text/html")
|
||||
/// .body("Hello!")))
|
||||
/// .responder(),
|
||||
/// )
|
||||
/// }
|
||||
/// }
|
||||
/// # fn is_a_variant() -> bool { true }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
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 Error = Error;
|
||||
type Future = EitherResponder<A::Future, B::Future>;
|
||||
|
||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
||||
match self {
|
||||
Either::A(a) => EitherResponder::A(a.respond_to(req)),
|
||||
Either::B(b) => EitherResponder::B(b.respond_to(req)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum EitherResponder<A, B>
|
||||
where
|
||||
A: Future<Item = Response>,
|
||||
A::Error: Into<Error>,
|
||||
B: Future<Item = Response>,
|
||||
B::Error: Into<Error>,
|
||||
{
|
||||
A(A),
|
||||
B(B),
|
||||
}
|
||||
|
||||
impl<A, B> Future for EitherResponder<A, B>
|
||||
where
|
||||
A: Future<Item = Response>,
|
||||
A::Error: Into<Error>,
|
||||
B: Future<Item = Response>,
|
||||
B::Error: Into<Error>,
|
||||
{
|
||||
type Item = Response;
|
||||
type Error = Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self {
|
||||
EitherResponder::A(ref mut fut) => Ok(fut.poll().map_err(|e| e.into())?),
|
||||
EitherResponder::B(ref mut fut) => Ok(fut.poll().map_err(|e| e.into())?),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E> Responder for Box<Future<Item = I, Error = E>>
|
||||
where
|
||||
I: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = Box<Future<Item = Response, Error = Error>>;
|
||||
|
||||
#[inline]
|
||||
fn respond_to(self, req: &HttpRequest) -> Self::Future {
|
||||
let req = req.clone();
|
||||
Box::new(
|
||||
self.map_err(|e| e.into())
|
||||
.and_then(move |r| ResponseFuture(r.respond_to(&req))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResponseFuture<T>(T);
|
||||
|
||||
impl<T> ResponseFuture<T> {
|
||||
pub fn new(fut: T) -> Self {
|
||||
ResponseFuture(fut)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Future for ResponseFuture<T>
|
||||
where
|
||||
T: Future<Item = Response>,
|
||||
T::Error: Into<Error>,
|
||||
{
|
||||
type Item = Response;
|
||||
type Error = Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
Ok(self.0.poll().map_err(|e| e.into())?)
|
||||
}
|
||||
}
|
896
src/route.rs
896
src/route.rs
@ -1,68 +1,153 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
use futures::{Async, Future, Poll};
|
||||
use actix_http::{http::Method, Error, Response};
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::{Async, Future, IntoFuture, 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};
|
||||
use crate::filter::{self, Filter};
|
||||
use crate::handler::{AsyncFactory, AsyncHandle, Extract, Factory, FromRequest, Handle};
|
||||
use crate::responder::Responder;
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
|
||||
type BoxedRouteService<Req, Res> = Box<
|
||||
Service<
|
||||
Request = Req,
|
||||
Response = Res,
|
||||
Error = (),
|
||||
Future = Box<Future<Item = Res, Error = ()>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
type BoxedRouteNewService<Req, Res> = Box<
|
||||
NewService<
|
||||
Request = Req,
|
||||
Response = Res,
|
||||
Error = (),
|
||||
InitError = (),
|
||||
Service = BoxedRouteService<Req, Res>,
|
||||
Future = Box<Future<Item = BoxedRouteService<Req, Res>, Error = ()>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
/// 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>,
|
||||
pub struct Route<P> {
|
||||
service: BoxedRouteNewService<ServiceRequest<P>, ServiceResponse>,
|
||||
filters: Rc<Vec<Box<Filter>>>,
|
||||
}
|
||||
|
||||
impl<S: 'static> Default for Route<S> {
|
||||
fn default() -> Route<S> {
|
||||
Route {
|
||||
preds: Vec::new(),
|
||||
handler: InnerHandler::new(|_: &_| HttpResponse::new(StatusCode::NOT_FOUND)),
|
||||
impl<P: 'static> Route<P> {
|
||||
pub fn build() -> RouteBuilder<P> {
|
||||
RouteBuilder::new()
|
||||
}
|
||||
|
||||
pub fn get() -> RouteBuilder<P> {
|
||||
RouteBuilder::new().method(Method::GET)
|
||||
}
|
||||
|
||||
pub fn post() -> RouteBuilder<P> {
|
||||
RouteBuilder::new().method(Method::POST)
|
||||
}
|
||||
|
||||
pub fn put() -> RouteBuilder<P> {
|
||||
RouteBuilder::new().method(Method::PUT)
|
||||
}
|
||||
|
||||
pub fn delete() -> RouteBuilder<P> {
|
||||
RouteBuilder::new().method(Method::DELETE)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> NewService for Route<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = ();
|
||||
type InitError = ();
|
||||
type Service = RouteService<P>;
|
||||
type Future = CreateRouteService<P>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
CreateRouteService {
|
||||
fut: self.service.new_service(&()),
|
||||
filters: self.filters.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
type RouteFuture<P> = Box<
|
||||
Future<Item = BoxedRouteService<ServiceRequest<P>, ServiceResponse>, Error = ()>,
|
||||
>;
|
||||
|
||||
pub struct CreateRouteService<P> {
|
||||
fut: RouteFuture<P>,
|
||||
filters: Rc<Vec<Box<Filter>>>,
|
||||
}
|
||||
|
||||
impl<P> Future for CreateRouteService<P> {
|
||||
type Item = RouteService<P>;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll()? {
|
||||
Async::Ready(service) => Ok(Async::Ready(RouteService {
|
||||
service,
|
||||
filters: self.filters.clone(),
|
||||
})),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RouteService<P> {
|
||||
service: BoxedRouteService<ServiceRequest<P>, ServiceResponse>,
|
||||
filters: Rc<Vec<Box<Filter>>>,
|
||||
}
|
||||
|
||||
impl<P> RouteService<P> {
|
||||
pub fn check(&self, req: &mut ServiceRequest<P>) -> bool {
|
||||
for f in self.filters.iter() {
|
||||
if !f.check(req.request()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
self.handler.handle(req)
|
||||
impl<P> Service for RouteService<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = ();
|
||||
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn compose(
|
||||
&self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||
) -> AsyncResult<HttpResponse> {
|
||||
AsyncResult::future(Box::new(Compose::new(req, mws, self.handler.clone())))
|
||||
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||
self.service.call(req)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RouteBuilder<P> {
|
||||
filters: Vec<Box<Filter>>,
|
||||
_t: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: 'static> RouteBuilder<P> {
|
||||
fn new() -> RouteBuilder<P> {
|
||||
RouteBuilder {
|
||||
filters: Vec::new(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add match predicate to route.
|
||||
/// Add method match filter to the route.
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// # extern crate actix_web;
|
||||
/// # use actix_web::*;
|
||||
/// # fn main() {
|
||||
@ -75,41 +160,52 @@ impl<S: 'static> Route<S> {
|
||||
/// # .finish();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn filter<T: Predicate<S> + 'static>(&mut self, p: T) -> &mut Self {
|
||||
self.preds.push(Box::new(p));
|
||||
pub fn method(mut self, method: Method) -> Self {
|
||||
self.filters.push(Box::new(filter::Method(method)));
|
||||
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);
|
||||
/// Add filter to the route.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # 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<F: Filter + 'static>(&mut self, f: F) -> &mut Self {
|
||||
self.filters.push(Box::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
// pub fn map<T, U, F: IntoNewService<T>>(
|
||||
// self,
|
||||
// md: F,
|
||||
// ) -> RouteServiceBuilder<T, S, (), U>
|
||||
// where
|
||||
// T: NewService<
|
||||
// Request = HandlerRequest<S>,
|
||||
// Response = HandlerRequest<S, U>,
|
||||
// InitError = (),
|
||||
// >,
|
||||
// {
|
||||
// RouteServiceBuilder {
|
||||
// service: md.into_new_service(),
|
||||
// filters: self.filters,
|
||||
// _t: PhantomData,
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Set handler function, use request extractor for parameters.
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// # extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
@ -136,7 +232,7 @@ impl<S: 'static> Route<S> {
|
||||
///
|
||||
/// It is possible to use multiple extractors for one handler function.
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// # extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
@ -163,56 +259,25 @@ impl<S: 'static> Route<S> {
|
||||
/// ); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
pub fn with<T, F, R>(&mut self, handler: F)
|
||||
pub fn to<F, T, R>(self, handler: F) -> Route<P>
|
||||
where
|
||||
F: WithFactory<T, S, R> + 'static,
|
||||
F: Factory<T, R> + 'static,
|
||||
T: FromRequest<P> + '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));
|
||||
Route {
|
||||
service: Box::new(RouteNewService::new(
|
||||
Extract::new().and_then(Handle::new(handler).map_err(|_| panic!())),
|
||||
)),
|
||||
filters: Rc::new(self.filters),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set async handler function, use request extractor for parameters.
|
||||
/// Also this method needs to be used if your handler function returns
|
||||
/// `impl Future<>`
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// # extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
@ -237,430 +302,233 @@ impl<S: 'static> Route<S> {
|
||||
/// ); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn to_async<F, T, R>(self, handler: F) -> Route<P>
|
||||
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,
|
||||
F: AsyncFactory<T, R>,
|
||||
T: FromRequest<P> + 'static,
|
||||
R: IntoFuture + 'static,
|
||||
R::Item: Into<Response>,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
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,
|
||||
Route {
|
||||
service: Box::new(RouteNewService::new(
|
||||
Extract::new().and_then(AsyncHandle::new(handler).map_err(|_| panic!())),
|
||||
)),
|
||||
filters: Rc::new(self.filters),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
pub struct RouteServiceBuilder<P, T, U1, U2> {
|
||||
service: T,
|
||||
filters: Vec<Box<Filter>>,
|
||||
_t: PhantomData<(P, U1, U2)>,
|
||||
}
|
||||
|
||||
Compose { state, info }
|
||||
// impl<T, S: 'static, U1, U2> RouteServiceBuilder<T, S, U1, U2>
|
||||
// where
|
||||
// T: NewService<
|
||||
// Request = HandlerRequest<S, U1>,
|
||||
// Response = HandlerRequest<S, U2>,
|
||||
// Error = Error,
|
||||
// InitError = (),
|
||||
// >,
|
||||
// {
|
||||
// pub fn new<F: IntoNewService<T>>(factory: F) -> Self {
|
||||
// RouteServiceBuilder {
|
||||
// service: factory.into_new_service(),
|
||||
// filters: Vec::new(),
|
||||
// _t: PhantomData,
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Add method match filter to the 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 method(mut self, method: Method) -> Self {
|
||||
// self.filters.push(Box::new(filter::Method(method)));
|
||||
// self
|
||||
// }
|
||||
|
||||
// /// Add filter to the 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<F: Filter<S> + 'static>(&mut self, f: F) -> &mut Self {
|
||||
// self.filters.push(Box::new(f));
|
||||
// self
|
||||
// }
|
||||
|
||||
// pub fn map<T1, U3, F: IntoNewService<T1>>(
|
||||
// self,
|
||||
// md: F,
|
||||
// ) -> RouteServiceBuilder<
|
||||
// impl NewService<
|
||||
// Request = HandlerRequest<S, U1>,
|
||||
// Response = HandlerRequest<S, U3>,
|
||||
// Error = Error,
|
||||
// InitError = (),
|
||||
// >,
|
||||
// S,
|
||||
// U1,
|
||||
// U2,
|
||||
// >
|
||||
// where
|
||||
// T1: NewService<
|
||||
// Request = HandlerRequest<S, U2>,
|
||||
// Response = HandlerRequest<S, U3>,
|
||||
// InitError = (),
|
||||
// >,
|
||||
// T1::Error: Into<Error>,
|
||||
// {
|
||||
// RouteServiceBuilder {
|
||||
// service: self
|
||||
// .service
|
||||
// .and_then(md.into_new_service().map_err(|e| e.into())),
|
||||
// filters: self.filters,
|
||||
// _t: PhantomData,
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn to_async<F, P, R>(self, handler: F) -> Route<S>
|
||||
// where
|
||||
// F: AsyncFactory<S, U2, P, R>,
|
||||
// P: FromRequest<S> + 'static,
|
||||
// R: IntoFuture,
|
||||
// R::Item: Into<Response>,
|
||||
// R::Error: Into<Error>,
|
||||
// {
|
||||
// Route {
|
||||
// service: self
|
||||
// .service
|
||||
// .and_then(Extract::new(P::Config::default()))
|
||||
// .then(AsyncHandle::new(handler)),
|
||||
// filters: Rc::new(self.filters),
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn to<F, P, R>(self, handler: F) -> Route<S>
|
||||
// where
|
||||
// F: Factory<S, U2, P, R> + 'static,
|
||||
// P: FromRequest<S> + 'static,
|
||||
// R: Responder<S> + 'static,
|
||||
// {
|
||||
// Route {
|
||||
// service: Box::new(RouteNewService::new(
|
||||
// self.service
|
||||
// .and_then(Extract::new(P::Config::default()))
|
||||
// .and_then(Handle::new(handler)),
|
||||
// )),
|
||||
// filters: Rc::new(self.filters),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
struct RouteNewService<P, T>
|
||||
where
|
||||
T: NewService<Request = ServiceRequest<P>, Error = (Error, ServiceRequest<P>)>,
|
||||
{
|
||||
service: T,
|
||||
}
|
||||
|
||||
impl<P: 'static, T> RouteNewService<P, T>
|
||||
where
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse,
|
||||
Error = (Error, ServiceRequest<P>),
|
||||
>,
|
||||
T::Future: 'static,
|
||||
T::Service: 'static,
|
||||
<T::Service as Service>::Future: 'static,
|
||||
{
|
||||
pub fn new(service: T) -> Self {
|
||||
RouteNewService { service }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Future for Compose<S> {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
impl<P: 'static, T> NewService for RouteNewService<P, T>
|
||||
where
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse,
|
||||
Error = (Error, ServiceRequest<P>),
|
||||
>,
|
||||
T::Future: 'static,
|
||||
T::Service: 'static,
|
||||
<T::Service as Service>::Future: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = ();
|
||||
type InitError = ();
|
||||
type Service = BoxedRouteService<Self::Request, Self::Response>;
|
||||
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
Box::new(
|
||||
self.service
|
||||
.new_service(&())
|
||||
.map_err(|_| ())
|
||||
.and_then(|service| {
|
||||
let service: BoxedRouteService<_, _> =
|
||||
Box::new(RouteServiceWrapper { service });
|
||||
Ok(service)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Middlewares start executor
|
||||
struct StartMiddlewares<S> {
|
||||
fut: Option<Fut>,
|
||||
_s: PhantomData<S>,
|
||||
struct RouteServiceWrapper<P, T: Service<Request = ServiceRequest<P>>> {
|
||||
service: T,
|
||||
}
|
||||
|
||||
type Fut = Box<Future<Item = Option<HttpResponse>, Error = Error>>;
|
||||
impl<P, T> Service for RouteServiceWrapper<P, T>
|
||||
where
|
||||
T::Future: 'static,
|
||||
T: Service<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse,
|
||||
Error = (Error, ServiceRequest<P>),
|
||||
>,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = ();
|
||||
type Future = Box<Future<Item = Self::Response, Error = Self::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_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready().map_err(|_| ())
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
Box::new(self.service.call(req).then(|res| match res {
|
||||
Ok(res) => Ok(res),
|
||||
Err((err, req)) => Ok(req.error_response(err)),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
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,383 +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::error::AcceptorError;
|
||||
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> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T> ServerMessageAcceptor<T>
|
||||
where
|
||||
T: NewService<Request = net::TcpStream>,
|
||||
{
|
||||
pub(crate) fn new(inner: T) -> Self {
|
||||
ServerMessageAcceptor { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> NewService for ServerMessageAcceptor<T>
|
||||
where
|
||||
T: NewService<Request = net::TcpStream>,
|
||||
{
|
||||
type Request = ServerMessage;
|
||||
type Response = ();
|
||||
type Error = T::Error;
|
||||
type InitError = T::InitError;
|
||||
type Service = ServerMessageAcceptorService<T::Service>;
|
||||
type Future = ServerMessageAcceptorResponse<T>;
|
||||
|
||||
fn new_service(&self) -> Self::Future {
|
||||
ServerMessageAcceptorResponse {
|
||||
fut: self.inner.new_service(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ServerMessageAcceptorResponse<T>
|
||||
where
|
||||
T: NewService<Request = net::TcpStream>,
|
||||
{
|
||||
fut: T::Future,
|
||||
}
|
||||
|
||||
impl<T> Future for ServerMessageAcceptorResponse<T>
|
||||
where
|
||||
T: NewService<Request = net::TcpStream>,
|
||||
{
|
||||
type Item = ServerMessageAcceptorService<T::Service>;
|
||||
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,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ServerMessageAcceptorService<T> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T> Service for ServerMessageAcceptorService<T>
|
||||
where
|
||||
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,134 +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, StreamConfiguration};
|
||||
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(
|
||||
TcpAcceptor::new(AcceptorTimeout::new(
|
||||
client_timeout,
|
||||
acceptor.create(),
|
||||
)).map_err(|_| ())
|
||||
.map_init_err(|_| ())
|
||||
.and_then(StreamConfiguration::new().nodelay(true))
|
||||
.and_then(
|
||||
HttpService::new(settings)
|
||||
.map_init_err(|_| ())
|
||||
.map_err(|_| ()),
|
||||
),
|
||||
))
|
||||
} else {
|
||||
Either::A(ServerMessageAcceptor::new(
|
||||
TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service))
|
||||
.map_err(|_| ())
|
||||
.map_init_err(|_| ())
|
||||
.and_then(StreamConfiguration::new().nodelay(true))
|
||||
.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,300 +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 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> {
|
||||
// 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,
|
||||
{
|
||||
proto: HttpProtocol<T, H>,
|
||||
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,
|
||||
proto: HttpProtocol::Unknown(settings, io, BytesMut::with_capacity(8192)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, H> Future for HttpChannel<T, H>
|
||||
where
|
||||
T: IoStream,
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = HttpDispatchError;
|
||||
|
||||
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(&mut self.proto, HttpProtocol::None);
|
||||
if let HttpProtocol::Unknown(settings, io, buf) = proto {
|
||||
self.proto = 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"),
|
||||
}
|
||||
}
|
||||
|
||||
let mut is_eof = false;
|
||||
let kind = match self.proto {
|
||||
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(&mut self.proto, HttpProtocol::None);
|
||||
if let HttpProtocol::Unknown(settings, io, buf) = proto {
|
||||
match kind {
|
||||
ProtocolKind::Http1 => {
|
||||
self.proto = HttpProtocol::H1(h1::Http1Dispatcher::new(
|
||||
settings,
|
||||
io,
|
||||
buf,
|
||||
is_eof,
|
||||
self.ka_timeout.take(),
|
||||
));
|
||||
return self.poll();
|
||||
}
|
||||
ProtocolKind::Http2 => {
|
||||
self.proto = 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,
|
||||
{
|
||||
proto: HttpProtocol<T, H>,
|
||||
}
|
||||
|
||||
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 {
|
||||
proto: HttpProtocol::H1(h1::Http1Dispatcher::new(
|
||||
settings,
|
||||
io,
|
||||
BytesMut::with_capacity(8192),
|
||||
false,
|
||||
None,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, H> Future for H1Channel<T, H>
|
||||
where
|
||||
T: IoStream,
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = HttpDispatchError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.proto {
|
||||
HttpProtocol::H1(ref mut h1) => h1.poll(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
use std::io;
|
||||
|
||||
use futures::{Async, Poll};
|
||||
use http2;
|
||||
|
||||
use super::{helpers, HttpHandlerTask, Writer};
|
||||
use http::{StatusCode, Version};
|
||||
use Error;
|
||||
|
||||
/// Errors produced by `AcceptorError` service.
|
||||
#[derive(Debug)]
|
||||
pub enum AcceptorError<T> {
|
||||
/// The inner service error
|
||||
Service(T),
|
||||
|
||||
/// Io specific error
|
||||
Io(io::Error),
|
||||
|
||||
/// The request did not complete within the specified timeout.
|
||||
Timeout,
|
||||
}
|
||||
|
||||
#[derive(Fail, Debug)]
|
||||
/// A set of errors that can occur during dispatching http requests
|
||||
pub enum HttpDispatchError {
|
||||
/// Application error
|
||||
#[fail(display = "Application specific error: {}", _0)]
|
||||
App(Error),
|
||||
|
||||
/// An `io::Error` that occurred while trying to read or write to a network
|
||||
/// stream.
|
||||
#[fail(display = "IO error: {}", _0)]
|
||||
Io(io::Error),
|
||||
|
||||
/// The first request did not complete within the specified timeout.
|
||||
#[fail(display = "The first request did not complete within the specified timeout")]
|
||||
SlowRequestTimeout,
|
||||
|
||||
/// Shutdown timeout
|
||||
#[fail(display = "Connection shutdown timeout")]
|
||||
ShutdownTimeout,
|
||||
|
||||
/// HTTP2 error
|
||||
#[fail(display = "HTTP2 error: {}", _0)]
|
||||
Http2(http2::Error),
|
||||
|
||||
/// Payload is not consumed
|
||||
#[fail(display = "Task is completed but request's payload is not consumed")]
|
||||
PayloadIsNotConsumed,
|
||||
|
||||
/// Malformed request
|
||||
#[fail(display = "Malformed request")]
|
||||
MalformedRequest,
|
||||
|
||||
/// Internal error
|
||||
#[fail(display = "Internal error")]
|
||||
InternalError,
|
||||
|
||||
/// Unknown error
|
||||
#[fail(display = "Unknown error")]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<Error> for HttpDispatchError {
|
||||
fn from(err: Error) -> Self {
|
||||
HttpDispatchError::App(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for HttpDispatchError {
|
||||
fn from(err: io::Error) -> Self {
|
||||
HttpDispatchError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<http2::Error> for HttpDispatchError {
|
||||
fn from(err: http2::Error) -> Self {
|
||||
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))
|
||||
}
|
||||
}
|
1353
src/server/h1.rs
1353
src/server/h1.rs
File diff suppressed because it is too large
Load Diff
@ -1,541 +0,0 @@
|
||||
use std::{io, mem};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{Async, Poll};
|
||||
use httparse;
|
||||
|
||||
use super::message::{MessageFlags, Request};
|
||||
use super::settings::ServiceConfig;
|
||||
use error::ParseError;
|
||||
use http::header::{HeaderName, HeaderValue};
|
||||
use http::{header, HttpTryFrom, Method, Uri, Version};
|
||||
use uri::Url;
|
||||
|
||||
const MAX_BUFFER_SIZE: usize = 131_072;
|
||||
const MAX_HEADERS: usize = 96;
|
||||
|
||||
pub(crate) struct H1Decoder {
|
||||
decoder: Option<EncodingDecoder>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Message {
|
||||
Message {
|
||||
msg: Request,
|
||||
payload: bool,
|
||||
expect: bool,
|
||||
},
|
||||
Chunk(Bytes),
|
||||
Eof,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum DecoderError {
|
||||
Io(io::Error),
|
||||
Error(ParseError),
|
||||
}
|
||||
|
||||
impl From<io::Error> for DecoderError {
|
||||
fn from(err: io::Error) -> DecoderError {
|
||||
DecoderError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl H1Decoder {
|
||||
pub fn new() -> H1Decoder {
|
||||
H1Decoder { decoder: None }
|
||||
}
|
||||
|
||||
pub fn decode<H>(
|
||||
&mut self,
|
||||
src: &mut BytesMut,
|
||||
settings: &ServiceConfig<H>,
|
||||
) -> Result<Option<Message>, DecoderError> {
|
||||
// read payload
|
||||
if self.decoder.is_some() {
|
||||
match self.decoder.as_mut().unwrap().decode(src)? {
|
||||
Async::Ready(Some(bytes)) => return Ok(Some(Message::Chunk(bytes))),
|
||||
Async::Ready(None) => {
|
||||
self.decoder.take();
|
||||
return Ok(Some(Message::Eof));
|
||||
}
|
||||
Async::NotReady => return Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
match self
|
||||
.parse_message(src, settings)
|
||||
.map_err(DecoderError::Error)?
|
||||
{
|
||||
Async::Ready((msg, expect, decoder)) => {
|
||||
self.decoder = decoder;
|
||||
Ok(Some(Message::Message {
|
||||
msg,
|
||||
expect,
|
||||
payload: self.decoder.is_some(),
|
||||
}))
|
||||
}
|
||||
Async::NotReady => {
|
||||
if src.len() >= MAX_BUFFER_SIZE {
|
||||
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
|
||||
Err(DecoderError::Error(ParseError::TooLarge))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_message<H>(
|
||||
&self,
|
||||
buf: &mut BytesMut,
|
||||
settings: &ServiceConfig<H>,
|
||||
) -> Poll<(Request, bool, Option<EncodingDecoder>), ParseError> {
|
||||
// Parse http message
|
||||
let mut has_upgrade = false;
|
||||
let mut chunked = false;
|
||||
let mut content_length = None;
|
||||
let mut expect_continue = false;
|
||||
|
||||
let msg = {
|
||||
// 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, method, path, version, headers_len) = {
|
||||
let mut parsed: [httparse::Header; MAX_HEADERS] =
|
||||
unsafe { mem::uninitialized() };
|
||||
|
||||
let mut req = httparse::Request::new(&mut parsed);
|
||||
match req.parse(buf)? {
|
||||
httparse::Status::Complete(len) => {
|
||||
let method = Method::from_bytes(req.method.unwrap().as_bytes())
|
||||
.map_err(|_| ParseError::Method)?;
|
||||
let path = Url::new(Uri::try_from(req.path.unwrap())?);
|
||||
let version = if req.version.unwrap() == 1 {
|
||||
Version::HTTP_11
|
||||
} else {
|
||||
Version::HTTP_10
|
||||
};
|
||||
HeaderIndex::record(buf, req.headers, &mut headers);
|
||||
|
||||
(len, method, path, version, req.headers.len())
|
||||
}
|
||||
httparse::Status::Partial => return Ok(Async::NotReady),
|
||||
}
|
||||
};
|
||||
|
||||
let slice = buf.split_to(len).freeze();
|
||||
|
||||
// convert headers
|
||||
let mut msg = settings.get_request();
|
||||
{
|
||||
let inner = msg.inner_mut();
|
||||
inner
|
||||
.flags
|
||||
.get_mut()
|
||||
.set(MessageFlags::KEEPALIVE, version != Version::HTTP_10);
|
||||
|
||||
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),
|
||||
)
|
||||
};
|
||||
match name {
|
||||
header::CONTENT_LENGTH => {
|
||||
if let Ok(s) = value.to_str() {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
content_length = Some(len);
|
||||
} else {
|
||||
debug!("illegal Content-Length: {:?}", len);
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
} else {
|
||||
debug!("illegal Content-Length: {:?}", len);
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
}
|
||||
// transfer-encoding
|
||||
header::TRANSFER_ENCODING => {
|
||||
if let Ok(s) = value.to_str().map(|s| s.trim()) {
|
||||
chunked = s.eq_ignore_ascii_case("chunked");
|
||||
} else {
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
}
|
||||
// connection keep-alive state
|
||||
header::CONNECTION => {
|
||||
let ka = if let Ok(conn) =
|
||||
value.to_str().map(|conn| conn.trim())
|
||||
{
|
||||
if version == Version::HTTP_10
|
||||
&& conn.eq_ignore_ascii_case("keep-alive")
|
||||
{
|
||||
true
|
||||
} else {
|
||||
version == Version::HTTP_11
|
||||
&& !(conn.eq_ignore_ascii_case("close")
|
||||
|| conn.eq_ignore_ascii_case("upgrade"))
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
inner.flags.get_mut().set(MessageFlags::KEEPALIVE, ka);
|
||||
}
|
||||
header::UPGRADE => {
|
||||
has_upgrade = true;
|
||||
// check content-length, some clients (dart)
|
||||
// sends "content-length: 0" with websocket upgrade
|
||||
if let Ok(val) = value.to_str().map(|val| val.trim()) {
|
||||
if val.eq_ignore_ascii_case("websocket") {
|
||||
content_length = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
header::EXPECT => {
|
||||
if value == "100-continue" {
|
||||
expect_continue = true
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
inner.headers.append(name, value);
|
||||
} else {
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
}
|
||||
|
||||
inner.url = path;
|
||||
inner.method = method;
|
||||
inner.version = version;
|
||||
}
|
||||
msg
|
||||
};
|
||||
|
||||
// https://tools.ietf.org/html/rfc7230#section-3.3.3
|
||||
let decoder = if chunked {
|
||||
// Chunked encoding
|
||||
Some(EncodingDecoder::chunked())
|
||||
} else if let Some(len) = content_length {
|
||||
// Content-Length
|
||||
Some(EncodingDecoder::length(len))
|
||||
} else if has_upgrade || msg.inner.method == Method::CONNECT {
|
||||
// upgrade(websocket) or connect
|
||||
Some(EncodingDecoder::eof())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Async::Ready((msg, expect_continue, decoder)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct HeaderIndex {
|
||||
pub(crate) name: (usize, usize),
|
||||
pub(crate) value: (usize, usize),
|
||||
}
|
||||
|
||||
impl HeaderIndex {
|
||||
pub(crate) fn record(
|
||||
bytes: &[u8],
|
||||
headers: &[httparse::Header],
|
||||
indices: &mut [HeaderIndex],
|
||||
) {
|
||||
let bytes_ptr = bytes.as_ptr() as usize;
|
||||
for (header, indices) in headers.iter().zip(indices.iter_mut()) {
|
||||
let name_start = header.name.as_ptr() as usize - bytes_ptr;
|
||||
let name_end = name_start + header.name.len();
|
||||
indices.name = (name_start, name_end);
|
||||
let value_start = header.value.as_ptr() as usize - bytes_ptr;
|
||||
let value_end = value_start + header.value.len();
|
||||
indices.value = (value_start, value_end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decoders to handle different Transfer-Encodings.
|
||||
///
|
||||
/// If a message body does not include a Transfer-Encoding, it *should*
|
||||
/// include a Content-Length header.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct EncodingDecoder {
|
||||
kind: Kind,
|
||||
}
|
||||
|
||||
impl EncodingDecoder {
|
||||
pub fn length(x: u64) -> EncodingDecoder {
|
||||
EncodingDecoder {
|
||||
kind: Kind::Length(x),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chunked() -> EncodingDecoder {
|
||||
EncodingDecoder {
|
||||
kind: Kind::Chunked(ChunkedState::Size, 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eof() -> EncodingDecoder {
|
||||
EncodingDecoder {
|
||||
kind: Kind::Eof(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum Kind {
|
||||
/// A Reader used when a Content-Length header is passed with a positive
|
||||
/// integer.
|
||||
Length(u64),
|
||||
/// A Reader used when Transfer-Encoding is `chunked`.
|
||||
Chunked(ChunkedState, u64),
|
||||
/// A Reader used for responses that don't indicate a length or chunked.
|
||||
///
|
||||
/// Note: This should only used for `Response`s. It is illegal for a
|
||||
/// `Request` to be made with both `Content-Length` and
|
||||
/// `Transfer-Encoding: chunked` missing, as explained from the spec:
|
||||
///
|
||||
/// > If a Transfer-Encoding header field is present in a response and
|
||||
/// > the chunked transfer coding is not the final encoding, the
|
||||
/// > message body length is determined by reading the connection until
|
||||
/// > it is closed by the server. If a Transfer-Encoding header field
|
||||
/// > is present in a request and the chunked transfer coding is not
|
||||
/// > the final encoding, the message body length cannot be determined
|
||||
/// > reliably; the server MUST respond with the 400 (Bad Request)
|
||||
/// > status code and then close the connection.
|
||||
Eof(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum ChunkedState {
|
||||
Size,
|
||||
SizeLws,
|
||||
Extension,
|
||||
SizeLf,
|
||||
Body,
|
||||
BodyCr,
|
||||
BodyLf,
|
||||
EndCr,
|
||||
EndLf,
|
||||
End,
|
||||
}
|
||||
|
||||
impl EncodingDecoder {
|
||||
pub fn decode(&mut self, body: &mut BytesMut) -> Poll<Option<Bytes>, io::Error> {
|
||||
match self.kind {
|
||||
Kind::Length(ref mut remaining) => {
|
||||
if *remaining == 0 {
|
||||
Ok(Async::Ready(None))
|
||||
} else {
|
||||
if body.is_empty() {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
let len = body.len() as u64;
|
||||
let buf;
|
||||
if *remaining > len {
|
||||
buf = body.take().freeze();
|
||||
*remaining -= len;
|
||||
} else {
|
||||
buf = body.split_to(*remaining as usize).freeze();
|
||||
*remaining = 0;
|
||||
}
|
||||
trace!("Length read: {}", buf.len());
|
||||
Ok(Async::Ready(Some(buf)))
|
||||
}
|
||||
}
|
||||
Kind::Chunked(ref mut state, ref mut size) => {
|
||||
loop {
|
||||
let mut buf = None;
|
||||
// advances the chunked state
|
||||
*state = try_ready!(state.step(body, size, &mut buf));
|
||||
if *state == ChunkedState::End {
|
||||
trace!("End of chunked stream");
|
||||
return Ok(Async::Ready(None));
|
||||
}
|
||||
if let Some(buf) = buf {
|
||||
return Ok(Async::Ready(Some(buf)));
|
||||
}
|
||||
if body.is_empty() {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
}
|
||||
}
|
||||
Kind::Eof(ref mut is_eof) => {
|
||||
if *is_eof {
|
||||
Ok(Async::Ready(None))
|
||||
} else if !body.is_empty() {
|
||||
Ok(Async::Ready(Some(body.take().freeze())))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! byte (
|
||||
($rdr:ident) => ({
|
||||
if $rdr.len() > 0 {
|
||||
let b = $rdr[0];
|
||||
$rdr.split_to(1);
|
||||
b
|
||||
} else {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
impl ChunkedState {
|
||||
fn step(
|
||||
&self,
|
||||
body: &mut BytesMut,
|
||||
size: &mut u64,
|
||||
buf: &mut Option<Bytes>,
|
||||
) -> Poll<ChunkedState, io::Error> {
|
||||
use self::ChunkedState::*;
|
||||
match *self {
|
||||
Size => ChunkedState::read_size(body, size),
|
||||
SizeLws => ChunkedState::read_size_lws(body),
|
||||
Extension => ChunkedState::read_extension(body),
|
||||
SizeLf => ChunkedState::read_size_lf(body, size),
|
||||
Body => ChunkedState::read_body(body, size, buf),
|
||||
BodyCr => ChunkedState::read_body_cr(body),
|
||||
BodyLf => ChunkedState::read_body_lf(body),
|
||||
EndCr => ChunkedState::read_end_cr(body),
|
||||
EndLf => ChunkedState::read_end_lf(body),
|
||||
End => Ok(Async::Ready(ChunkedState::End)),
|
||||
}
|
||||
}
|
||||
fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll<ChunkedState, io::Error> {
|
||||
let radix = 16;
|
||||
match byte!(rdr) {
|
||||
b @ b'0'...b'9' => {
|
||||
*size *= radix;
|
||||
*size += u64::from(b - b'0');
|
||||
}
|
||||
b @ b'a'...b'f' => {
|
||||
*size *= radix;
|
||||
*size += u64::from(b + 10 - b'a');
|
||||
}
|
||||
b @ b'A'...b'F' => {
|
||||
*size *= radix;
|
||||
*size += u64::from(b + 10 - b'A');
|
||||
}
|
||||
b'\t' | b' ' => return Ok(Async::Ready(ChunkedState::SizeLws)),
|
||||
b';' => return Ok(Async::Ready(ChunkedState::Extension)),
|
||||
b'\r' => return Ok(Async::Ready(ChunkedState::SizeLf)),
|
||||
_ => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid chunk size line: Invalid Size",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(ChunkedState::Size))
|
||||
}
|
||||
fn read_size_lws(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
|
||||
trace!("read_size_lws");
|
||||
match byte!(rdr) {
|
||||
// LWS can follow the chunk size, but no more digits can come
|
||||
b'\t' | b' ' => Ok(Async::Ready(ChunkedState::SizeLws)),
|
||||
b';' => Ok(Async::Ready(ChunkedState::Extension)),
|
||||
b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid chunk size linear white space",
|
||||
)),
|
||||
}
|
||||
}
|
||||
fn read_extension(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
|
||||
match byte!(rdr) {
|
||||
b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)),
|
||||
_ => Ok(Async::Ready(ChunkedState::Extension)), // no supported extensions
|
||||
}
|
||||
}
|
||||
fn read_size_lf(
|
||||
rdr: &mut BytesMut,
|
||||
size: &mut u64,
|
||||
) -> Poll<ChunkedState, io::Error> {
|
||||
match byte!(rdr) {
|
||||
b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)),
|
||||
b'\n' if *size == 0 => Ok(Async::Ready(ChunkedState::EndCr)),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid chunk size LF",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_body(
|
||||
rdr: &mut BytesMut,
|
||||
rem: &mut u64,
|
||||
buf: &mut Option<Bytes>,
|
||||
) -> Poll<ChunkedState, io::Error> {
|
||||
trace!("Chunked read, remaining={:?}", rem);
|
||||
|
||||
let len = rdr.len() as u64;
|
||||
if len == 0 {
|
||||
Ok(Async::Ready(ChunkedState::Body))
|
||||
} else {
|
||||
let slice;
|
||||
if *rem > len {
|
||||
slice = rdr.take().freeze();
|
||||
*rem -= len;
|
||||
} else {
|
||||
slice = rdr.split_to(*rem as usize).freeze();
|
||||
*rem = 0;
|
||||
}
|
||||
*buf = Some(slice);
|
||||
if *rem > 0 {
|
||||
Ok(Async::Ready(ChunkedState::Body))
|
||||
} else {
|
||||
Ok(Async::Ready(ChunkedState::BodyCr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_body_cr(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
|
||||
match byte!(rdr) {
|
||||
b'\r' => Ok(Async::Ready(ChunkedState::BodyLf)),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid chunk body CR",
|
||||
)),
|
||||
}
|
||||
}
|
||||
fn read_body_lf(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
|
||||
match byte!(rdr) {
|
||||
b'\n' => Ok(Async::Ready(ChunkedState::Size)),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid chunk body LF",
|
||||
)),
|
||||
}
|
||||
}
|
||||
fn read_end_cr(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
|
||||
match byte!(rdr) {
|
||||
b'\r' => Ok(Async::Ready(ChunkedState::EndLf)),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid chunk end CR",
|
||||
)),
|
||||
}
|
||||
}
|
||||
fn read_end_lf(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
|
||||
match byte!(rdr) {
|
||||
b'\n' => Ok(Async::Ready(ChunkedState::End)),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid chunk end LF",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,364 +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
|
||||
let mut len_is_set = true;
|
||||
match info.length {
|
||||
ResponseLength::Chunked => {
|
||||
buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\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::Zero => {
|
||||
len_is_set = false;
|
||||
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 => (),
|
||||
ResponseLength::Zero => {
|
||||
len_is_set = true;
|
||||
}
|
||||
_ => 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);
|
||||
}
|
||||
if !len_is_set {
|
||||
buffer.extend_from_slice(b"content-length: 0\r\n")
|
||||
}
|
||||
|
||||
// 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()?)
|
||||
}
|
||||
}
|
||||
}
|
472
src/server/h2.rs
472
src/server/h2.rs
@ -1,472 +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_0001;
|
||||
const SHUTDOWN = 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>>,
|
||||
extensions: Option<Rc<Extensions>>,
|
||||
ka_expire: Instant,
|
||||
ka_timer: Option<Delay>,
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// keep-alive timeout
|
||||
let (ka_expire, ka_timer) = if let Some(delay) = keepalive_timer {
|
||||
(delay.deadline(), Some(delay))
|
||||
} else if let Some(delay) = settings.keep_alive_timer() {
|
||||
(delay.deadline(), Some(delay))
|
||||
} else {
|
||||
(settings.now(), None)
|
||||
};
|
||||
|
||||
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,
|
||||
ka_expire,
|
||||
ka_timer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll(&mut self) -> Poll<(), HttpDispatchError> {
|
||||
self.poll_keepalive()?;
|
||||
|
||||
// server
|
||||
if let State::Connection(ref mut conn) = self.state {
|
||||
loop {
|
||||
// shutdown connection
|
||||
if self.flags.contains(Flags::SHUTDOWN) {
|
||||
return conn.poll_close().map_err(|e| e.into());
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// update keep-alive expire
|
||||
if self.ka_timer.is_some() {
|
||||
if let Some(expire) = self.settings.keep_alive_expire() {
|
||||
self.ka_expire = expire;
|
||||
}
|
||||
}
|
||||
|
||||
self.tasks.push_back(Entry::new(
|
||||
parts,
|
||||
body,
|
||||
resp,
|
||||
self.addr,
|
||||
self.settings.clone(),
|
||||
self.extensions.clone(),
|
||||
));
|
||||
}
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(err) => {
|
||||
trace!("Connection error: {}", err);
|
||||
self.flags.insert(Flags::SHUTDOWN);
|
||||
for entry in &mut self.tasks {
|
||||
entry.task.disconnected()
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
/// keep-alive timer. returns `true` is keep-alive, otherwise drop
|
||||
fn poll_keepalive(&mut self) -> Result<(), HttpDispatchError> {
|
||||
if let Some(ref mut timer) = self.ka_timer {
|
||||
match timer.poll() {
|
||||
Ok(Async::Ready(_)) => {
|
||||
// if we get timer during shutdown, just drop connection
|
||||
if self.flags.contains(Flags::SHUTDOWN) {
|
||||
return Err(HttpDispatchError::ShutdownTimeout);
|
||||
}
|
||||
if timer.deadline() >= self.ka_expire {
|
||||
// check for any outstanding request handling
|
||||
if self.tasks.is_empty() {
|
||||
return Err(HttpDispatchError::ShutdownTimeout);
|
||||
} else if let Some(dl) = self.settings.keep_alive_expire() {
|
||||
timer.reset(dl);
|
||||
let _ = timer.poll();
|
||||
}
|
||||
} else {
|
||||
timer.reset(self.ka_expire);
|
||||
let _ = timer.poll();
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady) => (),
|
||||
Err(e) => {
|
||||
error!("Timer error {:?}", e);
|
||||
return Err(HttpDispatchError::Unknown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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,268 +0,0 @@
|
||||
#![cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(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(());
|
||||
let mut len_is_set = false;
|
||||
*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 => (),
|
||||
ResponseLength::Zero => {
|
||||
len_is_set = true;
|
||||
}
|
||||
_ => 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 => {
|
||||
if !len_is_set {
|
||||
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());
|
||||
}
|
||||
ResponseLength::None => {
|
||||
self.flags.insert(Flags::EOF);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
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,208 +0,0 @@
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use http::Version;
|
||||
use std::{mem, ptr, slice};
|
||||
|
||||
const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
|
||||
2021222324252627282930313233343536373839\
|
||||
4041424344454647484950515253545556575859\
|
||||
6061626364656667686970717273747576777879\
|
||||
8081828384858687888990919293949596979899";
|
||||
|
||||
pub(crate) const STATUS_LINE_BUF_SIZE: usize = 13;
|
||||
|
||||
pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) {
|
||||
let mut buf: [u8; STATUS_LINE_BUF_SIZE] = [
|
||||
b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', b' ', b' ', b' ', b' ', b' ',
|
||||
];
|
||||
match version {
|
||||
Version::HTTP_2 => buf[5] = b'2',
|
||||
Version::HTTP_10 => buf[7] = b'0',
|
||||
Version::HTTP_09 => {
|
||||
buf[5] = b'0';
|
||||
buf[7] = b'9';
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let mut curr: isize = 12;
|
||||
let buf_ptr = buf.as_mut_ptr();
|
||||
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
|
||||
let four = n > 999;
|
||||
|
||||
// decode 2 more chars, if > 2 chars
|
||||
let d1 = (n % 100) << 1;
|
||||
n /= 100;
|
||||
curr -= 2;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2);
|
||||
}
|
||||
|
||||
// decode last 1 or 2 chars
|
||||
if n < 10 {
|
||||
curr -= 1;
|
||||
unsafe {
|
||||
*buf_ptr.offset(curr) = (n as u8) + b'0';
|
||||
}
|
||||
} else {
|
||||
let d1 = n << 1;
|
||||
curr -= 2;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(
|
||||
lut_ptr.offset(d1 as isize),
|
||||
buf_ptr.offset(curr),
|
||||
2,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bytes.put_slice(&buf);
|
||||
if four {
|
||||
bytes.put(b' ');
|
||||
}
|
||||
}
|
||||
|
||||
/// NOTE: bytes object has to contain enough space
|
||||
pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
|
||||
if n < 10 {
|
||||
let mut buf: [u8; 21] = [
|
||||
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', b'e',
|
||||
b'n', b'g', b't', b'h', b':', b' ', b'0', b'\r', b'\n',
|
||||
];
|
||||
buf[18] = (n as u8) + b'0';
|
||||
bytes.put_slice(&buf);
|
||||
} else if n < 100 {
|
||||
let mut buf: [u8; 22] = [
|
||||
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', b'e',
|
||||
b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'\r', b'\n',
|
||||
];
|
||||
let d1 = n << 1;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(
|
||||
DEC_DIGITS_LUT.as_ptr().add(d1),
|
||||
buf.as_mut_ptr().offset(18),
|
||||
2,
|
||||
);
|
||||
}
|
||||
bytes.put_slice(&buf);
|
||||
} else if n < 1000 {
|
||||
let mut buf: [u8; 23] = [
|
||||
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', b'e',
|
||||
b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'0', b'\r', b'\n',
|
||||
];
|
||||
// decode 2 more chars, if > 2 chars
|
||||
let d1 = (n % 100) << 1;
|
||||
n /= 100;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(
|
||||
DEC_DIGITS_LUT.as_ptr().add(d1),
|
||||
buf.as_mut_ptr().offset(19),
|
||||
2,
|
||||
)
|
||||
};
|
||||
|
||||
// decode last 1
|
||||
buf[18] = (n as u8) + b'0';
|
||||
|
||||
bytes.put_slice(&buf);
|
||||
} else {
|
||||
bytes.put_slice(b"\r\ncontent-length: ");
|
||||
convert_usize(n, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
|
||||
let mut curr: isize = 39;
|
||||
let mut buf: [u8; 41] = unsafe { mem::uninitialized() };
|
||||
buf[39] = b'\r';
|
||||
buf[40] = b'\n';
|
||||
let buf_ptr = buf.as_mut_ptr();
|
||||
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
|
||||
|
||||
// eagerly decode 4 characters at a time
|
||||
while n >= 10_000 {
|
||||
let rem = (n % 10_000) as isize;
|
||||
n /= 10_000;
|
||||
|
||||
let d1 = (rem / 100) << 1;
|
||||
let d2 = (rem % 100) << 1;
|
||||
curr -= 4;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
|
||||
ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2);
|
||||
}
|
||||
}
|
||||
|
||||
// if we reach here numbers are <= 9999, so at most 4 chars long
|
||||
let mut n = n as isize; // possibly reduce 64bit math
|
||||
|
||||
// decode 2 more chars, if > 2 chars
|
||||
if n >= 100 {
|
||||
let d1 = (n % 100) << 1;
|
||||
n /= 100;
|
||||
curr -= 2;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
|
||||
}
|
||||
}
|
||||
|
||||
// decode last 1 or 2 chars
|
||||
if n < 10 {
|
||||
curr -= 1;
|
||||
unsafe {
|
||||
*buf_ptr.offset(curr) = (n as u8) + b'0';
|
||||
}
|
||||
} else {
|
||||
let d1 = n << 1;
|
||||
curr -= 2;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
bytes.extend_from_slice(slice::from_raw_parts(
|
||||
buf_ptr.offset(curr),
|
||||
41 - curr as usize,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_write_content_length() {
|
||||
let mut bytes = BytesMut::new();
|
||||
bytes.reserve(50);
|
||||
write_content_length(0, &mut bytes);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(9, &mut bytes);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(10, &mut bytes);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(99, &mut bytes);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(100, &mut bytes);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(101, &mut bytes);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(998, &mut bytes);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(1000, &mut bytes);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(1001, &mut bytes);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(5909, &mut bytes);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]);
|
||||
}
|
||||
}
|
@ -1,579 +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
|
||||
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(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 address can be bound or an `Actix` system is not yet
|
||||
/// configured.
|
||||
///
|
||||
/// ```rust
|
||||
/// extern crate actix_web;
|
||||
/// extern crate actix;
|
||||
/// use actix_web::{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(|_| ()));
|
||||
}
|
||||
}
|
@ -1,288 +0,0 @@
|
||||
use std::io::{self, Write};
|
||||
|
||||
#[cfg(feature = "brotli")]
|
||||
use brotli2::write::BrotliDecoder;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use error::PayloadError;
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::write::{GzDecoder, ZlibDecoder};
|
||||
use header::ContentEncoding;
|
||||
use http::header::{HeaderMap, CONTENT_ENCODING};
|
||||
use payload::{PayloadSender, PayloadStatus, PayloadWriter};
|
||||
|
||||
pub(crate) enum PayloadType {
|
||||
Sender(PayloadSender),
|
||||
Encoding(Box<EncodedPayload>),
|
||||
}
|
||||
|
||||
impl PayloadType {
|
||||
#[cfg(any(feature = "brotli", feature = "flate2"))]
|
||||
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
|
||||
// check content-encoding
|
||||
let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) {
|
||||
if let Ok(enc) = enc.to_str() {
|
||||
ContentEncoding::from(enc)
|
||||
} else {
|
||||
ContentEncoding::Auto
|
||||
}
|
||||
} else {
|
||||
ContentEncoding::Auto
|
||||
};
|
||||
|
||||
match enc {
|
||||
ContentEncoding::Auto | ContentEncoding::Identity => {
|
||||
PayloadType::Sender(sender)
|
||||
}
|
||||
_ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
|
||||
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
|
||||
PayloadType::Sender(sender)
|
||||
}
|
||||
}
|
||||
|
||||
impl PayloadWriter for PayloadType {
|
||||
#[inline]
|
||||
fn set_error(&mut self, err: PayloadError) {
|
||||
match *self {
|
||||
PayloadType::Sender(ref mut sender) => sender.set_error(err),
|
||||
PayloadType::Encoding(ref mut enc) => enc.set_error(err),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn feed_eof(&mut self) {
|
||||
match *self {
|
||||
PayloadType::Sender(ref mut sender) => sender.feed_eof(),
|
||||
PayloadType::Encoding(ref mut enc) => enc.feed_eof(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn feed_data(&mut self, data: Bytes) {
|
||||
match *self {
|
||||
PayloadType::Sender(ref mut sender) => sender.feed_data(data),
|
||||
PayloadType::Encoding(ref mut enc) => enc.feed_data(data),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn need_read(&self) -> PayloadStatus {
|
||||
match *self {
|
||||
PayloadType::Sender(ref sender) => sender.need_read(),
|
||||
PayloadType::Encoding(ref enc) => enc.need_read(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload wrapper with content decompression support
|
||||
pub(crate) struct EncodedPayload {
|
||||
inner: PayloadSender,
|
||||
error: bool,
|
||||
payload: PayloadStream,
|
||||
}
|
||||
|
||||
impl EncodedPayload {
|
||||
pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload {
|
||||
EncodedPayload {
|
||||
inner,
|
||||
error: false,
|
||||
payload: PayloadStream::new(enc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PayloadWriter for EncodedPayload {
|
||||
fn set_error(&mut self, err: PayloadError) {
|
||||
self.inner.set_error(err)
|
||||
}
|
||||
|
||||
fn feed_eof(&mut self) {
|
||||
if !self.error {
|
||||
match self.payload.feed_eof() {
|
||||
Err(err) => {
|
||||
self.error = true;
|
||||
self.set_error(PayloadError::Io(err));
|
||||
}
|
||||
Ok(value) => {
|
||||
if let Some(b) = value {
|
||||
self.inner.feed_data(b);
|
||||
}
|
||||
self.inner.feed_eof();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn feed_data(&mut self, data: Bytes) {
|
||||
if self.error {
|
||||
return;
|
||||
}
|
||||
|
||||
match self.payload.feed_data(data) {
|
||||
Ok(Some(b)) => self.inner.feed_data(b),
|
||||
Ok(None) => (),
|
||||
Err(e) => {
|
||||
self.error = true;
|
||||
self.set_error(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn need_read(&self) -> PayloadStatus {
|
||||
self.inner.need_read()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Decoder {
|
||||
#[cfg(feature = "flate2")]
|
||||
Deflate(Box<ZlibDecoder<Writer>>),
|
||||
#[cfg(feature = "flate2")]
|
||||
Gzip(Box<GzDecoder<Writer>>),
|
||||
#[cfg(feature = "brotli")]
|
||||
Br(Box<BrotliDecoder<Writer>>),
|
||||
Identity,
|
||||
}
|
||||
|
||||
pub(crate) struct Writer {
|
||||
buf: BytesMut,
|
||||
}
|
||||
|
||||
impl Writer {
|
||||
fn new() -> Writer {
|
||||
Writer {
|
||||
buf: BytesMut::with_capacity(8192),
|
||||
}
|
||||
}
|
||||
fn take(&mut self) -> Bytes {
|
||||
self.buf.take().freeze()
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for Writer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.buf.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload stream with decompression support
|
||||
pub(crate) struct PayloadStream {
|
||||
decoder: Decoder,
|
||||
}
|
||||
|
||||
impl PayloadStream {
|
||||
pub fn new(enc: ContentEncoding) -> PayloadStream {
|
||||
let decoder = match enc {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => {
|
||||
Decoder::Br(Box::new(BrotliDecoder::new(Writer::new())))
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => {
|
||||
Decoder::Deflate(Box::new(ZlibDecoder::new(Writer::new())))
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => {
|
||||
Decoder::Gzip(Box::new(GzDecoder::new(Writer::new())))
|
||||
}
|
||||
_ => Decoder::Identity,
|
||||
};
|
||||
PayloadStream { decoder }
|
||||
}
|
||||
}
|
||||
|
||||
impl PayloadStream {
|
||||
pub fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
|
||||
match self.decoder {
|
||||
#[cfg(feature = "brotli")]
|
||||
Decoder::Br(ref mut decoder) => match decoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
let b = writer.take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Gzip(ref mut decoder) => match decoder.try_finish() {
|
||||
Ok(_) => {
|
||||
let b = decoder.get_mut().take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Deflate(ref mut decoder) => match decoder.try_finish() {
|
||||
Ok(_) => {
|
||||
let b = decoder.get_mut().take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Decoder::Identity => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
|
||||
match self.decoder {
|
||||
#[cfg(feature = "brotli")]
|
||||
Decoder::Br(ref mut decoder) => match decoder.write_all(&data) {
|
||||
Ok(_) => {
|
||||
decoder.flush()?;
|
||||
let b = decoder.get_mut().take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Gzip(ref mut decoder) => match decoder.write_all(&data) {
|
||||
Ok(_) => {
|
||||
decoder.flush()?;
|
||||
let b = decoder.get_mut().take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
|
||||
Ok(_) => {
|
||||
decoder.flush()?;
|
||||
let b = decoder.get_mut().take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Decoder::Identity => Ok(Some(data)),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,284 +0,0 @@
|
||||
use std::cell::{Cell, Ref, RefCell, RefMut};
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::net::SocketAddr;
|
||||
use std::rc::Rc;
|
||||
|
||||
use http::{header, HeaderMap, Method, Uri, Version};
|
||||
|
||||
use extensions::Extensions;
|
||||
use httpmessage::HttpMessage;
|
||||
use info::ConnectionInfo;
|
||||
use payload::Payload;
|
||||
use server::ServerSettings;
|
||||
use uri::Url as InnerUrl;
|
||||
|
||||
bitflags! {
|
||||
pub(crate) struct MessageFlags: u8 {
|
||||
const KEEPALIVE = 0b0000_0001;
|
||||
const CONN_INFO = 0b0000_0010;
|
||||
}
|
||||
}
|
||||
|
||||
/// Request's context
|
||||
pub struct Request {
|
||||
pub(crate) inner: Rc<InnerRequest>,
|
||||
}
|
||||
|
||||
pub(crate) struct InnerRequest {
|
||||
pub(crate) version: Version,
|
||||
pub(crate) method: Method,
|
||||
pub(crate) url: InnerUrl,
|
||||
pub(crate) flags: Cell<MessageFlags>,
|
||||
pub(crate) headers: HeaderMap,
|
||||
pub(crate) extensions: RefCell<Extensions>,
|
||||
pub(crate) addr: Option<SocketAddr>,
|
||||
pub(crate) info: RefCell<ConnectionInfo>,
|
||||
pub(crate) payload: RefCell<Option<Payload>>,
|
||||
pub(crate) settings: ServerSettings,
|
||||
pub(crate) stream_extensions: Option<Rc<Extensions>>,
|
||||
pool: &'static RequestPool,
|
||||
}
|
||||
|
||||
impl InnerRequest {
|
||||
#[inline]
|
||||
/// Reset request instance
|
||||
pub fn reset(&mut self) {
|
||||
self.headers.clear();
|
||||
self.extensions.borrow_mut().clear();
|
||||
self.flags.set(MessageFlags::empty());
|
||||
*self.payload.borrow_mut() = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpMessage for Request {
|
||||
type Stream = Payload;
|
||||
|
||||
fn headers(&self) -> &HeaderMap {
|
||||
&self.inner.headers
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn payload(&self) -> Payload {
|
||||
if let Some(payload) = self.inner.payload.borrow_mut().take() {
|
||||
payload
|
||||
} else {
|
||||
Payload::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Request {
|
||||
/// Create new RequestContext instance
|
||||
pub(crate) fn new(pool: &'static RequestPool, settings: ServerSettings) -> Request {
|
||||
Request {
|
||||
inner: Rc::new(InnerRequest {
|
||||
pool,
|
||||
settings,
|
||||
method: Method::GET,
|
||||
url: InnerUrl::default(),
|
||||
version: Version::HTTP_11,
|
||||
headers: HeaderMap::with_capacity(16),
|
||||
flags: Cell::new(MessageFlags::empty()),
|
||||
addr: None,
|
||||
info: RefCell::new(ConnectionInfo::default()),
|
||||
payload: RefCell::new(None),
|
||||
extensions: RefCell::new(Extensions::new()),
|
||||
stream_extensions: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn inner(&self) -> &InnerRequest {
|
||||
self.inner.as_ref()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn inner_mut(&mut self) -> &mut InnerRequest {
|
||||
Rc::get_mut(&mut self.inner).expect("Multiple copies exist")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn url(&self) -> &InnerUrl {
|
||||
&self.inner().url
|
||||
}
|
||||
|
||||
/// Read the Request Uri.
|
||||
#[inline]
|
||||
pub fn uri(&self) -> &Uri {
|
||||
self.inner().url.uri()
|
||||
}
|
||||
|
||||
/// Read the Request method.
|
||||
#[inline]
|
||||
pub fn method(&self) -> &Method {
|
||||
&self.inner().method
|
||||
}
|
||||
|
||||
/// Read the Request Version.
|
||||
#[inline]
|
||||
pub fn version(&self) -> Version {
|
||||
self.inner().version
|
||||
}
|
||||
|
||||
/// The target path of this Request.
|
||||
#[inline]
|
||||
pub fn path(&self) -> &str {
|
||||
self.inner().url.path()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Returns Request's headers.
|
||||
pub fn headers(&self) -> &HeaderMap {
|
||||
&self.inner().headers
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Returns mutable Request's headers.
|
||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.inner_mut().headers
|
||||
}
|
||||
|
||||
/// Peer socket address
|
||||
///
|
||||
/// Peer address is actual socket address, if proxy is used in front of
|
||||
/// actix http server, then peer address would be address of this proxy.
|
||||
///
|
||||
/// To get client connection information `connection_info()` method should
|
||||
/// be used.
|
||||
pub fn peer_addr(&self) -> Option<SocketAddr> {
|
||||
self.inner().addr
|
||||
}
|
||||
|
||||
/// Checks if a connection should be kept alive.
|
||||
#[inline]
|
||||
pub fn keep_alive(&self) -> bool {
|
||||
self.inner().flags.get().contains(MessageFlags::KEEPALIVE)
|
||||
}
|
||||
|
||||
/// Request extensions
|
||||
#[inline]
|
||||
pub fn extensions(&self) -> Ref<Extensions> {
|
||||
self.inner().extensions.borrow()
|
||||
}
|
||||
|
||||
/// Mutable reference to a the request's extensions
|
||||
#[inline]
|
||||
pub fn extensions_mut(&self) -> RefMut<Extensions> {
|
||||
self.inner().extensions.borrow_mut()
|
||||
}
|
||||
|
||||
/// Check if request requires connection upgrade
|
||||
pub fn upgrade(&self) -> bool {
|
||||
if let Some(conn) = self.inner().headers.get(header::CONNECTION) {
|
||||
if let Ok(s) = conn.to_str() {
|
||||
return s.to_lowercase().contains("upgrade");
|
||||
}
|
||||
}
|
||||
self.inner().method == Method::CONNECT
|
||||
}
|
||||
|
||||
/// Get *ConnectionInfo* for the correct request.
|
||||
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
|
||||
if self.inner().flags.get().contains(MessageFlags::CONN_INFO) {
|
||||
self.inner().info.borrow()
|
||||
} else {
|
||||
let mut flags = self.inner().flags.get();
|
||||
flags.insert(MessageFlags::CONN_INFO);
|
||||
self.inner().flags.set(flags);
|
||||
self.inner().info.borrow_mut().update(self);
|
||||
self.inner().info.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
/// Io stream extensions
|
||||
#[inline]
|
||||
pub fn stream_extensions(&self) -> Option<&Extensions> {
|
||||
self.inner().stream_extensions.as_ref().map(|e| e.as_ref())
|
||||
}
|
||||
|
||||
/// Server settings
|
||||
#[inline]
|
||||
pub fn server_settings(&self) -> &ServerSettings {
|
||||
&self.inner().settings
|
||||
}
|
||||
|
||||
pub(crate) fn clone(&self) -> Self {
|
||||
Request {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn release(self) {
|
||||
let mut inner = self.inner;
|
||||
if let Some(r) = Rc::get_mut(&mut inner) {
|
||||
r.reset();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
inner.pool.release(inner);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Request {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"\nRequest {:?} {}:{}",
|
||||
self.version(),
|
||||
self.method(),
|
||||
self.path()
|
||||
)?;
|
||||
if let Some(q) = self.uri().query().as_ref() {
|
||||
writeln!(f, " query: ?{:?}", q)?;
|
||||
}
|
||||
writeln!(f, " headers:")?;
|
||||
for (key, val) in self.headers().iter() {
|
||||
writeln!(f, " {:?}: {:?}", key, val)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RequestPool(
|
||||
RefCell<VecDeque<Rc<InnerRequest>>>,
|
||||
RefCell<ServerSettings>,
|
||||
);
|
||||
|
||||
thread_local!(static POOL: &'static RequestPool = RequestPool::create());
|
||||
|
||||
impl RequestPool {
|
||||
fn create() -> &'static RequestPool {
|
||||
let pool = RequestPool(
|
||||
RefCell::new(VecDeque::with_capacity(128)),
|
||||
RefCell::new(ServerSettings::default()),
|
||||
);
|
||||
Box::leak(Box::new(pool))
|
||||
}
|
||||
|
||||
pub fn pool(settings: ServerSettings) -> &'static RequestPool {
|
||||
POOL.with(|p| {
|
||||
*p.1.borrow_mut() = settings;
|
||||
*p
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(pool: &'static RequestPool) -> Request {
|
||||
if let Some(msg) = pool.0.borrow_mut().pop_front() {
|
||||
Request { inner: msg }
|
||||
} else {
|
||||
Request::new(pool, pool.1.borrow().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Release request instance
|
||||
pub fn release(&self, msg: Rc<InnerRequest>) {
|
||||
let v = &mut self.0.borrow_mut();
|
||||
if v.len() < 128 {
|
||||
v.push_front(msg);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,370 +0,0 @@
|
||||
//! Http server module
|
||||
//!
|
||||
//! The module contains everything necessary to setup
|
||||
//! HTTP server.
|
||||
//!
|
||||
//! In order to start HTTP server, first you need to create and configure it
|
||||
//! using factory that can be supplied to [new](fn.new.html).
|
||||
//!
|
||||
//! ## Factory
|
||||
//!
|
||||
//! Factory is a function that returns Application, describing how
|
||||
//! to serve incoming HTTP requests.
|
||||
//!
|
||||
//! As the server uses worker pool, the factory function is restricted to trait bounds
|
||||
//! `Send + Clone + 'static` so that each worker would be able to accept Application
|
||||
//! without a need for synchronization.
|
||||
//!
|
||||
//! If you wish to share part of state among all workers you should
|
||||
//! wrap it in `Arc` and potentially synchronization primitive like
|
||||
//! [RwLock](https://doc.rust-lang.org/std/sync/struct.RwLock.html)
|
||||
//! If the wrapped type is not thread safe.
|
||||
//!
|
||||
//! Note though that locking is not advisable for asynchronous programming
|
||||
//! and you should minimize all locks in your request handlers
|
||||
//!
|
||||
//! ## HTTPS Support
|
||||
//!
|
||||
//! Actix-web provides support for major crates that provides TLS.
|
||||
//! Each TLS implementation is provided with [AcceptorService](trait.AcceptorService.html)
|
||||
//! that describes how HTTP Server accepts connections.
|
||||
//!
|
||||
//! For `bind` and `listen` there are corresponding `bind_ssl|tls|rustls` and `listen_ssl|tls|rustls` that accepts
|
||||
//! these services.
|
||||
//!
|
||||
//! **NOTE:** `native-tls` doesn't support `HTTP2` yet
|
||||
//!
|
||||
//! ## Signal handling and shutdown
|
||||
//!
|
||||
//! By default HTTP Server listens for system signals
|
||||
//! and, gracefully shuts down at most after 30 seconds.
|
||||
//!
|
||||
//! Both signal handling and shutdown timeout can be controlled
|
||||
//! using corresponding methods.
|
||||
//!
|
||||
//! If worker, for some reason, unable to shut down within timeout
|
||||
//! it is forcibly dropped.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//!extern crate actix;
|
||||
//!extern crate actix_web;
|
||||
//!extern crate rustls;
|
||||
//!
|
||||
//!use actix_web::{http, middleware, server, App, Error, HttpRequest, HttpResponse, Responder};
|
||||
//!use std::io::BufReader;
|
||||
//!use rustls::internal::pemfile::{certs, rsa_private_keys};
|
||||
//!use rustls::{NoClientAuth, ServerConfig};
|
||||
//!
|
||||
//!fn index(req: &HttpRequest) -> Result<HttpResponse, Error> {
|
||||
//! Ok(HttpResponse::Ok().content_type("text/plain").body("Welcome!"))
|
||||
//!}
|
||||
//!
|
||||
//!fn load_ssl() -> ServerConfig {
|
||||
//! use std::io::BufReader;
|
||||
//!
|
||||
//! const CERT: &'static [u8] = include_bytes!("../cert.pem");
|
||||
//! const KEY: &'static [u8] = include_bytes!("../key.pem");
|
||||
//!
|
||||
//! let mut cert = BufReader::new(CERT);
|
||||
//! let mut key = BufReader::new(KEY);
|
||||
//!
|
||||
//! let mut config = ServerConfig::new(NoClientAuth::new());
|
||||
//! let cert_chain = certs(&mut cert).unwrap();
|
||||
//! let mut keys = rsa_private_keys(&mut key).unwrap();
|
||||
//! config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
|
||||
//!
|
||||
//! config
|
||||
//!}
|
||||
//!
|
||||
//!fn main() {
|
||||
//! let sys = actix::System::new("http-server");
|
||||
//! // load ssl keys
|
||||
//! let config = load_ssl();
|
||||
//!
|
||||
//! // create and start server at once
|
||||
//! server::new(|| {
|
||||
//! App::new()
|
||||
//! // register simple handler, handle all methods
|
||||
//! .resource("/index.html", |r| r.f(index))
|
||||
//! }))
|
||||
//! }).bind_rustls("127.0.0.1:8443", config)
|
||||
//! .unwrap()
|
||||
//! .start();
|
||||
//!
|
||||
//! println!("Started http server: 127.0.0.1:8080");
|
||||
//! //Run system so that server would start accepting connections
|
||||
//! let _ = sys.run();
|
||||
//!}
|
||||
//! ```
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
use std::rc::Rc;
|
||||
use std::{io, time};
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures::{Async, Poll};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
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 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 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;
|
||||
|
||||
#[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::helpers::write_content_length;
|
||||
|
||||
use body::Binary;
|
||||
use extensions::Extensions;
|
||||
use header::ContentEncoding;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
/// max buffer size 64k
|
||||
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;
|
||||
/// # extern crate actix;
|
||||
/// use actix_web::{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
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl From<usize> for KeepAlive {
|
||||
fn from(keepalive: usize) -> Self {
|
||||
KeepAlive::Timeout(keepalive)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<usize>> for KeepAlive {
|
||||
fn from(keepalive: Option<usize>) -> Self {
|
||||
if let Some(keepalive) = keepalive {
|
||||
KeepAlive::Timeout(keepalive)
|
||||
} else {
|
||||
KeepAlive::Disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
|
||||
/// Sets the value of the TCP_NODELAY option on this socket.
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>;
|
||||
|
||||
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 if e.kind() == io::ErrorKind::ConnectionReset && read_some {
|
||||
Ok(Async::Ready((read_some, true)))
|
||||
} 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(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_linger(&mut self, _dur: Option<time::Duration>) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_keepalive(&mut self, _dur: Option<time::Duration>) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||
TcpStream::set_nodelay(self, nodelay)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
TcpStream::set_linger(self, dur)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
TcpStream::set_keepalive(self, dur)
|
||||
}
|
||||
}
|
@ -1,760 +0,0 @@
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
use std::{cmp, fmt, io, mem};
|
||||
|
||||
#[cfg(feature = "brotli")]
|
||||
use brotli2::write::BrotliEncoder;
|
||||
use bytes::BytesMut;
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::Compression;
|
||||
use http::header::{ACCEPT_ENCODING, CONTENT_LENGTH};
|
||||
use http::{StatusCode, Version};
|
||||
|
||||
use super::message::InnerRequest;
|
||||
use body::{Binary, Body};
|
||||
use header::ContentEncoding;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ResponseLength {
|
||||
Chunked,
|
||||
Zero,
|
||||
Length(usize),
|
||||
Length64(u64),
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ResponseInfo {
|
||||
head: bool,
|
||||
pub length: ResponseLength,
|
||||
pub content_encoding: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl ResponseInfo {
|
||||
pub fn new(head: bool) -> Self {
|
||||
ResponseInfo {
|
||||
head,
|
||||
length: ResponseLength::None,
|
||||
content_encoding: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Output {
|
||||
Empty(BytesMut),
|
||||
Buffer(BytesMut),
|
||||
Encoder(ContentEncoder),
|
||||
TE(TransferEncoding),
|
||||
Done,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub fn take(&mut self) -> BytesMut {
|
||||
match mem::replace(self, Output::Done) {
|
||||
Output::Empty(bytes) => bytes,
|
||||
Output::Buffer(bytes) => bytes,
|
||||
Output::Encoder(mut enc) => enc.take_buf(),
|
||||
Output::TE(mut te) => te.take(),
|
||||
Output::Done => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_option(&mut self) -> Option<BytesMut> {
|
||||
match mem::replace(self, Output::Done) {
|
||||
Output::Empty(bytes) => Some(bytes),
|
||||
Output::Buffer(bytes) => Some(bytes),
|
||||
Output::Encoder(mut enc) => Some(enc.take_buf()),
|
||||
Output::TE(mut te) => Some(te.take()),
|
||||
Output::Done => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ref(&mut self) -> &BytesMut {
|
||||
match self {
|
||||
Output::Empty(ref mut bytes) => bytes,
|
||||
Output::Buffer(ref mut bytes) => bytes,
|
||||
Output::Encoder(ref mut enc) => enc.buf_ref(),
|
||||
Output::TE(ref mut te) => te.buf_ref(),
|
||||
Output::Done => panic!(),
|
||||
}
|
||||
}
|
||||
pub fn as_mut(&mut self) -> &mut BytesMut {
|
||||
match self {
|
||||
Output::Empty(ref mut bytes) => bytes,
|
||||
Output::Buffer(ref mut bytes) => bytes,
|
||||
Output::Encoder(ref mut enc) => enc.buf_mut(),
|
||||
Output::TE(ref mut te) => te.buf_mut(),
|
||||
Output::Done => panic!(),
|
||||
}
|
||||
}
|
||||
pub fn split_to(&mut self, cap: usize) -> BytesMut {
|
||||
match self {
|
||||
Output::Empty(ref mut bytes) => bytes.split_to(cap),
|
||||
Output::Buffer(ref mut bytes) => bytes.split_to(cap),
|
||||
Output::Encoder(ref mut enc) => enc.buf_mut().split_to(cap),
|
||||
Output::TE(ref mut te) => te.buf_mut().split_to(cap),
|
||||
Output::Done => BytesMut::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Output::Empty(ref bytes) => bytes.len(),
|
||||
Output::Buffer(ref bytes) => bytes.len(),
|
||||
Output::Encoder(ref enc) => enc.len(),
|
||||
Output::TE(ref te) => te.len(),
|
||||
Output::Done => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Output::Empty(ref bytes) => bytes.is_empty(),
|
||||
Output::Buffer(ref bytes) => bytes.is_empty(),
|
||||
Output::Encoder(ref enc) => enc.is_empty(),
|
||||
Output::TE(ref te) => te.is_empty(),
|
||||
Output::Done => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
|
||||
match self {
|
||||
Output::Buffer(ref mut bytes) => {
|
||||
bytes.extend_from_slice(data);
|
||||
Ok(())
|
||||
}
|
||||
Output::Encoder(ref mut enc) => enc.write(data),
|
||||
Output::TE(ref mut te) => te.encode(data).map(|_| ()),
|
||||
Output::Empty(_) | Output::Done => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_eof(&mut self) -> Result<bool, io::Error> {
|
||||
match self {
|
||||
Output::Buffer(_) => Ok(true),
|
||||
Output::Encoder(ref mut enc) => enc.write_eof(),
|
||||
Output::TE(ref mut te) => Ok(te.encode_eof()),
|
||||
Output::Empty(_) | Output::Done => Ok(true),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn for_server(
|
||||
&mut self, info: &mut ResponseInfo, req: &InnerRequest, resp: &mut HttpResponse,
|
||||
response_encoding: ContentEncoding,
|
||||
) {
|
||||
let buf = self.take();
|
||||
let version = resp.version().unwrap_or_else(|| req.version);
|
||||
let mut len = 0;
|
||||
|
||||
let has_body = match resp.body() {
|
||||
Body::Empty => false,
|
||||
Body::Binary(ref bin) => {
|
||||
len = bin.len();
|
||||
!(response_encoding == ContentEncoding::Auto && len < 96)
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// Enable content encoding only if response does not contain Content-Encoding
|
||||
// header
|
||||
#[cfg(any(feature = "brotli", feature = "flate2"))]
|
||||
let mut encoding = if has_body {
|
||||
let encoding = match response_encoding {
|
||||
ContentEncoding::Auto => {
|
||||
// negotiate content-encoding
|
||||
if let Some(val) = req.headers.get(ACCEPT_ENCODING) {
|
||||
if let Ok(enc) = val.to_str() {
|
||||
AcceptEncoding::parse(enc)
|
||||
} else {
|
||||
ContentEncoding::Identity
|
||||
}
|
||||
} else {
|
||||
ContentEncoding::Identity
|
||||
}
|
||||
}
|
||||
encoding => encoding,
|
||||
};
|
||||
if encoding.is_compression() {
|
||||
info.content_encoding = Some(encoding.as_str());
|
||||
}
|
||||
encoding
|
||||
} else {
|
||||
ContentEncoding::Identity
|
||||
};
|
||||
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
|
||||
let mut encoding = ContentEncoding::Identity;
|
||||
|
||||
let transfer = match resp.body() {
|
||||
Body::Empty => {
|
||||
info.length = match resp.status() {
|
||||
StatusCode::NO_CONTENT
|
||||
| StatusCode::CONTINUE
|
||||
| StatusCode::SWITCHING_PROTOCOLS
|
||||
| StatusCode::PROCESSING => ResponseLength::None,
|
||||
_ => ResponseLength::Zero,
|
||||
};
|
||||
*self = Output::Empty(buf);
|
||||
return;
|
||||
}
|
||||
Body::Binary(_) => {
|
||||
#[cfg(any(feature = "brotli", feature = "flate2"))]
|
||||
{
|
||||
if !(encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto)
|
||||
{
|
||||
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::fast()),
|
||||
),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => ContentEncoder::Gzip(
|
||||
GzEncoder::new(transfer, Compression::fast()),
|
||||
),
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => {
|
||||
ContentEncoder::Br(BrotliEncoder::new(transfer, 3))
|
||||
}
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let bin = resp.replace_body(Body::Empty).binary();
|
||||
|
||||
// TODO return error!
|
||||
let _ = enc.write(bin.as_ref());
|
||||
let _ = enc.write_eof();
|
||||
let body = enc.buf_mut().take();
|
||||
len = body.len();
|
||||
resp.replace_body(Binary::from(body));
|
||||
}
|
||||
}
|
||||
|
||||
info.length = ResponseLength::Length(len);
|
||||
if info.head {
|
||||
*self = Output::Empty(buf);
|
||||
} else {
|
||||
*self = Output::Buffer(buf);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Body::Streaming(_) | Body::Actor(_) => {
|
||||
if resp.upgrade() {
|
||||
if version == Version::HTTP_2 {
|
||||
error!("Connection upgrade is forbidden for HTTP/2");
|
||||
}
|
||||
if encoding != ContentEncoding::Identity {
|
||||
encoding = ContentEncoding::Identity;
|
||||
info.content_encoding.take();
|
||||
}
|
||||
TransferEncoding::eof(buf)
|
||||
} else {
|
||||
if !(encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto)
|
||||
{
|
||||
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||
}
|
||||
Output::streaming_encoding(info, buf, version, resp)
|
||||
}
|
||||
}
|
||||
};
|
||||
// check for head response
|
||||
if info.head {
|
||||
resp.set_body(Body::Empty);
|
||||
*self = Output::Empty(transfer.buf.unwrap());
|
||||
return;
|
||||
}
|
||||
|
||||
let enc = match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => {
|
||||
ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::fast()))
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => {
|
||||
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast()))
|
||||
}
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 3)),
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => {
|
||||
*self = Output::TE(transfer);
|
||||
return;
|
||||
}
|
||||
};
|
||||
*self = Output::Encoder(enc);
|
||||
}
|
||||
|
||||
fn streaming_encoding(
|
||||
info: &mut ResponseInfo, buf: BytesMut, version: Version,
|
||||
resp: &mut HttpResponse,
|
||||
) -> TransferEncoding {
|
||||
match resp.chunked() {
|
||||
Some(true) => {
|
||||
// Enable transfer encoding
|
||||
info.length = ResponseLength::Chunked;
|
||||
if version == Version::HTTP_2 {
|
||||
TransferEncoding::eof(buf)
|
||||
} else {
|
||||
TransferEncoding::chunked(buf)
|
||||
}
|
||||
}
|
||||
Some(false) => TransferEncoding::eof(buf),
|
||||
None => {
|
||||
// if Content-Length is specified, then use it as length hint
|
||||
let (len, chunked) =
|
||||
if let Some(len) = resp.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 {
|
||||
info.length = ResponseLength::Length64(len);
|
||||
TransferEncoding::length(len, buf)
|
||||
} else {
|
||||
TransferEncoding::eof(buf)
|
||||
}
|
||||
} else {
|
||||
// Enable transfer encoding
|
||||
info.length = ResponseLength::Chunked;
|
||||
if version == Version::HTTP_2 {
|
||||
TransferEncoding::eof(buf)
|
||||
} else {
|
||||
TransferEncoding::chunked(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum ContentEncoder {
|
||||
#[cfg(feature = "flate2")]
|
||||
Deflate(ZlibEncoder<TransferEncoding>),
|
||||
#[cfg(feature = "flate2")]
|
||||
Gzip(GzEncoder<TransferEncoding>),
|
||||
#[cfg(feature = "brotli")]
|
||||
Br(BrotliEncoder<TransferEncoding>),
|
||||
Identity(TransferEncoding),
|
||||
}
|
||||
|
||||
impl fmt::Debug for ContentEncoder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"),
|
||||
ContentEncoder::Identity(_) => writeln!(f, "ContentEncoder(Identity)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContentEncoder {
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref encoder) => encoder.get_ref().len(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().len(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().len(),
|
||||
ContentEncoder::Identity(ref encoder) => encoder.len(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref encoder) => encoder.get_ref().is_empty(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_empty(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_empty(),
|
||||
ContentEncoder::Identity(ref encoder) => encoder.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn take_buf(&mut self) -> BytesMut {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
|
||||
ContentEncoder::Identity(ref mut encoder) => encoder.take(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn buf_mut(&mut self) -> &mut BytesMut {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_mut(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_mut(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_mut(),
|
||||
ContentEncoder::Identity(ref mut encoder) => encoder.buf_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn buf_ref(&mut self) -> &BytesMut {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_ref(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_ref(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_ref(),
|
||||
ContentEncoder::Identity(ref mut encoder) => encoder.buf_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||
#[inline(always)]
|
||||
pub fn write_eof(&mut self) -> Result<bool, io::Error> {
|
||||
let encoder =
|
||||
mem::replace(self, ContentEncoder::Identity(TransferEncoding::empty()));
|
||||
|
||||
match encoder {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(encoder) => match encoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(true)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(encoder) => match encoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(true)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(encoder) => match encoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(true)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
ContentEncoder::Identity(mut writer) => {
|
||||
let res = writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||
#[inline(always)]
|
||||
pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding br encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding gzip encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding deflate encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
ContentEncoder::Identity(ref mut encoder) => {
|
||||
encoder.encode(data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encoders to handle different Transfer-Encodings.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TransferEncoding {
|
||||
buf: Option<BytesMut>,
|
||||
kind: TransferEncodingKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum TransferEncodingKind {
|
||||
/// An Encoder for when Transfer-Encoding includes `chunked`.
|
||||
Chunked(bool),
|
||||
/// An Encoder for when Content-Length is set.
|
||||
///
|
||||
/// Enforces that the body is not longer than the Content-Length header.
|
||||
Length(u64),
|
||||
/// An Encoder for when Content-Length is not known.
|
||||
///
|
||||
/// Application decides when to stop writing.
|
||||
Eof,
|
||||
}
|
||||
|
||||
impl TransferEncoding {
|
||||
fn take(&mut self) -> BytesMut {
|
||||
self.buf.take().unwrap()
|
||||
}
|
||||
|
||||
fn buf_ref(&mut self) -> &BytesMut {
|
||||
self.buf.as_ref().unwrap()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.buf.as_ref().unwrap().len()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.buf.as_ref().unwrap().is_empty()
|
||||
}
|
||||
|
||||
fn buf_mut(&mut self) -> &mut BytesMut {
|
||||
self.buf.as_mut().unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn empty() -> TransferEncoding {
|
||||
TransferEncoding {
|
||||
buf: None,
|
||||
kind: TransferEncodingKind::Eof,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn eof(buf: BytesMut) -> TransferEncoding {
|
||||
TransferEncoding {
|
||||
buf: Some(buf),
|
||||
kind: TransferEncodingKind::Eof,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn chunked(buf: BytesMut) -> TransferEncoding {
|
||||
TransferEncoding {
|
||||
buf: Some(buf),
|
||||
kind: TransferEncodingKind::Chunked(false),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn length(len: u64, buf: BytesMut) -> TransferEncoding {
|
||||
TransferEncoding {
|
||||
buf: Some(buf),
|
||||
kind: TransferEncodingKind::Length(len),
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode message. Return `EOF` state of encoder
|
||||
#[inline]
|
||||
pub fn encode(&mut self, msg: &[u8]) -> io::Result<bool> {
|
||||
match self.kind {
|
||||
TransferEncodingKind::Eof => {
|
||||
let eof = msg.is_empty();
|
||||
self.buf.as_mut().unwrap().extend_from_slice(msg);
|
||||
Ok(eof)
|
||||
}
|
||||
TransferEncodingKind::Chunked(ref mut eof) => {
|
||||
if *eof {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if msg.is_empty() {
|
||||
*eof = true;
|
||||
self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n");
|
||||
} else {
|
||||
let mut buf = BytesMut::new();
|
||||
writeln!(&mut buf, "{:X}\r", msg.len())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
|
||||
let b = self.buf.as_mut().unwrap();
|
||||
b.reserve(buf.len() + msg.len() + 2);
|
||||
b.extend_from_slice(buf.as_ref());
|
||||
b.extend_from_slice(msg);
|
||||
b.extend_from_slice(b"\r\n");
|
||||
}
|
||||
Ok(*eof)
|
||||
}
|
||||
TransferEncodingKind::Length(ref mut remaining) => {
|
||||
if *remaining > 0 {
|
||||
if msg.is_empty() {
|
||||
return Ok(*remaining == 0);
|
||||
}
|
||||
let len = cmp::min(*remaining, msg.len() as u64);
|
||||
|
||||
self.buf
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.extend_from_slice(&msg[..len as usize]);
|
||||
|
||||
*remaining -= len as u64;
|
||||
Ok(*remaining == 0)
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode eof. Return `EOF` state of encoder
|
||||
#[inline]
|
||||
pub fn encode_eof(&mut self) -> bool {
|
||||
match self.kind {
|
||||
TransferEncodingKind::Eof => true,
|
||||
TransferEncodingKind::Length(rem) => rem == 0,
|
||||
TransferEncodingKind::Chunked(ref mut eof) => {
|
||||
if !*eof {
|
||||
*eof = true;
|
||||
self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n");
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for TransferEncoding {
|
||||
#[inline]
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
if self.buf.is_some() {
|
||||
self.encode(buf)?;
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct AcceptEncoding {
|
||||
encoding: ContentEncoding,
|
||||
quality: f64,
|
||||
}
|
||||
|
||||
impl Eq for AcceptEncoding {}
|
||||
|
||||
impl Ord for AcceptEncoding {
|
||||
fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering {
|
||||
if self.quality > other.quality {
|
||||
cmp::Ordering::Less
|
||||
} else if self.quality < other.quality {
|
||||
cmp::Ordering::Greater
|
||||
} else {
|
||||
cmp::Ordering::Equal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for AcceptEncoding {
|
||||
fn partial_cmp(&self, other: &AcceptEncoding) -> Option<cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AcceptEncoding {
|
||||
fn eq(&self, other: &AcceptEncoding) -> bool {
|
||||
self.quality == other.quality
|
||||
}
|
||||
}
|
||||
|
||||
impl AcceptEncoding {
|
||||
fn new(tag: &str) -> Option<AcceptEncoding> {
|
||||
let parts: Vec<&str> = tag.split(';').collect();
|
||||
let encoding = match parts.len() {
|
||||
0 => return None,
|
||||
_ => ContentEncoding::from(parts[0]),
|
||||
};
|
||||
let quality = match parts.len() {
|
||||
1 => encoding.quality(),
|
||||
_ => match f64::from_str(parts[1]) {
|
||||
Ok(q) => q,
|
||||
Err(_) => 0.0,
|
||||
},
|
||||
};
|
||||
Some(AcceptEncoding { encoding, quality })
|
||||
}
|
||||
|
||||
/// Parse a raw Accept-Encoding header value into an ordered list.
|
||||
pub fn parse(raw: &str) -> ContentEncoding {
|
||||
let mut encodings: Vec<_> = raw
|
||||
.replace(' ', "")
|
||||
.split(',')
|
||||
.map(|l| AcceptEncoding::new(l))
|
||||
.collect();
|
||||
encodings.sort();
|
||||
|
||||
for enc in encodings {
|
||||
if let Some(enc) = enc {
|
||||
return enc.encoding;
|
||||
}
|
||||
}
|
||||
ContentEncoding::Identity
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bytes::Bytes;
|
||||
|
||||
#[test]
|
||||
fn test_chunked_te() {
|
||||
let bytes = BytesMut::new();
|
||||
let mut enc = TransferEncoding::chunked(bytes);
|
||||
{
|
||||
assert!(!enc.encode(b"test").ok().unwrap());
|
||||
assert!(enc.encode(b"").ok().unwrap());
|
||||
}
|
||||
assert_eq!(
|
||||
enc.buf_mut().take().freeze(),
|
||||
Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n")
|
||||
);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user