diff --git a/CHANGES.md b/CHANGES.md index 3c55c3f64..c3c38011c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,713 +1,5 @@ # Changes -## [0.7.9] - 2018-09-x +## [0.1.0] - 2018-09-x -### Added - -* Added client shutdown timeout setting - -* Added slow request timeout setting - -* Respond with 408 response on slow request timeout #523 - - -### Fixed - -* HTTP1 decoding errors are reported to the client. #512 - -* Correctly compose multiple allowed origins in CORS. #517 - -* Websocket server finished() isn't called if client disconnects #511 - -* Responses with the following codes: 100, 101, 102, 204 -- are sent without Content-Length header. #521 - -* Correct usage of `no_http2` flag in `bind_*` methods. #519 - - -## [0.7.8] - 2018-09-17 - -### Added - -* Use server `Keep-Alive` setting as slow request timeout #439 - -### Changed - -* Use 5 seconds keep-alive timer by default. - -### Fixed - -* Fixed wrong error message for i16 type #510 - - -## [0.7.7] - 2018-09-11 - -### Fixed - -* Fix linked list of HttpChannels #504 - -* Fix requests to TestServer fail #508 - - -## [0.7.6] - 2018-09-07 - -### Fixed - -* Fix system_exit in HttpServer #501 - -* Fix parsing of route param containin regexes with repetition #500 - -### Changes - -* Unhide `SessionBackend` and `SessionImpl` traits #455 - - -## [0.7.5] - 2018-09-04 - -### Added - -* Added the ability to pass a custom `TlsConnector`. - -* Allow to register handlers on scope level #465 - - -### Fixed - -* Handle socket read disconnect - -* Handling scoped paths without leading slashes #460 - - -### Changed - -* Read client response until eof if connection header set to close #464 - - -## [0.7.4] - 2018-08-23 - -### Added - -* Added `HttpServer::maxconn()` and `HttpServer::maxconnrate()`, - accept backpressure #250 - -* Allow to customize connection handshake process via `HttpServer::listen_with()` - and `HttpServer::bind_with()` methods - -* Support making client connections via `tokio-uds`'s `UnixStream` when "uds" feature is enabled #472 - -### Changed - -* It is allowed to use function with up to 10 parameters for handler with `extractor parameters`. - `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple - even for handler with one parameter. - -* native-tls - 0.2 - -* `Content-Disposition` is re-worked. Its parser is now more robust and handles quoted content better. See #461 - -### Fixed - -* Use zlib instead of raw deflate for decoding and encoding payloads with - `Content-Encoding: deflate`. - -* Fixed headers formating for CORS Middleware Access-Control-Expose-Headers #436 - -* Fix adding multiple response headers #446 - -* Client includes port in HOST header when it is not default(e.g. not 80 and 443). #448 - -* Panic during access without routing being set #452 - -* Fixed http/2 error handling - -### Deprecated - -* `HttpServer::no_http2()` is deprecated, use `OpensslAcceptor::with_flags()` or - `RustlsAcceptor::with_flags()` instead - -* `HttpServer::listen_tls()`, `HttpServer::listen_ssl()`, `HttpServer::listen_rustls()` have been - deprecated in favor of `HttpServer::listen_with()` with specific `acceptor`. - -* `HttpServer::bind_tls()`, `HttpServer::bind_ssl()`, `HttpServer::bind_rustls()` have been - deprecated in favor of `HttpServer::bind_with()` with specific `acceptor`. - - -## [0.7.3] - 2018-08-01 - -### Added - -* Support HTTP/2 with rustls #36 - -* Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433 - -### Fixed - -* Fixed failure 0.1.2 compatibility - -* Do not override HOST header for client request #428 - -* Gz streaming, use `flate2::write::GzDecoder` #228 - -* HttpRequest::url_for is not working with scopes #429 - -* Fixed headers' formating for CORS Middleware `Access-Control-Expose-Headers` header value to HTTP/1.1 & HTTP/2 spec-compliant format #436 - - -## [0.7.2] - 2018-07-26 - -### Added - -* Add implementation of `FromRequest` for `Option` and `Result` - -* 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/` to default headers when building a request - -* port `Extensions` type from http create, we don't need `Send + Sync` - -* `HttpRequest::query()` returns `Ref>` - -* `HttpRequest::cookies()` returns `Ref>>` - -* `StaticFiles::new()` returns `Result, Error>` instead of `StaticFiles` - -* `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> 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 diff --git a/Cargo.toml b/Cargo.toml index d70a65cf0..258301daa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "actix-web" -version = "0.7.9" +name = "actix-http" +version = "0.1.0" authors = ["Nikolay Kim "] -description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." +description = "Actix http" readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" @@ -14,7 +14,6 @@ categories = ["network-programming", "asynchronous", "web-programming::websocket"] license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] -build = "build.rs" [package.metadata.docs.rs] features = ["tls", "alpn", "rust-tls", "session", "brotli", "flate2-c"] @@ -25,7 +24,7 @@ appveyor = { repository = "fafhrd91/actix-web-hdy9d" } codecov = { repository = "actix/actix-web", branch = "master", service = "github" } [lib] -name = "actix_web" +name = "actix_http" path = "src/lib.rs" [features] @@ -130,6 +129,7 @@ webpki-roots = { version = "0.15", optional = true } tokio-uds = { version="0.2", optional = true } [dev-dependencies] +actix-web = "0.7" env_logger = "0.5" serde_derive = "1.0" @@ -140,8 +140,3 @@ version_check = "0.1" lto = true opt-level = 3 codegen-units = 1 - -[workspace] -members = [ - "./", -] diff --git a/MIGRATION.md b/MIGRATION.md deleted file mode 100644 index 3c0bdd943..000000000 --- a/MIGRATION.md +++ /dev/null @@ -1,176 +0,0 @@ -## 0.7.4 - -* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple - even for handler with one parameter. - - -## 0.7 - -* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload - use `HttpMessage::payload()` method. - - instead of - - ```rust - fn index(req: HttpRequest) -> impl Responder { - req - .from_err() - .fold(...) - .... - } - ``` - - use `.payload()` - - ```rust - fn index(req: HttpRequest) -> impl Responder { - req - .payload() // <- get request payload stream - .from_err() - .fold(...) - .... - } - ``` - -* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) - trait uses `&HttpRequest` instead of `&mut HttpRequest`. - -* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. - - instead of - - ```rust - fn index(query: Query<..>, info: Json impl Responder {} - ``` - - use tuple of extractors and use `.with()` for registration: - - ```rust - fn index((query, json): (Query<..>, Json impl Responder {} - ``` - -* `Handler::handle()` uses `&self` instead of `&mut self` - -* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value - -* Removed deprecated `HttpServer::threads()`, use - [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. - -* Renamed `client::ClientConnectorError::Connector` to - `client::ClientConnectorError::Resolver` - -* `Route::with()` does not return `ExtractorConfig`, to configure - extractor use `Route::with_config()` - - instead of - - ```rust - fn main() { - let app = App::new().resource("/index.html", |r| { - r.method(http::Method::GET) - .with(index) - .limit(4096); // <- limit size of the payload - }); - } - ``` - - use - - ```rust - - fn main() { - let app = App::new().resource("/index.html", |r| { - r.method(http::Method::GET) - .with_config(index, |cfg| { // <- register handler - cfg.limit(4096); // <- limit size of the payload - }) - }); - } - ``` - -* `Route::with_async()` does not return `ExtractorConfig`, to configure - extractor use `Route::with_async_config()` - - -## 0.6 - -* `Path` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest` - -* `ws::Message::Close` now includes optional close reason. - `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. - -* `HttpServer::threads()` renamed to `HttpServer::workers()`. - -* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated. - Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead. - -* `HttpRequest::extensions()` returns read only reference to the request's Extension - `HttpRequest::extensions_mut()` returns mutable reference. - -* Instead of - - `use actix_web::middleware::{ - CookieSessionBackend, CookieSessionError, RequestSession, - Session, SessionBackend, SessionImpl, SessionStorage};` - - use `actix_web::middleware::session` - - `use actix_web::middleware::session{CookieSessionBackend, CookieSessionError, - RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};` - -* `FromRequest::from_request()` accepts mutable reference to a request - -* `FromRequest::Result` has to implement `Into>` - -* [`Responder::respond_to()`]( - https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to) - is generic over `S` - -* Use `Query` extractor instead of HttpRequest::query()`. - - ```rust - fn index(q: Query>) -> Result<..> { - ... - } - ``` - - or - - ```rust - let q = Query::>::extract(req); - ``` - -* Websocket operations are implemented as `WsWriter` trait. - you need to use `use actix_web::ws::WsWriter` - - -## 0.5 - -* `HttpResponseBuilder::body()`, `.finish()`, `.json()` - methods return `HttpResponse` instead of `Result` - -* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version` - moved to `actix_web::http` module - -* `actix_web::header` moved to `actix_web::http::header` - -* `NormalizePath` moved to `actix_web::http` module - -* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function, - shortcut for `actix_web::server::HttpServer::new()` - -* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself - -* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead. - -* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type - -* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()` - functions should be used instead - -* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>` - instead of `Result<_, http::Error>` - -* `Application` renamed to a `App` - -* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev` diff --git a/Makefile b/Makefile deleted file mode 100644 index e3b8b2cf1..000000000 --- a/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -.PHONY: default build test doc book clean - -CARGO_FLAGS := --features "$(FEATURES) alpn tls" - -default: test - -build: - cargo build $(CARGO_FLAGS) - -test: build clippy - cargo test $(CARGO_FLAGS) - -doc: build - cargo doc --no-deps $(CARGO_FLAGS) diff --git a/README.md b/README.md index 4e396cb91..b092a1723 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,6 @@ -# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -Actix web is a simple, pragmatic and extremely fast web framework for Rust. - -* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols -* Streaming and pipelining -* Keep-alive and slow requests handling -* Client/server [WebSockets](https://actix.rs/docs/websockets/) support -* Transparent content compression/decompression (br, gzip, deflate) -* Configurable [request routing](https://actix.rs/docs/url-dispatch/) -* Graceful server shutdown -* Multipart streams -* Static assets -* SSL support with OpenSSL or `native-tls` -* Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/)) -* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) -* Built on top of [Actix actor framework](https://github.com/actix/actix) +Actix http ## Documentation & community resources @@ -44,30 +30,6 @@ fn main() { } ``` -### More examples - -* [Basics](https://github.com/actix/examples/tree/master/basics/) -* [Stateful](https://github.com/actix/examples/tree/master/state/) -* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/) -* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/) -* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/) -* [Tera](https://github.com/actix/examples/tree/master/template_tera/) / - [Askama](https://github.com/actix/examples/tree/master/template_askama/) templates -* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/) -* [r2d2](https://github.com/actix/examples/tree/master/r2d2/) -* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/) -* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/) -* [Json](https://github.com/actix/examples/tree/master/json/) - -You may consider checking out -[this directory](https://github.com/actix/examples/tree/master/) for more examples. - -## Benchmarks - -* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext) - -* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks). - ## License This project is licensed under either of @@ -80,5 +42,5 @@ at your option. ## Code of Conduct Contribution to the actix-web crate is organized under the terms of the -Contributor Covenant, the maintainer of actix-web, @fafhrd91, promises to +Contributor Covenant, the maintainer of actix-net, @fafhrd91, promises to intervene to uphold that code of conduct. diff --git a/build.rs b/build.rs deleted file mode 100644 index c8457944c..000000000 --- a/build.rs +++ /dev/null @@ -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 => (), - }; -} diff --git a/src/application.rs b/src/application.rs deleted file mode 100644 index d8a6cbe7b..000000000 --- a/src/application.rs +++ /dev/null @@ -1,879 +0,0 @@ -use std::rc::Rc; - -use handler::{AsyncResult, FromRequest, Handler, Responder, WrapHandler}; -use header::ContentEncoding; -use http::Method; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::Middleware; -use pipeline::{Pipeline, PipelineHandler}; -use pred::Predicate; -use resource::Resource; -use router::{ResourceDef, Router}; -use scope::Scope; -use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request}; -use with::WithFactory; - -/// Application -pub struct HttpApplication { - state: Rc, - prefix: String, - prefix_len: usize, - inner: Rc>, - filters: Option>>>, - middlewares: Rc>>>, -} - -#[doc(hidden)] -pub struct Inner { - router: Router, - encoding: ContentEncoding, -} - -impl PipelineHandler for Inner { - #[inline] - fn encoding(&self) -> ContentEncoding { - self.encoding - } - - fn handle(&self, req: &HttpRequest) -> AsyncResult { - self.router.handle(req) - } -} - -impl HttpApplication { - #[cfg(test)] - pub(crate) fn run(&self, req: Request) -> AsyncResult { - let info = self - .inner - .router - .recognize(&req, &self.state, self.prefix_len); - let req = HttpRequest::new(req, Rc::clone(&self.state), info); - - self.inner.handle(&req) - } -} - -impl HttpHandler for HttpApplication { - type Task = Pipeline>; - - fn handle(&self, msg: Request) -> Result>, Request> { - let m = { - if self.prefix_len == 0 { - true - } else { - let path = msg.path(); - path.starts_with(&self.prefix) - && (path.len() == self.prefix_len - || path.split_at(self.prefix_len).1.starts_with('/')) - } - }; - if m { - if let Some(ref filters) = self.filters { - for filter in filters { - if !filter.check(&msg, &self.state) { - return Err(msg); - } - } - } - - let info = self - .inner - .router - .recognize(&msg, &self.state, self.prefix_len); - - let inner = Rc::clone(&self.inner); - let req = HttpRequest::new(msg, Rc::clone(&self.state), info); - Ok(Pipeline::new(req, Rc::clone(&self.middlewares), inner)) - } else { - Err(msg) - } - } -} - -struct ApplicationParts { - state: S, - prefix: String, - router: Router, - encoding: ContentEncoding, - middlewares: Vec>>, - filters: Vec>>, -} - -/// Structure that follows the builder pattern for building application -/// instances. -pub struct App { - parts: Option>, -} - -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 App -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 { - App { - parts: Some(ApplicationParts { - state, - prefix: "".to_owned(), - router: Router::new(ResourceDef::prefix("")), - middlewares: Vec::new(), - filters: Vec::new(), - encoding: ContentEncoding::Auto, - }), - } - } - - /// Get reference to the application state - pub fn state(&self) -> &S { - let parts = self.parts.as_ref().expect("Use after finish"); - &parts.state - } - - /// Set application prefix. - /// - /// Only requests that match the application's prefix get - /// processed by this application. - /// - /// The application prefix always contains a leading slash (`/`). - /// If the supplied prefix does not contain leading slash, it is - /// inserted. - /// - /// Prefix should consist of valid path segments. i.e for an - /// application with the prefix `/app` any request with the paths - /// `/app`, `/app/` or `/app/test` would match, but the path - /// `/application` would not. - /// - /// In the following example only requests with an `/app/` path - /// prefix get handled. Requests with path `/app/test/` would be - /// handled, while requests with the paths `/application` or - /// `/other/...` would return `NOT FOUND`. It is also possible to - /// handle `/app` path, to do this you can register resource for - /// empty string `""` - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new() - /// .prefix("/app") - /// .resource("", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app` path - /// .resource("/", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app/` path - /// .resource("/test", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// }) - /// .finish(); - /// } - /// ``` - pub fn prefix>(mut self, prefix: P) -> App { - { - let parts = self.parts.as_mut().expect("Use after finish"); - let mut prefix = prefix.into(); - if !prefix.starts_with('/') { - prefix.insert(0, '/') - } - parts.router.set_prefix(&prefix); - parts.prefix = prefix; - } - self - } - - /// Add match predicate to application. - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn main() { - /// App::new() - /// .filter(pred::Host("www.rust-lang.org")) - /// .resource("/path", |r| r.f(|_| HttpResponse::Ok())) - /// # .finish(); - /// # } - /// ``` - pub fn filter + 'static>(mut self, p: T) -> App { - { - let parts = self.parts.as_mut().expect("Use after finish"); - parts.filters.push(Box::new(p)); - } - self - } - - /// Configure route for a specific path. - /// - /// This is a simplified version of the `App::resource()` method. - /// Handler functions need to accept one request extractor - /// argument. - /// - /// This method could be called multiple times, in that case - /// multiple routes would be registered for same resource path. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new() - /// .route("/test", http::Method::GET, |_: HttpRequest| { - /// HttpResponse::Ok() - /// }) - /// .route("/test", http::Method::POST, |_: HttpRequest| { - /// HttpResponse::MethodNotAllowed() - /// }); - /// } - /// ``` - pub fn route(mut self, path: &str, method: Method, f: F) -> App - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_route(path, method, f); - - self - } - - /// Configure scope for common root path. - /// - /// Scopes collect multiple paths under a common path prefix. - /// Scope path can contain variable path segments as resources. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().scope("/{project_id}", |scope| { - /// scope - /// .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - /// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) - /// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) - /// }); - /// } - /// ``` - /// - /// In the above example, three routes get added: - /// * /{project_id}/path1 - /// * /{project_id}/path2 - /// * /{project_id}/path3 - /// - pub fn scope(mut self, path: &str, f: F) -> App - where - F: FnOnce(Scope) -> Scope, - { - let scope = f(Scope::new(path)); - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_scope(scope); - self - } - - /// Configure resource for a specific path. - /// - /// Resources may have variable path segments. For example, a - /// resource with the path `/a/{name}/c` would match all incoming - /// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. - /// - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. This is done by - /// looking up the identifier in the `Params` object returned by - /// `HttpRequest.match_info()` method. - /// - /// By default, each segment matches the regular expression `[^{}/]+`. - /// - /// You can also specify a custom regex in the form `{identifier:regex}`: - /// - /// For instance, to route `GET`-requests on any route matching - /// `/users/{userid}/{friend}` and store `userid` and `friend` in - /// the exposed `Params` object: - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().resource("/users/{userid}/{friend}", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// }); - /// } - /// ``` - pub fn resource(mut self, path: &str, f: F) -> App - where - F: FnOnce(&mut Resource) -> R + 'static, - { - { - let parts = self.parts.as_mut().expect("Use after finish"); - - // create resource - let mut resource = Resource::new(ResourceDef::new(path)); - - // configure - f(&mut resource); - - parts.router.register_resource(resource); - } - self - } - - /// Configure resource for a specific path. - #[doc(hidden)] - pub fn register_resource(&mut self, resource: Resource) { - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_resource(resource); - } - - /// Default resource to be used if no matching route could be found. - pub fn default_resource(mut self, f: F) -> App - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // create and configure default resource - let mut resource = Resource::new(ResourceDef::new("")); - f(&mut resource); - - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_default_resource(resource.into()); - - self - } - - /// Set default content encoding. `ContentEncoding::Auto` is set by default. - pub fn default_encoding(mut self, encoding: ContentEncoding) -> App { - { - 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 { - /// 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(mut self, name: T, url: U) -> App - where - T: AsRef, - U: AsRef, - { - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_external(name.as_ref(), ResourceDef::external(url.as_ref())); - self - } - - /// Configure handler for specific path prefix. - /// - /// A path prefix consists of valid path segments, i.e for the - /// prefix `/app` any request with the paths `/app`, `/app/` or - /// `/app/test` would match, but the path `/application` would - /// not. - /// - /// Path tail is available as `tail` parameter in request's match_dict. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().handler("/app", |req: &HttpRequest| match *req.method() { - /// http::Method::GET => HttpResponse::Ok(), - /// http::Method::POST => HttpResponse::MethodNotAllowed(), - /// _ => HttpResponse::NotFound(), - /// }); - /// } - /// ``` - pub fn handler>(mut self, path: &str, handler: H) -> App { - { - let mut path = path.trim().trim_right_matches('/').to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/'); - }; - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_handler(&path, Box::new(WrapHandler::new(handler)), None); - } - self - } - - /// Register a middleware. - pub fn middleware>(mut self, mw: M) -> App { - self.parts - .as_mut() - .expect("Use after finish") - .middlewares - .push(Box::new(mw)); - self - } - - /// Run external configuration as part of the application building - /// process - /// - /// This function is useful for moving parts of configuration to a - /// different module or event library. For example we can move - /// some of the resources' configuration to different module. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{fs, middleware, App, HttpResponse}; - /// - /// // this function could be located in different module - /// fn config(app: App) -> App { - /// app.resource("/test", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// }) - /// } - /// - /// fn main() { - /// let app = App::new() - /// .middleware(middleware::Logger::default()) - /// .configure(config) // <- register resources - /// .handler("/static", fs::StaticFiles::new(".").unwrap()); - /// } - /// ``` - pub fn configure(self, cfg: F) -> App - where - F: Fn(App) -> App, - { - cfg(self) - } - - /// Finish application configuration and create `HttpHandler` object. - pub fn finish(&mut self) -> HttpApplication { - let mut parts = self.parts.take().expect("Use after finish"); - let prefix = parts.prefix.trim().trim_right_matches('/'); - parts.router.finish(); - - let inner = Rc::new(Inner { - router: parts.router, - encoding: parts.encoding, - }); - let filters = if parts.filters.is_empty() { - None - } else { - Some(parts.filters) - }; - - HttpApplication { - inner, - filters, - state: Rc::new(parts.state), - middlewares: Rc::new(parts.middlewares), - prefix: prefix.to_owned(), - prefix_len: prefix.len(), - } - } - - /// Convenience method for creating `Box` 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>> { - Box::new(BoxedApplication { app: self.finish() }) - } -} - -struct BoxedApplication { - app: HttpApplication, -} - -impl HttpHandler for BoxedApplication { - type Task = Box; - - fn handle(&self, req: Request) -> Result { - self.app.handle(req).map(|t| { - let task: Self::Task = Box::new(t); - task - }) - } -} - -impl IntoHttpHandler for App { - type Handler = HttpApplication; - - fn into_handler(mut self) -> HttpApplication { - self.finish() - } -} - -impl<'a, S: 'static> IntoHttpHandler for &'a mut App { - type Handler = HttpApplication; - - fn into_handler(self) -> HttpApplication { - self.finish() - } -} - -#[doc(hidden)] -impl Iterator for App { - type Item = HttpApplication; - - fn next(&mut self) -> Option { - if self.parts.is_some() { - Some(self.finish()) - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use body::{Binary, Body}; - use http::StatusCode; - use httprequest::HttpRequest; - use httpresponse::HttpResponse; - use pred; - use test::{TestRequest, TestServer}; - - #[test] - fn test_default_resource() { - let app = App::new() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let app = App::new() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) - .finish(); - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[test] - fn test_unhandled_prefix() { - let app = App::new() - .prefix("/test") - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - let ctx = TestRequest::default().request(); - assert!(app.handle(ctx).is_err()); - } - - #[test] - fn test_state() { - let app = App::with_state(10) - .resource("/", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - let req = TestRequest::with_state(10).request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_prefix() { - let app = App::new() - .prefix("/test") - .resource("/blah", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - let req = TestRequest::with_uri("/test").request(); - let resp = app.handle(req); - assert!(resp.is_ok()); - - let req = TestRequest::with_uri("/test/").request(); - let resp = app.handle(req); - assert!(resp.is_ok()); - - let req = TestRequest::with_uri("/test/blah").request(); - let resp = app.handle(req); - assert!(resp.is_ok()); - - let req = TestRequest::with_uri("/testing").request(); - let resp = app.handle(req); - assert!(resp.is_err()); - } - - #[test] - fn test_handler() { - let app = App::new() - .handler("/test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_handler2() { - let app = App::new() - .handler("test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_handler_with_prefix() { - let app = App::new() - .prefix("prefix") - .handler("/test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/prefix/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/prefix/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/prefix/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/prefix/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/prefix/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_route() { - let app = App::new() - .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) - .route("/test", Method::POST, |_: HttpRequest| { - HttpResponse::Created() - }).finish(); - - let req = TestRequest::with_uri("/test").method(Method::GET).request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - - let req = TestRequest::with_uri("/test") - .method(Method::HEAD) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_handler_prefix() { - let app = App::new() - .prefix("/app") - .handler("/test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_option_responder() { - let app = App::new() - .resource("/none", |r| r.f(|_| -> Option<&'static str> { None })) - .resource("/some", |r| r.f(|_| Some("some"))) - .finish(); - - let req = TestRequest::with_uri("/none").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/some").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some"))); - } - - #[test] - fn test_filter() { - let mut srv = TestServer::with_factory(|| { - App::new() - .filter(pred::Get()) - .handler("/test", |_: &_| HttpResponse::Ok()) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let request = srv.post().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_prefix_root() { - let mut srv = TestServer::with_factory(|| { - App::new() - .prefix("/test") - .resource("/", |r| r.f(|_| HttpResponse::Ok())) - .resource("", |r| r.f(|_| HttpResponse::Created())) - }); - - let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::CREATED); - } - -} diff --git a/src/body.rs b/src/body.rs index a93db1e92..e689b704c 100644 --- a/src/body.rs +++ b/src/body.rs @@ -3,10 +3,7 @@ use futures::Stream; use std::sync::Arc; use std::{fmt, mem}; -use context::ActorHttpContext; use error::Error; -use handler::Responder; -use httprequest::HttpRequest; use httpresponse::HttpResponse; /// Type represent streaming body @@ -21,8 +18,8 @@ pub enum Body { /// Unspecified streaming response. Developer is responsible for setting /// right `Content-Length` or `Transfer-Encoding` headers. Streaming(BodyStream), - /// Special body type for actor response. - Actor(Box), + // /// Special body type for actor response. + // Actor(Box), } /// Represents various types of binary body. @@ -45,7 +42,7 @@ impl Body { #[inline] pub fn is_streaming(&self) -> bool { match *self { - Body::Streaming(_) | Body::Actor(_) => true, + Body::Streaming(_) => true, _ => false, } } @@ -94,7 +91,7 @@ impl PartialEq for Body { Body::Binary(ref b2) => b == b2, _ => false, }, - Body::Streaming(_) | Body::Actor(_) => false, + Body::Streaming(_) => false, } } } @@ -105,7 +102,6 @@ impl fmt::Debug for Body { Body::Empty => write!(f, "Body::Empty"), Body::Binary(ref b) => write!(f, "Body::Binary({:?})", b), Body::Streaming(_) => write!(f, "Body::Streaming(_)"), - Body::Actor(_) => write!(f, "Body::Actor(_)"), } } } @@ -119,12 +115,6 @@ where } } -impl From> for Body { - fn from(ctx: Box) -> Body { - Body::Actor(ctx) - } -} - impl Binary { #[inline] /// Returns `true` if body is empty @@ -254,17 +244,6 @@ impl AsRef<[u8]> for Binary { } } -impl Responder for Binary { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(HttpResponse::build_from(req) - .content_type("application/octet-stream") - .body(self)) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/client/connector.rs b/src/client/connector.rs deleted file mode 100644 index 07c7b646d..000000000 --- a/src/client/connector.rs +++ /dev/null @@ -1,1344 +0,0 @@ -use std::collections::{HashMap, VecDeque}; -use std::net::Shutdown; -use std::time::{Duration, Instant}; -use std::{fmt, io, mem, time}; - -use actix::resolver::{Connect as ResolveConnect, Resolver, ResolverError}; -use actix::{ - fut, Actor, ActorFuture, ActorResponse, Addr, AsyncContext, Context, - ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised, - SystemService, WrapFuture, -}; - -use futures::sync::{mpsc, oneshot}; -use futures::{Async, Future, Poll}; -use http::{Error as HttpError, HttpTryFrom, Uri}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_timer::Delay; - -#[cfg(any(feature = "alpn", feature = "ssl"))] -use { - openssl::ssl::{Error as SslError, SslConnector, SslMethod}, - tokio_openssl::SslConnectorExt, -}; - -#[cfg(all( - feature = "tls", - not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")) -))] -use { - native_tls::{Error as SslError, TlsConnector as NativeTlsConnector}, - tokio_tls::TlsConnector as SslConnector, -}; - -#[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls", feature = "ssl")) -))] -use { - rustls::ClientConfig, std::io::Error as SslError, std::sync::Arc, - tokio_rustls::ClientConfigExt, webpki::DNSNameRef, webpki_roots, -}; - -#[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls", feature = "ssl")) -))] -type SslConnector = Arc; - -#[cfg(not(any( - feature = "alpn", - feature = "ssl", - feature = "tls", - feature = "rust-tls" -)))] -type SslConnector = (); - -use server::IoStream; -use {HAS_OPENSSL, HAS_RUSTLS, HAS_TLS}; - -/// Client connector usage stats -#[derive(Default, Message)] -pub struct ClientConnectorStats { - /// Number of waited-on connections - pub waits: usize, - /// Size of the wait queue - pub wait_queue: usize, - /// Number of reused connections - pub reused: usize, - /// Number of opened connections - pub opened: usize, - /// Number of closed connections - pub closed: usize, - /// Number of connections with errors - pub errors: usize, - /// Number of connection timeouts - pub timeouts: usize, -} - -#[derive(Debug)] -/// `Connect` type represents a message that can be sent to -/// `ClientConnector` with a connection request. -pub struct Connect { - pub(crate) uri: Uri, - pub(crate) wait_timeout: Duration, - pub(crate) conn_timeout: Duration, -} - -impl Connect { - /// Create `Connect` message for specified `Uri` - pub fn new(uri: U) -> Result - where - Uri: HttpTryFrom, - { - Ok(Connect { - uri: Uri::try_from(uri).map_err(|e| e.into())?, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), - }) - } - - /// Connection timeout, i.e. max time to connect to remote host. - /// Set to 1 second by default. - pub fn conn_timeout(mut self, timeout: Duration) -> Self { - self.conn_timeout = timeout; - self - } - - /// If connection pool limits are enabled, wait time indicates - /// max time to wait for a connection to become available. - /// Set to 5 seconds by default. - pub fn wait_timeout(mut self, timeout: Duration) -> Self { - self.wait_timeout = timeout; - self - } -} - -impl Message for Connect { - type Result = Result; -} - -/// Pause connection process for `ClientConnector` -/// -/// All connect requests enter wait state during connector pause. -pub struct Pause { - time: Option, -} - -impl Pause { - /// Create message with pause duration parameter - pub fn new(time: Duration) -> Pause { - Pause { time: Some(time) } - } -} - -impl Default for Pause { - fn default() -> Pause { - Pause { time: None } - } -} - -impl Message for Pause { - type Result = (); -} - -/// Resume connection process for `ClientConnector` -#[derive(Message)] -pub struct Resume; - -/// A set of errors that can occur while connecting to an HTTP host -#[derive(Fail, Debug)] -pub enum ClientConnectorError { - /// Invalid URL - #[fail(display = "Invalid URL")] - InvalidUrl, - - /// SSL feature is not enabled - #[fail(display = "SSL is not supported")] - SslIsNotSupported, - - /// SSL error - #[cfg(any( - feature = "tls", - feature = "alpn", - feature = "ssl", - feature = "rust-tls", - ))] - #[fail(display = "{}", _0)] - SslError(#[cause] SslError), - - /// Resolver error - #[fail(display = "{}", _0)] - Resolver(#[cause] ResolverError), - - /// Connection took too long - #[fail(display = "Timeout while establishing connection")] - Timeout, - - /// Connector has been disconnected - #[fail(display = "Internal error: connector has been disconnected")] - Disconnected, - - /// Connection IO error - #[fail(display = "{}", _0)] - IoError(#[cause] io::Error), -} - -impl From for ClientConnectorError { - fn from(err: ResolverError) -> ClientConnectorError { - match err { - ResolverError::Timeout => ClientConnectorError::Timeout, - _ => ClientConnectorError::Resolver(err), - } - } -} - -struct Waiter { - tx: oneshot::Sender>, - wait: Instant, - conn_timeout: Duration, -} - -enum Paused { - No, - Yes, - Timeout(Instant, Delay), -} - -impl Paused { - fn is_paused(&self) -> bool { - match *self { - Paused::No => false, - _ => true, - } - } -} - -/// `ClientConnector` type is responsible for transport layer of a -/// client connection. -pub struct ClientConnector { - #[allow(dead_code)] - connector: SslConnector, - - stats: ClientConnectorStats, - subscriber: Option>, - - acq_tx: mpsc::UnboundedSender, - acq_rx: Option>, - - resolver: Option>, - conn_lifetime: Duration, - conn_keep_alive: Duration, - limit: usize, - limit_per_host: usize, - acquired: usize, - acquired_per_host: HashMap, - available: HashMap>, - to_close: Vec, - waiters: Option>>, - wait_timeout: Option<(Instant, Delay)>, - paused: Paused, -} - -impl Actor for ClientConnector { - type Context = Context; - - fn started(&mut self, ctx: &mut Self::Context) { - if self.resolver.is_none() { - self.resolver = Some(Resolver::from_registry()) - } - self.collect_periodic(ctx); - ctx.add_stream(self.acq_rx.take().unwrap()); - ctx.spawn(Maintenance); - } -} - -impl Supervised for ClientConnector {} - -impl SystemService for ClientConnector {} - -impl Default for ClientConnector { - fn default() -> ClientConnector { - let connector = { - #[cfg(all(any(feature = "alpn", feature = "ssl")))] - { - SslConnector::builder(SslMethod::tls()).unwrap().build() - } - - #[cfg(all( - feature = "tls", - not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")) - ))] - { - NativeTlsConnector::builder().build().unwrap().into() - } - - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls", feature = "ssl")) - ))] - { - let mut config = ClientConfig::new(); - config - .root_store - .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - Arc::new(config) - } - - #[cfg_attr(rustfmt, rustfmt_skip)] - #[cfg(not(any( - feature = "alpn", feature = "ssl", feature = "tls", feature = "rust-tls")))] - { - () - } - }; - - #[cfg_attr(feature = "cargo-clippy", allow(clippy::let_unit_value))] - ClientConnector::with_connector_impl(connector) - } -} - -impl ClientConnector { - #[cfg(any(feature = "alpn", feature = "ssl"))] - /// Create `ClientConnector` actor with custom `SslConnector` instance. - /// - /// By default `ClientConnector` uses very a simple SSL configuration. - /// With `with_connector` method it is possible to use a custom - /// `SslConnector` object. - /// - /// ```rust,ignore - /// # #![cfg(feature="alpn")] - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::{future, Future}; - /// # use std::io::Write; - /// # use actix_web::actix::Actor; - /// extern crate openssl; - /// use actix_web::{actix, client::ClientConnector, client::Connect}; - /// - /// use openssl::ssl::{SslConnector, SslMethod}; - /// - /// fn main() { - /// actix::run(|| { - /// // Start `ClientConnector` with custom `SslConnector` - /// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); - /// let conn = ClientConnector::with_connector(ssl_conn).start(); - /// - /// conn.send( - /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host - /// .map_err(|_| ()) - /// .and_then(|res| { - /// if let Ok(mut stream) = res { - /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); - /// } - /// # actix::System::current().stop(); - /// Ok(()) - /// }) - /// }); - /// } - /// ``` - pub fn with_connector(connector: SslConnector) -> ClientConnector { - // keep level of indirection for docstrings matching featureflags - Self::with_connector_impl(connector) - } - - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "ssl", feature = "tls")) - ))] - /// Create `ClientConnector` actor with custom `SslConnector` instance. - /// - /// By default `ClientConnector` uses very a simple SSL configuration. - /// With `with_connector` method it is possible to use a custom - /// `SslConnector` object. - /// - /// ```rust - /// # #![cfg(feature = "rust-tls")] - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::{future, Future}; - /// # use std::io::Write; - /// # use actix_web::actix::Actor; - /// extern crate rustls; - /// extern crate webpki_roots; - /// use actix_web::{actix, client::ClientConnector, client::Connect}; - /// - /// use rustls::ClientConfig; - /// use std::sync::Arc; - /// - /// fn main() { - /// actix::run(|| { - /// // Start `ClientConnector` with custom `ClientConfig` - /// let mut config = ClientConfig::new(); - /// config - /// .root_store - /// .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - /// let conn = ClientConnector::with_connector(Arc::new(config)).start(); - /// - /// conn.send( - /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host - /// .map_err(|_| ()) - /// .and_then(|res| { - /// if let Ok(mut stream) = res { - /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); - /// } - /// # actix::System::current().stop(); - /// Ok(()) - /// }) - /// }); - /// } - /// ``` - pub fn with_connector(connector: ClientConfig) -> ClientConnector { - // keep level of indirection for docstrings matching featureflags - Self::with_connector_impl(Arc::new(connector)) - } - - #[cfg(all( - feature = "tls", - not(any(feature = "ssl", feature = "alpn", feature = "rust-tls")) - ))] - /// Create `ClientConnector` actor with custom `SslConnector` instance. - /// - /// By default `ClientConnector` uses very a simple SSL configuration. - /// With `with_connector` method it is possible to use a custom - /// `SslConnector` object. - /// - /// ```rust - /// # #![cfg(feature = "tls")] - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::{future, Future}; - /// # use std::io::Write; - /// # use actix_web::actix::Actor; - /// extern crate native_tls; - /// extern crate webpki_roots; - /// use native_tls::TlsConnector; - /// use actix_web::{actix, client::ClientConnector, client::Connect}; - /// - /// fn main() { - /// actix::run(|| { - /// let connector = TlsConnector::new().unwrap(); - /// let conn = ClientConnector::with_connector(connector.into()).start(); - /// - /// conn.send( - /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host - /// .map_err(|_| ()) - /// .and_then(|res| { - /// if let Ok(mut stream) = res { - /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); - /// } - /// # actix::System::current().stop(); - /// Ok(()) - /// }) - /// }); - /// } - /// ``` - pub fn with_connector(connector: SslConnector) -> ClientConnector { - // keep level of indirection for docstrings matching featureflags - Self::with_connector_impl(connector) - } - - #[inline] - fn with_connector_impl(connector: SslConnector) -> ClientConnector { - let (tx, rx) = mpsc::unbounded(); - - ClientConnector { - connector, - stats: ClientConnectorStats::default(), - subscriber: None, - acq_tx: tx, - acq_rx: Some(rx), - resolver: None, - conn_lifetime: Duration::from_secs(75), - conn_keep_alive: Duration::from_secs(15), - limit: 100, - limit_per_host: 0, - acquired: 0, - acquired_per_host: HashMap::new(), - available: HashMap::new(), - to_close: Vec::new(), - waiters: Some(HashMap::new()), - wait_timeout: None, - paused: Paused::No, - } - } - - /// Set total number of simultaneous connections. - /// - /// If limit is 0, the connector has no limit. - /// The default limit size is 100. - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - /// Set total number of simultaneous connections to the same endpoint. - /// - /// Endpoints are the same if they have equal (host, port, ssl) triplets. - /// If limit is 0, the connector has no limit. The default limit size is 0. - pub fn limit_per_host(mut self, limit: usize) -> Self { - self.limit_per_host = limit; - self - } - - /// Set keep-alive period for opened connection. - /// - /// Keep-alive period is the period between connection usage. If - /// the delay between repeated usages of the same connection - /// exceeds this period, the connection is closed. - /// Default keep-alive period is 15 seconds. - pub fn conn_keep_alive(mut self, dur: Duration) -> Self { - self.conn_keep_alive = dur; - self - } - - /// Set max lifetime period for connection. - /// - /// Connection lifetime is max lifetime of any opened connection - /// until it is closed regardless of keep-alive period. - /// Default lifetime period is 75 seconds. - pub fn conn_lifetime(mut self, dur: Duration) -> Self { - self.conn_lifetime = dur; - self - } - - /// Subscribe for connector stats. Only one subscriber is supported. - pub fn stats(mut self, subs: Recipient) -> Self { - self.subscriber = Some(subs); - self - } - - /// Use custom resolver actor - pub fn resolver(mut self, addr: Addr) -> Self { - self.resolver = Some(addr); - self - } - - fn acquire(&mut self, key: &Key) -> Acquire { - // check limits - if self.limit > 0 { - if self.acquired >= self.limit { - return Acquire::NotAvailable; - } - if self.limit_per_host > 0 { - if let Some(per_host) = self.acquired_per_host.get(key) { - if *per_host >= self.limit_per_host { - return Acquire::NotAvailable; - } - } - } - } else if self.limit_per_host > 0 { - if let Some(per_host) = self.acquired_per_host.get(key) { - if *per_host >= self.limit_per_host { - return Acquire::NotAvailable; - } - } - } - - self.reserve(key); - - // check if open connection is available - // cleanup stale connections at the same time - if let Some(ref mut connections) = self.available.get_mut(key) { - let now = Instant::now(); - while let Some(conn) = connections.pop_back() { - // check if it still usable - if (now - conn.0) > self.conn_keep_alive - || (now - conn.1.ts) > self.conn_lifetime - { - self.stats.closed += 1; - self.to_close.push(conn.1); - } else { - let mut conn = conn.1; - let mut buf = [0; 2]; - match conn.stream().read(&mut buf) { - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), - Ok(n) if n > 0 => { - self.stats.closed += 1; - self.to_close.push(conn); - continue; - } - Ok(_) | Err(_) => continue, - } - return Acquire::Acquired(conn); - } - } - } - Acquire::Available - } - - fn reserve(&mut self, key: &Key) { - self.acquired += 1; - let per_host = if let Some(per_host) = self.acquired_per_host.get(key) { - *per_host - } else { - 0 - }; - self.acquired_per_host.insert(key.clone(), per_host + 1); - } - - fn release_key(&mut self, key: &Key) { - if self.acquired > 0 { - self.acquired -= 1; - } - let per_host = if let Some(per_host) = self.acquired_per_host.get(key) { - *per_host - } else { - return; - }; - if per_host > 1 { - self.acquired_per_host.insert(key.clone(), per_host - 1); - } else { - self.acquired_per_host.remove(key); - } - } - - fn collect_periodic(&mut self, ctx: &mut Context) { - // check connections for shutdown - let mut idx = 0; - while idx < self.to_close.len() { - match AsyncWrite::shutdown(&mut self.to_close[idx]) { - Ok(Async::NotReady) => idx += 1, - _ => { - self.to_close.swap_remove(idx); - } - } - } - - // re-schedule next collect period - ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx)); - - // send stats - let mut stats = mem::replace(&mut self.stats, ClientConnectorStats::default()); - if let Some(ref mut subscr) = self.subscriber { - if let Some(ref waiters) = self.waiters { - for w in waiters.values() { - stats.wait_queue += w.len(); - } - } - let _ = subscr.do_send(stats); - } - } - - // TODO: waiters should be sorted by deadline. maybe timewheel? - fn collect_waiters(&mut self) { - let now = Instant::now(); - let mut next = None; - - for waiters in self.waiters.as_mut().unwrap().values_mut() { - let mut idx = 0; - while idx < waiters.len() { - let wait = waiters[idx].wait; - if wait <= now { - self.stats.timeouts += 1; - let waiter = waiters.swap_remove_back(idx).unwrap(); - let _ = waiter.tx.send(Err(ClientConnectorError::Timeout)); - } else { - if let Some(n) = next { - if wait < n { - next = Some(wait); - } - } else { - next = Some(wait); - } - idx += 1; - } - } - } - - if next.is_some() { - self.install_wait_timeout(next.unwrap()); - } - } - - fn install_wait_timeout(&mut self, time: Instant) { - if let Some(ref mut wait) = self.wait_timeout { - if wait.0 < time { - return; - } - } - - let mut timeout = Delay::new(time); - let _ = timeout.poll(); - self.wait_timeout = Some((time, timeout)); - } - - fn wait_for( - &mut self, key: Key, wait: Duration, conn_timeout: Duration, - ) -> oneshot::Receiver> { - // connection is not available, wait - let (tx, rx) = oneshot::channel(); - - let wait = Instant::now() + wait; - self.install_wait_timeout(wait); - - let waiter = Waiter { - tx, - wait, - conn_timeout, - }; - self.waiters - .as_mut() - .unwrap() - .entry(key) - .or_insert_with(VecDeque::new) - .push_back(waiter); - rx - } - - fn check_availibility(&mut self, ctx: &mut Context) { - // check waiters - let mut act_waiters = self.waiters.take().unwrap(); - - for (key, ref mut waiters) in &mut act_waiters { - while let Some(waiter) = waiters.pop_front() { - if waiter.tx.is_canceled() { - continue; - } - - match self.acquire(key) { - Acquire::Acquired(mut conn) => { - // use existing connection - self.stats.reused += 1; - conn.pool = - Some(AcquiredConn(key.clone(), Some(self.acq_tx.clone()))); - let _ = waiter.tx.send(Ok(conn)); - } - Acquire::NotAvailable => { - waiters.push_front(waiter); - break; - } - Acquire::Available => { - // create new connection - self.connect_waiter(&key, waiter, ctx); - } - } - } - } - - self.waiters = Some(act_waiters); - } - - fn connect_waiter(&mut self, key: &Key, waiter: Waiter, ctx: &mut Context) { - let key = key.clone(); - let conn = AcquiredConn(key.clone(), Some(self.acq_tx.clone())); - - let key2 = key.clone(); - fut::WrapFuture::::actfuture( - self.resolver.as_ref().unwrap().send( - ResolveConnect::host_and_port(&conn.0.host, conn.0.port) - .timeout(waiter.conn_timeout), - ), - ).map_err(move |_, act, _| { - act.release_key(&key2); - () - }).and_then(move |res, act, _| { - #[cfg(any(feature = "alpn", feature = "ssl"))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector - .connect_async(&key.host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } - } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(all(feature = "tls", not(any(feature = "alpn", feature = "ssl"))))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector - .connect(&conn.0.host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } - } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "ssl", feature = "tls")) - ))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - let host = DNSNameRef::try_from_ascii_str(&key.host).unwrap(); - fut::Either::A( - act.connector - .connect_async(host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } - } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(not(any( - feature = "alpn", - feature = "ssl", - feature = "tls", - feature = "rust-tls" - )))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::err(()) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - let _ = - waiter.tx.send(Err(ClientConnectorError::SslIsNotSupported)); - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - }; - fut::ok(()) - } - } - }).spawn(ctx); - } -} - -impl Handler for ClientConnector { - type Result = (); - - fn handle(&mut self, msg: Pause, _: &mut Self::Context) { - if let Some(time) = msg.time { - let when = Instant::now() + time; - let mut timeout = Delay::new(when); - let _ = timeout.poll(); - self.paused = Paused::Timeout(when, timeout); - } else { - self.paused = Paused::Yes; - } - } -} - -impl Handler for ClientConnector { - type Result = (); - - fn handle(&mut self, _: Resume, _: &mut Self::Context) { - self.paused = Paused::No; - } -} - -impl Handler for ClientConnector { - type Result = ActorResponse; - - fn handle(&mut self, msg: Connect, ctx: &mut Self::Context) -> Self::Result { - let uri = &msg.uri; - let wait_timeout = msg.wait_timeout; - let conn_timeout = msg.conn_timeout; - - // host name is required - if uri.host().is_none() { - return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)); - } - - // supported protocols - let proto = match uri.scheme_part() { - Some(scheme) => match Protocol::from(scheme.as_str()) { - Some(proto) => proto, - None => { - return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)) - } - }, - None => return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)), - }; - - // check ssl availability - if proto.is_secure() && !HAS_OPENSSL && !HAS_TLS && !HAS_RUSTLS { - return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported)); - } - - let host = uri.host().unwrap().to_owned(); - let port = uri.port().unwrap_or_else(|| proto.port()); - let key = Key { - host, - port, - ssl: proto.is_secure(), - }; - - // check pause state - if self.paused.is_paused() { - let rx = self.wait_for(key.clone(), wait_timeout, conn_timeout); - self.stats.waits += 1; - return ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - match err { - ClientConnectorError::Timeout => (), - _ => { - act.release_key(&key); - } - } - act.stats.errors += 1; - act.check_availibility(ctx); - fut::err(err) - } - }), - ); - } - - // do not re-use websockets connection - if !proto.is_http() { - let (tx, rx) = oneshot::channel(); - let wait = Instant::now() + wait_timeout; - let waiter = Waiter { - tx, - wait, - conn_timeout, - }; - self.connect_waiter(&key, waiter, ctx); - - return ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - act.stats.errors += 1; - act.release_key(&key); - act.check_availibility(ctx); - fut::err(err) - } - }), - ); - } - - // acquire connection - match self.acquire(&key) { - Acquire::Acquired(mut conn) => { - // use existing connection - conn.pool = Some(AcquiredConn(key, Some(self.acq_tx.clone()))); - self.stats.reused += 1; - ActorResponse::async(fut::ok(conn)) - } - Acquire::NotAvailable => { - // connection is not available, wait - let rx = self.wait_for(key.clone(), wait_timeout, conn_timeout); - self.stats.waits += 1; - - ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - match err { - ClientConnectorError::Timeout => (), - _ => { - act.release_key(&key); - } - } - act.stats.errors += 1; - act.check_availibility(ctx); - fut::err(err) - } - }), - ) - } - Acquire::Available => { - let (tx, rx) = oneshot::channel(); - let wait = Instant::now() + wait_timeout; - let waiter = Waiter { - tx, - wait, - conn_timeout, - }; - self.connect_waiter(&key, waiter, ctx); - - ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - act.stats.errors += 1; - act.release_key(&key); - act.check_availibility(ctx); - fut::err(err) - } - }), - ) - } - } - } -} - -impl StreamHandler for ClientConnector { - fn handle(&mut self, msg: AcquiredConnOperation, ctx: &mut Context) { - match msg { - AcquiredConnOperation::Close(conn) => { - self.release_key(&conn.key); - self.to_close.push(conn); - self.stats.closed += 1; - } - AcquiredConnOperation::Release(conn) => { - self.release_key(&conn.key); - if (Instant::now() - conn.ts) < self.conn_lifetime { - self.available - .entry(conn.key.clone()) - .or_insert_with(VecDeque::new) - .push_back(Conn(Instant::now(), conn)); - } else { - self.to_close.push(conn); - self.stats.closed += 1; - } - } - AcquiredConnOperation::ReleaseKey(key) => { - // closed - self.stats.closed += 1; - self.release_key(&key); - } - } - - self.check_availibility(ctx); - } -} - -struct Maintenance; - -impl fut::ActorFuture for Maintenance { - type Item = (); - type Error = (); - type Actor = ClientConnector; - - fn poll( - &mut self, act: &mut ClientConnector, ctx: &mut Context, - ) -> Poll { - // check pause duration - if let Paused::Timeout(inst, _) = act.paused { - if inst <= Instant::now() { - act.paused = Paused::No; - } - } - - // collect wait timers - act.collect_waiters(); - - // check waiters - act.check_availibility(ctx); - - Ok(Async::NotReady) - } -} - -#[derive(PartialEq, Hash, Debug, Clone, Copy)] -enum Protocol { - Http, - Https, - Ws, - Wss, -} - -impl Protocol { - fn from(s: &str) -> Option { - match s { - "http" => Some(Protocol::Http), - "https" => Some(Protocol::Https), - "ws" => Some(Protocol::Ws), - "wss" => Some(Protocol::Wss), - _ => None, - } - } - - fn is_http(self) -> bool { - match self { - Protocol::Https | Protocol::Http => true, - _ => false, - } - } - - fn is_secure(self) -> bool { - match self { - Protocol::Https | Protocol::Wss => true, - _ => false, - } - } - - fn port(self) -> u16 { - match self { - Protocol::Http | Protocol::Ws => 80, - Protocol::Https | Protocol::Wss => 443, - } - } -} - -#[derive(Hash, Eq, PartialEq, Clone, Debug)] -struct Key { - host: String, - port: u16, - ssl: bool, -} - -impl Key { - fn empty() -> Key { - Key { - host: String::new(), - port: 0, - ssl: false, - } - } -} - -#[derive(Debug)] -struct Conn(Instant, Connection); - -enum Acquire { - Acquired(Connection), - Available, - NotAvailable, -} - -enum AcquiredConnOperation { - Close(Connection), - Release(Connection), - ReleaseKey(Key), -} - -struct AcquiredConn(Key, Option>); - -impl AcquiredConn { - fn close(&mut self, conn: Connection) { - if let Some(tx) = self.1.take() { - let _ = tx.unbounded_send(AcquiredConnOperation::Close(conn)); - } - } - fn release(&mut self, conn: Connection) { - if let Some(tx) = self.1.take() { - let _ = tx.unbounded_send(AcquiredConnOperation::Release(conn)); - } - } -} - -impl Drop for AcquiredConn { - fn drop(&mut self) { - if let Some(tx) = self.1.take() { - let _ = tx.unbounded_send(AcquiredConnOperation::ReleaseKey(self.0.clone())); - } - } -} - -/// HTTP client connection -pub struct Connection { - key: Key, - stream: Box, - pool: Option, - ts: Instant, -} - -impl fmt::Debug for Connection { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Connection {}:{}", self.key.host, self.key.port) - } -} - -impl Connection { - fn new(key: Key, pool: Option, stream: Box) -> Self { - Connection { - key, - stream, - pool, - ts: Instant::now(), - } - } - - /// Raw IO stream - pub fn stream(&mut self) -> &mut IoStream { - &mut *self.stream - } - - /// Create a new connection from an IO Stream - /// - /// The stream can be a `UnixStream` if the Unix-only "uds" feature is enabled. - /// - /// See also `ClientRequestBuilder::with_connection()`. - pub fn from_stream(io: T) -> Connection { - Connection::new(Key::empty(), None, Box::new(io)) - } - - /// Close connection - pub fn close(mut self) { - if let Some(mut pool) = self.pool.take() { - pool.close(self) - } - } - - /// Release this connection to the connection pool - pub fn release(mut self) { - if let Some(mut pool) = self.pool.take() { - pool.release(self) - } - } -} - -impl IoStream for Connection { - fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { - IoStream::shutdown(&mut *self.stream, how) - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - IoStream::set_nodelay(&mut *self.stream, nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - IoStream::set_linger(&mut *self.stream, dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - IoStream::set_keepalive(&mut *self.stream, dur) - } -} - -impl io::Read for Connection { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.stream.read(buf) - } -} - -impl AsyncRead for Connection {} - -impl io::Write for Connection { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.stream.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.stream.flush() - } -} - -impl AsyncWrite for Connection { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.stream.shutdown() - } -} - -#[cfg(feature = "tls")] -use tokio_tls::TlsStream; - -#[cfg(feature = "tls")] -/// This is temp solution untile actix-net migration -impl IoStream for TlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = self.get_mut().shutdown(); - Ok(()) - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().get_mut().set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_linger(dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_keepalive(dur) - } -} diff --git a/src/client/mod.rs b/src/client/mod.rs deleted file mode 100644 index a0713fe32..000000000 --- a/src/client/mod.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! Http client api -//! -//! ```rust -//! # extern crate actix_web; -//! # extern crate futures; -//! # extern crate tokio; -//! # use futures::Future; -//! # use std::process; -//! use actix_web::{actix, client}; -//! -//! fn main() { -//! actix::run( -//! || client::get("http://www.rust-lang.org") // <- Create request builder -//! .header("User-Agent", "Actix-web") -//! .finish().unwrap() -//! .send() // <- Send http request -//! .map_err(|_| ()) -//! .and_then(|response| { // <- server http response -//! println!("Response: {:?}", response); -//! # actix::System::current().stop(); -//! Ok(()) -//! }) -//! ); -//! } -//! ``` -mod connector; -mod parser; -mod pipeline; -mod request; -mod response; -mod writer; - -pub use self::connector::{ - ClientConnector, ClientConnectorError, ClientConnectorStats, Connect, Connection, - Pause, Resume, -}; -pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; -pub(crate) use self::pipeline::Pipeline; -pub use self::pipeline::{SendRequest, SendRequestError}; -pub use self::request::{ClientRequest, ClientRequestBuilder}; -pub use self::response::ClientResponse; -pub(crate) use self::writer::HttpClientWriter; - -use error::ResponseError; -use http::Method; -use httpresponse::HttpResponse; - -/// Convert `SendRequestError` to a `HttpResponse` -impl ResponseError for SendRequestError { - fn error_response(&self) -> HttpResponse { - match *self { - SendRequestError::Timeout => HttpResponse::GatewayTimeout(), - SendRequestError::Connector(_) => HttpResponse::BadGateway(), - _ => HttpResponse::InternalServerError(), - }.into() - } -} - -/// Create request builder for `GET` requests -/// -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # extern crate tokio; -/// # extern crate env_logger; -/// # use futures::Future; -/// # use std::process; -/// use actix_web::{actix, client}; -/// -/// fn main() { -/// actix::run( -/// || client::get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .finish().unwrap() -/// .send() // <- Send http request -/// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// # actix::System::current().stop(); -/// Ok(()) -/// }), -/// ); -/// } -/// ``` -pub fn get>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::GET).uri(uri); - builder -} - -/// Create request builder for `HEAD` requests -pub fn head>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::HEAD).uri(uri); - builder -} - -/// Create request builder for `POST` requests -pub fn post>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::POST).uri(uri); - builder -} - -/// Create request builder for `PUT` requests -pub fn put>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::PUT).uri(uri); - builder -} - -/// Create request builder for `DELETE` requests -pub fn delete>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::DELETE).uri(uri); - builder -} diff --git a/src/client/parser.rs b/src/client/parser.rs deleted file mode 100644 index 11252fa52..000000000 --- a/src/client/parser.rs +++ /dev/null @@ -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, - 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( - &mut self, io: &mut T, buf: &mut BytesMut, - ) -> Poll - where - T: IoStream, - { - loop { - // Don't call parser until we have data to parse. - if !buf.is_empty() { - match HttpResponseParser::parse_message(buf) - .map_err(HttpResponseParserError::Error)? - { - Async::Ready((msg, info)) => { - if let Some((decoder, eof)) = info { - self.eof = eof; - self.decoder = Some(decoder); - } else { - self.eof = false; - self.decoder = None; - } - return Ok(Async::Ready(msg)); - } - Async::NotReady => { - if buf.capacity() >= MAX_BUFFER_SIZE { - return Err(HttpResponseParserError::Error( - ParseError::TooLarge, - )); - } - // Parser needs more data. - } - } - } - // Read some more data into the buffer for the parser. - match io.read_available(buf) { - Ok(Async::Ready((false, true))) => { - return Err(HttpResponseParserError::Disconnect) - } - Ok(Async::Ready(_)) => (), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => return Err(HttpResponseParserError::Error(err.into())), - } - } - } - - pub fn parse_payload( - &mut self, io: &mut T, buf: &mut BytesMut, - ) -> Poll, 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::() { - 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 { - 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) - } -} diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs deleted file mode 100644 index 394b7a6cd..000000000 --- a/src/client/pipeline.rs +++ /dev/null @@ -1,552 +0,0 @@ -use bytes::{Bytes, BytesMut}; -use futures::sync::oneshot; -use futures::{Async, Future, Poll, Stream}; -use http::header::CONTENT_ENCODING; -use std::time::{Duration, Instant}; -use std::{io, mem}; -use tokio_timer::Delay; - -use actix::{Addr, Request, SystemService}; - -use super::{ - ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect, - Connection, HttpClientWriter, HttpResponseParser, HttpResponseParserError, -}; -use body::{Body, BodyStream}; -use context::{ActorHttpContext, Frame}; -use error::Error; -use error::PayloadError; -use header::ContentEncoding; -use http::{Method, Uri}; -use httpmessage::HttpMessage; -use server::input::PayloadStream; -use server::WriterState; - -/// A set of errors that can occur during request sending and response reading -#[derive(Fail, Debug)] -pub enum SendRequestError { - /// Response took too long - #[fail(display = "Timeout while waiting for response")] - Timeout, - /// Failed to connect to host - #[fail(display = "Failed to connect to host: {}", _0)] - Connector(#[cause] ClientConnectorError), - /// Error parsing response - #[fail(display = "{}", _0)] - ParseError(#[cause] HttpResponseParserError), - /// Error reading response payload - #[fail(display = "Error reading response payload: {}", _0)] - Io(#[cause] io::Error), -} - -impl From for SendRequestError { - fn from(err: io::Error) -> SendRequestError { - SendRequestError::Io(err) - } -} - -impl From for SendRequestError { - fn from(err: ClientConnectorError) -> SendRequestError { - match err { - ClientConnectorError::Timeout => SendRequestError::Timeout, - _ => SendRequestError::Connector(err), - } - } -} - -enum State { - New, - Connect(Request), - Connection(Connection), - Send(Box), - 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>, - conn_timeout: Duration, - wait_timeout: Duration, - timeout: Option, -} - -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, - ) -> 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 { - 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, - writer: HttpClientWriter, - parser: Option, - parser_buf: BytesMut, - disconnected: bool, - drain: Option>, - decompress: Option, - should_decompress: bool, - write_state: RunningState, - timeout: Option, - meth: Method, - path: Uri, -} - -enum IoBody { - Payload(BodyStream), - Actor(Box), - 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 { - 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, 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 { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll, Self::Error> { - Pipeline::poll(self) - } -} diff --git a/src/client/request.rs b/src/client/request.rs deleted file mode 100644 index 76fb1be59..000000000 --- a/src/client/request.rs +++ /dev/null @@ -1,782 +0,0 @@ -use std::fmt::Write as FmtWrite; -use std::io::Write; -use std::time::Duration; -use std::{fmt, mem}; - -use actix::Addr; -use bytes::{BufMut, Bytes, BytesMut}; -use cookie::{Cookie, CookieJar}; -use futures::Stream; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; -use serde::Serialize; -use serde_json; -use serde_urlencoded; -use url::Url; - -use super::connector::{ClientConnector, Connection}; -use super::pipeline::SendRequest; -use body::Body; -use error::Error; -use header::{ContentEncoding, Header, IntoHeaderValue}; -use http::header::{self, HeaderName, HeaderValue}; -use http::{uri, Error as HttpError, HeaderMap, HttpTryFrom, Method, Uri, Version}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; - -/// An HTTP Client Request -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # extern crate tokio; -/// # use futures::Future; -/// # use std::process; -/// use actix_web::{actix, client}; -/// -/// fn main() { -/// actix::run( -/// || client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .finish().unwrap() -/// .send() // <- Send http request -/// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// # actix::System::current().stop(); -/// Ok(()) -/// }), -/// ); -/// } -/// ``` -pub struct ClientRequest { - uri: Uri, - method: Method, - version: Version, - headers: HeaderMap, - body: Body, - chunked: bool, - upgrade: bool, - timeout: Option, - encoding: ContentEncoding, - response_decompress: bool, - buffer_capacity: usize, - conn: ConnectionType, -} - -enum ConnectionType { - Default, - Connector(Addr), - 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>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::GET).uri(uri); - builder - } - - /// Create request builder for `HEAD` request - pub fn head>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::HEAD).uri(uri); - builder - } - - /// Create request builder for `POST` request - pub fn post>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::POST).uri(uri); - builder - } - - /// Create request builder for `PUT` request - pub fn put>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::PUT).uri(uri); - builder - } - - /// Create request builder for `DELETE` request - pub fn delete>(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>(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>(&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, - err: Option, - cookies: Option, - default_headers: bool, -} - -impl ClientRequestBuilder { - /// Set HTTP URI of request. - #[inline] - pub fn uri>(&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(&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(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - 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(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - 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(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - 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(&mut self, value: V) -> &mut Self - where - HeaderValue: HttpTryFrom, - { - 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) -> &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(&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(&mut self, value: Option, 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>(&mut self, body: B) -> Result { - if let Some(e) = self.err.take() { - return Err(e.into()); - } - - if self.default_headers { - // enable br only for https - let https = if let Some(parts) = parts(&mut self.request, &self.err) { - parts - .uri - .scheme_part() - .map(|s| s == &uri::Scheme::HTTPS) - .unwrap_or(true) - } else { - true - }; - - if https { - self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate"); - } else { - self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate"); - } - - // set request host header - if let Some(parts) = parts(&mut self.request, &self.err) { - if let Some(host) = parts.uri.host() { - if !parts.headers.contains_key(header::HOST) { - let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - - let _ = match parts.uri.port() { - None | Some(80) | Some(443) => write!(wrt, "{}", host), - Some(port) => write!(wrt, "{}:{}", host, port), - }; - - match wrt.get_mut().take().freeze().try_into() { - Ok(value) => { - parts.headers.insert(header::HOST, value); - } - Err(e) => self.err = Some(e.into()), - } - } - } - } - - // user agent - self.set_header_if_none( - header::USER_AGENT, - concat!("actix-web/", env!("CARGO_PKG_VERSION")), - ); - } - - let mut request = self.request.take().expect("cannot reuse request builder"); - - // set cookies - if let Some(ref mut jar) = self.cookies { - let mut cookie = String::new(); - for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - request.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); - } - request.body = body.into(); - Ok(request) - } - - /// Set a JSON body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> Result { - 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(&mut self, value: T) -> Result { - 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(&mut self, stream: S) -> Result - where - S: Stream + 'static, - E: Into, - { - 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 { - 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, err: &Option, -) -> 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> for ClientRequestBuilder { - fn from(req: &'a HttpRequest) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - for (key, value) in req.headers() { - builder.header(key.clone(), value.clone()); - } - builder.method(req.method().clone()); - builder - } -} diff --git a/src/client/response.rs b/src/client/response.rs deleted file mode 100644 index 5f1f42649..000000000 --- a/src/client/response.rs +++ /dev/null @@ -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, - pub cookies: Option>>, -} - -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>>); - -impl HttpMessage for ClientResponse { - type Stream = Box; - - /// Get the headers from the response. - #[inline] - fn headers(&self) -> &HeaderMap { - &self.0.headers - } - - #[inline] - fn payload(&self) -> Box { - 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) { - *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>, 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 { - 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")); - } -} diff --git a/src/client/writer.rs b/src/client/writer.rs deleted file mode 100644 index e74f22332..000000000 --- a/src/client/writer.rs +++ /dev/null @@ -1,412 +0,0 @@ -#![cfg_attr( - feature = "cargo-clippy", - allow(clippy::redundant_field_names) -)] - -use std::cell::RefCell; -use std::fmt::Write as FmtWrite; -use std::io::{self, Write}; - -#[cfg(feature = "brotli")] -use brotli2::write::BrotliEncoder; -use bytes::{BufMut, BytesMut}; -#[cfg(feature = "flate2")] -use flate2::write::{GzEncoder, ZlibEncoder}; -#[cfg(feature = "flate2")] -use flate2::Compression; -use futures::{Async, Poll}; -use http::header::{ - HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, -}; -use http::{HttpTryFrom, Version}; -use time::{self, Duration}; -use tokio_io::AsyncWrite; - -use body::{Binary, Body}; -use header::ContentEncoding; -use server::output::{ContentEncoder, Output, TransferEncoding}; -use server::WriterState; - -use client::ClientRequest; - -const AVERAGE_HEADER_SIZE: usize = 30; - -bitflags! { - struct Flags: u8 { - const STARTED = 0b0000_0001; - const UPGRADE = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const DISCONNECTED = 0b0000_1000; - } -} - -pub(crate) struct HttpClientWriter { - flags: Flags, - written: u64, - headers_size: u32, - buffer: Output, - buffer_capacity: usize, -} - -impl HttpClientWriter { - pub fn new() -> HttpClientWriter { - HttpClientWriter { - flags: Flags::empty(), - written: 0, - headers_size: 0, - buffer_capacity: 0, - buffer: Output::Buffer(BytesMut::new()), - } - } - - pub fn disconnected(&mut self) { - self.buffer.take(); - } - - pub fn is_completed(&self) -> bool { - self.buffer.is_empty() - } - - // pub fn keepalive(&self) -> bool { - // self.flags.contains(Flags::KEEPALIVE) && - // !self.flags.contains(Flags::UPGRADE) } - - fn write_to_stream( - &mut self, stream: &mut T, - ) -> io::Result { - 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 { - 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 { - 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( - &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::() { - (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 = 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; - } -} diff --git a/src/context.rs b/src/context.rs deleted file mode 100644 index 71a5af2d8..000000000 --- a/src/context.rs +++ /dev/null @@ -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>, Error>; -} - -#[derive(Debug)] -pub enum Frame { - Chunk(Option), - 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 -where - A: Actor>, -{ - inner: ContextParts, - stream: Option>, - request: HttpRequest, - disconnected: bool, -} - -impl ActorContext for HttpContext -where - A: Actor, -{ - fn stop(&mut self) { - self.inner.stop(); - } - fn terminate(&mut self) { - self.inner.terminate() - } - fn state(&self) -> ActorState { - self.inner.state() - } -} - -impl AsyncContext for HttpContext -where - A: Actor, -{ - #[inline] - fn spawn(&mut self, fut: F) -> SpawnHandle - where - F: ActorFuture + 'static, - { - self.inner.spawn(fut) - } - #[inline] - fn wait(&mut self, fut: F) - where - F: ActorFuture + '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 { - self.inner.address() - } -} - -impl HttpContext -where - A: Actor, -{ - #[inline] - /// Create a new HTTP Context from a request and an actor - pub fn create(req: HttpRequest, 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(req: HttpRequest, 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 HttpContext -where - A: Actor, -{ - /// Shared application state - #[inline] - pub fn state(&self) -> &S { - self.request.state() - } - - /// Incoming request - #[inline] - pub fn request(&mut self) -> &mut HttpRequest { - &mut self.request - } - - /// Write payload - #[inline] - pub fn write>(&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 { - 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 AsyncContextParts for HttpContext -where - A: Actor, -{ - fn parts(&mut self) -> &mut ContextParts { - &mut self.inner - } -} - -struct HttpContextFut -where - A: Actor>, -{ - fut: ContextFut>, -} - -impl HttpContextFut -where - A: Actor>, -{ - fn new(ctx: HttpContext, act: A, mailbox: Mailbox) -> Self { - let fut = ContextFut::new(ctx, act, mailbox); - HttpContextFut { fut } - } -} - -impl ActorHttpContext for HttpContextFut -where - A: Actor>, - S: 'static, -{ - #[inline] - fn disconnected(&mut self) { - self.fut.ctx().disconnected = true; - self.fut.ctx().stop(); - } - - fn poll(&mut self) -> Poll>, 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 ToEnvelope for HttpContext -where - A: Actor> + Handler, - M: Message + Send + 'static, - M::Result: Send, -{ - fn pack(msg: M, tx: Option>) -> Envelope { - Envelope::new(msg, tx) - } -} - -/// Consume a future -pub struct Drain { - fut: oneshot::Receiver<()>, - _a: PhantomData, -} - -impl Drain { - /// Create a drain from a future - pub fn new(fut: oneshot::Receiver<()>) -> Self { - Drain { - fut, - _a: PhantomData, - } - } -} - -impl ActorFuture for Drain { - type Item = (); - type Error = (); - type Actor = A; - - #[inline] - fn poll( - &mut self, _: &mut A, _: &mut ::Context, - ) -> Poll { - self.fut.poll().map_err(|_| ()) - } -} diff --git a/src/de.rs b/src/de.rs deleted file mode 100644 index 59ab79ba9..000000000 --- a/src/de.rs +++ /dev/null @@ -1,443 +0,0 @@ -use serde::de::{self, Deserializer, Error as DeError, Visitor}; - -use httprequest::HttpRequest; -use param::ParamsIter; - -macro_rules! unsupported_type { - ($trait_fn:ident, $name:expr) => { - fn $trait_fn(self, _: V) -> Result - where V: Visitor<'de> - { - Err(de::value::Error::custom(concat!("unsupported type: ", $name))) - } - }; -} - -macro_rules! parse_single_value { - ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { - fn $trait_fn(self, visitor: V) -> Result - where V: Visitor<'de> - { - if self.req.match_info().len() != 1 { - Err(de::value::Error::custom( - format!("wrong number of parameters: {} expected 1", - self.req.match_info().len()).as_str())) - } else { - let v = self.req.match_info()[0].parse().map_err( - |_| de::value::Error::custom( - format!("can not parse {:?} to a {}", - &self.req.match_info()[0], $tp)))?; - visitor.$visit_fn(v) - } - } - } -} - -pub struct PathDeserializer<'de, S: 'de> { - req: &'de HttpRequest, -} - -impl<'de, S: 'de> PathDeserializer<'de, S> { - pub fn new(req: &'de HttpRequest) -> Self { - PathDeserializer { req } - } -} - -impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { - type Error = de::value::Error; - - fn deserialize_map(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_map(ParamsDeserializer { - params: self.req.match_info().iter(), - current: None, - }) - } - - fn deserialize_struct( - self, _: &'static str, _: &'static [&'static str], visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - self.deserialize_map(visitor) - } - - fn deserialize_unit(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_unit_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - self.deserialize_unit(visitor) - } - - fn deserialize_newtype_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_newtype_struct(self) - } - - fn deserialize_tuple( - self, len: usize, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - if self.req.match_info().len() < len { - Err(de::value::Error::custom( - format!( - "wrong number of parameters: {} expected {}", - self.req.match_info().len(), - len - ).as_str(), - )) - } else { - visitor.visit_seq(ParamsSeq { - params: self.req.match_info().iter(), - }) - } - } - - fn deserialize_tuple_struct( - self, _: &'static str, len: usize, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - if self.req.match_info().len() < len { - Err(de::value::Error::custom( - format!( - "wrong number of parameters: {} expected {}", - self.req.match_info().len(), - len - ).as_str(), - )) - } else { - visitor.visit_seq(ParamsSeq { - params: self.req.match_info().iter(), - }) - } - } - - fn deserialize_enum( - self, _: &'static str, _: &'static [&'static str], _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: enum")) - } - - fn deserialize_str(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - if self.req.match_info().len() != 1 { - Err(de::value::Error::custom( - format!( - "wrong number of parameters: {} expected 1", - self.req.match_info().len() - ).as_str(), - )) - } else { - visitor.visit_str(&self.req.match_info()[0]) - } - } - - fn deserialize_seq(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_seq(ParamsSeq { - params: self.req.match_info().iter(), - }) - } - - unsupported_type!(deserialize_any, "'any'"); - unsupported_type!(deserialize_bytes, "bytes"); - unsupported_type!(deserialize_option, "Option"); - unsupported_type!(deserialize_identifier, "identifier"); - unsupported_type!(deserialize_ignored_any, "ignored_any"); - - parse_single_value!(deserialize_bool, visit_bool, "bool"); - parse_single_value!(deserialize_i8, visit_i8, "i8"); - parse_single_value!(deserialize_i16, visit_i16, "i16"); - parse_single_value!(deserialize_i32, visit_i32, "i32"); - parse_single_value!(deserialize_i64, visit_i64, "i64"); - parse_single_value!(deserialize_u8, visit_u8, "u8"); - parse_single_value!(deserialize_u16, visit_u16, "u16"); - parse_single_value!(deserialize_u32, visit_u32, "u32"); - parse_single_value!(deserialize_u64, visit_u64, "u64"); - parse_single_value!(deserialize_f32, visit_f32, "f32"); - parse_single_value!(deserialize_f64, visit_f64, "f64"); - parse_single_value!(deserialize_string, visit_string, "String"); - parse_single_value!(deserialize_byte_buf, visit_string, "String"); - parse_single_value!(deserialize_char, visit_char, "char"); -} - -struct ParamsDeserializer<'de> { - params: ParamsIter<'de>, - current: Option<(&'de str, &'de str)>, -} - -impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> { - type Error = de::value::Error; - - fn next_key_seed(&mut self, seed: K) -> Result, 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(&mut self, seed: V) -> Result - where - V: de::DeserializeSeed<'de>, - { - if let Some((_, value)) = self.current.take() { - seed.deserialize(Value { value }) - } else { - Err(de::value::Error::custom("unexpected item")) - } - } -} - -struct Key<'de> { - key: &'de str, -} - -impl<'de> Deserializer<'de> for Key<'de> { - type Error = de::value::Error; - - fn deserialize_identifier(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_str(self.key) - } - - fn deserialize_any(self, _visitor: V) -> Result - 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(self, visitor: V) -> Result - where V: Visitor<'de> - { - let v = self.value.parse().map_err( - |_| de::value::Error::custom( - format!("can not parse {:?} to a {}", self.value, $tp)))?; - visitor.$visit_fn(v) - } - } -} - -struct Value<'de> { - value: &'de str, -} - -impl<'de> Deserializer<'de> for Value<'de> { - type Error = de::value::Error; - - parse_value!(deserialize_bool, visit_bool, "bool"); - parse_value!(deserialize_i8, visit_i8, "i8"); - parse_value!(deserialize_i16, visit_i16, "i16"); - parse_value!(deserialize_i32, visit_i32, "i16"); - parse_value!(deserialize_i64, visit_i64, "i64"); - parse_value!(deserialize_u8, visit_u8, "u8"); - parse_value!(deserialize_u16, visit_u16, "u16"); - parse_value!(deserialize_u32, visit_u32, "u32"); - parse_value!(deserialize_u64, visit_u64, "u64"); - parse_value!(deserialize_f32, visit_f32, "f32"); - parse_value!(deserialize_f64, visit_f64, "f64"); - parse_value!(deserialize_string, visit_string, "String"); - parse_value!(deserialize_byte_buf, visit_string, "String"); - parse_value!(deserialize_char, visit_char, "char"); - - fn deserialize_ignored_any(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_unit(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_unit_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_bytes(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_borrowed_bytes(self.value.as_bytes()) - } - - fn deserialize_str(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_borrowed_str(self.value) - } - - fn deserialize_option(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_some(self) - } - - fn deserialize_enum( - self, _: &'static str, _: &'static [&'static str], visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_enum(ValueEnum { value: self.value }) - } - - fn deserialize_newtype_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_newtype_struct(self) - } - - fn deserialize_tuple(self, _: usize, _: V) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: tuple")) - } - - fn deserialize_struct( - self, _: &'static str, _: &'static [&'static str], _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: struct")) - } - - fn deserialize_tuple_struct( - self, _: &'static str, _: usize, _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: tuple struct")) - } - - unsupported_type!(deserialize_any, "any"); - unsupported_type!(deserialize_seq, "seq"); - unsupported_type!(deserialize_map, "map"); - unsupported_type!(deserialize_identifier, "identifier"); -} - -struct ParamsSeq<'de> { - params: ParamsIter<'de>, -} - -impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> { - type Error = de::value::Error; - - fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> - where - T: de::DeserializeSeed<'de>, - { - match self.params.next() { - Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)), - None => Ok(None), - } - } -} - -struct ValueEnum<'de> { - value: &'de str, -} - -impl<'de> de::EnumAccess<'de> for ValueEnum<'de> { - type Error = de::value::Error; - type Variant = UnitVariant; - - fn variant_seed(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(self, _seed: T) -> Result - where - T: de::DeserializeSeed<'de>, - { - Err(de::value::Error::custom("not supported")) - } - - fn tuple_variant(self, _len: usize, _visitor: V) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("not supported")) - } - - fn struct_variant( - self, _: &'static [&'static str], _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("not supported")) - } -} diff --git a/src/error.rs b/src/error.rs index 76c8e79ec..724803805 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,8 +22,7 @@ pub use url::ParseError as UrlParseError; // re-exports pub use cookie::ParseError as CookieParseError; -use handler::Responder; -use httprequest::HttpRequest; +// use httprequest::HttpRequest; use httpresponse::{HttpResponse, HttpResponseParts}; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) @@ -727,18 +726,6 @@ where } } -impl Responder for InternalError -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, _: &HttpRequest) -> Result { - Err(self.into()) - } -} - /// Helper function that creates wrapper of any error and generate *BAD /// REQUEST* response. #[allow(non_snake_case)] diff --git a/src/extractor.rs b/src/extractor.rs deleted file mode 100644 index 7b0b4b003..000000000 --- a/src/extractor.rs +++ /dev/null @@ -1,1024 +0,0 @@ -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use std::rc::Rc; -use std::{fmt, str}; - -use bytes::Bytes; -use encoding::all::UTF_8; -use encoding::types::{DecoderTrap, Encoding}; -use futures::{future, Async, Future, Poll}; -use mime::Mime; -use serde::de::{self, DeserializeOwned}; -use serde_urlencoded; - -use de::PathDeserializer; -use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError}; -use handler::{AsyncResult, FromRequest}; -use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; -use httprequest::HttpRequest; - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's path. -/// -/// ## Example -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// use actix_web::{http, App, Path, Result}; -/// -/// /// extract path info from "/{username}/{count}/index.html" url -/// /// {username} - deserializes to a String -/// /// {count} - - deserializes to a u32 -/// fn index(info: Path<(String, u32)>) -> Result { -/// Ok(format!("Welcome {}! {}", info.0, info.1)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/{username}/{count}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index), -/// ); // <- use `with` extractor -/// } -/// ``` -/// -/// It is possible to extract path information to a specific type that -/// implements `Deserialize` trait from *serde*. -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Path, Result}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// extract path info using serde -/// fn index(info: Path) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/{username}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index), -/// ); // <- use `with` extractor -/// } -/// ``` -pub struct Path { - inner: T, -} - -impl AsRef for Path { - fn as_ref(&self) -> &T { - &self.inner - } -} - -impl Deref for Path { - type Target = T; - - fn deref(&self) -> &T { - &self.inner - } -} - -impl DerefMut for Path { - fn deref_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -impl Path { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.inner - } -} - -impl From for Path { - fn from(inner: T) -> Path { - Path { inner } - } -} - -impl FromRequest for Path -where - T: DeserializeOwned, -{ - type Config = (); - type Result = Result; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - let req = req.clone(); - de::Deserialize::deserialize(PathDeserializer::new(&req)) - .map_err(ErrorNotFound) - .map(|inner| Path { inner }) - } -} - -impl fmt::Debug for Path { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } -} - -impl fmt::Display for Path { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } -} - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from from the request's query. -/// -/// ## Example -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Query, http}; -/// -/// -///#[derive(Debug, Deserialize)] -///pub enum ResponseType { -/// Token, -/// Code -///} -/// -///#[derive(Deserialize)] -///pub struct AuthRequest { -/// id: u64, -/// response_type: ResponseType, -///} -/// -/// // use `with` extractor for query info -/// // this handler get called only if request's query contains `username` field -/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` -/// fn index(info: Query) -> String { -/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor -/// } -/// ``` -pub struct Query(T); - -impl Deref for Query { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Query { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl Query { - /// Deconstruct to a inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl FromRequest for Query -where - T: de::DeserializeOwned, -{ - type Config = (); - type Result = Result; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - serde_urlencoded::from_str::(req.query_string()) - .map_err(|e| e.into()) - .map(Query) - } -} - -impl fmt::Debug for Query { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for Query { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's body. -/// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. -/// -/// [**FormConfig**](dev/struct.FormConfig.html) allows to configure extraction -/// process. -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Form, Result}; -/// -/// #[derive(Deserialize)] -/// struct FormData { -/// username: String, -/// } -/// -/// /// extract form data using serde -/// /// this handler get called only if content type is *x-www-form-urlencoded* -/// /// and content of the request could be deserialized to a `FormData` struct -/// fn index(form: Form) -> Result { -/// Ok(format!("Welcome {}!", form.username)) -/// } -/// # fn main() {} -/// ``` -pub struct Form(pub T); - -impl Form { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl Deref for Form { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Form { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl FromRequest for Form -where - T: DeserializeOwned + 'static, - S: 'static, -{ - type Config = FormConfig; - type Result = Box>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - let req2 = req.clone(); - let err = Rc::clone(&cfg.ehandler); - Box::new( - UrlEncoded::new(req) - .limit(cfg.limit) - .map_err(move |e| (*err)(e, &req2)) - .map(Form), - ) - } -} - -impl fmt::Debug for Form { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for Form { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -/// Form extractor configuration -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Form, Result}; -/// -/// #[derive(Deserialize)] -/// struct FormData { -/// username: String, -/// } -/// -/// /// extract form data using serde. -/// /// custom configuration is used for this handler, max payload size is 4k -/// fn index(form: Form) -> Result { -/// Ok(format!("Welcome {}!", form.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| { -/// r.method(http::Method::GET) -/// // register form handler and change form extractor configuration -/// .with_config(index, |cfg| {cfg.0.limit(4096);}) -/// }, -/// ); -/// } -/// ``` -pub struct FormConfig { - limit: usize, - ehandler: Rc) -> Error>, -} - -impl FormConfig { - /// 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(&mut self, f: F) -> &mut Self - where - F: Fn(UrlencodedError, &HttpRequest) -> Error + 'static, - { - self.ehandler = Rc::new(f); - self - } -} - -impl Default for FormConfig { - fn default() -> Self { - FormConfig { - limit: 262_144, - ehandler: Rc::new(|e, _| e.into()), - } - } -} - -/// Request payload extractor. -/// -/// Loads request's payload and construct Bytes instance. -/// -/// [**PayloadConfig**](dev/struct.PayloadConfig.html) allows to configure -/// extraction process. -/// -/// ## Example -/// -/// ```rust -/// extern crate bytes; -/// # extern crate actix_web; -/// use actix_web::{http, App, Result}; -/// -/// /// extract text data from request -/// fn index(body: bytes::Bytes) -> Result { -/// Ok(format!("Body {:?}!", body)) -/// } -/// -/// fn main() { -/// let app = App::new() -/// .resource("/index.html", |r| r.method(http::Method::GET).with(index)); -/// } -/// ``` -impl FromRequest for Bytes { - type Config = PayloadConfig; - type Result = Result>, Error>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - // check content-type - cfg.check_mimetype(req)?; - - Ok(Box::new(MessageBody::new(req).limit(cfg.limit).from_err())) - } -} - -/// Extract text information from the request's body. -/// -/// Text extractor automatically decode body according to the request's charset. -/// -/// [**PayloadConfig**](dev/struct.PayloadConfig.html) allows to configure -/// extraction process. -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{http, App, Result}; -/// -/// /// extract text data from request -/// fn index(body: String) -> Result { -/// 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 with extractor params -/// cfg.0.limit(4096); // <- limit size of the payload -/// }) -/// }); -/// } -/// ``` -impl FromRequest for String { - type Config = PayloadConfig; - type Result = Result>, Error>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - // check content-type - cfg.check_mimetype(req)?; - - // check charset - let encoding = req.encoding()?; - - Ok(Box::new( - MessageBody::new(req) - .limit(cfg.limit) - .from_err() - .and_then(move |body| { - let enc: *const Encoding = encoding as *const Encoding; - if enc == UTF_8 { - Ok(str::from_utf8(body.as_ref()) - .map_err(|_| ErrorBadRequest("Can not decode body"))? - .to_owned()) - } else { - Ok(encoding - .decode(&body, DecoderTrap::Strict) - .map_err(|_| ErrorBadRequest("Can not decode body"))?) - } - }), - )) - } -} - -/// Optionally extract a field from the request -/// -/// If the FromRequest for T fails, return None rather than returning an error response -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// extern crate rand; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest}; -/// use actix_web::error::ErrorBadRequest; -/// -/// #[derive(Debug, Deserialize)] -/// struct Thing { name: String } -/// -/// impl FromRequest for Thing { -/// type Config = (); -/// type Result = Result; -/// -/// #[inline] -/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { -/// if rand::random() { -/// Ok(Thing { name: "thingy".into() }) -/// } else { -/// Err(ErrorBadRequest("no luck")) -/// } -/// -/// } -/// } -/// -/// /// extract text data from request -/// fn index(supplied_thing: Option) -> Result { -/// match supplied_thing { -/// // Puns not intended -/// Some(thing) => Ok(format!("Got something: {:?}", thing)), -/// None => Ok(format!("No thing!")) -/// } -/// } -/// -/// fn main() { -/// let app = App::new().resource("/users/:first", |r| { -/// r.method(http::Method::POST).with(index) -/// }); -/// } -/// ``` -impl FromRequest for Option -where - T: FromRequest, -{ - type Config = T::Config; - type Result = Box, Error = Error>>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new(T::from_request(req, cfg).into().then(|r| match r { - Ok(v) => future::ok(Some(v)), - Err(_) => future::ok(None), - })) - } -} - -/// Optionally extract a field from the request or extract the Error if unsuccessful -/// -/// If the FromRequest for T fails, inject Err into handler rather than returning an error response -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// extern crate rand; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest}; -/// use actix_web::error::ErrorBadRequest; -/// -/// #[derive(Debug, Deserialize)] -/// struct Thing { name: String } -/// -/// impl FromRequest for Thing { -/// type Config = (); -/// type Result = Result; -/// -/// #[inline] -/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { -/// if rand::random() { -/// Ok(Thing { name: "thingy".into() }) -/// } else { -/// Err(ErrorBadRequest("no luck")) -/// } -/// -/// } -/// } -/// -/// /// extract text data from request -/// fn index(supplied_thing: Result) -> Result { -/// match supplied_thing { -/// Ok(thing) => Ok(format!("Got thing: {:?}", thing)), -/// Err(e) => Ok(format!("Error extracting thing: {}", e)) -/// } -/// } -/// -/// fn main() { -/// let app = App::new().resource("/users/:first", |r| { -/// r.method(http::Method::POST).with(index) -/// }); -/// } -/// ``` -impl FromRequest for Result -where - T: FromRequest, -{ - type Config = T::Config; - type Result = Box, Error = Error>>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new(T::from_request(req, cfg).into().then(future::ok)) - } -} - -/// Payload configuration for request's payload. -pub struct PayloadConfig { - limit: usize, - mimetype: Option, -} - -impl PayloadConfig { - /// 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 required mime-type of the request. By default mime type is not - /// enforced. - pub fn mimetype(&mut self, mt: Mime) -> &mut Self { - self.mimetype = Some(mt); - self - } - - fn check_mimetype(&self, req: &HttpRequest) -> Result<(), Error> { - // check content-type - if let Some(ref mt) = self.mimetype { - match req.mime_type() { - Ok(Some(ref req_mt)) => { - if mt != req_mt { - return Err(ErrorBadRequest("Unexpected Content-Type")); - } - } - Ok(None) => { - return Err(ErrorBadRequest("Content-Type is expected")); - } - Err(err) => { - return Err(err.into()); - } - } - } - Ok(()) - } -} - -impl Default for PayloadConfig { - fn default() -> Self { - PayloadConfig { - limit: 262_144, - mimetype: None, - } - } -} - -macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { - - /// FromRequest implementation for tuple - impl + 'static),+> FromRequest for ($($T,)+) - where - S: 'static, - { - type Config = ($($T::Config,)+); - type Result = Box>; - - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new($fut_type { - s: PhantomData, - items: <($(Option<$T>,)+)>::default(), - futs: ($(Some($T::from_request(req, &cfg.$n).into()),)+), - }) - } - } - - struct $fut_type),+> - where - S: 'static, - { - s: PhantomData, - items: ($(Option<$T>,)+), - futs: ($(Option>,)+), - } - - impl),+> Future for $fut_type - where - S: 'static, - { - type Item = ($($T,)+); - type Error = Error; - - fn poll(&mut self) -> Poll { - let mut ready = true; - - $( - if self.futs.$n.is_some() { - match self.futs.$n.as_mut().unwrap().poll() { - Ok(Async::Ready(item)) => { - self.items.$n = Some(item); - self.futs.$n.take(); - } - Ok(Async::NotReady) => ready = false, - Err(e) => return Err(e), - } - } - )+ - - if ready { - Ok(Async::Ready( - ($(self.items.$n.take().unwrap(),)+) - )) - } else { - Ok(Async::NotReady) - } - } - } -}); - -impl FromRequest for () { - type Config = (); - type Result = Self; - fn from_request(_req: &HttpRequest, _cfg: &Self::Config) -> Self::Result {} -} - -tuple_from_req!(TupleFromRequest1, (0, A)); -tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); -tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); -tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D)); -tuple_from_req!(TupleFromRequest5, (0, A), (1, B), (2, C), (3, D), (4, E)); -tuple_from_req!( - TupleFromRequest6, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F) -); -tuple_from_req!( - TupleFromRequest7, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G) -); -tuple_from_req!( - TupleFromRequest8, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H) -); -tuple_from_req!( - TupleFromRequest9, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H), - (8, I) -); - -#[cfg(test)] -mod tests { - use super::*; - use bytes::Bytes; - use futures::{Async, Future}; - use http::header; - use mime; - use resource::Resource; - use router::{ResourceDef, Router}; - use test::TestRequest; - - #[derive(Deserialize, Debug, PartialEq)] - struct Info { - hello: String, - } - - #[test] - fn test_bytes() { - let cfg = PayloadConfig::default(); - let req = TestRequest::with_header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s, Bytes::from_static(b"hello=world")); - } - _ => unreachable!(), - } - } - - #[test] - fn test_string() { - let cfg = PayloadConfig::default(); - let req = TestRequest::with_header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - match String::from_request(&req, &cfg).unwrap().poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s, "hello=world"); - } - _ => unreachable!(), - } - } - - #[test] - fn test_form() { - 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 mut cfg = FormConfig::default(); - cfg.limit(4096); - match Form::::from_request(&req, &cfg).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.hello, "world"); - } - _ => unreachable!(), - } - } - - #[test] - fn test_option() { - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).finish(); - - let mut cfg = FormConfig::default(); - cfg.limit(4096); - - match Option::>::from_request(&req, &cfg) - .poll() - .unwrap() - { - Async::Ready(r) => assert_eq!(r, None), - _ => unreachable!(), - } - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - match Option::>::from_request(&req, &cfg) - .poll() - .unwrap() - { - Async::Ready(r) => assert_eq!( - r, - Some(Form(Info { - hello: "world".into() - })) - ), - _ => unreachable!(), - } - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .finish(); - - match Option::>::from_request(&req, &cfg) - .poll() - .unwrap() - { - Async::Ready(r) => assert_eq!(r, None), - _ => unreachable!(), - } - } - - #[test] - fn test_result() { - 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(); - - match Result::, Error>::from_request(&req, &FormConfig::default()) - .poll() - .unwrap() - { - Async::Ready(Ok(r)) => assert_eq!( - r, - Form(Info { - hello: "world".into() - }) - ), - _ => unreachable!(), - } - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .finish(); - - match Result::, Error>::from_request(&req, &FormConfig::default()) - .poll() - .unwrap() - { - Async::Ready(r) => assert!(r.is_err()), - _ => unreachable!(), - } - } - - #[test] - fn test_payload_config() { - let req = TestRequest::default().finish(); - let mut cfg = PayloadConfig::default(); - cfg.mimetype(mime::APPLICATION_JSON); - assert!(cfg.check_mimetype(&req).is_err()); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).finish(); - assert!(cfg.check_mimetype(&req).is_err()); - - let req = - TestRequest::with_header(header::CONTENT_TYPE, "application/json").finish(); - assert!(cfg.check_mimetype(&req).is_ok()); - } - - #[derive(Deserialize)] - struct MyStruct { - key: String, - value: String, - } - - #[derive(Deserialize)] - struct Id { - id: String, - } - - #[derive(Deserialize)] - struct Test2 { - key: String, - value: u32, - } - - #[test] - fn test_request_extract() { - let req = TestRequest::with_uri("/name/user1/?id=test").finish(); - - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - - let s = Path::::from_request(&req, &()).unwrap(); - assert_eq!(s.key, "name"); - assert_eq!(s.value, "user1"); - - let s = Path::<(String, String)>::from_request(&req, &()).unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, "user1"); - - let s = Query::::from_request(&req, &()).unwrap(); - assert_eq!(s.id, "test"); - - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - let req = TestRequest::with_uri("/name/32/").finish(); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - - let s = Path::::from_request(&req, &()).unwrap(); - assert_eq!(s.as_ref().key, "name"); - assert_eq!(s.value, 32); - - let s = Path::<(String, u8)>::from_request(&req, &()).unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, 32); - - let res = Path::>::extract(&req).unwrap(); - assert_eq!(res[0], "name".to_owned()); - assert_eq!(res[1], "32".to_owned()); - } - - #[test] - fn test_extract_path_single() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); - - let req = TestRequest::with_uri("/32/").finish(); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - assert_eq!(*Path::::from_request(&req, &()).unwrap(), 32); - } - - #[test] - fn test_tuple_extract() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - - let req = TestRequest::with_uri("/name/user1/?id=test").finish(); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - - let res = match <(Path<(String, String)>,)>::extract(&req).poll() { - Ok(Async::Ready(res)) => res, - _ => panic!("error"), - }; - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - - let res = match <(Path<(String, String)>, Path<(String, String)>)>::extract(&req) - .poll() - { - Ok(Async::Ready(res)) => res, - _ => panic!("error"), - }; - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - assert_eq!((res.1).0, "name"); - assert_eq!((res.1).1, "user1"); - - let () = <()>::extract(&req); - } -} diff --git a/src/fs.rs b/src/fs.rs deleted file mode 100644 index 10cdaff7b..000000000 --- a/src/fs.rs +++ /dev/null @@ -1,1786 +0,0 @@ -//! Static files support -use std::fmt::Write; -use std::fs::{DirEntry, File, Metadata}; -use std::io::{Read, Seek}; -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use std::path::{Path, PathBuf}; -use std::time::{SystemTime, UNIX_EPOCH}; -use std::{cmp, io}; - -#[cfg(unix)] -use std::os::unix::fs::MetadataExt; - -use bytes::Bytes; -use futures::{Async, Future, Poll, Stream}; -use futures_cpupool::{CpuFuture, CpuPool}; -use htmlescape::encode_minimal as escape_html_entity; -use mime; -use mime_guess::{get_mime_type, guess_mime_type}; -use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; - -use error::{Error, StaticFileError}; -use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; -use header; -use header::{ContentDisposition, DispositionParam, DispositionType}; -use http::{ContentEncoding, Method, StatusCode}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use param::FromParam; -use server::settings::DEFAULT_CPUPOOL; - -///Describes `StaticFiles` configiration -/// -///To configure actix's static resources you need -///to define own configiration type and implement any method -///you wish to customize. -///As trait implements reasonable defaults for Actix. -/// -///## Example -/// -///```rust -/// extern crate mime; -/// extern crate actix_web; -/// use actix_web::http::header::DispositionType; -/// use actix_web::fs::{StaticFileConfig, NamedFile}; -/// -/// #[derive(Default)] -/// struct MyConfig; -/// -/// impl StaticFileConfig for MyConfig { -/// fn content_disposition_map(typ: mime::Name) -> DispositionType { -/// DispositionType::Attachment -/// } -/// } -/// -/// let file = NamedFile::open_with_config("foo.txt", MyConfig); -///``` -pub trait StaticFileConfig: Default { - ///Describes mapping for mime type to content disposition header - /// - ///By default `IMAGE`, `TEXT` and `VIDEO` are mapped to Inline. - ///Others are mapped to Attachment - fn content_disposition_map(typ: mime::Name) -> DispositionType { - match typ { - mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, - _ => DispositionType::Attachment, - } - } - - ///Describes whether Actix should attempt to calculate `ETag` - /// - ///Defaults to `true` - fn is_use_etag() -> bool { - true - } - - ///Describes whether Actix should use last modified date of file. - /// - ///Defaults to `true` - fn is_use_last_modifier() -> bool { - true - } - - ///Describes allowed methods to access static resources. - /// - ///By default all methods are allowed - fn is_method_allowed(_method: &Method) -> bool { - true - } -} - -///Default content disposition as described in -///[StaticFileConfig](trait.StaticFileConfig.html) -#[derive(Default)] -pub struct DefaultConfig; -impl StaticFileConfig for DefaultConfig {} - -/// Return the MIME type associated with a filename extension (case-insensitive). -/// If `ext` is empty or no associated type for the extension was found, returns -/// the type `application/octet-stream`. -#[inline] -pub fn file_extension_to_mime(ext: &str) -> mime::Mime { - get_mime_type(ext) -} - -/// A file with an associated name. -#[derive(Debug)] -pub struct NamedFile { - path: PathBuf, - file: File, - content_type: mime::Mime, - content_disposition: header::ContentDisposition, - md: Metadata, - modified: Option, - cpu_pool: Option, - encoding: Option, - status_code: StatusCode, - _cd_map: PhantomData, -} - -impl NamedFile { - /// Attempts to open a file in read-only mode. - /// - /// # Examples - /// - /// ```rust - /// use actix_web::fs::NamedFile; - /// - /// let file = NamedFile::open("foo.txt"); - /// ``` - pub fn open>(path: P) -> io::Result { - Self::open_with_config(path, DefaultConfig) - } -} - -impl NamedFile { - /// Attempts to open a file in read-only mode using provided configiration. - /// - /// # Examples - /// - /// ```rust - /// use actix_web::fs::{DefaultConfig, NamedFile}; - /// - /// let file = NamedFile::open_with_config("foo.txt", DefaultConfig); - /// ``` - pub fn open_with_config>(path: P, _: C) -> io::Result> { - let path = path.as_ref().to_path_buf(); - - // Get the name of the file and use it to construct default Content-Type - // and Content-Disposition values - let (content_type, content_disposition) = { - let filename = match path.file_name() { - Some(name) => name.to_string_lossy(), - None => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Provided path has no filename", - )) - } - }; - - let ct = guess_mime_type(&path); - let disposition_type = C::content_disposition_map(ct.type_()); - let cd = ContentDisposition { - disposition: disposition_type, - parameters: vec![DispositionParam::Filename(filename.into_owned())], - }; - (ct, cd) - }; - - let file = File::open(&path)?; - let md = file.metadata()?; - let modified = md.modified().ok(); - let cpu_pool = None; - let encoding = None; - Ok(NamedFile { - path, - file, - content_type, - content_disposition, - md, - modified, - cpu_pool, - encoding, - status_code: StatusCode::OK, - _cd_map: PhantomData, - }) - } - - /// Returns reference to the underlying `File` object. - #[inline] - pub fn file(&self) -> &File { - &self.file - } - - /// Retrieve the path of this file. - /// - /// # Examples - /// - /// ```rust - /// # use std::io; - /// use actix_web::fs::NamedFile; - /// - /// # fn path() -> io::Result<()> { - /// let file = NamedFile::open("test.txt")?; - /// assert_eq!(file.path().as_os_str(), "foo.txt"); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn path(&self) -> &Path { - self.path.as_path() - } - - /// Set `CpuPool` to use - #[inline] - pub fn set_cpu_pool(mut self, cpu_pool: CpuPool) -> Self { - self.cpu_pool = Some(cpu_pool); - self - } - - /// Set response **Status Code** - pub fn set_status_code(mut self, status: StatusCode) -> Self { - self.status_code = status; - self - } - - /// Set the MIME Content-Type for serving this file. By default - /// the Content-Type is inferred from the filename extension. - #[inline] - pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self { - self.content_type = mime_type; - self - } - - /// Set the Content-Disposition for serving this file. This allows - /// changing the inline/attachment disposition as well as the filename - /// sent to the peer. By default the disposition is `inline` for text, - /// image, and video content types, and `attachment` otherwise, and - /// the filename is taken from the path provided in the `open` method - /// after converting it to UTF-8 using - /// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy). - #[inline] - pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { - self.content_disposition = cd; - self - } - - /// Set content encoding for serving this file - #[inline] - pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { - self.encoding = Some(enc); - self - } - - fn etag(&self) -> Option { - // This etag format is similar to Apache's. - self.modified.as_ref().map(|mtime| { - let ino = { - #[cfg(unix)] - { - self.md.ino() - } - #[cfg(not(unix))] - { - 0 - } - }; - - let dur = mtime - .duration_since(UNIX_EPOCH) - .expect("modification time must be after epoch"); - header::EntityTag::strong(format!( - "{:x}:{:x}:{:x}:{:x}", - ino, - self.md.len(), - dur.as_secs(), - dur.subsec_nanos() - )) - }) - } - - fn last_modified(&self) -> Option { - self.modified.map(|mtime| mtime.into()) - } -} - -impl Deref for NamedFile { - type Target = File; - - fn deref(&self) -> &File { - &self.file - } -} - -impl DerefMut for NamedFile { - fn deref_mut(&mut self) -> &mut File { - &mut self.file - } -} - -/// Returns true if `req` has no `If-Match` header or one which matches `etag`. -fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { - match req.get_header::() { - None | Some(header::IfMatch::Any) => true, - Some(header::IfMatch::Items(ref items)) => { - if let Some(some_etag) = etag { - for item in items { - if item.strong_eq(some_etag) { - return true; - } - } - } - false - } - } -} - -/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`. -fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { - match req.get_header::() { - Some(header::IfNoneMatch::Any) => false, - Some(header::IfNoneMatch::Items(ref items)) => { - if let Some(some_etag) = etag { - for item in items { - if item.weak_eq(some_etag) { - return false; - } - } - } - true - } - None => true, - } -} - -impl Responder for NamedFile { - type Item = HttpResponse; - type Error = io::Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - if self.status_code != StatusCode::OK { - let mut resp = HttpResponse::build(self.status_code); - resp.set(header::ContentType(self.content_type.clone())) - .header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); - - if let Some(current_encoding) = self.encoding { - resp.content_encoding(current_encoding); - } - let reader = ChunkedReadFile { - size: self.md.len(), - offset: 0, - cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), - file: Some(self.file), - fut: None, - counter: 0, - }; - return Ok(resp.streaming(reader)); - } - - if !C::is_method_allowed(req.method()) { - return Ok(HttpResponse::MethodNotAllowed() - .header(header::CONTENT_TYPE, "text/plain") - .header(header::ALLOW, "GET, HEAD") - .body("This resource only supports GET and HEAD.")); - } - - let etag = if C::is_use_etag() { self.etag() } else { None }; - let last_modified = if C::is_use_last_modifier() { - self.last_modified() - } else { - None - }; - - // check preconditions - let precondition_failed = if !any_match(etag.as_ref(), req) { - true - } else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) = - (last_modified, req.get_header()) - { - m > since - } else { - false - }; - - // check last modified - let not_modified = if !none_match(etag.as_ref(), req) { - true - } else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) = - (last_modified, req.get_header()) - { - m <= since - } else { - false - }; - - let mut resp = HttpResponse::build(self.status_code); - resp.set(header::ContentType(self.content_type.clone())) - .header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); - - if let Some(current_encoding) = self.encoding { - resp.content_encoding(current_encoding); - } - - resp.if_some(last_modified, |lm, resp| { - resp.set(header::LastModified(lm)); - }).if_some(etag, |etag, resp| { - resp.set(header::ETag(etag)); - }); - - resp.header(header::ACCEPT_RANGES, "bytes"); - - let mut length = self.md.len(); - let mut offset = 0; - - // check for range header - if let Some(ranges) = req.headers().get(header::RANGE) { - if let Ok(rangesheader) = ranges.to_str() { - if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { - length = rangesvec[0].length; - offset = rangesvec[0].start; - resp.content_encoding(ContentEncoding::Identity); - resp.header( - header::CONTENT_RANGE, - format!( - "bytes {}-{}/{}", - offset, - offset + length - 1, - self.md.len() - ), - ); - } else { - resp.header(header::CONTENT_RANGE, format!("bytes */{}", length)); - return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); - }; - } else { - return Ok(resp.status(StatusCode::BAD_REQUEST).finish()); - }; - }; - - resp.header(header::CONTENT_LENGTH, format!("{}", length)); - - if precondition_failed { - return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); - } else if not_modified { - return Ok(resp.status(StatusCode::NOT_MODIFIED).finish()); - } - - if *req.method() == Method::HEAD { - Ok(resp.finish()) - } else { - let reader = ChunkedReadFile { - offset, - size: length, - cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), - file: Some(self.file), - fut: None, - counter: 0, - }; - if offset != 0 || length != self.md.len() { - return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)); - }; - Ok(resp.streaming(reader)) - } - } -} - -/// A helper created from a `std::fs::File` which reads the file -/// chunk-by-chunk on a `CpuPool`. -pub struct ChunkedReadFile { - size: u64, - offset: u64, - cpu_pool: CpuPool, - file: Option, - fut: Option>, - counter: u64, -} - -impl Stream for ChunkedReadFile { - type Item = Bytes; - type Error = Error; - - fn poll(&mut self) -> Poll, Error> { - if self.fut.is_some() { - return match self.fut.as_mut().unwrap().poll()? { - Async::Ready((file, bytes)) => { - self.fut.take(); - self.file = Some(file); - self.offset += bytes.len() as u64; - self.counter += bytes.len() as u64; - Ok(Async::Ready(Some(bytes))) - } - Async::NotReady => Ok(Async::NotReady), - }; - } - - let size = self.size; - let offset = self.offset; - let counter = self.counter; - - if size == counter { - Ok(Async::Ready(None)) - } else { - let mut file = self.file.take().expect("Use after completion"); - self.fut = Some(self.cpu_pool.spawn_fn(move || { - let max_bytes: usize; - max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; - let mut buf = Vec::with_capacity(max_bytes); - file.seek(io::SeekFrom::Start(offset))?; - let nbytes = - file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; - if nbytes == 0 { - return Err(io::ErrorKind::UnexpectedEof.into()); - } - Ok((file, Bytes::from(buf))) - })); - self.poll() - } - } -} - -type DirectoryRenderer = - Fn(&Directory, &HttpRequest) -> Result; - -/// A directory; responds with the generated directory listing. -#[derive(Debug)] -pub struct Directory { - /// Base directory - pub base: PathBuf, - /// Path of subdirectory to generate listing for - pub path: PathBuf, -} - -impl Directory { - /// Create a new directory - pub fn new(base: PathBuf, path: PathBuf) -> Directory { - Directory { base, path } - } - - /// Is this entry visible from this directory? - pub fn is_visible(&self, entry: &io::Result) -> bool { - if let Ok(ref entry) = *entry { - if let Some(name) = entry.file_name().to_str() { - if name.starts_with('.') { - return false; - } - } - if let Ok(ref md) = entry.metadata() { - let ft = md.file_type(); - return ft.is_dir() || ft.is_file() || ft.is_symlink(); - } - } - false - } -} - -fn directory_listing( - dir: &Directory, req: &HttpRequest, -) -> Result { - let index_of = format!("Index of {}", req.path()); - let mut body = String::new(); - let base = Path::new(req.path()); - - for entry in dir.path.read_dir()? { - if dir.is_visible(&entry) { - let entry = entry.unwrap(); - let p = match entry.path().strip_prefix(&dir.path) { - Ok(p) => base.join(p), - Err(_) => continue, - }; - // show file url as relative to static path - let file_url = utf8_percent_encode(&p.to_string_lossy(), DEFAULT_ENCODE_SET) - .to_string(); - // " -- " & -- & ' -- ' < -- < > -- > - let file_name = escape_html_entity(&entry.file_name().to_string_lossy()); - - // if file is a directory, add '/' to the end of the name - if let Ok(metadata) = entry.metadata() { - if metadata.is_dir() { - let _ = write!( - body, - "
  • {}/
  • ", - file_url, file_name - ); - } else { - let _ = write!( - body, - "
  • {}
  • ", - file_url, file_name - ); - } - } else { - continue; - } - } - } - - let html = format!( - "\ - {}\ -

    {}

    \ -
      \ - {}\ -
    \n", - index_of, index_of, body - ); - Ok(HttpResponse::Ok() - .content_type("text/html; charset=utf-8") - .body(html)) -} - -/// Static files handling -/// -/// `StaticFile` handler must be registered with `App::handler()` method, -/// because `StaticFile` handler requires access sub-path information. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{fs, App}; -/// -/// fn main() { -/// let app = App::new() -/// .handler("/static", fs::StaticFiles::new(".").unwrap()) -/// .finish(); -/// } -/// ``` -pub struct StaticFiles { - directory: PathBuf, - index: Option, - show_index: bool, - cpu_pool: CpuPool, - default: Box>, - renderer: Box>, - _chunk_size: usize, - _follow_symlinks: bool, - _cd_map: PhantomData, -} - -impl StaticFiles { - /// Create new `StaticFiles` instance for specified base directory. - /// - /// `StaticFile` uses `CpuPool` for blocking filesystem operations. - /// By default pool with 20 threads is used. - /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. - pub fn new>(dir: T) -> Result, Error> { - Self::with_config(dir, DefaultConfig) - } - - /// Create new `StaticFiles` instance for specified base directory and - /// `CpuPool`. - pub fn with_pool>( - dir: T, pool: CpuPool, - ) -> Result, Error> { - Self::with_config_pool(dir, pool, DefaultConfig) - } -} - -impl StaticFiles { - /// Create new `StaticFiles` instance for specified base directory. - /// - /// Identical with `new` but allows to specify configiration to use. - pub fn with_config>( - dir: T, config: C, - ) -> Result, Error> { - // use default CpuPool - let pool = { DEFAULT_CPUPOOL.lock().clone() }; - - StaticFiles::with_config_pool(dir, pool, config) - } - - /// Create new `StaticFiles` instance for specified base directory with config and - /// `CpuPool`. - pub fn with_config_pool>( - dir: T, pool: CpuPool, _: C, - ) -> Result, Error> { - let dir = dir.into().canonicalize()?; - - if !dir.is_dir() { - return Err(StaticFileError::IsNotDirectory.into()); - } - - Ok(StaticFiles { - directory: dir, - index: None, - show_index: false, - cpu_pool: pool, - default: Box::new(WrapHandler::new(|_: &_| { - HttpResponse::new(StatusCode::NOT_FOUND) - })), - renderer: Box::new(directory_listing), - _chunk_size: 0, - _follow_symlinks: false, - _cd_map: PhantomData, - }) - } - - /// Show files listing for directories. - /// - /// By default show files listing is disabled. - pub fn show_files_listing(mut self) -> Self { - self.show_index = true; - self - } - - /// Set custom directory renderer - pub fn files_listing_renderer(mut self, f: F) -> Self - where - for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) - -> Result - + 'static, - { - self.renderer = Box::new(f); - self - } - - /// Set index file - /// - /// Redirects to specific index file for directory "/" instead of - /// showing files listing. - pub fn index_file>(mut self, index: T) -> StaticFiles { - self.index = Some(index.into()); - self - } - - /// Sets default handler which is used when no matched file could be found. - pub fn default_handler>(mut self, handler: H) -> StaticFiles { - self.default = Box::new(WrapHandler::new(handler)); - self - } - - fn try_handle( - &self, req: &HttpRequest, - ) -> Result, Error> { - let tail: String = req.match_info().query("tail")?; - let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?; - - // full filepath - let path = self.directory.join(&relpath).canonicalize()?; - - if path.is_dir() { - if let Some(ref redir_index) = self.index { - // TODO: Don't redirect, just return the index content. - // TODO: It'd be nice if there were a good usable URL manipulation - // library - let mut new_path: String = req.path().to_owned(); - if !new_path.ends_with('/') { - new_path.push('/'); - } - new_path.push_str(redir_index); - HttpResponse::Found() - .header(header::LOCATION, new_path.as_str()) - .finish() - .respond_to(&req) - } else if self.show_index { - let dir = Directory::new(self.directory.clone(), path); - Ok((*self.renderer)(&dir, &req)?.into()) - } else { - Err(StaticFileError::IsDirectory.into()) - } - } else { - NamedFile::open_with_config(path, C::default())? - .set_cpu_pool(self.cpu_pool.clone()) - .respond_to(&req)? - .respond_to(&req) - } - } -} - -impl Handler for StaticFiles { - type Result = Result, Error>; - - fn handle(&self, req: &HttpRequest) -> Self::Result { - self.try_handle(req).or_else(|e| { - debug!("StaticFiles: Failed to handle {}: {}", req.path(), e); - Ok(self.default.handle(req)) - }) - } -} - -/// HTTP Range header representation. -#[derive(Debug, Clone, Copy)] -struct HttpRange { - pub start: u64, - pub length: u64, -} - -static PREFIX: &'static str = "bytes="; -const PREFIX_LEN: usize = 6; - -impl HttpRange { - /// Parses Range HTTP header string as per RFC 2616. - /// - /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`). - /// `size` is full size of response (file). - fn parse(header: &str, size: u64) -> Result, ()> { - if header.is_empty() { - return Ok(Vec::new()); - } - if !header.starts_with(PREFIX) { - return Err(()); - } - - let size_sig = size as i64; - let mut no_overlap = false; - - let all_ranges: Vec> = header[PREFIX_LEN..] - .split(',') - .map(|x| x.trim()) - .filter(|x| !x.is_empty()) - .map(|ra| { - let mut start_end_iter = ra.split('-'); - - let start_str = start_end_iter.next().ok_or(())?.trim(); - let end_str = start_end_iter.next().ok_or(())?.trim(); - - if start_str.is_empty() { - // If no start is specified, end specifies the - // range start relative to the end of the file. - let mut length: i64 = try!(end_str.parse().map_err(|_| ())); - - if length > size_sig { - length = size_sig; - } - - Ok(Some(HttpRange { - start: (size_sig - length) as u64, - length: length as u64, - })) - } else { - let start: i64 = start_str.parse().map_err(|_| ())?; - - if start < 0 { - return Err(()); - } - if start >= size_sig { - no_overlap = true; - return Ok(None); - } - - let length = if end_str.is_empty() { - // If no end is specified, range extends to end of the file. - size_sig - start - } else { - let mut end: i64 = end_str.parse().map_err(|_| ())?; - - if start > end { - return Err(()); - } - - if end >= size_sig { - end = size_sig - 1; - } - - end - start + 1 - }; - - Ok(Some(HttpRange { - start: start as u64, - length: length as u64, - })) - } - }).collect::>()?; - - let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); - - if no_overlap && ranges.is_empty() { - return Err(()); - } - - Ok(ranges) - } -} - -#[cfg(test)] -mod tests { - use std::fs; - - use super::*; - use application::App; - use body::{Binary, Body}; - use http::{header, Method, StatusCode}; - use test::{self, TestRequest}; - - #[test] - fn test_file_extension_to_mime() { - let m = file_extension_to_mime("jpg"); - assert_eq!(m, mime::IMAGE_JPEG); - - let m = file_extension_to_mime("invalid extension!!"); - assert_eq!(m, mime::APPLICATION_OCTET_STREAM); - - let m = file_extension_to_mime(""); - assert_eq!(m, mime::APPLICATION_OCTET_STREAM); - } - - #[test] - fn test_named_file_text() { - assert!(NamedFile::open("test--").is_err()); - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - } - - #[test] - fn test_named_file_set_content_type() { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_content_type(mime::TEXT_XML) - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/xml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - } - - #[test] - fn test_named_file_image() { - let mut file = NamedFile::open("tests/test.png") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"test.png\"" - ); - } - - #[test] - fn test_named_file_image_attachment() { - use header::{ContentDisposition, DispositionParam, DispositionType}; - let cd = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Filename(String::from("test.png"))], - }; - let mut file = NamedFile::open("tests/test.png") - .unwrap() - .set_content_disposition(cd) - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.png\"" - ); - } - - #[derive(Default)] - pub struct AllAttachmentConfig; - impl StaticFileConfig for AllAttachmentConfig { - fn content_disposition_map(_typ: mime::Name) -> DispositionType { - DispositionType::Attachment - } - } - - #[derive(Default)] - pub struct AllInlineConfig; - impl StaticFileConfig for AllInlineConfig { - fn content_disposition_map(_typ: mime::Name) -> DispositionType { - DispositionType::Inline - } - } - - #[test] - fn test_named_file_image_attachment_and_custom_config() { - let file = NamedFile::open_with_config("tests/test.png", AllAttachmentConfig) - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.png\"" - ); - - let file = NamedFile::open_with_config("tests/test.png", AllInlineConfig) - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"test.png\"" - ); - } - - #[test] - fn test_named_file_binary() { - let mut file = NamedFile::open("tests/test.binary") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "application/octet-stream" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.binary\"" - ); - } - - #[test] - fn test_named_file_status_code_text() { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_status_code(StatusCode::NOT_FOUND) - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_named_file_ranges_status_code() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".").unwrap().index_file("Cargo.toml"), - ) - }); - - // Valid range header - let request = srv - .get() - .uri(srv.url("/t%65st/Cargo.toml")) - .header(header::RANGE, "bytes=10-20") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); - - // Invalid range header - let request = srv - .get() - .uri(srv.url("/t%65st/Cargo.toml")) - .header(header::RANGE, "bytes=1-0") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - - assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); - } - - #[test] - fn test_named_file_content_range_headers() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".") - .unwrap() - .index_file("tests/test.binary"), - ) - }); - - // Valid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-20") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentrange = response - .headers() - .get(header::CONTENT_RANGE) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentrange, "bytes 10-20/100"); - - // Invalid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-5") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentrange = response - .headers() - .get(header::CONTENT_RANGE) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentrange, "bytes */100"); - } - - #[test] - fn test_named_file_content_length_headers() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".") - .unwrap() - .index_file("tests/test.binary"), - ) - }); - - // Valid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-20") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "11"); - - // Invalid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-8") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "0"); - - // Without range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .no_default_headers() - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "100"); - - // chunked - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - { - let te = response - .headers() - .get(header::TRANSFER_ENCODING) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(te, "chunked"); - } - let bytes = srv.execute(response.body()).unwrap(); - let data = Bytes::from(fs::read("tests/test.binary").unwrap()); - assert_eq!(bytes, data); - } - - #[derive(Default)] - pub struct OnlyMethodHeadConfig; - impl StaticFileConfig for OnlyMethodHeadConfig { - fn is_method_allowed(method: &Method) -> bool { - match *method { - Method::HEAD => true, - _ => false, - } - } - } - - #[test] - fn test_named_file_not_allowed() { - let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); - let req = TestRequest::default().method(Method::POST).finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); - let req = TestRequest::default().method(Method::PUT).finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); - let req = TestRequest::default().method(Method::GET).finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[test] - fn test_named_file_content_encoding() { - let req = TestRequest::default().method(Method::GET).finish(); - let file = NamedFile::open("Cargo.toml").unwrap(); - - assert!(file.encoding.is_none()); - let resp = file - .set_content_encoding(ContentEncoding::Identity) - .respond_to(&req) - .unwrap(); - - assert!(resp.content_encoding().is_some()); - assert_eq!(resp.content_encoding().unwrap().as_str(), "identity"); - } - - #[test] - fn test_named_file_any_method() { - let req = TestRequest::default().method(Method::POST).finish(); - let file = NamedFile::open("Cargo.toml").unwrap(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[test] - fn test_static_files() { - let mut st = StaticFiles::new(".").unwrap().show_files_listing(); - let req = TestRequest::with_uri("/missing") - .param("tail", "missing") - .finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - st.show_index = false; - let req = TestRequest::default().finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let req = TestRequest::default().param("tail", "").finish(); - - st.show_index = true; - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/html; charset=utf-8" - ); - assert!(resp.body().is_binary()); - assert!(format!("{:?}", resp.body()).contains("README.md")); - } - - #[test] - fn test_static_files_bad_directory() { - let st: Result, Error> = StaticFiles::new("missing"); - assert!(st.is_err()); - - let st: Result, Error> = StaticFiles::new("Cargo.toml"); - assert!(st.is_err()); - } - - #[test] - fn test_default_handler_file_missing() { - let st = StaticFiles::new(".") - .unwrap() - .default_handler(|_: &_| "default content"); - let req = TestRequest::with_uri("/missing") - .param("tail", "missing") - .finish(); - - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body(), - &Body::Binary(Binary::Slice(b"default content")) - ); - } - - #[test] - fn test_redirect_to_index() { - let st = StaticFiles::new(".").unwrap().index_file("index.html"); - let req = TestRequest::default().uri("/tests").finish(); - - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!( - resp.headers().get(header::LOCATION).unwrap(), - "/tests/index.html" - ); - - let req = TestRequest::default().uri("/tests/").finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!( - resp.headers().get(header::LOCATION).unwrap(), - "/tests/index.html" - ); - } - - #[test] - fn test_redirect_to_index_nested() { - let st = StaticFiles::new(".").unwrap().index_file("mod.rs"); - let req = TestRequest::default().uri("/src/client").finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!( - resp.headers().get(header::LOCATION).unwrap(), - "/src/client/mod.rs" - ); - } - - #[test] - fn integration_redirect_to_index_with_prefix() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .prefix("public") - .handler("/", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) - }); - - let request = srv.get().uri(srv.url("/public")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/public/Cargo.toml"); - - let request = srv.get().uri(srv.url("/public/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/public/Cargo.toml"); - } - - #[test] - fn integration_redirect_to_index() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".").unwrap().index_file("Cargo.toml"), - ) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/test/Cargo.toml"); - - let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/test/Cargo.toml"); - } - - #[test] - fn integration_percent_encoded() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".").unwrap().index_file("Cargo.toml"), - ) - }); - - let request = srv - .get() - .uri(srv.url("/test/%43argo.toml")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - } - - struct T(&'static str, u64, Vec); - - #[test] - fn test_parse() { - let tests = vec![ - T("", 0, vec![]), - T("", 1000, vec![]), - T("foo", 0, vec![]), - T("bytes=", 0, vec![]), - T("bytes=7", 10, vec![]), - T("bytes= 7 ", 10, vec![]), - T("bytes=1-", 0, vec![]), - T("bytes=5-4", 10, vec![]), - T("bytes=0-2,5-4", 10, vec![]), - T("bytes=2-5,4-3", 10, vec![]), - T("bytes=--5,4--3", 10, vec![]), - T("bytes=A-", 10, vec![]), - T("bytes=A- ", 10, vec![]), - T("bytes=A-Z", 10, vec![]), - T("bytes= -Z", 10, vec![]), - T("bytes=5-Z", 10, vec![]), - T("bytes=Ran-dom, garbage", 10, vec![]), - T("bytes=0x01-0x02", 10, vec![]), - T("bytes= ", 10, vec![]), - T("bytes= , , , ", 10, vec![]), - T( - "bytes=0-9", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=0-", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=5-", - 10, - vec![HttpRange { - start: 5, - length: 5, - }], - ), - T( - "bytes=0-20", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=15-,0-5", - 10, - vec![HttpRange { - start: 0, - length: 6, - }], - ), - T( - "bytes=1-2,5-", - 10, - vec![ - HttpRange { - start: 1, - length: 2, - }, - HttpRange { - start: 5, - length: 5, - }, - ], - ), - T( - "bytes=-2 , 7-", - 11, - vec![ - HttpRange { - start: 9, - length: 2, - }, - HttpRange { - start: 7, - length: 4, - }, - ], - ), - T( - "bytes=0-0 ,2-2, 7-", - 11, - vec![ - HttpRange { - start: 0, - length: 1, - }, - HttpRange { - start: 2, - length: 1, - }, - HttpRange { - start: 7, - length: 4, - }, - ], - ), - T( - "bytes=-5", - 10, - vec![HttpRange { - start: 5, - length: 5, - }], - ), - T( - "bytes=-15", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=0-499", - 10000, - vec![HttpRange { - start: 0, - length: 500, - }], - ), - T( - "bytes=500-999", - 10000, - vec![HttpRange { - start: 500, - length: 500, - }], - ), - T( - "bytes=-500", - 10000, - vec![HttpRange { - start: 9500, - length: 500, - }], - ), - T( - "bytes=9500-", - 10000, - vec![HttpRange { - start: 9500, - length: 500, - }], - ), - T( - "bytes=0-0,-1", - 10000, - vec![ - HttpRange { - start: 0, - length: 1, - }, - HttpRange { - start: 9999, - length: 1, - }, - ], - ), - T( - "bytes=500-600,601-999", - 10000, - vec![ - HttpRange { - start: 500, - length: 101, - }, - HttpRange { - start: 601, - length: 399, - }, - ], - ), - T( - "bytes=500-700,601-999", - 10000, - vec![ - HttpRange { - start: 500, - length: 201, - }, - HttpRange { - start: 601, - length: 399, - }, - ], - ), - // Match Apache laxity: - T( - "bytes= 1 -2 , 4- 5, 7 - 8 , ,,", - 11, - vec![ - HttpRange { - start: 1, - length: 2, - }, - HttpRange { - start: 4, - length: 2, - }, - HttpRange { - start: 7, - length: 2, - }, - ], - ), - ]; - - for t in tests { - let header = t.0; - let size = t.1; - let expected = t.2; - - let res = HttpRange::parse(header, size); - - if res.is_err() { - if expected.is_empty() { - continue; - } else { - assert!( - false, - "parse({}, {}) returned error {:?}", - header, - size, - res.unwrap_err() - ); - } - } - - let got = res.unwrap(); - - if got.len() != expected.len() { - assert!( - false, - "len(parseRange({}, {})) = {}, want {}", - header, - size, - got.len(), - expected.len() - ); - continue; - } - - for i in 0..expected.len() { - if got[i].start != expected[i].start { - assert!( - false, - "parseRange({}, {})[{}].start = {}, want {}", - header, size, i, got[i].start, expected[i].start - ) - } - if got[i].length != expected[i].length { - assert!( - false, - "parseRange({}, {})[{}].length = {}, want {}", - header, size, i, got[i].length, expected[i].length - ) - } - } - } - } -} diff --git a/src/handler.rs b/src/handler.rs deleted file mode 100644 index 88210fbc0..000000000 --- a/src/handler.rs +++ /dev/null @@ -1,562 +0,0 @@ -use std::marker::PhantomData; -use std::ops::Deref; - -use futures::future::{err, ok, Future}; -use futures::{Async, Poll}; - -use error::Error; -use http::StatusCode; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use resource::DefaultResource; - -/// Trait defines object that could be registered as route handler -#[allow(unused_variables)] -pub trait Handler: 'static { - /// The type of value that handler will return. - type Result: Responder; - - /// Handle request - fn handle(&self, req: &HttpRequest) -> 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>; - - /// The associated error which can be returned. - type Error: Into; - - /// Convert itself to `AsyncResult` or `Error`. - fn respond_to( - self, req: &HttpRequest, - ) -> Result; -} - -/// 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: Sized { - /// Configuration for conversion process - type Config: Default; - - /// Future that resolves to a Self - type Result: Into>; - - /// Convert request to a Self - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result; - - /// Convert request to a Self - /// - /// This method uses default extractor configuration - fn extract(req: &HttpRequest) -> Self::Result { - Self::from_request(req, &Self::Config::default()) - } -} - -/// Combines two different responder types into a single type -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # use futures::future::Future; -/// use actix_web::{AsyncResponder, Either, Error, HttpRequest, HttpResponse}; -/// use futures::future::result; -/// -/// type RegisterResult = -/// Either>>; -/// -/// fn index(req: HttpRequest) -> RegisterResult { -/// if is_a_variant() { -/// // <- choose variant A -/// Either::A(HttpResponse::BadRequest().body("Bad data")) -/// } else { -/// Either::B( -/// // <- variant B -/// result(Ok(HttpResponse::Ok() -/// .content_type("text/html") -/// .body("Hello!"))) -/// .responder(), -/// ) -/// } -/// } -/// # fn is_a_variant() -> bool { true } -/// # fn main() {} -/// ``` -#[derive(Debug)] -pub enum Either { - /// First branch of the type - A(A), - /// Second branch of the type - B(B), -} - -impl Responder for Either -where - A: Responder, - B: Responder, -{ - type Item = AsyncResult; - type Error = Error; - - fn respond_to( - self, req: &HttpRequest, - ) -> Result, 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 Future for Either -where - A: Future, - B: Future, -{ - type Item = I; - type Error = E; - - fn poll(&mut self) -> Poll { - match *self { - Either::A(ref mut fut) => fut.poll(), - Either::B(ref mut fut) => fut.poll(), - } - } -} - -impl Responder for Option -where - T: Responder, -{ - type Item = AsyncResult; - type Error = Error; - - fn respond_to( - self, req: &HttpRequest, - ) -> Result, 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> { -/// 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: Sized { - /// Convert to a boxed future - fn responder(self) -> Box>; -} - -impl AsyncResponder for F -where - F: Future + 'static, - I: Responder + 'static, - E: Into + 'static, -{ - fn responder(self) -> Box> { - Box::new(self) - } -} - -/// Handler for Fn() -impl Handler for F -where - F: Fn(&HttpRequest) -> R + 'static, - R: Responder + 'static, -{ - type Result = R; - - fn handle(&self, req: &HttpRequest) -> 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 - reply process completes in the future -pub struct AsyncResult(Option>); - -impl Future for AsyncResult { - type Item = I; - type Error = E; - - fn poll(&mut self) -> Poll { - 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 { - Ok(I), - Err(E), - Future(Box>), -} - -impl AsyncResult { - /// Create async response - #[inline] - pub fn async(fut: Box>) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Future(fut))) - } - - /// Send response - #[inline] - pub fn ok>(ok: R) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Ok(ok.into()))) - } - - /// Send error - #[inline] - pub fn err>(err: R) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Err(err.into()))) - } - - #[inline] - pub(crate) fn into(self) -> AsyncResultItem { - 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 { - type Item = AsyncResult; - type Error = Error; - - fn respond_to( - self, _: &HttpRequest, - ) -> Result, Error> { - Ok(self) - } -} - -impl Responder for HttpResponse { - type Item = AsyncResult; - type Error = Error; - - #[inline] - fn respond_to( - self, _: &HttpRequest, - ) -> Result, Error> { - Ok(AsyncResult(Some(AsyncResultItem::Ok(self)))) - } -} - -impl From for AsyncResult { - #[inline] - fn from(resp: T) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Ok(resp))) - } -} - -impl> Responder for Result { - type Item = ::Item; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - match self { - Ok(val) => match val.respond_to(req) { - Ok(val) => Ok(val), - Err(err) => Err(err.into()), - }, - Err(err) => Err(err.into()), - } - } -} - -impl> From, E>> for AsyncResult { - #[inline] - fn from(res: Result, E>) -> Self { - match res { - Ok(val) => val, - Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), - } - } -} - -impl> From> for AsyncResult { - #[inline] - fn from(res: Result) -> Self { - match res { - Ok(val) => AsyncResult(Some(AsyncResultItem::Ok(val))), - Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), - } - } -} - -impl From>, E>> for AsyncResult -where - T: 'static, - E: Into + 'static, -{ - #[inline] - fn from(res: Result>, 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 From>> for AsyncResult { - #[inline] - fn from(fut: Box>) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Future(fut))) - } -} - -/// Convenience type alias -pub type FutureResponse = Box>; - -impl Responder for Box> -where - I: Responder + 'static, - E: Into + 'static, -{ - type Item = AsyncResult; - type Error = Error; - - #[inline] - fn respond_to( - self, req: &HttpRequest, - ) -> Result, Error> { - let req = req.clone(); - let fut = self - .map_err(|e| e.into()) - .then(move |r| match r.respond_to(&req) { - Ok(reply) => match reply.into().into() { - AsyncResultItem::Ok(resp) => ok(resp), - _ => panic!("Nested async replies are not supported"), - }, - Err(e) => err(e), - }); - Ok(AsyncResult::async(Box::new(fut))) - } -} - -pub(crate) trait RouteHandler: 'static { - fn handle(&self, &HttpRequest) -> AsyncResult; - - fn has_default_resource(&self) -> bool { - false - } - - fn default_resource(&mut self, _: DefaultResource) {} - - fn finish(&mut self) {} -} - -/// Route handler wrapper for Handler -pub(crate) struct WrapHandler -where - H: Handler, - R: Responder, - S: 'static, -{ - h: H, - s: PhantomData, -} - -impl WrapHandler -where - H: Handler, - R: Responder, - S: 'static, -{ - pub fn new(h: H) -> Self { - WrapHandler { h, s: PhantomData } - } -} - -impl RouteHandler for WrapHandler -where - H: Handler, - R: Responder + 'static, - S: 'static, -{ - fn handle(&self, req: &HttpRequest) -> AsyncResult { - match self.h.handle(req).respond_to(req) { - Ok(reply) => reply.into(), - Err(err) => AsyncResult::err(err.into()), - } - } -} - -/// Async route handler -pub(crate) struct AsyncHandler -where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - S: 'static, -{ - h: Box, - s: PhantomData, -} - -impl AsyncHandler -where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - S: 'static, -{ - pub fn new(h: H) -> Self { - AsyncHandler { - h: Box::new(h), - s: PhantomData, - } - } -} - -impl RouteHandler for AsyncHandler -where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - S: 'static, -{ - fn handle(&self, req: &HttpRequest) -> AsyncResult { - let req = req.clone(); - let fut = (self.h)(&req).map_err(|e| e.into()).then(move |r| { - match r.respond_to(&req) { - Ok(reply) => match reply.into().into() { - AsyncResultItem::Ok(resp) => Either::A(ok(resp)), - AsyncResultItem::Err(e) => Either::A(err(e)), - AsyncResultItem::Future(fut) => Either::B(fut), - }, - Err(e) => Either::A(err(e)), - } - }); - AsyncResult::async(Box::new(fut)) - } -} - -/// Access an application state -/// -/// `S` - application state type -/// -/// ## Example -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Path, State}; -/// -/// /// Application state -/// struct MyApp { -/// msg: &'static str, -/// } -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// extract path info using serde -/// fn index(state: State, path: Path) -> 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(HttpRequest); - -impl Deref for State { - type Target = S; - - fn deref(&self) -> &S { - self.0.state() - } -} - -impl FromRequest for State { - type Config = (); - type Result = State; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - State(req.clone()) - } -} diff --git a/src/helpers.rs b/src/helpers.rs deleted file mode 100644 index e82d61616..000000000 --- a/src/helpers.rs +++ /dev/null @@ -1,571 +0,0 @@ -//! Various helpers - -use http::{header, StatusCode}; -use regex::Regex; - -use handler::Handler; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; - -/// Path normalization helper -/// -/// By normalizing it means: -/// -/// - Add a trailing slash to the path. -/// - Remove a trailing slash from the path. -/// - Double slashes are replaced by one. -/// -/// The handler returns as soon as it finds a path that resolves -/// correctly. The order if all enable is 1) merge, 3) both merge and append -/// and 3) append. If the path resolves with -/// at least one of those conditions, it will redirect to the new path. -/// -/// If *append* is *true* append slash when needed. If a resource is -/// defined with trailing slash and the request comes without it, it will -/// append it automatically. -/// -/// If *merge* is *true*, merge multiple consecutive slashes in the path into -/// one. -/// -/// This handler designed to be use as a handler for application's *default -/// resource*. -/// -/// ```rust -/// # extern crate actix_web; -/// # #[macro_use] extern crate serde_derive; -/// # use actix_web::*; -/// use actix_web::http::NormalizePath; -/// -/// # fn index(req: &HttpRequest) -> HttpResponse { -/// # HttpResponse::Ok().into() -/// # } -/// fn main() { -/// let app = App::new() -/// .resource("/test/", |r| r.f(index)) -/// .default_resource(|r| r.h(NormalizePath::default())) -/// .finish(); -/// } -/// ``` -/// In this example `/test`, `/test///` will be redirected to `/test/` url. -pub struct NormalizePath { - append: bool, - merge: bool, - re_merge: Regex, - redirect: StatusCode, - not_found: StatusCode, -} - -impl Default for NormalizePath { - /// Create default `NormalizePath` instance, *append* is set to *true*, - /// *merge* is set to *true* and *redirect* is set to - /// `StatusCode::MOVED_PERMANENTLY` - fn default() -> NormalizePath { - NormalizePath { - append: true, - merge: true, - re_merge: Regex::new("//+").unwrap(), - redirect: StatusCode::MOVED_PERMANENTLY, - not_found: StatusCode::NOT_FOUND, - } - } -} - -impl NormalizePath { - /// Create new `NormalizePath` instance - pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath { - NormalizePath { - append, - merge, - redirect, - re_merge: Regex::new("//+").unwrap(), - not_found: StatusCode::NOT_FOUND, - } - } -} - -impl Handler for NormalizePath { - type Result = HttpResponse; - - fn handle(&self, req: &HttpRequest) -> Self::Result { - let query = req.query_string(); - if self.merge { - // merge slashes - let p = self.re_merge.replace_all(req.path(), "/"); - if p.len() != req.path().len() { - if req.resource().has_prefixed_resource(p.as_ref()) { - let p = if !query.is_empty() { - p + "?" + query - } else { - p - }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_ref()) - .finish(); - } - // merge slashes and append trailing slash - if self.append && !p.ends_with('/') { - let p = p.as_ref().to_owned() + "/"; - if req.resource().has_prefixed_resource(&p) { - let p = if !query.is_empty() { - p + "?" + query - } else { - p - }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_str()) - .finish(); - } - } - - // try to remove trailing slash - if p.ends_with('/') { - let p = p.as_ref().trim_right_matches('/'); - if req.resource().has_prefixed_resource(p) { - let mut req = HttpResponse::build(self.redirect); - return if !query.is_empty() { - req.header( - header::LOCATION, - (p.to_owned() + "?" + query).as_str(), - ) - } else { - req.header(header::LOCATION, p) - }.finish(); - } - } - } else if p.ends_with('/') { - // try to remove trailing slash - let p = p.as_ref().trim_right_matches('/'); - if req.resource().has_prefixed_resource(p) { - let mut req = HttpResponse::build(self.redirect); - return if !query.is_empty() { - req.header( - header::LOCATION, - (p.to_owned() + "?" + query).as_str(), - ) - } else { - req.header(header::LOCATION, p) - }.finish(); - } - } - } - // append trailing slash - if self.append && !req.path().ends_with('/') { - let p = req.path().to_owned() + "/"; - if req.resource().has_prefixed_resource(&p) { - let p = if !query.is_empty() { - p + "?" + query - } else { - p - }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_str()) - .finish(); - } - } - HttpResponse::new(self.not_found) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use application::App; - use http::{header, Method}; - use test::TestRequest; - - fn index(_req: &HttpRequest) -> HttpResponse { - HttpResponse::new(StatusCode::OK) - } - - #[test] - fn test_normalize_path_trailing_slashes() { - let app = App::new() - .resource("/resource1", |r| r.method(Method::GET).f(index)) - .resource("/resource2/", |r| r.method(Method::GET).f(index)) - .default_resource(|r| r.h(NormalizePath::default())) - .finish(); - - // trailing slashes - let params = vec![ - ("/resource1", "", StatusCode::OK), - ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY), - ("/resource2/", "", StatusCode::OK), - ("/resource1?p1=1&p2=2", "", StatusCode::OK), - ( - "/resource1/?p1=1&p2=2", - "/resource1?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/resource2?p1=1&p2=2", - "/resource2/?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY, - ), - ("/resource2/?p1=1&p2=2", "", StatusCode::OK), - ]; - for (path, target, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - if !target.is_empty() { - assert_eq!( - target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap() - ); - } - } - } - - #[test] - fn test_prefixed_normalize_path_trailing_slashes() { - let app = App::new() - .prefix("/test") - .resource("/resource1", |r| r.method(Method::GET).f(index)) - .resource("/resource2/", |r| r.method(Method::GET).f(index)) - .default_resource(|r| r.h(NormalizePath::default())) - .finish(); - - // trailing slashes - let params = vec![ - ("/test/resource1", "", StatusCode::OK), - ( - "/test/resource1/", - "/test/resource1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/test/resource2", - "/test/resource2/", - StatusCode::MOVED_PERMANENTLY, - ), - ("/test/resource2/", "", StatusCode::OK), - ("/test/resource1?p1=1&p2=2", "", StatusCode::OK), - ( - "/test/resource1/?p1=1&p2=2", - "/test/resource1?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/test/resource2?p1=1&p2=2", - "/test/resource2/?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY, - ), - ("/test/resource2/?p1=1&p2=2", "", StatusCode::OK), - ]; - for (path, target, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - if !target.is_empty() { - assert_eq!( - target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap() - ); - } - } - } - - #[test] - fn test_normalize_path_trailing_slashes_disabled() { - let app = App::new() - .resource("/resource1", |r| r.method(Method::GET).f(index)) - .resource("/resource2/", |r| r.method(Method::GET).f(index)) - .default_resource(|r| { - r.h(NormalizePath::new( - false, - true, - StatusCode::MOVED_PERMANENTLY, - )) - }).finish(); - - // trailing slashes - let params = vec![ - ("/resource1", StatusCode::OK), - ("/resource1/", StatusCode::MOVED_PERMANENTLY), - ("/resource2", StatusCode::NOT_FOUND), - ("/resource2/", StatusCode::OK), - ("/resource1?p1=1&p2=2", StatusCode::OK), - ("/resource1/?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY), - ("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND), - ("/resource2/?p1=1&p2=2", StatusCode::OK), - ]; - for (path, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - } - } - - #[test] - fn test_normalize_path_merge_slashes() { - let app = App::new() - .resource("/resource1", |r| r.method(Method::GET).f(index)) - .resource("/resource1/a/b", |r| r.method(Method::GET).f(index)) - .default_resource(|r| r.h(NormalizePath::default())) - .finish(); - - // trailing slashes - let params = vec![ - ("/resource1/a/b", "", StatusCode::OK), - ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY), - ( - "//resource1//a//b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource1//a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource1//a//b//", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ("/resource1/a/b?p=1", "", StatusCode::OK), - ( - "//resource1//a//b?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource1//a//b/?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a//b/?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a//b//?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ]; - for (path, target, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - if !target.is_empty() { - assert_eq!( - target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap() - ); - } - } - } - - #[test] - fn test_normalize_path_merge_and_append_slashes() { - let app = App::new() - .resource("/resource1", |r| r.method(Method::GET).f(index)) - .resource("/resource2/", |r| r.method(Method::GET).f(index)) - .resource("/resource1/a/b", |r| r.method(Method::GET).f(index)) - .resource("/resource2/a/b/", |r| r.method(Method::GET).f(index)) - .default_resource(|r| r.h(NormalizePath::default())) - .finish(); - - // trailing slashes - let params = vec![ - ("/resource1/a/b", "", StatusCode::OK), - ( - "/resource1/a/b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b/", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b//", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/resource2/a/b", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ("/resource2/a/b/", "", StatusCode::OK), - ( - "//resource2//a//b", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b/", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource2//a//b", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource2//a//b/", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource2/a///b", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource2/a///b/", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), - ("/resource1/a/b?p=1", "", StatusCode::OK), - ( - "/resource1/a/b/?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b/?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b/?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b/?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b//?p=1", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/resource2/a/b?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource2//a//b/?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource2//a//b?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource2//a//b/?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource2/a///b?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource2/a///b/?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ]; - for (path, target, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - if !target.is_empty() { - assert_eq!( - target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap() - ); - } - } - } -} diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 60f77b07e..8c972bd13 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -15,7 +15,6 @@ use error::{ }; use header::Header; use json::JsonBody; -use multipart::Multipart; /// Trait that implements general purpose operations on http messages pub trait HttpMessage: Sized { @@ -203,46 +202,6 @@ pub trait HttpMessage: Sized { JsonBody::new(self) } - /// Return stream to http payload processes as multipart. - /// - /// Content-type: multipart/form-data; - /// - /// ## Server example - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate env_logger; - /// # extern crate futures; - /// # use std::str; - /// # use actix_web::*; - /// # use actix_web::actix::fut::FinishStream; - /// # use futures::{Future, Stream}; - /// # use futures::future::{ok, result, Either}; - /// fn index(mut req: HttpRequest) -> Box> { - /// 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 { - let boundary = Multipart::boundary(self.headers()); - Multipart::new(boundary, self.payload()) - } - /// Return stream of lines. fn readlines(&self) -> Readlines { Readlines::new(self) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 59815c58c..73de380ad 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -13,12 +13,10 @@ use serde::Serialize; use serde_json; use body::Body; -use client::ClientResponse; use error::Error; -use handler::Responder; use header::{ContentEncoding, Header, IntoHeaderValue}; use httpmessage::HttpMessage; -use httprequest::HttpRequest; +// use httprequest::HttpRequest; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; @@ -720,16 +718,6 @@ impl From for HttpResponse { } } -impl Responder for HttpResponseBuilder { - type Item = HttpResponse; - type Error = Error; - - #[inline] - fn respond_to(mut self, _: &HttpRequest) -> Result { - Ok(self.finish()) - } -} - impl From<&'static str> for HttpResponse { fn from(val: &'static str) -> Self { HttpResponse::Ok() @@ -738,18 +726,6 @@ impl From<&'static str> for HttpResponse { } } -impl Responder for &'static str { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - impl From<&'static [u8]> for HttpResponse { fn from(val: &'static [u8]) -> Self { HttpResponse::Ok() @@ -758,18 +734,6 @@ impl From<&'static [u8]> for HttpResponse { } } -impl Responder for &'static [u8] { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - impl From for HttpResponse { fn from(val: String) -> Self { HttpResponse::Ok() @@ -778,18 +742,6 @@ impl From for HttpResponse { } } -impl Responder for String { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - impl<'a> From<&'a String> for HttpResponse { fn from(val: &'a String) -> Self { HttpResponse::build(StatusCode::OK) @@ -798,18 +750,6 @@ impl<'a> From<&'a String> for HttpResponse { } } -impl<'a> Responder for &'a String { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - impl From for HttpResponse { fn from(val: Bytes) -> Self { HttpResponse::Ok() @@ -818,18 +758,6 @@ impl From for HttpResponse { } } -impl Responder for Bytes { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - impl From for HttpResponse { fn from(val: BytesMut) -> Self { HttpResponse::Ok() @@ -838,40 +766,6 @@ impl From for HttpResponse { } } -impl Responder for BytesMut { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - -/// Create `HttpResponseBuilder` from `ClientResponse` -/// -/// It is useful for proxy response. This implementation -/// copies all responses's headers and status. -impl<'a> From<&'a ClientResponse> for HttpResponseBuilder { - fn from(resp: &'a ClientResponse) -> HttpResponseBuilder { - let mut builder = HttpResponse::build(resp.status()); - for (key, value) in resp.headers() { - builder.header(key.clone(), value.clone()); - } - builder - } -} - -impl<'a, S> From<&'a HttpRequest> for HttpResponseBuilder { - fn from(req: &'a HttpRequest) -> HttpResponseBuilder { - req.request() - .server_settings() - .get_response_builder(StatusCode::OK) - } -} - #[derive(Debug)] struct InnerHttpResponse { version: Option, @@ -921,7 +815,7 @@ impl InnerHttpResponse { let body = match mem::replace(&mut self.body, Body::Empty) { Body::Empty => None, Body::Binary(mut bin) => Some(bin.take()), - Body::Streaming(_) | Body::Actor(_) => { + Body::Streaming(_) => { error!("Streaming or Actor body is not support by error response"); None } diff --git a/src/json.rs b/src/json.rs index 178143f11..04dd369eb 100644 --- a/src/json.rs +++ b/src/json.rs @@ -11,10 +11,9 @@ use serde::Serialize; use serde_json; use error::{Error, JsonPayloadError}; -use handler::{FromRequest, Responder}; use http::StatusCode; use httpmessage::HttpMessage; -use httprequest::HttpRequest; +// use httprequest::HttpRequest; use httpresponse::HttpResponse; /// Json helper @@ -116,102 +115,6 @@ where } } -impl Responder for Json { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - let body = serde_json::to_string(&self.0)?; - - Ok(req - .build_response(StatusCode::OK) - .content_type("application/json") - .body(body)) - } -} - -impl FromRequest for Json -where - T: DeserializeOwned + 'static, - S: 'static, -{ - type Config = JsonConfig; - type Result = Box>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - let req2 = req.clone(); - let err = Rc::clone(&cfg.ehandler); - Box::new( - JsonBody::new(req) - .limit(cfg.limit) - .map_err(move |e| (*err)(e, &req2)) - .map(Json), - ) - } -} - -/// Json extractor configuration -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{error, http, App, HttpResponse, Json, Result}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body, max payload size is 4kb -/// fn index(info: Json) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource("/index.html", |r| { -/// r.method(http::Method::POST) -/// .with_config(index, |cfg| { -/// cfg.0.limit(4096) // <- change json extractor configuration -/// .error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// }); -/// }) -/// }); -/// } -/// ``` -pub struct JsonConfig { - limit: usize, - ehandler: Rc) -> Error>, -} - -impl JsonConfig { - /// 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(&mut self, f: F) -> &mut Self - where - F: Fn(JsonPayloadError, &HttpRequest) -> Error + 'static, - { - self.ehandler = Rc::new(f); - self - } -} - -impl Default for JsonConfig { - fn default() -> Self { - JsonConfig { - limit: 262_144, - ehandler: Rc::new(|e, _| e.into()), - } - } -} - /// Request payload json parser that resolves to a deserialized `T` value. /// /// Returns error: diff --git a/src/lib.rs b/src/lib.rs index f494c05de..6df1a770e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,13 +77,11 @@ //! * `flate2-rust` - experimental rust based implementation for //! `gzip`, `deflate` compression. //! -#![cfg_attr(actix_nightly, feature( - specialization, // for impl ErrorResponse for std::error::Error - extern_prelude, - tool_lints, -))] +#![cfg_attr(actix_nightly, feature(tool_lints))] #![warn(missing_docs)] +#![allow(unused_imports, unused_variables, dead_code)] +extern crate actix; #[macro_use] extern crate log; extern crate base64; @@ -140,109 +138,36 @@ extern crate serde_json; extern crate smallvec; extern crate actix_net; -#[macro_use] -extern crate actix as actix_inner; #[cfg(test)] #[macro_use] extern crate serde_derive; -#[cfg(feature = "tls")] -extern crate native_tls; -#[cfg(feature = "tls")] -extern crate tokio_tls; - -#[cfg(feature = "openssl")] -extern crate openssl; -#[cfg(feature = "openssl")] -extern crate tokio_openssl; - -#[cfg(feature = "rust-tls")] -extern crate rustls; -#[cfg(feature = "rust-tls")] -extern crate tokio_rustls; -#[cfg(feature = "rust-tls")] -extern crate webpki; -#[cfg(feature = "rust-tls")] -extern crate webpki_roots; - -mod application; mod body; -mod context; -mod de; mod extensions; -mod extractor; -mod handler; mod header; -mod helpers; mod httpcodes; mod httpmessage; -mod httprequest; +//mod httprequest; mod httpresponse; mod info; mod json; -mod param; mod payload; -mod pipeline; -mod resource; -mod route; -mod router; -mod scope; mod uri; -mod with; -pub mod client; pub mod error; -pub mod fs; -pub mod middleware; -pub mod multipart; -pub mod pred; pub mod server; -pub mod test; -pub mod ws; -pub use application::App; +//pub mod test; +//pub mod ws; pub use body::{Binary, Body}; -pub use context::HttpContext; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; -pub use extractor::{Form, Path, Query}; -pub use handler::{ - AsyncResponder, Either, FromRequest, FutureResponse, Responder, State, -}; pub use httpmessage::HttpMessage; -pub use httprequest::HttpRequest; +//pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use json::Json; -pub use scope::Scope; pub use server::Request; -pub mod actix { - //! Re-exports [actix's](https://docs.rs/actix/) prelude - - extern crate actix; - pub use self::actix::actors::resolver; - pub use self::actix::actors::signal; - pub use self::actix::fut; - pub use self::actix::msgs; - pub use self::actix::prelude::*; - pub use self::actix::{run, spawn}; -} - -#[cfg(feature = "openssl")] -pub(crate) const HAS_OPENSSL: bool = true; -#[cfg(not(feature = "openssl"))] -pub(crate) const HAS_OPENSSL: bool = false; - -#[cfg(feature = "tls")] -pub(crate) const HAS_TLS: bool = true; -#[cfg(not(feature = "tls"))] -pub(crate) const HAS_TLS: bool = false; - -#[cfg(feature = "rust-tls")] -pub(crate) const HAS_RUSTLS: bool = true; -#[cfg(not(feature = "rust-tls"))] -pub(crate) const HAS_RUSTLS: bool = false; - pub mod dev { //! The `actix-web` prelude for library developers //! @@ -255,19 +180,11 @@ pub mod dev { //! ``` pub use body::BodyStream; - pub use context::Drain; - pub use extractor::{FormConfig, PayloadConfig}; - pub use handler::{AsyncResult, Handler}; pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; pub use httpresponse::HttpResponseBuilder; pub use info::ConnectionInfo; - pub use json::{JsonBody, JsonConfig}; - pub use param::{FromParam, Params}; + pub use json::JsonBody; pub use payload::{Payload, PayloadBuffer}; - pub use pipeline::Pipeline; - pub use resource::Resource; - pub use route::Route; - pub use router::{ResourceDef, ResourceInfo, ResourceType, Router}; } pub mod http { @@ -281,8 +198,6 @@ pub mod http { pub use cookie::{Cookie, CookieBuilder}; - pub use helpers::NormalizePath; - /// Various http headers pub mod header { pub use header::*; diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs deleted file mode 100644 index 953f2911c..000000000 --- a/src/middleware/cors.rs +++ /dev/null @@ -1,1183 +0,0 @@ -//! Cross-origin resource sharing (CORS) for Actix applications -//! -//! CORS middleware could be used with application and with resource. -//! First you need to construct CORS middleware instance. -//! -//! To construct a cors: -//! -//! 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building. -//! 2. Use any of the builder methods to set fields in the backend. -//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the -//! constructed backend. -//! -//! Cors middleware could be used as parameter for `App::middleware()` or -//! `Resource::middleware()` methods. But you have to use -//! `Cors::for_app()` method to support *preflight* OPTIONS request. -//! -//! -//! # Example -//! -//! ```rust -//! # extern crate actix_web; -//! use actix_web::middleware::cors::Cors; -//! use actix_web::{http, App, HttpRequest, HttpResponse}; -//! -//! fn index(mut req: HttpRequest) -> &'static str { -//! "Hello world" -//! } -//! -//! fn main() { -//! let app = App::new().configure(|app| { -//! Cors::for_app(app) // <- Construct CORS middleware builder -//! .allowed_origin("https://www.rust-lang.org/") -//! .allowed_methods(vec!["GET", "POST"]) -//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) -//! .allowed_header(http::header::CONTENT_TYPE) -//! .max_age(3600) -//! .resource("/index.html", |r| { -//! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -//! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); -//! }) -//! .register() -//! }); -//! } -//! ``` -//! In this example custom *CORS* middleware get registered for "/index.html" -//! endpoint. -//! -//! Cors middleware automatically handle *OPTIONS* preflight request. -use std::collections::HashSet; -use std::iter::FromIterator; -use std::rc::Rc; - -use http::header::{self, HeaderName, HeaderValue}; -use http::{self, HttpTryFrom, Method, StatusCode, Uri}; - -use application::App; -use error::{ResponseError, Result}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response, Started}; -use resource::Resource; -use router::ResourceDef; -use server::Request; - -/// A set of errors that can occur during processing CORS -#[derive(Debug, Fail)] -pub enum CorsError { - /// The HTTP request header `Origin` is required but was not provided - #[fail( - display = "The HTTP request header `Origin` is required but was not provided" - )] - MissingOrigin, - /// The HTTP request header `Origin` could not be parsed correctly. - #[fail(display = "The HTTP request header `Origin` could not be parsed correctly.")] - BadOrigin, - /// The request header `Access-Control-Request-Method` is required but is - /// missing - #[fail( - display = "The request header `Access-Control-Request-Method` is required but is missing" - )] - MissingRequestMethod, - /// The request header `Access-Control-Request-Method` has an invalid value - #[fail( - display = "The request header `Access-Control-Request-Method` has an invalid value" - )] - BadRequestMethod, - /// The request header `Access-Control-Request-Headers` has an invalid - /// value - #[fail( - display = "The request header `Access-Control-Request-Headers` has an invalid value" - )] - BadRequestHeaders, - /// The request header `Access-Control-Request-Headers` is required but is - /// missing. - #[fail( - display = "The request header `Access-Control-Request-Headers` is required but is - missing" - )] - MissingRequestHeaders, - /// Origin is not allowed to make this request - #[fail(display = "Origin is not allowed to make this request")] - OriginNotAllowed, - /// Requested method is not allowed - #[fail(display = "Requested method is not allowed")] - MethodNotAllowed, - /// One or more headers requested are not allowed - #[fail(display = "One or more headers requested are not allowed")] - HeadersNotAllowed, -} - -impl ResponseError for CorsError { - fn error_response(&self) -> HttpResponse { - HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self)) - } -} - -/// An enum signifying that some of type T is allowed, or `All` (everything is -/// allowed). -/// -/// `Default` is implemented for this enum and is `All`. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum AllOrSome { - /// Everything is allowed. Usually equivalent to the "*" value. - All, - /// Only some of `T` is allowed - Some(T), -} - -impl Default for AllOrSome { - fn default() -> Self { - AllOrSome::All - } -} - -impl AllOrSome { - /// Returns whether this is an `All` variant - pub fn is_all(&self) -> bool { - match *self { - AllOrSome::All => true, - AllOrSome::Some(_) => false, - } - } - - /// Returns whether this is a `Some` variant - pub fn is_some(&self) -> bool { - !self.is_all() - } - - /// Returns &T - pub fn as_ref(&self) -> Option<&T> { - match *self { - AllOrSome::All => None, - AllOrSome::Some(ref t) => Some(t), - } - } -} - -/// `Middleware` for Cross-origin resource sharing support -/// -/// The Cors struct contains the settings for CORS requests to be validated and -/// for responses to be generated. -#[derive(Clone)] -pub struct Cors { - inner: Rc, -} - -struct Inner { - methods: HashSet, - origins: AllOrSome>, - origins_str: Option, - headers: AllOrSome>, - expose_hdrs: Option, - max_age: Option, - preflight: bool, - send_wildcard: bool, - supports_credentials: bool, - vary_header: bool, -} - -impl Default for Cors { - fn default() -> Cors { - let inner = Inner { - origins: AllOrSome::default(), - origins_str: None, - methods: HashSet::from_iter( - vec![ - Method::GET, - Method::HEAD, - Method::POST, - Method::OPTIONS, - Method::PUT, - Method::PATCH, - Method::DELETE, - ].into_iter(), - ), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }; - Cors { - inner: Rc::new(inner), - } - } -} - -impl Cors { - /// Build a new CORS middleware instance - pub fn build() -> CorsBuilder<()> { - CorsBuilder { - cors: Some(Inner { - origins: AllOrSome::All, - origins_str: None, - methods: HashSet::new(), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }), - methods: false, - error: None, - expose_hdrs: HashSet::new(), - resources: Vec::new(), - app: None, - } - } - - /// Create CorsBuilder for a specified application. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::middleware::cors::Cors; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().configure( - /// |app| { - /// Cors::for_app(app) // <- Construct CORS builder - /// .allowed_origin("https://www.rust-lang.org/") - /// .resource("/resource", |r| { // register resource - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// }) - /// .register() - /// }, // construct CORS and return application instance - /// ); - /// } - /// ``` - pub fn for_app(app: App) -> CorsBuilder { - CorsBuilder { - cors: Some(Inner { - origins: AllOrSome::All, - origins_str: None, - methods: HashSet::new(), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }), - methods: false, - error: None, - expose_hdrs: HashSet::new(), - resources: Vec::new(), - app: Some(app), - } - } - - /// This method register cors middleware with resource and - /// adds route for *OPTIONS* preflight requests. - /// - /// It is possible to register *Cors* middleware with - /// `Resource::middleware()` method, but in that case *Cors* - /// middleware wont be able to handle *OPTIONS* requests. - pub fn register(self, resource: &mut Resource) { - resource - .method(Method::OPTIONS) - .h(|_: &_| HttpResponse::Ok()); - resource.middleware(self); - } - - fn validate_origin(&self, req: &Request) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ORIGIN) { - if let Ok(origin) = hdr.to_str() { - return match self.inner.origins { - AllOrSome::All => Ok(()), - AllOrSome::Some(ref allowed_origins) => allowed_origins - .get(origin) - .and_then(|_| Some(())) - .ok_or_else(|| CorsError::OriginNotAllowed), - }; - } - Err(CorsError::BadOrigin) - } else { - return match self.inner.origins { - AllOrSome::All => Ok(()), - _ => Err(CorsError::MissingOrigin), - }; - } - } - - fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { - if let Ok(meth) = hdr.to_str() { - if let Ok(method) = Method::try_from(meth) { - return self - .inner - .methods - .get(&method) - .and_then(|_| Some(())) - .ok_or_else(|| CorsError::MethodNotAllowed); - } - } - Err(CorsError::BadRequestMethod) - } else { - Err(CorsError::MissingRequestMethod) - } - } - - fn validate_allowed_headers(&self, req: &Request) -> Result<(), CorsError> { - match self.inner.headers { - AllOrSome::All => Ok(()), - AllOrSome::Some(ref allowed_headers) => { - if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) - { - if let Ok(headers) = hdr.to_str() { - let mut hdrs = HashSet::new(); - for hdr in headers.split(',') { - match HeaderName::try_from(hdr.trim()) { - Ok(hdr) => hdrs.insert(hdr), - Err(_) => return Err(CorsError::BadRequestHeaders), - }; - } - - if !hdrs.is_empty() && !hdrs.is_subset(allowed_headers) { - return Err(CorsError::HeadersNotAllowed); - } - return Ok(()); - } - Err(CorsError::BadRequestHeaders) - } else { - Err(CorsError::MissingRequestHeaders) - } - } - } - } -} - -impl Middleware for Cors { - fn start(&self, req: &HttpRequest) -> Result { - if self.inner.preflight && Method::OPTIONS == *req.method() { - self.validate_origin(req)?; - self.validate_allowed_method(&req)?; - self.validate_allowed_headers(&req)?; - - // allowed headers - let headers = if let Some(headers) = self.inner.headers.as_ref() { - Some( - HeaderValue::try_from( - &headers - .iter() - .fold(String::new(), |s, v| s + "," + v.as_str()) - .as_str()[1..], - ).unwrap(), - ) - } else if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) - { - Some(hdr.clone()) - } else { - None - }; - - Ok(Started::Response( - HttpResponse::Ok() - .if_some(self.inner.max_age.as_ref(), |max_age, resp| { - let _ = resp.header( - header::ACCESS_CONTROL_MAX_AGE, - format!("{}", max_age).as_str(), - ); - }).if_some(headers, |headers, resp| { - let _ = - resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); - }).if_true(self.inner.origins.is_all(), |resp| { - if self.inner.send_wildcard { - resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); - } else { - let origin = req.headers().get(header::ORIGIN).unwrap(); - resp.header( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - origin.clone(), - ); - } - }).if_true(self.inner.origins.is_some(), |resp| { - resp.header( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.inner.origins_str.as_ref().unwrap().clone(), - ); - }).if_true(self.inner.supports_credentials, |resp| { - resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); - }).header( - header::ACCESS_CONTROL_ALLOW_METHODS, - &self - .inner - .methods - .iter() - .fold(String::new(), |s, v| s + "," + v.as_str()) - .as_str()[1..], - ).finish(), - )) - } else { - // Only check requests with a origin header. - if req.headers().contains_key(header::ORIGIN) { - self.validate_origin(req)?; - } - - Ok(Started::Done) - } - } - - fn response( - &self, req: &HttpRequest, mut resp: HttpResponse, - ) -> Result { - match self.inner.origins { - AllOrSome::All => { - if self.inner.send_wildcard { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - HeaderValue::from_static("*"), - ); - } else if let Some(origin) = req.headers().get(header::ORIGIN) { - resp.headers_mut() - .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); - } - } - AllOrSome::Some(_) => { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.inner.origins_str.as_ref().unwrap().clone(), - ); - } - } - - if let Some(ref expose) = self.inner.expose_hdrs { - resp.headers_mut().insert( - header::ACCESS_CONTROL_EXPOSE_HEADERS, - HeaderValue::try_from(expose.as_str()).unwrap(), - ); - } - if self.inner.supports_credentials { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_CREDENTIALS, - HeaderValue::from_static("true"), - ); - } - if self.inner.vary_header { - let value = if let Some(hdr) = resp.headers_mut().get(header::VARY) { - let mut val: Vec = Vec::with_capacity(hdr.as_bytes().len() + 8); - val.extend(hdr.as_bytes()); - val.extend(b", Origin"); - HeaderValue::try_from(&val[..]).unwrap() - } else { - HeaderValue::from_static("Origin") - }; - resp.headers_mut().insert(header::VARY, value); - } - Ok(Response::Done(resp)) - } -} - -/// Structure that follows the builder pattern for building `Cors` middleware -/// structs. -/// -/// To construct a cors: -/// -/// 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building. -/// 2. Use any of the builder methods to set fields in the backend. -/// 3. Call [finish](struct.Cors.html#method.finish) to retrieve the -/// constructed backend. -/// -/// # Example -/// -/// ```rust -/// # extern crate http; -/// # extern crate actix_web; -/// use actix_web::middleware::cors; -/// use http::header; -/// -/// # fn main() { -/// let cors = cors::Cors::build() -/// .allowed_origin("https://www.rust-lang.org/") -/// .allowed_methods(vec!["GET", "POST"]) -/// .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) -/// .allowed_header(header::CONTENT_TYPE) -/// .max_age(3600) -/// .finish(); -/// # } -/// ``` -pub struct CorsBuilder { - cors: Option, - methods: bool, - error: Option, - expose_hdrs: HashSet, - resources: Vec>, - app: Option>, -} - -fn cors<'a>( - parts: &'a mut Option, err: &Option, -) -> Option<&'a mut Inner> { - if err.is_some() { - return None; - } - parts.as_mut() -} - -impl CorsBuilder { - /// Add an origin that are allowed to make requests. - /// Will be verified against the `Origin` request header. - /// - /// When `All` is set, and `send_wildcard` is set, "*" will be sent in - /// the `Access-Control-Allow-Origin` response header. Otherwise, the - /// client's `Origin` request header will be echoed back in the - /// `Access-Control-Allow-Origin` response header. - /// - /// When `Some` is set, the client's `Origin` request header will be - /// checked in a case-sensitive manner. - /// - /// This is the `list of origins` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// Defaults to `All`. - /// - /// Builder panics if supplied origin is not valid uri. - pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - match Uri::try_from(origin) { - Ok(_) => { - if cors.origins.is_all() { - cors.origins = AllOrSome::Some(HashSet::new()); - } - if let AllOrSome::Some(ref mut origins) = cors.origins { - origins.insert(origin.to_owned()); - } - } - Err(e) => { - self.error = Some(e.into()); - } - } - } - self - } - - /// Set a list of methods which the allowed origins are allowed to access - /// for requests. - /// - /// This is the `list of methods` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]` - pub fn allowed_methods(&mut self, methods: U) -> &mut CorsBuilder - where - U: IntoIterator, - Method: HttpTryFrom, - { - self.methods = true; - if let Some(cors) = cors(&mut self.cors, &self.error) { - for m in methods { - match Method::try_from(m) { - Ok(method) => { - cors.methods.insert(method); - } - Err(e) => { - self.error = Some(e.into()); - break; - } - } - } - } - self - } - - /// Set an allowed header - pub fn allowed_header(&mut self, header: H) -> &mut CorsBuilder - where - HeaderName: HttpTryFrom, - { - if let Some(cors) = cors(&mut self.cors, &self.error) { - match HeaderName::try_from(header) { - Ok(method) => { - if cors.headers.is_all() { - cors.headers = AllOrSome::Some(HashSet::new()); - } - if let AllOrSome::Some(ref mut headers) = cors.headers { - headers.insert(method); - } - } - Err(e) => self.error = Some(e.into()), - } - } - self - } - - /// Set a list of header field names which can be used when - /// this resource is accessed by allowed origins. - /// - /// If `All` is set, whatever is requested by the client in - /// `Access-Control-Request-Headers` will be echoed back in the - /// `Access-Control-Allow-Headers` header. - /// - /// This is the `list of headers` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// Defaults to `All`. - pub fn allowed_headers(&mut self, headers: U) -> &mut CorsBuilder - where - U: IntoIterator, - HeaderName: HttpTryFrom, - { - if let Some(cors) = cors(&mut self.cors, &self.error) { - for h in headers { - match HeaderName::try_from(h) { - Ok(method) => { - if cors.headers.is_all() { - cors.headers = AllOrSome::Some(HashSet::new()); - } - if let AllOrSome::Some(ref mut headers) = cors.headers { - headers.insert(method); - } - } - Err(e) => { - self.error = Some(e.into()); - break; - } - } - } - } - self - } - - /// Set a list of headers which are safe to expose to the API of a CORS API - /// specification. This corresponds to the - /// `Access-Control-Expose-Headers` response header. - /// - /// This is the `list of exposed headers` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// This defaults to an empty set. - pub fn expose_headers(&mut self, headers: U) -> &mut CorsBuilder - where - U: IntoIterator, - HeaderName: HttpTryFrom, - { - for h in headers { - match HeaderName::try_from(h) { - Ok(method) => { - self.expose_hdrs.insert(method); - } - Err(e) => { - self.error = Some(e.into()); - break; - } - } - } - self - } - - /// Set a maximum time for which this CORS request maybe cached. - /// This value is set as the `Access-Control-Max-Age` header. - /// - /// This defaults to `None` (unset). - pub fn max_age(&mut self, max_age: usize) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.max_age = Some(max_age) - } - self - } - - /// Set a wildcard origins - /// - /// If send wildcard is set and the `allowed_origins` parameter is `All`, a - /// wildcard `Access-Control-Allow-Origin` response header is sent, - /// rather than the request’s `Origin` header. - /// - /// This is the `supports credentials flag` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// This **CANNOT** be used in conjunction with `allowed_origins` set to - /// `All` and `allow_credentials` set to `true`. Depending on the mode - /// of usage, this will either result in an `Error:: - /// CredentialsWithWildcardOrigin` error during actix launch or runtime. - /// - /// Defaults to `false`. - pub fn send_wildcard(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.send_wildcard = true - } - self - } - - /// Allows users to make authenticated requests - /// - /// If true, injects the `Access-Control-Allow-Credentials` header in - /// responses. This allows cookies and credentials to be submitted - /// across domains. - /// - /// This option cannot be used in conjunction with an `allowed_origin` set - /// to `All` and `send_wildcards` set to `true`. - /// - /// Defaults to `false`. - /// - /// Builder panics if credentials are allowed, but the Origin is set to "*". - /// This is not allowed by W3C - pub fn supports_credentials(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.supports_credentials = true - } - self - } - - /// Disable `Vary` header support. - /// - /// When enabled the header `Vary: Origin` will be returned as per the W3 - /// implementation guidelines. - /// - /// Setting this header when the `Access-Control-Allow-Origin` is - /// dynamically generated (e.g. when there is more than one allowed - /// origin, and an Origin than '*' is returned) informs CDNs and other - /// caches that the CORS headers are dynamic, and cannot be cached. - /// - /// By default `vary` header support is enabled. - pub fn disable_vary_header(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.vary_header = false - } - self - } - - /// Disable *preflight* request support. - /// - /// When enabled cors middleware automatically handles *OPTIONS* request. - /// This is useful application level middleware. - /// - /// By default *preflight* support is enabled. - pub fn disable_preflight(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.preflight = false - } - self - } - - /// Configure resource for a specific path. - /// - /// This is similar to a `App::resource()` method. Except, cors middleware - /// get registered for the resource. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::middleware::cors::Cors; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().configure( - /// |app| { - /// Cors::for_app(app) // <- Construct CORS builder - /// .allowed_origin("https://www.rust-lang.org/") - /// .allowed_methods(vec!["GET", "POST"]) - /// .allowed_header(http::header::CONTENT_TYPE) - /// .max_age(3600) - /// .resource("/resource1", |r| { // register resource - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// }) - /// .resource("/resource2", |r| { // register another resource - /// r.method(http::Method::HEAD) - /// .f(|_| HttpResponse::MethodNotAllowed()); - /// }) - /// .register() - /// }, // construct CORS and return application instance - /// ); - /// } - /// ``` - pub fn resource(&mut self, path: &str, f: F) -> &mut CorsBuilder - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // add resource handler - let mut resource = Resource::new(ResourceDef::new(path)); - f(&mut resource); - - self.resources.push(resource); - self - } - - fn construct(&mut self) -> Cors { - if !self.methods { - self.allowed_methods(vec![ - Method::GET, - Method::HEAD, - Method::POST, - Method::OPTIONS, - Method::PUT, - Method::PATCH, - Method::DELETE, - ]); - } - - if let Some(e) = self.error.take() { - panic!("{}", e); - } - - let mut cors = self.cors.take().expect("cannot reuse CorsBuilder"); - - if cors.supports_credentials && cors.send_wildcard && cors.origins.is_all() { - panic!("Credentials are allowed, but the Origin is set to \"*\""); - } - - if let AllOrSome::Some(ref origins) = cors.origins { - let s = origins - .iter() - .fold(String::new(), |s, v| format!("{}, {}", s, v)); - cors.origins_str = Some(HeaderValue::try_from(&s[2..]).unwrap()); - } - - if !self.expose_hdrs.is_empty() { - cors.expose_hdrs = Some( - self.expose_hdrs - .iter() - .fold(String::new(), |s, v| format!("{}, {}", s, v.as_str()))[2..] - .to_owned(), - ); - } - Cors { - inner: Rc::new(cors), - } - } - - /// Finishes building and returns the built `Cors` instance. - /// - /// This method panics in case of any configuration error. - pub fn finish(&mut self) -> Cors { - if !self.resources.is_empty() { - panic!( - "CorsBuilder::resource() was used, - to construct CORS `.register(app)` method should be used" - ); - } - self.construct() - } - - /// Finishes building Cors middleware and register middleware for - /// application - /// - /// This method panics in case of any configuration error or if non of - /// resources are registered. - pub fn register(&mut self) -> App { - if self.resources.is_empty() { - panic!("No resources are registered."); - } - - let cors = self.construct(); - let mut app = self - .app - .take() - .expect("CorsBuilder has to be constructed with Cors::for_app(app)"); - - // register resources - for mut resource in self.resources.drain(..) { - cors.clone().register(&mut resource); - app.register_resource(resource); - } - - app - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test::{self, TestRequest}; - - impl Started { - fn is_done(&self) -> bool { - match *self { - Started::Done => true, - _ => false, - } - } - fn response(self) -> HttpResponse { - match self { - Started::Response(resp) => resp, - _ => panic!(), - } - } - } - impl Response { - fn response(self) -> HttpResponse { - match self { - Response::Done(resp) => resp, - _ => panic!(), - } - } - } - - #[test] - #[should_panic(expected = "Credentials are allowed, but the Origin is set to")] - fn cors_validates_illegal_allow_credentials() { - Cors::build() - .supports_credentials() - .send_wildcard() - .finish(); - } - - #[test] - #[should_panic(expected = "No resources are registered")] - fn no_resource() { - Cors::build() - .supports_credentials() - .send_wildcard() - .register(); - } - - #[test] - #[should_panic(expected = "Cors::for_app(app)")] - fn no_resource2() { - Cors::build() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .register(); - } - - #[test] - fn validate_origin_allows_all_origins() { - let cors = Cors::default(); - let req = TestRequest::with_header("Origin", "https://www.example.com").finish(); - - assert!(cors.start(&req).ok().unwrap().is_done()) - } - - #[test] - fn test_preflight() { - let mut cors = Cors::build() - .send_wildcard() - .max_age(3600) - .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) - .allowed_header(header::CONTENT_TYPE) - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .finish(); - - assert!(cors.start(&req).is_err()); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put") - .method(Method::OPTIONS) - .finish(); - - assert!(cors.start(&req).is_err()); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") - .header( - header::ACCESS_CONTROL_REQUEST_HEADERS, - "AUTHORIZATION,ACCEPT", - ).method(Method::OPTIONS) - .finish(); - - let resp = cors.start(&req).unwrap().response(); - assert_eq!( - &b"*"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - assert_eq!( - &b"3600"[..], - resp.headers() - .get(header::ACCESS_CONTROL_MAX_AGE) - .unwrap() - .as_bytes() - ); - //assert_eq!( - // &b"authorization,accept,content-type"[..], - // resp.headers().get(header::ACCESS_CONTROL_ALLOW_HEADERS).unwrap(). - // as_bytes()); assert_eq!( - // &b"POST,GET,OPTIONS"[..], - // resp.headers().get(header::ACCESS_CONTROL_ALLOW_METHODS).unwrap(). - // as_bytes()); - - Rc::get_mut(&mut cors.inner).unwrap().preflight = false; - assert!(cors.start(&req).unwrap().is_done()); - } - - // #[test] - // #[should_panic(expected = "MissingOrigin")] - // fn test_validate_missing_origin() { - // let cors = Cors::build() - // .allowed_origin("https://www.example.com") - // .finish(); - // let mut req = HttpRequest::default(); - // cors.start(&req).unwrap(); - // } - - #[test] - #[should_panic(expected = "OriginNotAllowed")] - fn test_validate_not_allowed_origin() { - let cors = Cors::build() - .allowed_origin("https://www.example.com") - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.unknown.com") - .method(Method::GET) - .finish(); - cors.start(&req).unwrap(); - } - - #[test] - fn test_validate_origin() { - let cors = Cors::build() - .allowed_origin("https://www.example.com") - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::GET) - .finish(); - - assert!(cors.start(&req).unwrap().is_done()); - } - - #[test] - fn test_no_origin_response() { - let cors = Cors::build().finish(); - - let req = TestRequest::default().method(Method::GET).finish(); - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); - assert!( - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .is_none() - ); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .finish(); - let resp = cors.response(&req, resp).unwrap().response(); - assert_eq!( - &b"https://www.example.com"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - } - - #[test] - fn test_response() { - let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; - let cors = Cors::build() - .send_wildcard() - .disable_preflight() - .max_age(3600) - .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(exposed_headers.clone()) - .expose_headers(exposed_headers.clone()) - .allowed_header(header::CONTENT_TYPE) - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .finish(); - - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); - assert_eq!( - &b"*"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - assert_eq!( - &b"Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes() - ); - - { - let headers = resp - .headers() - .get(header::ACCESS_CONTROL_EXPOSE_HEADERS) - .unwrap() - .to_str() - .unwrap() - .split(',') - .map(|s| s.trim()) - .collect::>(); - - for h in exposed_headers { - assert!(headers.contains(&h.as_str())); - } - } - - let resp: HttpResponse = - HttpResponse::Ok().header(header::VARY, "Accept").finish(); - let resp = cors.response(&req, resp).unwrap().response(); - assert_eq!( - &b"Accept, Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes() - ); - - let cors = Cors::build() - .disable_vary_header() - .allowed_origin("https://www.example.com") - .allowed_origin("https://www.google.com") - .finish(); - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); - - let origins_str = resp - .headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .to_str() - .unwrap(); - - if origins_str.starts_with("https://www.example.com") { - assert_eq!( - "https://www.example.com, https://www.google.com", - origins_str - ); - } else { - assert_eq!( - "https://www.google.com, https://www.example.com", - origins_str - ); - } - } - - #[test] - fn cors_resource() { - let mut srv = test::TestServer::with_factory(|| { - App::new().configure(|app| { - Cors::for_app(app) - .allowed_origin("https://www.example.com") - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .register() - }) - }); - - let request = srv - .get() - .uri(srv.url("/test")) - .header("ORIGIN", "https://www.example2.com") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let request = srv - .get() - .uri(srv.url("/test")) - .header("ORIGIN", "https://www.example.com") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - } -} diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs deleted file mode 100644 index 02cd150d5..000000000 --- a/src/middleware/csrf.rs +++ /dev/null @@ -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 { - match (uri.scheme_part(), uri.host(), uri.port()) { - (Some(scheme), Some(host), Some(port)) => { - Some(format!("{}://{}:{}", scheme, host, port)) - } - (Some(scheme), Some(host), None) => Some(format!("{}://{}", scheme, host)), - _ => None, - } -} - -fn origin(headers: &HeaderMap) -> Option, 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, - 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>(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 Middleware for CsrfFilter { - fn start(&self, req: &HttpRequest) -> Result { - 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()); - } -} diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs deleted file mode 100644 index d980a2503..000000000 --- a/src/middleware/defaultheaders.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! Default response headers -use http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; -use http::{HeaderMap, HttpTryFrom}; - -use error::Result; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response}; - -/// `Middleware` for setting default response headers. -/// -/// This middleware does not set header if response headers already contains it. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{http, middleware, App, HttpResponse}; -/// -/// fn main() { -/// let app = App::new() -/// .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) -/// .resource("/test", |r| { -/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -/// r.method(http::Method::HEAD) -/// .f(|_| HttpResponse::MethodNotAllowed()); -/// }) -/// .finish(); -/// } -/// ``` -pub struct DefaultHeaders { - ct: bool, - headers: HeaderMap, -} - -impl Default for DefaultHeaders { - fn default() -> Self { - DefaultHeaders { - ct: false, - headers: HeaderMap::new(), - } - } -} - -impl DefaultHeaders { - /// Construct `DefaultHeaders` middleware. - pub fn new() -> DefaultHeaders { - DefaultHeaders::default() - } - - /// Set a header. - #[inline] - #[cfg_attr(feature = "cargo-clippy", allow(clippy::match_wild_err_arm))] - pub fn header(mut self, key: K, value: V) -> Self - where - HeaderName: HttpTryFrom, - HeaderValue: HttpTryFrom, - { - match HeaderName::try_from(key) { - Ok(key) => match HeaderValue::try_from(value) { - Ok(value) => { - self.headers.append(key, value); - } - Err(_) => panic!("Can not create header value"), - }, - Err(_) => panic!("Can not create header name"), - } - self - } - - /// Set *CONTENT-TYPE* header if response does not contain this header. - pub fn content_type(mut self) -> Self { - self.ct = true; - self - } -} - -impl Middleware for DefaultHeaders { - fn response(&self, _: &HttpRequest, mut resp: HttpResponse) -> Result { - for (key, value) in self.headers.iter() { - if !resp.headers().contains_key(key) { - resp.headers_mut().insert(key, value.clone()); - } - } - // default content-type - if self.ct && !resp.headers().contains_key(CONTENT_TYPE) { - resp.headers_mut().insert( - CONTENT_TYPE, - HeaderValue::from_static("application/octet-stream"), - ); - } - Ok(Response::Done(resp)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use http::header::CONTENT_TYPE; - use test::TestRequest; - - #[test] - fn test_default_headers() { - let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); - - let req = TestRequest::default().finish(); - - let resp = HttpResponse::Ok().finish(); - let resp = match mw.response(&req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - - let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish(); - let resp = match mw.response(&req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); - } -} diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs deleted file mode 100644 index c7d19d334..000000000 --- a/src/middleware/errhandlers.rs +++ /dev/null @@ -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 = Fn(&HttpRequest, HttpResponse) -> Result; - -/// `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(_: &HttpRequest, resp: HttpResponse) -> Result { -/// 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 { - handlers: HashMap>>, -} - -impl Default for ErrorHandlers { - fn default() -> Self { - ErrorHandlers { - handlers: HashMap::new(), - } - } -} - -impl ErrorHandlers { - /// Construct new `ErrorHandlers` instance - pub fn new() -> Self { - ErrorHandlers::default() - } - - /// Register error handler for specified status code - pub fn handler(mut self, status: StatusCode, handler: F) -> Self - where - F: Fn(&HttpRequest, HttpResponse) -> Result + 'static, - { - self.handlers.insert(status, Box::new(handler)); - self - } -} - -impl Middleware for ErrorHandlers { - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - 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(_: &HttpRequest, resp: HttpResponse) -> Result { - 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 Middleware for MiddlewareOne { - fn start(&self, _: &HttpRequest) -> Result { - 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"); - } -} diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs deleted file mode 100644 index d890bebef..000000000 --- a/src/middleware/identity.rs +++ /dev/null @@ -1,387 +0,0 @@ -//! Request identity service for Actix applications. -//! -//! [**IdentityService**](struct.IdentityService.html) middleware can be -//! used with different policies types to store identity information. -//! -//! By default, only cookie identity policy is implemented. Other backend -//! implementations can be added separately. -//! -//! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html) -//! uses cookies as identity storage. -//! -//! To access current request identity -//! [**RequestIdentity**](trait.RequestIdentity.html) should be used. -//! *HttpRequest* implements *RequestIdentity* trait. -//! -//! ```rust -//! use actix_web::middleware::identity::RequestIdentity; -//! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; -//! use actix_web::*; -//! -//! fn index(req: HttpRequest) -> Result { -//! // access request identity -//! if let Some(id) = req.identity() { -//! Ok(format!("Welcome! {}", id)) -//! } else { -//! Ok("Welcome Anonymous!".to_owned()) -//! } -//! } -//! -//! fn login(mut req: HttpRequest) -> HttpResponse { -//! req.remember("User1".to_owned()); // <- remember identity -//! HttpResponse::Ok().finish() -//! } -//! -//! fn logout(mut req: HttpRequest) -> HttpResponse { -//! req.forget(); // <- remove identity -//! HttpResponse::Ok().finish() -//! } -//! -//! fn main() { -//! let app = App::new().middleware(IdentityService::new( -//! // <- create identity middleware -//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend -//! .name("auth-cookie") -//! .secure(false), -//! )); -//! } -//! ``` -use std::rc::Rc; - -use cookie::{Cookie, CookieJar, Key}; -use futures::future::{err as FutErr, ok as FutOk, FutureResult}; -use futures::Future; -use time::Duration; - -use error::{Error, Result}; -use http::header::{self, HeaderValue}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response, Started}; - -/// The helper trait to obtain your identity from a request. -/// -/// ```rust -/// use actix_web::middleware::identity::RequestIdentity; -/// use actix_web::*; -/// -/// fn index(req: HttpRequest) -> Result { -/// // 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; - - /// Remember identity. - fn remember(&self, identity: String); - - /// This method is used to 'forget' the current identity on subsequent - /// requests. - fn forget(&self); -} - -impl RequestIdentity for HttpRequest { - fn identity(&self) -> Option { - if let Some(id) = self.extensions().get::() { - return id.0.identity().map(|s| s.to_owned()); - } - None - } - - fn remember(&self, identity: String) { - if let Some(id) = self.extensions_mut().get_mut::() { - return id.0.as_mut().remember(identity); - } - } - - fn forget(&self) { - if let Some(id) = self.extensions_mut().get_mut::() { - 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; -} - -/// Identity policy definition. -pub trait IdentityPolicy: Sized + 'static { - /// The associated identity - type Identity: Identity; - - /// The return type of the middleware - type Future: Future; - - /// Parse the session from request and load data from a service identity. - fn from_request(&self, request: &HttpRequest) -> 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 { - backend: T, -} - -impl IdentityService { - /// Create new identity service with specified backend. - pub fn new(backend: T) -> Self { - IdentityService { backend } - } -} - -struct IdentityBox(Box); - -impl> Middleware for IdentityService { - fn start(&self, req: &HttpRequest) -> Result { - 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, resp: HttpResponse) -> Result { - if let Some(ref mut id) = req.extensions_mut().get_mut::() { - 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, - inner: Rc, -} - -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 { - 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, - secure: bool, - max_age: Option, -} - -impl CookieIdentityInner { - fn new(key: &[u8]) -> CookieIdentityInner { - CookieIdentityInner { - key: Key::from_master(key), - name: "actix-identity".to_owned(), - path: "/".to_owned(), - domain: None, - secure: true, - max_age: None, - } - } - - fn set_cookie(&self, resp: &mut HttpResponse, id: Option) -> Result<()> { - let some = id.is_some(); - { - let id = id.unwrap_or_else(String::new); - let mut cookie = Cookie::new(self.name.clone(), id); - cookie.set_path(self.path.clone()); - cookie.set_secure(self.secure); - cookie.set_http_only(true); - - if let Some(ref domain) = self.domain { - cookie.set_domain(domain.clone()); - } - - if let Some(max_age) = self.max_age { - cookie.set_max_age(max_age); - } - - let mut jar = CookieJar::new(); - if some { - jar.private(&self.key).add(cookie); - } else { - jar.add_original(cookie.clone()); - jar.private(&self.key).remove(cookie); - } - - for cookie in jar.delta() { - let val = HeaderValue::from_str(&cookie.to_string())?; - resp.headers_mut().append(header::SET_COOKIE, val); - } - } - - Ok(()) - } - - fn load(&self, req: &HttpRequest) -> Option { - 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); - -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>(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>(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>(mut self, value: S) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); - self - } - - /// Sets the `secure` field in the session cookie being built. - /// - /// If the `secure` field is set, a cookie will only be transmitted when the - /// connection is secure - i.e. `https` - pub fn secure(mut self, value: bool) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().secure = value; - self - } - - /// Sets the `max-age` field in the session cookie being built. - pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); - self - } -} - -impl IdentityPolicy for CookieIdentityPolicy { - type Identity = CookieIdentity; - type Future = FutureResult; - - fn from_request(&self, req: &HttpRequest) -> Self::Future { - let identity = self.0.load(req); - FutOk(CookieIdentity { - identity, - changed: false, - inner: Rc::clone(&self.0), - }) - } -} diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs deleted file mode 100644 index b7bb1bb80..000000000 --- a/src/middleware/logger.rs +++ /dev/null @@ -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, -} - -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>(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(&self, req: &HttpRequest, resp: &HttpResponse) { - if let Some(entry_time) = req.extensions().get::() { - let render = |fmt: &mut Formatter| { - for unit in &self.format.0 { - unit.render(fmt, req, resp, entry_time.0)?; - } - Ok(()) - }; - info!("{}", FormatDisplay(&render)); - } - } -} - -impl Middleware for Logger { - fn start(&self, req: &HttpRequest) -> Result { - if !self.exclude.contains(req.path()) { - req.extensions_mut().insert(StartTime(time::now())); - } - Ok(Started::Done) - } - - fn finish(&self, req: &HttpRequest, 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); - -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( - &self, fmt: &mut Formatter, req: &HttpRequest, 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")); - } -} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs deleted file mode 100644 index c69dbb3e0..000000000 --- a/src/middleware/mod.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Middlewares -use futures::Future; - -use error::{Error, Result}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; - -mod logger; - -pub mod cors; -pub mod csrf; -mod defaultheaders; -mod errhandlers; -#[cfg(feature = "session")] -pub mod identity; -#[cfg(feature = "session")] -pub mod session; -pub use self::defaultheaders::DefaultHeaders; -pub use self::errhandlers::ErrorHandlers; -pub use self::logger::Logger; - -/// Middleware start result -pub enum Started { - /// Middleware is completed, continue to next middleware - Done, - /// New http response got generated. If middleware generates response - /// handler execution halts. - Response(HttpResponse), - /// Execution completed, runs future to completion. - Future(Box, Error = Error>>), -} - -/// Middleware execution result -pub enum Response { - /// New http response got generated - Done(HttpResponse), - /// Result is a future that resolves to a new http response - Future(Box>), -} - -/// Middleware finish result -pub enum Finished { - /// Execution completed - Done, - /// Execution completed, but run future to completion - Future(Box>), -} - -/// Middleware definition -#[allow(unused_variables)] -pub trait Middleware: '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) -> Result { - Ok(Started::Done) - } - - /// Method is called when handler returns response, - /// but before sending http message to peer. - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - Ok(Response::Done(resp)) - } - - /// Method is called after body stream get sent to peer. - fn finish(&self, req: &HttpRequest, resp: &HttpResponse) -> Finished { - Finished::Done - } -} diff --git a/src/middleware/session.rs b/src/middleware/session.rs deleted file mode 100644 index e8b0e5558..000000000 --- a/src/middleware/session.rs +++ /dev/null @@ -1,617 +0,0 @@ -//! User sessions. -//! -//! Actix provides a general solution for session management. The -//! [**SessionStorage**](struct.SessionStorage.html) -//! middleware can be used with different backend types to store session -//! data in different backends. -//! -//! By default, only cookie session backend is implemented. Other -//! backend implementations can be added. -//! -//! [**CookieSessionBackend**](struct.CookieSessionBackend.html) -//! uses cookies as session storage. `CookieSessionBackend` creates sessions -//! which are limited to storing fewer than 4000 bytes of data, as the payload -//! must fit into a single cookie. An internal server error is generated if a -//! session contains more than 4000 bytes. -//! -//! A cookie may have a security policy of *signed* or *private*. Each has -//! a respective `CookieSessionBackend` constructor. -//! -//! A *signed* cookie may be viewed but not modified by the client. A *private* -//! cookie may neither be viewed nor modified by the client. -//! -//! The constructors take a key as an argument. This is the private key -//! for cookie session - when this value is changed, all session data is lost. -//! -//! In general, you create a `SessionStorage` middleware and initialize it -//! with specific backend implementation, such as a `CookieSessionBackend`. -//! To access session data, -//! [*HttpRequest::session()*](trait.RequestSession.html#tymethod.session) -//! must be used. This method returns a -//! [*Session*](struct.Session.html) object, which allows us to get or set -//! session data. -//! -//! ```rust -//! # extern crate actix_web; -//! use actix_web::{actix, server, App, HttpRequest, Result}; -//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; -//! -//! fn index(req: HttpRequest) -> Result<&'static str> { -//! // access session data -//! if let Some(count) = req.session().get::("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::("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 RequestSession for HttpRequest { - fn session(&self) -> Session { - if let Some(s_impl) = self.extensions().get::>() { - 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::("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), - None, -} - -impl Session { - /// Get a `value` from the session. - pub fn get(&self, key: &str) -> Result> { - 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(&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::("counter")? { -/// session.set("counter", count + 1)?; -/// } else { -/// session.set("counter", 1)?; -/// } -/// -/// Ok("Welcome!") -/// } -/// # fn main() {} -/// ``` -impl FromRequest for Session { - type Config = (); - type Result = Session; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - req.session() - } -} - -struct SessionImplCell(RefCell>); - -/// 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, PhantomData); - -impl> SessionStorage { - /// Create session storage - pub fn new(backend: T) -> SessionStorage { - SessionStorage(backend, PhantomData) - } -} - -impl> Middleware for SessionStorage { - fn start(&self, req: &HttpRequest) -> Result { - 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, resp: HttpResponse) -> Result { - if let Some(s_box) = req.extensions().get::>() { - 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; -} - -/// Session's storage backend trait definition. -pub trait SessionBackend: Sized + 'static { - /// Session item - type Session: SessionImpl; - /// Future that reads session - type ReadFuture: Future; - - /// Parse the session from request and load data from a storage backend. - fn from_request(&self, request: &mut HttpRequest) -> Self::ReadFuture; -} - -/// Session that uses signed cookies as session storage -pub struct CookieSession { - changed: bool, - state: HashMap, - inner: Rc, -} - -/// 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 { - 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, - secure: bool, - http_only: bool, - max_age: Option, - same_site: Option, -} - -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, - ) -> 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(&self, req: &mut HttpRequest) -> HashMap { - 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); - -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>(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>(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>(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 SessionBackend for CookieSessionBackend { - type Session = CookieSession; - type ReadFuture = FutureResult; - - fn from_request(&self, req: &mut HttpRequest) -> 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()); - } -} diff --git a/src/multipart.rs b/src/multipart.rs deleted file mode 100644 index 862f60ecb..000000000 --- a/src/multipart.rs +++ /dev/null @@ -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 { - safety: Safety, - error: Option, - inner: Option>>>, -} - -/// -pub enum MultipartItem { - /// Multipart field - Field(Field), - /// Nested multipart stream - Nested(Multipart), -} - -enum InnerMultipartItem { - None, - Field(Rc>>), - Multipart(Rc>>), -} - -#[derive(PartialEq, Debug)] -enum InnerState { - /// Stream eof - Eof, - /// Skip data until first boundary - FirstBoundary, - /// Reading boundary - Boundary, - /// Reading Headers, - Headers, -} - -struct InnerMultipart { - payload: PayloadRef, - boundary: String, - state: InnerState, - item: InnerMultipartItem, -} - -impl Multipart<()> { - /// Extract boundary info from headers. - pub fn boundary(headers: &HeaderMap) -> Result { - 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::() { - 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 Multipart -where - S: Stream, -{ - /// Create multipart instance for boundary. - pub fn new(boundary: Result, stream: S) -> Multipart { - 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 Stream for Multipart -where - S: Stream, -{ - type Item = MultipartItem; - type Error = MultipartError; - - fn poll(&mut self) -> Poll, 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 InnerMultipart -where - S: Stream, -{ - fn read_headers(payload: &mut PayloadBuffer) -> Poll { - 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, boundary: &str, - ) -> Poll { - // 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, boundary: &str, - ) -> Poll { - 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>, 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::() { - 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 Drop for InnerMultipart { - 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 { - ct: mime::Mime, - headers: HeaderMap, - inner: Rc>>, - safety: Safety, -} - -impl Field -where - S: Stream, -{ - fn new( - safety: Safety, headers: HeaderMap, ct: mime::Mime, - inner: Rc>>, - ) -> 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 { - // 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 Stream for Field -where - S: Stream, -{ - type Item = Bytes; - type Error = MultipartError; - - fn poll(&mut self) -> Poll, Self::Error> { - if self.safety.current() { - self.inner.borrow_mut().poll(&self.safety) - } else { - Ok(Async::NotReady) - } - } -} - -impl fmt::Debug for Field { - 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 { - payload: Option>, - boundary: String, - eof: bool, - length: Option, -} - -impl InnerField -where - S: Stream, -{ - fn new( - payload: PayloadRef, boundary: String, headers: &HeaderMap, - ) -> Result, 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::() { - 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, size: &mut u64, - ) -> Poll, 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, boundary: &str, - ) -> Poll, 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, 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 { - payload: Rc>>, -} - -impl PayloadRef -where - S: Stream, -{ - fn new(payload: PayloadBuffer) -> PayloadRef { - PayloadRef { - payload: Rc::new(payload.into()), - } - } - - fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadBuffer> - 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 = unsafe { &mut *self.payload.get() }; - Some(payload) - } else { - None - } - } -} - -impl Clone for PayloadRef { - fn clone(&self) -> PayloadRef { - 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, - level: usize, - payload: Rc>, -} - -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(); - } -} diff --git a/src/param.rs b/src/param.rs deleted file mode 100644 index d0664df99..000000000 --- a/src/param.rs +++ /dev/null @@ -1,303 +0,0 @@ -use std; -use std::ops::Index; -use std::path::PathBuf; -use std::rc::Rc; -use std::str::FromStr; - -use http::StatusCode; -use smallvec::SmallVec; - -use error::{InternalError, ResponseError, UriSegmentError}; -use uri::Url; - -/// A trait to abstract the idea of creating a new instance of a type from a -/// path parameter. -pub trait FromParam: Sized { - /// The associated error which can be returned from parsing. - type Err: ResponseError; - - /// Parses a string `s` to return a value of this type. - fn from_param(s: &str) -> Result; -} - -#[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, 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, value: ParamItem) { - self.segments.push((name, value)); - } - - pub(crate) fn add_static(&mut self, name: &str, value: &'static str) { - self.segments - .push((Rc::new(name.to_string()), ParamItem::Static(value))); - } - - /// Check if there are any matched patterns - pub fn is_empty(&self) -> bool { - self.segments.is_empty() - } - - /// Check number of extracted parameters - pub fn len(&self) -> usize { - self.segments.len() - } - - /// Get matched parameter by name without type conversion - pub fn get(&self, key: &str) -> Option<&str> { - for item in self.segments.iter() { - if key == item.0.as_str() { - return match item.1 { - ParamItem::Static(ref s) => Some(&s), - ParamItem::UrlSegment(s, e) => { - Some(&self.url.path()[(s as usize)..(e as usize)]) - } - }; - } - } - if key == "tail" { - Some(&self.url.path()[(self.tail as usize)..]) - } else { - None - } - } - - /// Get unprocessed part of path - pub fn unprocessed(&self) -> &str { - &self.url.path()[(self.tail as usize)..] - } - - /// Get matched `FromParam` compatible parameter by name. - /// - /// If keyed parameter is not available empty string is used as default - /// value. - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// fn index(req: HttpRequest) -> Result { - /// let ivalue: isize = req.match_info().query("val")?; - /// Ok(format!("isuze value: {:?}", ivalue)) - /// } - /// # fn main() {} - /// ``` - pub fn query(&self, key: &str) -> Result::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 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 { - 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 { - <$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"])) - ); - } -} diff --git a/src/pipeline.rs b/src/pipeline.rs deleted file mode 100644 index 1940f9308..000000000 --- a/src/pipeline.rs +++ /dev/null @@ -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 { - fn encoding(&self) -> ContentEncoding; - - fn handle(&self, &HttpRequest) -> AsyncResult; -} - -#[doc(hidden)] -pub struct Pipeline( - PipelineInfo, - PipelineState, - Rc>>>, -); - -enum PipelineState { - None, - Error, - Starting(StartMiddlewares), - Handler(WaitingResponse), - RunMiddlewares(RunMiddlewares), - Response(ProcessResponse), - Finishing(FinishingMiddlewares), - Completed(Completed), -} - -impl> PipelineState { - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - 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 { - req: HttpRequest, - count: u16, - context: Option>, - error: Option, - disconnected: Option, - encoding: ContentEncoding, -} - -impl PipelineInfo { - 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> Pipeline { - pub(crate) fn new( - req: HttpRequest, mws: Rc>>>, handler: Rc, - ) -> Pipeline { - 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 Pipeline { - #[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> HttpHandlerTask for Pipeline { - fn disconnected(&mut self) { - self.0.disconnected = Some(true); - } - - fn poll_io(&mut self, io: &mut Writer) -> Poll { - 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, Error = Error>>; - -/// Middlewares start executor -struct StartMiddlewares { - hnd: Rc, - fut: Option, - _s: PhantomData, -} - -impl> StartMiddlewares { - fn init( - info: &mut PipelineInfo, mws: &[Box>], hnd: Rc, - ) -> PipelineState { - // 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, mws: &[Box>], - ) -> Option> { - 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 { - fut: Box>, - _s: PhantomData, - _h: PhantomData, -} - -impl WaitingResponse { - #[inline] - fn init( - info: &mut PipelineInfo, mws: &[Box>], - reply: AsyncResult, - ) -> PipelineState { - 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, mws: &[Box>], - ) -> Option> { - 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 { - curr: usize, - fut: Option>>, - _s: PhantomData, - _h: PhantomData, -} - -impl RunMiddlewares { - #[inline] - fn init( - info: &mut PipelineInfo, mws: &[Box>], mut resp: HttpResponse, - ) -> PipelineState { - 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, mws: &[Box>], - ) -> Option> { - 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 { - resp: Option, - iostate: IOState, - running: RunningState, - drain: Option>, - _s: PhantomData, - _h: PhantomData, -} - -#[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), - Done, -} - -impl ProcessResponse { - #[inline] - fn init(resp: HttpResponse) -> PipelineState { - 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, mws: &[Box>], - ) -> Option> { - // 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, - mws: &[Box>], - ) -> Result, PipelineState> { - loop { - if self.drain.is_none() && self.running != RunningState::Paused { - // if task is paused, write buffer is probably full - 'inner: loop { - let result = match mem::replace(&mut self.iostate, IOState::Done) { - IOState::Response => { - let encoding = self - .resp - .as_ref() - .unwrap() - .content_encoding() - .unwrap_or(info.encoding); - - let result = match io.start( - &info.req, - self.resp.as_mut().unwrap(), - encoding, - ) { - Ok(res) => res, - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - }; - - if let Some(err) = self.resp.as_ref().unwrap().error() { - if self.resp.as_ref().unwrap().status().is_server_error() - { - error!( - "Error occured during request handling, status: {} {}", - self.resp.as_ref().unwrap().status(), err - ); - } else { - warn!( - "Error occured during request handling: {}", - err - ); - } - if log_enabled!(Debug) { - debug!("{:?}", err); - } - } - - // always poll stream or actor for the first time - match self.resp.as_mut().unwrap().replace_body(Body::Empty) { - Body::Streaming(stream) => { - self.iostate = IOState::Payload(stream); - continue 'inner; - } - Body::Actor(ctx) => { - self.iostate = IOState::Actor(ctx); - continue 'inner; - } - _ => (), - } - - result - } - IOState::Payload(mut body) => match body.poll() { - Ok(Async::Ready(None)) => { - if let Err(err) = io.write_eof() { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - break; - } - Ok(Async::Ready(Some(chunk))) => { - self.iostate = IOState::Payload(body); - match io.write(&chunk.into()) { - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - Ok(result) => result, - } - } - Ok(Async::NotReady) => { - self.iostate = IOState::Payload(body); - break; - } - Err(err) => { - info.error = Some(err); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - }, - IOState::Actor(mut ctx) => { - if info.disconnected.take().is_some() { - ctx.disconnected(); - } - match ctx.poll() { - Ok(Async::Ready(Some(vec))) => { - if vec.is_empty() { - self.iostate = IOState::Actor(ctx); - break; - } - let mut res = None; - for frame in vec { - match frame { - Frame::Chunk(None) => { - info.context = Some(ctx); - if let Err(err) = io.write_eof() { - info.error = Some(err.into()); - return Ok( - FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - ), - ); - } - break 'inner; - } - Frame::Chunk(Some(chunk)) => match io - .write(&chunk) - { - Err(err) => { - info.context = Some(ctx); - info.error = Some(err.into()); - return Ok( - FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - ), - ); - } - Ok(result) => res = Some(result), - }, - Frame::Drain(fut) => self.drain = Some(fut), - } - } - self.iostate = IOState::Actor(ctx); - if self.drain.is_some() { - self.running.resume(); - break 'inner; - } - res.unwrap() - } - Ok(Async::Ready(None)) => break, - Ok(Async::NotReady) => { - self.iostate = IOState::Actor(ctx); - break; - } - Err(err) => { - info.context = Some(ctx); - info.error = Some(err); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - } - IOState::Done => break, - }; - - match result { - WriterState::Pause => { - self.running.pause(); - break; - } - WriterState::Done => self.running.resume(), - } - } - } - - // flush io but only if we need to - if self.running == RunningState::Paused || self.drain.is_some() { - match io.poll_completed(false) { - Ok(Async::Ready(_)) => { - self.running.resume(); - - // resolve drain futures - if let Some(tx) = self.drain.take() { - let _ = tx.send(()); - } - // restart io processing - continue; - } - Ok(Async::NotReady) => return Err(PipelineState::Response(self)), - Err(err) => { - if let IOState::Actor(mut ctx) = - mem::replace(&mut self.iostate, IOState::Done) - { - ctx.disconnected(); - info.context = Some(ctx); - } - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - } - break; - } - - // response is completed - match self.iostate { - IOState::Done => { - match io.write_eof() { - Ok(_) => (), - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - self.resp.as_mut().unwrap().set_response_size(io.written()); - Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )) - } - _ => Err(PipelineState::Response(self)), - } - } -} - -/// Middlewares start executor -struct FinishingMiddlewares { - resp: Option, - fut: Option>>, - _s: PhantomData, - _h: PhantomData, -} - -impl FinishingMiddlewares { - #[inline] - fn init( - info: &mut PipelineInfo, mws: &[Box>], resp: HttpResponse, - ) -> PipelineState { - 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, mws: &[Box>], - ) -> Option> { - 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(PhantomData, PhantomData); - -impl Completed { - #[inline] - fn init(info: &mut PipelineInfo) -> PipelineState { - 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) -> Option> { - match info.poll_context() { - Ok(Async::NotReady) => None, - Ok(Async::Ready(())) => Some(PipelineState::None), - Err(_) => Some(PipelineState::Error), - } - } -} diff --git a/src/pred.rs b/src/pred.rs deleted file mode 100644 index 99d6e608b..000000000 --- a/src/pred.rs +++ /dev/null @@ -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 { - /// 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 + 'static>(pred: P) -> AnyPredicate { - AnyPredicate(vec![Box::new(pred)]) -} - -/// Matches if any of supplied predicate matches. -pub struct AnyPredicate(Vec>>); - -impl AnyPredicate { - /// Add new predicate to list of predicates to check - pub fn or + 'static>(mut self, pred: P) -> Self { - self.0.push(Box::new(pred)); - self - } -} - -impl Predicate for AnyPredicate { - 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 + 'static>(pred: P) -> AllPredicate { - AllPredicate(vec![Box::new(pred)]) -} - -/// Matches if all of supplied predicate matches. -pub struct AllPredicate(Vec>>); - -impl AllPredicate { - /// Add new predicate to list of predicates to check - pub fn and + 'static>(mut self, pred: P) -> Self { - self.0.push(Box::new(pred)); - self - } -} - -impl Predicate for AllPredicate { - 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 + 'static>(pred: P) -> NotPredicate { - NotPredicate(Box::new(pred)) -} - -#[doc(hidden)] -pub struct NotPredicate(Box>); - -impl Predicate for NotPredicate { - fn check(&self, req: &Request, state: &S) -> bool { - !self.0.check(req, state) - } -} - -/// Http method predicate -#[doc(hidden)] -pub struct MethodPredicate(http::Method, PhantomData); - -impl Predicate for MethodPredicate { - fn check(&self, req: &Request, _: &S) -> bool { - *req.method() == self.0 - } -} - -/// Predicate to match *GET* http method -pub fn Get() -> MethodPredicate { - MethodPredicate(http::Method::GET, PhantomData) -} - -/// Predicate to match *POST* http method -pub fn Post() -> MethodPredicate { - MethodPredicate(http::Method::POST, PhantomData) -} - -/// Predicate to match *PUT* http method -pub fn Put() -> MethodPredicate { - MethodPredicate(http::Method::PUT, PhantomData) -} - -/// Predicate to match *DELETE* http method -pub fn Delete() -> MethodPredicate { - MethodPredicate(http::Method::DELETE, PhantomData) -} - -/// Predicate to match *HEAD* http method -pub fn Head() -> MethodPredicate { - MethodPredicate(http::Method::HEAD, PhantomData) -} - -/// Predicate to match *OPTIONS* http method -pub fn Options() -> MethodPredicate { - MethodPredicate(http::Method::OPTIONS, PhantomData) -} - -/// Predicate to match *CONNECT* http method -pub fn Connect() -> MethodPredicate { - MethodPredicate(http::Method::CONNECT, PhantomData) -} - -/// Predicate to match *PATCH* http method -pub fn Patch() -> MethodPredicate { - MethodPredicate(http::Method::PATCH, PhantomData) -} - -/// Predicate to match *TRACE* http method -pub fn Trace() -> MethodPredicate { - MethodPredicate(http::Method::TRACE, PhantomData) -} - -/// Predicate to match specified http method -pub fn Method(method: http::Method) -> MethodPredicate { - MethodPredicate(method, PhantomData) -} - -/// Return predicate that matches if request contains specified header and -/// value. -pub fn Header( - name: &'static str, value: &'static str, -) -> HeaderPredicate { - HeaderPredicate( - header::HeaderName::try_from(name).unwrap(), - header::HeaderValue::from_static(value), - PhantomData, - ) -} - -#[doc(hidden)] -pub struct HeaderPredicate(header::HeaderName, header::HeaderValue, PhantomData); - -impl Predicate for HeaderPredicate { - 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>(host: H) -> HostPredicate { - HostPredicate(host.as_ref().to_string(), None, PhantomData) -} - -#[doc(hidden)] -pub struct HostPredicate(String, Option, PhantomData); - -impl HostPredicate { - /// Set reuest scheme to match - pub fn scheme>(&mut self, scheme: H) { - self.1 = Some(scheme.as_ref().to_string()) - } -} - -impl Predicate for HostPredicate { - 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())); - } -} diff --git a/src/resource.rs b/src/resource.rs deleted file mode 100644 index d884dd447..000000000 --- a/src/resource.rs +++ /dev/null @@ -1,324 +0,0 @@ -use std::ops::Deref; -use std::rc::Rc; - -use futures::Future; -use http::Method; -use smallvec::SmallVec; - -use error::Error; -use handler::{AsyncResult, FromRequest, Handler, Responder}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::Middleware; -use pred; -use route::Route; -use router::ResourceDef; -use with::WithFactory; - -#[derive(Copy, Clone)] -pub(crate) struct RouteId(usize); - -/// *Resource* is an entry in route table which corresponds to requested URL. -/// -/// Resource in turn has at least one route. -/// Route consists of an object that implements `Handler` trait (handler) -/// and list of predicates (objects that implement `Predicate` trait). -/// Route uses builder-like pattern for configuration. -/// During request handling, resource object iterate through all routes -/// and check all predicates for specific route, if request matches all -/// predicates route route considered matched and route handler get called. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{App, HttpResponse, http}; -/// -/// fn main() { -/// let app = App::new() -/// .resource( -/// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok())) -/// .finish(); -/// } -pub struct Resource { - rdef: ResourceDef, - routes: SmallVec<[Route; 3]>, - middlewares: Rc>>>, -} - -impl Resource { - /// Create new resource with specified resource definition - pub fn new(rdef: ResourceDef) -> Self { - Resource { - rdef, - routes: SmallVec::new(), - middlewares: Rc::new(Vec::new()), - } - } - - /// Name of the resource - pub(crate) fn get_name(&self) -> &str { - self.rdef.name() - } - - /// Set resource name - pub fn name(&mut self, name: &str) { - self.rdef.set_name(name); - } - - /// Resource definition - pub fn rdef(&self) -> &ResourceDef { - &self.rdef - } -} - -impl Resource { - /// Register a new route and return mutable reference to *Route* object. - /// *Route* is used for route configuration, i.e. adding predicates, - /// setting up handler. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// - /// fn main() { - /// let app = App::new() - /// .resource("/", |r| { - /// r.route() - /// .filter(pred::Any(pred::Get()).or(pred::Put())) - /// .filter(pred::Header("Content-Type", "text/plain")) - /// .f(|r| HttpResponse::Ok()) - /// }) - /// .finish(); - /// } - /// ``` - pub fn route(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap() - } - - /// Register a new `GET` route. - pub fn get(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Get()) - } - - /// Register a new `POST` route. - pub fn post(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Post()) - } - - /// Register a new `PUT` route. - pub fn put(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Put()) - } - - /// Register a new `DELETE` route. - pub fn delete(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Delete()) - } - - /// Register a new `HEAD` route. - pub fn head(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Head()) - } - - /// Register a new route and add method check to route. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// - /// App::new().resource("/", |r| r.method(http::Method::GET).f(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().resource("/", |r| r.route().filter(pred::Get()).f(index)); - /// ``` - pub fn method(&mut self, method: Method) -> &mut Route { - 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>(&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(&mut self, handler: F) - where - F: Fn(&HttpRequest) -> R + 'static, - R: Responder + 'static, - { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().f(handler) - } - - /// Register a new route and add handler. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } - /// - /// App::new().resource("/", |r| r.with(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().resource("/", |r| r.route().with(index)); - /// ``` - pub fn with(&mut self, handler: F) - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().with(handler); - } - - /// Register a new route and add async handler. - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate futures; - /// use actix_web::*; - /// use futures::future::Future; - /// - /// fn index(req: HttpRequest) -> Box> { - /// unimplemented!() - /// } - /// - /// App::new().resource("/", |r| r.with_async(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate futures; - /// # use actix_web::*; - /// # use futures::future::Future; - /// # fn index(req: HttpRequest) -> Box> { - /// # unimplemented!() - /// # } - /// App::new().resource("/", |r| r.route().with_async(index)); - /// ``` - pub fn with_async(&mut self, handler: F) - where - F: Fn(T) -> R + 'static, - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().with_async(handler); - } - - /// Register a resource middleware - /// - /// This is similar to `App's` middlewares, but - /// middlewares get invoked on resource level. - /// - /// *Note* `Middleware::finish()` fires right after response get - /// prepared. It does not wait until body get sent to peer. - pub fn middleware>(&mut self, mw: M) { - Rc::get_mut(&mut self.middlewares) - .unwrap() - .push(Box::new(mw)); - } - - #[inline] - pub(crate) fn get_route_id(&self, req: &HttpRequest) -> Option { - for idx in 0..self.routes.len() { - if (&self.routes[idx]).check(req) { - return Some(RouteId(idx)); - } - } - None - } - - #[inline] - pub(crate) fn handle( - &self, id: RouteId, req: &HttpRequest, - ) -> AsyncResult { - if self.middlewares.is_empty() { - (&self.routes[id.0]).handle(req) - } else { - (&self.routes[id.0]).compose(req.clone(), Rc::clone(&self.middlewares)) - } - } -} - -/// Default resource -pub struct DefaultResource(Rc>); - -impl Deref for DefaultResource { - type Target = Resource; - - fn deref(&self) -> &Resource { - self.0.as_ref() - } -} - -impl Clone for DefaultResource { - fn clone(&self) -> Self { - DefaultResource(self.0.clone()) - } -} - -impl From> for DefaultResource { - fn from(res: Resource) -> Self { - DefaultResource(Rc::new(res)) - } -} diff --git a/src/route.rs b/src/route.rs deleted file mode 100644 index e4a7a9572..000000000 --- a/src/route.rs +++ /dev/null @@ -1,666 +0,0 @@ -use std::marker::PhantomData; -use std::rc::Rc; - -use futures::{Async, Future, Poll}; - -use error::Error; -use handler::{ - AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler, Responder, - RouteHandler, WrapHandler, -}; -use http::StatusCode; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{ - Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse, - Started as MiddlewareStarted, -}; -use pred::Predicate; -use with::{WithAsyncFactory, WithFactory}; - -/// Resource route definition -/// -/// Route uses builder-like pattern for configuration. -/// If handler is not explicitly set, default *404 Not Found* handler is used. -pub struct Route { - preds: Vec>>, - handler: InnerHandler, -} - -impl Default for Route { - fn default() -> Route { - Route { - preds: Vec::new(), - handler: InnerHandler::new(|_: &_| HttpResponse::new(StatusCode::NOT_FOUND)), - } - } -} - -impl Route { - #[inline] - pub(crate) fn check(&self, req: &HttpRequest) -> bool { - let state = req.state(); - for pred in &self.preds { - if !pred.check(req, state) { - return false; - } - } - true - } - - #[inline] - pub(crate) fn handle(&self, req: &HttpRequest) -> AsyncResult { - self.handler.handle(req) - } - - #[inline] - pub(crate) fn compose( - &self, req: HttpRequest, mws: Rc>>>, - ) -> AsyncResult { - AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone()))) - } - - /// Add match predicate to route. - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn main() { - /// App::new().resource("/path", |r| { - /// r.route() - /// .filter(pred::Get()) - /// .filter(pred::Header("content-type", "text/plain")) - /// .f(|req| HttpResponse::Ok()) - /// }) - /// # .finish(); - /// # } - /// ``` - pub fn filter + 'static>(&mut self, p: T) -> &mut Self { - self.preds.push(Box::new(p)); - self - } - - /// Set handler object. Usually call to this method is last call - /// during route configuration, so it does not return reference to self. - pub fn h>(&mut self, handler: H) { - self.handler = InnerHandler::new(handler); - } - - /// Set handler function. Usually call to this method is last call - /// during route configuration, so it does not return reference to self. - pub fn f(&mut self, handler: F) - where - F: Fn(&HttpRequest) -> R + 'static, - R: Responder + 'static, - { - self.handler = InnerHandler::new(handler); - } - - /// Set async handler function. - pub fn a(&mut self, handler: H) - where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - { - self.handler = InnerHandler::async(handler); - } - - /// Set handler function, use request extractor for parameters. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Path, Result}; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// fn index(info: Path) -> Result { - /// Ok(format!("Welcome {}!", info.username)) - /// } - /// - /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with(index), - /// ); // <- use `with` extractor - /// } - /// ``` - /// - /// It is possible to use multiple extractors for one handler function. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// # use std::collections::HashMap; - /// use actix_web::{http, App, Json, Path, Query, Result}; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// fn index( - /// path: Path, query: Query>, body: Json, - /// ) -> Result { - /// Ok(format!("Welcome {}!", path.username)) - /// } - /// - /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with(index), - /// ); // <- use `with` extractor - /// } - /// ``` - pub fn with(&mut self, handler: F) - where - F: WithFactory + 'static, - R: Responder + 'static, - T: FromRequest + '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 { - /// 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(&mut self, handler: F, cfg_f: C) - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - C: FnOnce(&mut T::Config), - { - let mut cfg = ::default(); - cfg_f(&mut cfg); - self.h(handler.create_with_config(cfg)); - } - - /// Set async handler function, use request extractor for parameters. - /// Also this method needs to be used if your handler function returns - /// `impl Future<>` - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Error, Path}; - /// use futures::Future; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// fn index(info: Path) -> Box> { - /// unimplemented!() - /// } - /// - /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with_async(index), - /// ); // <- use `with` extractor - /// } - /// ``` - pub fn with_async(&mut self, handler: F) - where - F: WithAsyncFactory, - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - { - self.h(handler.create()); - } - - /// Set async handler function, use request extractor for parameters. - /// This method allows to configure extractor. Configuration closure - /// accepts config objects as tuple. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Error, Form}; - /// use futures::Future; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// fn index(info: Form) -> Box> { - /// 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(&mut self, handler: F, cfg: C) - where - F: WithAsyncFactory, - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - C: FnOnce(&mut T::Config), - { - let mut extractor_cfg = ::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(Rc>>); - -impl InnerHandler { - #[inline] - fn new>(h: H) -> Self { - InnerHandler(Rc::new(Box::new(WrapHandler::new(h)))) - } - - #[inline] - fn async(h: H) -> Self - where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - { - InnerHandler(Rc::new(Box::new(AsyncHandler::new(h)))) - } - - #[inline] - pub fn handle(&self, req: &HttpRequest) -> AsyncResult { - self.0.handle(req) - } -} - -impl Clone for InnerHandler { - #[inline] - fn clone(&self) -> Self { - InnerHandler(Rc::clone(&self.0)) - } -} - -/// Compose resource level middlewares with route handler. -struct Compose { - info: ComposeInfo, - state: ComposeState, -} - -struct ComposeInfo { - count: usize, - req: HttpRequest, - mws: Rc>>>, - handler: InnerHandler, -} - -enum ComposeState { - Starting(StartMiddlewares), - Handler(WaitingResponse), - RunMiddlewares(RunMiddlewares), - Finishing(FinishingMiddlewares), - Completed(Response), -} - -impl ComposeState { - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - match *self { - ComposeState::Starting(ref mut state) => state.poll(info), - ComposeState::Handler(ref mut state) => state.poll(info), - ComposeState::RunMiddlewares(ref mut state) => state.poll(info), - ComposeState::Finishing(ref mut state) => state.poll(info), - ComposeState::Completed(_) => None, - } - } -} - -impl Compose { - fn new( - req: HttpRequest, mws: Rc>>>, handler: InnerHandler, - ) -> Self { - let mut info = ComposeInfo { - count: 0, - req, - mws, - handler, - }; - let state = StartMiddlewares::init(&mut info); - - Compose { state, info } - } -} - -impl Future for Compose { - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - loop { - if let ComposeState::Completed(ref mut resp) = self.state { - let resp = resp.resp.take().unwrap(); - return Ok(Async::Ready(resp)); - } - if let Some(state) = self.state.poll(&mut self.info) { - self.state = state; - } else { - return Ok(Async::NotReady); - } - } - } -} - -/// Middlewares start executor -struct StartMiddlewares { - fut: Option, - _s: PhantomData, -} - -type Fut = Box, Error = Error>>; - -impl StartMiddlewares { - fn init(info: &mut ComposeInfo) -> ComposeState { - let len = info.mws.len(); - - loop { - if info.count == len { - let reply = info.handler.handle(&info.req); - return WaitingResponse::init(info, reply); - } else { - let result = info.mws[info.count].start(&info.req); - match result { - Ok(MiddlewareStarted::Done) => info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => { - return RunMiddlewares::init(info, resp); - } - Ok(MiddlewareStarted::Future(fut)) => { - return ComposeState::Starting(StartMiddlewares { - fut: Some(fut), - _s: PhantomData, - }); - } - Err(err) => { - return RunMiddlewares::init(info, err.into()); - } - } - } - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - 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; - -// waiting for response -struct WaitingResponse { - fut: Box, - _s: PhantomData, -} - -impl WaitingResponse { - #[inline] - fn init( - info: &mut ComposeInfo, reply: AsyncResult, - ) -> ComposeState { - 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) -> Option> { - 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 { - curr: usize, - fut: Option>>, - _s: PhantomData, -} - -impl RunMiddlewares { - fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { - 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) -> Option> { - 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 { - resp: Option, - fut: Option>>, - _s: PhantomData, -} - -impl FinishingMiddlewares { - fn init(info: &mut ComposeInfo, resp: HttpResponse) -> ComposeState { - 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) -> Option> { - 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 { - resp: Option, - _s: PhantomData, -} - -impl Response { - fn init(resp: HttpResponse) -> ComposeState { - ComposeState::Completed(Response { - resp: Some(resp), - _s: PhantomData, - }) - } -} diff --git a/src/router.rs b/src/router.rs deleted file mode 100644 index aa15e46d2..000000000 --- a/src/router.rs +++ /dev/null @@ -1,1247 +0,0 @@ -use std::cell::RefCell; -use std::cmp::min; -use std::collections::HashMap; -use std::hash::{Hash, Hasher}; -use std::rc::Rc; - -use regex::{escape, Regex}; -use url::Url; - -use error::UrlGenerationError; -use handler::{AsyncResult, FromRequest, Responder, RouteHandler}; -use http::{Method, StatusCode}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use param::{ParamItem, Params}; -use pred::Predicate; -use resource::{DefaultResource, Resource}; -use scope::Scope; -use server::Request; -use with::WithFactory; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub(crate) enum ResourceId { - Default, - Normal(u16), -} - -enum ResourcePattern { - Resource(ResourceDef), - Handler(ResourceDef, Option>>>), - Scope(ResourceDef, Vec>>), -} - -enum ResourceItem { - Resource(Resource), - Handler(Box>), - Scope(Scope), -} - -/// Interface for application router. -pub struct Router { - rmap: Rc, - patterns: Vec>, - resources: Vec>, - default: Option>, -} - -/// Information about current resource -#[derive(Clone)] -pub struct ResourceInfo { - rmap: Rc, - resource: ResourceId, - params: Params, - prefix: u16, -} - -impl ResourceInfo { - /// Name os the resource - #[inline] - pub fn name(&self) -> &str { - if let ResourceId::Normal(idx) = self.resource { - self.rmap.patterns[idx as usize].0.name() - } else { - "" - } - } - - /// This method returns reference to matched `ResourceDef` object. - #[inline] - pub fn rdef(&self) -> Option<&ResourceDef> { - if let ResourceId::Normal(idx) = self.resource { - Some(&self.rmap.patterns[idx as usize].0) - } else { - None - } - } - - pub(crate) fn set_prefix(&mut self, prefix: u16) { - self.prefix = prefix; - } - - /// 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.params - } - - #[inline] - pub(crate) fn merge(&mut self, info: &ResourceInfo) { - let mut p = info.params.clone(); - p.set_tail(self.params.tail); - for item in &self.params.segments { - p.add(item.0.clone(), item.1.clone()); - } - - self.prefix = info.params.tail; - self.params = p; - } - - /// Generate url for named resource - /// - /// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method. - /// url_for) for detailed information. - pub fn url_for( - &self, req: &Request, name: &str, elements: U, - ) -> Result - where - U: IntoIterator, - I: AsRef, - { - let mut path = String::new(); - let mut elements = elements.into_iter(); - - if self - .rmap - .patterns_for(name, &mut path, &mut elements)? - .is_some() - { - if path.starts_with('/') { - let conn = req.connection_info(); - Ok(Url::parse(&format!( - "{}://{}{}", - conn.scheme(), - conn.host(), - path - ))?) - } else { - Ok(Url::parse(&path)?) - } - } else { - Err(UrlGenerationError::ResourceNotFound) - } - } - - /// Check if application contains matching resource. - /// - /// This method does not take `prefix` into account. - /// For example if prefix is `/test` and router contains route `/name`, - /// following path would be recognizable `/test/name` but `has_resource()` call - /// would return `false`. - pub fn has_resource(&self, path: &str) -> bool { - self.rmap.has_resource(path) - } - - /// Check if application contains matching resource. - /// - /// This method does take `prefix` into account - /// but behaves like `has_route` in case `prefix` is not set in the router. - /// - /// For example if prefix is `/test` and router contains route `/name`, the - /// following path would be recognizable `/test/name` and `has_prefixed_route()` call - /// would return `true`. - /// It will not match against prefix in case it's not given. For example for `/name` - /// with a `/test` prefix would return `false` - pub fn has_prefixed_resource(&self, path: &str) -> bool { - let prefix = self.prefix as usize; - if prefix >= path.len() { - return false; - } - self.rmap.has_resource(&path[prefix..]) - } -} - -pub(crate) struct ResourceMap { - root: ResourceDef, - parent: RefCell>>, - named: HashMap, - patterns: Vec<(ResourceDef, Option>)>, - nested: Vec>, -} - -impl ResourceMap { - fn has_resource(&self, path: &str) -> bool { - let path = if path.is_empty() { "/" } else { path }; - - for (pattern, rmap) in &self.patterns { - if let Some(ref rmap) = rmap { - if let Some(plen) = pattern.is_prefix_match(path) { - return rmap.has_resource(&path[plen..]); - } - } else if pattern.is_match(path) { - return true; - } - } - false - } - - fn patterns_for( - &self, name: &str, path: &mut String, elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if self.pattern_for(name, path, elements)?.is_some() { - Ok(Some(())) - } else { - self.parent_pattern_for(name, path, elements) - } - } - - fn pattern_for( - &self, name: &str, path: &mut String, elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(pattern) = self.named.get(name) { - self.fill_root(path, elements)?; - pattern.resource_path(path, elements)?; - Ok(Some(())) - } else { - for rmap in &self.nested { - if rmap.pattern_for(name, path, elements)?.is_some() { - return Ok(Some(())); - } - } - Ok(None) - } - } - - fn fill_root( - &self, path: &mut String, elements: &mut U, - ) -> Result<(), UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(ref parent) = *self.parent.borrow() { - parent.fill_root(path, elements)?; - } - self.root.resource_path(path, elements) - } - - fn parent_pattern_for( - &self, name: &str, path: &mut String, elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(ref parent) = *self.parent.borrow() { - if let Some(pattern) = parent.named.get(name) { - self.fill_root(path, elements)?; - pattern.resource_path(path, elements)?; - Ok(Some(())) - } else { - parent.parent_pattern_for(name, path, elements) - } - } else { - Ok(None) - } - } -} - -impl Default for Router { - fn default() -> Self { - Router::new(ResourceDef::new("")) - } -} - -impl Router { - pub(crate) fn new(root: ResourceDef) -> Self { - Router { - rmap: Rc::new(ResourceMap { - root, - parent: RefCell::new(None), - named: HashMap::new(), - patterns: Vec::new(), - nested: Vec::new(), - }), - resources: Vec::new(), - patterns: Vec::new(), - default: None, - } - } - - #[inline] - pub(crate) fn route_info_params(&self, idx: u16, params: Params) -> ResourceInfo { - ResourceInfo { - params, - prefix: 0, - rmap: self.rmap.clone(), - resource: ResourceId::Normal(idx), - } - } - - #[cfg(test)] - pub(crate) fn default_route_info(&self) -> ResourceInfo { - ResourceInfo { - params: Params::new(), - rmap: self.rmap.clone(), - resource: ResourceId::Default, - prefix: 0, - } - } - - pub(crate) fn set_prefix(&mut self, path: &str) { - Rc::get_mut(&mut self.rmap).unwrap().root = ResourceDef::new(path); - } - - pub(crate) fn register_resource(&mut self, resource: Resource) { - { - let rmap = Rc::get_mut(&mut self.rmap).unwrap(); - - let name = resource.get_name(); - if !name.is_empty() { - assert!( - !rmap.named.contains_key(name), - "Named resource {:?} is registered.", - name - ); - rmap.named.insert(name.to_owned(), resource.rdef().clone()); - } - rmap.patterns.push((resource.rdef().clone(), None)); - } - self.patterns - .push(ResourcePattern::Resource(resource.rdef().clone())); - self.resources.push(ResourceItem::Resource(resource)); - } - - pub(crate) fn register_scope(&mut self, mut scope: Scope) { - Rc::get_mut(&mut self.rmap) - .unwrap() - .patterns - .push((scope.rdef().clone(), Some(scope.router().rmap.clone()))); - Rc::get_mut(&mut self.rmap) - .unwrap() - .nested - .push(scope.router().rmap.clone()); - - let filters = scope.take_filters(); - self.patterns - .push(ResourcePattern::Scope(scope.rdef().clone(), filters)); - self.resources.push(ResourceItem::Scope(scope)); - } - - pub(crate) fn register_handler( - &mut self, path: &str, hnd: Box>, - filters: Option>>>, - ) { - let rdef = ResourceDef::prefix(path); - Rc::get_mut(&mut self.rmap) - .unwrap() - .patterns - .push((rdef.clone(), None)); - self.resources.push(ResourceItem::Handler(hnd)); - self.patterns.push(ResourcePattern::Handler(rdef, filters)); - } - - pub(crate) fn has_default_resource(&self) -> bool { - self.default.is_some() - } - - pub(crate) fn register_default_resource(&mut self, resource: DefaultResource) { - self.default = Some(resource); - } - - pub(crate) fn finish(&mut self) { - for resource in &mut self.resources { - match resource { - ResourceItem::Resource(_) => (), - ResourceItem::Scope(scope) => { - if !scope.has_default_resource() { - if let Some(ref default) = self.default { - scope.default_resource(default.clone()); - } - } - *scope.router().rmap.parent.borrow_mut() = Some(self.rmap.clone()); - scope.finish(); - } - ResourceItem::Handler(hnd) => { - if !hnd.has_default_resource() { - if let Some(ref default) = self.default { - hnd.default_resource(default.clone()); - } - } - hnd.finish() - } - } - } - } - - pub(crate) fn register_external(&mut self, name: &str, rdef: ResourceDef) { - let rmap = Rc::get_mut(&mut self.rmap).unwrap(); - assert!( - !rmap.named.contains_key(name), - "Named resource {:?} is registered.", - name - ); - rmap.named.insert(name.to_owned(), rdef); - } - - pub(crate) fn register_route(&mut self, path: &str, method: Method, f: F) - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - let out = { - // get resource handler - let mut iterator = self.resources.iter_mut(); - - loop { - if let Some(ref mut resource) = iterator.next() { - if let ResourceItem::Resource(ref mut resource) = resource { - if resource.rdef().pattern() == path { - resource.method(method).with(f); - break None; - } - } - } else { - let mut resource = Resource::new(ResourceDef::new(path)); - resource.method(method).with(f); - break Some(resource); - } - } - }; - if let Some(out) = out { - self.register_resource(out); - } - } - - /// Handle request - pub fn handle(&self, req: &HttpRequest) -> AsyncResult { - let resource = match req.resource().resource { - ResourceId::Normal(idx) => &self.resources[idx as usize], - ResourceId::Default => { - if let Some(ref default) = self.default { - if let Some(id) = default.get_route_id(req) { - return default.handle(id, req); - } - } - return AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)); - } - }; - match resource { - ResourceItem::Resource(ref resource) => { - if let Some(id) = resource.get_route_id(req) { - return resource.handle(id, req); - } - - if let Some(ref default) = self.default { - if let Some(id) = default.get_route_id(req) { - return default.handle(id, req); - } - } - } - ResourceItem::Handler(hnd) => return hnd.handle(req), - ResourceItem::Scope(hnd) => return hnd.handle(req), - } - AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) - } - - /// Query for matched resource - pub fn recognize(&self, req: &Request, state: &S, tail: usize) -> ResourceInfo { - if tail <= req.path().len() { - 'outer: for (idx, resource) in self.patterns.iter().enumerate() { - match resource { - ResourcePattern::Resource(rdef) => { - if let Some(params) = rdef.match_with_params(req, tail) { - return self.route_info_params(idx as u16, params); - } - } - ResourcePattern::Handler(rdef, filters) => { - if let Some(params) = rdef.match_prefix_with_params(req, tail) { - if let Some(ref filters) = filters { - for filter in filters { - if !filter.check(req, state) { - continue 'outer; - } - } - } - return self.route_info_params(idx as u16, params); - } - } - ResourcePattern::Scope(rdef, filters) => { - if let Some(params) = rdef.match_prefix_with_params(req, tail) { - for filter in filters { - if !filter.check(req, state) { - continue 'outer; - } - } - return self.route_info_params(idx as u16, params); - } - } - } - } - } - ResourceInfo { - prefix: tail as u16, - params: Params::new(), - rmap: self.rmap.clone(), - resource: ResourceId::Default, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -enum PatternElement { - Str(String), - Var(String), -} - -#[derive(Clone, Debug)] -enum PatternType { - Static(String), - Prefix(String), - Dynamic(Regex, Vec>, usize), -} - -#[derive(Debug, Copy, Clone, PartialEq)] -/// Resource type -pub enum ResourceType { - /// Normal resource - Normal, - /// Resource for application default handler - Default, - /// External resource - External, - /// Unknown resource type - Unset, -} - -/// Resource type describes an entry in resources table -#[derive(Clone, Debug)] -pub struct ResourceDef { - tp: PatternType, - rtp: ResourceType, - name: String, - pattern: String, - elements: Vec, -} - -impl ResourceDef { - /// Parse path pattern and create new `ResourceDef` instance. - /// - /// Panics if path pattern is wrong. - pub fn new(path: &str) -> Self { - ResourceDef::with_prefix(path, false, !path.is_empty()) - } - - /// Parse path pattern and create new `ResourceDef` instance. - /// - /// Use `prefix` type instead of `static`. - /// - /// Panics if path regex pattern is wrong. - pub fn prefix(path: &str) -> Self { - ResourceDef::with_prefix(path, true, !path.is_empty()) - } - - /// Construct external resource def - /// - /// Panics if path pattern is wrong. - pub fn external(path: &str) -> Self { - let mut resource = ResourceDef::with_prefix(path, false, false); - resource.rtp = ResourceType::External; - resource - } - - /// Parse path pattern and create new `ResourceDef` instance with custom prefix - pub fn with_prefix(path: &str, for_prefix: bool, slash: bool) -> Self { - let mut path = path.to_owned(); - if slash && !path.starts_with('/') { - path.insert(0, '/'); - } - let (pattern, elements, is_dynamic, len) = ResourceDef::parse(&path, for_prefix); - - let tp = if is_dynamic { - let re = match Regex::new(&pattern) { - Ok(re) => re, - Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err), - }; - // actix creates one router per thread - let names = re - .capture_names() - .filter_map(|name| name.map(|name| Rc::new(name.to_owned()))) - .collect(); - PatternType::Dynamic(re, names, len) - } else if for_prefix { - PatternType::Prefix(pattern.clone()) - } else { - PatternType::Static(pattern.clone()) - }; - - ResourceDef { - tp, - elements, - name: "".to_string(), - rtp: ResourceType::Normal, - pattern: path.to_owned(), - } - } - - /// Resource type - pub fn rtype(&self) -> ResourceType { - self.rtp - } - - /// Resource name - pub fn name(&self) -> &str { - &self.name - } - - /// Resource name - pub(crate) fn set_name(&mut self, name: &str) { - self.name = name.to_owned(); - } - - /// Path pattern of the resource - pub fn pattern(&self) -> &str { - &self.pattern - } - - /// Is this path a match against this resource? - pub fn is_match(&self, path: &str) -> bool { - match self.tp { - PatternType::Static(ref s) => s == path, - PatternType::Dynamic(ref re, _, _) => re.is_match(path), - PatternType::Prefix(ref s) => path.starts_with(s), - } - } - - fn is_prefix_match(&self, path: &str) -> Option { - let plen = path.len(); - let path = if path.is_empty() { "/" } else { path }; - - match self.tp { - PatternType::Static(ref s) => if s == path { - Some(plen) - } else { - None - }, - PatternType::Dynamic(ref re, _, len) => { - if let Some(captures) = re.captures(path) { - let mut pos = 0; - let mut passed = false; - for capture in captures.iter() { - if let Some(ref m) = capture { - if !passed { - passed = true; - continue; - } - - pos = m.end(); - } - } - Some(plen + pos + len) - } else { - None - } - } - PatternType::Prefix(ref s) => { - let len = if path == s { - s.len() - } else if path.starts_with(s) - && (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/')) - { - if s.ends_with('/') { - s.len() - 1 - } else { - s.len() - } - } else { - return None; - }; - Some(min(plen, len)) - } - } - } - - /// Are the given path and parameters a match against this resource? - pub fn match_with_params(&self, req: &Request, plen: usize) -> Option { - let path = &req.path()[plen..]; - - match self.tp { - PatternType::Static(ref s) => if s != path { - None - } else { - Some(Params::with_url(req.url())) - }, - PatternType::Dynamic(ref re, ref names, _) => { - if let Some(captures) = re.captures(path) { - let mut params = Params::with_url(req.url()); - let mut idx = 0; - let mut passed = false; - for capture in captures.iter() { - if let Some(ref m) = capture { - if !passed { - passed = true; - continue; - } - params.add( - names[idx].clone(), - ParamItem::UrlSegment( - (plen + m.start()) as u16, - (plen + m.end()) as u16, - ), - ); - idx += 1; - } - } - params.set_tail(req.path().len() as u16); - Some(params) - } else { - None - } - } - PatternType::Prefix(ref s) => if !path.starts_with(s) { - None - } else { - Some(Params::with_url(req.url())) - }, - } - } - - /// Is the given path a prefix match and do the parameters match against this resource? - pub fn match_prefix_with_params( - &self, req: &Request, plen: usize, - ) -> Option { - let path = &req.path()[plen..]; - let path = if path.is_empty() { "/" } else { path }; - - match self.tp { - PatternType::Static(ref s) => if s == path { - let mut params = Params::with_url(req.url()); - params.set_tail(req.path().len() as u16); - Some(params) - } else { - None - }, - PatternType::Dynamic(ref re, ref names, len) => { - if let Some(captures) = re.captures(path) { - let mut params = Params::with_url(req.url()); - let mut pos = 0; - let mut passed = false; - let mut idx = 0; - for capture in captures.iter() { - if let Some(ref m) = capture { - if !passed { - passed = true; - continue; - } - - params.add( - names[idx].clone(), - ParamItem::UrlSegment( - (plen + m.start()) as u16, - (plen + m.end()) as u16, - ), - ); - idx += 1; - pos = m.end(); - } - } - params.set_tail((plen + pos + len) as u16); - Some(params) - } else { - None - } - } - PatternType::Prefix(ref s) => { - let len = if path == s { - s.len() - } else if path.starts_with(s) - && (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/')) - { - if s.ends_with('/') { - s.len() - 1 - } else { - s.len() - } - } else { - return None; - }; - let mut params = Params::with_url(req.url()); - params.set_tail(min(req.path().len(), plen + len) as u16); - Some(params) - } - } - } - - /// Build resource path. - pub fn resource_path( - &self, path: &mut String, elements: &mut U, - ) -> Result<(), UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - match self.tp { - PatternType::Prefix(ref p) => path.push_str(p), - PatternType::Static(ref p) => path.push_str(p), - PatternType::Dynamic(..) => { - for el in &self.elements { - match *el { - PatternElement::Str(ref s) => path.push_str(s), - PatternElement::Var(_) => { - if let Some(val) = elements.next() { - path.push_str(val.as_ref()) - } else { - return Err(UrlGenerationError::NotEnoughElements); - } - } - } - } - } - }; - Ok(()) - } - - fn parse_param(pattern: &str) -> (PatternElement, String, &str) { - const DEFAULT_PATTERN: &str = "[^/]+"; - let mut params_nesting = 0usize; - let close_idx = pattern - .find(|c| match c { - '{' => { - params_nesting += 1; - false - } - '}' => { - params_nesting -= 1; - params_nesting == 0 - } - _ => false, - }).expect("malformed param"); - let (mut param, rem) = pattern.split_at(close_idx + 1); - param = ¶m[1..param.len() - 1]; // Remove outer brackets - let (name, pattern) = match param.find(':') { - Some(idx) => { - let (name, pattern) = param.split_at(idx); - (name, &pattern[1..]) - } - None => (param, DEFAULT_PATTERN), - }; - ( - PatternElement::Var(name.to_string()), - format!(r"(?P<{}>{})", &name, &pattern), - rem, - ) - } - - fn parse( - mut pattern: &str, for_prefix: bool, - ) -> (String, Vec, bool, usize) { - if pattern.find('{').is_none() { - return ( - String::from(pattern), - vec![PatternElement::Str(String::from(pattern))], - false, - pattern.chars().count(), - ); - }; - - let mut elems = Vec::new(); - let mut re = String::from("^"); - - while let Some(idx) = pattern.find('{') { - let (prefix, rem) = pattern.split_at(idx); - elems.push(PatternElement::Str(String::from(prefix))); - re.push_str(&escape(prefix)); - let (param_pattern, re_part, rem) = Self::parse_param(rem); - elems.push(param_pattern); - re.push_str(&re_part); - pattern = rem; - } - - elems.push(PatternElement::Str(String::from(pattern))); - re.push_str(&escape(pattern)); - - if !for_prefix { - re.push_str("$"); - } - - (re, elems, true, pattern.chars().count()) - } -} - -impl PartialEq for ResourceDef { - fn eq(&self, other: &ResourceDef) -> bool { - self.pattern == other.pattern - } -} - -impl Eq for ResourceDef {} - -impl Hash for ResourceDef { - fn hash(&self, state: &mut H) { - self.pattern.hash(state); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test::TestRequest; - - #[test] - fn test_recognizer10() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/name"))); - router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); - router.register_resource(Resource::new(ResourceDef::new( - "/name/{val}/index.html", - ))); - router.register_resource(Resource::new(ResourceDef::new("/file/{file}.{ext}"))); - router.register_resource(Resource::new(ResourceDef::new( - "/v{val}/{val2}/index.html", - ))); - router.register_resource(Resource::new(ResourceDef::new("/v/{tail:.*}"))); - router.register_resource(Resource::new(ResourceDef::new("/test2/{test}.html"))); - router.register_resource(Resource::new(ResourceDef::new("/{test}/index.html"))); - - let req = TestRequest::with_uri("/name").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(0)); - assert!(info.match_info().is_empty()); - - let req = TestRequest::with_uri("/name/value").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(info.match_info().get("val").unwrap(), "value"); - assert_eq!(&info.match_info()["val"], "value"); - - let req = TestRequest::with_uri("/name/value2/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(2)); - assert_eq!(info.match_info().get("val").unwrap(), "value2"); - - let req = TestRequest::with_uri("/file/file.gz").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(3)); - assert_eq!(info.match_info().get("file").unwrap(), "file"); - assert_eq!(info.match_info().get("ext").unwrap(), "gz"); - - let req = TestRequest::with_uri("/vtest/ttt/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(4)); - assert_eq!(info.match_info().get("val").unwrap(), "test"); - assert_eq!(info.match_info().get("val2").unwrap(), "ttt"); - - let req = TestRequest::with_uri("/v/blah-blah/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(5)); - assert_eq!( - info.match_info().get("tail").unwrap(), - "blah-blah/index.html" - ); - - let req = TestRequest::with_uri("/test2/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(6)); - assert_eq!(info.match_info().get("test").unwrap(), "index"); - - let req = TestRequest::with_uri("/bbb/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(7)); - assert_eq!(info.match_info().get("test").unwrap(), "bbb"); - } - - #[test] - fn test_recognizer_2() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/index.json"))); - router.register_resource(Resource::new(ResourceDef::new("/{source}.json"))); - - let req = TestRequest::with_uri("/index.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(0)); - - let req = TestRequest::with_uri("/test.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - } - - #[test] - fn test_recognizer_with_prefix() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/name"))); - router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); - - let req = TestRequest::with_uri("/name").finish(); - let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, ResourceId::Default); - - let req = TestRequest::with_uri("/test/name").finish(); - let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, ResourceId::Normal(0)); - - let req = TestRequest::with_uri("/test/name/value").finish(); - let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(info.match_info().get("val").unwrap(), "value"); - assert_eq!(&info.match_info()["val"], "value"); - - // same patterns - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/name"))); - router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); - - let req = TestRequest::with_uri("/name").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Default); - - let req = TestRequest::with_uri("/test2/name").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Normal(0)); - - let req = TestRequest::with_uri("/test2/name-test").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Default); - - let req = TestRequest::with_uri("/test2/name/ttt").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(&info.match_info()["val"], "ttt"); - } - - #[test] - fn test_parse_static() { - let re = ResourceDef::new("/"); - assert!(re.is_match("/")); - assert!(!re.is_match("/a")); - - let re = ResourceDef::new("/name"); - assert!(re.is_match("/name")); - assert!(!re.is_match("/name1")); - assert!(!re.is_match("/name/")); - assert!(!re.is_match("/name~")); - - let re = ResourceDef::new("/name/"); - assert!(re.is_match("/name/")); - assert!(!re.is_match("/name")); - assert!(!re.is_match("/name/gs")); - - let re = ResourceDef::new("/user/profile"); - assert!(re.is_match("/user/profile")); - assert!(!re.is_match("/user/profile/profile")); - } - - #[test] - fn test_parse_param() { - let re = ResourceDef::new("/user/{id}"); - assert!(re.is_match("/user/profile")); - assert!(re.is_match("/user/2345")); - assert!(!re.is_match("/user/2345/")); - assert!(!re.is_match("/user/2345/sdg")); - - let req = TestRequest::with_uri("/user/profile").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("id").unwrap(), "profile"); - - let req = TestRequest::with_uri("/user/1245125").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("id").unwrap(), "1245125"); - - let re = ResourceDef::new("/v{version}/resource/{id}"); - assert!(re.is_match("/v1/resource/320120")); - assert!(!re.is_match("/v/resource/1")); - assert!(!re.is_match("/resource")); - - let req = TestRequest::with_uri("/v151/resource/adahg32").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("version").unwrap(), "151"); - assert_eq!(info.get("id").unwrap(), "adahg32"); - - let re = ResourceDef::new("/{id:[[:digit:]]{6}}"); - assert!(re.is_match("/012345")); - assert!(!re.is_match("/012")); - assert!(!re.is_match("/01234567")); - assert!(!re.is_match("/XXXXXX")); - - let req = TestRequest::with_uri("/012345").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("id").unwrap(), "012345"); - } - - #[test] - fn test_resource_prefix() { - let re = ResourceDef::prefix("/name"); - assert!(re.is_match("/name")); - assert!(re.is_match("/name/")); - assert!(re.is_match("/name/test/test")); - assert!(re.is_match("/name1")); - assert!(re.is_match("/name~")); - - let re = ResourceDef::prefix("/name/"); - assert!(re.is_match("/name/")); - assert!(re.is_match("/name/gs")); - assert!(!re.is_match("/name")); - } - - #[test] - fn test_reousrce_prefix_dynamic() { - let re = ResourceDef::prefix("/{name}/"); - assert!(re.is_match("/name/")); - assert!(re.is_match("/name/gs")); - assert!(!re.is_match("/name")); - - let req = TestRequest::with_uri("/test2/").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(&info["name"], "test2"); - assert_eq!(&info[0], "test2"); - - let req = TestRequest::with_uri("/test2/subpath1/subpath2/index.html").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(&info["name"], "test2"); - assert_eq!(&info[0], "test2"); - } - - #[test] - fn test_request_resource() { - let mut router = Router::<()>::default(); - let mut resource = Resource::new(ResourceDef::new("/index.json")); - resource.name("r1"); - router.register_resource(resource); - let mut resource = Resource::new(ResourceDef::new("/test.json")); - resource.name("r2"); - router.register_resource(resource); - - let req = TestRequest::with_uri("/index.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(0)); - - assert_eq!(info.name(), "r1"); - - let req = TestRequest::with_uri("/test.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(info.name(), "r2"); - } - - #[test] - fn test_has_resource() { - let mut router = Router::<()>::default(); - let scope = Scope::new("/test").resource("/name", |_| "done"); - router.register_scope(scope); - - { - let info = router.default_route_info(); - assert!(!info.has_resource("/test")); - assert!(info.has_resource("/test/name")); - } - - let scope = - Scope::new("/test2").nested("/test10", |s| s.resource("/name", |_| "done")); - router.register_scope(scope); - - let info = router.default_route_info(); - assert!(info.has_resource("/test2/test10/name")); - } - - #[test] - fn test_url_for() { - let mut router = Router::<()>::new(ResourceDef::prefix("")); - - let mut resource = Resource::new(ResourceDef::new("/tttt")); - resource.name("r0"); - router.register_resource(resource); - - let scope = Scope::new("/test").resource("/name", |r| { - r.name("r1"); - }); - router.register_scope(scope); - - let scope = Scope::new("/test2") - .nested("/test10", |s| s.resource("/name", |r| r.name("r2"))); - router.register_scope(scope); - router.finish(); - - let req = TestRequest::with_uri("/test").request(); - { - let info = router.default_route_info(); - - let res = info - .url_for(&req, "r0", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/tttt"); - - let res = info - .url_for(&req, "r1", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test/name"); - - let res = info - .url_for(&req, "r2", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test2/test10/name"); - } - - let req = TestRequest::with_uri("/test/name").request(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - - let res = info - .url_for(&req, "r0", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/tttt"); - - let res = info - .url_for(&req, "r1", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test/name"); - - let res = info - .url_for(&req, "r2", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test2/test10/name"); - } - - #[test] - fn test_url_for_dynamic() { - let mut router = Router::<()>::new(ResourceDef::prefix("")); - - let mut resource = Resource::new(ResourceDef::new("/{name}/test/index.{ext}")); - resource.name("r0"); - router.register_resource(resource); - - let scope = Scope::new("/{name1}").nested("/{name2}", |s| { - s.resource("/{name3}/test/index.{ext}", |r| r.name("r2")) - }); - router.register_scope(scope); - router.finish(); - - let req = TestRequest::with_uri("/test").request(); - { - let info = router.default_route_info(); - - let res = info.url_for(&req, "r0", vec!["sec1", "html"]).unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/sec1/test/index.html"); - - let res = info - .url_for(&req, "r2", vec!["sec1", "sec2", "sec3", "html"]) - .unwrap(); - assert_eq!( - res.as_str(), - "http://localhost:8080/sec1/sec2/sec3/test/index.html" - ); - } - } -} diff --git a/src/scope.rs b/src/scope.rs deleted file mode 100644 index 43789d427..000000000 --- a/src/scope.rs +++ /dev/null @@ -1,1236 +0,0 @@ -use std::marker::PhantomData; -use std::mem; -use std::rc::Rc; - -use futures::{Async, Future, Poll}; - -use error::Error; -use handler::{ - AsyncResult, AsyncResultItem, FromRequest, Handler, Responder, RouteHandler, - WrapHandler, -}; -use http::Method; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{ - Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse, - Started as MiddlewareStarted, -}; -use pred::Predicate; -use resource::{DefaultResource, Resource}; -use router::{ResourceDef, Router}; -use server::Request; -use with::WithFactory; - -/// Resources scope -/// -/// Scope is a set of resources with common root path. -/// Scopes collect multiple paths under a common path prefix. -/// Scope path can contain variable path segments as resources. -/// Scope prefix is always complete path segment, i.e `/app` would -/// be converted to a `/app/` and it would not match `/app` path. -/// -/// You can get variable path segments from `HttpRequest::match_info()`. -/// `Path` extractor also is able to extract scope level variable segments. -/// -/// ```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 registered: -/// * /{project_id}/path1 - reponds to all http method -/// * /{project_id}/path2 - `GET` requests -/// * /{project_id}/path3 - `HEAD` requests -/// -pub struct Scope { - rdef: ResourceDef, - router: Rc>, - filters: Vec>>, - middlewares: Rc>>>, -} - -#[cfg_attr( - feature = "cargo-clippy", - allow(clippy::new_without_default_derive) -)] -impl Scope { - /// Create a new scope - pub fn new(path: &str) -> Scope { - let rdef = ResourceDef::prefix(path); - Scope { - rdef: rdef.clone(), - router: Rc::new(Router::new(rdef)), - filters: Vec::new(), - middlewares: Rc::new(Vec::new()), - } - } - - #[inline] - pub(crate) fn rdef(&self) -> &ResourceDef { - &self.rdef - } - - pub(crate) fn router(&self) -> &Router { - self.router.as_ref() - } - - #[inline] - pub(crate) fn take_filters(&mut self) -> Vec>> { - mem::replace(&mut self.filters, Vec::new()) - } - - /// Add match predicate to scope. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, pred, App, HttpRequest, HttpResponse, Path}; - /// - /// fn index(data: Path<(String, String)>) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope - /// .filter(pred::Header("content-type", "text/plain")) - /// .route("/test1", http::Method::GET, index) - /// .route("/test2", http::Method::POST, |_: HttpRequest| { - /// HttpResponse::MethodNotAllowed() - /// }) - /// }); - /// } - /// ``` - pub fn filter + 'static>(mut self, p: T) -> Self { - self.filters.push(Box::new(p)); - self - } - - /// Create nested scope with new state. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest}; - /// - /// struct AppState; - /// - /// fn index(req: &HttpRequest) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope.with_state("/state2", AppState, |scope| { - /// scope.resource("/test1", |r| r.f(index)) - /// }) - /// }); - /// } - /// ``` - pub fn with_state(mut self, path: &str, state: T, f: F) -> Scope - where - F: FnOnce(Scope) -> Scope, - { - let rdef = ResourceDef::prefix(path); - let scope = Scope { - rdef: rdef.clone(), - filters: Vec::new(), - router: Rc::new(Router::new(rdef)), - middlewares: Rc::new(Vec::new()), - }; - let mut scope = f(scope); - - let state = Rc::new(state); - let filters: Vec>> = vec![Box::new(FiltersWrapper { - state: Rc::clone(&state), - filters: scope.take_filters(), - })]; - let handler = Box::new(Wrapper { scope, state }); - - Rc::get_mut(&mut self.router).unwrap().register_handler( - path, - handler, - Some(filters), - ); - - self - } - - /// Create nested scope. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest}; - /// - /// struct AppState; - /// - /// fn index(req: &HttpRequest) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::with_state(AppState).scope("/app", |scope| { - /// scope.nested("/v1", |scope| scope.resource("/test1", |r| r.f(index))) - /// }); - /// } - /// ``` - pub fn nested(mut self, path: &str, f: F) -> Scope - where - F: FnOnce(Scope) -> Scope, - { - let rdef = ResourceDef::prefix(&insert_slash(path)); - let scope = Scope { - rdef: rdef.clone(), - filters: Vec::new(), - router: Rc::new(Router::new(rdef)), - middlewares: Rc::new(Vec::new()), - }; - Rc::get_mut(&mut self.router) - .unwrap() - .register_scope(f(scope)); - - self - } - - /// Configure route for a specific path. - /// - /// This is a simplified version of the `Scope::resource()` method. - /// Handler functions need to accept one request extractor - /// argument. - /// - /// This method could be called multiple times, in that case - /// multiple routes would be registered for same resource path. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse, Path}; - /// - /// fn index(data: Path<(String, String)>) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope.route("/test1", http::Method::GET, index).route( - /// "/test2", - /// http::Method::POST, - /// |_: HttpRequest| HttpResponse::MethodNotAllowed(), - /// ) - /// }); - /// } - /// ``` - pub fn route(mut self, path: &str, method: Method, f: F) -> Scope - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - Rc::get_mut(&mut self.router).unwrap().register_route( - &insert_slash(path), - method, - f, - ); - self - } - - /// Configure resource for a specific path. - /// - /// This method is similar to an `App::resource()` method. - /// Resources may have variable path segments. Resource path uses scope - /// path as a path prefix. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// - /// fn main() { - /// let app = App::new().scope("/api", |scope| { - /// scope.resource("/users/{userid}/{friend}", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// r.route() - /// .filter(pred::Any(pred::Get()).or(pred::Put())) - /// .filter(pred::Header("Content-Type", "text/plain")) - /// .f(|_| HttpResponse::Ok()) - /// }) - /// }); - /// } - /// ``` - pub fn resource(mut self, path: &str, f: F) -> Scope - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // add resource - let mut resource = Resource::new(ResourceDef::new(&insert_slash(path))); - f(&mut resource); - - Rc::get_mut(&mut self.router) - .unwrap() - .register_resource(resource); - self - } - - /// Default resource to be used if no matching route could be found. - pub fn default_resource(mut self, f: F) -> Scope - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // create and configure default resource - let mut resource = Resource::new(ResourceDef::new("")); - f(&mut resource); - - Rc::get_mut(&mut self.router) - .expect("Multiple copies of scope router") - .register_default_resource(resource.into()); - - self - } - - /// Configure handler for specific path prefix. - /// - /// A path prefix consists of valid path segments, i.e for the - /// prefix `/app` any request with the paths `/app`, `/app/` or - /// `/app/test` would match, but the path `/application` would - /// not. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().scope("/scope-prefix", |scope| { - /// scope.handler("/app", |req: &HttpRequest| match *req.method() { - /// http::Method::GET => HttpResponse::Ok(), - /// http::Method::POST => HttpResponse::MethodNotAllowed(), - /// _ => HttpResponse::NotFound(), - /// }) - /// }); - /// } - /// ``` - pub fn handler>(mut self, path: &str, handler: H) -> Scope { - let path = insert_slash(path.trim().trim_right_matches('/')); - Rc::get_mut(&mut self.router) - .expect("Multiple copies of scope router") - .register_handler(&path, Box::new(WrapHandler::new(handler)), None); - self - } - - /// Register a scope middleware - /// - /// This is similar to `App's` middlewares, but - /// middlewares get invoked on scope level. - /// - /// *Note* `Middleware::finish()` fires right after response get - /// prepared. It does not wait until body get sent to the peer. - pub fn middleware>(mut self, mw: M) -> Scope { - Rc::get_mut(&mut self.middlewares) - .expect("Can not use after configuration") - .push(Box::new(mw)); - self - } -} - -fn insert_slash(path: &str) -> String { - let mut path = path.to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/'); - }; - path -} - -impl RouteHandler for Scope { - fn handle(&self, req: &HttpRequest) -> AsyncResult { - let tail = req.match_info().tail as usize; - - // recognize resources - let info = self.router.recognize(req, req.state(), tail); - let req2 = req.with_route_info(info); - if self.middlewares.is_empty() { - self.router.handle(&req2) - } else { - AsyncResult::async(Box::new(Compose::new( - req2, - Rc::clone(&self.router), - Rc::clone(&self.middlewares), - ))) - } - } - - fn has_default_resource(&self) -> bool { - self.router.has_default_resource() - } - - fn default_resource(&mut self, default: DefaultResource) { - Rc::get_mut(&mut self.router) - .expect("Can not use after configuration") - .register_default_resource(default); - } - - fn finish(&mut self) { - Rc::get_mut(&mut self.router) - .expect("Can not use after configuration") - .finish(); - } -} - -struct Wrapper { - state: Rc, - scope: Scope, -} - -impl RouteHandler for Wrapper { - fn handle(&self, req: &HttpRequest) -> AsyncResult { - let req = req.with_state(Rc::clone(&self.state)); - self.scope.handle(&req) - } -} - -struct FiltersWrapper { - state: Rc, - filters: Vec>>, -} - -impl Predicate for FiltersWrapper { - fn check(&self, req: &Request, _: &S2) -> bool { - for filter in &self.filters { - if !filter.check(&req, &self.state) { - return false; - } - } - true - } -} - -/// Compose resource level middlewares with route handler. -struct Compose { - info: ComposeInfo, - state: ComposeState, -} - -struct ComposeInfo { - count: usize, - req: HttpRequest, - router: Rc>, - mws: Rc>>>, -} - -enum ComposeState { - Starting(StartMiddlewares), - Handler(WaitingResponse), - RunMiddlewares(RunMiddlewares), - Finishing(FinishingMiddlewares), - Completed(Response), -} - -impl ComposeState { - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - match *self { - ComposeState::Starting(ref mut state) => state.poll(info), - ComposeState::Handler(ref mut state) => state.poll(info), - ComposeState::RunMiddlewares(ref mut state) => state.poll(info), - ComposeState::Finishing(ref mut state) => state.poll(info), - ComposeState::Completed(_) => None, - } - } -} - -impl Compose { - fn new( - req: HttpRequest, router: Rc>, mws: Rc>>>, - ) -> Self { - let mut info = ComposeInfo { - mws, - req, - router, - count: 0, - }; - let state = StartMiddlewares::init(&mut info); - - Compose { state, info } - } -} - -impl Future for Compose { - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - loop { - if let ComposeState::Completed(ref mut resp) = self.state { - let resp = resp.resp.take().unwrap(); - return Ok(Async::Ready(resp)); - } - if let Some(state) = self.state.poll(&mut self.info) { - self.state = state; - } else { - return Ok(Async::NotReady); - } - } - } -} - -/// Middlewares start executor -struct StartMiddlewares { - fut: Option, - _s: PhantomData, -} - -type Fut = Box, Error = Error>>; - -impl StartMiddlewares { - fn init(info: &mut ComposeInfo) -> ComposeState { - let len = info.mws.len(); - - loop { - if info.count == len { - let reply = info.router.handle(&info.req); - return WaitingResponse::init(info, reply); - } else { - let result = info.mws[info.count].start(&info.req); - match result { - Ok(MiddlewareStarted::Done) => info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => { - return RunMiddlewares::init(info, resp); - } - Ok(MiddlewareStarted::Future(fut)) => { - return ComposeState::Starting(StartMiddlewares { - fut: Some(fut), - _s: PhantomData, - }); - } - Err(err) => { - return RunMiddlewares::init(info, err.into()); - } - } - } - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - 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.router.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())); - } - } - } - } -} - -// waiting for response -struct WaitingResponse { - fut: Box>, - _s: PhantomData, -} - -impl WaitingResponse { - #[inline] - fn init( - info: &mut ComposeInfo, reply: AsyncResult, - ) -> ComposeState { - 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) -> Option> { - 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 { - curr: usize, - fut: Option>>, - _s: PhantomData, -} - -impl RunMiddlewares { - fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { - 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) -> Option> { - 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 { - resp: Option, - fut: Option>>, - _s: PhantomData, -} - -impl FinishingMiddlewares { - fn init(info: &mut ComposeInfo, resp: HttpResponse) -> ComposeState { - 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) -> Option> { - 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 { - resp: Option, - _s: PhantomData, -} - -impl Response { - fn init(resp: HttpResponse) -> ComposeState { - ComposeState::Completed(Response { - resp: Some(resp), - _s: PhantomData, - }) - } -} - -#[cfg(test)] -mod tests { - use bytes::Bytes; - - use application::App; - use body::Body; - use http::{Method, StatusCode}; - use httprequest::HttpRequest; - use httpresponse::HttpResponse; - use pred; - use test::TestRequest; - - #[test] - fn test_scope() { - let app = App::new() - .scope("/app", |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_root() { - let app = App::new() - .scope("/app", |scope| { - scope - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Created())) - }).finish(); - - let req = TestRequest::with_uri("/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_scope_root2() { - let app = App::new() - .scope("/app/", |scope| { - scope.resource("", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_root3() { - let app = App::new() - .scope("/app/", |scope| { - scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_route() { - let app = App::new() - .scope("app", |scope| { - scope - .route("/path1", Method::GET, |_: HttpRequest<_>| { - HttpResponse::Ok() - }).route("/path1", Method::DELETE, |_: HttpRequest<_>| { - HttpResponse::Ok() - }) - }).finish(); - - let req = TestRequest::with_uri("/app/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_route_without_leading_slash() { - let app = App::new() - .scope("app", |scope| { - scope - .route("path1", Method::GET, |_: HttpRequest<_>| HttpResponse::Ok()) - .route("path1", Method::DELETE, |_: HttpRequest<_>| { - HttpResponse::Ok() - }) - }).finish(); - - let req = TestRequest::with_uri("/app/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_filter() { - let app = App::new() - .scope("/app", |scope| { - scope - .filter(pred::Get()) - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::GET) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_variable_segment() { - let app = App::new() - .scope("/ab-{project}", |scope| { - scope.resource("/path1", |r| { - r.f(|r| { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) - }) - }) - }).finish(); - - let req = TestRequest::with_uri("/ab-project1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - match resp.as_msg().body() { - &Body::Binary(ref b) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project1")); - } - _ => panic!(), - } - - let req = TestRequest::with_uri("/aa-project1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_with_state() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1", State, |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_scope_with_state_root() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1", State, |scope| { - scope - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_scope_with_state_root2() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1/", State, |scope| { - scope.resource("", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_with_state_root3() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1/", State, |scope| { - scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_with_state_filter() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1", State, |scope| { - scope - .filter(pred::Get()) - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::GET) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_nested_scope() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_nested_scope_no_slash() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("t1", |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_nested_scope_root() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_nested_scope_filter() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope - .filter(pred::Get()) - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::GET) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_nested_scope_with_variable_segment() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/{project_id}", |scope| { - scope.resource("/path1", |r| { - r.f(|r| { - HttpResponse::Created().body(format!( - "project: {}", - &r.match_info()["project_id"] - )) - }) - }) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/project_1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - - match resp.as_msg().body() { - &Body::Binary(ref b) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project_1")); - } - _ => panic!(), - } - } - - #[test] - fn test_nested2_scope_with_variable_segment() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/{project}", |scope| { - scope.nested("/{id}", |scope| { - scope.resource("/path1", |r| { - r.f(|r| { - HttpResponse::Created().body(format!( - "project: {} - {}", - &r.match_info()["project"], - &r.match_info()["id"], - )) - }) - }) - }) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/test/1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - - match resp.as_msg().body() { - &Body::Binary(ref b) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); - } - _ => panic!(), - } - - let req = TestRequest::with_uri("/app/test/1/path2").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_default_resource() { - let app = App::new() - .scope("/app", |scope| { - scope - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - .default_resource(|r| r.f(|_| HttpResponse::BadRequest())) - }).finish(); - - let req = TestRequest::with_uri("/app/path2").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); - - let req = TestRequest::with_uri("/path2").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_default_resource_propagation() { - let app = App::new() - .scope("/app1", |scope| { - scope.default_resource(|r| r.f(|_| HttpResponse::BadRequest())) - }).scope("/app2", |scope| scope) - .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) - .finish(); - - let req = TestRequest::with_uri("/non-exist").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); - - let req = TestRequest::with_uri("/app1/non-exist").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); - - let req = TestRequest::with_uri("/app2/non-exist").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[test] - fn test_handler() { - let app = App::new() - .scope("/scope", |scope| { - scope.handler("/test", |_: &_| HttpResponse::Ok()) - }).finish(); - - let req = TestRequest::with_uri("/scope/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/scope/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/scope/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/scope/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/scope/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } -} diff --git a/src/server/acceptor.rs b/src/server/acceptor.rs deleted file mode 100644 index 2e1b1f283..000000000 --- a/src/server/acceptor.rs +++ /dev/null @@ -1,396 +0,0 @@ -use std::time::Duration; -use std::{fmt, net}; - -use actix_net::server::ServerMessage; -use actix_net::service::{NewService, Service}; -use futures::future::{err, ok, Either, FutureResult}; -use futures::{Async, Future, Poll}; -use tokio_reactor::Handle; -use tokio_tcp::TcpStream; -use tokio_timer::{sleep, Delay}; - -use super::channel::HttpProtocol; -use super::error::AcceptorError; -use super::handler::HttpHandler; -use super::settings::ServiceConfig; -use super::IoStream; - -/// This trait indicates types that can create acceptor service for http server. -pub trait AcceptorServiceFactory: Send + Clone + 'static { - type Io: IoStream + Send; - type NewService: NewService; - - fn create(&self) -> Self::NewService; -} - -impl AcceptorServiceFactory for F -where - F: Fn() -> T + Send + Clone + 'static, - T::Response: IoStream + Send, - T: NewService, - 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; - - fn new_service(&self) -> Self::Future { - ok(DefaultAcceptor) - } -} - -impl Service for DefaultAcceptor { - type Request = TcpStream; - type Response = TcpStream; - type Error = (); - type Future = FutureResult; - - 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 { - inner: T, -} - -impl TcpAcceptor -where - T: NewService>, - T::InitError: fmt::Debug, -{ - pub(crate) fn new(inner: T) -> Self { - TcpAcceptor { inner } - } -} - -impl NewService for TcpAcceptor -where - T: NewService>, - T::InitError: fmt::Debug, -{ - type Request = net::TcpStream; - type Response = T::Response; - type Error = AcceptorError; - type InitError = T::InitError; - type Service = TcpAcceptorService; - type Future = TcpAcceptorResponse; - - fn new_service(&self) -> Self::Future { - TcpAcceptorResponse { - fut: self.inner.new_service(), - } - } -} - -pub(crate) struct TcpAcceptorResponse -where - T: NewService, - T::InitError: fmt::Debug, -{ - fut: T::Future, -} - -impl Future for TcpAcceptorResponse -where - T: NewService, - T::InitError: fmt::Debug, -{ - type Item = TcpAcceptorService; - type Error = T::InitError; - - fn poll(&mut self) -> Poll { - 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 { - inner: T, -} - -impl Service for TcpAcceptorService -where - T: Service>, -{ - type Request = net::TcpStream; - type Response = T::Response; - type Error = AcceptorError; - type Future = Either>; - - 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 { - inner: T, - timeout: Duration, -} - -impl AcceptorTimeout { - /// Create new `AcceptorTimeout` instance. timeout is in milliseconds. - pub fn new(timeout: u64, inner: T) -> Self { - Self { - inner, - timeout: Duration::from_millis(timeout), - } - } -} - -impl NewService for AcceptorTimeout { - type Request = T::Request; - type Response = T::Response; - type Error = AcceptorError; - type InitError = T::InitError; - type Service = AcceptorTimeoutService; - type Future = AcceptorTimeoutFut; - - fn new_service(&self) -> Self::Future { - AcceptorTimeoutFut { - fut: self.inner.new_service(), - timeout: self.timeout, - } - } -} - -#[doc(hidden)] -pub struct AcceptorTimeoutFut { - fut: T::Future, - timeout: Duration, -} - -impl Future for AcceptorTimeoutFut { - type Item = AcceptorTimeoutService; - type Error = T::InitError; - - fn poll(&mut self) -> Poll { - 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 { - inner: T, - timeout: Duration, -} - -impl Service for AcceptorTimeoutService { - type Request = T::Request; - type Response = T::Response; - type Error = AcceptorError; - type Future = AcceptorTimeoutResponse; - - 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 { - fut: T::Future, - sleep: Delay, -} - -impl Future for AcceptorTimeoutResponse { - type Item = T::Response; - type Error = AcceptorError; - - fn poll(&mut self) -> Poll { - 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 { - inner: T, - settings: ServiceConfig, -} - -impl ServerMessageAcceptor -where - H: HttpHandler, - T: NewService, -{ - pub(crate) fn new(settings: ServiceConfig, inner: T) -> Self { - ServerMessageAcceptor { inner, settings } - } -} - -impl NewService for ServerMessageAcceptor -where - H: HttpHandler, - T: NewService, -{ - type Request = ServerMessage; - type Response = (); - type Error = T::Error; - type InitError = T::InitError; - type Service = ServerMessageAcceptorService; - type Future = ServerMessageAcceptorResponse; - - fn new_service(&self) -> Self::Future { - ServerMessageAcceptorResponse { - fut: self.inner.new_service(), - settings: self.settings.clone(), - } - } -} - -pub(crate) struct ServerMessageAcceptorResponse -where - H: HttpHandler, - T: NewService, -{ - fut: T::Future, - settings: ServiceConfig, -} - -impl Future for ServerMessageAcceptorResponse -where - H: HttpHandler, - T: NewService, -{ - type Item = ServerMessageAcceptorService; - type Error = T::InitError; - - fn poll(&mut self) -> Poll { - match self.fut.poll()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(service) => Ok(Async::Ready(ServerMessageAcceptorService { - inner: service, - settings: self.settings.clone(), - })), - } - } -} - -pub(crate) struct ServerMessageAcceptorService { - inner: T, - settings: ServiceConfig, -} - -impl Service for ServerMessageAcceptorService -where - H: HttpHandler, - T: Service, -{ - type Request = ServerMessage; - type Response = (); - type Error = T::Error; - type Future = - Either, 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| proto.shutdown()); - Either::B(ok(())) - } - } - } -} - -pub(crate) struct ServerMessageAcceptorServiceFut { - fut: T::Future, -} - -impl Future for ServerMessageAcceptorServiceFut -where - T: Service, -{ - type Item = (); - type Error = T::Error; - - fn poll(&mut self) -> Poll { - match self.fut.poll()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(_) => Ok(Async::Ready(())), - } - } -} diff --git a/src/server/builder.rs b/src/server/builder.rs deleted file mode 100644 index ec6ce9923..000000000 --- a/src/server/builder.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::{fmt, net}; - -use actix_net::either::Either; -use actix_net::server::{Server, ServiceFactory}; -use actix_net::service::{NewService, NewServiceExt}; - -use super::acceptor::{ - AcceptorServiceFactory, AcceptorTimeout, ServerMessageAcceptor, TcpAcceptor, -}; -use super::error::AcceptorError; -use super::handler::IntoHttpHandler; -use super::service::HttpService; -use super::settings::{ServerSettings, ServiceConfig}; -use super::KeepAlive; - -pub(crate) trait ServiceProvider { - fn register( - &self, server: Server, lst: net::TcpListener, host: String, - addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64, - client_shutdown: u64, - ) -> Server; -} - -/// Utility type that builds complete http pipeline -pub(crate) struct HttpServiceBuilder -where - F: Fn() -> H + Send + Clone, -{ - factory: F, - acceptor: A, -} - -impl HttpServiceBuilder -where - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler, - A: AcceptorServiceFactory, - ::InitError: fmt::Debug, -{ - /// Create http service builder - pub fn new(factory: F, acceptor: A) -> Self { - Self { factory, acceptor } - } - - fn finish( - &self, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, - client_timeout: u64, client_shutdown: u64, - ) -> impl ServiceFactory { - let factory = self.factory.clone(); - let acceptor = self.acceptor.clone(); - move || { - let app = (factory)().into_handler(); - let settings = ServiceConfig::new( - app, - keep_alive, - client_timeout, - client_shutdown, - ServerSettings::new(addr, &host, false), - ); - - if secure { - Either::B(ServerMessageAcceptor::new( - settings.clone(), - TcpAcceptor::new(AcceptorTimeout::new( - client_timeout, - acceptor.create(), - )).map_err(|_| ()) - .map_init_err(|_| ()) - .and_then( - HttpService::new(settings) - .map_init_err(|_| ()) - .map_err(|_| ()), - ), - )) - } else { - Either::A(ServerMessageAcceptor::new( - settings.clone(), - TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service)) - .map_err(|_| ()) - .map_init_err(|_| ()) - .and_then( - HttpService::new(settings) - .map_init_err(|_| ()) - .map_err(|_| ()), - ), - )) - } - } - } -} - -impl ServiceProvider for HttpServiceBuilder -where - F: Fn() -> H + Send + Clone + 'static, - A: AcceptorServiceFactory, - ::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, - ), - ) - } -} diff --git a/src/server/channel.rs b/src/server/channel.rs deleted file mode 100644 index b1fef964e..000000000 --- a/src/server/channel.rs +++ /dev/null @@ -1,436 +0,0 @@ -use std::net::Shutdown; -use std::{io, mem, time}; - -use bytes::{Buf, BufMut, BytesMut}; -use futures::{Async, Future, Poll}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_timer::Delay; - -use super::error::HttpDispatchError; -use super::settings::ServiceConfig; -use super::{h1, h2, HttpHandler, IoStream}; -use error::Error; -use http::StatusCode; - -const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; - -pub(crate) enum HttpProtocol { - H1(h1::Http1Dispatcher), - H2(h2::Http2), - Unknown(ServiceConfig, T, BytesMut), - None, -} - -impl HttpProtocol { - pub(crate) fn shutdown(&mut self) { - match self { - HttpProtocol::H1(ref mut h1) => { - let io = h1.io(); - let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); - let _ = IoStream::shutdown(io, Shutdown::Both); - } - HttpProtocol::H2(ref mut h2) => h2.shutdown(), - HttpProtocol::Unknown(_, io, _) => { - let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); - let _ = IoStream::shutdown(io, Shutdown::Both); - } - HttpProtocol::None => (), - } - } -} - -enum ProtocolKind { - Http1, - Http2, -} - -#[doc(hidden)] -pub struct HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - node: Node>, - node_reg: bool, - ka_timeout: Option, -} - -impl HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - pub(crate) fn new(settings: ServiceConfig, io: T) -> HttpChannel { - let ka_timeout = settings.client_timer(); - - HttpChannel { - ka_timeout, - node_reg: false, - node: Node::new(HttpProtocol::Unknown( - settings, - io, - BytesMut::with_capacity(8192), - )), - } - } -} - -impl Drop for HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - fn drop(&mut self) { - self.node.remove(); - } -} - -impl Future for HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - type Item = (); - type Error = HttpDispatchError; - - fn poll(&mut self) -> Poll { - // keep-alive timer - if self.ka_timeout.is_some() { - match self.ka_timeout.as_mut().unwrap().poll() { - Ok(Async::Ready(_)) => { - trace!("Slow request timed out, close connection"); - let proto = mem::replace(self.node.get_mut(), HttpProtocol::None); - if let HttpProtocol::Unknown(settings, io, buf) = proto { - *self.node.get_mut() = - HttpProtocol::H1(h1::Http1Dispatcher::for_error( - settings, - io, - StatusCode::REQUEST_TIMEOUT, - self.ka_timeout.take(), - buf, - )); - return self.poll(); - } - return Ok(Async::Ready(())); - } - Ok(Async::NotReady) => (), - Err(_) => panic!("Something is really wrong"), - } - } - - if !self.node_reg { - self.node_reg = true; - let settings = match self.node.get_mut() { - HttpProtocol::H1(ref mut h1) => h1.settings().clone(), - HttpProtocol::H2(ref mut h2) => h2.settings().clone(), - HttpProtocol::Unknown(ref mut settings, _, _) => settings.clone(), - HttpProtocol::None => unreachable!(), - }; - settings.head().insert(&mut self.node); - } - - let mut is_eof = false; - let kind = match self.node.get_mut() { - HttpProtocol::H1(ref mut h1) => return h1.poll(), - HttpProtocol::H2(ref mut h2) => return h2.poll(), - HttpProtocol::Unknown(_, ref mut io, ref mut buf) => { - let mut err = None; - let mut disconnect = false; - match io.read_available(buf) { - Ok(Async::Ready((read_some, stream_closed))) => { - is_eof = stream_closed; - // Only disconnect if no data was read. - if is_eof && !read_some { - disconnect = true; - } - } - Err(e) => { - err = Some(e.into()); - } - _ => (), - } - if disconnect { - debug!("Ignored premature client disconnection"); - return Ok(Async::Ready(())); - } else if let Some(e) = err { - return Err(e); - } - - if buf.len() >= 14 { - if buf[..14] == HTTP2_PREFACE[..] { - ProtocolKind::Http2 - } else { - ProtocolKind::Http1 - } - } else { - return Ok(Async::NotReady); - } - } - HttpProtocol::None => unreachable!(), - }; - - // upgrade to specific http protocol - let proto = mem::replace(self.node.get_mut(), HttpProtocol::None); - if let HttpProtocol::Unknown(settings, io, buf) = proto { - match kind { - ProtocolKind::Http1 => { - *self.node.get_mut() = HttpProtocol::H1(h1::Http1Dispatcher::new( - settings, - io, - buf, - is_eof, - self.ka_timeout.take(), - )); - return self.poll(); - } - ProtocolKind::Http2 => { - *self.node.get_mut() = HttpProtocol::H2(h2::Http2::new( - settings, - io, - buf.freeze(), - self.ka_timeout.take(), - )); - return self.poll(); - } - } - } - unreachable!() - } -} - -#[doc(hidden)] -pub struct H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - node: Node>, - node_reg: bool, -} - -impl H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - pub(crate) fn new(settings: ServiceConfig, io: T) -> H1Channel { - H1Channel { - node_reg: false, - node: Node::new(HttpProtocol::H1(h1::Http1Dispatcher::new( - settings, - io, - BytesMut::with_capacity(8192), - false, - None, - ))), - } - } -} - -impl Drop for H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - fn drop(&mut self) { - self.node.remove(); - } -} - -impl Future for H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - type Item = (); - type Error = HttpDispatchError; - - fn poll(&mut self) -> Poll { - if !self.node_reg { - self.node_reg = true; - let settings = match self.node.get_mut() { - HttpProtocol::H1(ref mut h1) => h1.settings().clone(), - _ => unreachable!(), - }; - settings.head().insert(&mut self.node); - } - - match self.node.get_mut() { - HttpProtocol::H1(ref mut h1) => h1.poll(), - _ => unreachable!(), - } - } -} - -pub(crate) struct Node { - next: Option<*mut Node>, - prev: Option<*mut Node>, - element: T, -} - -impl Node { - fn new(element: T) -> Self { - Node { - element, - next: None, - prev: None, - } - } - - fn get_mut(&mut self) -> &mut T { - &mut self.element - } - - fn insert(&mut self, next_el: &mut Node) { - let next: *mut Node = next_el as *const _ as *mut _; - - if let Some(next2) = self.next { - unsafe { - let n = next2.as_mut().unwrap(); - n.prev = Some(next); - } - next_el.next = Some(next2 as *mut _); - } - self.next = Some(next); - - unsafe { - let next: &mut Node = &mut *next; - next.prev = Some(self as *mut _); - } - } - - fn remove(&mut self) { - let next = self.next.take(); - let prev = self.prev.take(); - - if let Some(prev) = prev { - unsafe { - prev.as_mut().unwrap().next = next; - } - } - if let Some(next) = next { - unsafe { - next.as_mut().unwrap().prev = prev; - } - } - } -} - -impl Node<()> { - pub(crate) fn head() -> Self { - Node { - next: None, - prev: None, - element: (), - } - } - - pub(crate) fn traverse)>(&self, f: F) - where - T: IoStream, - H: HttpHandler + 'static, - { - if let Some(n) = self.next.as_ref() { - unsafe { - let mut next: &mut Node> = - &mut *(n.as_ref().unwrap() as *const _ as *mut _); - loop { - f(&mut next.element); - - next = if let Some(n) = next.next.as_ref() { - &mut **n - } else { - return; - } - } - } - } - } -} - -/// Wrapper for `AsyncRead + AsyncWrite` types -pub(crate) struct WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - io: T, -} - -impl WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - pub fn new(io: T) -> Self { - WrapperStream { io } - } -} - -impl IoStream for WrapperStream -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) -> io::Result<()> { - Ok(()) - } - #[inline] - fn set_keepalive(&mut self, _: Option) -> io::Result<()> { - Ok(()) - } -} - -impl io::Read for WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - #[inline] - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.io.read(buf) - } -} - -impl io::Write for WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - self.io.write(buf) - } - #[inline] - fn flush(&mut self) -> io::Result<()> { - self.io.flush() - } -} - -impl AsyncRead for WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - #[inline] - fn read_buf(&mut self, buf: &mut B) -> Poll { - self.io.read_buf(buf) - } -} - -impl AsyncWrite for WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - #[inline] - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.io.shutdown() - } - #[inline] - fn write_buf(&mut self, buf: &mut B) -> Poll { - self.io.write_buf(buf) - } -} diff --git a/src/server/error.rs b/src/server/error.rs index 3ae9a107b..d9e1239e1 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -4,7 +4,6 @@ use std::io; use futures::{Async, Poll}; use http2; -use super::{helpers, HttpHandlerTask, Writer}; use error::{Error, ParseError}; use http::{StatusCode, Version}; @@ -89,31 +88,3 @@ impl From for HttpDispatchError { HttpDispatchError::Http2(err) } } - -pub(crate) struct ServerError(Version, StatusCode); - -impl ServerError { - pub fn err(ver: Version, status: StatusCode) -> Box { - Box::new(ServerError(ver, status)) - } -} - -impl HttpHandlerTask for ServerError { - fn poll_io(&mut self, io: &mut Writer) -> Poll { - { - 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)) - } -} diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs deleted file mode 100644 index c27a4c44a..000000000 --- a/src/server/h1writer.rs +++ /dev/null @@ -1,356 +0,0 @@ -// #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] - -use std::io::{self, Write}; - -use bytes::{BufMut, BytesMut}; -use futures::{Async, Poll}; -use tokio_io::AsyncWrite; - -use super::helpers; -use super::output::{Output, ResponseInfo, ResponseLength}; -use super::settings::ServiceConfig; -use super::Request; -use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; -use body::{Binary, Body}; -use header::ContentEncoding; -use http::header::{ - HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, -}; -use http::{Method, Version}; -use httpresponse::HttpResponse; - -const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific - -bitflags! { - struct Flags: u8 { - const STARTED = 0b0000_0001; - const UPGRADE = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const DISCONNECTED = 0b0000_1000; - } -} - -pub(crate) struct H1Writer { - flags: Flags, - stream: T, - written: u64, - headers_size: u32, - buffer: Output, - buffer_capacity: usize, - settings: ServiceConfig, -} - -impl H1Writer { - pub fn new(stream: T, settings: ServiceConfig) -> H1Writer { - 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 { - 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 Drop for H1Writer { - fn drop(&mut self) { - if let Some(bytes) = self.buffer.take_option() { - self.settings.release_bytes(bytes); - } - } -} - -impl Writer for H1Writer { - #[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 { - // prepare task - let mut info = ResponseInfo::new(req.inner.method == Method::HEAD); - self.buffer.for_server(&mut info, &req.inner, msg, encoding); - if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { - self.flags = Flags::STARTED | Flags::KEEPALIVE; - } else { - self.flags = Flags::STARTED; - } - - // Connection upgrade - let version = msg.version().unwrap_or_else(|| req.inner.version); - if msg.upgrade() { - self.flags.insert(Flags::UPGRADE); - msg.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("upgrade")); - } - // keep-alive - else if self.flags.contains(Flags::KEEPALIVE) { - if version < Version::HTTP_11 { - msg.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("keep-alive")); - } - } else if version >= Version::HTTP_11 { - msg.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("close")); - } - let body = msg.replace_body(Body::Empty); - - // render message - { - // output buffer - let mut buffer = self.buffer.as_mut(); - - let reason = msg.reason().as_bytes(); - if let Body::Binary(ref bytes) = body { - buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE - + bytes.len() - + reason.len(), - ); - } else { - buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len(), - ); - } - - // status line - helpers::write_status_line(version, msg.status().as_u16(), &mut buffer); - buffer.extend_from_slice(reason); - - // content length - match info.length { - ResponseLength::Chunked => { - buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") - } - ResponseLength::Zero => { - buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n") - } - ResponseLength::Length(len) => { - helpers::write_content_length(len, &mut buffer) - } - ResponseLength::Length64(len) => { - buffer.extend_from_slice(b"\r\ncontent-length: "); - write!(buffer.writer(), "{}", len)?; - buffer.extend_from_slice(b"\r\n"); - } - ResponseLength::None => buffer.extend_from_slice(b"\r\n"), - } - if let Some(ce) = info.content_encoding { - buffer.extend_from_slice(b"content-encoding: "); - buffer.extend_from_slice(ce.as_ref()); - buffer.extend_from_slice(b"\r\n"); - } - - // write headers - let mut pos = 0; - let mut has_date = false; - let mut remaining = buffer.remaining_mut(); - let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) }; - for (key, value) in msg.headers() { - match *key { - TRANSFER_ENCODING => continue, - CONTENT_ENCODING => if encoding != ContentEncoding::Identity { - continue; - }, - CONTENT_LENGTH => match info.length { - ResponseLength::None => (), - _ => continue, - }, - DATE => { - has_date = true; - } - _ => (), - } - - let v = value.as_ref(); - let k = key.as_str().as_bytes(); - let len = k.len() + v.len() + 4; - if len > remaining { - unsafe { - buffer.advance_mut(pos); - } - pos = 0; - buffer.reserve(len); - remaining = buffer.remaining_mut(); - unsafe { - buf = &mut *(buffer.bytes_mut() as *mut _); - } - } - - buf[pos..pos + k.len()].copy_from_slice(k); - pos += k.len(); - buf[pos..pos + 2].copy_from_slice(b": "); - pos += 2; - buf[pos..pos + v.len()].copy_from_slice(v); - pos += v.len(); - buf[pos..pos + 2].copy_from_slice(b"\r\n"); - pos += 2; - remaining -= len; - } - unsafe { - buffer.advance_mut(pos); - } - - // optimized date header, set_date writes \r\n - if !has_date { - self.settings.set_date(&mut buffer, true); - } else { - // msg eof - buffer.extend_from_slice(b"\r\n"); - } - self.headers_size = buffer.len() as u32; - } - - if let Body::Binary(bytes) = body { - self.written = bytes.len() as u64; - self.buffer.write(bytes.as_ref())?; - } else { - // capacity, makes sense only for streaming or actor - self.buffer_capacity = msg.write_buffer_capacity(); - - msg.replace_body(body); - } - Ok(WriterState::Done) - } - - fn write(&mut self, payload: &Binary) -> io::Result { - 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 { - 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()?) - } - } -} diff --git a/src/server/h2.rs b/src/server/h2.rs deleted file mode 100644 index 27ae47851..000000000 --- a/src/server/h2.rs +++ /dev/null @@ -1,453 +0,0 @@ -use std::collections::VecDeque; -use std::io::{Read, Write}; -use std::net::SocketAddr; -use std::rc::Rc; -use std::time::Instant; -use std::{cmp, io, mem}; - -use bytes::{Buf, Bytes}; -use futures::{Async, Future, Poll, Stream}; -use http2::server::{self, Connection, Handshake, SendResponse}; -use http2::{Reason, RecvStream}; -use modhttp::request::Parts; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_timer::Delay; - -use error::{Error, PayloadError}; -use extensions::Extensions; -use http::{StatusCode, Version}; -use payload::{Payload, PayloadStatus, PayloadWriter}; -use uri::Url; - -use super::error::{HttpDispatchError, ServerError}; -use super::h2writer::H2Writer; -use super::input::PayloadType; -use super::settings::ServiceConfig; -use super::{HttpHandler, HttpHandlerTask, IoStream, Writer}; - -bitflags! { - struct Flags: u8 { - const DISCONNECTED = 0b0000_0010; - } -} - -/// HTTP/2 Transport -pub(crate) struct Http2 -where - T: AsyncRead + AsyncWrite + 'static, - H: HttpHandler + 'static, -{ - flags: Flags, - settings: ServiceConfig, - addr: Option, - state: State>, - tasks: VecDeque>, - keepalive_timer: Option, - extensions: Option>, -} - -enum State { - Handshake(Handshake), - Connection(Connection), - Empty, -} - -impl Http2 -where - T: IoStream + 'static, - H: HttpHandler + 'static, -{ - pub fn new( - settings: ServiceConfig, io: T, buf: Bytes, keepalive_timer: Option, - ) -> Self { - let addr = io.peer_addr(); - let extensions = io.extensions(); - Http2 { - flags: Flags::empty(), - tasks: VecDeque::new(), - state: State::Handshake(server::handshake(IoWrapper { - unread: if buf.is_empty() { None } else { Some(buf) }, - inner: io, - })), - addr, - settings, - extensions, - keepalive_timer, - } - } - - pub(crate) fn shutdown(&mut self) { - self.state = State::Empty; - self.tasks.clear(); - self.keepalive_timer.take(); - } - - pub fn settings(&self) -> &ServiceConfig { - &self.settings - } - - pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { - // server - if let State::Connection(ref mut conn) = self.state { - // keep-alive timer - if let Some(ref mut timeout) = self.keepalive_timer { - match timeout.poll() { - Ok(Async::Ready(_)) => { - trace!("Keep-alive timeout, close connection"); - return Ok(Async::Ready(())); - } - Ok(Async::NotReady) => (), - Err(_) => unreachable!(), - } - } - - loop { - let mut not_ready = true; - let disconnected = self.flags.contains(Flags::DISCONNECTED); - - // check in-flight connections - for item in &mut self.tasks { - // read payload - if !disconnected { - item.poll_payload(); - } - - if !item.flags.contains(EntryFlags::EOF) { - if disconnected { - item.flags.insert(EntryFlags::EOF); - } else { - let retry = item.payload.need_read() == PayloadStatus::Read; - loop { - match item.task.poll_io(&mut item.stream) { - Ok(Async::Ready(ready)) => { - if ready { - item.flags.insert( - EntryFlags::EOF | EntryFlags::FINISHED, - ); - } else { - item.flags.insert(EntryFlags::EOF); - } - not_ready = false; - } - Ok(Async::NotReady) => { - if item.payload.need_read() - == PayloadStatus::Read - && !retry - { - continue; - } - } - Err(err) => { - error!("Unhandled error: {}", err); - item.flags.insert( - EntryFlags::EOF - | EntryFlags::ERROR - | EntryFlags::WRITE_DONE, - ); - item.stream.reset(Reason::INTERNAL_ERROR); - } - } - break; - } - } - } - - if item.flags.contains(EntryFlags::EOF) - && !item.flags.contains(EntryFlags::FINISHED) - { - match item.task.poll_completed() { - Ok(Async::NotReady) => (), - Ok(Async::Ready(_)) => { - item.flags.insert( - EntryFlags::FINISHED | EntryFlags::WRITE_DONE, - ); - } - Err(err) => { - item.flags.insert( - EntryFlags::ERROR - | EntryFlags::WRITE_DONE - | EntryFlags::FINISHED, - ); - error!("Unhandled error: {}", err); - } - } - } - - if item.flags.contains(EntryFlags::FINISHED) - && !item.flags.contains(EntryFlags::WRITE_DONE) - && !disconnected - { - match item.stream.poll_completed(false) { - Ok(Async::NotReady) => (), - Ok(Async::Ready(_)) => { - not_ready = false; - item.flags.insert(EntryFlags::WRITE_DONE); - } - Err(_) => { - item.flags.insert(EntryFlags::ERROR); - } - } - } - } - - // cleanup finished tasks - while !self.tasks.is_empty() { - if self.tasks[0].flags.contains(EntryFlags::FINISHED) - && self.tasks[0].flags.contains(EntryFlags::WRITE_DONE) - || self.tasks[0].flags.contains(EntryFlags::ERROR) - { - self.tasks.pop_front(); - } else { - break; - } - } - - // get request - if !self.flags.contains(Flags::DISCONNECTED) { - match conn.poll() { - Ok(Async::Ready(None)) => { - not_ready = false; - self.flags.insert(Flags::DISCONNECTED); - for entry in &mut self.tasks { - entry.task.disconnected() - } - } - Ok(Async::Ready(Some((req, resp)))) => { - not_ready = false; - let (parts, body) = req.into_parts(); - - // stop keepalive timer - self.keepalive_timer.take(); - - self.tasks.push_back(Entry::new( - parts, - body, - resp, - self.addr, - self.settings.clone(), - self.extensions.clone(), - )); - } - Ok(Async::NotReady) => { - // start keep-alive timer - if self.tasks.is_empty() { - if self.settings.keep_alive_enabled() { - if self.keepalive_timer.is_none() { - if let Some(ka) = self.settings.keep_alive() { - trace!("Start keep-alive timer"); - let mut timeout = - Delay::new(Instant::now() + ka); - // register timeout - let _ = timeout.poll(); - self.keepalive_timer = Some(timeout); - } - } - } else { - // keep-alive disable, drop connection - return conn.poll_close().map_err(|e| e.into()); - } - } else { - // keep-alive unset, rely on operating system - return Ok(Async::NotReady); - } - } - Err(err) => { - trace!("Connection error: {}", err); - self.flags.insert(Flags::DISCONNECTED); - for entry in &mut self.tasks { - entry.task.disconnected() - } - self.keepalive_timer.take(); - } - } - } - - if not_ready { - if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) - { - return conn.poll_close().map_err(|e| e.into()); - } else { - return Ok(Async::NotReady); - } - } - } - } - - // handshake - self.state = if let State::Handshake(ref mut handshake) = self.state { - match handshake.poll() { - Ok(Async::Ready(conn)) => State::Connection(conn), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => { - trace!("Error handling connection: {}", err); - return Err(err.into()); - } - } - } else { - mem::replace(&mut self.state, State::Empty) - }; - - self.poll() - } -} - -bitflags! { - struct EntryFlags: u8 { - const EOF = 0b0000_0001; - const REOF = 0b0000_0010; - const ERROR = 0b0000_0100; - const FINISHED = 0b0000_1000; - const WRITE_DONE = 0b0001_0000; - } -} - -enum EntryPipe { - Task(H::Task), - Error(Box), -} - -impl EntryPipe { - 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 { - 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 { - task: EntryPipe, - payload: PayloadType, - recv: RecvStream, - stream: H2Writer, - flags: EntryFlags, -} - -impl Entry { - fn new( - parts: Parts, recv: RecvStream, resp: SendResponse, - addr: Option, settings: ServiceConfig, - extensions: Option>, - ) -> Entry - 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 { - unread: Option, - inner: T, -} - -impl Read for IoWrapper { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - 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 Write for IoWrapper { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.inner.write(buf) - } - fn flush(&mut self) -> io::Result<()> { - self.inner.flush() - } -} - -impl AsyncRead for IoWrapper { - unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { - self.inner.prepare_uninitialized_buffer(buf) - } -} - -impl AsyncWrite for IoWrapper { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.inner.shutdown() - } - fn write_buf(&mut self, buf: &mut B) -> Poll { - self.inner.write_buf(buf) - } -} diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs deleted file mode 100644 index 51d4dce6f..000000000 --- a/src/server/h2writer.rs +++ /dev/null @@ -1,259 +0,0 @@ -#![cfg_attr( - feature = "cargo-clippy", - allow(clippy::redundant_field_names) -)] - -use std::{cmp, io}; - -use bytes::{Bytes, BytesMut}; -use futures::{Async, Poll}; -use http2::server::SendResponse; -use http2::{Reason, SendStream}; -use modhttp::Response; - -use super::helpers; -use super::message::Request; -use super::output::{Output, ResponseInfo, ResponseLength}; -use super::settings::ServiceConfig; -use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; -use body::{Binary, Body}; -use header::ContentEncoding; -use http::header::{ - HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, -}; -use http::{HttpTryFrom, Method, Version}; -use httpresponse::HttpResponse; - -const CHUNK_SIZE: usize = 16_384; - -bitflags! { - struct Flags: u8 { - const STARTED = 0b0000_0001; - const DISCONNECTED = 0b0000_0010; - const EOF = 0b0000_0100; - const RESERVED = 0b0000_1000; - } -} - -pub(crate) struct H2Writer { - respond: SendResponse, - stream: Option>, - flags: Flags, - written: u64, - buffer: Output, - buffer_capacity: usize, - settings: ServiceConfig, -} - -impl H2Writer { - pub fn new(respond: SendResponse, settings: ServiceConfig) -> H2Writer { - 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 Drop for H2Writer { - fn drop(&mut self) { - self.settings.release_bytes(self.buffer.take()); - } -} - -impl Writer for H2Writer { - 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 { - // prepare response - self.flags.insert(Flags::STARTED); - let mut info = ResponseInfo::new(req.inner.method == Method::HEAD); - self.buffer.for_server(&mut info, &req.inner, msg, encoding); - - let mut has_date = false; - let mut resp = Response::new(()); - *resp.status_mut() = msg.status(); - *resp.version_mut() = Version::HTTP_2; - for (key, value) in msg.headers().iter() { - match *key { - // http2 specific - CONNECTION | TRANSFER_ENCODING => continue, - CONTENT_ENCODING => if encoding != ContentEncoding::Identity { - continue; - }, - CONTENT_LENGTH => match info.length { - ResponseLength::None => (), - _ => continue, - }, - DATE => has_date = true, - _ => (), - } - resp.headers_mut().append(key, value.clone()); - } - - // set date header - if !has_date { - let mut bytes = BytesMut::with_capacity(29); - self.settings.set_date(&mut bytes, false); - resp.headers_mut() - .insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); - } - - // content length - match info.length { - ResponseLength::Zero => { - resp.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); - self.flags.insert(Flags::EOF); - } - ResponseLength::Length(len) => { - let mut val = BytesMut::new(); - helpers::convert_usize(len, &mut val); - let l = val.len(); - resp.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(), - ); - } - ResponseLength::Length64(len) => { - let l = format!("{}", len); - resp.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap()); - } - _ => (), - } - if let Some(ce) = info.content_encoding { - resp.headers_mut() - .insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap()); - } - - trace!("Response: {:?}", resp); - - match self - .respond - .send_response(resp, self.flags.contains(Flags::EOF)) - { - Ok(stream) => self.stream = Some(stream), - Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")), - } - - let body = msg.replace_body(Body::Empty); - if let Body::Binary(bytes) = body { - if bytes.is_empty() { - Ok(WriterState::Done) - } else { - self.flags.insert(Flags::EOF); - self.buffer.write(bytes.as_ref())?; - if let Some(ref mut stream) = self.stream { - self.flags.insert(Flags::RESERVED); - stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); - } - Ok(WriterState::Pause) - } - } else { - msg.replace_body(body); - self.buffer_capacity = msg.write_buffer_capacity(); - Ok(WriterState::Done) - } - } - - fn write(&mut self, payload: &Binary) -> io::Result { - 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 { - 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(())) - } -} diff --git a/src/server/handler.rs b/src/server/handler.rs deleted file mode 100644 index 33e50ac34..000000000 --- a/src/server/handler.rs +++ /dev/null @@ -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; -} - -impl HttpHandler for Box>> { - type Task = Box; - - fn handle(&self, req: Request) -> Result, 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; - - /// Connection is disconnected - fn disconnected(&mut self) {} -} - -impl HttpHandlerTask for Box { - fn poll_io(&mut self, io: &mut Writer) -> Poll { - self.as_mut().poll_io(io) - } -} - -pub(super) struct HttpHandlerTaskFut { - task: T, -} - -impl HttpHandlerTaskFut { - pub(crate) fn new(task: T) -> Self { - Self { task } - } -} - -impl Future for HttpHandlerTaskFut { - 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 IntoHttpHandler for T { - type Handler = T; - - fn into_handler(self) -> Self::Handler { - self - } -} - -impl IntoHttpHandler for Vec { - type Handler = VecHttpHandler; - - fn into_handler(self) -> Self::Handler { - VecHttpHandler(self.into_iter().map(|item| item.into_handler()).collect()) - } -} - -#[doc(hidden)] -pub struct VecHttpHandler(Vec); - -impl HttpHandler for VecHttpHandler { - type Task = H::Task; - - fn handle(&self, mut req: Request) -> Result { - 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 { - $( - 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 { - 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) -); diff --git a/src/server/http.rs b/src/server/http.rs deleted file mode 100644 index 6a7790c13..000000000 --- a/src/server/http.rs +++ /dev/null @@ -1,584 +0,0 @@ -use std::{fmt, io, mem, net}; - -use actix::{Addr, System}; -use actix_net::server::Server; -use actix_net::service::NewService; -use actix_net::ssl; - -use net2::TcpBuilder; -use num_cpus; - -#[cfg(feature = "tls")] -use native_tls::TlsAcceptor; - -#[cfg(any(feature = "alpn", feature = "ssl"))] -use openssl::ssl::SslAcceptorBuilder; - -#[cfg(feature = "rust-tls")] -use rustls::ServerConfig; - -use super::acceptor::{AcceptorServiceFactory, DefaultAcceptor}; -use super::builder::{HttpServiceBuilder, ServiceProvider}; -use super::{IntoHttpHandler, KeepAlive}; - -struct Socket { - scheme: &'static str, - lst: net::TcpListener, - addr: net::SocketAddr, - handler: Box, -} - -/// 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 -where - H: IntoHttpHandler + 'static, - F: Fn() -> H + Send + Clone, -{ - pub(super) factory: F, - pub(super) host: Option, - 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, -} - -impl HttpServer -where - H: IntoHttpHandler + 'static, - F: Fn() -> H + Send + Clone + 'static, -{ - /// Create new http server with application factory - pub fn new(factory: F) -> HttpServer { - 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>(mut self, val: T) -> Self { - self.keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } - - /// Set server connection shutdown timeout in milliseconds. - /// - /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete - /// within this time, the request is dropped. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_shutdown(mut self, val: u64) -> Self { - self.client_shutdown = val; - self - } - - /// Set server host name. - /// - /// Host name is used by application router aa a hostname for url - /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. - /// html#method.host) documentation for more information. - pub fn server_hostname(mut self, val: String) -> Self { - self.host = Some(val); - self - } - - /// Stop actix system. - /// - /// `SystemExit` message stops currently running system. - pub fn system_exit(mut self) -> Self { - self.exit = true; - self - } - - /// Disable signal handling - pub fn disable_signals(mut self) -> Self { - self.no_signals = true; - self - } - - /// Timeout for graceful workers shutdown. - /// - /// After receiving a stop signal, workers have this much time to finish - /// serving requests. Workers still alive after the timeout are force - /// dropped. - /// - /// By default shutdown timeout sets to 30 seconds. - pub fn shutdown_timeout(mut self, sec: u16) -> Self { - self.shutdown_timeout = sec; - self - } - - /// Disable `HTTP/2` support - // #[doc(hidden)] - // #[deprecated( - // since = "0.7.4", - // note = "please use acceptor service with proper ServerFlags parama" - // )] - pub fn no_http2(mut self) -> Self { - self.no_http2 = true; - self - } - - /// Get addresses of bound sockets. - pub fn addrs(&self) -> Vec { - 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(mut self, lst: net::TcpListener, acceptor: A) -> Self - where - A: AcceptorServiceFactory, - ::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 { - 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(mut self, addr: S) -> io::Result { - let sockets = self.bind2(addr)?; - - for lst in sockets { - self = self.listen(lst); - } - - Ok(self) - } - - /// Start listening for incoming connections with supplied acceptor. - #[doc(hidden)] - #[cfg_attr( - feature = "cargo-clippy", - allow(clippy::needless_pass_by_value) - )] - pub fn bind_with(mut self, addr: S, acceptor: A) -> io::Result - where - S: net::ToSocketAddrs, - A: AcceptorServiceFactory, - ::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( - &self, addr: S, - ) -> io::Result> { - 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( - self, addr: S, acceptor: TlsAcceptor, - ) -> io::Result { - 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(self, addr: S, builder: SslAcceptorBuilder) -> io::Result - 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( - self, addr: S, builder: ServerConfig, - ) -> io::Result { - 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 + Send + Clone> HttpServer { - /// Start listening for incoming connections. - /// - /// This method starts number of http workers in separate threads. - /// For each address this method starts separate thread which does - /// `accept()` in a loop. - /// - /// This methods panics if no socket addresses get bound. - /// - /// This method requires to run within properly configured `Actix` system. - /// - /// ```rust - /// extern crate actix_web; - /// use actix_web::{actix, server, App, HttpResponse}; - /// - /// fn main() { - /// let sys = actix::System::new("example"); // <- create Actix system - /// - /// server::new(|| App::new().resource("/", |r| r.h(|_: &_| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0") - /// .expect("Can not bind to 127.0.0.1:0") - /// .start(); - /// # actix::System::current().stop(); - /// sys.run(); // <- Run actix system, this method starts all async processes - /// } - /// ``` - pub fn start(mut self) -> Addr { - 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 { - 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)?) -} diff --git a/src/server/incoming.rs b/src/server/incoming.rs deleted file mode 100644 index b13bba2a7..000000000 --- a/src/server/incoming.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Support for `Stream`, 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 Message for WrapperStream { - type Result = (); -} - -impl HttpServer -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(self, stream: S, secure: bool) - where - S: Stream + '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 { - settings: ServiceConfig, -} - -impl Actor for HttpIncoming { - type Context = Context; -} - -impl Handler> for HttpIncoming -where - T: AsyncRead + AsyncWrite, - H: HttpHandler, -{ - type Result = (); - - fn handle(&mut self, msg: WrapperStream, _: &mut Context) -> Self::Result { - Arbiter::spawn(HttpChannel::new(self.settings.clone(), msg).map_err(|_| ())); - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index ce3f2fbf6..7d64a6e20 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -117,33 +117,20 @@ use tokio_tcp::TcpStream; pub use actix_net::server::{PauseServer, ResumeServer, StopServer}; -pub(crate) mod acceptor; -pub(crate) mod builder; -mod channel; mod error; -pub(crate) mod h1; +// pub(crate) mod h1; #[doc(hidden)] pub mod h1codec; #[doc(hidden)] pub mod h1decoder; -mod h1writer; -mod h2; -mod h2writer; -mod handler; pub(crate) mod helpers; -mod http; -pub(crate) mod incoming; pub(crate) mod input; pub(crate) mod message; pub(crate) mod output; -pub(crate) mod service; +// pub(crate) mod service; pub(crate) mod settings; -mod ssl; -pub use self::handler::*; -pub use self::http::HttpServer; pub use self::message::Request; -pub use self::ssl::*; pub use self::error::{AcceptorError, HttpDispatchError}; pub use self::settings::ServerSettings; @@ -151,14 +138,11 @@ pub use self::settings::ServerSettings; #[doc(hidden)] pub mod h1disp; -#[doc(hidden)] -pub use self::acceptor::AcceptorTimeout; - #[doc(hidden)] pub use self::settings::{ServiceConfig, ServiceConfigBuilder}; -#[doc(hidden)] -pub use self::service::{H1Service, HttpService, StreamConfiguration}; +//#[doc(hidden)] +//pub use self::service::{H1Service, HttpService, StreamConfiguration}; #[doc(hidden)] pub use self::helpers::write_content_length; @@ -174,53 +158,11 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; const LW_BUFFER_SIZE: usize = 4096; const HW_BUFFER_SIZE: usize = 32_768; -/// Create new http server with application factory. -/// -/// This is shortcut for `server::HttpServer::new()` method. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{actix, server, App, HttpResponse}; -/// -/// fn main() { -/// let sys = actix::System::new("example"); // <- create Actix system -/// -/// server::new( -/// || App::new() -/// .resource("/", |r| r.f(|_| HttpResponse::Ok()))) -/// .bind("127.0.0.1:59090").unwrap() -/// .start(); -/// -/// # actix::System::current().stop(); -/// sys.run(); -/// } -/// ``` -pub fn new(factory: F) -> HttpServer -where - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler + 'static, -{ - HttpServer::new(factory) -} - -#[doc(hidden)] -bitflags! { - ///Flags that can be used to configure HTTP Server. - pub struct ServerFlags: u8 { - ///Use HTTP1 protocol - const HTTP1 = 0b0000_0001; - ///Use HTTP2 protocol - const HTTP2 = 0b0000_0010; - } -} - #[derive(Debug, PartialEq, Clone, Copy)] /// Server keep-alive setting pub enum KeepAlive { /// Keep alive in seconds Timeout(usize), - /// Use `SO_KEEPALIVE` socket option, value in seconds - Tcp(usize), /// Relay on OS to shutdown tcp connection Os, /// Disabled @@ -243,41 +185,9 @@ impl From> for KeepAlive { } } -#[doc(hidden)] -#[derive(Debug)] -pub enum WriterState { - Done, - Pause, -} - -#[doc(hidden)] -/// Stream writer -pub trait Writer { - /// number of bytes written to the stream - fn written(&self) -> u64; - - #[doc(hidden)] - fn set_date(&mut self); - - #[doc(hidden)] - fn buffer(&mut self) -> &mut BytesMut; - - fn start( - &mut self, req: &Request, resp: &mut HttpResponse, encoding: ContentEncoding, - ) -> io::Result; - - fn write(&mut self, payload: &Binary) -> io::Result; - - fn write_eof(&mut self) -> io::Result; - - 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 { None @@ -289,54 +199,10 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static { fn set_linger(&mut self, dur: Option) -> io::Result<()>; fn set_keepalive(&mut self, dur: Option) -> io::Result<()>; - - fn read_available(&mut self, buf: &mut BytesMut) -> Poll<(bool, bool), io::Error> { - let mut read_some = false; - loop { - if buf.remaining_mut() < LW_BUFFER_SIZE { - buf.reserve(HW_BUFFER_SIZE); - } - - let read = unsafe { self.read(buf.bytes_mut()) }; - match read { - Ok(n) => { - if n == 0 { - return Ok(Async::Ready((read_some, true))); - } else { - read_some = true; - unsafe { - buf.advance_mut(n); - } - } - } - Err(e) => { - return if e.kind() == io::ErrorKind::WouldBlock { - if read_some { - Ok(Async::Ready((read_some, false))) - } else { - Ok(Async::NotReady) - } - } else { - Err(e) - }; - } - } - } - } - - /// Extra io stream extensions - fn extensions(&self) -> Option> { - None - } } #[cfg(all(unix, feature = "uds"))] impl IoStream for ::tokio_uds::UnixStream { - #[inline] - fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { - ::tokio_uds::UnixStream::shutdown(self, how) - } - #[inline] fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> { Ok(()) @@ -354,11 +220,6 @@ impl IoStream for ::tokio_uds::UnixStream { } impl IoStream for TcpStream { - #[inline] - fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { - TcpStream::shutdown(self, how) - } - #[inline] fn peer_addr(&self) -> Option { TcpStream::peer_addr(self).ok() diff --git a/src/server/output.rs b/src/server/output.rs index 1da7e9025..143ba4029 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -254,7 +254,7 @@ impl Output { } return; } - Body::Streaming(_) | Body::Actor(_) => { + Body::Streaming(_) => { if resp.upgrade() { if version == Version::HTTP_2 { error!("Connection upgrade is forbidden for HTTP/2"); diff --git a/src/server/settings.rs b/src/server/settings.rs index 9df1e457f..f21283590 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -15,7 +15,6 @@ use time; use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; -use super::channel::Node; use super::message::{Request, RequestPool}; use super::KeepAlive; use body::Body; @@ -128,35 +127,33 @@ impl ServerSettings { const DATE_VALUE_LENGTH: usize = 29; /// Http service configuration -pub struct ServiceConfig(Rc>); +pub struct ServiceConfig(Rc); -struct Inner { - handler: H, +struct Inner { keep_alive: Option, client_timeout: u64, client_shutdown: u64, ka_enabled: bool, bytes: Rc, messages: &'static RequestPool, - node: RefCell>, date: UnsafeCell<(bool, Date)>, } -impl Clone for ServiceConfig { +impl Clone for ServiceConfig { fn clone(&self) -> Self { ServiceConfig(self.0.clone()) } } -impl ServiceConfig { +impl ServiceConfig { /// Create instance of `ServiceConfig` pub(crate) fn new( - handler: H, keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, + keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, settings: ServerSettings, - ) -> ServiceConfig { + ) -> ServiceConfig { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), - KeepAlive::Os | KeepAlive::Tcp(_) => (0, true), + KeepAlive::Os => (0, true), KeepAlive::Disabled => (0, false), }; let keep_alive = if ka_enabled && keep_alive > 0 { @@ -166,29 +163,19 @@ impl ServiceConfig { }; ServiceConfig(Rc::new(Inner { - handler, keep_alive, ka_enabled, client_timeout, client_shutdown, bytes: Rc::new(SharedBytesPool::new()), messages: RequestPool::pool(settings), - node: RefCell::new(Node::head()), date: UnsafeCell::new((false, Date::new())), })) } /// Create worker settings builder. - pub fn build(handler: H) -> ServiceConfigBuilder { - ServiceConfigBuilder::new(handler) - } - - pub(crate) fn head(&self) -> RefMut> { - self.0.node.borrow_mut() - } - - pub(crate) fn handler(&self) -> &H { - &self.0.handler + pub fn build() -> ServiceConfigBuilder { + ServiceConfigBuilder::new() } #[inline] @@ -226,7 +213,7 @@ impl ServiceConfig { } } -impl ServiceConfig { +impl ServiceConfig { #[inline] /// Client timeout for first request. pub fn client_timer(&self) -> Option { @@ -329,8 +316,7 @@ impl ServiceConfig { /// /// This type can be used to construct an instance of `ServiceConfig` through a /// builder-like pattern. -pub struct ServiceConfigBuilder { - handler: H, +pub struct ServiceConfigBuilder { keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, @@ -339,11 +325,10 @@ pub struct ServiceConfigBuilder { secure: bool, } -impl ServiceConfigBuilder { +impl ServiceConfigBuilder { /// Create instance of `ServiceConfigBuilder` - pub fn new(handler: H) -> ServiceConfigBuilder { + pub fn new() -> ServiceConfigBuilder { ServiceConfigBuilder { - handler, keep_alive: KeepAlive::Timeout(5), client_timeout: 5000, client_shutdown: 5000, @@ -426,12 +411,11 @@ impl ServiceConfigBuilder { } /// Finish service configuration and create `ServiceConfig` object. - pub fn finish(self) -> ServiceConfig { + pub fn finish(self) -> ServiceConfig { let settings = ServerSettings::new(self.addr, &self.host, self.secure); let client_shutdown = if self.secure { self.client_shutdown } else { 0 }; ServiceConfig::new( - self.handler, self.keep_alive, self.client_timeout, client_shutdown, diff --git a/src/server/ssl/mod.rs b/src/server/ssl/mod.rs deleted file mode 100644 index c09573fe3..000000000 --- a/src/server/ssl/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -#[cfg(any(feature = "alpn", feature = "ssl"))] -mod openssl; -#[cfg(any(feature = "alpn", feature = "ssl"))] -pub use self::openssl::{openssl_acceptor_with_flags, OpensslAcceptor}; - -#[cfg(feature = "tls")] -mod nativetls; - -#[cfg(feature = "rust-tls")] -mod rustls; -#[cfg(feature = "rust-tls")] -pub use self::rustls::RustlsAcceptor; diff --git a/src/server/ssl/nativetls.rs b/src/server/ssl/nativetls.rs deleted file mode 100644 index a9797ffb3..000000000 --- a/src/server/ssl/nativetls.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::net::{Shutdown, SocketAddr}; -use std::{io, time}; - -use actix_net::ssl::TlsStream; - -use server::IoStream; - -impl IoStream for TlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = self.get_mut().shutdown(); - Ok(()) - } - - #[inline] - fn peer_addr(&self) -> Option { - self.get_ref().get_ref().peer_addr() - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().get_mut().set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_linger(dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_keepalive(dur) - } -} diff --git a/src/server/ssl/openssl.rs b/src/server/ssl/openssl.rs deleted file mode 100644 index 9d370f8be..000000000 --- a/src/server/ssl/openssl.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::net::{Shutdown, SocketAddr}; -use std::{io, time}; - -use actix_net::ssl; -use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_openssl::SslStream; - -use server::{IoStream, ServerFlags}; - -/// Support `SSL` connections via openssl package -/// -/// `ssl` feature enables `OpensslAcceptor` type -pub struct OpensslAcceptor { - _t: ssl::OpensslAcceptor, -} - -impl OpensslAcceptor { - /// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support. - pub fn new(builder: SslAcceptorBuilder) -> io::Result> { - OpensslAcceptor::with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2) - } - - /// Create `OpensslAcceptor` with custom server flags. - pub fn with_flags( - builder: SslAcceptorBuilder, flags: ServerFlags, - ) -> io::Result> { - let acceptor = openssl_acceptor_with_flags(builder, flags)?; - - Ok(ssl::OpensslAcceptor::new(acceptor)) - } -} - -/// Configure `SslAcceptorBuilder` with custom server flags. -pub fn openssl_acceptor_with_flags( - mut builder: SslAcceptorBuilder, flags: ServerFlags, -) -> io::Result { - let mut protos = Vec::new(); - if flags.contains(ServerFlags::HTTP1) { - protos.extend(b"\x08http/1.1"); - } - if flags.contains(ServerFlags::HTTP2) { - protos.extend(b"\x02h2"); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(AlpnError::NOACK) - } - }); - } - - if !protos.is_empty() { - builder.set_alpn_protos(&protos)?; - } - - Ok(builder.build()) -} - -impl IoStream for SslStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = self.get_mut().shutdown(); - Ok(()) - } - - #[inline] - fn peer_addr(&self) -> Option { - self.get_ref().get_ref().peer_addr() - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().get_mut().set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_linger(dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_keepalive(dur) - } -} diff --git a/src/server/ssl/rustls.rs b/src/server/ssl/rustls.rs deleted file mode 100644 index a53a53a98..000000000 --- a/src/server/ssl/rustls.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::net::{Shutdown, SocketAddr}; -use std::{io, time}; - -use actix_net::ssl; //::RustlsAcceptor; -use rustls::{ClientSession, ServerConfig, ServerSession}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_rustls::TlsStream; - -use server::{IoStream, ServerFlags}; - -/// Support `SSL` connections via rustls package -/// -/// `rust-tls` feature enables `RustlsAcceptor` type -pub struct RustlsAcceptor { - _t: ssl::RustlsAcceptor, -} - -impl RustlsAcceptor { - /// Create `RustlsAcceptor` with custom server flags. - pub fn with_flags( - mut config: ServerConfig, flags: ServerFlags, - ) -> ssl::RustlsAcceptor { - let mut protos = Vec::new(); - if flags.contains(ServerFlags::HTTP2) { - protos.push("h2".to_string()); - } - if flags.contains(ServerFlags::HTTP1) { - protos.push("http/1.1".to_string()); - } - if !protos.is_empty() { - config.set_protocols(&protos); - } - - ssl::RustlsAcceptor::new(config) - } -} - -impl IoStream for TlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = ::shutdown(self); - Ok(()) - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().0.set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_linger(dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_keepalive(dur) - } -} - -impl IoStream for TlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = ::shutdown(self); - Ok(()) - } - - #[inline] - fn peer_addr(&self) -> Option { - self.get_ref().0.peer_addr() - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().0.set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_linger(dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_keepalive(dur) - } -} diff --git a/src/with.rs b/src/with.rs deleted file mode 100644 index c6d54dee8..000000000 --- a/src/with.rs +++ /dev/null @@ -1,383 +0,0 @@ -use futures::{Async, Future, Poll}; -use std::marker::PhantomData; -use std::rc::Rc; - -use error::Error; -use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; - -trait FnWith: 'static { - fn call_with(self: &Self, T) -> R; -} - -impl R + 'static> FnWith for F { - fn call_with(self: &Self, arg: T) -> R { - (*self)(arg) - } -} - -#[doc(hidden)] -pub trait WithFactory: 'static -where - T: FromRequest, - R: Responder, -{ - fn create(self) -> With; - - fn create_with_config(self, T::Config) -> With; -} - -#[doc(hidden)] -pub trait WithAsyncFactory: 'static -where - T: FromRequest, - R: Future, - I: Responder, - E: Into, -{ - fn create(self) -> WithAsync; - - fn create_with_config(self, T::Config) -> WithAsync; -} - -#[doc(hidden)] -pub struct With -where - T: FromRequest, - S: 'static, -{ - hnd: Rc>, - cfg: Rc, - _s: PhantomData, -} - -impl With -where - T: FromRequest, - S: 'static, -{ - pub fn new R + 'static>(f: F, cfg: T::Config) -> Self { - With { - cfg: Rc::new(cfg), - hnd: Rc::new(f), - _s: PhantomData, - } - } -} - -impl Handler for With -where - R: Responder + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Result = AsyncResult; - - fn handle(&self, req: &HttpRequest) -> Self::Result { - let mut fut = WithHandlerFut { - req: req.clone(), - started: false, - hnd: Rc::clone(&self.hnd), - cfg: self.cfg.clone(), - fut1: None, - fut2: None, - }; - - match fut.poll() { - Ok(Async::Ready(resp)) => AsyncResult::ok(resp), - Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), - Err(e) => AsyncResult::err(e), - } - } -} - -struct WithHandlerFut -where - R: Responder, - T: FromRequest + 'static, - S: 'static, -{ - started: bool, - hnd: Rc>, - cfg: Rc, - req: HttpRequest, - fut1: Option>>, - fut2: Option>>, -} - -impl Future for WithHandlerFut -where - R: Responder + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut2 { - return fut.poll(); - } - - let item = if !self.started { - self.started = true; - let reply = T::from_request(&self.req, self.cfg.as_ref()).into(); - match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.fut1 = Some(fut); - return self.poll(); - } - } - } else { - match self.fut1.as_mut().unwrap().poll()? { - Async::Ready(item) => item, - Async::NotReady => return Ok(Async::NotReady), - } - }; - - let item = match self.hnd.as_ref().call_with(item).respond_to(&self.req) { - Ok(item) => item.into(), - Err(e) => return Err(e.into()), - }; - - match item.into() { - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => { - self.fut2 = Some(fut); - self.poll() - } - } - } -} - -#[doc(hidden)] -pub struct WithAsync -where - R: Future, - I: Responder, - E: Into, - T: FromRequest, - S: 'static, -{ - hnd: Rc>, - cfg: Rc, - _s: PhantomData, -} - -impl WithAsync -where - R: Future, - I: Responder, - E: Into, - T: FromRequest, - S: 'static, -{ - pub fn new R + 'static>(f: F, cfg: T::Config) -> Self { - WithAsync { - cfg: Rc::new(cfg), - hnd: Rc::new(f), - _s: PhantomData, - } - } -} - -impl Handler for WithAsync -where - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Result = AsyncResult; - - fn handle(&self, req: &HttpRequest) -> Self::Result { - let mut fut = WithAsyncHandlerFut { - req: req.clone(), - started: false, - hnd: Rc::clone(&self.hnd), - cfg: Rc::clone(&self.cfg), - fut1: None, - fut2: None, - fut3: None, - }; - - match fut.poll() { - Ok(Async::Ready(resp)) => AsyncResult::ok(resp), - Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), - Err(e) => AsyncResult::err(e), - } - } -} - -struct WithAsyncHandlerFut -where - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - S: 'static, -{ - started: bool, - hnd: Rc>, - cfg: Rc, - req: HttpRequest, - fut1: Option>>, - fut2: Option, - fut3: Option>>, -} - -impl Future for WithAsyncHandlerFut -where - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut3 { - return fut.poll(); - } - - if self.fut2.is_some() { - return match self.fut2.as_mut().unwrap().poll() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(r)) => match r.respond_to(&self.req) { - Ok(r) => match r.into().into() { - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => { - self.fut3 = Some(fut); - self.poll() - } - }, - Err(e) => Err(e.into()), - }, - Err(e) => Err(e.into()), - }; - } - - let item = if !self.started { - self.started = true; - let reply = T::from_request(&self.req, self.cfg.as_ref()).into(); - match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.fut1 = Some(fut); - return self.poll(); - } - } - } else { - match self.fut1.as_mut().unwrap().poll()? { - Async::Ready(item) => item, - Async::NotReady => return Ok(Async::NotReady), - } - }; - - self.fut2 = Some(self.hnd.as_ref().call_with(item)); - self.poll() - } -} - -macro_rules! with_factory_tuple ({$(($n:tt, $T:ident)),+} => { - impl<$($T,)+ State, Func, Res> WithFactory<($($T,)+), State, Res> for Func - where Func: Fn($($T,)+) -> Res + 'static, - $($T: FromRequest + 'static,)+ - Res: Responder + 'static, - State: 'static, - { - fn create(self) -> With<($($T,)+), State, Res> { - With::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+)) - } - - fn create_with_config(self, cfg: ($($T::Config,)+)) -> With<($($T,)+), State, Res> { - With::new(move |($($n,)+)| (self)($($n,)+), cfg) - } - } -}); - -macro_rules! with_async_factory_tuple ({$(($n:tt, $T:ident)),+} => { - impl<$($T,)+ State, Func, Res, Item, Err> WithAsyncFactory<($($T,)+), State, Res, Item, Err> for Func - where Func: Fn($($T,)+) -> Res + 'static, - $($T: FromRequest + 'static,)+ - Res: Future, - Item: Responder + 'static, - Err: Into, - State: 'static, - { - fn create(self) -> WithAsync<($($T,)+), State, Res, Item, Err> { - WithAsync::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+)) - } - - fn create_with_config(self, cfg: ($($T::Config,)+)) -> WithAsync<($($T,)+), State, Res, Item, Err> { - WithAsync::new(move |($($n,)+)| (self)($($n,)+), cfg) - } - } -}); - -with_factory_tuple!((a, A)); -with_factory_tuple!((a, A), (b, B)); -with_factory_tuple!((a, A), (b, B), (c, C)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G)); -with_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H) -); -with_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H), - (i, I) -); - -with_async_factory_tuple!((a, A)); -with_async_factory_tuple!((a, A), (b, B)); -with_async_factory_tuple!((a, A), (b, B), (c, C)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G)); -with_async_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H) -); -with_async_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H), - (i, I) -); diff --git a/tests/test_client.rs b/tests/test_client.rs deleted file mode 100644 index 8c5d5819d..000000000 --- a/tests/test_client.rs +++ /dev/null @@ -1,508 +0,0 @@ -#![allow(deprecated)] -extern crate actix; -extern crate actix_web; -extern crate bytes; -extern crate flate2; -extern crate futures; -extern crate rand; -#[cfg(all(unix, feature = "uds"))] -extern crate tokio_uds; - -use std::io::{Read, Write}; -use std::{net, thread}; - -use bytes::Bytes; -use flate2::read::GzDecoder; -use futures::stream::once; -use futures::Future; -use rand::Rng; - -use actix_web::*; - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -#[test] -fn test_simple() { - let mut srv = - test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().header("x-test", "111").finish().unwrap(); - let repr = format!("{:?}", request); - assert!(repr.contains("ClientRequest")); - assert!(repr.contains("x-test")); - - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - - let request = srv.post().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_connection_close() { - let mut srv = - test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().header("Connection", "close").finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_with_query_parameter() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| match req.query().get("qp") { - Some(_) => HttpResponse::Ok().finish(), - None => HttpResponse::BadRequest().finish(), - }) - }); - - let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap(); - - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_no_decompress() { - let mut srv = - test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - - // POST - let request = srv.post().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - - let bytes = srv.execute(response.body()).unwrap(); - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_client_gzip_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Gzip) - .body(STR) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_client_gzip_encoding_large() { - let data = STR.repeat(10); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_client_gzip_encoding_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(100_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(unix, feature = "uds"))] -#[test] -fn test_compatible_with_unix_socket_stream() { - let (stream, _) = tokio_uds::UnixStream::pair().unwrap(); - let _ = client::Connection::from_stream(stream); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_brotli_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .client(http::Method::POST, "/") - .content_encoding(http::ContentEncoding::Br) - .body(STR) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_brotli_encoding_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(70_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(move |bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .client(http::Method::POST, "/") - .content_encoding(http::ContentEncoding::Br) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_deflate_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Deflate) - .body(STR) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_deflate_encoding_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(70_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Deflate) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_client_streaming_explicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .map_err(Error::from) - .and_then(|body| { - Ok(HttpResponse::Ok() - .chunked() - .content_encoding(http::ContentEncoding::Identity) - .body(body)) - }).responder() - }) - }); - - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - - let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_streaming_implicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_client_cookie_handling() { - use actix_web::http::Cookie; - fn err() -> Error { - use std::io::{Error as IoError, ErrorKind}; - // stub some generic error - Error::from(IoError::from(ErrorKind::NotFound)) - } - let cookie1 = Cookie::build("cookie1", "value1").finish(); - let cookie2 = Cookie::build("cookie2", "value2") - .domain("www.example.org") - .path("/") - .secure(true) - .http_only(true) - .finish(); - // Q: are all these clones really necessary? A: Yes, possibly - let cookie1b = cookie1.clone(); - let cookie2b = cookie2.clone(); - let mut srv = test::TestServer::new(move |app| { - let cookie1 = cookie1b.clone(); - let cookie2 = cookie2b.clone(); - app.handler(move |req: &HttpRequest| { - // Check cookies were sent correctly - req.cookie("cookie1") - .ok_or_else(err) - .and_then(|c1| { - if c1.value() == "value1" { - Ok(()) - } else { - Err(err()) - } - }).and_then(|()| req.cookie("cookie2").ok_or_else(err)) - .and_then(|c2| { - if c2.value() == "value2" { - Ok(()) - } else { - Err(err()) - } - }) - // Send some cookies back - .map(|_| { - HttpResponse::Ok() - .cookie(cookie1.clone()) - .cookie(cookie2.clone()) - .finish() - }) - }) - }); - - let request = srv - .get() - .cookie(cookie1.clone()) - .cookie(cookie2.clone()) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - let c1 = response.cookie("cookie1").expect("Missing cookie1"); - assert_eq!(c1, cookie1); - let c2 = response.cookie("cookie2").expect("Missing cookie2"); - assert_eq!(c2, cookie2); -} - -#[test] -fn test_default_headers() { - let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().finish().unwrap(); - let repr = format!("{:?}", request); - assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\"")); - assert!(repr.contains(concat!( - "\"user-agent\": \"actix-web/", - env!("CARGO_PKG_VERSION"), - "\"" - ))); - - let request_override = srv - .get() - .header("User-Agent", "test") - .header("Accept-Encoding", "over_test") - .finish() - .unwrap(); - let repr_override = format!("{:?}", request_override); - assert!(repr_override.contains("\"user-agent\": \"test\"")); - assert!(repr_override.contains("\"accept-encoding\": \"over_test\"")); - assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\"")); - assert!(!repr_override.contains(concat!( - "\"user-agent\": \"Actix-web/", - env!("CARGO_PKG_VERSION"), - "\"" - ))); -} - -#[test] -fn client_read_until_eof() { - let addr = test::TestServer::unused_addr(); - - thread::spawn(move || { - let lst = net::TcpListener::bind(addr).unwrap(); - - for stream in lst.incoming() { - let mut stream = stream.unwrap(); - let mut b = [0; 1000]; - let _ = stream.read(&mut b).unwrap(); - let _ = stream - .write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!"); - } - }); - - let mut sys = actix::System::new("test"); - - // client request - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = sys.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"welcome!")); -} diff --git a/tests/test_custom_pipeline.rs b/tests/test_custom_pipeline.rs deleted file mode 100644 index 6b5df00e3..000000000 --- a/tests/test_custom_pipeline.rs +++ /dev/null @@ -1,81 +0,0 @@ -extern crate actix; -extern crate actix_net; -extern crate actix_web; - -use std::{thread, time}; - -use actix::System; -use actix_net::server::Server; -use actix_net::service::NewServiceExt; -use actix_web::server::{HttpService, KeepAlive, ServiceConfig, StreamConfiguration}; -use actix_web::{client, http, test, App, HttpRequest}; - -#[test] -fn test_custom_pipeline() { - let addr = test::TestServer::unused_addr(); - - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - let app = App::new() - .route("/", http::Method::GET, |_: HttpRequest| "OK") - .finish(); - let settings = ServiceConfig::build(app) - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_shutdown(1000) - .server_hostname("localhost") - .server_address(addr) - .finish(); - - StreamConfiguration::new() - .nodelay(true) - .tcp_keepalive(Some(time::Duration::from_secs(10))) - .and_then(HttpService::new(settings)) - }).unwrap() - .run(); - }); - - let mut sys = System::new("test"); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } -} - -#[test] -fn test_h1() { - use actix_web::server::H1Service; - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - let app = App::new() - .route("/", http::Method::GET, |_: HttpRequest| "OK") - .finish(); - let settings = ServiceConfig::build(app) - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_shutdown(1000) - .server_hostname("localhost") - .server_address(addr) - .finish(); - - H1Service::new(settings) - }).unwrap() - .run(); - }); - - let mut sys = System::new("test"); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } -} diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs index 7e8e9a42c..77b6d202f 100644 --- a/tests/test_h1v2.rs +++ b/tests/test_h1v2.rs @@ -1,4 +1,5 @@ extern crate actix; +extern crate actix_http; extern crate actix_net; extern crate actix_web; extern crate futures; @@ -8,12 +9,12 @@ use std::thread; use actix::System; use actix_net::server::Server; use actix_net::service::{IntoNewService, IntoService}; +use actix_web::{client, test}; use futures::future; -use actix_web::server::h1disp::Http1Dispatcher; -use actix_web::server::KeepAlive; -use actix_web::server::ServiceConfig; -use actix_web::{client, test, App, Error, HttpRequest, HttpResponse}; +use actix_http::server::h1disp::Http1Dispatcher; +use actix_http::server::{KeepAlive, ServiceConfig}; +use actix_http::{Error, HttpResponse}; #[test] fn test_h1_v2() { @@ -21,10 +22,7 @@ fn test_h1_v2() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - let app = App::new() - .route("/", http::Method::GET, |_: HttpRequest| "OK") - .finish(); - let settings = ServiceConfig::build(app) + let settings = ServiceConfig::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) .client_shutdown(1000) diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs deleted file mode 100644 index 3ea709c92..000000000 --- a/tests/test_handlers.rs +++ /dev/null @@ -1,677 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate bytes; -extern crate futures; -extern crate h2; -extern crate http; -extern crate tokio_timer; -#[macro_use] -extern crate serde_derive; -extern crate serde_json; - -use std::io; -use std::time::{Duration, Instant}; - -use actix_web::*; -use bytes::Bytes; -use futures::Future; -use http::StatusCode; -use serde_json::Value; -use tokio_timer::Delay; - -#[derive(Deserialize)] -struct PParam { - username: String, -} - -#[test] -fn test_path_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.with(|p: Path| format!("Welcome {}!", p.username)) - }); - }); - - // client request - let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); -} - -#[test] -fn test_async_handler() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|p: Path| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| Ok(format!("Welcome {}!", p.username))) - .responder() - }) - }); - }); - - // client request - let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); -} - -#[test] -fn test_query_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/index.html", |r| { - r.with(|p: Query| format!("Welcome {}!", p.username)) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/index.html?username=test")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); - - // client request - let request = srv.get().uri(srv.url("/index.html")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[derive(Deserialize, Debug)] -pub enum ResponseType { - Token, - Code, -} - -#[derive(Debug, Deserialize)] -pub struct AuthRequest { - id: u64, - response_type: ResponseType, -} - -#[test] -fn test_query_enum_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/index.html", |r| { - r.with(|p: Query| format!("{:?}", p.into_inner())) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/index.html?id=64&response_type=Code")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!( - bytes, - Bytes::from_static(b"AuthRequest { id: 64, response_type: Code }") - ); - - let request = srv - .get() - .uri(srv.url("/index.html?id=64&response_type=Co")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - - let request = srv - .get() - .uri(srv.url("/index.html?response_type=Code")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_async_extractor_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|data: Json| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| Ok(format!("{}", data.0))) - .responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}")); -} - -#[derive(Deserialize, Serialize)] -struct FormData { - username: String, -} - -#[test] -fn test_form_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|form: Form| format!("{}", form.username)) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .form(FormData { - username: "test".to_string(), - }).unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"test")); -} - -#[test] -fn test_form_extractor2() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with_config( - |form: Form| format!("{}", form.username), - |cfg| { - cfg.0.error_handler(|err, _| { - error::InternalError::from_response( - err, - HttpResponse::Conflict().finish(), - ).into() - }); - }, - ); - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/x-www-form-urlencoded") - .body("918237129hdk:D:D:D:D:D:DjASHDKJhaswkjeq") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_client_error()); -} - -#[test] -fn test_path_and_query_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|(p, q): (Path, Query)| { - format!("Welcome {} - {}!", p.username, q.username) - }) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html?username=test2")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|(_r, p, q): (HttpRequest, Path, Query)| { - format!("Welcome {} - {}!", p.username, q.username) - }) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html?username=test2")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with( - |(p, _q, data): (Path, Query, Json)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }, - ) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); -} - -#[test] -fn test_path_and_query_extractor3_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|(p, data): (Path, Json)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_path_and_query_extractor4_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|(data, p): (Json, Path)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_path_and_query_extractor2_async2() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with( - |(p, data, _q): (Path, Json, Query)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }, - ) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2_async3() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|data: Json, p: Path, _: Query| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2_async4() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|data: (Json, Path, Query)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_scope_and_path_extractor() { - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/sc", |scope| { - scope.resource("/{num}/index.html", |r| { - r.route() - .with(|p: Path<(usize,)>| format!("Welcome {}!", p.0)) - }) - }) - }); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/10/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome 10!")); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); -} - -#[test] -fn test_nested_scope_and_path_extractor() { - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/sc", |scope| { - scope.nested("/{num}", |scope| { - scope.resource("/{num}/index.html", |r| { - r.route().with(|p: Path<(usize, usize)>| { - format!("Welcome {} {}!", p.0, p.1) - }) - }) - }) - }) - }); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/10/12/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome 10 12!")); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/10/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); -} - -#[cfg(actix_impl_trait)] -fn test_impl_trait( - data: (Json, Path, Query), -) -> impl Future { - Delay::new(Instant::now() + Duration::from_millis(10)) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout")) - .and_then(move |_| Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0))) -} - -#[cfg(actix_impl_trait)] -fn test_impl_trait_err( - _data: (Json, Path, Query), -) -> impl Future { - Delay::new(Instant::now() + Duration::from_millis(10)) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout")) - .and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other"))) -} - -#[cfg(actix_impl_trait)] -#[test] -fn test_path_and_query_extractor2_async4_impl_trait() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with_async(test_impl_trait) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[cfg(actix_impl_trait)] -#[test] -fn test_path_and_query_extractor2_async4_impl_trait_err() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with_async(test_impl_trait_err) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); -} - -#[test] -fn test_non_ascii_route() { - let mut srv = test::TestServer::new(|app| { - app.resource("/中文/index.html", |r| r.f(|_| "success")); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/中文/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"success")); -} - -#[test] -fn test_unsafe_path_route() { - let mut srv = test::TestServer::new(|app| { - app.resource("/test/{url}", |r| { - r.f(|r| format!("success: {}", &r.match_info()["url"])) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/test/http%3A%2F%2Fexample.com")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!( - bytes, - Bytes::from_static(b"success: http:%2F%2Fexample.com") - ); -} diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs deleted file mode 100644 index 6cb6ee363..000000000 --- a/tests/test_middleware.rs +++ /dev/null @@ -1,1055 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate futures; -extern crate tokio_timer; - -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::thread; -use std::time::{Duration, Instant}; - -use actix_web::error::{Error, ErrorInternalServerError}; -use actix_web::*; -use futures::{future, Future}; -use tokio_timer::Delay; - -struct MiddlewareTest { - start: Arc, - response: Arc, - finish: Arc, -} - -impl middleware::Middleware for MiddlewareTest { - fn start(&self, _: &HttpRequest) -> Result { - self.start - .store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - Ok(middleware::Started::Done) - } - - fn response( - &self, _: &HttpRequest, resp: HttpResponse, - ) -> Result { - self.response - .store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - Ok(middleware::Response::Done(resp)) - } - - fn finish(&self, _: &HttpRequest, _: &HttpResponse) -> middleware::Finished { - self.finish - .store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - middleware::Finished::Done - } -} - -#[test] -fn test_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_resource_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_scope_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_middleware_async_handler() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/", |r| { - r.route().a(|_| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(|_| Ok(HttpResponse::Ok())) - }) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_async_handler() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", |r| { - r.middleware(mw); - r.route().a(|_| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(|_| Ok(HttpResponse::Ok())) - }) - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_async_handler() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| { - r.route().a(|_| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(|_| Ok(HttpResponse::Ok())) - }) - }) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -fn index_test_middleware_async_error(_: &HttpRequest) -> FutureResponse { - future::result(Err(error::ErrorBadRequest("TEST"))).responder() -} - -#[test] -fn test_middleware_async_error() { - let req = Arc::new(AtomicUsize::new(0)); - let resp = Arc::new(AtomicUsize::new(0)); - let fin = Arc::new(AtomicUsize::new(0)); - - let act_req = Arc::clone(&req); - let act_resp = Arc::clone(&resp); - let act_fin = Arc::clone(&fin); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_req), - response: Arc::clone(&act_resp), - finish: Arc::clone(&act_fin), - }).handler(index_test_middleware_async_error) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - assert_eq!(req.load(Ordering::Relaxed), 1); - assert_eq!(resp.load(Ordering::Relaxed), 1); - assert_eq!(fin.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_async_error() { - let req = Arc::new(AtomicUsize::new(0)); - let resp = Arc::new(AtomicUsize::new(0)); - let fin = Arc::new(AtomicUsize::new(0)); - - let act_req = Arc::clone(&req); - let act_resp = Arc::clone(&resp); - let act_fin = Arc::clone(&fin); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_req), - response: Arc::clone(&act_resp), - finish: Arc::clone(&act_fin), - }).resource("/test", |r| r.f(index_test_middleware_async_error)) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - assert_eq!(req.load(Ordering::Relaxed), 1); - assert_eq!(resp.load(Ordering::Relaxed), 1); - assert_eq!(fin.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_async_error() { - let req = Arc::new(AtomicUsize::new(0)); - let resp = Arc::new(AtomicUsize::new(0)); - let fin = Arc::new(AtomicUsize::new(0)); - - let act_req = Arc::clone(&req); - let act_resp = Arc::clone(&resp); - let act_fin = Arc::clone(&fin); - - let mut srv = test::TestServer::with_factory(move || { - let mw = MiddlewareAsyncTest { - start: Arc::clone(&act_req), - response: Arc::clone(&act_resp), - finish: Arc::clone(&act_fin), - }; - - App::new().resource("/test", move |r| { - r.middleware(mw); - r.f(index_test_middleware_async_error); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - assert_eq!(req.load(Ordering::Relaxed), 1); - assert_eq!(resp.load(Ordering::Relaxed), 1); - assert_eq!(fin.load(Ordering::Relaxed), 1); -} - -struct MiddlewareAsyncTest { - start: Arc, - response: Arc, - finish: Arc, -} - -impl middleware::Middleware for MiddlewareAsyncTest { - fn start(&self, _: &HttpRequest) -> Result { - let to = Delay::new(Instant::now() + Duration::from_millis(10)); - - let start = Arc::clone(&self.start); - Ok(middleware::Started::Future(Box::new( - to.from_err().and_then(move |_| { - start.fetch_add(1, Ordering::Relaxed); - Ok(None) - }), - ))) - } - - fn response( - &self, _: &HttpRequest, resp: HttpResponse, - ) -> Result { - let to = Delay::new(Instant::now() + Duration::from_millis(10)); - - let response = Arc::clone(&self.response); - Ok(middleware::Response::Future(Box::new( - to.from_err().and_then(move |_| { - response.fetch_add(1, Ordering::Relaxed); - Ok(resp) - }), - ))) - } - - fn finish(&self, _: &HttpRequest, _: &HttpResponse) -> middleware::Finished { - let to = Delay::new(Instant::now() + Duration::from_millis(10)); - - let finish = Arc::clone(&self.finish); - middleware::Finished::Future(Box::new(to.from_err().and_then(move |_| { - finish.fetch_add(1, Ordering::Relaxed); - Ok(()) - }))) - } -} - -#[test] -fn test_async_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_async_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(50)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_sync_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(50)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_scope_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_async_scope_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_async_scope_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_resource_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - - thread::sleep(Duration::from_millis(40)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_async_resource_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - let mw2 = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(mw2); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(40)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_sync_resource_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - let mw2 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(mw2); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(40)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -struct MiddlewareWithErr; - -impl middleware::Middleware for MiddlewareWithErr { - fn start(&self, _: &HttpRequest) -> Result { - Err(ErrorInternalServerError("middleware error")) - } -} - -struct MiddlewareAsyncWithErr; - -impl middleware::Middleware for MiddlewareAsyncWithErr { - fn start(&self, _: &HttpRequest) -> Result { - Ok(middleware::Started::Future(Box::new(future::err( - ErrorInternalServerError("middleware error"), - )))) - } -} - -#[test] -fn test_middleware_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new() - .middleware(mw1) - .middleware(MiddlewareWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_middleware_async_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new() - .middleware(mw1) - .middleware(MiddlewareAsyncWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().scope("/scope", |scope| { - scope - .middleware(mw1) - .middleware(MiddlewareWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_async_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().scope("/scope", |scope| { - scope - .middleware(mw1) - .middleware(MiddlewareAsyncWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(MiddlewareWithErr); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_async_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(MiddlewareAsyncWithErr); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[cfg(feature = "session")] -#[test] -fn test_session_storage_middleware() { - use actix_web::middleware::session::{ - CookieSessionBackend, RequestSession, SessionStorage, - }; - - const SIMPLE_NAME: &'static str = "simple"; - const SIMPLE_PAYLOAD: &'static str = "kantan"; - const COMPLEX_NAME: &'static str = "test"; - const COMPLEX_PAYLOAD: &'static str = "url=https://test.com&generate_204"; - //TODO: investigate how to handle below input - //const COMPLEX_PAYLOAD: &'static str = "FJc%26continue_url%3Dhttp%253A%252F%252Fconnectivitycheck.gstatic.com%252Fgenerate_204"; - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(SessionStorage::new( - CookieSessionBackend::signed(&[0; 32]).secure(false), - )).resource("/index", move |r| { - r.f(|req| { - let res = req.session().set(COMPLEX_NAME, COMPLEX_PAYLOAD); - assert!(res.is_ok()); - let value = req.session().get::(COMPLEX_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), COMPLEX_PAYLOAD); - - let res = req.session().set(SIMPLE_NAME, SIMPLE_PAYLOAD); - assert!(res.is_ok()); - let value = req.session().get::(SIMPLE_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), SIMPLE_PAYLOAD); - - HttpResponse::Ok() - }) - }).resource("/expect_cookie", move |r| { - r.f(|req| { - let _cookies = req.cookies().expect("To get cookies"); - - let value = req.session().get::(SIMPLE_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), SIMPLE_PAYLOAD); - - let value = req.session().get::(COMPLEX_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), COMPLEX_PAYLOAD); - - HttpResponse::Ok() - }) - }) - }); - - let request = srv.get().uri(srv.url("/index")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - - assert!(response.headers().contains_key("set-cookie")); - let set_cookie = response.headers().get("set-cookie"); - assert!(set_cookie.is_some()); - let set_cookie = set_cookie.unwrap().to_str().expect("Convert to str"); - - let request = srv - .get() - .uri(srv.url("/expect_cookie")) - .header("cookie", set_cookie.split(';').next().unwrap()) - .finish() - .unwrap(); - - srv.execute(request.send()).unwrap(); -} diff --git a/tests/test_server.rs b/tests/test_server.rs deleted file mode 100644 index 477d3e64b..000000000 --- a/tests/test_server.rs +++ /dev/null @@ -1,1357 +0,0 @@ -extern crate actix; -extern crate actix_net; -extern crate actix_web; -#[cfg(feature = "brotli")] -extern crate brotli2; -extern crate bytes; -extern crate flate2; -extern crate futures; -extern crate h2; -extern crate http as modhttp; -extern crate rand; -extern crate tokio; -extern crate tokio_current_thread; -extern crate tokio_current_thread as current_thread; -extern crate tokio_reactor; -extern crate tokio_tcp; - -#[cfg(feature = "tls")] -extern crate native_tls; -#[cfg(feature = "ssl")] -extern crate openssl; -#[cfg(feature = "rust-tls")] -extern crate rustls; - -use std::io::{Read, Write}; -use std::sync::Arc; -use std::{thread, time}; - -#[cfg(feature = "brotli")] -use brotli2::write::{BrotliDecoder, BrotliEncoder}; -use bytes::{Bytes, BytesMut}; -use flate2::read::GzDecoder; -use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; -use flate2::Compression; -use futures::stream::once; -use futures::{Future, Stream}; -use h2::client as h2client; -use modhttp::Request; -use rand::distributions::Alphanumeric; -use rand::Rng; -use tokio::runtime::current_thread::Runtime; -use tokio_current_thread::spawn; -use tokio_tcp::TcpStream; - -use actix_web::*; - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -#[test] -#[cfg(unix)] -fn test_start() { - use actix::System; - use std::sync::mpsc; - - let _ = test::TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); - - thread::spawn(|| { - System::run(move || { - let srv = server::new(|| { - vec![App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] - }); - - let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0]; - let srv_addr = srv.start(); - let _ = tx.send((addr, srv_addr, System::current())); - }); - }); - let (addr, srv_addr, sys) = rx.recv().unwrap(); - System::set_current(sys.clone()); - - let mut rt = Runtime::new().unwrap(); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } - - // pause - let _ = srv_addr.send(server::PauseServer).wait(); - thread::sleep(time::Duration::from_millis(200)); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .timeout(time::Duration::from_millis(200)) - .finish() - .unwrap(); - assert!(rt.block_on(req.send()).is_err()); - } - - // resume - let _ = srv_addr.send(server::ResumeServer).wait(); - thread::sleep(time::Duration::from_millis(200)); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } - - let _ = sys.stop(); -} - -#[test] -#[cfg(unix)] -fn test_shutdown() { - use actix::System; - use std::net; - use std::sync::mpsc; - - let _ = test::TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); - - thread::spawn(|| { - System::run(move || { - let srv = server::new(|| { - vec![App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] - }); - - let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0]; - let srv_addr = srv.shutdown_timeout(1).start(); - let _ = tx.send((addr, srv_addr, System::current())); - }); - }); - let (addr, srv_addr, sys) = rx.recv().unwrap(); - System::set_current(sys.clone()); - - let mut rt = Runtime::new().unwrap(); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - srv_addr.do_send(server::StopServer { graceful: true }); - assert!(response.status().is_success()); - } - - thread::sleep(time::Duration::from_millis(1000)); - assert!(net::TcpStream::connect(addr).is_err()); - - let _ = sys.stop(); -} - -#[test] -#[cfg(unix)] -fn test_panic() { - use actix::System; - use std::sync::mpsc; - - let _ = test::TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); - - thread::spawn(|| { - System::run(move || { - let srv = server::new(|| { - App::new() - .resource("/panic", |r| { - r.method(http::Method::GET).f(|_| -> &'static str { - panic!("error"); - }); - }).resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }).workers(1); - - let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0]; - srv.start(); - let _ = tx.send((addr, System::current())); - }); - }); - let (addr, sys) = rx.recv().unwrap(); - System::set_current(sys.clone()); - - let mut rt = Runtime::new().unwrap(); - { - let req = client::ClientRequest::get(format!("http://{}/panic", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()); - assert!(response.is_err()); - } - - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()); - assert!(response.is_err()); - } - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } - - let _ = sys.stop(); -} - -#[test] -fn test_simple() { - let mut srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok())); - let req = srv.get().finish().unwrap(); - let response = srv.execute(req.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_headers() { - let data = STR.repeat(10); - let srv_data = Arc::new(data.clone()); - let mut srv = test::TestServer::new(move |app| { - let data = srv_data.clone(); - app.handler(move |_| { - let mut builder = HttpResponse::Ok(); - for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); - } - builder.body(data.as_ref()) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_body() { - let mut srv = - test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_gzip() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(STR) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_gzip_large() { - let data = STR.repeat(10); - let srv_data = Arc::new(data.clone()); - - let mut srv = test::TestServer::new(move |app| { - let data = srv_data.clone(); - app.handler(move |_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.as_ref()) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from(data)); -} - -#[test] -fn test_body_gzip_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(70_000) - .collect::(); - let srv_data = Arc::new(data.clone()); - - let mut srv = test::TestServer::new(move |app| { - let data = srv_data.clone(); - app.handler(move |_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.as_ref()) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(dec.len(), data.len()); - assert_eq!(Bytes::from(dec), Bytes::from(data)); -} - -#[test] -fn test_body_chunked_implicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_body_br_streaming() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode br - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_head_empty() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| HttpResponse::Ok().content_length(STR.len() as u64).finish()) - }); - - let request = srv.head().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert!(bytes.is_empty()); -} - -#[test] -fn test_head_binary() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .content_length(100) - .body(STR) - }) - }); - - let request = srv.head().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert!(bytes.is_empty()); -} - -#[test] -fn test_head_binary2() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(STR) - }) - }); - - let request = srv.head().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } -} - -#[test] -fn test_body_length() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_length(STR.len() as u64) - .content_encoding(http::ContentEncoding::Identity) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_chunked_explicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .chunked() - .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_identity() { - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - let enc2 = enc.clone(); - - let mut srv = test::TestServer::new(move |app| { - let enc3 = enc2.clone(); - app.handler(move |_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc3.clone()) - }) - }); - - // client request - let request = srv - .get() - .header("accept-encoding", "deflate") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode deflate - assert_eq!(bytes, Bytes::from(STR)); -} - -#[test] -fn test_body_deflate() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(STR) - }) - }); - - // client request - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode deflate - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_body_brotli() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(STR) - }) - }); - - // client request - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode brotli - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_gzip_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_gzip_encoding_large() { - let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_reading_gzip_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(60_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_reading_deflate_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_reading_deflate_encoding_large() { - let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_reading_deflate_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_brotli_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "br") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_brotli_encoding_large() { - let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "br") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(feature = "brotli", feature = "ssl"))] -#[test] -fn test_brotli_encoding_large_ssl() { - use actix::{Actor, System}; - use openssl::ssl::{ - SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, - }; - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - let data = STR.repeat(10); - let srv = test::TestServer::build().ssl(builder).start(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - let mut rt = System::new("test"); - - // client connector - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let conn = client::ClientConnector::with_connector(builder.build()).start(); - - // body - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = client::ClientRequest::build() - .uri(srv.url("/")) - .method(http::Method::POST) - .header(http::header::CONTENT_ENCODING, "br") - .with_connector(conn) - .body(enc) - .unwrap(); - let response = rt.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = rt.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(feature = "rust-tls", feature = "ssl"))] -#[test] -fn test_reading_deflate_encoding_large_random_ssl() { - use actix::{Actor, System}; - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - use rustls::internal::pemfile::{certs, rsa_private_keys}; - use rustls::{NoClientAuth, ServerConfig}; - use std::fs::File; - use std::io::BufReader; - - // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = rsa_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); - - let srv = test::TestServer::build().rustls(config).start(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut rt = System::new("test"); - - // client connector - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let conn = client::ClientConnector::with_connector(builder.build()).start(); - - // encode data - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = client::ClientRequest::build() - .uri(srv.url("/")) - .method(http::Method::POST) - .header(http::header::CONTENT_ENCODING, "deflate") - .with_connector(conn) - .body(enc) - .unwrap(); - let response = rt.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = rt.block_on(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(feature = "tls", feature = "ssl"))] -#[test] -fn test_reading_deflate_encoding_large_random_tls() { - use native_tls::{Identity, TlsAcceptor}; - use openssl::ssl::{ - SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, - }; - use std::fs::File; - use std::sync::mpsc; - - use actix::{Actor, System}; - let (tx, rx) = mpsc::channel(); - - // load ssl keys - let mut file = File::open("tests/identity.pfx").unwrap(); - let mut identity = vec![]; - file.read_to_end(&mut identity).unwrap(); - let identity = Identity::from_pkcs12(&identity, "1").unwrap(); - let acceptor = TlsAcceptor::new(identity).unwrap(); - - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - System::run(move || { - server::new(|| { - App::new().handler("/", |req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }).bind_tls(addr, acceptor) - .unwrap() - .start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - - let mut rt = System::new("test"); - - // client connector - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let conn = client::ClientConnector::with_connector(builder.build()).start(); - - // encode data - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = client::ClientRequest::build() - .uri(format!("https://{}/", addr)) - .method(http::Method::POST) - .header(http::header::CONTENT_ENCODING, "deflate") - .with_connector(conn) - .body(enc) - .unwrap(); - let response = rt.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = rt.block_on(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); - - let _ = sys.stop(); -} - -#[test] -fn test_h2() { - let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - let addr = srv.addr(); - thread::sleep(time::Duration::from_millis(500)); - - let mut core = Runtime::new().unwrap(); - let tcp = TcpStream::connect(&addr); - - let tcp = tcp - .then(|res| h2client::handshake(res.unwrap())) - .then(move |res| { - let (mut client, h2) = res.unwrap(); - - let request = Request::builder() - .uri(format!("https://{}/", addr).as_str()) - .body(()) - .unwrap(); - let (response, _) = client.send_request(request, false).unwrap(); - - // Spawn a task to run the conn... - spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); - - response.and_then(|response| { - assert_eq!(response.status(), http::StatusCode::OK); - - let (_, body) = response.into_parts(); - - body.fold(BytesMut::new(), |mut b, c| -> Result<_, h2::Error> { - b.extend(c); - Ok(b) - }) - }) - }); - let _res = core.block_on(tcp); - // assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_application() { - let mut srv = test::TestServer::with_factory(|| { - App::new().resource("/", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_default_404_handler_response() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .prefix("/app") - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Ok())) - }); - let addr = srv.addr(); - - let mut buf = [0; 24]; - let request = TcpStream::connect(&addr) - .and_then(|sock| { - tokio::io::write_all(sock, "HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n") - .and_then(|(sock, _)| tokio::io::read_exact(sock, &mut buf)) - .and_then(|(_, buf)| Ok(buf)) - }).map_err(|e| panic!("{:?}", e)); - let response = srv.execute(request).unwrap(); - let rep = String::from_utf8_lossy(&response[..]); - assert!(rep.contains("HTTP/1.1 404 Not Found")); -} - -#[test] -fn test_server_cookies() { - use actix_web::http; - - let mut srv = test::TestServer::with_factory(|| { - App::new().resource("/", |r| { - r.f(|_| { - HttpResponse::Ok() - .cookie( - http::CookieBuilder::new("first", "first_value") - .http_only(true) - .finish(), - ).cookie(http::Cookie::new("second", "first_value")) - .cookie(http::Cookie::new("second", "second_value")) - .finish() - }) - }) - }); - - let first_cookie = http::CookieBuilder::new("first", "first_value") - .http_only(true) - .finish(); - let second_cookie = http::Cookie::new("second", "second_value"); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - let cookies = response.cookies().expect("To have cookies"); - assert_eq!(cookies.len(), 2); - if cookies[0] == first_cookie { - assert_eq!(cookies[1], second_cookie); - } else { - assert_eq!(cookies[0], second_cookie); - assert_eq!(cookies[1], first_cookie); - } - - let first_cookie = first_cookie.to_string(); - let second_cookie = second_cookie.to_string(); - //Check that we have exactly two instances of raw cookie headers - let cookies = response - .headers() - .get_all(http::header::SET_COOKIE) - .iter() - .map(|header| header.to_str().expect("To str").to_string()) - .collect::>(); - assert_eq!(cookies.len(), 2); - if cookies[0] == first_cookie { - assert_eq!(cookies[1], second_cookie); - } else { - assert_eq!(cookies[0], second_cookie); - assert_eq!(cookies[1], first_cookie); - } -} - -#[test] -fn test_slow_request() { - use actix::System; - use std::net; - use std::sync::mpsc; - let (tx, rx) = mpsc::channel(); - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - System::run(move || { - let srv = server::new(|| { - vec![App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] - }); - - let srv = srv.bind(addr).unwrap(); - srv.client_timeout(200).start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - - thread::sleep(time::Duration::from_millis(200)); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); - - sys.stop(); -} - -#[test] -fn test_malformed_request() { - use actix::System; - use std::net; - use std::sync::mpsc; - let (tx, rx) = mpsc::channel(); - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - System::run(move || { - let srv = server::new(|| { - App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }); - - let _ = srv.bind(addr).unwrap().start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - thread::sleep(time::Duration::from_millis(200)); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 400 Bad Request")); - - sys.stop(); -} - -#[test] -fn test_app_404() { - let mut srv = test::TestServer::with_factory(|| { - App::new().prefix("/prefix").resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }); - - let request = srv.client(http::Method::GET, "/prefix/").finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - let request = srv.client(http::Method::GET, "/").finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::NOT_FOUND); -} - -#[test] -#[cfg(feature = "ssl")] -fn test_ssl_handshake_timeout() { - use actix::System; - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - use std::net; - use std::sync::mpsc; - - let (tx, rx) = mpsc::channel(); - let addr = test::TestServer::unused_addr(); - - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - thread::spawn(move || { - System::run(move || { - let srv = server::new(|| { - App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }); - - srv.bind_ssl(addr, builder) - .unwrap() - .workers(1) - .client_timeout(200) - .start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.is_empty()); - - let _ = sys.stop(); -} diff --git a/tests/test_ws.rs b/tests/test_ws.rs deleted file mode 100644 index 5a0ce204f..000000000 --- a/tests/test_ws.rs +++ /dev/null @@ -1,394 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate bytes; -extern crate futures; -extern crate http; -extern crate rand; - -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::{thread, time}; - -use bytes::Bytes; -use futures::Stream; -use rand::distributions::Alphanumeric; -use rand::Rng; - -#[cfg(feature = "ssl")] -extern crate openssl; -#[cfg(feature = "rust-tls")] -extern crate rustls; - -use actix::prelude::*; -use actix_web::*; - -struct Ws; - -impl Actor for Ws { - type Context = ws::WebsocketContext; -} - -impl StreamHandler for Ws { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason), - _ => (), - } - } -} - -#[test] -fn test_simple() { - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (reader, mut writer) = srv.ws().unwrap(); - - writer.text("text"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); - - writer.binary(b"text".as_ref()); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Binary(Bytes::from_static(b"text").into())) - ); - - writer.ping("ping"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Pong("ping".to_owned()))); - - writer.close(Some(ws::CloseCode::Normal.into())); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - ); -} - -// websocket resource helper function -fn start_ws_resource(req: &HttpRequest) -> Result { - ws::start(req, Ws) -} - -#[test] -fn test_simple_path() { - const PATH: &str = "/v1/ws/"; - - // Create a websocket at a specific path. - let mut srv = test::TestServer::new(|app| { - app.resource(PATH, |r| r.route().f(start_ws_resource)); - }); - // fetch the sockets for the resource at a given path. - let (reader, mut writer) = srv.ws_at(PATH).unwrap(); - - writer.text("text"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); - - writer.binary(b"text".as_ref()); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Binary(Bytes::from_static(b"text").into())) - ); - - writer.ping("ping"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Pong("ping".to_owned()))); - - writer.close(Some(ws::CloseCode::Normal.into())); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - ); -} - -#[test] -fn test_empty_close_code() { - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (reader, mut writer) = srv.ws().unwrap(); - - writer.close(None); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Close(None))); -} - -#[test] -fn test_close_description() { - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (reader, mut writer) = srv.ws().unwrap(); - - let close_reason: ws::CloseReason = - (ws::CloseCode::Normal, "close description").into(); - writer.close(Some(close_reason.clone())); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Close(Some(close_reason)))); -} - -#[test] -fn test_large_text() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(65_536) - .collect::(); - - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (mut reader, mut writer) = srv.ws().unwrap(); - - for _ in 0..100 { - writer.text(data.clone()); - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, Some(ws::Message::Text(data.clone()))); - } -} - -#[test] -fn test_large_bin() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(65_536) - .collect::(); - - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (mut reader, mut writer) = srv.ws().unwrap(); - - for _ in 0..100 { - writer.binary(data.clone()); - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, Some(ws::Message::Binary(Binary::from(data.clone())))); - } -} - -#[test] -fn test_client_frame_size() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(131_072) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req| -> Result { - let mut resp = ws::handshake(req)?; - let stream = ws::WsStream::new(req.payload()).max_size(131_072); - - let body = ws::WebsocketContext::create(req.clone(), Ws, stream); - Ok(resp.body(body)) - }) - }); - let (reader, mut writer) = srv.ws().unwrap(); - - writer.binary(data.clone()); - match srv.execute(reader.into_future()).err().unwrap().0 { - ws::ProtocolError::Overflow => (), - _ => panic!(), - } -} - -struct Ws2 { - count: usize, - bin: bool, -} - -impl Actor for Ws2 { - type Context = ws::WebsocketContext; - - fn started(&mut self, ctx: &mut Self::Context) { - self.send(ctx); - } -} - -impl Ws2 { - fn send(&mut self, ctx: &mut ws::WebsocketContext) { - if self.bin { - ctx.binary(Vec::from("0".repeat(65_536))); - } else { - ctx.text("0".repeat(65_536)); - } - ctx.drain() - .and_then(|_, act, ctx| { - act.count += 1; - if act.count != 10_000 { - act.send(ctx); - } - actix::fut::ok(()) - }).wait(ctx); - } -} - -impl StreamHandler for Ws2 { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason), - _ => (), - } - } -} - -#[test] -fn test_server_send_text() { - let data = Some(ws::Message::Text("0".repeat(65_536))); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); - let (mut reader, _writer) = srv.ws().unwrap(); - - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -#[test] -fn test_server_send_bin() { - let data = Some(ws::Message::Binary(Binary::from("0".repeat(65_536)))); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: true, - }, - ) - }) - }); - let (mut reader, _writer) = srv.ws().unwrap(); - - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -#[test] -#[cfg(feature = "ssl")] -fn test_ws_server_ssl() { - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - let mut srv = test::TestServer::build().ssl(builder).start(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); - let (mut reader, _writer) = srv.ws().unwrap(); - - let data = Some(ws::Message::Text("0".repeat(65_536))); - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -#[test] -#[cfg(feature = "rust-tls")] -fn test_ws_server_rust_tls() { - use rustls::internal::pemfile::{certs, rsa_private_keys}; - use rustls::{NoClientAuth, ServerConfig}; - use std::fs::File; - use std::io::BufReader; - - // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = rsa_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - - let mut srv = test::TestServer::build().rustls(config).start(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); - - let (mut reader, _writer) = srv.ws().unwrap(); - - let data = Some(ws::Message::Text("0".repeat(65_536))); - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -struct WsStopped(Arc); - -impl Actor for WsStopped { - type Context = ws::WebsocketContext; - - fn stopped(&mut self, _: &mut Self::Context) { - self.0.fetch_add(1, Ordering::Relaxed); - } -} - -impl StreamHandler for WsStopped { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Text(text) => ctx.text(text), - _ => (), - } - } -} - -#[test] -fn test_ws_stopped() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); - - let mut srv = test::TestServer::new(move |app| { - let num3 = num2.clone(); - app.handler(move |req| ws::start(req, WsStopped(num3.clone()))) - }); - { - let (reader, mut writer) = srv.ws().unwrap(); - writer.text("text"); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); - } - thread::sleep(time::Duration::from_millis(1000)); - - assert_eq!(num.load(Ordering::Relaxed), 1); -}