1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-27 17:22:57 +01:00

Merge branch '1.0'

This commit is contained in:
Nikolay Kim 2019-03-23 10:16:32 -07:00
commit 1e069bb843
156 changed files with 14431 additions and 43864 deletions

View File

@ -10,9 +10,9 @@ matrix:
include:
- rust: stable
- rust: beta
- rust: nightly
- rust: nightly-2019-03-02
allow_failures:
- rust: nightly
- rust: nightly-2019-03-02
env:
global:
@ -24,34 +24,32 @@ before_install:
- sudo apt-get update -qq
- sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
before_cache: |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin
fi
# Add clippy
before_script:
- export PATH=$PATH:~/.cargo/bin
script:
- |
if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then
cargo clean
cargo check --features rust-tls
cargo check --features ssl
cargo check --features tls
cargo test --features="ssl,tls,rust-tls,uds" -- --nocapture
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin
RUST_BACKTRACE=1 cargo tarpaulin --features="ssl,tls,rust-tls" --out Xml
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"
fi
- cargo clean
- cargo test --all -- --nocapture
# Upload docs
after_success:
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cargo doc --features "ssl,tls,rust-tls,session" --no-deps &&
cargo doc --no-deps &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
git clone https://github.com/davisp/ghp-import.git &&
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
echo "Uploaded documentation"
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then
taskset -c 0 cargo tarpaulin --out Xml --all
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"
fi

View File

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

View File

@ -1,23 +1,19 @@
[package]
name = "actix-web"
version = "0.7.18"
version = "1.0.0-alpha.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git"
documentation = "https://actix.rs/api/actix-web/stable/actix_web/"
documentation = "https://docs.rs/actix-web/"
categories = ["network-programming", "asynchronous",
"web-programming::http-server",
"web-programming::http-client",
"web-programming::websocket"]
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
build = "build.rs"
[package.metadata.docs.rs]
features = ["tls", "ssl", "rust-tls", "session", "brotli", "flate2-c"]
edition = "2018"
[badges]
travis-ci = { repository = "actix/actix-web", branch = "master" }
@ -28,26 +24,20 @@ codecov = { repository = "actix/actix-web", branch = "master", service = "github
name = "actix_web"
path = "src/lib.rs"
[workspace]
members = [
".",
"actix-files",
"actix-session",
"actix-web-actors",
"actix-web-codegen",
]
[package.metadata.docs.rs]
features = ["ssl", "tls", "rust-tls", "brotli", "flate2-c", "cookies"]
[features]
default = ["session", "brotli", "flate2-c", "cell"]
# tls
tls = ["native-tls", "tokio-tls", "actix-net/tls"]
# openssl
ssl = ["openssl", "tokio-openssl", "actix-net/ssl"]
# deprecated, use "ssl"
alpn = ["openssl", "tokio-openssl", "actix-net/ssl"]
# rustls
rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots", "actix-net/rust-tls"]
# unix sockets
uds = ["tokio-uds"]
# sessions feature, session require "ring" crate and c compiler
session = ["cookie/secure"]
default = ["brotli", "flate2-c", "cookies"]
# brotli encoding, requires c compiler
brotli = ["brotli2"]
@ -58,81 +48,64 @@ flate2-c = ["flate2/miniz-sys"]
# rust backend for flate2 crate
flate2-rust = ["flate2/rust_backend"]
cell = ["actix-net/cell"]
# sessions feature, session require "ring" crate and c compiler
cookies = ["cookie", "actix-http/cookies"]
# tls
tls = ["native-tls", "actix-server/ssl"]
# openssl
ssl = ["openssl", "actix-server/ssl"]
# rustls
# rust-tls = ["rustls", "actix-server/rustls"]
[dependencies]
actix = "0.7.9"
actix-net = "0.2.6"
actix-codec = "0.1.1"
actix-service = "0.3.4"
actix-utils = "0.3.4"
actix-router = "0.1.0"
actix-rt = "0.2.1"
actix-web-codegen = { path="actix-web-codegen" }
actix-http = { git = "https://github.com/actix/actix-http.git", features=["fail"] }
actix-server = "0.4.1"
actix-server-config = "0.1.0"
v_htmlescape = "0.4"
base64 = "0.10"
bitflags = "1.0"
failure = "^0.1.2"
h2 = "0.1"
http = "^0.1.14"
httparse = "1.3"
bytes = "0.4"
derive_more = "0.14"
encoding = "0.2"
futures = "0.1"
hashbrown = "0.1.8"
log = "0.4"
mime = "0.3"
mime_guess = "2.0.0-alpha"
num_cpus = "1.0"
percent-encoding = "1.0"
rand = "0.6"
regex = "1.0"
serde = "1.0"
serde_json = "1.0"
sha1 = "0.6"
smallvec = "0.6"
time = "0.1"
encoding = "0.2"
language-tags = "0.2"
lazy_static = "1.0"
lazycell = "1.0.0"
net2 = "0.2.33"
parking_lot = "0.7"
regex = "1.0"
serde = { version = "1.0", features=["derive"] }
serde_json = "1.0"
serde_urlencoded = "^0.5.3"
time = "0.1"
url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.11", features=["percent-encode"] }
# cookies support
cookie = { version="0.11", features=["secure", "percent-encode"], optional = true }
# compression
brotli2 = { version="^0.3.2", optional = true }
flate2 = { version="^1.0.2", optional = true, default-features = false }
# io
mio = "^0.6.13"
net2 = "0.2"
bytes = "0.4"
byteorder = "1.2"
futures = "0.1"
futures-cpupool = "0.1"
slab = "0.4"
tokio = "0.1"
tokio-io = "0.1"
tokio-tcp = "0.1"
tokio-timer = "0.2.8"
tokio-reactor = "0.1"
tokio-current-thread = "0.1"
# native-tls
# ssl support
native-tls = { version="0.2", optional = true }
tokio-tls = { version="0.2", optional = true }
# openssl
openssl = { version="0.10", optional = true }
tokio-openssl = { version="0.2", optional = true }
#rustls
rustls = { version = "0.14", optional = true }
tokio-rustls = { version = "0.8", optional = true }
webpki = { version = "0.18", optional = true }
webpki-roots = { version = "0.15", optional = true }
# unix sockets
tokio-uds = { version="0.2", optional = true }
# rustls = { version = "^0.15", optional = true }
[dev-dependencies]
actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] }
actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] }
rand = "0.6"
env_logger = "0.6"
serde_derive = "1.0"
[build-dependencies]
version_check = "0.1"
[profile.release]
lto = true
opt-level = 3

View File

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

5
actix-files/CHANGES.md Normal file
View File

@ -0,0 +1,5 @@
# Changes
## [0.1.0] - 2018-10-x
* Initial impl

43
actix-files/Cargo.toml Normal file
View File

@ -0,0 +1,43 @@
[package]
name = "actix-files"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for Actix web."
readme = "README.md"
keywords = ["actix", "http", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git"
documentation = "https://actix.rs/api/actix-web/stable/actix_web/"
categories = ["asynchronous", "web-programming::http-server"]
license = "MIT/Apache-2.0"
edition = "2018"
workspace = ".."
[lib]
name = "actix_files"
path = "src/lib.rs"
[dependencies]
actix-web = { path=".." }
actix-http = { git = "https://github.com/actix/actix-http.git" }
actix-service = "0.3.3"
bytes = "0.4"
futures = "0.1"
derive_more = "0.14"
log = "0.4"
mime = "0.3"
mime_guess = "2.0.0-alpha"
percent-encoding = "1.0"
v_htmlescape = "0.4"
[dev-dependencies]
actix-rt = "0.1.0"
#actix-server = { version="0.2", features=["ssl"] }
actix-web = { path="..", features=["ssl"] }
actix-server = { git = "https://github.com/actix/actix-net.git", features=["ssl"] }
actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] }
actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] }
rand = "0.6"
env_logger = "0.6"
serde_derive = "1.0"

82
actix-files/README.md Normal file
View File

@ -0,0 +1,82 @@
# 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 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/)
* 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)
* Experimental [Async/Await](https://github.com/mehcode/actix-web-async-await) support.
## Documentation & community resources
* [User Guide](https://actix.rs/docs/)
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
* Minimum supported Rust version: 1.31 or later
## Example
```rust
extern crate actix_web;
use actix_web::{http, server, App, Path, Responder};
fn index(info: Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", info.1, info.0)
}
fn main() {
server::new(
|| App::new()
.route("/{id}/{name}/index.html", http::Method::GET, index))
.bind("127.0.0.1:8080").unwrap()
.run();
}
```
### 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)
## License
This project is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
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
intervene to uphold that code of conduct.

70
actix-files/src/config.rs Normal file
View File

@ -0,0 +1,70 @@
use actix_http::http::header::DispositionType;
use actix_web::http::Method;
use mime;
/// 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,ignore
/// 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 {}

41
actix-files/src/error.rs Normal file
View File

@ -0,0 +1,41 @@
use actix_web::{http::StatusCode, HttpResponse, ResponseError};
use derive_more::Display;
/// Errors which can occur when serving static files.
#[derive(Display, Debug, PartialEq)]
pub enum FilesError {
/// Path is not a directory
#[display(fmt = "Path is not a directory. Unable to serve static files")]
IsNotDirectory,
/// Cannot render directory
#[display(fmt = "Unable to render directory without index file")]
IsDirectory,
}
/// Return `NotFound` for `FilesError`
impl ResponseError for FilesError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::NOT_FOUND)
}
}
#[derive(Display, Debug, PartialEq)]
pub enum UriSegmentError {
/// The segment started with the wrapped invalid character.
#[display(fmt = "The segment started with the wrapped invalid character")]
BadStart(char),
/// The segment contained the wrapped invalid character.
#[display(fmt = "The segment contained the wrapped invalid character")]
BadChar(char),
/// The segment ended with the wrapped invalid character.
#[display(fmt = "The segment ended with the wrapped invalid character")]
BadEnd(char),
}
/// Return `BadRequest` for `UriSegmentError`
impl ResponseError for UriSegmentError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST)
}
}

1125
actix-files/src/lib.rs Normal file

File diff suppressed because it is too large Load Diff

439
actix-files/src/named.rs Normal file
View File

@ -0,0 +1,439 @@
use std::fs::{File, Metadata};
use std::io;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
use mime;
use mime_guess::guess_mime_type;
use actix_http::error::Error;
use actix_http::http::header::{self, ContentDisposition, DispositionParam};
use actix_web::http::{ContentEncoding, Method, StatusCode};
use actix_web::{HttpMessage, HttpRequest, HttpResponse, Responder};
use crate::config::{DefaultConfig, StaticFileConfig};
use crate::range::HttpRange;
use crate::ChunkedReadFile;
/// A file with an associated name.
#[derive(Debug)]
pub struct NamedFile<C = DefaultConfig> {
path: PathBuf,
file: File,
pub(crate) content_type: mime::Mime,
pub(crate) content_disposition: header::ContentDisposition,
pub(crate) md: Metadata,
modified: Option<SystemTime>,
encoding: Option<ContentEncoding>,
pub(crate) status_code: StatusCode,
_cd_map: PhantomData<C>,
}
impl NamedFile {
/// Creates an instance from a previously opened file.
///
/// The given `path` need not exist and is only used to determine the `ContentType` and
/// `ContentDisposition` headers.
///
/// # Examples
///
/// ```rust,ignore
/// extern crate actix_web;
///
/// use actix_web::fs::NamedFile;
/// use std::io::{self, Write};
/// use std::env;
/// use std::fs::File;
///
/// fn main() -> io::Result<()> {
/// let mut file = File::create("foo.txt")?;
/// file.write_all(b"Hello, world!")?;
/// let named_file = NamedFile::from_file(file, "bar.txt")?;
/// Ok(())
/// }
/// ```
pub fn from_file<P: AsRef<Path>>(file: File, path: P) -> io::Result<NamedFile> {
Self::from_file_with_config(file, path, DefaultConfig)
}
/// Attempts to open a file in read-only mode.
///
/// # Examples
///
/// ```rust,ignore
/// use actix_web::fs::NamedFile;
///
/// let file = NamedFile::open("foo.txt");
/// ```
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> {
Self::open_with_config(path, DefaultConfig)
}
}
impl<C: StaticFileConfig> NamedFile<C> {
/// Creates an instance from a previously opened file using the provided configuration.
///
/// The given `path` need not exist and is only used to determine the `ContentType` and
/// `ContentDisposition` headers.
///
/// # Examples
///
/// ```rust,ignore
/// extern crate actix_web;
///
/// use actix_web::fs::{DefaultConfig, NamedFile};
/// use std::io::{self, Write};
/// use std::env;
/// use std::fs::File;
///
/// fn main() -> io::Result<()> {
/// let mut file = File::create("foo.txt")?;
/// file.write_all(b"Hello, world!")?;
/// let named_file = NamedFile::from_file_with_config(file, "bar.txt", DefaultConfig)?;
/// Ok(())
/// }
/// ```
pub fn from_file_with_config<P: AsRef<Path>>(
file: File,
path: P,
_: C,
) -> io::Result<NamedFile<C>> {
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 md = file.metadata()?;
let modified = md.modified().ok();
let encoding = None;
Ok(NamedFile {
path,
file,
content_type,
content_disposition,
md,
modified,
encoding,
status_code: StatusCode::OK,
_cd_map: PhantomData,
})
}
/// Attempts to open a file in read-only mode using provided configuration.
///
/// # Examples
///
/// ```rust,ignore
/// use actix_web::fs::{DefaultConfig, NamedFile};
///
/// let file = NamedFile::open_with_config("foo.txt", DefaultConfig);
/// ```
pub fn open_with_config<P: AsRef<Path>>(
path: P,
config: C,
) -> io::Result<NamedFile<C>> {
Self::from_file_with_config(File::open(&path)?, path, config)
}
/// Returns reference to the underlying `File` object.
#[inline]
pub fn file(&self) -> &File {
&self.file
}
/// Retrieve the path of this file.
///
/// # Examples
///
/// ```rust,ignore
/// # 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 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
}
pub(crate) fn etag(&self) -> Option<header::EntityTag> {
// 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()
))
})
}
pub(crate) fn last_modified(&self) -> Option<header::HttpDate> {
self.modified.map(|mtime| mtime.into())
}
}
impl<C> Deref for NamedFile<C> {
type Target = File;
fn deref(&self) -> &File {
&self.file
}
}
impl<C> DerefMut for NamedFile<C> {
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::<header::IfMatch>() {
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::<header::IfNoneMatch>() {
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<C: StaticFileConfig> Responder for NamedFile<C> {
type Error = Error;
type Future = Result<HttpResponse, Error>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
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(),
);
// TODO blocking by compressing
// if let Some(current_encoding) = self.encoding {
// resp.content_encoding(current_encoding);
// }
let reader = ChunkedReadFile {
size: self.md.len(),
offset: 0,
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 req.headers().contains_key(header::IF_NONE_MATCH) {
false
} 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(),
);
// TODO blocking by compressing
// 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;
// TODO blocking by compressing
// 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,
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))
}
}
}

375
actix-files/src/range.rs Normal file
View File

@ -0,0 +1,375 @@
/// HTTP Range header representation.
#[derive(Debug, Clone, Copy)]
pub 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).
pub fn parse(header: &str, size: u64) -> Result<Vec<HttpRange>, ()> {
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<Option<HttpRange>> = 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 = 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::<Result<_, _>>()?;
let ranges: Vec<HttpRange> = all_ranges.into_iter().filter_map(|x| x).collect();
if no_overlap && ranges.is_empty() {
return Err(());
}
Ok(ranges)
}
}
#[cfg(test)]
mod tests {
use super::*;
struct T(&'static str, u64, Vec<HttpRange>);
#[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
)
}
}
}
}
}

View File

Before

Width:  |  Height:  |  Size: 168 B

After

Width:  |  Height:  |  Size: 168 B

47
actix-session/Cargo.toml Normal file
View File

@ -0,0 +1,47 @@
[package]
name = "actix-session"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Session for actix web framework."
readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-web/"
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
workspace = ".."
edition = "2018"
[lib]
name = "actix_session"
path = "src/lib.rs"
[features]
default = ["cookie-session"]
# sessions feature, session require "ring" crate and c compiler
cookie-session = ["cookie/secure"]
[dependencies]
actix-web = { path=".." }
actix-codec = "0.1.1"
actix-service = "0.3.3"
actix-utils = "0.3.3"
actix-http = { git = "https://github.com/actix/actix-http.git" }
actix-router = { git = "https://github.com/actix/actix-net.git" }
actix-server = { git = "https://github.com/actix/actix-net.git" }
bytes = "0.4"
cookie = { version="0.11", features=["percent-encode"], optional=true }
derive_more = "0.14"
encoding = "0.2"
futures = "0.1"
hashbrown = "0.1.8"
log = "0.4"
serde = "1.0"
serde_json = "1.0"
time = "0.1"
[dev-dependencies]
actix-rt = "0.2.0"

359
actix-session/src/cookie.rs Normal file
View File

@ -0,0 +1,359 @@
//! Cookie session.
//!
//! [**CookieSession**](struct.CookieSession.html)
//! uses cookies as session storage. `CookieSession` 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 `CookieSession` 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.
use std::collections::HashMap;
use std::rc::Rc;
use actix_service::{Service, Transform};
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::http::{header::SET_COOKIE, HeaderValue};
use actix_web::{Error, HttpMessage, ResponseError};
use cookie::{Cookie, CookieJar, Key, SameSite};
use derive_more::{Display, From};
use futures::future::{ok, Future, FutureResult};
use futures::Poll;
use serde_json::error::Error as JsonError;
use time::Duration;
use crate::Session;
/// Errors that can occur during handling cookie session
#[derive(Debug, From, Display)]
pub enum CookieSessionError {
/// Size of the serialized session is greater than 4000 bytes.
#[display(fmt = "Size of the serialized session is greater than 4000 bytes.")]
Overflow,
/// Fail to serialize session.
#[display(fmt = "Fail to serialize session")]
Serialize(JsonError),
}
impl ResponseError for CookieSessionError {}
enum CookieSecurity {
Signed,
Private,
}
struct CookieSessionInner {
key: Key,
security: CookieSecurity,
name: String,
path: String,
domain: Option<String>,
secure: bool,
http_only: bool,
max_age: Option<Duration>,
same_site: Option<SameSite>,
}
impl CookieSessionInner {
fn new(key: &[u8], security: CookieSecurity) -> CookieSessionInner {
CookieSessionInner {
security,
key: Key::from_master(key),
name: "actix-session".to_owned(),
path: "/".to_owned(),
domain: None,
secure: true,
http_only: true,
max_age: None,
same_site: None,
}
}
fn set_cookie<B>(
&self,
res: &mut ServiceResponse<B>,
state: impl Iterator<Item = (String, String)>,
) -> Result<(), Error> {
let state: HashMap<String, String> = state.collect();
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())?;
res.headers_mut().append(SET_COOKIE, val);
}
Ok(())
}
fn load<P>(&self, req: &ServiceRequest<P>) -> HashMap<String, String> {
if let Ok(cookies) = req.cookies() {
for cookie in cookies.iter() {
if cookie.name() == self.name {
let mut jar = CookieJar::new();
jar.add_original(cookie.clone());
let cookie_opt = match self.security {
CookieSecurity::Signed => jar.signed(&self.key).get(&self.name),
CookieSecurity::Private => {
jar.private(&self.key).get(&self.name)
}
};
if let Some(cookie) = cookie_opt {
if let Ok(val) = serde_json::from_str(cookie.value()) {
return val;
}
}
}
}
}
HashMap::new()
}
}
/// Use cookies for session storage.
///
/// `CookieSession` 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
/// use actix_session::CookieSession;
/// use actix_web::{web, App, HttpResponse, HttpServer};
///
/// fn main() {
/// let app = App::new().middleware(
/// CookieSession::signed(&[0; 32])
/// .domain("www.rust-lang.org")
/// .name("actix_session")
/// .path("/")
/// .secure(true))
/// .service(web::resource("/").to(|| HttpResponse::Ok()));
/// }
/// ```
pub struct CookieSession(Rc<CookieSessionInner>);
impl CookieSession {
/// Construct new *signed* `CookieSessionBackend` instance.
///
/// Panics if key length is less than 32 bytes.
pub fn signed(key: &[u8]) -> CookieSession {
CookieSession(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]) -> CookieSession {
CookieSession(Rc::new(CookieSessionInner::new(
key,
CookieSecurity::Private,
)))
}
/// Sets the `path` field in the session cookie being built.
pub fn path<S: Into<String>>(mut self, value: S) -> CookieSession {
Rc::get_mut(&mut self.0).unwrap().path = value.into();
self
}
/// Sets the `name` field in the session cookie being built.
pub fn name<S: Into<String>>(mut self, value: S) -> CookieSession {
Rc::get_mut(&mut self.0).unwrap().name = value.into();
self
}
/// Sets the `domain` field in the session cookie being built.
pub fn domain<S: Into<String>>(mut self, value: S) -> CookieSession {
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) -> CookieSession {
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) -> CookieSession {
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) -> CookieSession {
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) -> CookieSession {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
self
}
}
impl<S, P, B: 'static> Transform<S> for CookieSession
where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
S::Error: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = S::Error;
type InitError = ();
type Transform = CookieSessionMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
ok(CookieSessionMiddleware {
service,
inner: self.0.clone(),
})
}
}
/// Cookie session middleware
pub struct CookieSessionMiddleware<S> {
service: S,
inner: Rc<CookieSessionInner>,
}
impl<S, P, B: 'static> Service for CookieSessionMiddleware<S>
where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
S::Error: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = S::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
//self.service.poll_ready().map_err(|e| e.into())
self.service.poll_ready()
}
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
let inner = self.inner.clone();
let state = self.inner.load(&req);
Session::set_session(state.into_iter(), &mut req);
Box::new(self.service.call(req).map(move |mut res| {
if let Some(state) = Session::get_changes(&mut res) {
res.checked_expr(|res| inner.set_cookie(res, state))
} else {
res
}
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{test, web, App};
#[test]
fn cookie_session() {
let mut app = test::init_service(
App::new()
.middleware(CookieSession::signed(&[0; 32]).secure(false))
.service(web::resource("/").to(|ses: Session| {
let _ = ses.set("counter", 100);
"test"
})),
);
let request = test::TestRequest::get().to_request();
let response = test::block_on(app.call(request)).unwrap();
assert!(response
.cookies()
.find(|c| c.name() == "actix-session")
.is_some());
}
#[test]
fn cookie_session_extractor() {
let mut app = test::init_service(
App::new()
.middleware(CookieSession::signed(&[0; 32]).secure(false))
.service(web::resource("/").to(|ses: Session| {
let _ = ses.set("counter", 100);
"test"
})),
);
let request = test::TestRequest::get().to_request();
let response = test::block_on(app.call(request)).unwrap();
assert!(response
.cookies()
.find(|c| c.name() == "actix-session")
.is_some());
}
}

183
actix-session/src/lib.rs Normal file
View File

@ -0,0 +1,183 @@
//! User sessions.
//!
//! Actix provides a general solution for session management. Session
//! middlewares could provide different implementations which could
//! be accessed via general session api.
//!
//! By default, only cookie session backend is implemented. Other
//! backend implementations can be added.
//!
//! In general, you insert a *session* middleware and initialize it
//! , such as a `CookieSessionBackend`. To access session data,
//! [*Session*](struct.Session.html) extractor must be used. Session
//! extractor allows us to get or set session data.
//!
//! ```rust
//! use actix_web::{web, App, HttpServer, HttpResponse, Error};
//! use actix_session::{Session, CookieSession};
//!
//! fn index(session: Session) -> Result<&'static str, Error> {
//! // access session data
//! if let Some(count) = session.get::<i32>("counter")? {
//! println!("SESSION value: {}", count);
//! session.set("counter", count+1)?;
//! } else {
//! session.set("counter", 1)?;
//! }
//!
//! Ok("Welcome!")
//! }
//!
//! fn main() -> std::io::Result<()> {
//! # std::thread::spawn(||
//! HttpServer::new(
//! || App::new().middleware(
//! CookieSession::signed(&[0; 32]) // <- create cookie based session middleware
//! .secure(false)
//! )
//! .service(web::resource("/").to(|| HttpResponse::Ok())))
//! .bind("127.0.0.1:59880")?
//! .run()
//! # );
//! # Ok(())
//! }
//! ```
use std::cell::RefCell;
use std::rc::Rc;
use actix_web::dev::{ServiceFromRequest, ServiceRequest, ServiceResponse};
use actix_web::{Error, FromRequest, HttpMessage};
use hashbrown::HashMap;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json;
mod cookie;
pub use crate::cookie::CookieSession;
/// 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_session::Session;
/// use actix_web::*;
///
/// fn index(session: Session) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = session.get::<i32>("counter")? {
/// session.set("counter", count + 1)?;
/// } else {
/// session.set("counter", 1)?;
/// }
///
/// Ok("Welcome!")
/// }
/// # fn main() {}
/// ```
pub struct Session(Rc<RefCell<SessionInner>>);
#[derive(Default)]
struct SessionInner {
state: HashMap<String, String>,
changed: bool,
}
impl Session {
/// Get a `value` from the session.
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, Error> {
if let Some(s) = self.0.borrow().state.get(key) {
Ok(Some(serde_json::from_str(s)?))
} else {
Ok(None)
}
}
/// Set a `value` from the session.
pub fn set<T: Serialize>(&self, key: &str, value: T) -> Result<(), Error> {
let mut inner = self.0.borrow_mut();
inner.changed = true;
inner
.state
.insert(key.to_owned(), serde_json::to_string(&value)?);
Ok(())
}
/// Remove value from the session.
pub fn remove(&self, key: &str) {
let mut inner = self.0.borrow_mut();
inner.changed = true;
inner.state.remove(key);
}
/// Clear the session.
pub fn clear(&self) {
let mut inner = self.0.borrow_mut();
inner.changed = true;
inner.state.clear()
}
pub fn set_session<P>(
data: impl Iterator<Item = (String, String)>,
req: &mut ServiceRequest<P>,
) {
let session = Session::get_session(req);
let mut inner = session.0.borrow_mut();
inner.state.extend(data);
}
pub fn get_changes<B>(
res: &mut ServiceResponse<B>,
) -> Option<impl Iterator<Item = (String, String)>> {
if let Some(s_impl) = res
.request()
.extensions()
.get::<Rc<RefCell<SessionInner>>>()
{
let state =
std::mem::replace(&mut s_impl.borrow_mut().state, HashMap::new());
Some(state.into_iter())
} else {
None
}
}
fn get_session<R: HttpMessage>(req: R) -> Session {
if let Some(s_impl) = req.extensions().get::<Rc<RefCell<SessionInner>>>() {
return Session(Rc::clone(&s_impl));
}
let inner = Rc::new(RefCell::new(SessionInner::default()));
req.extensions_mut().insert(inner.clone());
Session(inner)
}
}
/// Extractor implementation for Session type.
///
/// ```rust
/// # use actix_web::*;
/// use actix_session::Session;
///
/// fn index(session: Session) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = session.get::<i32>("counter")? {
/// session.set("counter", count + 1)?;
/// } else {
/// session.set("counter", 1)?;
/// }
///
/// Ok("Welcome!")
/// }
/// # fn main() {}
/// ```
impl<P> FromRequest<P> for Session {
type Error = Error;
type Future = Result<Session, Error>;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
Ok(Session::get_session(req))
}
}

View File

@ -0,0 +1,31 @@
[package]
name = "actix-web-actors"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for actix web framework."
readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-web-actors/"
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
workspace = ".."
edition = "2018"
[lib]
name = "actix_web_actors"
path = "src/lib.rs"
[dependencies]
actix-web = { path=".." }
actix = { git = "https://github.com/actix/actix.git" }
actix-http = { git = "https://github.com/actix/actix-http.git" }
actix-codec = "0.1.1"
bytes = "0.4"
futures = "0.1"
[dev-dependencies]
env_logger = "0.6"
actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] }
actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] }

View File

@ -0,0 +1,253 @@
use std::collections::VecDeque;
use actix::dev::{
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope,
};
use actix::fut::ActorFuture;
use actix::{
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
};
use actix_web::error::{Error, ErrorInternalServerError};
use bytes::Bytes;
use futures::sync::oneshot::Sender;
use futures::{Async, Future, Poll, Stream};
/// Execution context for http actors
pub struct HttpContext<A>
where
A: Actor<Context = HttpContext<A>>,
{
inner: ContextParts<A>,
stream: VecDeque<Option<Bytes>>,
}
impl<A> ActorContext for HttpContext<A>
where
A: Actor<Context = Self>,
{
fn stop(&mut self) {
self.inner.stop();
}
fn terminate(&mut self) {
self.inner.terminate()
}
fn state(&self) -> ActorState {
self.inner.state()
}
}
impl<A> AsyncContext<A> for HttpContext<A>
where
A: Actor<Context = Self>,
{
#[inline]
fn spawn<F>(&mut self, fut: F) -> SpawnHandle
where
F: ActorFuture<Item = (), Error = (), Actor = A> + 'static,
{
self.inner.spawn(fut)
}
#[inline]
fn wait<F>(&mut self, fut: F)
where
F: ActorFuture<Item = (), Error = (), Actor = A> + 'static,
{
self.inner.wait(fut)
}
#[doc(hidden)]
#[inline]
fn waiting(&self) -> bool {
self.inner.waiting()
|| self.inner.state() == ActorState::Stopping
|| self.inner.state() == ActorState::Stopped
}
#[inline]
fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
self.inner.cancel_future(handle)
}
#[inline]
fn address(&self) -> Addr<A> {
self.inner.address()
}
}
impl<A> HttpContext<A>
where
A: Actor<Context = Self>,
{
#[inline]
/// Create a new HTTP Context from a request and an actor
pub fn create(actor: A) -> impl Stream<Item = Bytes, Error = Error> {
let mb = Mailbox::default();
let ctx = HttpContext {
inner: ContextParts::new(mb.sender_producer()),
stream: VecDeque::new(),
};
HttpContextFut::new(ctx, actor, mb)
}
/// Create a new HTTP Context
pub fn with_factory<F>(f: F) -> impl Stream<Item = Bytes, Error = Error>
where
F: FnOnce(&mut Self) -> A + 'static,
{
let mb = Mailbox::default();
let mut ctx = HttpContext {
inner: ContextParts::new(mb.sender_producer()),
stream: VecDeque::new(),
};
let act = f(&mut ctx);
HttpContextFut::new(ctx, act, mb)
}
}
impl<A> HttpContext<A>
where
A: Actor<Context = Self>,
{
/// Write payload
#[inline]
pub fn write(&mut self, data: Bytes) {
self.stream.push_back(Some(data));
}
/// Indicate end of streaming payload. Also this method calls `Self::close`.
#[inline]
pub fn write_eof(&mut self) {
self.stream.push_back(None);
}
/// Handle of the running future
///
/// SpawnHandle is the handle returned by `AsyncContext::spawn()` method.
pub fn handle(&self) -> SpawnHandle {
self.inner.curr_handle()
}
}
impl<A> AsyncContextParts<A> for HttpContext<A>
where
A: Actor<Context = Self>,
{
fn parts(&mut self) -> &mut ContextParts<A> {
&mut self.inner
}
}
struct HttpContextFut<A>
where
A: Actor<Context = HttpContext<A>>,
{
fut: ContextFut<A, HttpContext<A>>,
}
impl<A> HttpContextFut<A>
where
A: Actor<Context = HttpContext<A>>,
{
fn new(ctx: HttpContext<A>, act: A, mailbox: Mailbox<A>) -> Self {
let fut = ContextFut::new(ctx, act, mailbox);
HttpContextFut { fut }
}
}
impl<A> Stream for HttpContextFut<A>
where
A: Actor<Context = HttpContext<A>>,
{
type Item = Bytes;
type Error = Error;
fn poll(&mut self) -> Poll<Option<Bytes>, 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.pop_front() {
Ok(Async::Ready(data))
} else if self.fut.alive() {
Ok(Async::NotReady)
} else {
Ok(Async::Ready(None))
}
}
}
impl<A, M> ToEnvelope<A, M> for HttpContext<A>
where
A: Actor<Context = HttpContext<A>> + Handler<M>,
M: Message + Send + 'static,
M::Result: Send,
{
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> Envelope<A> {
Envelope::new(msg, tx)
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use actix::Actor;
use actix_web::http::StatusCode;
use actix_web::test::{block_on, call_success, init_service, TestRequest};
use actix_web::{web, App, HttpResponse};
use bytes::{Bytes, BytesMut};
use super::*;
struct MyActor {
count: usize,
}
impl Actor for MyActor {
type Context = HttpContext<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
ctx.run_later(Duration::from_millis(100), |slf, ctx| slf.write(ctx));
}
}
impl MyActor {
fn write(&mut self, ctx: &mut HttpContext<Self>) {
self.count += 1;
if self.count > 3 {
ctx.write_eof()
} else {
ctx.write(Bytes::from(format!("LINE-{}", self.count).as_bytes()));
ctx.run_later(Duration::from_millis(100), |slf, ctx| slf.write(ctx));
}
}
}
#[test]
fn test_default_resource() {
let mut srv =
init_service(App::new().service(web::resource("/test").to(|| {
HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 }))
})));
let req = TestRequest::with_uri("/test").to_request();
let mut resp = call_success(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let body = block_on(resp.take_body().fold(
BytesMut::new(),
move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, Error>(body)
},
))
.unwrap();
assert_eq!(body.freeze(), Bytes::from_static(b"LINE-1LINE-2LINE-3"));
}
}

View File

@ -0,0 +1,5 @@
//! Actix actors integration for Actix web framework
mod context;
pub mod ws;
pub use self::context::HttpContext;

547
actix-web-actors/src/ws.rs Normal file
View File

@ -0,0 +1,547 @@
//! Websocket integration
use std::collections::VecDeque;
use std::io;
use actix::dev::{
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler,
ToEnvelope,
};
use actix::fut::ActorFuture;
use actix::{
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler,
Message as ActixMessage, SpawnHandle,
};
use actix_codec::{Decoder, Encoder};
use actix_http::ws::{hash_key, Codec};
pub use actix_http::ws::{
CloseCode, CloseReason, Frame, HandshakeError, Message, ProtocolError,
};
use actix_web::dev::{Head, HttpResponseBuilder};
use actix_web::error::{Error, ErrorInternalServerError, PayloadError};
use actix_web::http::{header, Method, StatusCode};
use actix_web::{HttpMessage, HttpRequest, HttpResponse};
use bytes::{Bytes, BytesMut};
use futures::sync::oneshot::Sender;
use futures::{Async, Future, Poll, Stream};
/// Do websocket handshake and start ws actor.
pub fn start<A, T>(actor: A, req: &HttpRequest, stream: T) -> Result<HttpResponse, Error>
where
A: Actor<Context = WebsocketContext<A>> + StreamHandler<Message, ProtocolError>,
T: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
let mut res = handshake(req)?;
Ok(res.streaming(WebsocketContext::create(actor, stream)))
}
/// Prepare `WebSocket` handshake response.
///
/// This function returns handshake `HttpResponse`, ready to send to peer.
/// It does not perform any IO.
///
// /// `protocols` is a sequence of known protocols. On successful handshake,
// /// the returned response headers contain the first protocol in this list
// /// which the server also knows.
pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeError> {
// WebSocket accepts only GET
if *req.method() != Method::GET {
return Err(HandshakeError::GetMethodRequired);
}
// Check for "UPGRADE" to websocket header
let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) {
if let Ok(s) = hdr.to_str() {
s.to_ascii_lowercase().contains("websocket")
} else {
false
}
} else {
false
};
if !has_hdr {
return Err(HandshakeError::NoWebsocketUpgrade);
}
// Upgrade connection
if !req.upgrade() {
return Err(HandshakeError::NoConnectionUpgrade);
}
// check supported version
if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) {
return Err(HandshakeError::NoVersionHeader);
}
let supported_ver = {
if let Some(hdr) = req.headers().get(header::SEC_WEBSOCKET_VERSION) {
hdr == "13" || hdr == "8" || hdr == "7"
} else {
false
}
};
if !supported_ver {
return Err(HandshakeError::UnsupportedVersion);
}
// check client handshake for validity
if !req.headers().contains_key(header::SEC_WEBSOCKET_KEY) {
return Err(HandshakeError::BadWebsocketKey);
}
let key = {
let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap();
hash_key(key.as_ref())
};
Ok(HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS)
.upgrade("websocket")
.header(header::TRANSFER_ENCODING, "chunked")
.header(header::SEC_WEBSOCKET_ACCEPT, key.as_str())
.take())
}
/// Execution context for `WebSockets` actors
pub struct WebsocketContext<A>
where
A: Actor<Context = WebsocketContext<A>>,
{
inner: ContextParts<A>,
messages: VecDeque<Option<Message>>,
}
impl<A> ActorContext for WebsocketContext<A>
where
A: Actor<Context = Self>,
{
fn stop(&mut self) {
self.inner.stop();
}
fn terminate(&mut self) {
self.inner.terminate()
}
fn state(&self) -> ActorState {
self.inner.state()
}
}
impl<A> AsyncContext<A> for WebsocketContext<A>
where
A: Actor<Context = Self>,
{
fn spawn<F>(&mut self, fut: F) -> SpawnHandle
where
F: ActorFuture<Item = (), Error = (), Actor = A> + 'static,
{
self.inner.spawn(fut)
}
fn wait<F>(&mut self, fut: F)
where
F: ActorFuture<Item = (), Error = (), Actor = A> + 'static,
{
self.inner.wait(fut)
}
#[doc(hidden)]
#[inline]
fn waiting(&self) -> bool {
self.inner.waiting()
|| self.inner.state() == ActorState::Stopping
|| self.inner.state() == ActorState::Stopped
}
fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
self.inner.cancel_future(handle)
}
#[inline]
fn address(&self) -> Addr<A> {
self.inner.address()
}
}
impl<A> WebsocketContext<A>
where
A: Actor<Context = Self>,
{
#[inline]
/// Create a new Websocket context from a request and an actor
pub fn create<S>(actor: A, stream: S) -> impl Stream<Item = Bytes, Error = Error>
where
A: StreamHandler<Message, ProtocolError>,
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
let mb = Mailbox::default();
let mut ctx = WebsocketContext {
inner: ContextParts::new(mb.sender_producer()),
messages: VecDeque::new(),
};
ctx.add_stream(WsStream::new(stream));
WebsocketContextFut::new(ctx, actor, mb)
}
/// Create a new Websocket context
pub fn with_factory<S, F>(
stream: S,
f: F,
) -> impl Stream<Item = Bytes, Error = Error>
where
F: FnOnce(&mut Self) -> A + 'static,
A: StreamHandler<Message, ProtocolError>,
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
let mb = Mailbox::default();
let mut ctx = WebsocketContext {
inner: ContextParts::new(mb.sender_producer()),
messages: VecDeque::new(),
};
ctx.add_stream(WsStream::new(stream));
let act = f(&mut ctx);
WebsocketContextFut::new(ctx, act, mb)
}
}
impl<A> WebsocketContext<A>
where
A: Actor<Context = Self>,
{
/// Write payload
///
/// This is a low-level function that accepts framed messages that should
/// be created using `Frame::message()`. If you want to send text or binary
/// data you should prefer the `text()` or `binary()` convenience functions
/// that handle the framing for you.
#[inline]
pub fn write_raw(&mut self, msg: Message) {
self.messages.push_back(Some(msg));
}
/// Send text frame
#[inline]
pub fn text<T: Into<String>>(&mut self, text: T) {
self.write_raw(Message::Text(text.into()));
}
/// Send binary frame
#[inline]
pub fn binary<B: Into<Bytes>>(&mut self, data: B) {
self.write_raw(Message::Binary(data.into()));
}
/// Send ping frame
#[inline]
pub fn ping(&mut self, message: &str) {
self.write_raw(Message::Ping(message.to_string()));
}
/// Send pong frame
#[inline]
pub fn pong(&mut self, message: &str) {
self.write_raw(Message::Pong(message.to_string()));
}
/// Send close frame
#[inline]
pub fn close(&mut self, reason: Option<CloseReason>) {
self.write_raw(Message::Close(reason));
}
/// Handle of the running future
///
/// SpawnHandle is the handle returned by `AsyncContext::spawn()` method.
pub fn handle(&self) -> SpawnHandle {
self.inner.curr_handle()
}
/// Set mailbox capacity
///
/// By default mailbox capacity is 16 messages.
pub fn set_mailbox_capacity(&mut self, cap: usize) {
self.inner.set_mailbox_capacity(cap)
}
}
impl<A> AsyncContextParts<A> for WebsocketContext<A>
where
A: Actor<Context = Self>,
{
fn parts(&mut self) -> &mut ContextParts<A> {
&mut self.inner
}
}
struct WebsocketContextFut<A>
where
A: Actor<Context = WebsocketContext<A>>,
{
fut: ContextFut<A, WebsocketContext<A>>,
encoder: Codec,
buf: BytesMut,
closed: bool,
}
impl<A> WebsocketContextFut<A>
where
A: Actor<Context = WebsocketContext<A>>,
{
fn new(ctx: WebsocketContext<A>, act: A, mailbox: Mailbox<A>) -> Self {
let fut = ContextFut::new(ctx, act, mailbox);
WebsocketContextFut {
fut,
encoder: Codec::new(),
buf: BytesMut::new(),
closed: false,
}
}
}
impl<A> Stream for WebsocketContextFut<A>
where
A: Actor<Context = WebsocketContext<A>>,
{
type Item = Bytes;
type Error = Error;
fn poll(&mut self) -> Poll<Option<Bytes>, Error> {
if self.fut.alive() && self.fut.poll().is_err() {
return Err(ErrorInternalServerError("error"));
}
// encode messages
while let Some(item) = self.fut.ctx().messages.pop_front() {
if let Some(msg) = item {
self.encoder.encode(msg, &mut self.buf)?;
} else {
self.closed = true;
break;
}
}
if !self.buf.is_empty() {
Ok(Async::Ready(Some(self.buf.take().freeze())))
} else if self.fut.alive() && !self.closed {
Ok(Async::NotReady)
} else {
Ok(Async::Ready(None))
}
}
}
impl<A, M> ToEnvelope<A, M> for WebsocketContext<A>
where
A: Actor<Context = WebsocketContext<A>> + Handler<M>,
M: ActixMessage + Send + 'static,
M::Result: Send,
{
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> Envelope<A> {
Envelope::new(msg, tx)
}
}
struct WsStream<S> {
stream: S,
decoder: Codec,
buf: BytesMut,
closed: bool,
}
impl<S> WsStream<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
fn new(stream: S) -> Self {
Self {
stream,
decoder: Codec::new(),
buf: BytesMut::new(),
closed: false,
}
}
}
impl<S> Stream for WsStream<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
type Item = Message;
type Error = ProtocolError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if !self.closed {
loop {
match self.stream.poll() {
Ok(Async::Ready(Some(chunk))) => {
self.buf.extend_from_slice(&chunk[..]);
}
Ok(Async::Ready(None)) => {
self.closed = true;
break;
}
Ok(Async::NotReady) => break,
Err(e) => {
return Err(ProtocolError::Io(io::Error::new(
io::ErrorKind::Other,
format!("{}", e),
)));
}
}
}
}
match self.decoder.decode(&mut self.buf)? {
None => {
if self.closed {
Ok(Async::Ready(None))
} else {
Ok(Async::NotReady)
}
}
Some(frm) => {
let msg = match frm {
Frame::Text(data) => {
if let Some(data) = data {
Message::Text(
std::str::from_utf8(&data)
.map_err(|_| ProtocolError::BadEncoding)?
.to_string(),
)
} else {
Message::Text(String::new())
}
}
Frame::Binary(data) => Message::Binary(
data.map(|b| b.freeze()).unwrap_or_else(|| Bytes::new()),
),
Frame::Ping(s) => Message::Ping(s),
Frame::Pong(s) => Message::Pong(s),
Frame::Close(reason) => Message::Close(reason),
};
Ok(Async::Ready(Some(msg)))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::http::{header, Method};
use actix_web::test::TestRequest;
#[test]
fn test_handshake() {
let req = TestRequest::default()
.method(Method::POST)
.to_http_request();
assert_eq!(
HandshakeError::GetMethodRequired,
handshake(&req).err().unwrap()
);
let req = TestRequest::default().to_http_request();
assert_eq!(
HandshakeError::NoWebsocketUpgrade,
handshake(&req).err().unwrap()
);
let req = TestRequest::default()
.header(header::UPGRADE, header::HeaderValue::from_static("test"))
.to_http_request();
assert_eq!(
HandshakeError::NoWebsocketUpgrade,
handshake(&req).err().unwrap()
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.to_http_request();
assert_eq!(
HandshakeError::NoConnectionUpgrade,
handshake(&req).err().unwrap()
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.to_http_request();
assert_eq!(
HandshakeError::NoVersionHeader,
handshake(&req).err().unwrap()
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("5"),
)
.to_http_request();
assert_eq!(
HandshakeError::UnsupportedVersion,
handshake(&req).err().unwrap()
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
)
.to_http_request();
assert_eq!(
HandshakeError::BadWebsocketKey,
handshake(&req).err().unwrap()
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
)
.header(
header::SEC_WEBSOCKET_KEY,
header::HeaderValue::from_static("13"),
)
.to_http_request();
assert_eq!(
StatusCode::SWITCHING_PROTOCOLS,
handshake(&req).unwrap().finish().status()
);
}
}

View File

@ -0,0 +1,67 @@
use actix::prelude::*;
use actix_http::HttpService;
use actix_http_test::TestServer;
use actix_web::{web, App, HttpRequest};
use actix_web_actors::*;
use bytes::{Bytes, BytesMut};
use futures::{Sink, Stream};
struct Ws;
impl Actor for Ws {
type Context = ws::WebsocketContext<Self>;
}
impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
match msg {
ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Close(reason) => ctx.close(reason),
_ => (),
}
}
}
#[test]
fn test_simple() {
let mut srv = TestServer::new(|| {
HttpService::new(App::new().service(web::resource("/").to(
|req: HttpRequest, stream: web::Payload<_>| ws::start(Ws, &req, stream),
)))
});
// client service
let framed = srv.ws().unwrap();
let framed = srv
.block_on(framed.send(ws::Message::Text("text".to_string())))
.unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text")))));
let framed = srv
.block_on(framed.send(ws::Message::Binary("text".into())))
.unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(
item,
Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into())))
);
let framed = srv
.block_on(framed.send(ws::Message::Ping("text".into())))
.unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into())));
let framed = srv
.block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))))
.unwrap();
let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!(
item,
Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into())))
);
}

View File

@ -0,0 +1,20 @@
[package]
name = "actix-web-codegen"
description = "Actix web codegen macros"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
license = "MIT/Apache-2.0"
edition = "2018"
workspace = ".."
[lib]
proc-macro = true
[dependencies]
quote = "0.6"
syn = { version = "0.15", features = ["full", "parsing"] }
[dev-dependencies]
actix-web = { path = ".." }
actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] }
actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] }

View File

@ -0,0 +1,118 @@
#![recursion_limit = "512"]
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::parse_macro_input;
/// #[get("path")] attribute
#[proc_macro_attribute]
pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
if args.is_empty() {
panic!("invalid server definition, expected: #[get(\"some path\")]");
}
// path
let path = match args[0] {
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
let fname = quote!(#fname).to_string();
fname.as_str()[1..fname.len() - 1].to_owned()
}
_ => panic!("resource path"),
};
let ast: syn::ItemFn = syn::parse(input).unwrap();
let name = ast.ident.clone();
(quote! {
#[allow(non_camel_case_types)]
struct #name;
impl<P: 'static> actix_web::dev::HttpServiceFactory<P> for #name {
fn register(self, config: &mut actix_web::dev::ServiceConfig<P>) {
#ast
actix_web::dev::HttpServiceFactory::register(
actix_web::Resource::new(#path)
.guard(actix_web::guard::Get())
.to(#name), config);
}
}
})
.into()
}
/// #[post("path")] attribute
#[proc_macro_attribute]
pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
if args.is_empty() {
panic!("invalid server definition, expected: #[get(\"some path\")]");
}
// path
let path = match args[0] {
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
let fname = quote!(#fname).to_string();
fname.as_str()[1..fname.len() - 1].to_owned()
}
_ => panic!("resource path"),
};
let ast: syn::ItemFn = syn::parse(input).unwrap();
let name = ast.ident.clone();
(quote! {
#[allow(non_camel_case_types)]
struct #name;
impl<P: 'static> actix_web::dev::HttpServiceFactory<P> for #name {
fn register(self, config: &mut actix_web::dev::ServiceConfig<P>) {
#ast
actix_web::dev::HttpServiceFactory::register(
actix_web::Resource::new(#path)
.guard(actix_web::guard::Post())
.to(#name), config);
}
}
})
.into()
}
/// #[put("path")] attribute
#[proc_macro_attribute]
pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
if args.is_empty() {
panic!("invalid server definition, expected: #[get(\"some path\")]");
}
// path
let path = match args[0] {
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
let fname = quote!(#fname).to_string();
fname.as_str()[1..fname.len() - 1].to_owned()
}
_ => panic!("resource path"),
};
let ast: syn::ItemFn = syn::parse(input).unwrap();
let name = ast.ident.clone();
(quote! {
#[allow(non_camel_case_types)]
struct #name;
impl<P: 'static> actix_web::dev::HttpServiceFactory<P> for #name {
fn register(self, config: &mut actix_web::dev::ServiceConfig<P>) {
#ast
actix_web::dev::HttpServiceFactory::register(
actix_web::Resource::new(#path)
.guard(actix_web::guard::Put())
.to(#name), config);
}
}
})
.into()
}

View File

@ -0,0 +1,17 @@
use actix_http::HttpService;
use actix_http_test::TestServer;
use actix_web::{get, App, HttpResponse, Responder};
#[get("/test")]
fn test() -> impl Responder {
HttpResponse::Ok()
}
#[test]
fn test_body() {
let mut srv = TestServer::new(|| HttpService::new(App::new().service(test)));
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.send_request(request).unwrap();
assert!(response.status().is_success());
}

View File

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

49
examples/basic.rs Normal file
View File

@ -0,0 +1,49 @@
use futures::IntoFuture;
use actix_web::{
get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer,
};
#[get("/resource1/{name}/index.html")]
fn index(req: HttpRequest, name: web::Path<String>) -> String {
println!("REQ: {:?}", req);
format!("Hello: {}!\r\n", name)
}
fn index_async(req: HttpRequest) -> impl IntoFuture<Item = &'static str, Error = Error> {
println!("REQ: {:?}", req);
Ok("Hello world!\r\n")
}
#[get("/")]
fn no_params() -> &'static str {
"Hello world!\r\n"
}
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
env_logger::init();
HttpServer::new(|| {
App::new()
.middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
.middleware(middleware::Compress::default())
.middleware(middleware::Logger::default())
.service(index)
.service(no_params)
.service(
web::resource("/resource2/index.html")
.middleware(
middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"),
)
.default_resource(|r| {
r.route(web::route().to(|| HttpResponse::MethodNotAllowed()))
})
.route(web::get().to_async(index_async)),
)
.service(web::resource("/test1.html").to(|| "Test\r\n"))
})
.bind("127.0.0.1:8080")?
.workers(1)
.run()
}

View File

@ -1,5 +1,2 @@
max_width = 89
reorder_imports = true
#wrap_comments = true
fn_args_density = "Compressed"
#use_small_heuristics = false

513
src/app.rs Normal file
View File

@ -0,0 +1,513 @@
use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
use actix_http::body::{Body, MessageBody};
use actix_server_config::ServerConfig;
use actix_service::boxed::{self, BoxedNewService};
use actix_service::{
ApplyTransform, IntoNewService, IntoTransform, NewService, Transform,
};
use futures::IntoFuture;
use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory};
use crate::config::{AppConfig, AppConfigInner};
use crate::data::{Data, DataFactory};
use crate::dev::{PayloadStream, ResourceDef};
use crate::error::Error;
use crate::resource::Resource;
use crate::route::Route;
use crate::service::{
HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest,
ServiceResponse,
};
type HttpNewService<P> =
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
/// Application builder - structure that follows the builder pattern
/// for building application instances.
pub struct App<P, T>
where
T: NewService<Request = ServiceRequest, Response = ServiceRequest<P>>,
{
chain: T,
data: Vec<Box<DataFactory>>,
config: AppConfigInner,
_t: PhantomData<(P,)>,
}
impl App<PayloadStream, AppChain> {
/// Create application builder. Application can be configured with a builder-like pattern.
pub fn new() -> Self {
App {
chain: AppChain,
data: Vec::new(),
config: AppConfigInner::default(),
_t: PhantomData,
}
}
}
impl<P, T> App<P, T>
where
P: 'static,
T: NewService<
Request = ServiceRequest,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
{
/// Set application data. Applicatin data could be accessed
/// by using `Data<T>` extractor where `T` is data type.
///
/// **Note**: http server accepts an application factory rather than
/// an application instance. Http server constructs an application
/// instance for each thread, thus application data must be constructed
/// multiple times. If you want to share data between different
/// threads, a shared object should be used, e.g. `Arc`. Application
/// data does not need to be `Send` or `Sync`.
///
/// ```rust
/// use std::cell::Cell;
/// use actix_web::{web, App};
///
/// struct MyData {
/// counter: Cell<usize>,
/// }
///
/// fn index(data: web::Data<MyData>) {
/// data.counter.set(data.counter.get() + 1);
/// }
///
/// fn main() {
/// let app = App::new()
/// .data(MyData{ counter: Cell::new(0) })
/// .service(
/// web::resource("/index.html").route(
/// web::get().to(index)));
/// }
/// ```
pub fn data<S: 'static>(mut self, data: S) -> Self {
self.data.push(Box::new(Data::new(data)));
self
}
/// Set application data factory. This function is
/// similar to `.data()` but it accepts data factory. Data object get
/// constructed asynchronously during application initialization.
pub fn data_factory<F, Out>(mut self, data: F) -> Self
where
F: Fn() -> Out + 'static,
Out: IntoFuture + 'static,
Out::Error: std::fmt::Debug,
{
self.data.push(Box::new(data));
self
}
/// Register a middleware.
pub fn middleware<M, B, F>(
self,
mw: F,
) -> AppRouter<
T,
P,
B,
impl NewService<
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
>
where
M: Transform<
AppRouting<P>,
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
F: IntoTransform<M, AppRouting<P>>,
{
let fref = Rc::new(RefCell::new(None));
let endpoint = ApplyTransform::new(mw, AppEntry::new(fref.clone()));
AppRouter {
endpoint,
chain: self.chain,
data: self.data,
services: Vec::new(),
default: None,
factory_ref: fref,
config: self.config,
external: Vec::new(),
_t: PhantomData,
}
}
/// Register a request modifier. It can modify any request parameters
/// including payload stream type.
pub fn chain<C, F, P1>(
self,
chain: C,
) -> App<
P1,
impl NewService<
Request = ServiceRequest,
Response = ServiceRequest<P1>,
Error = Error,
InitError = (),
>,
>
where
C: NewService<
Request = ServiceRequest<P>,
Response = ServiceRequest<P1>,
Error = Error,
InitError = (),
>,
F: IntoNewService<C>,
{
let chain = self.chain.and_then(chain.into_new_service());
App {
chain,
data: self.data,
config: self.config,
_t: PhantomData,
}
}
/// Configure route for a specific path.
///
/// This is a simplified version of the `App::service()` method.
/// This method can be used multiple times with same path, in that case
/// multiple resources with one route would be registered for same resource path.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn index(data: web::Path<(String, String)>) -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .route("/test1", web::get().to(index))
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()));
/// }
/// ```
pub fn route(
self,
path: &str,
mut route: Route<P>,
) -> AppRouter<T, P, Body, AppEntry<P>> {
self.service(
Resource::new(path)
.add_guards(route.take_guards())
.route(route),
)
}
/// Register http service.
///
/// Http service is any type that implements `HttpServiceFactory` trait.
///
/// Actix web provides several services implementations:
///
/// * *Resource* is an entry in resource table which corresponds to requested URL.
/// * *Scope* is a set of resources with common root path.
/// * "StaticFiles" is a service for static files support
pub fn service<F>(self, service: F) -> AppRouter<T, P, Body, AppEntry<P>>
where
F: HttpServiceFactory<P> + 'static,
{
let fref = Rc::new(RefCell::new(None));
AppRouter {
chain: self.chain,
default: None,
endpoint: AppEntry::new(fref.clone()),
factory_ref: fref,
data: self.data,
config: self.config,
services: vec![Box::new(ServiceFactoryWrapper::new(service))],
external: Vec::new(),
_t: PhantomData,
}
}
/// Set server host name.
///
/// Host name is used by application router as a hostname for url
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
/// html#method.host) documentation for more information.
///
/// By default host name is set to a "localhost" value.
pub fn hostname(mut self, val: &str) -> Self {
self.config.host = val.to_owned();
self
}
}
/// Application router builder - Structure that follows the builder pattern
/// for building application instances.
pub struct AppRouter<C, P, B, T> {
chain: C,
endpoint: T,
services: Vec<Box<ServiceFactory<P>>>,
default: Option<Rc<HttpNewService<P>>>,
factory_ref: Rc<RefCell<Option<AppRoutingFactory<P>>>>,
data: Vec<Box<DataFactory>>,
config: AppConfigInner,
external: Vec<ResourceDef>,
_t: PhantomData<(P, B)>,
}
impl<C, P, B, T> AppRouter<C, P, B, T>
where
P: 'static,
B: MessageBody,
T: NewService<
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
{
/// Configure route for a specific path.
///
/// This is a simplified version of the `App::service()` method.
/// This method can not be could multiple times, in that case
/// multiple resources with one route would be registered for same resource path.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn index(data: web::Path<(String, String)>) -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .route("/test1", web::get().to(index))
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()));
/// }
/// ```
pub fn route(self, path: &str, mut route: Route<P>) -> Self {
self.service(
Resource::new(path)
.add_guards(route.take_guards())
.route(route),
)
}
/// Register http service.
///
/// Http service is any type that implements `HttpServiceFactory` trait.
///
/// Actix web provides several services implementations:
///
/// * *Resource* is an entry in resource table which corresponds to requested URL.
/// * *Scope* is a set of resources with common root path.
/// * "StaticFiles" is a service for static files support
pub fn service<F>(mut self, factory: F) -> Self
where
F: HttpServiceFactory<P> + 'static,
{
self.services
.push(Box::new(ServiceFactoryWrapper::new(factory)));
self
}
/// Register a middleware.
pub fn middleware<M, B1, F>(
self,
mw: F,
) -> AppRouter<
C,
P,
B1,
impl NewService<
Request = ServiceRequest<P>,
Response = ServiceResponse<B1>,
Error = Error,
InitError = (),
>,
>
where
M: Transform<
T::Service,
Request = ServiceRequest<P>,
Response = ServiceResponse<B1>,
Error = Error,
InitError = (),
>,
B1: MessageBody,
F: IntoTransform<M, T::Service>,
{
let endpoint = ApplyTransform::new(mw, self.endpoint);
AppRouter {
endpoint,
chain: self.chain,
data: self.data,
services: self.services,
default: self.default,
factory_ref: self.factory_ref,
config: self.config,
external: self.external,
_t: PhantomData,
}
}
/// Default resource to be used if no matching route could be found.
pub fn default_resource<F, U>(mut self, f: F) -> Self
where
F: FnOnce(Resource<P>) -> Resource<P, U>,
U: NewService<
Request = ServiceRequest<P>,
Response = ServiceResponse,
Error = Error,
InitError = (),
> + 'static,
{
// create and configure default resource
self.default = Some(Rc::new(boxed::new_service(
f(Resource::new("")).into_new_service().map_init_err(|_| ()),
)));
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
/// use actix_web::{web, App, HttpRequest, HttpResponse, Result};
///
/// fn index(req: HttpRequest) -> Result<HttpResponse> {
/// let url = req.url_for("youtube", &["asdlkjqme"])?;
/// assert_eq!(url.as_str(), "https://youtube.com/watch/asdlkjqme");
/// Ok(HttpResponse::Ok().into())
/// }
///
/// fn main() {
/// let app = App::new()
/// .service(web::resource("/index.html").route(
/// web::get().to(index)))
/// .external_resource("youtube", "https://youtube.com/watch/{video_id}");
/// }
/// ```
pub fn external_resource<N, U>(mut self, name: N, url: U) -> Self
where
N: AsRef<str>,
U: AsRef<str>,
{
let mut rdef = ResourceDef::new(url.as_ref());
*rdef.name_mut() = name.as_ref().to_string();
self.external.push(rdef);
self
}
}
impl<C, T, P: 'static, B: MessageBody> IntoNewService<AppInit<C, T, P, B>, ServerConfig>
for AppRouter<C, P, B, T>
where
T: NewService<
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
C: NewService<
Request = ServiceRequest,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
{
fn into_new_service(self) -> AppInit<C, T, P, B> {
AppInit {
chain: self.chain,
data: self.data,
endpoint: self.endpoint,
services: RefCell::new(self.services),
external: RefCell::new(self.external),
default: self.default,
factory_ref: self.factory_ref,
config: RefCell::new(AppConfig(Rc::new(self.config))),
}
}
}
#[cfg(test)]
mod tests {
use actix_service::Service;
use super::*;
use crate::http::{Method, StatusCode};
use crate::test::{block_on, init_service, TestRequest};
use crate::{web, HttpResponse};
#[test]
fn test_default_resource() {
let mut srv = init_service(
App::new().service(web::resource("/test").to(|| HttpResponse::Ok())),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/blah").to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut srv = init_service(
App::new()
.service(web::resource("/test").to(|| HttpResponse::Ok()))
.service(
web::resource("/test2")
.default_resource(|r| r.to(|| HttpResponse::Created()))
.route(web::get().to(|| HttpResponse::Ok())),
)
.default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())),
);
let req = TestRequest::with_uri("/blah").to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let req = TestRequest::with_uri("/test2").to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test2")
.method(Method::POST)
.to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::CREATED);
}
#[test]
fn test_data_factory() {
let mut srv =
init_service(App::new().data_factory(|| Ok::<_, ()>(10usize)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let mut srv =
init_service(App::new().data_factory(|| Ok::<_, ()>(10u32)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
}

444
src/app_service.rs Normal file
View File

@ -0,0 +1,444 @@
use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
use actix_http::{Request, Response};
use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url};
use actix_server_config::ServerConfig;
use actix_service::boxed::{self, BoxedNewService, BoxedService};
use actix_service::{fn_service, AndThen, NewService, Service, ServiceExt};
use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, Poll};
use crate::config::{AppConfig, ServiceConfig};
use crate::data::{DataFactory, DataFactoryResult};
use crate::error::Error;
use crate::guard::Guard;
use crate::rmap::ResourceMap;
use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse};
type Guards = Vec<Box<Guard>>;
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>;
type HttpNewService<P> =
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
type BoxedResponse = Box<Future<Item = ServiceResponse, Error = Error>>;
/// Service factory to convert `Request` to a `ServiceRequest<S>`.
/// It also executes data factories.
pub struct AppInit<C, T, P, B>
where
C: NewService<Request = ServiceRequest, Response = ServiceRequest<P>>,
T: NewService<
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
{
pub(crate) chain: C,
pub(crate) endpoint: T,
pub(crate) data: Vec<Box<DataFactory>>,
pub(crate) config: RefCell<AppConfig>,
pub(crate) services: RefCell<Vec<Box<ServiceFactory<P>>>>,
pub(crate) default: Option<Rc<HttpNewService<P>>>,
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory<P>>>>,
pub(crate) external: RefCell<Vec<ResourceDef>>,
}
impl<C, T, P: 'static, B> NewService<ServerConfig> for AppInit<C, T, P, B>
where
C: NewService<
Request = ServiceRequest,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
T: NewService<
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
{
type Request = Request;
type Response = ServiceResponse<B>;
type Error = C::Error;
type InitError = C::InitError;
type Service = AndThen<AppInitService<C::Service, P>, T::Service>;
type Future = AppInitResult<C, T, P, B>;
fn new_service(&self, cfg: &ServerConfig) -> Self::Future {
// update resource default service
let default = self.default.clone().unwrap_or_else(|| {
Rc::new(boxed::new_service(fn_service(|req: ServiceRequest<P>| {
Ok(req.into_response(Response::NotFound().finish()))
})))
});
{
let mut c = self.config.borrow_mut();
let loc_cfg = Rc::get_mut(&mut c.0).unwrap();
loc_cfg.secure = cfg.secure();
loc_cfg.addr = cfg.local_addr();
}
let mut config =
ServiceConfig::new(self.config.borrow().clone(), default.clone());
// register services
std::mem::replace(&mut *self.services.borrow_mut(), Vec::new())
.into_iter()
.for_each(|mut srv| srv.register(&mut config));
let mut rmap = ResourceMap::new(ResourceDef::new(""));
// complete pipeline creation
*self.factory_ref.borrow_mut() = Some(AppRoutingFactory {
default,
services: Rc::new(
config
.into_services()
.into_iter()
.map(|(mut rdef, srv, guards, nested)| {
rmap.add(&mut rdef, nested);
(rdef, srv, RefCell::new(guards))
})
.collect(),
),
});
// external resources
for mut rdef in std::mem::replace(&mut *self.external.borrow_mut(), Vec::new()) {
rmap.add(&mut rdef, None);
}
// complete ResourceMap tree creation
let rmap = Rc::new(rmap);
rmap.finish(rmap.clone());
AppInitResult {
chain: None,
chain_fut: self.chain.new_service(&()),
endpoint: None,
endpoint_fut: self.endpoint.new_service(&()),
data: self.data.iter().map(|s| s.construct()).collect(),
config: self.config.borrow().clone(),
rmap,
_t: PhantomData,
}
}
}
pub struct AppInitResult<C, T, P, B>
where
C: NewService,
T: NewService,
{
chain: Option<C::Service>,
endpoint: Option<T::Service>,
chain_fut: C::Future,
endpoint_fut: T::Future,
rmap: Rc<ResourceMap>,
data: Vec<Box<DataFactoryResult>>,
config: AppConfig,
_t: PhantomData<(P, B)>,
}
impl<C, T, P, B> Future for AppInitResult<C, T, P, B>
where
C: NewService<
Request = ServiceRequest,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
T: NewService<
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
{
type Item = AndThen<AppInitService<C::Service, P>, T::Service>;
type Error = C::InitError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut idx = 0;
let mut extensions = self.config.0.extensions.borrow_mut();
while idx < self.data.len() {
if let Async::Ready(_) = self.data[idx].poll_result(&mut extensions)? {
self.data.remove(idx);
} else {
idx += 1;
}
}
if self.chain.is_none() {
if let Async::Ready(srv) = self.chain_fut.poll()? {
self.chain = Some(srv);
}
}
if self.endpoint.is_none() {
if let Async::Ready(srv) = self.endpoint_fut.poll()? {
self.endpoint = Some(srv);
}
}
if self.chain.is_some() && self.endpoint.is_some() {
Ok(Async::Ready(
AppInitService {
chain: self.chain.take().unwrap(),
rmap: self.rmap.clone(),
config: self.config.clone(),
}
.and_then(self.endpoint.take().unwrap()),
))
} else {
Ok(Async::NotReady)
}
}
}
/// Service to convert `Request` to a `ServiceRequest<S>`
pub struct AppInitService<C, P>
where
C: Service<Request = ServiceRequest, Response = ServiceRequest<P>, Error = Error>,
{
chain: C,
rmap: Rc<ResourceMap>,
config: AppConfig,
}
impl<C, P> Service for AppInitService<C, P>
where
C: Service<Request = ServiceRequest, Response = ServiceRequest<P>, Error = Error>,
{
type Request = Request;
type Response = ServiceRequest<P>;
type Error = C::Error;
type Future = C::Future;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.chain.poll_ready()
}
fn call(&mut self, req: Request) -> Self::Future {
let req = ServiceRequest::new(
Path::new(Url::new(req.uri().clone())),
req,
self.rmap.clone(),
self.config.clone(),
);
self.chain.call(req)
}
}
pub struct AppRoutingFactory<P> {
services: Rc<Vec<(ResourceDef, HttpNewService<P>, RefCell<Option<Guards>>)>>,
default: Rc<HttpNewService<P>>,
}
impl<P: 'static> NewService for AppRoutingFactory<P> {
type Request = ServiceRequest<P>;
type Response = ServiceResponse;
type Error = Error;
type InitError = ();
type Service = AppRouting<P>;
type Future = AppRoutingFactoryResponse<P>;
fn new_service(&self, _: &()) -> Self::Future {
AppRoutingFactoryResponse {
fut: self
.services
.iter()
.map(|(path, service, guards)| {
CreateAppRoutingItem::Future(
Some(path.clone()),
guards.borrow_mut().take(),
service.new_service(&()),
)
})
.collect(),
default: None,
default_fut: Some(self.default.new_service(&())),
}
}
}
type HttpServiceFut<P> = Box<Future<Item = HttpService<P>, Error = ()>>;
/// Create app service
#[doc(hidden)]
pub struct AppRoutingFactoryResponse<P> {
fut: Vec<CreateAppRoutingItem<P>>,
default: Option<HttpService<P>>,
default_fut: Option<Box<Future<Item = HttpService<P>, Error = ()>>>,
}
enum CreateAppRoutingItem<P> {
Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut<P>),
Service(ResourceDef, Option<Guards>, HttpService<P>),
}
impl<P> Future for AppRoutingFactoryResponse<P> {
type Item = AppRouting<P>;
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut done = true;
if let Some(ref mut fut) = self.default_fut {
match fut.poll()? {
Async::Ready(default) => self.default = Some(default),
Async::NotReady => done = false,
}
}
// poll http services
for item in &mut self.fut {
let res = match item {
CreateAppRoutingItem::Future(
ref mut path,
ref mut guards,
ref mut fut,
) => match fut.poll()? {
Async::Ready(service) => {
Some((path.take().unwrap(), guards.take(), service))
}
Async::NotReady => {
done = false;
None
}
},
CreateAppRoutingItem::Service(_, _, _) => continue,
};
if let Some((path, guards, service)) = res {
*item = CreateAppRoutingItem::Service(path, guards, service);
}
}
if done {
let router = self
.fut
.drain(..)
.fold(Router::build(), |mut router, item| {
match item {
CreateAppRoutingItem::Service(path, guards, service) => {
router.rdef(path, service).2 = guards;
}
CreateAppRoutingItem::Future(_, _, _) => unreachable!(),
}
router
});
Ok(Async::Ready(AppRouting {
ready: None,
router: router.finish(),
default: self.default.take(),
}))
} else {
Ok(Async::NotReady)
}
}
}
pub struct AppRouting<P> {
router: Router<HttpService<P>, Guards>,
ready: Option<(ServiceRequest<P>, ResourceInfo)>,
default: Option<HttpService<P>>,
}
impl<P> Service for AppRouting<P> {
type Request = ServiceRequest<P>;
type Response = ServiceResponse;
type Error = Error;
type Future = Either<BoxedResponse, FutureResult<Self::Response, Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
if self.ready.is_none() {
Ok(Async::Ready(()))
} else {
Ok(Async::NotReady)
}
}
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
let res = self.router.recognize_mut_checked(&mut req, |req, guards| {
if let Some(ref guards) = guards {
for f in guards {
if !f.check(req.head()) {
return false;
}
}
}
true
});
if let Some((srv, _info)) = res {
Either::A(srv.call(req))
} else if let Some(ref mut default) = self.default {
Either::A(default.call(req))
} else {
let req = req.into_request();
Either::B(ok(ServiceResponse::new(req, Response::NotFound().finish())))
}
}
}
/// Wrapper service for routing
pub struct AppEntry<P> {
factory: Rc<RefCell<Option<AppRoutingFactory<P>>>>,
}
impl<P> AppEntry<P> {
pub fn new(factory: Rc<RefCell<Option<AppRoutingFactory<P>>>>) -> Self {
AppEntry { factory }
}
}
impl<P: 'static> NewService for AppEntry<P> {
type Request = ServiceRequest<P>;
type Response = ServiceResponse;
type Error = Error;
type InitError = ();
type Service = AppRouting<P>;
type Future = AppRoutingFactoryResponse<P>;
fn new_service(&self, _: &()) -> Self::Future {
self.factory.borrow_mut().as_mut().unwrap().new_service(&())
}
}
#[doc(hidden)]
pub struct AppChain;
impl NewService for AppChain {
type Request = ServiceRequest;
type Response = ServiceRequest;
type Error = Error;
type InitError = ();
type Service = AppChain;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &()) -> Self::Future {
ok(AppChain)
}
}
impl Service for AppChain {
type Request = ServiceRequest;
type Response = ServiceRequest;
type Error = Error;
type Future = FutureResult<Self::Response, Self::Error>;
#[inline]
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
#[inline]
fn call(&mut self, req: ServiceRequest) -> Self::Future {
ok(req)
}
}

View File

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

View File

@ -1,391 +0,0 @@
use bytes::{Bytes, BytesMut};
use futures::Stream;
use std::borrow::Cow;
use std::sync::Arc;
use std::{fmt, mem};
use context::ActorHttpContext;
use error::Error;
use handler::Responder;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
/// Type represent streaming body
pub type BodyStream = Box<Stream<Item = Bytes, Error = Error>>;
/// Represents various types of http message body.
pub enum Body {
/// Empty response. `Content-Length` header is set to `0`
Empty,
/// Specific response body.
Binary(Binary),
/// Unspecified streaming response. Developer is responsible for setting
/// right `Content-Length` or `Transfer-Encoding` headers.
Streaming(BodyStream),
/// Special body type for actor response.
Actor(Box<ActorHttpContext>),
}
/// Represents various types of binary body.
/// `Content-Length` header is set to length of the body.
#[derive(Debug, PartialEq)]
pub enum Binary {
/// Bytes body
Bytes(Bytes),
/// Static slice
Slice(&'static [u8]),
/// Shared string body
#[doc(hidden)]
SharedString(Arc<String>),
/// Shared vec body
SharedVec(Arc<Vec<u8>>),
}
impl Body {
/// Does this body streaming.
#[inline]
pub fn is_streaming(&self) -> bool {
match *self {
Body::Streaming(_) | Body::Actor(_) => true,
_ => false,
}
}
/// Is this binary body.
#[inline]
pub fn is_binary(&self) -> bool {
match *self {
Body::Binary(_) => true,
_ => false,
}
}
/// Is this binary empy.
#[inline]
pub fn is_empty(&self) -> bool {
match *self {
Body::Empty => true,
_ => false,
}
}
/// Create body from slice (copy)
pub fn from_slice(s: &[u8]) -> Body {
Body::Binary(Binary::Bytes(Bytes::from(s)))
}
/// Is this binary body.
#[inline]
pub(crate) fn binary(self) -> Binary {
match self {
Body::Binary(b) => b,
_ => panic!(),
}
}
}
impl PartialEq for Body {
fn eq(&self, other: &Body) -> bool {
match *self {
Body::Empty => match *other {
Body::Empty => true,
_ => false,
},
Body::Binary(ref b) => match *other {
Body::Binary(ref b2) => b == b2,
_ => false,
},
Body::Streaming(_) | Body::Actor(_) => false,
}
}
}
impl fmt::Debug for Body {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Body::Empty => write!(f, "Body::Empty"),
Body::Binary(ref b) => write!(f, "Body::Binary({:?})", b),
Body::Streaming(_) => write!(f, "Body::Streaming(_)"),
Body::Actor(_) => write!(f, "Body::Actor(_)"),
}
}
}
impl<T> From<T> for Body
where
T: Into<Binary>,
{
fn from(b: T) -> Body {
Body::Binary(b.into())
}
}
impl From<Box<ActorHttpContext>> for Body {
fn from(ctx: Box<ActorHttpContext>) -> Body {
Body::Actor(ctx)
}
}
impl Binary {
#[inline]
/// Returns `true` if body is empty
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
/// Length of body in bytes
pub fn len(&self) -> usize {
match *self {
Binary::Bytes(ref bytes) => bytes.len(),
Binary::Slice(slice) => slice.len(),
Binary::SharedString(ref s) => s.len(),
Binary::SharedVec(ref s) => s.len(),
}
}
/// Create binary body from slice
pub fn from_slice(s: &[u8]) -> Binary {
Binary::Bytes(Bytes::from(s))
}
/// Convert Binary to a Bytes instance
pub fn take(&mut self) -> Bytes {
mem::replace(self, Binary::Slice(b"")).into()
}
}
impl Clone for Binary {
fn clone(&self) -> Binary {
match *self {
Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()),
Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)),
Binary::SharedString(ref s) => Binary::SharedString(s.clone()),
Binary::SharedVec(ref s) => Binary::SharedVec(s.clone()),
}
}
}
impl Into<Bytes> for Binary {
fn into(self) -> Bytes {
match self {
Binary::Bytes(bytes) => bytes,
Binary::Slice(slice) => Bytes::from(slice),
Binary::SharedString(s) => Bytes::from(s.as_str()),
Binary::SharedVec(s) => Bytes::from(AsRef::<[u8]>::as_ref(s.as_ref())),
}
}
}
impl From<&'static str> for Binary {
fn from(s: &'static str) -> Binary {
Binary::Slice(s.as_ref())
}
}
impl From<&'static [u8]> for Binary {
fn from(s: &'static [u8]) -> Binary {
Binary::Slice(s)
}
}
impl From<Vec<u8>> for Binary {
fn from(vec: Vec<u8>) -> Binary {
Binary::Bytes(Bytes::from(vec))
}
}
impl From<Cow<'static, [u8]>> for Binary {
fn from(b: Cow<'static, [u8]>) -> Binary {
match b {
Cow::Borrowed(s) => Binary::Slice(s),
Cow::Owned(vec) => Binary::Bytes(Bytes::from(vec)),
}
}
}
impl From<String> for Binary {
fn from(s: String) -> Binary {
Binary::Bytes(Bytes::from(s))
}
}
impl From<Cow<'static, str>> for Binary {
fn from(s: Cow<'static, str>) -> Binary {
match s {
Cow::Borrowed(s) => Binary::Slice(s.as_ref()),
Cow::Owned(s) => Binary::Bytes(Bytes::from(s)),
}
}
}
impl<'a> From<&'a String> for Binary {
fn from(s: &'a String) -> Binary {
Binary::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s)))
}
}
impl From<Bytes> for Binary {
fn from(s: Bytes) -> Binary {
Binary::Bytes(s)
}
}
impl From<BytesMut> for Binary {
fn from(s: BytesMut) -> Binary {
Binary::Bytes(s.freeze())
}
}
impl From<Arc<String>> for Binary {
fn from(body: Arc<String>) -> Binary {
Binary::SharedString(body)
}
}
impl<'a> From<&'a Arc<String>> for Binary {
fn from(body: &'a Arc<String>) -> Binary {
Binary::SharedString(Arc::clone(body))
}
}
impl From<Arc<Vec<u8>>> for Binary {
fn from(body: Arc<Vec<u8>>) -> Binary {
Binary::SharedVec(body)
}
}
impl<'a> From<&'a Arc<Vec<u8>>> for Binary {
fn from(body: &'a Arc<Vec<u8>>) -> Binary {
Binary::SharedVec(Arc::clone(body))
}
}
impl AsRef<[u8]> for Binary {
#[inline]
fn as_ref(&self) -> &[u8] {
match *self {
Binary::Bytes(ref bytes) => bytes.as_ref(),
Binary::Slice(slice) => slice,
Binary::SharedString(ref s) => s.as_bytes(),
Binary::SharedVec(ref s) => s.as_ref().as_ref(),
}
}
}
impl Responder for Binary {
type Item = HttpResponse;
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(HttpResponse::build_from(req)
.content_type("application/octet-stream")
.body(self))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_body_is_streaming() {
assert_eq!(Body::Empty.is_streaming(), false);
assert_eq!(Body::Binary(Binary::from("")).is_streaming(), false);
}
#[test]
fn test_is_empty() {
assert_eq!(Binary::from("").is_empty(), true);
assert_eq!(Binary::from("test").is_empty(), false);
}
#[test]
fn test_static_str() {
assert_eq!(Binary::from("test").len(), 4);
assert_eq!(Binary::from("test").as_ref(), b"test");
}
#[test]
fn test_cow_str() {
let cow: Cow<'static, str> = Cow::Borrowed("test");
assert_eq!(Binary::from(cow.clone()).len(), 4);
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
let cow: Cow<'static, str> = Cow::Owned("test".to_owned());
assert_eq!(Binary::from(cow.clone()).len(), 4);
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
}
#[test]
fn test_static_bytes() {
assert_eq!(Binary::from(b"test".as_ref()).len(), 4);
assert_eq!(Binary::from(b"test".as_ref()).as_ref(), b"test");
assert_eq!(Binary::from_slice(b"test".as_ref()).len(), 4);
assert_eq!(Binary::from_slice(b"test".as_ref()).as_ref(), b"test");
}
#[test]
fn test_vec() {
assert_eq!(Binary::from(Vec::from("test")).len(), 4);
assert_eq!(Binary::from(Vec::from("test")).as_ref(), b"test");
}
#[test]
fn test_bytes() {
assert_eq!(Binary::from(Bytes::from("test")).len(), 4);
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test");
}
#[test]
fn test_cow_bytes() {
let cow: Cow<'static, [u8]> = Cow::Borrowed(b"test");
assert_eq!(Binary::from(cow.clone()).len(), 4);
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
let cow: Cow<'static, [u8]> = Cow::Owned(Vec::from("test"));
assert_eq!(Binary::from(cow.clone()).len(), 4);
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
}
#[test]
fn test_arc_string() {
let b = Arc::new("test".to_owned());
assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), b"test");
}
#[test]
fn test_string() {
let b = "test".to_owned();
assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), b"test");
}
#[test]
fn test_shared_vec() {
let b = Arc::new(Vec::from(&b"test"[..]));
assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b.clone()).as_ref(), &b"test"[..]);
assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), &b"test"[..]);
}
#[test]
fn test_bytes_mut() {
let b = BytesMut::from("test");
assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b).as_ref(), b"test");
}
#[test]
fn test_binary_into() {
let bytes = Bytes::from_static(b"test");
let b: Bytes = Binary::from("test").into();
assert_eq!(b, bytes);
let b: Bytes = Binary::from(bytes.clone()).into();
assert_eq!(b, bytes);
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

159
src/config.rs Normal file
View File

@ -0,0 +1,159 @@
use std::cell::{Ref, RefCell};
use std::net::SocketAddr;
use std::rc::Rc;
use actix_http::Extensions;
use actix_router::ResourceDef;
use actix_service::{boxed, IntoNewService, NewService};
use crate::error::Error;
use crate::guard::Guard;
use crate::rmap::ResourceMap;
use crate::service::{ServiceRequest, ServiceResponse};
type Guards = Vec<Box<Guard>>;
type HttpNewService<P> =
boxed::BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
/// Application configuration
pub struct ServiceConfig<P> {
config: AppConfig,
root: bool,
default: Rc<HttpNewService<P>>,
services: Vec<(
ResourceDef,
HttpNewService<P>,
Option<Guards>,
Option<Rc<ResourceMap>>,
)>,
}
impl<P: 'static> ServiceConfig<P> {
/// Crate server settings instance
pub(crate) fn new(config: AppConfig, default: Rc<HttpNewService<P>>) -> Self {
ServiceConfig {
config,
default,
root: true,
services: Vec::new(),
}
}
/// Check if root is beeing configured
pub fn is_root(&self) -> bool {
self.root
}
pub(crate) fn into_services(
self,
) -> Vec<(
ResourceDef,
HttpNewService<P>,
Option<Guards>,
Option<Rc<ResourceMap>>,
)> {
self.services
}
pub(crate) fn clone_config(&self) -> Self {
ServiceConfig {
config: self.config.clone(),
default: self.default.clone(),
services: Vec::new(),
root: false,
}
}
/// Service configuration
pub fn config(&self) -> &AppConfig {
&self.config
}
/// Default resource
pub fn default_service(&self) -> Rc<HttpNewService<P>> {
self.default.clone()
}
pub fn register_service<F, S>(
&mut self,
rdef: ResourceDef,
guards: Option<Vec<Box<Guard>>>,
service: F,
nested: Option<Rc<ResourceMap>>,
) where
F: IntoNewService<S>,
S: NewService<
Request = ServiceRequest<P>,
Response = ServiceResponse,
Error = Error,
InitError = (),
> + 'static,
{
self.services.push((
rdef,
boxed::new_service(service.into_new_service()),
guards,
nested,
));
}
}
#[derive(Clone)]
pub struct AppConfig(pub(crate) Rc<AppConfigInner>);
impl AppConfig {
pub(crate) fn new(inner: AppConfigInner) -> Self {
AppConfig(Rc::new(inner))
}
/// 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.
///
/// By default host name is set to a "localhost" value.
pub fn host(&self) -> &str {
&self.0.host
}
/// Returns true if connection is secure(https)
pub fn secure(&self) -> bool {
self.0.secure
}
/// Returns the socket address of the local half of this TCP connection
pub fn local_addr(&self) -> SocketAddr {
self.0.addr
}
/// Resource map
pub fn rmap(&self) -> &ResourceMap {
&self.0.rmap
}
/// Application extensions
pub fn extensions(&self) -> Ref<Extensions> {
self.0.extensions.borrow()
}
}
pub(crate) struct AppConfigInner {
pub(crate) secure: bool,
pub(crate) host: String,
pub(crate) addr: SocketAddr,
pub(crate) rmap: ResourceMap,
pub(crate) extensions: RefCell<Extensions>,
}
impl Default for AppConfigInner {
fn default() -> AppConfigInner {
AppConfigInner {
secure: false,
addr: "127.0.0.1:8080".parse().unwrap(),
host: "localhost:8080".to_owned(),
rmap: ResourceMap::new(ResourceDef::new("")),
extensions: RefCell::new(Extensions::new()),
}
}
}

View File

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

299
src/data.rs Normal file
View File

@ -0,0 +1,299 @@
use std::ops::Deref;
use std::sync::Arc;
use actix_http::error::{Error, ErrorInternalServerError};
use actix_http::Extensions;
use futures::{Async, Future, IntoFuture, Poll};
use crate::extract::FromRequest;
use crate::service::ServiceFromRequest;
/// Application data factory
pub(crate) trait DataFactory {
fn construct(&self) -> Box<DataFactoryResult>;
}
pub(crate) trait DataFactoryResult {
fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()>;
}
/// Application data.
///
/// Application data is an arbitrary data attached to the app.
/// Application data is available to all routes and could be added
/// during application configuration process
/// with `App::data()` method.
///
/// Applicatin data could be accessed by using `Data<T>`
/// extractor where `T` is data type.
///
/// **Note**: http server accepts an application factory rather than
/// an application instance. Http server constructs an application
/// instance for each thread, thus application data must be constructed
/// multiple times. If you want to share data between different
/// threads, a shared object should be used, e.g. `Arc`. Application
/// data does not need to be `Send` or `Sync`.
///
/// If route data is not set for a handler, using `Data<T>` extractor would
/// cause *Internal Server Error* response.
///
/// ```rust
/// use std::cell::Cell;
/// use actix_web::{web, App};
///
/// struct MyData {
/// counter: Cell<usize>,
/// }
///
/// /// Use `Data<T>` extractor to access data in handler.
/// fn index(data: web::Data<MyData>) {
/// data.counter.set(data.counter.get() + 1);
/// }
///
/// fn main() {
/// let app = App::new()
/// // Store `MyData` in application storage.
/// .data(MyData{ counter: Cell::new(0) })
/// .service(
/// web::resource("/index.html").route(
/// web::get().to(index)));
/// }
/// ```
pub struct Data<T>(Arc<T>);
impl<T> Data<T> {
pub(crate) fn new(state: T) -> Data<T> {
Data(Arc::new(state))
}
/// Get referecnce to inner app data.
pub fn get_ref(&self) -> &T {
self.0.as_ref()
}
}
impl<T> Deref for Data<T> {
type Target = T;
fn deref(&self) -> &T {
self.0.as_ref()
}
}
impl<T> Clone for Data<T> {
fn clone(&self) -> Data<T> {
Data(self.0.clone())
}
}
impl<T: 'static, P> FromRequest<P> for Data<T> {
type Error = Error;
type Future = Result<Self, Error>;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
if let Some(st) = req.config().extensions().get::<Data<T>>() {
Ok(st.clone())
} else {
Err(ErrorInternalServerError(
"App data is not configured, to configure use App::data()",
))
}
}
}
impl<T: 'static> DataFactory for Data<T> {
fn construct(&self) -> Box<DataFactoryResult> {
Box::new(DataFut { st: self.clone() })
}
}
struct DataFut<T> {
st: Data<T>,
}
impl<T: 'static> DataFactoryResult for DataFut<T> {
fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()> {
extensions.insert(self.st.clone());
Ok(Async::Ready(()))
}
}
impl<F, Out> DataFactory for F
where
F: Fn() -> Out + 'static,
Out: IntoFuture + 'static,
Out::Error: std::fmt::Debug,
{
fn construct(&self) -> Box<DataFactoryResult> {
Box::new(DataFactoryFut {
fut: (*self)().into_future(),
})
}
}
struct DataFactoryFut<T, F>
where
F: Future<Item = T>,
F::Error: std::fmt::Debug,
{
fut: F,
}
impl<T: 'static, F> DataFactoryResult for DataFactoryFut<T, F>
where
F: Future<Item = T>,
F::Error: std::fmt::Debug,
{
fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()> {
match self.fut.poll() {
Ok(Async::Ready(s)) => {
extensions.insert(Data::new(s));
Ok(Async::Ready(()))
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => {
log::error!("Can not construct application state: {:?}", e);
Err(())
}
}
}
}
/// Route data.
///
/// Route data is an arbitrary data attached to specific route.
/// Route data could be added to route during route configuration process
/// with `Route::data()` method. Route data is also used as an extractor
/// configuration storage. Route data could be accessed in handler
/// via `RouteData<T>` extractor.
///
/// If route data is not set for a handler, using `RouteData` extractor
/// would cause *Internal Server Error* response.
///
/// ```rust
/// # use std::cell::Cell;
/// use actix_web::{web, App};
///
/// struct MyData {
/// counter: Cell<usize>,
/// }
///
/// /// Use `RouteData<T>` extractor to access data in handler.
/// fn index(data: web::RouteData<MyData>) {
/// data.counter.set(data.counter.get() + 1);
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html").route(
/// web::get()
/// // Store `MyData` in route storage
/// .data(MyData{ counter: Cell::new(0) })
/// // Route data could be used as extractor configuration storage,
/// // limit size of the payload
/// .data(web::PayloadConfig::new(4096))
/// // register handler
/// .to(index)
/// ));
/// }
/// ```
pub struct RouteData<T>(Arc<T>);
impl<T> RouteData<T> {
pub(crate) fn new(state: T) -> RouteData<T> {
RouteData(Arc::new(state))
}
/// Get referecnce to inner data object.
pub fn get_ref(&self) -> &T {
self.0.as_ref()
}
}
impl<T> Deref for RouteData<T> {
type Target = T;
fn deref(&self) -> &T {
self.0.as_ref()
}
}
impl<T> Clone for RouteData<T> {
fn clone(&self) -> RouteData<T> {
RouteData(self.0.clone())
}
}
impl<T: 'static, P> FromRequest<P> for RouteData<T> {
type Error = Error;
type Future = Result<Self, Error>;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
if let Some(st) = req.route_data::<T>() {
Ok(st.clone())
} else {
Err(ErrorInternalServerError(
"Route data is not configured, to configure use Route::data()",
))
}
}
}
#[cfg(test)]
mod tests {
use actix_service::Service;
use crate::http::StatusCode;
use crate::test::{block_on, init_service, TestRequest};
use crate::{web, App, HttpResponse};
#[test]
fn test_data_extractor() {
let mut srv =
init_service(App::new().data(10usize).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let mut srv =
init_service(App::new().data(10u32).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn test_route_data_extractor() {
let mut srv = init_service(App::new().service(web::resource("/").route(
web::get().data(10usize).to(|data: web::RouteData<usize>| {
let _ = data.clone();
HttpResponse::Ok()
}),
)));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
// different type
let mut srv = init_service(
App::new().service(
web::resource("/").route(
web::get()
.data(10u32)
.to(|_: web::RouteData<usize>| HttpResponse::Ok()),
),
),
);
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
}

455
src/de.rs
View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,114 +0,0 @@
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::fmt;
use std::hash::{BuildHasherDefault, Hasher};
struct IdHasher {
id: u64,
}
impl Default for IdHasher {
fn default() -> IdHasher {
IdHasher { id: 0 }
}
}
impl Hasher for IdHasher {
fn write(&mut self, bytes: &[u8]) {
for &x in bytes {
self.id.wrapping_add(u64::from(x));
}
}
fn write_u64(&mut self, u: u64) {
self.id = u;
}
fn finish(&self) -> u64 {
self.id
}
}
type AnyMap = HashMap<TypeId, Box<Any>, BuildHasherDefault<IdHasher>>;
#[derive(Default)]
/// A type map of request extensions.
pub struct Extensions {
map: AnyMap,
}
impl Extensions {
/// Create an empty `Extensions`.
#[inline]
pub fn new() -> Extensions {
Extensions {
map: HashMap::default(),
}
}
/// Insert a type into this `Extensions`.
///
/// If a extension of this type already existed, it will
/// be returned.
pub fn insert<T: 'static>(&mut self, val: T) {
self.map.insert(TypeId::of::<T>(), Box::new(val));
}
/// Get a reference to a type previously inserted on this `Extensions`.
pub fn get<T: 'static>(&self) -> Option<&T> {
self.map
.get(&TypeId::of::<T>())
.and_then(|boxed| (&**boxed as &(Any + 'static)).downcast_ref())
}
/// Get a mutable reference to a type previously inserted on this `Extensions`.
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.map
.get_mut(&TypeId::of::<T>())
.and_then(|boxed| (&mut **boxed as &mut (Any + 'static)).downcast_mut())
}
/// Remove a type from this `Extensions`.
///
/// If a extension of this type existed, it will be returned.
pub fn remove<T: 'static>(&mut self) -> Option<T> {
self.map.remove(&TypeId::of::<T>()).and_then(|boxed| {
(boxed as Box<Any + 'static>)
.downcast()
.ok()
.map(|boxed| *boxed)
})
}
/// Clear the `Extensions` of all inserted extensions.
#[inline]
pub fn clear(&mut self) {
self.map.clear();
}
}
impl fmt::Debug for Extensions {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Extensions").finish()
}
}
#[test]
fn test_extensions() {
#[derive(Debug, PartialEq)]
struct MyType(i32);
let mut extensions = Extensions::new();
extensions.insert(5i32);
extensions.insert(MyType(10));
assert_eq!(extensions.get(), Some(&5i32));
assert_eq!(extensions.get_mut(), Some(&mut 5i32));
assert_eq!(extensions.remove::<i32>(), Some(5i32));
assert!(extensions.get::<i32>().is_none());
assert_eq!(extensions.get::<bool>(), None);
assert_eq!(extensions.get(), Some(&MyType(10)));
}

372
src/extract.rs Normal file
View File

@ -0,0 +1,372 @@
//! Request extractors
use actix_http::error::Error;
use futures::future::ok;
use futures::{future, Async, Future, IntoFuture, Poll};
use crate::service::ServiceFromRequest;
/// Trait implemented by types that can be extracted from request.
///
/// Types that implement this trait can be used with `Route` handlers.
pub trait FromRequest<P>: Sized {
/// The associated error which can be returned.
type Error: Into<Error>;
/// Future that resolves to a Self
type Future: IntoFuture<Item = Self, Error = Self::Error>;
/// Convert request to a Self
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future;
}
/// Optionally extract a field from the request
///
/// If the FromRequest for T fails, return None rather than returning an error response
///
/// ## Example
///
/// ```rust
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{web, dev, App, Error, FromRequest};
/// use actix_web::error::ErrorBadRequest;
/// use rand;
///
/// #[derive(Debug, Deserialize)]
/// struct Thing {
/// name: String
/// }
///
/// impl<P> FromRequest<P> for Thing {
/// type Error = Error;
/// type Future = Result<Self, Self::Error>;
///
/// fn from_request(req: &mut dev::ServiceFromRequest<P>) -> Self::Future {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// }
///
/// }
/// }
///
/// /// extract `Thing` from request
/// fn index(supplied_thing: Option<Thing>) -> String {
/// match supplied_thing {
/// // Puns not intended
/// Some(thing) => format!("Got something: {:?}", thing),
/// None => format!("No thing!")
/// }
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/users/:first").route(
/// web::post().to(index))
/// );
/// }
/// ```
impl<T: 'static, P> FromRequest<P> for Option<T>
where
T: FromRequest<P>,
T::Future: 'static,
{
type Error = Error;
type Future = Box<Future<Item = Option<T>, Error = Error>>;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
Box::new(T::from_request(req).into_future().then(|r| match r {
Ok(v) => future::ok(Some(v)),
Err(e) => {
log::debug!("Error for Option<T> extractor: {}", e.into());
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
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{web, dev, App, Result, Error, FromRequest};
/// use actix_web::error::ErrorBadRequest;
/// use rand;
///
/// #[derive(Debug, Deserialize)]
/// struct Thing {
/// name: String
/// }
///
/// impl<P> FromRequest<P> for Thing {
/// type Error = Error;
/// type Future = Result<Thing, Error>;
///
/// fn from_request(req: &mut dev::ServiceFromRequest<P>) -> Self::Future {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// }
/// }
/// }
///
/// /// extract `Thing` from request
/// fn index(supplied_thing: Result<Thing>) -> String {
/// match supplied_thing {
/// Ok(thing) => format!("Got thing: {:?}", thing),
/// Err(e) => format!("Error extracting thing: {}", e)
/// }
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/users/:first").route(web::post().to(index))
/// );
/// }
/// ```
impl<T: 'static, P> FromRequest<P> for Result<T, T::Error>
where
T: FromRequest<P>,
T::Future: 'static,
T::Error: 'static,
{
type Error = Error;
type Future = Box<Future<Item = Result<T, T::Error>, Error = Error>>;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
Box::new(T::from_request(req).into_future().then(|res| match res {
Ok(v) => ok(Ok(v)),
Err(e) => ok(Err(e)),
}))
}
}
#[doc(hidden)]
impl<P> FromRequest<P> for () {
type Error = Error;
type Future = Result<(), Error>;
fn from_request(_req: &mut ServiceFromRequest<P>) -> Self::Future {
Ok(())
}
}
macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
/// FromRequest implementation for tuple
#[doc(hidden)]
impl<P, $($T: FromRequest<P> + 'static),+> FromRequest<P> for ($($T,)+)
{
type Error = Error;
type Future = $fut_type<P, $($T),+>;
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
$fut_type {
items: <($(Option<$T>,)+)>::default(),
futs: ($($T::from_request(req).into_future(),)+),
}
}
}
#[doc(hidden)]
pub struct $fut_type<P, $($T: FromRequest<P>),+> {
items: ($(Option<$T>,)+),
futs: ($(<$T::Future as futures::IntoFuture>::Future,)+),
}
impl<P, $($T: FromRequest<P>),+> Future for $fut_type<P, $($T),+>
{
type Item = ($($T,)+);
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut ready = true;
$(
if self.items.$n.is_none() {
match self.futs.$n.poll() {
Ok(Async::Ready(item)) => {
self.items.$n = Some(item);
}
Ok(Async::NotReady) => ready = false,
Err(e) => return Err(e.into()),
}
}
)+
if ready {
Ok(Async::Ready(
($(self.items.$n.take().unwrap(),)+)
))
} else {
Ok(Async::NotReady)
}
}
}
});
#[rustfmt::skip]
mod m {
use super::*;
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));
tuple_from_req!(TupleFromRequest10, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J));
}
#[cfg(test)]
mod tests {
use actix_http::http::header;
use actix_router::ResourceDef;
use bytes::Bytes;
use serde_derive::Deserialize;
use super::*;
use crate::test::{block_on, TestRequest};
use crate::types::{Form, FormConfig, Path, Query};
#[derive(Deserialize, Debug, PartialEq)]
struct Info {
hello: String,
}
#[test]
fn test_option() {
let mut req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.route_data(FormConfig::default().limit(4096))
.to_from();
let r = block_on(Option::<Form<Info>>::from_request(&mut req)).unwrap();
assert_eq!(r, None);
let mut 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"))
.to_from();
let r = block_on(Option::<Form<Info>>::from_request(&mut req)).unwrap();
assert_eq!(
r,
Some(Form(Info {
hello: "world".into()
}))
);
let mut 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"))
.to_from();
let r = block_on(Option::<Form<Info>>::from_request(&mut req)).unwrap();
assert_eq!(r, None);
}
#[test]
fn test_result() {
let mut 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"))
.to_from();
let r = block_on(Result::<Form<Info>, Error>::from_request(&mut req))
.unwrap()
.unwrap();
assert_eq!(
r,
Form(Info {
hello: "world".into()
})
);
let mut 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"))
.to_from();
let r = block_on(Result::<Form<Info>, Error>::from_request(&mut req)).unwrap();
assert!(r.is_err());
}
#[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 mut req = TestRequest::with_uri("/name/user1/?id=test").to_from();
let resource = ResourceDef::new("/{key}/{value}/");
resource.match_path(req.match_info_mut());
let s = Path::<MyStruct>::from_request(&mut req).unwrap();
assert_eq!(s.key, "name");
assert_eq!(s.value, "user1");
let s = Path::<(String, String)>::from_request(&mut req).unwrap();
assert_eq!(s.0, "name");
assert_eq!(s.1, "user1");
let s = Query::<Id>::from_request(&mut req).unwrap();
assert_eq!(s.id, "test");
let mut req = TestRequest::with_uri("/name/32/").to_from();
let resource = ResourceDef::new("/{key}/{value}/");
resource.match_path(req.match_info_mut());
let s = Path::<Test2>::from_request(&mut req).unwrap();
assert_eq!(s.as_ref().key, "name");
assert_eq!(s.value, 32);
let s = Path::<(String, u8)>::from_request(&mut req).unwrap();
assert_eq!(s.0, "name");
assert_eq!(s.1, 32);
let res = Path::<Vec<String>>::from_request(&mut req).unwrap();
assert_eq!(res[0], "name".to_owned());
assert_eq!(res[1], "32".to_owned());
}
}

File diff suppressed because it is too large Load Diff

1927
src/fs.rs

File diff suppressed because it is too large Load Diff

375
src/guard.rs Normal file
View File

@ -0,0 +1,375 @@
//! Route match guards.
//!
//! Guards are one of the way how actix-web router chooses
//! handler service. In essence it just function that accepts
//! reference to a `RequestHead` instance and returns boolean.
//! It is possible to add guards to *scopes*, *resources*
//! and *routes*. Actix provide several guards by default, like various
//! http methods, header, etc. To become a guard, type must implement `Guard`
//! trait. Simple functions coulds guards as well.
//!
//! Guard can not modify request object. But it is possible to
//! to store extra attributes on a request by using `Extensions` container.
//! Extensions container available via `RequestHead::extensions()` method.
//!
//! ```rust
//! use actix_web::{web, http, dev, guard, App, HttpResponse};
//!
//! fn main() {
//! App::new().service(web::resource("/index.html").route(
//! web::route()
//! .guard(guard::Post())
//! .guard(|head: &dev::RequestHead| head.method == http::Method::GET)
//! .to(|| HttpResponse::MethodNotAllowed()))
//! );
//! }
//! ```
#![allow(non_snake_case)]
use actix_http::http::{self, header, HttpTryFrom};
use actix_http::RequestHead;
/// Trait defines resource guards. Guards are used for routes selection.
///
/// Guard can not modify request object. But it is possible to
/// to store extra attributes on request by using `Extensions` container,
/// Extensions container available via `RequestHead::extensions()` method.
pub trait Guard {
/// Check if request matches predicate
fn check(&self, request: &RequestHead) -> bool;
}
#[doc(hidden)]
pub struct FnGuard<F: Fn(&RequestHead) -> bool + 'static>(F);
impl<F> Guard for F
where
F: Fn(&RequestHead) -> bool + 'static,
{
fn check(&self, head: &RequestHead) -> bool {
(*self)(head)
}
}
/// Return guard that matches if any of supplied guards.
///
/// ```rust
/// use actix_web::{web, guard, App, HttpResponse};
///
/// fn main() {
/// App::new().service(web::resource("/index.html").route(
/// web::route()
/// .guard(guard::Any(guard::Get()).or(guard::Post()))
/// .to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
pub fn Any<F: Guard + 'static>(guard: F) -> AnyGuard {
AnyGuard(vec![Box::new(guard)])
}
/// Matches if any of supplied guards matche.
pub struct AnyGuard(Vec<Box<Guard>>);
impl AnyGuard {
/// Add guard to a list of guards to check
pub fn or<F: Guard + 'static>(mut self, guard: F) -> Self {
self.0.push(Box::new(guard));
self
}
}
impl Guard for AnyGuard {
fn check(&self, req: &RequestHead) -> bool {
for p in &self.0 {
if p.check(req) {
return true;
}
}
false
}
}
/// Return guard that matches if all of the supplied guards.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{guard, web, App, HttpResponse};
///
/// fn main() {
/// App::new().service(web::resource("/index.html").route(
/// web::route()
/// .guard(
/// guard::All(guard::Get()).and(guard::Header("content-type", "text/plain")))
/// .to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
pub fn All<F: Guard + 'static>(guard: F) -> AllGuard {
AllGuard(vec![Box::new(guard)])
}
/// Matches if all of supplied guards.
pub struct AllGuard(Vec<Box<Guard>>);
impl AllGuard {
/// Add new guard to the list of guards to check
pub fn and<F: Guard + 'static>(mut self, guard: F) -> Self {
self.0.push(Box::new(guard));
self
}
}
impl Guard for AllGuard {
fn check(&self, request: &RequestHead) -> bool {
for p in &self.0 {
if !p.check(request) {
return false;
}
}
true
}
}
/// Return guard that matches if supplied guard does not match.
pub fn Not<F: Guard + 'static>(guard: F) -> NotGuard {
NotGuard(Box::new(guard))
}
#[doc(hidden)]
pub struct NotGuard(Box<Guard>);
impl Guard for NotGuard {
fn check(&self, request: &RequestHead) -> bool {
!self.0.check(request)
}
}
/// Http method guard
#[doc(hidden)]
pub struct MethodGuard(http::Method);
impl Guard for MethodGuard {
fn check(&self, request: &RequestHead) -> bool {
request.method == self.0
}
}
/// Guard to match *GET* http method
pub fn Get() -> MethodGuard {
MethodGuard(http::Method::GET)
}
/// Predicate to match *POST* http method
pub fn Post() -> MethodGuard {
MethodGuard(http::Method::POST)
}
/// Predicate to match *PUT* http method
pub fn Put() -> MethodGuard {
MethodGuard(http::Method::PUT)
}
/// Predicate to match *DELETE* http method
pub fn Delete() -> MethodGuard {
MethodGuard(http::Method::DELETE)
}
/// Predicate to match *HEAD* http method
pub fn Head() -> MethodGuard {
MethodGuard(http::Method::HEAD)
}
/// Predicate to match *OPTIONS* http method
pub fn Options() -> MethodGuard {
MethodGuard(http::Method::OPTIONS)
}
/// Predicate to match *CONNECT* http method
pub fn Connect() -> MethodGuard {
MethodGuard(http::Method::CONNECT)
}
/// Predicate to match *PATCH* http method
pub fn Patch() -> MethodGuard {
MethodGuard(http::Method::PATCH)
}
/// Predicate to match *TRACE* http method
pub fn Trace() -> MethodGuard {
MethodGuard(http::Method::TRACE)
}
/// Predicate to match specified http method
pub fn Method(method: http::Method) -> MethodGuard {
MethodGuard(method)
}
/// Return predicate that matches if request contains specified header and
/// value.
pub fn Header(name: &'static str, value: &'static str) -> HeaderGuard {
HeaderGuard(
header::HeaderName::try_from(name).unwrap(),
header::HeaderValue::from_static(value),
)
}
#[doc(hidden)]
pub struct HeaderGuard(header::HeaderName, header::HeaderValue);
impl Guard for HeaderGuard {
fn check(&self, req: &RequestHead) -> bool {
if let Some(val) = req.headers.get(&self.0) {
return val == self.1;
}
false
}
}
// /// Return predicate that matches if request contains specified Host name.
// ///
// /// ```rust,ignore
// /// # extern crate actix_web;
// /// use actix_web::{pred, App, HttpResponse};
// ///
// /// fn main() {
// /// App::new().resource("/index.html", |r| {
// /// r.route()
// /// .guard(pred::Host("www.rust-lang.org"))
// /// .f(|_| HttpResponse::MethodNotAllowed())
// /// });
// /// }
// /// ```
// pub fn Host<H: AsRef<str>>(host: H) -> HostGuard {
// HostGuard(host.as_ref().to_string(), None)
// }
// #[doc(hidden)]
// pub struct HostGuard(String, Option<String>);
// impl HostGuard {
// /// Set reuest scheme to match
// pub fn scheme<H: AsRef<str>>(&mut self, scheme: H) {
// self.1 = Some(scheme.as_ref().to_string())
// }
// }
// impl Guard for HostGuard {
// fn check(&self, _req: &RequestHead) -> bool {
// // let info = req.connection_info();
// // if let Some(ref scheme) = self.1 {
// // self.0 == info.host() && scheme == info.scheme()
// // } else {
// // self.0 == info.host()
// // }
// false
// }
// }
#[cfg(test)]
mod tests {
use actix_http::http::{header, Method};
use super::*;
use crate::test::TestRequest;
#[test]
fn test_header() {
let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked")
.to_http_request();
let pred = Header("transfer-encoding", "chunked");
assert!(pred.check(&req));
let pred = Header("transfer-encoding", "other");
assert!(!pred.check(&req));
let pred = Header("content-type", "other");
assert!(!pred.check(&req));
}
// #[test]
// fn test_host() {
// let req = TestServiceRequest::default()
// .header(
// header::HOST,
// header::HeaderValue::from_static("www.rust-lang.org"),
// )
// .request();
// let pred = Host("www.rust-lang.org");
// assert!(pred.check(&req));
// let pred = Host("localhost");
// assert!(!pred.check(&req));
// }
#[test]
fn test_methods() {
let req = TestRequest::default().to_http_request();
let req2 = TestRequest::default()
.method(Method::POST)
.to_http_request();
assert!(Get().check(&req));
assert!(!Get().check(&req2));
assert!(Post().check(&req2));
assert!(!Post().check(&req));
let r = TestRequest::default().method(Method::PUT).to_http_request();
assert!(Put().check(&r));
assert!(!Put().check(&req));
let r = TestRequest::default()
.method(Method::DELETE)
.to_http_request();
assert!(Delete().check(&r));
assert!(!Delete().check(&req));
let r = TestRequest::default()
.method(Method::HEAD)
.to_http_request();
assert!(Head().check(&r));
assert!(!Head().check(&req));
let r = TestRequest::default()
.method(Method::OPTIONS)
.to_http_request();
assert!(Options().check(&r));
assert!(!Options().check(&req));
let r = TestRequest::default()
.method(Method::CONNECT)
.to_http_request();
assert!(Connect().check(&r));
assert!(!Connect().check(&req));
let r = TestRequest::default()
.method(Method::PATCH)
.to_http_request();
assert!(Patch().check(&r));
assert!(!Patch().check(&req));
let r = TestRequest::default()
.method(Method::TRACE)
.to_http_request();
assert!(Trace().check(&r));
assert!(!Trace().check(&req));
}
#[test]
fn test_preds() {
let r = TestRequest::default()
.method(Method::TRACE)
.to_http_request();
assert!(Not(Get()).check(&r));
assert!(!Not(Trace()).check(&r));
assert!(All(Trace()).and(Trace()).check(&r));
assert!(!All(Get()).and(Trace()).check(&r));
assert!(Any(Get()).or(Trace()).check(&r));
assert!(!Any(Get()).or(Get()).check(&r));
}
}

View File

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

View File

@ -1,159 +0,0 @@
use header::{qitem, QualityItem};
use http::header as http;
use mime::{self, Mime};
header! {
/// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2)
///
/// The `Accept` header field can be used by user agents to specify
/// response media types that are acceptable. Accept header fields can
/// be used to indicate that the request is specifically limited to a
/// small set of desired types, as in the case of a request for an
/// in-line image
///
/// # ABNF
///
/// ```text
/// Accept = #( media-range [ accept-params ] )
///
/// media-range = ( "*/*"
/// / ( type "/" "*" )
/// / ( type "/" subtype )
/// ) *( OWS ";" OWS parameter )
/// accept-params = weight *( accept-ext )
/// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ]
/// ```
///
/// # Example values
/// * `audio/*; q=0.2, audio/basic`
/// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c`
///
/// # Examples
/// ```rust
/// # extern crate actix_web;
/// extern crate mime;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{Accept, qitem};
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
///
/// builder.set(
/// Accept(vec![
/// qitem(mime::TEXT_HTML),
/// ])
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate actix_web;
/// extern crate mime;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{Accept, qitem};
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
///
/// builder.set(
/// Accept(vec![
/// qitem(mime::APPLICATION_JSON),
/// ])
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate actix_web;
/// extern crate mime;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{Accept, QualityItem, q, qitem};
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
///
/// builder.set(
/// Accept(vec![
/// qitem(mime::TEXT_HTML),
/// qitem("application/xhtml+xml".parse().unwrap()),
/// QualityItem::new(
/// mime::TEXT_XML,
/// q(900)
/// ),
/// qitem("image/webp".parse().unwrap()),
/// QualityItem::new(
/// mime::STAR_STAR,
/// q(800)
/// ),
/// ])
/// );
/// # }
/// ```
(Accept, http::ACCEPT) => (QualityItem<Mime>)+
test_accept {
// Tests from the RFC
test_header!(
test1,
vec![b"audio/*; q=0.2, audio/basic"],
Some(HeaderField(vec![
QualityItem::new("audio/*".parse().unwrap(), q(200)),
qitem("audio/basic".parse().unwrap()),
])));
test_header!(
test2,
vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
Some(HeaderField(vec![
QualityItem::new(TEXT_PLAIN, q(500)),
qitem(TEXT_HTML),
QualityItem::new(
"text/x-dvi".parse().unwrap(),
q(800)),
qitem("text/x-c".parse().unwrap()),
])));
// Custom tests
test_header!(
test3,
vec![b"text/plain; charset=utf-8"],
Some(Accept(vec![
qitem(TEXT_PLAIN_UTF_8),
])));
test_header!(
test4,
vec![b"text/plain; charset=utf-8; q=0.5"],
Some(Accept(vec![
QualityItem::new(TEXT_PLAIN_UTF_8,
q(500)),
])));
#[test]
fn test_fuzzing1() {
use test::TestRequest;
let req = TestRequest::with_header(super::http::ACCEPT, "chunk#;e").finish();
let header = Accept::parse(&req);
assert!(header.is_ok());
}
}
}
impl Accept {
/// A constructor to easily create `Accept: */*`.
pub fn star() -> Accept {
Accept(vec![qitem(mime::STAR_STAR)])
}
/// A constructor to easily create `Accept: application/json`.
pub fn json() -> Accept {
Accept(vec![qitem(mime::APPLICATION_JSON)])
}
/// A constructor to easily create `Accept: text/*`.
pub fn text() -> Accept {
Accept(vec![qitem(mime::TEXT_STAR)])
}
/// A constructor to easily create `Accept: image/*`.
pub fn image() -> Accept {
Accept(vec![qitem(mime::IMAGE_STAR)])
}
}

View File

@ -1,69 +0,0 @@
use header::{Charset, QualityItem, ACCEPT_CHARSET};
header! {
/// `Accept-Charset` header, defined in
/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3)
///
/// The `Accept-Charset` header field can be sent by a user agent to
/// indicate what charsets are acceptable in textual response content.
/// This field allows user agents capable of understanding more
/// comprehensive or special-purpose charsets to signal that capability
/// to an origin server that is capable of representing information in
/// those charsets.
///
/// # ABNF
///
/// ```text
/// Accept-Charset = 1#( ( charset / "*" ) [ weight ] )
/// ```
///
/// # Example values
/// * `iso-8859-5, unicode-1-1;q=0.8`
///
/// # Examples
/// ```rust
/// # extern crate actix_web;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptCharset, Charset, qitem};
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// AcceptCharset(vec![qitem(Charset::Us_Ascii)])
/// );
/// # }
/// ```
/// ```rust
/// # extern crate actix_web;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptCharset, Charset, q, QualityItem};
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// AcceptCharset(vec![
/// QualityItem::new(Charset::Us_Ascii, q(900)),
/// QualityItem::new(Charset::Iso_8859_10, q(200)),
/// ])
/// );
/// # }
/// ```
/// ```rust
/// # extern crate actix_web;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptCharset, Charset, qitem};
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))])
/// );
/// # }
/// ```
(AcceptCharset, ACCEPT_CHARSET) => (QualityItem<Charset>)+
test_accept_charset {
/// Test case from RFC
test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]);
}
}

View File

@ -1,72 +0,0 @@
use header::{Encoding, QualityItem};
header! {
/// `Accept-Encoding` header, defined in
/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4)
///
/// The `Accept-Encoding` header field can be used by user agents to
/// indicate what response content-codings are
/// acceptable in the response. An `identity` token is used as a synonym
/// for "no encoding" in order to communicate when no encoding is
/// preferred.
///
/// # ABNF
///
/// ```text
/// Accept-Encoding = #( codings [ weight ] )
/// codings = content-coding / "identity" / "*"
/// ```
///
/// # Example values
/// * `compress, gzip`
/// * ``
/// * `*`
/// * `compress;q=0.5, gzip;q=1`
/// * `gzip;q=1.0, identity; q=0.5, *;q=0`
///
/// # Examples
/// ```
/// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem};
///
/// let mut headers = Headers::new();
/// headers.set(
/// AcceptEncoding(vec![qitem(Encoding::Chunked)])
/// );
/// ```
/// ```
/// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem};
///
/// let mut headers = Headers::new();
/// headers.set(
/// AcceptEncoding(vec![
/// qitem(Encoding::Chunked),
/// qitem(Encoding::Gzip),
/// qitem(Encoding::Deflate),
/// ])
/// );
/// ```
/// ```
/// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem};
///
/// let mut headers = Headers::new();
/// headers.set(
/// AcceptEncoding(vec![
/// qitem(Encoding::Chunked),
/// QualityItem::new(Encoding::Gzip, q(600)),
/// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)),
/// ])
/// );
/// ```
(AcceptEncoding, "Accept-Encoding") => (QualityItem<Encoding>)*
test_accept_encoding {
// From the RFC
test_header!(test1, vec![b"compress, gzip"]);
test_header!(test2, vec![b""], Some(AcceptEncoding(vec![])));
test_header!(test3, vec![b"*"]);
// Note: Removed quality 1 from gzip
test_header!(test4, vec![b"compress;q=0.5, gzip"]);
// Note: Removed quality 1 from gzip
test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]);
}
}

View File

@ -1,75 +0,0 @@
use header::{QualityItem, ACCEPT_LANGUAGE};
use language_tags::LanguageTag;
header! {
/// `Accept-Language` header, defined in
/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5)
///
/// The `Accept-Language` header field can be used by user agents to
/// indicate the set of natural languages that are preferred in the
/// response.
///
/// # ABNF
///
/// ```text
/// Accept-Language = 1#( language-range [ weight ] )
/// language-range = <language-range, see [RFC4647], Section 2.1>
/// ```
///
/// # Example values
/// * `da, en-gb;q=0.8, en;q=0.7`
/// * `en-us;q=1.0, en;q=0.5, fr`
///
/// # Examples
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate language_tags;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem};
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// let mut langtag: LanguageTag = Default::default();
/// langtag.language = Some("en".to_owned());
/// langtag.region = Some("US".to_owned());
/// builder.set(
/// AcceptLanguage(vec![
/// qitem(langtag),
/// ])
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate actix_web;
/// # #[macro_use] extern crate language_tags;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem};
/// #
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// AcceptLanguage(vec![
/// qitem(langtag!(da)),
/// QualityItem::new(langtag!(en;;;GB), q(800)),
/// QualityItem::new(langtag!(en), q(700)),
/// ])
/// );
/// # }
/// ```
(AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem<LanguageTag>)+
test_accept_language {
// From the RFC
test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]);
// Own test
test_header!(
test2, vec![b"en-US, en; q=0.5, fr"],
Some(AcceptLanguage(vec![
qitem("en-US".parse().unwrap()),
QualityItem::new("en".parse().unwrap(), q(500)),
qitem("fr".parse().unwrap()),
])));
}
}

View File

@ -1,85 +0,0 @@
use http::Method;
use http::header;
header! {
/// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1)
///
/// The `Allow` header field lists the set of methods advertised as
/// supported by the target resource. The purpose of this field is
/// strictly to inform the recipient of valid request methods associated
/// with the resource.
///
/// # ABNF
///
/// ```text
/// Allow = #method
/// ```
///
/// # Example values
/// * `GET, HEAD, PUT`
/// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr`
/// * ``
///
/// # Examples
///
/// ```rust
/// # extern crate http;
/// # extern crate actix_web;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::Allow;
/// use http::Method;
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// Allow(vec![Method::GET])
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate http;
/// # extern crate actix_web;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::Allow;
/// use http::Method;
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// Allow(vec![
/// Method::GET,
/// Method::POST,
/// Method::PATCH,
/// ])
/// );
/// # }
/// ```
(Allow, header::ALLOW) => (Method)*
test_allow {
// From the RFC
test_header!(
test1,
vec![b"GET, HEAD, PUT"],
Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT])));
// Own tests
test_header!(
test2,
vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"],
Some(HeaderField(vec![
Method::OPTIONS,
Method::GET,
Method::PUT,
Method::POST,
Method::DELETE,
Method::HEAD,
Method::TRACE,
Method::CONNECT,
Method::PATCH])));
test_header!(
test3,
vec![b""],
Some(HeaderField(Vec::<Method>::new())));
}
}

View File

@ -1,254 +0,0 @@
use header::{Header, IntoHeaderValue, Writer};
use header::{fmt_comma_delimited, from_comma_delimited};
use http::header;
use std::fmt::{self, Write};
use std::str::FromStr;
/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2)
///
/// The `Cache-Control` header field is used to specify directives for
/// caches along the request/response chain. Such cache directives are
/// unidirectional in that the presence of a directive in a request does
/// not imply that the same directive is to be given in the response.
///
/// # ABNF
///
/// ```text
/// Cache-Control = 1#cache-directive
/// cache-directive = token [ "=" ( token / quoted-string ) ]
/// ```
///
/// # Example values
///
/// * `no-cache`
/// * `private, community="UCI"`
/// * `max-age=30`
///
/// # Examples
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{CacheControl, CacheDirective};
///
/// let mut builder = HttpResponse::Ok();
/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)]));
/// ```
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{CacheControl, CacheDirective};
///
/// let mut builder = HttpResponse::Ok();
/// builder.set(CacheControl(vec![
/// CacheDirective::NoCache,
/// CacheDirective::Private,
/// CacheDirective::MaxAge(360u32),
/// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())),
/// ]));
/// ```
#[derive(PartialEq, Clone, Debug)]
pub struct CacheControl(pub Vec<CacheDirective>);
__hyper__deref!(CacheControl => Vec<CacheDirective>);
//TODO: this could just be the header! macro
impl Header for CacheControl {
fn name() -> header::HeaderName {
header::CACHE_CONTROL
}
#[inline]
fn parse<T>(msg: &T) -> Result<Self, ::error::ParseError>
where
T: ::HttpMessage,
{
let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?;
if !directives.is_empty() {
Ok(CacheControl(directives))
} else {
Err(::error::ParseError::Header)
}
}
}
impl fmt::Display for CacheControl {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt_comma_delimited(f, &self[..])
}
}
impl IntoHeaderValue for CacheControl {
type Error = header::InvalidHeaderValueBytes;
fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self);
header::HeaderValue::from_shared(writer.take())
}
}
/// `CacheControl` contains a list of these directives.
#[derive(PartialEq, Clone, Debug)]
pub enum CacheDirective {
/// "no-cache"
NoCache,
/// "no-store"
NoStore,
/// "no-transform"
NoTransform,
/// "only-if-cached"
OnlyIfCached,
// request directives
/// "max-age=delta"
MaxAge(u32),
/// "max-stale=delta"
MaxStale(u32),
/// "min-fresh=delta"
MinFresh(u32),
// response directives
/// "must-revalidate"
MustRevalidate,
/// "public"
Public,
/// "private"
Private,
/// "proxy-revalidate"
ProxyRevalidate,
/// "s-maxage=delta"
SMaxAge(u32),
/// Extension directives. Optionally include an argument.
Extension(String, Option<String>),
}
impl fmt::Display for CacheDirective {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::CacheDirective::*;
fmt::Display::fmt(
match *self {
NoCache => "no-cache",
NoStore => "no-store",
NoTransform => "no-transform",
OnlyIfCached => "only-if-cached",
MaxAge(secs) => return write!(f, "max-age={}", secs),
MaxStale(secs) => return write!(f, "max-stale={}", secs),
MinFresh(secs) => return write!(f, "min-fresh={}", secs),
MustRevalidate => "must-revalidate",
Public => "public",
Private => "private",
ProxyRevalidate => "proxy-revalidate",
SMaxAge(secs) => return write!(f, "s-maxage={}", secs),
Extension(ref name, None) => &name[..],
Extension(ref name, Some(ref arg)) => {
return write!(f, "{}={}", name, arg)
}
},
f,
)
}
}
impl FromStr for CacheDirective {
type Err = Option<<u32 as FromStr>::Err>;
fn from_str(s: &str) -> Result<CacheDirective, Option<<u32 as FromStr>::Err>> {
use self::CacheDirective::*;
match s {
"no-cache" => Ok(NoCache),
"no-store" => Ok(NoStore),
"no-transform" => Ok(NoTransform),
"only-if-cached" => Ok(OnlyIfCached),
"must-revalidate" => Ok(MustRevalidate),
"public" => Ok(Public),
"private" => Ok(Private),
"proxy-revalidate" => Ok(ProxyRevalidate),
"" => Err(None),
_ => match s.find('=') {
Some(idx) if idx + 1 < s.len() => {
match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) {
("max-age", secs) => secs.parse().map(MaxAge).map_err(Some),
("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some),
("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some),
("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some),
(left, right) => {
Ok(Extension(left.to_owned(), Some(right.to_owned())))
}
}
}
Some(_) => Err(None),
None => Ok(Extension(s.to_owned(), None)),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use header::Header;
use test::TestRequest;
#[test]
fn test_parse_multiple_headers() {
let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private")
.finish();
let cache = Header::parse(&req);
assert_eq!(
cache.ok(),
Some(CacheControl(vec![
CacheDirective::NoCache,
CacheDirective::Private,
]))
)
}
#[test]
fn test_parse_argument() {
let req =
TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private")
.finish();
let cache = Header::parse(&req);
assert_eq!(
cache.ok(),
Some(CacheControl(vec![
CacheDirective::MaxAge(100),
CacheDirective::Private,
]))
)
}
#[test]
fn test_parse_quote_form() {
let req =
TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish();
let cache = Header::parse(&req);
assert_eq!(
cache.ok(),
Some(CacheControl(vec![CacheDirective::MaxAge(200)]))
)
}
#[test]
fn test_parse_extension() {
let req =
TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish();
let cache = Header::parse(&req);
assert_eq!(
cache.ok(),
Some(CacheControl(vec![
CacheDirective::Extension("foo".to_owned(), None),
CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())),
]))
)
}
#[test]
fn test_parse_bad_syntax() {
let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish();
let cache: Result<CacheControl, _> = Header::parse(&req);
assert_eq!(cache.ok(), None)
}
}

View File

@ -1,914 +0,0 @@
// # References
//
// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt
// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt
// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt
// Browser conformance tests at: http://greenbytes.de/tech/tc2231/
// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml
use header;
use header::ExtendedValue;
use header::{Header, IntoHeaderValue, Writer};
use regex::Regex;
use std::fmt::{self, Write};
/// Split at the index of the first `needle` if it exists or at the end.
fn split_once(haystack: &str, needle: char) -> (&str, &str) {
haystack.find(needle).map_or_else(
|| (haystack, ""),
|sc| {
let (first, last) = haystack.split_at(sc);
(first, last.split_at(1).1)
},
)
}
/// Split at the index of the first `needle` if it exists or at the end, trim the right of the
/// first part and the left of the last part.
fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) {
let (first, last) = split_once(haystack, needle);
(first.trim_right(), last.trim_left())
}
/// The implied disposition of the content of the HTTP body.
#[derive(Clone, Debug, PartialEq)]
pub enum DispositionType {
/// Inline implies default processing
Inline,
/// Attachment implies that the recipient should prompt the user to save the response locally,
/// rather than process it normally (as per its media type).
Attachment,
/// Used in *multipart/form-data* as defined in
/// [RFC7578](https://tools.ietf.org/html/rfc7578) to carry the field name and the file name.
FormData,
/// Extension type. Should be handled by recipients the same way as Attachment
Ext(String),
}
impl<'a> From<&'a str> for DispositionType {
fn from(origin: &'a str) -> DispositionType {
if origin.eq_ignore_ascii_case("inline") {
DispositionType::Inline
} else if origin.eq_ignore_ascii_case("attachment") {
DispositionType::Attachment
} else if origin.eq_ignore_ascii_case("form-data") {
DispositionType::FormData
} else {
DispositionType::Ext(origin.to_owned())
}
}
}
/// Parameter in [`ContentDisposition`].
///
/// # Examples
/// ```
/// use actix_web::http::header::DispositionParam;
///
/// let param = DispositionParam::Filename(String::from("sample.txt"));
/// assert!(param.is_filename());
/// assert_eq!(param.as_filename().unwrap(), "sample.txt");
/// ```
#[derive(Clone, Debug, PartialEq)]
pub enum DispositionParam {
/// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from
/// the form.
Name(String),
/// A plain file name.
Filename(String),
/// An extended file name. It must not exist for `ContentType::Formdata` according to
/// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2).
FilenameExt(ExtendedValue),
/// An unrecognized regular parameter as defined in
/// [RFC5987](https://tools.ietf.org/html/rfc5987) as *reg-parameter*, in
/// [RFC6266](https://tools.ietf.org/html/rfc6266) as *token "=" value*. Recipients should
/// ignore unrecognizable parameters.
Unknown(String, String),
/// An unrecognized extended paramater as defined in
/// [RFC5987](https://tools.ietf.org/html/rfc5987) as *ext-parameter*, in
/// [RFC6266](https://tools.ietf.org/html/rfc6266) as *ext-token "=" ext-value*. The single
/// trailling asterisk is not included. Recipients should ignore unrecognizable parameters.
UnknownExt(String, ExtendedValue),
}
impl DispositionParam {
/// Returns `true` if the paramater is [`Name`](DispositionParam::Name).
#[inline]
pub fn is_name(&self) -> bool {
self.as_name().is_some()
}
/// Returns `true` if the paramater is [`Filename`](DispositionParam::Filename).
#[inline]
pub fn is_filename(&self) -> bool {
self.as_filename().is_some()
}
/// Returns `true` if the paramater is [`FilenameExt`](DispositionParam::FilenameExt).
#[inline]
pub fn is_filename_ext(&self) -> bool {
self.as_filename_ext().is_some()
}
/// Returns `true` if the paramater is [`Unknown`](DispositionParam::Unknown) and the `name`
#[inline]
/// matches.
pub fn is_unknown<T: AsRef<str>>(&self, name: T) -> bool {
self.as_unknown(name).is_some()
}
/// Returns `true` if the paramater is [`UnknownExt`](DispositionParam::UnknownExt) and the
/// `name` matches.
#[inline]
pub fn is_unknown_ext<T: AsRef<str>>(&self, name: T) -> bool {
self.as_unknown_ext(name).is_some()
}
/// Returns the name if applicable.
#[inline]
pub fn as_name(&self) -> Option<&str> {
match self {
DispositionParam::Name(ref name) => Some(name.as_str()),
_ => None,
}
}
/// Returns the filename if applicable.
#[inline]
pub fn as_filename(&self) -> Option<&str> {
match self {
DispositionParam::Filename(ref filename) => Some(filename.as_str()),
_ => None,
}
}
/// Returns the filename* if applicable.
#[inline]
pub fn as_filename_ext(&self) -> Option<&ExtendedValue> {
match self {
DispositionParam::FilenameExt(ref value) => Some(value),
_ => None,
}
}
/// Returns the value of the unrecognized regular parameter if it is
/// [`Unknown`](DispositionParam::Unknown) and the `name` matches.
#[inline]
pub fn as_unknown<T: AsRef<str>>(&self, name: T) -> Option<&str> {
match self {
DispositionParam::Unknown(ref ext_name, ref value)
if ext_name.eq_ignore_ascii_case(name.as_ref()) =>
{
Some(value.as_str())
}
_ => None,
}
}
/// Returns the value of the unrecognized extended parameter if it is
/// [`Unknown`](DispositionParam::Unknown) and the `name` matches.
#[inline]
pub fn as_unknown_ext<T: AsRef<str>>(&self, name: T) -> Option<&ExtendedValue> {
match self {
DispositionParam::UnknownExt(ref ext_name, ref value)
if ext_name.eq_ignore_ascii_case(name.as_ref()) =>
{
Some(value)
}
_ => None,
}
}
}
/// A *Content-Disposition* header. It is compatible to be used either as
/// [a response header for the main body](https://mdn.io/Content-Disposition#As_a_response_header_for_the_main_body)
/// as (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266), or as
/// [a header for a multipart body](https://mdn.io/Content-Disposition#As_a_header_for_a_multipart_body)
/// as (re)defined in [RFC7587](https://tools.ietf.org/html/rfc7578).
///
/// In a regular HTTP response, the *Content-Disposition* response header is a header indicating if
/// the content is expected to be displayed *inline* in the browser, that is, as a Web page or as
/// part of a Web page, or as an attachment, that is downloaded and saved locally, and also can be
/// used to attach additional metadata, such as the filename to use when saving the response payload
/// locally.
///
/// In a *multipart/form-data* body, the HTTP *Content-Disposition* general header is a header that
/// can be used on the subpart of a multipart body to give information about the field it applies to.
/// The subpart is delimited by the boundary defined in the *Content-Type* header. Used on the body
/// itself, *Content-Disposition* has no effect.
///
/// # ABNF
/// ```text
/// content-disposition = "Content-Disposition" ":"
/// disposition-type *( ";" disposition-parm )
///
/// disposition-type = "inline" | "attachment" | disp-ext-type
/// ; case-insensitive
///
/// disp-ext-type = token
///
/// disposition-parm = filename-parm | disp-ext-parm
///
/// filename-parm = "filename" "=" value
/// | "filename*" "=" ext-value
///
/// disp-ext-parm = token "=" value
/// | ext-token "=" ext-value
///
/// ext-token = <the characters in token, followed by "*">
/// ```
///
/// **Note**: filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within
/// *multipart/form-data*.
///
/// # Example
///
/// ```
/// use actix_web::http::header::{
/// Charset, ContentDisposition, DispositionParam, DispositionType,
/// ExtendedValue,
/// };
///
/// let cd1 = ContentDisposition {
/// disposition: DispositionType::Attachment,
/// parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
/// charset: Charset::Iso_8859_1, // The character set for the bytes of the filename
/// language_tag: None, // The optional language tag (see `language-tag` crate)
/// value: b"\xa9 Copyright 1989.txt".to_vec(), // the actual bytes of the filename
/// })],
/// };
/// assert!(cd1.is_attachment());
/// assert!(cd1.get_filename_ext().is_some());
///
/// let cd2 = ContentDisposition {
/// disposition: DispositionType::FormData,
/// parameters: vec![
/// DispositionParam::Name(String::from("file")),
/// DispositionParam::Filename(String::from("bill.odt")),
/// ],
/// };
/// assert_eq!(cd2.get_name(), Some("file")); // field name
/// assert_eq!(cd2.get_filename(), Some("bill.odt"));
/// ```
///
/// # WARN
/// If "filename" parameter is supplied, do not use the file name blindly, check and possibly
/// change to match local file system conventions if applicable, and do not use directory path
/// information that may be present. See [RFC2183](https://tools.ietf.org/html/rfc2183#section-2.3)
/// .
#[derive(Clone, Debug, PartialEq)]
pub struct ContentDisposition {
/// The disposition type
pub disposition: DispositionType,
/// Disposition parameters
pub parameters: Vec<DispositionParam>,
}
impl ContentDisposition {
/// Parse a raw Content-Disposition header value.
pub fn from_raw(hv: &header::HeaderValue) -> Result<Self, ::error::ParseError> {
// `header::from_one_raw_str` invokes `hv.to_str` which assumes `hv` contains only visible
// ASCII characters. So `hv.as_bytes` is necessary here.
let hv = String::from_utf8(hv.as_bytes().to_vec())
.map_err(|_| ::error::ParseError::Header)?;
let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';');
if disp_type.is_empty() {
return Err(::error::ParseError::Header);
}
let mut cd = ContentDisposition {
disposition: disp_type.into(),
parameters: Vec::new(),
};
while !left.is_empty() {
let (param_name, new_left) = split_once_and_trim(left, '=');
if param_name.is_empty() || param_name == "*" || new_left.is_empty() {
return Err(::error::ParseError::Header);
}
left = new_left;
if param_name.ends_with('*') {
// extended parameters
let param_name = &param_name[..param_name.len() - 1]; // trim asterisk
let (ext_value, new_left) = split_once_and_trim(left, ';');
left = new_left;
let ext_value = header::parse_extended_value(ext_value)?;
let param = if param_name.eq_ignore_ascii_case("filename") {
DispositionParam::FilenameExt(ext_value)
} else {
DispositionParam::UnknownExt(param_name.to_owned(), ext_value)
};
cd.parameters.push(param);
} else {
// regular parameters
let value = if left.starts_with('\"') {
// quoted-string: defined in RFC6266 -> RFC2616 Section 3.6
let mut escaping = false;
let mut quoted_string = vec![];
let mut end = None;
// search for closing quote
for (i, &c) in left.as_bytes().iter().skip(1).enumerate() {
if escaping {
escaping = false;
quoted_string.push(c);
} else if c == 0x5c {
// backslash
escaping = true;
} else if c == 0x22 {
// double quote
end = Some(i + 1); // cuz skipped 1 for the leading quote
break;
} else {
quoted_string.push(c);
}
}
left = &left[end.ok_or(::error::ParseError::Header)? + 1..];
left = split_once(left, ';').1.trim_left();
// In fact, it should not be Err if the above code is correct.
String::from_utf8(quoted_string).map_err(|_| ::error::ParseError::Header)?
} else {
// token: won't contains semicolon according to RFC 2616 Section 2.2
let (token, new_left) = split_once_and_trim(left, ';');
left = new_left;
token.to_owned()
};
if value.is_empty() {
return Err(::error::ParseError::Header);
}
let param = if param_name.eq_ignore_ascii_case("name") {
DispositionParam::Name(value)
} else if param_name.eq_ignore_ascii_case("filename") {
DispositionParam::Filename(value)
} else {
DispositionParam::Unknown(param_name.to_owned(), value)
};
cd.parameters.push(param);
}
}
Ok(cd)
}
/// Returns `true` if it is [`Inline`](DispositionType::Inline).
pub fn is_inline(&self) -> bool {
match self.disposition {
DispositionType::Inline => true,
_ => false,
}
}
/// Returns `true` if it is [`Attachment`](DispositionType::Attachment).
pub fn is_attachment(&self) -> bool {
match self.disposition {
DispositionType::Attachment => true,
_ => false,
}
}
/// Returns `true` if it is [`FormData`](DispositionType::FormData).
pub fn is_form_data(&self) -> bool {
match self.disposition {
DispositionType::FormData => true,
_ => false,
}
}
/// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches.
pub fn is_ext<T: AsRef<str>>(&self, disp_type: T) -> bool {
match self.disposition {
DispositionType::Ext(ref t)
if t.eq_ignore_ascii_case(disp_type.as_ref()) =>
{
true
}
_ => false,
}
}
/// Return the value of *name* if exists.
pub fn get_name(&self) -> Option<&str> {
self.parameters.iter().filter_map(|p| p.as_name()).nth(0)
}
/// Return the value of *filename* if exists.
pub fn get_filename(&self) -> Option<&str> {
self.parameters
.iter()
.filter_map(|p| p.as_filename())
.nth(0)
}
/// Return the value of *filename\** if exists.
pub fn get_filename_ext(&self) -> Option<&ExtendedValue> {
self.parameters
.iter()
.filter_map(|p| p.as_filename_ext())
.nth(0)
}
/// Return the value of the parameter which the `name` matches.
pub fn get_unknown<T: AsRef<str>>(&self, name: T) -> Option<&str> {
let name = name.as_ref();
self.parameters
.iter()
.filter_map(|p| p.as_unknown(name))
.nth(0)
}
/// Return the value of the extended parameter which the `name` matches.
pub fn get_unknown_ext<T: AsRef<str>>(&self, name: T) -> Option<&ExtendedValue> {
let name = name.as_ref();
self.parameters
.iter()
.filter_map(|p| p.as_unknown_ext(name))
.nth(0)
}
}
impl IntoHeaderValue for ContentDisposition {
type Error = header::InvalidHeaderValueBytes;
fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self);
header::HeaderValue::from_shared(writer.take())
}
}
impl Header for ContentDisposition {
fn name() -> header::HeaderName {
header::CONTENT_DISPOSITION
}
fn parse<T: ::HttpMessage>(msg: &T) -> Result<Self, ::error::ParseError> {
if let Some(h) = msg.headers().get(Self::name()) {
Self::from_raw(&h)
} else {
Err(::error::ParseError::Header)
}
}
}
impl fmt::Display for DispositionType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DispositionType::Inline => write!(f, "inline"),
DispositionType::Attachment => write!(f, "attachment"),
DispositionType::FormData => write!(f, "form-data"),
DispositionType::Ext(ref s) => write!(f, "{}", s),
}
}
}
impl fmt::Display for DispositionParam {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// All ASCII control charaters (0-30, 127) excepting horizontal tab, double quote, and
// backslash should be escaped in quoted-string (i.e. "foobar").
// Ref: RFC6266 S4.1 -> RFC2616 S2.2; RFC 7578 S4.2 -> RFC2183 S2 -> ... .
lazy_static! {
static ref RE: Regex = Regex::new("[\x01-\x08\x10\x1F\x7F\"\\\\]").unwrap();
}
match self {
DispositionParam::Name(ref value) => write!(f, "name={}", value),
DispositionParam::Filename(ref value) => {
write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref())
}
DispositionParam::Unknown(ref name, ref value) => write!(
f,
"{}=\"{}\"",
name,
&RE.replace_all(value, "\\$0").as_ref()
),
DispositionParam::FilenameExt(ref ext_value) => {
write!(f, "filename*={}", ext_value)
}
DispositionParam::UnknownExt(ref name, ref ext_value) => {
write!(f, "{}*={}", name, ext_value)
}
}
}
}
impl fmt::Display for ContentDisposition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.disposition)?;
self.parameters
.iter()
.map(|param| write!(f, "; {}", param))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::{ContentDisposition, DispositionParam, DispositionType};
use header::shared::Charset;
use header::{ExtendedValue, HeaderValue};
#[test]
fn test_from_raw_basic() {
assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err());
let a = HeaderValue::from_static(
"form-data; dummy=3; name=upload; filename=\"sample.png\"",
);
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::FormData,
parameters: vec![
DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
DispositionParam::Name("upload".to_owned()),
DispositionParam::Filename("sample.png".to_owned()),
],
};
assert_eq!(a, b);
let a = HeaderValue::from_static("attachment; filename=\"image.jpg\"");
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![DispositionParam::Filename("image.jpg".to_owned())],
};
assert_eq!(a, b);
let a = HeaderValue::from_static("inline; filename=image.jpg");
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::Inline,
parameters: vec![DispositionParam::Filename("image.jpg".to_owned())],
};
assert_eq!(a, b);
let a = HeaderValue::from_static(
"attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"",
);
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![DispositionParam::Unknown(
String::from("creation-date"),
"Wed, 12 Feb 1997 16:29:51 -0500".to_owned(),
)],
};
assert_eq!(a, b);
}
#[test]
fn test_from_raw_extended() {
let a = HeaderValue::from_static(
"attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
);
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
charset: Charset::Ext(String::from("UTF-8")),
language_tag: None,
value: vec![
0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20,
b'r', b'a', b't', b'e', b's',
],
})],
};
assert_eq!(a, b);
let a = HeaderValue::from_static(
"attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates",
);
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![DispositionParam::FilenameExt(ExtendedValue {
charset: Charset::Ext(String::from("UTF-8")),
language_tag: None,
value: vec![
0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20,
b'r', b'a', b't', b'e', b's',
],
})],
};
assert_eq!(a, b);
}
#[test]
fn test_from_raw_extra_whitespace() {
let a = HeaderValue::from_static(
"form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ",
);
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::FormData,
parameters: vec![
DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()),
DispositionParam::Name("upload".to_owned()),
DispositionParam::Filename("sample.png".to_owned()),
],
};
assert_eq!(a, b);
}
#[test]
fn test_from_raw_unordered() {
let a = HeaderValue::from_static(
"form-data; dummy=3; filename=\"sample.png\" ; name=upload;",
// Actually, a trailling semolocon is not compliant. But it is fine to accept.
);
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::FormData,
parameters: vec![
DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
DispositionParam::Filename("sample.png".to_owned()),
DispositionParam::Name("upload".to_owned()),
],
};
assert_eq!(a, b);
let a = HeaderValue::from_str(
"attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"",
).unwrap();
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![
DispositionParam::FilenameExt(ExtendedValue {
charset: Charset::Iso_8859_1,
language_tag: None,
value: b"foo-\xe4.html".to_vec(),
}),
DispositionParam::Filename("foo-ä.html".to_owned()),
],
};
assert_eq!(a, b);
}
#[test]
fn test_from_raw_only_disp() {
let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment"))
.unwrap();
let b = ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![],
};
assert_eq!(a, b);
let a =
ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap();
let b = ContentDisposition {
disposition: DispositionType::Inline,
parameters: vec![],
};
assert_eq!(a, b);
let a = ContentDisposition::from_raw(&HeaderValue::from_static(
"unknown-disp-param",
)).unwrap();
let b = ContentDisposition {
disposition: DispositionType::Ext(String::from("unknown-disp-param")),
parameters: vec![],
};
assert_eq!(a, b);
}
#[test]
fn from_raw_with_mixed_case() {
let a = HeaderValue::from_str(
"InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"",
).unwrap();
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::Inline,
parameters: vec![
DispositionParam::FilenameExt(ExtendedValue {
charset: Charset::Iso_8859_1,
language_tag: None,
value: b"foo-\xe4.html".to_vec(),
}),
DispositionParam::Filename("foo-ä.html".to_owned()),
],
};
assert_eq!(a, b);
}
#[test]
fn from_raw_with_unicode() {
/* RFC7578 Section 4.2:
Some commonly deployed systems use multipart/form-data with file names directly encoded
including octets outside the US-ASCII range. The encoding used for the file names is
typically UTF-8, although HTML forms will use the charset associated with the form.
Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above.
(And now, only UTF-8 is handled by this implementation.)
*/
let a =
HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"")
.unwrap();
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::FormData,
parameters: vec![
DispositionParam::Name(String::from("upload")),
DispositionParam::Filename(String::from("文件.webp")),
],
};
assert_eq!(a, b);
let a =
HeaderValue::from_str("form-data; name=upload; filename=\"余固知謇謇之為患兮,忍而不能舍也.pptx\"").unwrap();
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::FormData,
parameters: vec![
DispositionParam::Name(String::from("upload")),
DispositionParam::Filename(String::from(
"余固知謇謇之為患兮,忍而不能舍也.pptx",
)),
],
};
assert_eq!(a, b);
}
#[test]
fn test_from_raw_escape() {
let a = HeaderValue::from_static(
"form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"",
);
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::FormData,
parameters: vec![
DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
DispositionParam::Name("upload".to_owned()),
DispositionParam::Filename(
['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g']
.iter()
.collect(),
),
],
};
assert_eq!(a, b);
}
#[test]
fn test_from_raw_semicolon() {
let a =
HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\"");
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::FormData,
parameters: vec![DispositionParam::Filename(String::from(
"A semicolon here;.pdf",
))],
};
assert_eq!(a, b);
}
#[test]
fn test_from_raw_uncessary_percent_decode() {
let a = HeaderValue::from_static(
"form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", // Should not be decoded!
);
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::FormData,
parameters: vec![
DispositionParam::Name("photo".to_owned()),
DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")),
],
};
assert_eq!(a, b);
let a = HeaderValue::from_static(
"form-data; name=photo; filename=\"%74%65%73%74.png\"",
);
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::FormData,
parameters: vec![
DispositionParam::Name("photo".to_owned()),
DispositionParam::Filename(String::from("%74%65%73%74.png")),
],
};
assert_eq!(a, b);
}
#[test]
fn test_from_raw_param_value_missing() {
let a = HeaderValue::from_static("form-data; name=upload ; filename=");
assert!(ContentDisposition::from_raw(&a).is_err());
let a = HeaderValue::from_static("attachment; dummy=; filename=invoice.pdf");
assert!(ContentDisposition::from_raw(&a).is_err());
let a = HeaderValue::from_static("inline; filename= ");
assert!(ContentDisposition::from_raw(&a).is_err());
}
#[test]
fn test_from_raw_param_name_missing() {
let a = HeaderValue::from_static("inline; =\"test.txt\"");
assert!(ContentDisposition::from_raw(&a).is_err());
let a = HeaderValue::from_static("inline; =diary.odt");
assert!(ContentDisposition::from_raw(&a).is_err());
let a = HeaderValue::from_static("inline; =");
assert!(ContentDisposition::from_raw(&a).is_err());
}
#[test]
fn test_display_extended() {
let as_string =
"attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates";
let a = HeaderValue::from_static(as_string);
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let display_rendered = format!("{}", a);
assert_eq!(as_string, display_rendered);
let a = HeaderValue::from_static("attachment; filename=colourful.csv");
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let display_rendered = format!("{}", a);
assert_eq!(
"attachment; filename=\"colourful.csv\"".to_owned(),
display_rendered
);
}
#[test]
fn test_display_quote() {
let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\"";
as_string
.find(['\\', '\"'].iter().collect::<String>().as_str())
.unwrap(); // ensure `\"` is there
let a = HeaderValue::from_static(as_string);
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let display_rendered = format!("{}", a);
assert_eq!(as_string, display_rendered);
}
#[test]
fn test_display_space_tab() {
let as_string = "form-data; name=upload; filename=\"Space here.png\"";
let a = HeaderValue::from_static(as_string);
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let display_rendered = format!("{}", a);
assert_eq!(as_string, display_rendered);
let a: ContentDisposition = ContentDisposition {
disposition: DispositionType::Inline,
parameters: vec![DispositionParam::Filename(String::from("Tab\there.png"))],
};
let display_rendered = format!("{}", a);
assert_eq!("inline; filename=\"Tab\x09here.png\"", display_rendered);
}
#[test]
fn test_display_control_characters() {
/* let a = "attachment; filename=\"carriage\rreturn.png\"";
let a = HeaderValue::from_static(a);
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let display_rendered = format!("{}", a);
assert_eq!(
"attachment; filename=\"carriage\\\rreturn.png\"",
display_rendered
);*/
// No way to create a HeaderValue containing a carriage return.
let a: ContentDisposition = ContentDisposition {
disposition: DispositionType::Inline,
parameters: vec![DispositionParam::Filename(String::from("bell\x07.png"))],
};
let display_rendered = format!("{}", a);
assert_eq!("inline; filename=\"bell\\\x07.png\"", display_rendered);
}
#[test]
fn test_param_methods() {
let param = DispositionParam::Filename(String::from("sample.txt"));
assert!(param.is_filename());
assert_eq!(param.as_filename().unwrap(), "sample.txt");
let param = DispositionParam::Unknown(String::from("foo"), String::from("bar"));
assert!(param.is_unknown("foo"));
assert_eq!(param.as_unknown("fOo"), Some("bar"));
}
#[test]
fn test_disposition_methods() {
let cd = ContentDisposition {
disposition: DispositionType::FormData,
parameters: vec![
DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()),
DispositionParam::Name("upload".to_owned()),
DispositionParam::Filename("sample.png".to_owned()),
],
};
assert_eq!(cd.get_name(), Some("upload"));
assert_eq!(cd.get_unknown("dummy"), Some("3"));
assert_eq!(cd.get_filename(), Some("sample.png"));
assert_eq!(cd.get_unknown_ext("dummy"), None);
assert_eq!(cd.get_unknown("duMMy"), Some("3"));
}
}

View File

@ -1,65 +0,0 @@
use header::{QualityItem, CONTENT_LANGUAGE};
use language_tags::LanguageTag;
header! {
/// `Content-Language` header, defined in
/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2)
///
/// The `Content-Language` header field describes the natural language(s)
/// of the intended audience for the representation. Note that this
/// might not be equivalent to all the languages used within the
/// representation.
///
/// # ABNF
///
/// ```text
/// Content-Language = 1#language-tag
/// ```
///
/// # Example values
///
/// * `da`
/// * `mi, en`
///
/// # Examples
///
/// ```rust
/// # extern crate actix_web;
/// # #[macro_use] extern crate language_tags;
/// use actix_web::HttpResponse;
/// # use actix_web::http::header::{ContentLanguage, qitem};
/// #
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// ContentLanguage(vec![
/// qitem(langtag!(en)),
/// ])
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate actix_web;
/// # #[macro_use] extern crate language_tags;
/// use actix_web::HttpResponse;
/// # use actix_web::http::header::{ContentLanguage, qitem};
/// #
/// # fn main() {
///
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// ContentLanguage(vec![
/// qitem(langtag!(da)),
/// qitem(langtag!(en;;;GB)),
/// ])
/// );
/// # }
/// ```
(ContentLanguage, CONTENT_LANGUAGE) => (QualityItem<LanguageTag>)+
test_content_language {
test_header!(test1, vec![b"da"]);
test_header!(test2, vec![b"mi, en"]);
}
}

View File

@ -1,210 +0,0 @@
use error::ParseError;
use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer,
CONTENT_RANGE};
use std::fmt::{self, Display, Write};
use std::str::FromStr;
header! {
/// `Content-Range` header, defined in
/// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2)
(ContentRange, CONTENT_RANGE) => [ContentRangeSpec]
test_content_range {
test_header!(test_bytes,
vec![b"bytes 0-499/500"],
Some(ContentRange(ContentRangeSpec::Bytes {
range: Some((0, 499)),
instance_length: Some(500)
})));
test_header!(test_bytes_unknown_len,
vec![b"bytes 0-499/*"],
Some(ContentRange(ContentRangeSpec::Bytes {
range: Some((0, 499)),
instance_length: None
})));
test_header!(test_bytes_unknown_range,
vec![b"bytes */500"],
Some(ContentRange(ContentRangeSpec::Bytes {
range: None,
instance_length: Some(500)
})));
test_header!(test_unregistered,
vec![b"seconds 1-2"],
Some(ContentRange(ContentRangeSpec::Unregistered {
unit: "seconds".to_owned(),
resp: "1-2".to_owned()
})));
test_header!(test_no_len,
vec![b"bytes 0-499"],
None::<ContentRange>);
test_header!(test_only_unit,
vec![b"bytes"],
None::<ContentRange>);
test_header!(test_end_less_than_start,
vec![b"bytes 499-0/500"],
None::<ContentRange>);
test_header!(test_blank,
vec![b""],
None::<ContentRange>);
test_header!(test_bytes_many_spaces,
vec![b"bytes 1-2/500 3"],
None::<ContentRange>);
test_header!(test_bytes_many_slashes,
vec![b"bytes 1-2/500/600"],
None::<ContentRange>);
test_header!(test_bytes_many_dashes,
vec![b"bytes 1-2-3/500"],
None::<ContentRange>);
}
}
/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2)
///
/// # ABNF
///
/// ```text
/// Content-Range = byte-content-range
/// / other-content-range
///
/// byte-content-range = bytes-unit SP
/// ( byte-range-resp / unsatisfied-range )
///
/// byte-range-resp = byte-range "/" ( complete-length / "*" )
/// byte-range = first-byte-pos "-" last-byte-pos
/// unsatisfied-range = "*/" complete-length
///
/// complete-length = 1*DIGIT
///
/// other-content-range = other-range-unit SP other-range-resp
/// other-range-resp = *CHAR
/// ```
#[derive(PartialEq, Clone, Debug)]
pub enum ContentRangeSpec {
/// Byte range
Bytes {
/// First and last bytes of the range, omitted if request could not be
/// satisfied
range: Option<(u64, u64)>,
/// Total length of the instance, can be omitted if unknown
instance_length: Option<u64>,
},
/// Custom range, with unit not registered at IANA
Unregistered {
/// other-range-unit
unit: String,
/// other-range-resp
resp: String,
},
}
fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> {
let mut iter = s.splitn(2, separator);
match (iter.next(), iter.next()) {
(Some(a), Some(b)) => Some((a, b)),
_ => None,
}
}
impl FromStr for ContentRangeSpec {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, ParseError> {
let res = match split_in_two(s, ' ') {
Some(("bytes", resp)) => {
let (range, instance_length) =
split_in_two(resp, '/').ok_or(ParseError::Header)?;
let instance_length = if instance_length == "*" {
None
} else {
Some(instance_length
.parse()
.map_err(|_| ParseError::Header)?)
};
let range = if range == "*" {
None
} else {
let (first_byte, last_byte) =
split_in_two(range, '-').ok_or(ParseError::Header)?;
let first_byte = first_byte.parse().map_err(|_| ParseError::Header)?;
let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?;
if last_byte < first_byte {
return Err(ParseError::Header);
}
Some((first_byte, last_byte))
};
ContentRangeSpec::Bytes {
range,
instance_length,
}
}
Some((unit, resp)) => ContentRangeSpec::Unregistered {
unit: unit.to_owned(),
resp: resp.to_owned(),
},
_ => return Err(ParseError::Header),
};
Ok(res)
}
}
impl Display for ContentRangeSpec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ContentRangeSpec::Bytes {
range,
instance_length,
} => {
f.write_str("bytes ")?;
match range {
Some((first_byte, last_byte)) => {
write!(f, "{}-{}", first_byte, last_byte)?;
}
None => {
f.write_str("*")?;
}
};
f.write_str("/")?;
if let Some(v) = instance_length {
write!(f, "{}", v)
} else {
f.write_str("*")
}
}
ContentRangeSpec::Unregistered {
ref unit,
ref resp,
} => {
f.write_str(unit)?;
f.write_str(" ")?;
f.write_str(resp)
}
}
}
}
impl IntoHeaderValue for ContentRangeSpec {
type Error = InvalidHeaderValueBytes;
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self);
HeaderValue::from_shared(writer.take())
}
}

View File

@ -1,122 +0,0 @@
use header::CONTENT_TYPE;
use mime::{self, Mime};
header! {
/// `Content-Type` header, defined in
/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5)
///
/// The `Content-Type` header field indicates the media type of the
/// associated representation: either the representation enclosed in the
/// message payload or the selected representation, as determined by the
/// message semantics. The indicated media type defines both the data
/// format and how that data is intended to be processed by a recipient,
/// within the scope of the received message semantics, after any content
/// codings indicated by Content-Encoding are decoded.
///
/// Although the `mime` crate allows the mime options to be any slice, this crate
/// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If
/// this is an issue, it's possible to implement `Header` on a custom struct.
///
/// # ABNF
///
/// ```text
/// Content-Type = media-type
/// ```
///
/// # Example values
///
/// * `text/html; charset=utf-8`
/// * `application/json`
///
/// # Examples
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::ContentType;
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// ContentType::json()
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate mime;
/// # extern crate actix_web;
/// use mime::TEXT_HTML;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::ContentType;
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// ContentType(TEXT_HTML)
/// );
/// # }
/// ```
(ContentType, CONTENT_TYPE) => [Mime]
test_content_type {
test_header!(
test1,
vec![b"text/html"],
Some(HeaderField(TEXT_HTML)));
}
}
impl ContentType {
/// A constructor to easily create a `Content-Type: application/json`
/// header.
#[inline]
pub fn json() -> ContentType {
ContentType(mime::APPLICATION_JSON)
}
/// A constructor to easily create a `Content-Type: text/plain;
/// charset=utf-8` header.
#[inline]
pub fn plaintext() -> ContentType {
ContentType(mime::TEXT_PLAIN_UTF_8)
}
/// A constructor to easily create a `Content-Type: text/html` header.
#[inline]
pub fn html() -> ContentType {
ContentType(mime::TEXT_HTML)
}
/// A constructor to easily create a `Content-Type: text/xml` header.
#[inline]
pub fn xml() -> ContentType {
ContentType(mime::TEXT_XML)
}
/// A constructor to easily create a `Content-Type:
/// application/www-form-url-encoded` header.
#[inline]
pub fn form_url_encoded() -> ContentType {
ContentType(mime::APPLICATION_WWW_FORM_URLENCODED)
}
/// A constructor to easily create a `Content-Type: image/jpeg` header.
#[inline]
pub fn jpeg() -> ContentType {
ContentType(mime::IMAGE_JPEG)
}
/// A constructor to easily create a `Content-Type: image/png` header.
#[inline]
pub fn png() -> ContentType {
ContentType(mime::IMAGE_PNG)
}
/// A constructor to easily create a `Content-Type:
/// application/octet-stream` header.
#[inline]
pub fn octet_stream() -> ContentType {
ContentType(mime::APPLICATION_OCTET_STREAM)
}
}
impl Eq for ContentType {}

View File

@ -1,42 +0,0 @@
use header::{HttpDate, DATE};
use std::time::SystemTime;
header! {
/// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2)
///
/// The `Date` header field represents the date and time at which the
/// message was originated.
///
/// # ABNF
///
/// ```text
/// Date = HTTP-date
/// ```
///
/// # Example values
///
/// * `Tue, 15 Nov 1994 08:12:31 GMT`
///
/// # Example
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::Date;
/// use std::time::SystemTime;
///
/// let mut builder = HttpResponse::Ok();
/// builder.set(Date(SystemTime::now().into()));
/// ```
(Date, DATE) => [HttpDate]
test_date {
test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]);
}
}
impl Date {
/// Create a date instance set to the current system time
pub fn now() -> Date {
Date(SystemTime::now().into())
}
}

View File

@ -1,96 +0,0 @@
use header::{EntityTag, ETAG};
header! {
/// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3)
///
/// The `ETag` header field in a response provides the current entity-tag
/// for the selected representation, as determined at the conclusion of
/// handling the request. An entity-tag is an opaque validator for
/// differentiating between multiple representations of the same
/// resource, regardless of whether those multiple representations are
/// due to resource state changes over time, content negotiation
/// resulting in multiple representations being valid at the same time,
/// or both. An entity-tag consists of an opaque quoted string, possibly
/// prefixed by a weakness indicator.
///
/// # ABNF
///
/// ```text
/// ETag = entity-tag
/// ```
///
/// # Example values
///
/// * `"xyzzy"`
/// * `W/"xyzzy"`
/// * `""`
///
/// # Examples
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{ETag, EntityTag};
///
/// let mut builder = HttpResponse::Ok();
/// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned())));
/// ```
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{ETag, EntityTag};
///
/// let mut builder = HttpResponse::Ok();
/// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned())));
/// ```
(ETag, ETAG) => [EntityTag]
test_etag {
// From the RFC
test_header!(test1,
vec![b"\"xyzzy\""],
Some(ETag(EntityTag::new(false, "xyzzy".to_owned()))));
test_header!(test2,
vec![b"W/\"xyzzy\""],
Some(ETag(EntityTag::new(true, "xyzzy".to_owned()))));
test_header!(test3,
vec![b"\"\""],
Some(ETag(EntityTag::new(false, "".to_owned()))));
// Own tests
test_header!(test4,
vec![b"\"foobar\""],
Some(ETag(EntityTag::new(false, "foobar".to_owned()))));
test_header!(test5,
vec![b"\"\""],
Some(ETag(EntityTag::new(false, "".to_owned()))));
test_header!(test6,
vec![b"W/\"weak-etag\""],
Some(ETag(EntityTag::new(true, "weak-etag".to_owned()))));
test_header!(test7,
vec![b"W/\"\x65\x62\""],
Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned()))));
test_header!(test8,
vec![b"W/\"\""],
Some(ETag(EntityTag::new(true, "".to_owned()))));
test_header!(test9,
vec![b"no-dquotes"],
None::<ETag>);
test_header!(test10,
vec![b"w/\"the-first-w-is-case-sensitive\""],
None::<ETag>);
test_header!(test11,
vec![b""],
None::<ETag>);
test_header!(test12,
vec![b"\"unmatched-dquotes1"],
None::<ETag>);
test_header!(test13,
vec![b"unmatched-dquotes2\""],
None::<ETag>);
test_header!(test14,
vec![b"matched-\"dquotes\""],
None::<ETag>);
test_header!(test15,
vec![b"\""],
None::<ETag>);
}
}

View File

@ -1,39 +0,0 @@
use header::{HttpDate, EXPIRES};
header! {
/// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3)
///
/// The `Expires` header field gives the date/time after which the
/// response is considered stale.
///
/// The presence of an Expires field does not imply that the original
/// resource will change or cease to exist at, before, or after that
/// time.
///
/// # ABNF
///
/// ```text
/// Expires = HTTP-date
/// ```
///
/// # Example values
/// * `Thu, 01 Dec 1994 16:00:00 GMT`
///
/// # Example
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::Expires;
/// use std::time::{SystemTime, Duration};
///
/// let mut builder = HttpResponse::Ok();
/// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24);
/// builder.set(Expires(expiration.into()));
/// ```
(Expires, EXPIRES) => [HttpDate]
test_expires {
// Test case from RFC
test_header!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]);
}
}

View File

@ -1,70 +0,0 @@
use header::{EntityTag, IF_MATCH};
header! {
/// `If-Match` header, defined in
/// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1)
///
/// The `If-Match` header field makes the request method conditional on
/// the recipient origin server either having at least one current
/// representation of the target resource, when the field-value is "*",
/// or having a current representation of the target resource that has an
/// entity-tag matching a member of the list of entity-tags provided in
/// the field-value.
///
/// An origin server MUST use the strong comparison function when
/// comparing entity-tags for `If-Match`, since the client
/// intends this precondition to prevent the method from being applied if
/// there have been any changes to the representation data.
///
/// # ABNF
///
/// ```text
/// If-Match = "*" / 1#entity-tag
/// ```
///
/// # Example values
///
/// * `"xyzzy"`
/// * "xyzzy", "r2d2xxxx", "c3piozzzz"
///
/// # Examples
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::IfMatch;
///
/// let mut builder = HttpResponse::Ok();
/// builder.set(IfMatch::Any);
/// ```
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{IfMatch, EntityTag};
///
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// IfMatch::Items(vec![
/// EntityTag::new(false, "xyzzy".to_owned()),
/// EntityTag::new(false, "foobar".to_owned()),
/// EntityTag::new(false, "bazquux".to_owned()),
/// ])
/// );
/// ```
(IfMatch, IF_MATCH) => {Any / (EntityTag)+}
test_if_match {
test_header!(
test1,
vec![b"\"xyzzy\""],
Some(HeaderField::Items(
vec![EntityTag::new(false, "xyzzy".to_owned())])));
test_header!(
test2,
vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""],
Some(HeaderField::Items(
vec![EntityTag::new(false, "xyzzy".to_owned()),
EntityTag::new(false, "r2d2xxxx".to_owned()),
EntityTag::new(false, "c3piozzzz".to_owned())])));
test_header!(test3, vec![b"*"], Some(IfMatch::Any));
}
}

View File

@ -1,39 +0,0 @@
use header::{HttpDate, IF_MODIFIED_SINCE};
header! {
/// `If-Modified-Since` header, defined in
/// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3)
///
/// The `If-Modified-Since` header field makes a GET or HEAD request
/// method conditional on the selected representation's modification date
/// being more recent than the date provided in the field-value.
/// Transfer of the selected representation's data is avoided if that
/// data has not changed.
///
/// # ABNF
///
/// ```text
/// If-Unmodified-Since = HTTP-date
/// ```
///
/// # Example values
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
///
/// # Example
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::IfModifiedSince;
/// use std::time::{SystemTime, Duration};
///
/// let mut builder = HttpResponse::Ok();
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
/// builder.set(IfModifiedSince(modified.into()));
/// ```
(IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate]
test_if_modified_since {
// Test case from RFC
test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
}
}

View File

@ -1,92 +0,0 @@
use header::{EntityTag, IF_NONE_MATCH};
header! {
/// `If-None-Match` header, defined in
/// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2)
///
/// The `If-None-Match` header field makes the request method conditional
/// on a recipient cache or origin server either not having any current
/// representation of the target resource, when the field-value is "*",
/// or having a selected representation with an entity-tag that does not
/// match any of those listed in the field-value.
///
/// A recipient MUST use the weak comparison function when comparing
/// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags
/// can be used for cache validation even if there have been changes to
/// the representation data.
///
/// # ABNF
///
/// ```text
/// If-None-Match = "*" / 1#entity-tag
/// ```
///
/// # Example values
///
/// * `"xyzzy"`
/// * `W/"xyzzy"`
/// * `"xyzzy", "r2d2xxxx", "c3piozzzz"`
/// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"`
/// * `*`
///
/// # Examples
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::IfNoneMatch;
///
/// let mut builder = HttpResponse::Ok();
/// builder.set(IfNoneMatch::Any);
/// ```
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{IfNoneMatch, EntityTag};
///
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// IfNoneMatch::Items(vec![
/// EntityTag::new(false, "xyzzy".to_owned()),
/// EntityTag::new(false, "foobar".to_owned()),
/// EntityTag::new(false, "bazquux".to_owned()),
/// ])
/// );
/// ```
(IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+}
test_if_none_match {
test_header!(test1, vec![b"\"xyzzy\""]);
test_header!(test2, vec![b"W/\"xyzzy\""]);
test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]);
test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]);
test_header!(test5, vec![b"*"]);
}
}
#[cfg(test)]
mod tests {
use super::IfNoneMatch;
use header::{EntityTag, Header, IF_NONE_MATCH};
use test::TestRequest;
#[test]
fn test_if_none_match() {
let mut if_none_match: Result<IfNoneMatch, _>;
let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish();
if_none_match = Header::parse(&req);
assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any));
let req =
TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..])
.finish();
if_none_match = Header::parse(&req);
let mut entities: Vec<EntityTag> = Vec::new();
let foobar_etag = EntityTag::new(false, "foobar".to_owned());
let weak_etag = EntityTag::new(true, "weak-etag".to_owned());
entities.push(foobar_etag);
entities.push(weak_etag);
assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities)));
}
}

View File

@ -1,115 +0,0 @@
use error::ParseError;
use header::from_one_raw_str;
use header::{EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue,
InvalidHeaderValueBytes, Writer};
use http::header;
use httpmessage::HttpMessage;
use std::fmt::{self, Display, Write};
/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2)
///
/// If a client has a partial copy of a representation and wishes to have
/// an up-to-date copy of the entire representation, it could use the
/// Range header field with a conditional GET (using either or both of
/// If-Unmodified-Since and If-Match.) However, if the precondition
/// fails because the representation has been modified, the client would
/// then have to make a second request to obtain the entire current
/// representation.
///
/// The `If-Range` header field allows a client to \"short-circuit\" the
/// second request. Informally, its meaning is as follows: if the
/// representation is unchanged, send me the part(s) that I am requesting
/// in Range; otherwise, send me the entire representation.
///
/// # ABNF
///
/// ```text
/// If-Range = entity-tag / HTTP-date
/// ```
///
/// # Example values
///
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
/// * `\"xyzzy\"`
///
/// # Examples
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{EntityTag, IfRange};
///
/// let mut builder = HttpResponse::Ok();
/// builder.set(IfRange::EntityTag(EntityTag::new(
/// false,
/// "xyzzy".to_owned(),
/// )));
/// ```
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::IfRange;
/// use std::time::{Duration, SystemTime};
///
/// let mut builder = HttpResponse::Ok();
/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
/// builder.set(IfRange::Date(fetched.into()));
/// ```
#[derive(Clone, Debug, PartialEq)]
pub enum IfRange {
/// The entity-tag the client has of the resource
EntityTag(EntityTag),
/// The date when the client retrieved the resource
Date(HttpDate),
}
impl Header for IfRange {
fn name() -> HeaderName {
header::IF_RANGE
}
#[inline]
fn parse<T>(msg: &T) -> Result<Self, ParseError>
where
T: HttpMessage,
{
let etag: Result<EntityTag, _> =
from_one_raw_str(msg.headers().get(header::IF_RANGE));
if let Ok(etag) = etag {
return Ok(IfRange::EntityTag(etag));
}
let date: Result<HttpDate, _> =
from_one_raw_str(msg.headers().get(header::IF_RANGE));
if let Ok(date) = date {
return Ok(IfRange::Date(date));
}
Err(ParseError::Header)
}
}
impl Display for IfRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
IfRange::EntityTag(ref x) => Display::fmt(x, f),
IfRange::Date(ref x) => Display::fmt(x, f),
}
}
}
impl IntoHeaderValue for IfRange {
type Error = InvalidHeaderValueBytes;
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self);
HeaderValue::from_shared(writer.take())
}
}
#[cfg(test)]
mod test_if_range {
use super::IfRange as HeaderField;
use header::*;
use std::str;
test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
test_header!(test2, vec![b"\"xyzzy\""]);
test_header!(test3, vec![b"this-is-invalid"], None::<IfRange>);
}

View File

@ -1,40 +0,0 @@
use header::{HttpDate, IF_UNMODIFIED_SINCE};
header! {
/// `If-Unmodified-Since` header, defined in
/// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4)
///
/// The `If-Unmodified-Since` header field makes the request method
/// conditional on the selected representation's last modification date
/// being earlier than or equal to the date provided in the field-value.
/// This field accomplishes the same purpose as If-Match for cases where
/// the user agent does not have an entity-tag for the representation.
///
/// # ABNF
///
/// ```text
/// If-Unmodified-Since = HTTP-date
/// ```
///
/// # Example values
///
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
///
/// # Example
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::IfUnmodifiedSince;
/// use std::time::{SystemTime, Duration};
///
/// let mut builder = HttpResponse::Ok();
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
/// builder.set(IfUnmodifiedSince(modified.into()));
/// ```
(IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate]
test_if_unmodified_since {
// Test case from RFC
test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
}
}

View File

@ -1,38 +0,0 @@
use header::{HttpDate, LAST_MODIFIED};
header! {
/// `Last-Modified` header, defined in
/// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2)
///
/// The `Last-Modified` header field in a response provides a timestamp
/// indicating the date and time at which the origin server believes the
/// selected representation was last modified, as determined at the
/// conclusion of handling the request.
///
/// # ABNF
///
/// ```text
/// Expires = HTTP-date
/// ```
///
/// # Example values
///
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
///
/// # Example
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::LastModified;
/// use std::time::{SystemTime, Duration};
///
/// let mut builder = HttpResponse::Ok();
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
/// builder.set(LastModified(modified.into()));
/// ```
(LastModified, LAST_MODIFIED) => [HttpDate]
test_last_modified {
// Test case from RFC
test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);}
}

View File

@ -1,350 +0,0 @@
//! A Collection of Header implementations for common HTTP Headers.
//!
//! ## Mime
//!
//! Several header fields use MIME values for their contents. Keeping with the
//! strongly-typed theme, the [mime](https://docs.rs/mime) crate
//! is used, such as `ContentType(pub Mime)`.
#![cfg_attr(rustfmt, rustfmt_skip)]
pub use self::accept_charset::AcceptCharset;
//pub use self::accept_encoding::AcceptEncoding;
pub use self::accept_language::AcceptLanguage;
pub use self::accept::Accept;
pub use self::allow::Allow;
pub use self::cache_control::{CacheControl, CacheDirective};
pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam};
pub use self::content_language::ContentLanguage;
pub use self::content_range::{ContentRange, ContentRangeSpec};
pub use self::content_type::ContentType;
pub use self::date::Date;
pub use self::etag::ETag;
pub use self::expires::Expires;
pub use self::if_match::IfMatch;
pub use self::if_modified_since::IfModifiedSince;
pub use self::if_none_match::IfNoneMatch;
pub use self::if_range::IfRange;
pub use self::if_unmodified_since::IfUnmodifiedSince;
pub use self::last_modified::LastModified;
//pub use self::range::{Range, ByteRangeSpec};
#[doc(hidden)]
#[macro_export]
macro_rules! __hyper__deref {
($from:ty => $to:ty) => {
impl ::std::ops::Deref for $from {
type Target = $to;
#[inline]
fn deref(&self) -> &$to {
&self.0
}
}
impl ::std::ops::DerefMut for $from {
#[inline]
fn deref_mut(&mut self) -> &mut $to {
&mut self.0
}
}
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! __hyper__tm {
($id:ident, $tm:ident{$($tf:item)*}) => {
#[allow(unused_imports)]
#[cfg(test)]
mod $tm{
use std::str;
use http::Method;
use $crate::header::*;
use $crate::mime::*;
use super::$id as HeaderField;
$($tf)*
}
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! test_header {
($id:ident, $raw:expr) => {
#[test]
fn $id() {
use test;
let raw = $raw;
let a: Vec<Vec<u8>> = raw.iter().map(|x| x.to_vec()).collect();
let mut req = test::TestRequest::default();
for item in a {
req = req.header(HeaderField::name(), item);
}
let req = req.finish();
let value = HeaderField::parse(&req);
let result = format!("{}", value.unwrap());
let expected = String::from_utf8(raw[0].to_vec()).unwrap();
let result_cmp: Vec<String> = result
.to_ascii_lowercase()
.split(' ')
.map(|x| x.to_owned())
.collect();
let expected_cmp: Vec<String> = expected
.to_ascii_lowercase()
.split(' ')
.map(|x| x.to_owned())
.collect();
assert_eq!(result_cmp.concat(), expected_cmp.concat());
}
};
($id:ident, $raw:expr, $typed:expr) => {
#[test]
fn $id() {
use $crate::test;
let a: Vec<Vec<u8>> = $raw.iter().map(|x| x.to_vec()).collect();
let mut req = test::TestRequest::default();
for item in a {
req = req.header(HeaderField::name(), item);
}
let req = req.finish();
let val = HeaderField::parse(&req);
let typed: Option<HeaderField> = $typed;
// Test parsing
assert_eq!(val.ok(), typed);
// Test formatting
if typed.is_some() {
let raw = &($raw)[..];
let mut iter = raw.iter().map(|b|str::from_utf8(&b[..]).unwrap());
let mut joined = String::new();
joined.push_str(iter.next().unwrap());
for s in iter {
joined.push_str(", ");
joined.push_str(s);
}
assert_eq!(format!("{}", typed.unwrap()), joined);
}
}
}
}
#[macro_export]
macro_rules! header {
// $a:meta: Attributes associated with the header item (usually docs)
// $id:ident: Identifier of the header
// $n:expr: Lowercase name of the header
// $nn:expr: Nice name of the header
// List header, zero or more items
($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)*) => {
$(#[$a])*
#[derive(Clone, Debug, PartialEq)]
pub struct $id(pub Vec<$item>);
__hyper__deref!($id => Vec<$item>);
impl $crate::header::Header for $id {
#[inline]
fn name() -> $crate::header::HeaderName {
$name
}
#[inline]
fn parse<T>(msg: &T) -> Result<Self, $crate::error::ParseError>
where T: $crate::HttpMessage
{
$crate::header::from_comma_delimited(
msg.headers().get_all(Self::name())).map($id)
}
}
impl ::std::fmt::Display for $id {
#[inline]
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
$crate::header::fmt_comma_delimited(f, &self.0[..])
}
}
impl $crate::header::IntoHeaderValue for $id {
type Error = $crate::header::InvalidHeaderValueBytes;
fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> {
use std::fmt::Write;
let mut writer = $crate::header::Writer::new();
let _ = write!(&mut writer, "{}", self);
$crate::header::HeaderValue::from_shared(writer.take())
}
}
};
// List header, one or more items
($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)+) => {
$(#[$a])*
#[derive(Clone, Debug, PartialEq)]
pub struct $id(pub Vec<$item>);
__hyper__deref!($id => Vec<$item>);
impl $crate::header::Header for $id {
#[inline]
fn name() -> $crate::header::HeaderName {
$name
}
#[inline]
fn parse<T>(msg: &T) -> Result<Self, $crate::error::ParseError>
where T: $crate::HttpMessage
{
$crate::header::from_comma_delimited(
msg.headers().get_all(Self::name())).map($id)
}
}
impl ::std::fmt::Display for $id {
#[inline]
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
$crate::header::fmt_comma_delimited(f, &self.0[..])
}
}
impl $crate::header::IntoHeaderValue for $id {
type Error = $crate::header::InvalidHeaderValueBytes;
fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> {
use std::fmt::Write;
let mut writer = $crate::header::Writer::new();
let _ = write!(&mut writer, "{}", self);
$crate::header::HeaderValue::from_shared(writer.take())
}
}
};
// Single value header
($(#[$a:meta])*($id:ident, $name:expr) => [$value:ty]) => {
$(#[$a])*
#[derive(Clone, Debug, PartialEq)]
pub struct $id(pub $value);
__hyper__deref!($id => $value);
impl $crate::header::Header for $id {
#[inline]
fn name() -> $crate::header::HeaderName {
$name
}
#[inline]
fn parse<T>(msg: &T) -> Result<Self, $crate::error::ParseError>
where T: $crate::HttpMessage
{
$crate::header::from_one_raw_str(
msg.headers().get(Self::name())).map($id)
}
}
impl ::std::fmt::Display for $id {
#[inline]
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
::std::fmt::Display::fmt(&self.0, f)
}
}
impl $crate::header::IntoHeaderValue for $id {
type Error = $crate::header::InvalidHeaderValueBytes;
fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> {
self.0.try_into()
}
}
};
// List header, one or more items with "*" option
($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => {
$(#[$a])*
#[derive(Clone, Debug, PartialEq)]
pub enum $id {
/// Any value is a match
Any,
/// Only the listed items are a match
Items(Vec<$item>),
}
impl $crate::header::Header for $id {
#[inline]
fn name() -> $crate::header::HeaderName {
$name
}
#[inline]
fn parse<T>(msg: &T) -> Result<Self, $crate::error::ParseError>
where T: $crate::HttpMessage
{
let any = msg.headers().get(Self::name()).and_then(|hdr| {
hdr.to_str().ok().and_then(|hdr| Some(hdr.trim() == "*"))});
if let Some(true) = any {
Ok($id::Any)
} else {
Ok($id::Items(
$crate::header::from_comma_delimited(
msg.headers().get_all(Self::name()))?))
}
}
}
impl ::std::fmt::Display for $id {
#[inline]
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
$id::Any => f.write_str("*"),
$id::Items(ref fields) => $crate::header::fmt_comma_delimited(
f, &fields[..])
}
}
}
impl $crate::header::IntoHeaderValue for $id {
type Error = $crate::header::InvalidHeaderValueBytes;
fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> {
use std::fmt::Write;
let mut writer = $crate::header::Writer::new();
let _ = write!(&mut writer, "{}", self);
$crate::header::HeaderValue::from_shared(writer.take())
}
}
};
// optional test module
($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => {
header! {
$(#[$a])*
($id, $name) => ($item)*
}
__hyper__tm! { $id, $tm { $($tf)* }}
};
($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => {
header! {
$(#[$a])*
($id, $n) => ($item)+
}
__hyper__tm! { $id, $tm { $($tf)* }}
};
($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => {
header! {
$(#[$a])* ($id, $name) => [$item]
}
__hyper__tm! { $id, $tm { $($tf)* }}
};
($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => {
header! {
$(#[$a])*
($id, $name) => {Any / ($item)+}
}
__hyper__tm! { $id, $tm { $($tf)* }}
};
}
mod accept_charset;
//mod accept_encoding;
mod accept_language;
mod accept;
mod allow;
mod cache_control;
mod content_disposition;
mod content_language;
mod content_range;
mod content_type;
mod date;
mod etag;
mod expires;
mod if_match;
mod if_modified_since;
mod if_none_match;
mod if_range;
mod if_unmodified_since;
mod last_modified;
//mod range;

View File

@ -1,434 +0,0 @@
use std::fmt::{self, Display};
use std::str::FromStr;
use header::parsing::from_one_raw_str;
use header::{Header, Raw};
/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1)
///
/// The "Range" header field on a GET request modifies the method
/// semantics to request transfer of only one or more subranges of the
/// selected representation data, rather than the entire selected
/// representation data.
///
/// # ABNF
///
/// ```text
/// Range = byte-ranges-specifier / other-ranges-specifier
/// other-ranges-specifier = other-range-unit "=" other-range-set
/// other-range-set = 1*VCHAR
///
/// bytes-unit = "bytes"
///
/// byte-ranges-specifier = bytes-unit "=" byte-range-set
/// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec)
/// byte-range-spec = first-byte-pos "-" [last-byte-pos]
/// first-byte-pos = 1*DIGIT
/// last-byte-pos = 1*DIGIT
/// ```
///
/// # Example values
///
/// * `bytes=1000-`
/// * `bytes=-2000`
/// * `bytes=0-1,30-40`
/// * `bytes=0-10,20-90,-100`
/// * `custom_unit=0-123`
/// * `custom_unit=xxx-yyy`
///
/// # Examples
///
/// ```
/// use hyper::header::{Headers, Range, ByteRangeSpec};
///
/// let mut headers = Headers::new();
/// headers.set(Range::Bytes(
/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)]
/// ));
///
/// headers.clear();
/// headers.set(Range::Unregistered("letters".to_owned(), "a-f".to_owned()));
/// ```
///
/// ```
/// use hyper::header::{Headers, Range};
///
/// let mut headers = Headers::new();
/// headers.set(Range::bytes(1, 100));
///
/// headers.clear();
/// headers.set(Range::bytes_multi(vec![(1, 100), (200, 300)]));
/// ```
#[derive(PartialEq, Clone, Debug)]
pub enum Range {
/// Byte range
Bytes(Vec<ByteRangeSpec>),
/// Custom range, with unit not registered at IANA
/// (`other-range-unit`: String , `other-range-set`: String)
Unregistered(String, String),
}
/// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`.
/// Each `ByteRangeSpec` defines a range of bytes to fetch
#[derive(PartialEq, Clone, Debug)]
pub enum ByteRangeSpec {
/// Get all bytes between x and y ("x-y")
FromTo(u64, u64),
/// Get all bytes starting from x ("x-")
AllFrom(u64),
/// Get last x bytes ("-x")
Last(u64),
}
impl ByteRangeSpec {
/// Given the full length of the entity, attempt to normalize the byte range
/// into an satisfiable end-inclusive (from, to) range.
///
/// The resulting range is guaranteed to be a satisfiable range within the
/// bounds of `0 <= from <= to < full_length`.
///
/// If the byte range is deemed unsatisfiable, `None` is returned.
/// An unsatisfiable range is generally cause for a server to either reject
/// the client request with a `416 Range Not Satisfiable` status code, or to
/// simply ignore the range header and serve the full entity using a `200
/// OK` status code.
///
/// This function closely follows [RFC 7233][1] section 2.1.
/// As such, it considers ranges to be satisfiable if they meet the
/// following conditions:
///
/// > If a valid byte-range-set includes at least one byte-range-spec with
/// a first-byte-pos that is less than the current length of the
/// representation, or at least one suffix-byte-range-spec with a
/// non-zero suffix-length, then the byte-range-set is satisfiable.
/// Otherwise, the byte-range-set is unsatisfiable.
///
/// The function also computes remainder ranges based on the RFC:
///
/// > If the last-byte-pos value is
/// absent, or if the value is greater than or equal to the current
/// length of the representation data, the byte range is interpreted as
/// the remainder of the representation (i.e., the server replaces the
/// value of last-byte-pos with a value that is one less than the current
/// length of the selected representation).
///
/// [1]: https://tools.ietf.org/html/rfc7233
pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> {
// If the full length is zero, there is no satisfiable end-inclusive range.
if full_length == 0 {
return None;
}
match self {
&ByteRangeSpec::FromTo(from, to) => {
if from < full_length && from <= to {
Some((from, ::std::cmp::min(to, full_length - 1)))
} else {
None
}
}
&ByteRangeSpec::AllFrom(from) => {
if from < full_length {
Some((from, full_length - 1))
} else {
None
}
}
&ByteRangeSpec::Last(last) => {
if last > 0 {
// From the RFC: If the selected representation is shorter
// than the specified suffix-length,
// the entire representation is used.
if last > full_length {
Some((0, full_length - 1))
} else {
Some((full_length - last, full_length - 1))
}
} else {
None
}
}
}
}
}
impl Range {
/// Get the most common byte range header ("bytes=from-to")
pub fn bytes(from: u64, to: u64) -> Range {
Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)])
}
/// Get byte range header with multiple subranges
/// ("bytes=from1-to1,from2-to2,fromX-toX")
pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range {
Range::Bytes(
ranges
.iter()
.map(|r| ByteRangeSpec::FromTo(r.0, r.1))
.collect(),
)
}
}
impl fmt::Display for ByteRangeSpec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to),
ByteRangeSpec::Last(pos) => write!(f, "-{}", pos),
ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos),
}
}
}
impl fmt::Display for Range {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Range::Bytes(ref ranges) => {
try!(write!(f, "bytes="));
for (i, range) in ranges.iter().enumerate() {
if i != 0 {
try!(f.write_str(","));
}
try!(Display::fmt(range, f));
}
Ok(())
}
Range::Unregistered(ref unit, ref range_str) => {
write!(f, "{}={}", unit, range_str)
}
}
}
}
impl FromStr for Range {
type Err = ::Error;
fn from_str(s: &str) -> ::Result<Range> {
let mut iter = s.splitn(2, '=');
match (iter.next(), iter.next()) {
(Some("bytes"), Some(ranges)) => {
let ranges = from_comma_delimited(ranges);
if ranges.is_empty() {
return Err(::Error::Header);
}
Ok(Range::Bytes(ranges))
}
(Some(unit), Some(range_str)) if unit != "" && range_str != "" => Ok(
Range::Unregistered(unit.to_owned(), range_str.to_owned()),
),
_ => Err(::Error::Header),
}
}
}
impl FromStr for ByteRangeSpec {
type Err = ::Error;
fn from_str(s: &str) -> ::Result<ByteRangeSpec> {
let mut parts = s.splitn(2, '-');
match (parts.next(), parts.next()) {
(Some(""), Some(end)) => end.parse()
.or(Err(::Error::Header))
.map(ByteRangeSpec::Last),
(Some(start), Some("")) => start
.parse()
.or(Err(::Error::Header))
.map(ByteRangeSpec::AllFrom),
(Some(start), Some(end)) => match (start.parse(), end.parse()) {
(Ok(start), Ok(end)) if start <= end => {
Ok(ByteRangeSpec::FromTo(start, end))
}
_ => Err(::Error::Header),
},
_ => Err(::Error::Header),
}
}
}
fn from_comma_delimited<T: FromStr>(s: &str) -> Vec<T> {
s.split(',')
.filter_map(|x| match x.trim() {
"" => None,
y => Some(y),
})
.filter_map(|x| x.parse().ok())
.collect()
}
impl Header for Range {
fn header_name() -> &'static str {
static NAME: &'static str = "Range";
NAME
}
fn parse_header(raw: &Raw) -> ::Result<Range> {
from_one_raw_str(raw)
}
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}
#[test]
fn test_parse_bytes_range_valid() {
let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap();
let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap();
let r3 = Range::bytes(1, 100);
assert_eq!(r, r2);
assert_eq!(r2, r3);
let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap();
let r2: Range =
Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap();
let r3 = Range::Bytes(vec![
ByteRangeSpec::FromTo(1, 100),
ByteRangeSpec::AllFrom(200),
]);
assert_eq!(r, r2);
assert_eq!(r2, r3);
let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap();
let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap();
let r3 = Range::Bytes(vec![
ByteRangeSpec::FromTo(1, 100),
ByteRangeSpec::Last(100),
]);
assert_eq!(r, r2);
assert_eq!(r2, r3);
let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap();
let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned());
assert_eq!(r, r2);
}
#[test]
fn test_parse_unregistered_range_valid() {
let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap();
let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned());
assert_eq!(r, r2);
let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap();
let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned());
assert_eq!(r, r2);
let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap();
let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned());
assert_eq!(r, r2);
}
#[test]
fn test_parse_invalid() {
let r: ::Result<Range> = Header::parse_header(&"bytes=1-a,-".into());
assert_eq!(r.ok(), None);
let r: ::Result<Range> = Header::parse_header(&"bytes=1-2-3".into());
assert_eq!(r.ok(), None);
let r: ::Result<Range> = Header::parse_header(&"abc".into());
assert_eq!(r.ok(), None);
let r: ::Result<Range> = Header::parse_header(&"bytes=1-100=".into());
assert_eq!(r.ok(), None);
let r: ::Result<Range> = Header::parse_header(&"bytes=".into());
assert_eq!(r.ok(), None);
let r: ::Result<Range> = Header::parse_header(&"custom=".into());
assert_eq!(r.ok(), None);
let r: ::Result<Range> = Header::parse_header(&"=1-100".into());
assert_eq!(r.ok(), None);
}
#[test]
fn test_fmt() {
use header::Headers;
let mut headers = Headers::new();
headers.set(Range::Bytes(vec![
ByteRangeSpec::FromTo(0, 1000),
ByteRangeSpec::AllFrom(2000),
]));
assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n");
headers.clear();
headers.set(Range::Bytes(vec![]));
assert_eq!(&headers.to_string(), "Range: bytes=\r\n");
headers.clear();
headers.set(Range::Unregistered(
"custom".to_owned(),
"1-xxx".to_owned(),
));
assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n");
}
#[test]
fn test_byte_range_spec_to_satisfiable_range() {
assert_eq!(
Some((0, 0)),
ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3)
);
assert_eq!(
Some((1, 2)),
ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3)
);
assert_eq!(
Some((1, 2)),
ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3)
);
assert_eq!(
None,
ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3)
);
assert_eq!(
None,
ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3)
);
assert_eq!(
None,
ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0)
);
assert_eq!(
Some((0, 2)),
ByteRangeSpec::AllFrom(0).to_satisfiable_range(3)
);
assert_eq!(
Some((2, 2)),
ByteRangeSpec::AllFrom(2).to_satisfiable_range(3)
);
assert_eq!(
None,
ByteRangeSpec::AllFrom(3).to_satisfiable_range(3)
);
assert_eq!(
None,
ByteRangeSpec::AllFrom(5).to_satisfiable_range(3)
);
assert_eq!(
None,
ByteRangeSpec::AllFrom(0).to_satisfiable_range(0)
);
assert_eq!(
Some((1, 2)),
ByteRangeSpec::Last(2).to_satisfiable_range(3)
);
assert_eq!(
Some((2, 2)),
ByteRangeSpec::Last(1).to_satisfiable_range(3)
);
assert_eq!(
Some((0, 2)),
ByteRangeSpec::Last(5).to_satisfiable_range(3)
);
assert_eq!(None, ByteRangeSpec::Last(0).to_satisfiable_range(3));
assert_eq!(None, ByteRangeSpec::Last(2).to_satisfiable_range(0));
}

View File

@ -1,471 +0,0 @@
//! Various http headers
// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header)
use std::fmt;
use std::str::FromStr;
use bytes::{Bytes, BytesMut};
use mime::Mime;
use modhttp::header::GetAll;
use modhttp::Error as HttpError;
use percent_encoding;
pub use modhttp::header::*;
use error::ParseError;
use httpmessage::HttpMessage;
mod common;
mod shared;
#[doc(hidden)]
pub use self::common::*;
#[doc(hidden)]
pub use self::shared::*;
#[doc(hidden)]
/// A trait for any object that will represent a header field and value.
pub trait Header
where
Self: IntoHeaderValue,
{
/// Returns the name of the header field
fn name() -> HeaderName;
/// Parse a header
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
}
#[doc(hidden)]
/// A trait for any object that can be Converted to a `HeaderValue`
pub trait IntoHeaderValue: Sized {
/// The type returned in the event of a conversion error.
type Error: Into<HttpError>;
/// Try to convert value to a Header value.
fn try_into(self) -> Result<HeaderValue, Self::Error>;
}
impl IntoHeaderValue for HeaderValue {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
Ok(self)
}
}
impl<'a> IntoHeaderValue for &'a str {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
self.parse()
}
}
impl<'a> IntoHeaderValue for &'a [u8] {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_bytes(self)
}
}
impl IntoHeaderValue for Bytes {
type Error = InvalidHeaderValueBytes;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_shared(self)
}
}
impl IntoHeaderValue for Vec<u8> {
type Error = InvalidHeaderValueBytes;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_shared(Bytes::from(self))
}
}
impl IntoHeaderValue for String {
type Error = InvalidHeaderValueBytes;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_shared(Bytes::from(self))
}
}
impl IntoHeaderValue for Mime {
type Error = InvalidHeaderValueBytes;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_shared(Bytes::from(format!("{}", self)))
}
}
/// Represents supported types of content encodings
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum ContentEncoding {
/// Automatically select encoding based on encoding negotiation
Auto,
/// A format using the Brotli algorithm
#[cfg(feature = "brotli")]
Br,
/// A format using the zlib structure with deflate algorithm
#[cfg(feature = "flate2")]
Deflate,
/// Gzip algorithm
#[cfg(feature = "flate2")]
Gzip,
/// Indicates the identity function (i.e. no compression, nor modification)
Identity,
}
impl ContentEncoding {
#[inline]
/// Is the content compressed?
pub fn is_compression(self) -> bool {
match self {
ContentEncoding::Identity | ContentEncoding::Auto => false,
_ => true,
}
}
#[inline]
/// Convert content encoding to string
pub fn as_str(self) -> &'static str {
match self {
#[cfg(feature = "brotli")]
ContentEncoding::Br => "br",
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => "gzip",
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => "deflate",
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
}
}
#[inline]
/// default quality value
pub fn quality(self) -> f64 {
match self {
#[cfg(feature = "brotli")]
ContentEncoding::Br => 1.1,
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => 1.0,
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => 0.9,
ContentEncoding::Identity | ContentEncoding::Auto => 0.1,
}
}
}
// TODO: remove memory allocation
impl<'a> From<&'a str> for ContentEncoding {
fn from(s: &'a str) -> ContentEncoding {
match AsRef::<str>::as_ref(&s.trim().to_lowercase()) {
#[cfg(feature = "brotli")]
"br" => ContentEncoding::Br,
#[cfg(feature = "flate2")]
"gzip" => ContentEncoding::Gzip,
#[cfg(feature = "flate2")]
"deflate" => ContentEncoding::Deflate,
_ => ContentEncoding::Identity,
}
}
}
#[doc(hidden)]
pub(crate) struct Writer {
buf: BytesMut,
}
impl Writer {
fn new() -> Writer {
Writer {
buf: BytesMut::new(),
}
}
fn take(&mut self) -> Bytes {
self.buf.take().freeze()
}
}
impl fmt::Write for Writer {
#[inline]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.buf.extend_from_slice(s.as_bytes());
Ok(())
}
#[inline]
fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
fmt::write(self, args)
}
}
#[inline]
#[doc(hidden)]
/// Reads a comma-delimited raw header into a Vec.
pub fn from_comma_delimited<T: FromStr>(
all: GetAll<HeaderValue>,
) -> Result<Vec<T>, ParseError> {
let mut result = Vec::new();
for h in all {
let s = h.to_str().map_err(|_| ParseError::Header)?;
result.extend(
s.split(',')
.filter_map(|x| match x.trim() {
"" => None,
y => Some(y),
}).filter_map(|x| x.trim().parse().ok()),
)
}
Ok(result)
}
#[inline]
#[doc(hidden)]
/// Reads a single string when parsing a header.
pub fn from_one_raw_str<T: FromStr>(val: Option<&HeaderValue>) -> Result<T, ParseError> {
if let Some(line) = val {
let line = line.to_str().map_err(|_| ParseError::Header)?;
if !line.is_empty() {
return T::from_str(line).or(Err(ParseError::Header));
}
}
Err(ParseError::Header)
}
#[inline]
#[doc(hidden)]
/// Format an array into a comma-delimited string.
pub fn fmt_comma_delimited<T>(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result
where
T: fmt::Display,
{
let mut iter = parts.iter();
if let Some(part) = iter.next() {
fmt::Display::fmt(part, f)?;
}
for part in iter {
f.write_str(", ")?;
fmt::Display::fmt(part, f)?;
}
Ok(())
}
// From hyper v0.11.27 src/header/parsing.rs
/// The value part of an extended parameter consisting of three parts:
/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`),
/// and a character sequence representing the actual value (`value`), separated by single quote
/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
#[derive(Clone, Debug, PartialEq)]
pub struct ExtendedValue {
/// The character set that is used to encode the `value` to a string.
pub charset: Charset,
/// The human language details of the `value`, if available.
pub language_tag: Option<LanguageTag>,
/// The parameter value, as expressed in octets.
pub value: Vec<u8>,
}
/// Parses extended header parameter values (`ext-value`), as defined in
/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
///
/// Extended values are denoted by parameter names that end with `*`.
///
/// ## ABNF
///
/// ```text
/// ext-value = charset "'" [ language ] "'" value-chars
/// ; like RFC 2231's <extended-initial-value>
/// ; (see [RFC2231], Section 7)
///
/// charset = "UTF-8" / "ISO-8859-1" / mime-charset
///
/// mime-charset = 1*mime-charsetc
/// mime-charsetc = ALPHA / DIGIT
/// / "!" / "#" / "$" / "%" / "&"
/// / "+" / "-" / "^" / "_" / "`"
/// / "{" / "}" / "~"
/// ; as <mime-charset> in Section 2.3 of [RFC2978]
/// ; except that the single quote is not included
/// ; SHOULD be registered in the IANA charset registry
///
/// language = <Language-Tag, defined in [RFC5646], Section 2.1>
///
/// value-chars = *( pct-encoded / attr-char )
///
/// pct-encoded = "%" HEXDIG HEXDIG
/// ; see [RFC3986], Section 2.1
///
/// attr-char = ALPHA / DIGIT
/// / "!" / "#" / "$" / "&" / "+" / "-" / "."
/// / "^" / "_" / "`" / "|" / "~"
/// ; token except ( "*" / "'" / "%" )
/// ```
pub fn parse_extended_value(val: &str) -> Result<ExtendedValue, ::error::ParseError> {
// Break into three pieces separated by the single-quote character
let mut parts = val.splitn(3, '\'');
// Interpret the first piece as a Charset
let charset: Charset = match parts.next() {
None => return Err(::error::ParseError::Header),
Some(n) => FromStr::from_str(n).map_err(|_| ::error::ParseError::Header)?,
};
// Interpret the second piece as a language tag
let language_tag: Option<LanguageTag> = match parts.next() {
None => return Err(::error::ParseError::Header),
Some("") => None,
Some(s) => match s.parse() {
Ok(lt) => Some(lt),
Err(_) => return Err(::error::ParseError::Header),
},
};
// Interpret the third piece as a sequence of value characters
let value: Vec<u8> = match parts.next() {
None => return Err(::error::ParseError::Header),
Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(),
};
Ok(ExtendedValue {
value,
charset,
language_tag,
})
}
impl fmt::Display for ExtendedValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let encoded_value = percent_encoding::percent_encode(
&self.value[..],
self::percent_encoding_http::HTTP_VALUE,
);
if let Some(ref lang) = self.language_tag {
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
} else {
write!(f, "{}''{}", self.charset, encoded_value)
}
}
}
/// Percent encode a sequence of bytes with a character set defined in
/// [https://tools.ietf.org/html/rfc5987#section-3.2][url]
///
/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2
pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result {
let encoded =
percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE);
fmt::Display::fmt(&encoded, f)
}
mod percent_encoding_http {
use percent_encoding;
// internal module because macro is hard-coded to make a public item
// but we don't want to public export this item
define_encode_set! {
// This encode set is used for HTTP header values and is defined at
// https://tools.ietf.org/html/rfc5987#section-3.2
pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | {
' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?',
'[', '\\', ']', '{', '}'
}
}
}
#[cfg(test)]
mod tests {
use super::{parse_extended_value, ExtendedValue};
use header::shared::Charset;
use language_tags::LanguageTag;
#[test]
fn test_parse_extended_value_with_encoding_and_language_tag() {
let expected_language_tag = "en".parse::<LanguageTag>().unwrap();
// RFC 5987, Section 3.2.2
// Extended notation, using the Unicode character U+00A3 (POUND SIGN)
let result = parse_extended_value("iso-8859-1'en'%A3%20rates");
assert!(result.is_ok());
let extended_value = result.unwrap();
assert_eq!(Charset::Iso_8859_1, extended_value.charset);
assert!(extended_value.language_tag.is_some());
assert_eq!(expected_language_tag, extended_value.language_tag.unwrap());
assert_eq!(
vec![163, b' ', b'r', b'a', b't', b'e', b's'],
extended_value.value
);
}
#[test]
fn test_parse_extended_value_with_encoding() {
// RFC 5987, Section 3.2.2
// Extended notation, using the Unicode characters U+00A3 (POUND SIGN)
// and U+20AC (EURO SIGN)
let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
assert!(result.is_ok());
let extended_value = result.unwrap();
assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset);
assert!(extended_value.language_tag.is_none());
assert_eq!(
vec![
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
b't', b'e', b's',
],
extended_value.value
);
}
#[test]
fn test_parse_extended_value_missing_language_tag_and_encoding() {
// From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2
let result = parse_extended_value("foo%20bar.html");
assert!(result.is_err());
}
#[test]
fn test_parse_extended_value_partially_formatted() {
let result = parse_extended_value("UTF-8'missing third part");
assert!(result.is_err());
}
#[test]
fn test_parse_extended_value_partially_formatted_blank() {
let result = parse_extended_value("blank second part'");
assert!(result.is_err());
}
#[test]
fn test_fmt_extended_value_with_encoding_and_language_tag() {
let extended_value = ExtendedValue {
charset: Charset::Iso_8859_1,
language_tag: Some("en".parse().expect("Could not parse language tag")),
value: vec![163, b' ', b'r', b'a', b't', b'e', b's'],
};
assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value));
}
#[test]
fn test_fmt_extended_value_with_encoding() {
let extended_value = ExtendedValue {
charset: Charset::Ext("UTF-8".to_string()),
language_tag: None,
value: vec![
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
b't', b'e', b's',
],
};
assert_eq!(
"UTF-8''%C2%A3%20and%20%E2%82%AC%20rates",
format!("{}", extended_value)
);
}
}

View File

@ -1,152 +0,0 @@
use std::fmt::{self, Display};
use std::str::FromStr;
use self::Charset::*;
/// A Mime charset.
///
/// The string representation is normalized to upper case.
///
/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url].
///
/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml
#[derive(Clone, Debug, PartialEq)]
#[allow(non_camel_case_types)]
pub enum Charset {
/// US ASCII
Us_Ascii,
/// ISO-8859-1
Iso_8859_1,
/// ISO-8859-2
Iso_8859_2,
/// ISO-8859-3
Iso_8859_3,
/// ISO-8859-4
Iso_8859_4,
/// ISO-8859-5
Iso_8859_5,
/// ISO-8859-6
Iso_8859_6,
/// ISO-8859-7
Iso_8859_7,
/// ISO-8859-8
Iso_8859_8,
/// ISO-8859-9
Iso_8859_9,
/// ISO-8859-10
Iso_8859_10,
/// Shift_JIS
Shift_Jis,
/// EUC-JP
Euc_Jp,
/// ISO-2022-KR
Iso_2022_Kr,
/// EUC-KR
Euc_Kr,
/// ISO-2022-JP
Iso_2022_Jp,
/// ISO-2022-JP-2
Iso_2022_Jp_2,
/// ISO-8859-6-E
Iso_8859_6_E,
/// ISO-8859-6-I
Iso_8859_6_I,
/// ISO-8859-8-E
Iso_8859_8_E,
/// ISO-8859-8-I
Iso_8859_8_I,
/// GB2312
Gb2312,
/// Big5
Big5,
/// KOI8-R
Koi8_R,
/// An arbitrary charset specified as a string
Ext(String),
}
impl Charset {
fn label(&self) -> &str {
match *self {
Us_Ascii => "US-ASCII",
Iso_8859_1 => "ISO-8859-1",
Iso_8859_2 => "ISO-8859-2",
Iso_8859_3 => "ISO-8859-3",
Iso_8859_4 => "ISO-8859-4",
Iso_8859_5 => "ISO-8859-5",
Iso_8859_6 => "ISO-8859-6",
Iso_8859_7 => "ISO-8859-7",
Iso_8859_8 => "ISO-8859-8",
Iso_8859_9 => "ISO-8859-9",
Iso_8859_10 => "ISO-8859-10",
Shift_Jis => "Shift-JIS",
Euc_Jp => "EUC-JP",
Iso_2022_Kr => "ISO-2022-KR",
Euc_Kr => "EUC-KR",
Iso_2022_Jp => "ISO-2022-JP",
Iso_2022_Jp_2 => "ISO-2022-JP-2",
Iso_8859_6_E => "ISO-8859-6-E",
Iso_8859_6_I => "ISO-8859-6-I",
Iso_8859_8_E => "ISO-8859-8-E",
Iso_8859_8_I => "ISO-8859-8-I",
Gb2312 => "GB2312",
Big5 => "big5",
Koi8_R => "KOI8-R",
Ext(ref s) => s,
}
}
}
impl Display for Charset {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.label())
}
}
impl FromStr for Charset {
type Err = ::Error;
fn from_str(s: &str) -> ::Result<Charset> {
Ok(match s.to_ascii_uppercase().as_ref() {
"US-ASCII" => Us_Ascii,
"ISO-8859-1" => Iso_8859_1,
"ISO-8859-2" => Iso_8859_2,
"ISO-8859-3" => Iso_8859_3,
"ISO-8859-4" => Iso_8859_4,
"ISO-8859-5" => Iso_8859_5,
"ISO-8859-6" => Iso_8859_6,
"ISO-8859-7" => Iso_8859_7,
"ISO-8859-8" => Iso_8859_8,
"ISO-8859-9" => Iso_8859_9,
"ISO-8859-10" => Iso_8859_10,
"SHIFT-JIS" => Shift_Jis,
"EUC-JP" => Euc_Jp,
"ISO-2022-KR" => Iso_2022_Kr,
"EUC-KR" => Euc_Kr,
"ISO-2022-JP" => Iso_2022_Jp,
"ISO-2022-JP-2" => Iso_2022_Jp_2,
"ISO-8859-6-E" => Iso_8859_6_E,
"ISO-8859-6-I" => Iso_8859_6_I,
"ISO-8859-8-E" => Iso_8859_8_E,
"ISO-8859-8-I" => Iso_8859_8_I,
"GB2312" => Gb2312,
"big5" => Big5,
"KOI8-R" => Koi8_R,
s => Ext(s.to_owned()),
})
}
}
#[test]
fn test_parse() {
assert_eq!(Us_Ascii, "us-ascii".parse().unwrap());
assert_eq!(Us_Ascii, "US-Ascii".parse().unwrap());
assert_eq!(Us_Ascii, "US-ASCII".parse().unwrap());
assert_eq!(Shift_Jis, "Shift-JIS".parse().unwrap());
assert_eq!(Ext("ABCD".to_owned()), "abcd".parse().unwrap());
}
#[test]
fn test_display() {
assert_eq!("US-ASCII", format!("{}", Us_Ascii));
assert_eq!("ABCD", format!("{}", Ext("ABCD".to_owned())));
}

View File

@ -1,59 +0,0 @@
use std::fmt;
use std::str;
pub use self::Encoding::{
Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers,
};
/// A value to represent an encoding used in `Transfer-Encoding`
/// or `Accept-Encoding` header.
#[derive(Clone, PartialEq, Debug)]
pub enum Encoding {
/// The `chunked` encoding.
Chunked,
/// The `br` encoding.
Brotli,
/// The `gzip` encoding.
Gzip,
/// The `deflate` encoding.
Deflate,
/// The `compress` encoding.
Compress,
/// The `identity` encoding.
Identity,
/// The `trailers` encoding.
Trailers,
/// Some other encoding that is less common, can be any String.
EncodingExt(String),
}
impl fmt::Display for Encoding {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match *self {
Chunked => "chunked",
Brotli => "br",
Gzip => "gzip",
Deflate => "deflate",
Compress => "compress",
Identity => "identity",
Trailers => "trailers",
EncodingExt(ref s) => s.as_ref(),
})
}
}
impl str::FromStr for Encoding {
type Err = ::error::ParseError;
fn from_str(s: &str) -> Result<Encoding, ::error::ParseError> {
match s {
"chunked" => Ok(Chunked),
"br" => Ok(Brotli),
"deflate" => Ok(Deflate),
"gzip" => Ok(Gzip),
"compress" => Ok(Compress),
"identity" => Ok(Identity),
"trailers" => Ok(Trailers),
_ => Ok(EncodingExt(s.to_owned())),
}
}
}

View File

@ -1,266 +0,0 @@
use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer};
use std::fmt::{self, Display, Write};
use std::str::FromStr;
/// check that each char in the slice is either:
/// 1. `%x21`, or
/// 2. in the range `%x23` to `%x7E`, or
/// 3. above `%x80`
fn check_slice_validity(slice: &str) -> bool {
slice
.bytes()
.all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80'))
}
/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3)
///
/// An entity tag consists of a string enclosed by two literal double quotes.
/// Preceding the first double quote is an optional weakness indicator,
/// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and
/// `W/"xyzzy"`.
///
/// # ABNF
///
/// ```text
/// entity-tag = [ weak ] opaque-tag
/// weak = %x57.2F ; "W/", case-sensitive
/// opaque-tag = DQUOTE *etagc DQUOTE
/// etagc = %x21 / %x23-7E / obs-text
/// ; VCHAR except double quotes, plus obs-text
/// ```
///
/// # Comparison
/// To check if two entity tags are equivalent in an application always use the
/// `strong_eq` or `weak_eq` methods based on the context of the Tag. Only use
/// `==` to check if two tags are identical.
///
/// The example below shows the results for a set of entity-tag pairs and
/// both the weak and strong comparison function results:
///
/// | `ETag 1`| `ETag 2`| Strong Comparison | Weak Comparison |
/// |---------|---------|-------------------|-----------------|
/// | `W/"1"` | `W/"1"` | no match | match |
/// | `W/"1"` | `W/"2"` | no match | no match |
/// | `W/"1"` | `"1"` | no match | match |
/// | `"1"` | `"1"` | match | match |
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EntityTag {
/// Weakness indicator for the tag
pub weak: bool,
/// The opaque string in between the DQUOTEs
tag: String,
}
impl EntityTag {
/// Constructs a new EntityTag.
/// # Panics
/// If the tag contains invalid characters.
pub fn new(weak: bool, tag: String) -> EntityTag {
assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag);
EntityTag { weak, tag }
}
/// Constructs a new weak EntityTag.
/// # Panics
/// If the tag contains invalid characters.
pub fn weak(tag: String) -> EntityTag {
EntityTag::new(true, tag)
}
/// Constructs a new strong EntityTag.
/// # Panics
/// If the tag contains invalid characters.
pub fn strong(tag: String) -> EntityTag {
EntityTag::new(false, tag)
}
/// Get the tag.
pub fn tag(&self) -> &str {
self.tag.as_ref()
}
/// Set the tag.
/// # Panics
/// If the tag contains invalid characters.
pub fn set_tag(&mut self, tag: String) {
assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag);
self.tag = tag
}
/// For strong comparison two entity-tags are equivalent if both are not
/// weak and their opaque-tags match character-by-character.
pub fn strong_eq(&self, other: &EntityTag) -> bool {
!self.weak && !other.weak && self.tag == other.tag
}
/// For weak comparison two entity-tags are equivalent if their
/// opaque-tags match character-by-character, regardless of either or
/// both being tagged as "weak".
pub fn weak_eq(&self, other: &EntityTag) -> bool {
self.tag == other.tag
}
/// The inverse of `EntityTag.strong_eq()`.
pub fn strong_ne(&self, other: &EntityTag) -> bool {
!self.strong_eq(other)
}
/// The inverse of `EntityTag.weak_eq()`.
pub fn weak_ne(&self, other: &EntityTag) -> bool {
!self.weak_eq(other)
}
}
impl Display for EntityTag {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.weak {
write!(f, "W/\"{}\"", self.tag)
} else {
write!(f, "\"{}\"", self.tag)
}
}
}
impl FromStr for EntityTag {
type Err = ::error::ParseError;
fn from_str(s: &str) -> Result<EntityTag, ::error::ParseError> {
let length: usize = s.len();
let slice = &s[..];
// Early exits if it doesn't terminate in a DQUOTE.
if !slice.ends_with('"') || slice.len() < 2 {
return Err(::error::ParseError::Header);
}
// The etag is weak if its first char is not a DQUOTE.
if slice.len() >= 2
&& slice.starts_with('"')
&& check_slice_validity(&slice[1..length - 1])
{
// No need to check if the last char is a DQUOTE,
// we already did that above.
return Ok(EntityTag {
weak: false,
tag: slice[1..length - 1].to_owned(),
});
} else if slice.len() >= 4
&& slice.starts_with("W/\"")
&& check_slice_validity(&slice[3..length - 1])
{
return Ok(EntityTag {
weak: true,
tag: slice[3..length - 1].to_owned(),
});
}
Err(::error::ParseError::Header)
}
}
impl IntoHeaderValue for EntityTag {
type Error = InvalidHeaderValueBytes;
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut wrt = Writer::new();
write!(wrt, "{}", self).unwrap();
HeaderValue::from_shared(wrt.take())
}
}
#[cfg(test)]
mod tests {
use super::EntityTag;
#[test]
fn test_etag_parse_success() {
// Expected success
assert_eq!(
"\"foobar\"".parse::<EntityTag>().unwrap(),
EntityTag::strong("foobar".to_owned())
);
assert_eq!(
"\"\"".parse::<EntityTag>().unwrap(),
EntityTag::strong("".to_owned())
);
assert_eq!(
"W/\"weaktag\"".parse::<EntityTag>().unwrap(),
EntityTag::weak("weaktag".to_owned())
);
assert_eq!(
"W/\"\x65\x62\"".parse::<EntityTag>().unwrap(),
EntityTag::weak("\x65\x62".to_owned())
);
assert_eq!(
"W/\"\"".parse::<EntityTag>().unwrap(),
EntityTag::weak("".to_owned())
);
}
#[test]
fn test_etag_parse_failures() {
// Expected failures
assert!("no-dquotes".parse::<EntityTag>().is_err());
assert!(
"w/\"the-first-w-is-case-sensitive\""
.parse::<EntityTag>()
.is_err()
);
assert!("".parse::<EntityTag>().is_err());
assert!("\"unmatched-dquotes1".parse::<EntityTag>().is_err());
assert!("unmatched-dquotes2\"".parse::<EntityTag>().is_err());
assert!("matched-\"dquotes\"".parse::<EntityTag>().is_err());
}
#[test]
fn test_etag_fmt() {
assert_eq!(
format!("{}", EntityTag::strong("foobar".to_owned())),
"\"foobar\""
);
assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\"");
assert_eq!(
format!("{}", EntityTag::weak("weak-etag".to_owned())),
"W/\"weak-etag\""
);
assert_eq!(
format!("{}", EntityTag::weak("\u{0065}".to_owned())),
"W/\"\x65\""
);
assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\"");
}
#[test]
fn test_cmp() {
// | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison |
// |---------|---------|-------------------|-----------------|
// | `W/"1"` | `W/"1"` | no match | match |
// | `W/"1"` | `W/"2"` | no match | no match |
// | `W/"1"` | `"1"` | no match | match |
// | `"1"` | `"1"` | match | match |
let mut etag1 = EntityTag::weak("1".to_owned());
let mut etag2 = EntityTag::weak("1".to_owned());
assert!(!etag1.strong_eq(&etag2));
assert!(etag1.weak_eq(&etag2));
assert!(etag1.strong_ne(&etag2));
assert!(!etag1.weak_ne(&etag2));
etag1 = EntityTag::weak("1".to_owned());
etag2 = EntityTag::weak("2".to_owned());
assert!(!etag1.strong_eq(&etag2));
assert!(!etag1.weak_eq(&etag2));
assert!(etag1.strong_ne(&etag2));
assert!(etag1.weak_ne(&etag2));
etag1 = EntityTag::weak("1".to_owned());
etag2 = EntityTag::strong("1".to_owned());
assert!(!etag1.strong_eq(&etag2));
assert!(etag1.weak_eq(&etag2));
assert!(etag1.strong_ne(&etag2));
assert!(!etag1.weak_ne(&etag2));
etag1 = EntityTag::strong("1".to_owned());
etag2 = EntityTag::strong("1".to_owned());
assert!(etag1.strong_eq(&etag2));
assert!(etag1.weak_eq(&etag2));
assert!(!etag1.strong_ne(&etag2));
assert!(!etag1.weak_ne(&etag2));
}
}

View File

@ -1,119 +0,0 @@
use std::fmt::{self, Display};
use std::io::Write;
use std::str::FromStr;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use bytes::{BufMut, BytesMut};
use http::header::{HeaderValue, InvalidHeaderValueBytes};
use time;
use error::ParseError;
use header::IntoHeaderValue;
/// A timestamp with HTTP formatting and parsing
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct HttpDate(time::Tm);
impl FromStr for HttpDate {
type Err = ParseError;
fn from_str(s: &str) -> Result<HttpDate, ParseError> {
match time::strptime(s, "%a, %d %b %Y %T %Z")
.or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z"))
.or_else(|_| time::strptime(s, "%c"))
{
Ok(t) => Ok(HttpDate(t)),
Err(_) => Err(ParseError::Header),
}
}
}
impl Display for HttpDate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0.to_utc().rfc822(), f)
}
}
impl From<time::Tm> for HttpDate {
fn from(tm: time::Tm) -> HttpDate {
HttpDate(tm)
}
}
impl From<SystemTime> for HttpDate {
fn from(sys: SystemTime) -> HttpDate {
let tmspec = match sys.duration_since(UNIX_EPOCH) {
Ok(dur) => {
time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32)
}
Err(err) => {
let neg = err.duration();
time::Timespec::new(
-(neg.as_secs() as i64),
-(neg.subsec_nanos() as i32),
)
}
};
HttpDate(time::at_utc(tmspec))
}
}
impl IntoHeaderValue for HttpDate {
type Error = InvalidHeaderValueBytes;
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut wrt = BytesMut::with_capacity(29).writer();
write!(wrt, "{}", self.0.rfc822()).unwrap();
HeaderValue::from_shared(wrt.get_mut().take().freeze())
}
}
impl From<HttpDate> for SystemTime {
fn from(date: HttpDate) -> SystemTime {
let spec = date.0.to_timespec();
if spec.sec >= 0 {
UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32)
} else {
UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32)
}
}
}
#[cfg(test)]
mod tests {
use super::HttpDate;
use time::Tm;
const NOV_07: HttpDate = HttpDate(Tm {
tm_nsec: 0,
tm_sec: 37,
tm_min: 48,
tm_hour: 8,
tm_mday: 7,
tm_mon: 10,
tm_year: 94,
tm_wday: 0,
tm_isdst: 0,
tm_yday: 0,
tm_utcoff: 0,
});
#[test]
fn test_date() {
assert_eq!(
"Sun, 07 Nov 1994 08:48:37 GMT".parse::<HttpDate>().unwrap(),
NOV_07
);
assert_eq!(
"Sunday, 07-Nov-94 08:48:37 GMT"
.parse::<HttpDate>()
.unwrap(),
NOV_07
);
assert_eq!(
"Sun Nov 7 08:48:37 1994".parse::<HttpDate>().unwrap(),
NOV_07
);
assert!("this-is-no-date".parse::<HttpDate>().is_err());
}
}

View File

@ -1,14 +0,0 @@
//! Copied for `hyper::header::shared`;
pub use self::charset::Charset;
pub use self::encoding::Encoding;
pub use self::entity::EntityTag;
pub use self::httpdate::HttpDate;
pub use self::quality_item::{q, qitem, Quality, QualityItem};
pub use language_tags::LanguageTag;
mod charset;
mod encoding;
mod entity;
mod httpdate;
mod quality_item;

View File

@ -1,294 +0,0 @@
use std::cmp;
use std::default::Default;
use std::fmt;
use std::str;
use self::internal::IntoQuality;
/// Represents a quality used in quality values.
///
/// Can be created with the `q` function.
///
/// # Implementation notes
///
/// The quality value is defined as a number between 0 and 1 with three decimal
/// places. This means there are 1001 possible values. Since floating point
/// numbers are not exact and the smallest floating point data type (`f32`)
/// consumes four bytes, hyper uses an `u16` value to store the
/// quality internally. For performance reasons you may set quality directly to
/// a value between 0 and 1000 e.g. `Quality(532)` matches the quality
/// `q=0.532`.
///
/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1)
/// gives more information on quality values in HTTP header fields.
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Quality(u16);
impl Default for Quality {
fn default() -> Quality {
Quality(1000)
}
}
/// Represents an item with a quality value as defined in
/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1).
#[derive(Clone, PartialEq, Debug)]
pub struct QualityItem<T> {
/// The actual contents of the field.
pub item: T,
/// The quality (client or server preference) for the value.
pub quality: Quality,
}
impl<T> QualityItem<T> {
/// Creates a new `QualityItem` from an item and a quality.
/// The item can be of any type.
/// The quality should be a value in the range [0, 1].
pub fn new(item: T, quality: Quality) -> QualityItem<T> {
QualityItem { item, quality }
}
}
impl<T: PartialEq> cmp::PartialOrd for QualityItem<T> {
fn partial_cmp(&self, other: &QualityItem<T>) -> Option<cmp::Ordering> {
self.quality.partial_cmp(&other.quality)
}
}
impl<T: fmt::Display> fmt::Display for QualityItem<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.item, f)?;
match self.quality.0 {
1000 => Ok(()),
0 => f.write_str("; q=0"),
x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')),
}
}
}
impl<T: str::FromStr> str::FromStr for QualityItem<T> {
type Err = ::error::ParseError;
fn from_str(s: &str) -> Result<QualityItem<T>, ::error::ParseError> {
if !s.is_ascii() {
return Err(::error::ParseError::Header);
}
// Set defaults used if parsing fails.
let mut raw_item = s;
let mut quality = 1f32;
let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect();
if parts.len() == 2 {
if parts[0].len() < 2 {
return Err(::error::ParseError::Header);
}
let start = &parts[0][0..2];
if start == "q=" || start == "Q=" {
let q_part = &parts[0][2..parts[0].len()];
if q_part.len() > 5 {
return Err(::error::ParseError::Header);
}
match q_part.parse::<f32>() {
Ok(q_value) => {
if 0f32 <= q_value && q_value <= 1f32 {
quality = q_value;
raw_item = parts[1];
} else {
return Err(::error::ParseError::Header);
}
}
Err(_) => return Err(::error::ParseError::Header),
}
}
}
match raw_item.parse::<T>() {
// we already checked above that the quality is within range
Ok(item) => Ok(QualityItem::new(item, from_f32(quality))),
Err(_) => Err(::error::ParseError::Header),
}
}
}
#[inline]
fn from_f32(f: f32) -> Quality {
// this function is only used internally. A check that `f` is within range
// should be done before calling this method. Just in case, this
// debug_assert should catch if we were forgetful
debug_assert!(
f >= 0f32 && f <= 1f32,
"q value must be between 0.0 and 1.0"
);
Quality((f * 1000f32) as u16)
}
/// Convenience function to wrap a value in a `QualityItem`
/// Sets `q` to the default 1.0
pub fn qitem<T>(item: T) -> QualityItem<T> {
QualityItem::new(item, Default::default())
}
/// Convenience function to create a `Quality` from a float or integer.
///
/// Implemented for `u16` and `f32`. Panics if value is out of range.
pub fn q<T: IntoQuality>(val: T) -> Quality {
val.into_quality()
}
mod internal {
use super::Quality;
// TryFrom is probably better, but it's not stable. For now, we want to
// keep the functionality of the `q` function, while allowing it to be
// generic over `f32` and `u16`.
//
// `q` would panic before, so keep that behavior. `TryFrom` can be
// introduced later for a non-panicking conversion.
pub trait IntoQuality: Sealed + Sized {
fn into_quality(self) -> Quality;
}
impl IntoQuality for f32 {
fn into_quality(self) -> Quality {
assert!(
self >= 0f32 && self <= 1f32,
"float must be between 0.0 and 1.0"
);
super::from_f32(self)
}
}
impl IntoQuality for u16 {
fn into_quality(self) -> Quality {
assert!(self <= 1000, "u16 must be between 0 and 1000");
Quality(self)
}
}
pub trait Sealed {}
impl Sealed for u16 {}
impl Sealed for f32 {}
}
#[cfg(test)]
mod tests {
use super::super::encoding::*;
use super::*;
#[test]
fn test_quality_item_fmt_q_1() {
let x = qitem(Chunked);
assert_eq!(format!("{}", x), "chunked");
}
#[test]
fn test_quality_item_fmt_q_0001() {
let x = QualityItem::new(Chunked, Quality(1));
assert_eq!(format!("{}", x), "chunked; q=0.001");
}
#[test]
fn test_quality_item_fmt_q_05() {
// Custom value
let x = QualityItem {
item: EncodingExt("identity".to_owned()),
quality: Quality(500),
};
assert_eq!(format!("{}", x), "identity; q=0.5");
}
#[test]
fn test_quality_item_fmt_q_0() {
// Custom value
let x = QualityItem {
item: EncodingExt("identity".to_owned()),
quality: Quality(0),
};
assert_eq!(x.to_string(), "identity; q=0");
}
#[test]
fn test_quality_item_from_str1() {
let x: Result<QualityItem<Encoding>, _> = "chunked".parse();
assert_eq!(
x.unwrap(),
QualityItem {
item: Chunked,
quality: Quality(1000),
}
);
}
#[test]
fn test_quality_item_from_str2() {
let x: Result<QualityItem<Encoding>, _> = "chunked; q=1".parse();
assert_eq!(
x.unwrap(),
QualityItem {
item: Chunked,
quality: Quality(1000),
}
);
}
#[test]
fn test_quality_item_from_str3() {
let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.5".parse();
assert_eq!(
x.unwrap(),
QualityItem {
item: Gzip,
quality: Quality(500),
}
);
}
#[test]
fn test_quality_item_from_str4() {
let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.273".parse();
assert_eq!(
x.unwrap(),
QualityItem {
item: Gzip,
quality: Quality(273),
}
);
}
#[test]
fn test_quality_item_from_str5() {
let x: Result<QualityItem<Encoding>, _> = "gzip; q=0.2739999".parse();
assert!(x.is_err());
}
#[test]
fn test_quality_item_from_str6() {
let x: Result<QualityItem<Encoding>, _> = "gzip; q=2".parse();
assert!(x.is_err());
}
#[test]
fn test_quality_item_ordering() {
let x: QualityItem<Encoding> = "gzip; q=0.5".parse().ok().unwrap();
let y: QualityItem<Encoding> = "gzip; q=0.273".parse().ok().unwrap();
let comparision_result: bool = x.gt(&y);
assert!(comparision_result)
}
#[test]
fn test_quality() {
assert_eq!(q(0.5), Quality(500));
}
#[test]
#[should_panic] // FIXME - 32-bit msvc unwinding broken
#[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
fn test_quality_invalid() {
q(-1.0);
}
#[test]
#[should_panic] // FIXME - 32-bit msvc unwinding broken
#[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)]
fn test_quality_invalid2() {
q(2.0);
}
#[test]
fn test_fuzzing_bugs() {
assert!("99999;".parse::<QualityItem<String>>().is_err());
assert!("\x0d;;;=\u{d6aa}==".parse::<QualityItem<String>>().is_err())
}
}

View File

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

View File

@ -1,84 +0,0 @@
//! Basic http responses
#![allow(non_upper_case_globals)]
use http::StatusCode;
use httpresponse::{HttpResponse, HttpResponseBuilder};
macro_rules! STATIC_RESP {
($name:ident, $status:expr) => {
#[allow(non_snake_case, missing_docs)]
pub fn $name() -> HttpResponseBuilder {
HttpResponse::build($status)
}
};
}
impl HttpResponse {
STATIC_RESP!(Ok, StatusCode::OK);
STATIC_RESP!(Created, StatusCode::CREATED);
STATIC_RESP!(Accepted, StatusCode::ACCEPTED);
STATIC_RESP!(
NonAuthoritativeInformation,
StatusCode::NON_AUTHORITATIVE_INFORMATION
);
STATIC_RESP!(NoContent, StatusCode::NO_CONTENT);
STATIC_RESP!(ResetContent, StatusCode::RESET_CONTENT);
STATIC_RESP!(PartialContent, StatusCode::PARTIAL_CONTENT);
STATIC_RESP!(MultiStatus, StatusCode::MULTI_STATUS);
STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED);
STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES);
STATIC_RESP!(MovedPermanenty, StatusCode::MOVED_PERMANENTLY);
STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY);
STATIC_RESP!(Found, StatusCode::FOUND);
STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER);
STATIC_RESP!(NotModified, StatusCode::NOT_MODIFIED);
STATIC_RESP!(UseProxy, StatusCode::USE_PROXY);
STATIC_RESP!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT);
STATIC_RESP!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT);
STATIC_RESP!(BadRequest, StatusCode::BAD_REQUEST);
STATIC_RESP!(NotFound, StatusCode::NOT_FOUND);
STATIC_RESP!(Unauthorized, StatusCode::UNAUTHORIZED);
STATIC_RESP!(PaymentRequired, StatusCode::PAYMENT_REQUIRED);
STATIC_RESP!(Forbidden, StatusCode::FORBIDDEN);
STATIC_RESP!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED);
STATIC_RESP!(NotAcceptable, StatusCode::NOT_ACCEPTABLE);
STATIC_RESP!(
ProxyAuthenticationRequired,
StatusCode::PROXY_AUTHENTICATION_REQUIRED
);
STATIC_RESP!(RequestTimeout, StatusCode::REQUEST_TIMEOUT);
STATIC_RESP!(Conflict, StatusCode::CONFLICT);
STATIC_RESP!(Gone, StatusCode::GONE);
STATIC_RESP!(LengthRequired, StatusCode::LENGTH_REQUIRED);
STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED);
STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE);
STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG);
STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR);
STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED);
STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY);
STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE);
STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT);
STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED);
STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES);
STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE);
STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED);
}
#[cfg(test)]
mod tests {
use body::Body;
use http::StatusCode;
use httpresponse::HttpResponse;
#[test]
fn test_build() {
let resp = HttpResponse::Ok().body(Body::Empty);
assert_eq!(resp.status(), StatusCode::OK);
}
}

View File

@ -1,855 +0,0 @@
use bytes::{Bytes, BytesMut};
use encoding::all::UTF_8;
use encoding::label::encoding_from_whatwg_label;
use encoding::types::{DecoderTrap, Encoding};
use encoding::EncodingRef;
use futures::{Async, Future, Poll, Stream};
use http::{header, HeaderMap};
use mime::Mime;
use serde::de::DeserializeOwned;
use serde_urlencoded;
use std::str;
use error::{
ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError,
};
use header::Header;
use json::JsonBody;
use multipart::Multipart;
/// Trait that implements general purpose operations on http messages
pub trait HttpMessage: Sized {
/// Type of message payload stream
type Stream: Stream<Item = Bytes, Error = PayloadError> + Sized;
/// Read the message headers.
fn headers(&self) -> &HeaderMap;
/// Message payload stream
fn payload(&self) -> Self::Stream;
#[doc(hidden)]
/// Get a header
fn get_header<H: Header>(&self) -> Option<H>
where
Self: Sized,
{
if self.headers().contains_key(H::name()) {
H::parse(self).ok()
} else {
None
}
}
/// Read the request content type. If request does not contain
/// *Content-Type* header, empty str get returned.
fn content_type(&self) -> &str {
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
return content_type.split(';').next().unwrap().trim();
}
}
""
}
/// Get content type encoding
///
/// UTF-8 is used by default, If request charset is not set.
fn encoding(&self) -> Result<EncodingRef, ContentTypeError> {
if let Some(mime_type) = self.mime_type()? {
if let Some(charset) = mime_type.get_param("charset") {
if let Some(enc) = encoding_from_whatwg_label(charset.as_str()) {
Ok(enc)
} else {
Err(ContentTypeError::UnknownEncoding)
}
} else {
Ok(UTF_8)
}
} else {
Ok(UTF_8)
}
}
/// Convert the request content type to a known mime type.
fn mime_type(&self) -> Result<Option<Mime>, ContentTypeError> {
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
return match content_type.parse() {
Ok(mt) => Ok(Some(mt)),
Err(_) => Err(ContentTypeError::ParseError),
};
} else {
return Err(ContentTypeError::ParseError);
}
}
Ok(None)
}
/// Check if request has chunked transfer encoding
fn chunked(&self) -> Result<bool, ParseError> {
if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
if let Ok(s) = encodings.to_str() {
Ok(s.to_lowercase().contains("chunked"))
} else {
Err(ParseError::Header)
}
} else {
Ok(false)
}
}
/// Load http message body.
///
/// By default only 256Kb payload reads to a memory, then
/// `PayloadError::Overflow` get returned. Use `MessageBody::limit()`
/// method to change upper limit.
///
/// ## Server example
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{
/// AsyncResponder, FutureResponse, HttpMessage, HttpRequest, HttpResponse,
/// };
/// use bytes::Bytes;
/// use futures::future::Future;
///
/// fn index(mut req: HttpRequest) -> FutureResponse<HttpResponse> {
/// req.body() // <- get Body future
/// .limit(1024) // <- change max size of the body to a 1kb
/// .from_err()
/// .and_then(|bytes: Bytes| { // <- complete body
/// println!("==== BODY ==== {:?}", bytes);
/// Ok(HttpResponse::Ok().into())
/// }).responder()
/// }
/// # fn main() {}
/// ```
fn body(&self) -> MessageBody<Self> {
MessageBody::new(self)
}
/// Parse `application/x-www-form-urlencoded` encoded request's body.
/// Return `UrlEncoded` future. Form can be deserialized to any type that
/// implements `Deserialize` trait from *serde*.
///
/// Returns error:
///
/// * content type is not `application/x-www-form-urlencoded`
/// * content-length is greater than 256k
///
/// ## Server example
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # use futures::Future;
/// # use std::collections::HashMap;
/// use actix_web::{FutureResponse, HttpMessage, HttpRequest, HttpResponse};
///
/// fn index(mut req: HttpRequest) -> FutureResponse<HttpResponse> {
/// Box::new(
/// req.urlencoded::<HashMap<String, String>>() // <- get UrlEncoded future
/// .from_err()
/// .and_then(|params| { // <- url encoded parameters
/// println!("==== BODY ==== {:?}", params);
/// Ok(HttpResponse::Ok().into())
/// }),
/// )
/// }
/// # fn main() {}
/// ```
fn urlencoded<T: DeserializeOwned>(&self) -> UrlEncoded<Self, T> {
UrlEncoded::new(self)
}
/// Parse `application/json` encoded body.
/// Return `JsonBody<T>` future. It resolves to a `T` value.
///
/// Returns error:
///
/// * content type is not `application/json`
/// * content length is greater than 256k
///
/// ## Server example
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::*;
/// use futures::future::{ok, Future};
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
/// println!("==== BODY ==== {:?}", val);
/// Ok(HttpResponse::Ok().into())
/// }).responder()
/// }
/// # fn main() {}
/// ```
fn json<T: DeserializeOwned>(&self) -> JsonBody<Self, T> {
JsonBody::new::<()>(self, None)
}
/// Return stream to http payload processes as multipart.
///
/// Content-type: multipart/form-data;
///
/// ## Server example
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate env_logger;
/// # extern crate futures;
/// # extern crate actix;
/// # use std::str;
/// # use actix_web::*;
/// # use actix::FinishStream;
/// # use futures::{Future, Stream};
/// # use futures::future::{ok, result, Either};
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
/// req.multipart().from_err() // <- get multipart stream for current request
/// .and_then(|item| match item { // <- iterate over multipart items
/// multipart::MultipartItem::Field(field) => {
/// // Field in turn is stream of *Bytes* object
/// Either::A(field.from_err()
/// .map(|c| println!("-- CHUNK: \n{:?}", str::from_utf8(&c)))
/// .finish())
/// },
/// multipart::MultipartItem::Nested(mp) => {
/// // Or item could be nested Multipart stream
/// Either::B(ok(()))
/// }
/// })
/// .finish() // <- Stream::finish() combinator from actix
/// .map(|_| HttpResponse::Ok().into())
/// .responder()
/// }
/// # fn main() {}
/// ```
fn multipart(&self) -> Multipart<Self::Stream> {
let boundary = Multipart::boundary(self.headers());
Multipart::new(boundary, self.payload())
}
/// Return stream of lines.
fn readlines(&self) -> Readlines<Self> {
Readlines::new(self)
}
}
/// Stream to read request line by line.
pub struct Readlines<T: HttpMessage> {
stream: T::Stream,
buff: BytesMut,
limit: usize,
checked_buff: bool,
encoding: EncodingRef,
err: Option<ReadlinesError>,
}
impl<T: HttpMessage> Readlines<T> {
/// Create a new stream to read request line by line.
fn new(req: &T) -> Self {
let encoding = match req.encoding() {
Ok(enc) => enc,
Err(err) => return Self::err(req, err.into()),
};
Readlines {
stream: req.payload(),
buff: BytesMut::with_capacity(262_144),
limit: 262_144,
checked_buff: true,
err: None,
encoding,
}
}
/// Change max line size. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
fn err(req: &T, err: ReadlinesError) -> Self {
Readlines {
stream: req.payload(),
buff: BytesMut::new(),
limit: 262_144,
checked_buff: true,
encoding: UTF_8,
err: Some(err),
}
}
}
impl<T: HttpMessage + 'static> Stream for Readlines<T> {
type Item = String;
type Error = ReadlinesError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if let Some(err) = self.err.take() {
return Err(err);
}
// check if there is a newline in the buffer
if !self.checked_buff {
let mut found: Option<usize> = None;
for (ind, b) in self.buff.iter().enumerate() {
if *b == b'\n' {
found = Some(ind);
break;
}
}
if let Some(ind) = found {
// check if line is longer than limit
if ind + 1 > self.limit {
return Err(ReadlinesError::LimitOverflow);
}
let enc: *const Encoding = self.encoding as *const Encoding;
let line = if enc == UTF_8 {
str::from_utf8(&self.buff.split_to(ind + 1))
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
self.encoding
.decode(&self.buff.split_to(ind + 1), DecoderTrap::Strict)
.map_err(|_| ReadlinesError::EncodingError)?
};
return Ok(Async::Ready(Some(line)));
}
self.checked_buff = true;
}
// poll req for more bytes
match self.stream.poll() {
Ok(Async::Ready(Some(mut bytes))) => {
// check if there is a newline in bytes
let mut found: Option<usize> = None;
for (ind, b) in bytes.iter().enumerate() {
if *b == b'\n' {
found = Some(ind);
break;
}
}
if let Some(ind) = found {
// check if line is longer than limit
if ind + 1 > self.limit {
return Err(ReadlinesError::LimitOverflow);
}
let enc: *const Encoding = self.encoding as *const Encoding;
let line = if enc == UTF_8 {
str::from_utf8(&bytes.split_to(ind + 1))
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
self.encoding
.decode(&bytes.split_to(ind + 1), DecoderTrap::Strict)
.map_err(|_| ReadlinesError::EncodingError)?
};
// extend buffer with rest of the bytes;
self.buff.extend_from_slice(&bytes);
self.checked_buff = false;
return Ok(Async::Ready(Some(line)));
}
self.buff.extend_from_slice(&bytes);
Ok(Async::NotReady)
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => {
if self.buff.is_empty() {
return Ok(Async::Ready(None));
}
if self.buff.len() > self.limit {
return Err(ReadlinesError::LimitOverflow);
}
let enc: *const Encoding = self.encoding as *const Encoding;
let line = if enc == UTF_8 {
str::from_utf8(&self.buff)
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
self.encoding
.decode(&self.buff, DecoderTrap::Strict)
.map_err(|_| ReadlinesError::EncodingError)?
};
self.buff.clear();
Ok(Async::Ready(Some(line)))
}
Err(e) => Err(ReadlinesError::from(e)),
}
}
}
/// Future that resolves to a complete http message body.
pub struct MessageBody<T: HttpMessage> {
limit: usize,
length: Option<usize>,
stream: Option<T::Stream>,
err: Option<PayloadError>,
fut: Option<Box<Future<Item = Bytes, Error = PayloadError>>>,
}
impl<T: HttpMessage> MessageBody<T> {
/// Create `MessageBody` for request.
pub fn new(req: &T) -> MessageBody<T> {
let mut len = None;
if let Some(l) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() {
len = Some(l)
} else {
return Self::err(PayloadError::UnknownLength);
}
} else {
return Self::err(PayloadError::UnknownLength);
}
}
MessageBody {
limit: 262_144,
length: len,
stream: Some(req.payload()),
fut: None,
err: None,
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
fn err(e: PayloadError) -> Self {
MessageBody {
stream: None,
limit: 262_144,
fut: None,
err: Some(e),
length: None,
}
}
}
impl<T> Future for MessageBody<T>
where
T: HttpMessage + 'static,
{
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut {
return fut.poll();
}
if let Some(err) = self.err.take() {
return Err(err);
}
if let Some(len) = self.length.take() {
if len > self.limit {
return Err(PayloadError::Overflow);
}
}
// future
let limit = self.limit;
self.fut = Some(Box::new(
self.stream
.take()
.expect("Can not be used second time")
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(PayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
}).map(|body| body.freeze()),
));
self.poll()
}
}
/// Future that resolves to a parsed urlencoded values.
pub struct UrlEncoded<T: HttpMessage, U> {
stream: Option<T::Stream>,
limit: usize,
length: Option<usize>,
encoding: EncodingRef,
err: Option<UrlencodedError>,
fut: Option<Box<Future<Item = U, Error = UrlencodedError>>>,
}
impl<T: HttpMessage, U> UrlEncoded<T, U> {
/// Create a new future to URL encode a request
pub fn new(req: &T) -> UrlEncoded<T, U> {
// check content type
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
return Self::err(UrlencodedError::ContentType);
}
let encoding = match req.encoding() {
Ok(enc) => enc,
Err(_) => return Self::err(UrlencodedError::ContentType),
};
let mut len = None;
if let Some(l) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() {
len = Some(l)
} else {
return Self::err(UrlencodedError::UnknownLength);
}
} else {
return Self::err(UrlencodedError::UnknownLength);
}
};
UrlEncoded {
encoding,
stream: Some(req.payload()),
limit: 262_144,
length: len,
fut: None,
err: None,
}
}
fn err(e: UrlencodedError) -> Self {
UrlEncoded {
stream: None,
limit: 262_144,
fut: None,
err: Some(e),
length: None,
encoding: UTF_8,
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
impl<T, U> Future for UrlEncoded<T, U>
where
T: HttpMessage + 'static,
U: DeserializeOwned + 'static,
{
type Item = U;
type Error = UrlencodedError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut {
return fut.poll();
}
if let Some(err) = self.err.take() {
return Err(err);
}
// payload size
let limit = self.limit;
if let Some(len) = self.length.take() {
if len > limit {
return Err(UrlencodedError::Overflow);
}
}
// future
let encoding = self.encoding;
let fut = self
.stream
.take()
.expect("UrlEncoded could not be used second time")
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(UrlencodedError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
}).and_then(move |body| {
if (encoding as *const Encoding) == UTF_8 {
serde_urlencoded::from_bytes::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
} else {
let body = encoding
.decode(&body, DecoderTrap::Strict)
.map_err(|_| UrlencodedError::Parse)?;
serde_urlencoded::from_str::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
}
});
self.fut = Some(Box::new(fut));
self.poll()
}
}
#[cfg(test)]
mod tests {
use super::*;
use encoding::all::ISO_8859_2;
use encoding::Encoding;
use futures::Async;
use mime;
use test::TestRequest;
#[test]
fn test_content_type() {
let req = TestRequest::with_header("content-type", "text/plain").finish();
assert_eq!(req.content_type(), "text/plain");
let req =
TestRequest::with_header("content-type", "application/json; charset=utf=8")
.finish();
assert_eq!(req.content_type(), "application/json");
let req = TestRequest::default().finish();
assert_eq!(req.content_type(), "");
}
#[test]
fn test_mime_type() {
let req = TestRequest::with_header("content-type", "application/json").finish();
assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON));
let req = TestRequest::default().finish();
assert_eq!(req.mime_type().unwrap(), None);
let req =
TestRequest::with_header("content-type", "application/json; charset=utf-8")
.finish();
let mt = req.mime_type().unwrap().unwrap();
assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8));
assert_eq!(mt.type_(), mime::APPLICATION);
assert_eq!(mt.subtype(), mime::JSON);
}
#[test]
fn test_mime_type_error() {
let req = TestRequest::with_header(
"content-type",
"applicationadfadsfasdflknadsfklnadsfjson",
).finish();
assert_eq!(Err(ContentTypeError::ParseError), req.mime_type());
}
#[test]
fn test_encoding() {
let req = TestRequest::default().finish();
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
let req = TestRequest::with_header("content-type", "application/json").finish();
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
let req = TestRequest::with_header(
"content-type",
"application/json; charset=ISO-8859-2",
).finish();
assert_eq!(ISO_8859_2.name(), req.encoding().unwrap().name());
}
#[test]
fn test_encoding_error() {
let req = TestRequest::with_header("content-type", "applicatjson").finish();
assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
let req = TestRequest::with_header(
"content-type",
"application/json; charset=kkkttktk",
).finish();
assert_eq!(
Some(ContentTypeError::UnknownEncoding),
req.encoding().err()
);
}
#[test]
fn test_chunked() {
let req = TestRequest::default().finish();
assert!(!req.chunked().unwrap());
let req =
TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert!(req.chunked().unwrap());
let req = TestRequest::default()
.header(
header::TRANSFER_ENCODING,
Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"),
).finish();
assert!(req.chunked().is_err());
}
impl PartialEq for UrlencodedError {
fn eq(&self, other: &UrlencodedError) -> bool {
match *self {
UrlencodedError::Chunked => match *other {
UrlencodedError::Chunked => true,
_ => false,
},
UrlencodedError::Overflow => match *other {
UrlencodedError::Overflow => true,
_ => false,
},
UrlencodedError::UnknownLength => match *other {
UrlencodedError::UnknownLength => true,
_ => false,
},
UrlencodedError::ContentType => match *other {
UrlencodedError::ContentType => true,
_ => false,
},
_ => false,
}
}
}
#[derive(Deserialize, Debug, PartialEq)]
struct Info {
hello: String,
}
#[test]
fn test_urlencoded_error() {
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "xxxx")
.finish();
assert_eq!(
req.urlencoded::<Info>().poll().err().unwrap(),
UrlencodedError::UnknownLength
);
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "1000000")
.finish();
assert_eq!(
req.urlencoded::<Info>().poll().err().unwrap(),
UrlencodedError::Overflow
);
let req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain")
.header(header::CONTENT_LENGTH, "10")
.finish();
assert_eq!(
req.urlencoded::<Info>().poll().err().unwrap(),
UrlencodedError::ContentType
);
}
#[test]
fn test_urlencoded() {
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
let result = req.urlencoded::<Info>().poll().ok().unwrap();
assert_eq!(
result,
Async::Ready(Info {
hello: "world".to_owned()
})
);
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded; charset=utf-8",
).header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
let result = req.urlencoded().poll().ok().unwrap();
assert_eq!(
result,
Async::Ready(Info {
hello: "world".to_owned()
})
);
}
#[test]
fn test_message_body() {
let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish();
match req.body().poll().err().unwrap() {
PayloadError::UnknownLength => (),
_ => unreachable!("error"),
}
let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish();
match req.body().poll().err().unwrap() {
PayloadError::Overflow => (),
_ => unreachable!("error"),
}
let req = TestRequest::default()
.set_payload(Bytes::from_static(b"test"))
.finish();
match req.body().poll().ok().unwrap() {
Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")),
_ => unreachable!("error"),
}
let req = TestRequest::default()
.set_payload(Bytes::from_static(b"11111111111111"))
.finish();
match req.body().limit(5).poll().err().unwrap() {
PayloadError::Overflow => (),
_ => unreachable!("error"),
}
}
#[test]
fn test_readlines() {
let req = TestRequest::default()
.set_payload(Bytes::from_static(
b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\
industry. Lorem Ipsum has been the industry's standard dummy\n\
Contrary to popular belief, Lorem Ipsum is not simply random text.",
)).finish();
let mut r = Readlines::new(&req);
match r.poll().ok().unwrap() {
Async::Ready(Some(s)) => assert_eq!(
s,
"Lorem Ipsum is simply dummy text of the printing and typesetting\n"
),
_ => unreachable!("error"),
}
match r.poll().ok().unwrap() {
Async::Ready(Some(s)) => assert_eq!(
s,
"industry. Lorem Ipsum has been the industry's standard dummy\n"
),
_ => unreachable!("error"),
}
match r.poll().ok().unwrap() {
Async::Ready(Some(s)) => assert_eq!(
s,
"Contrary to popular belief, Lorem Ipsum is not simply random text."
),
_ => unreachable!("error"),
}
}
}

View File

@ -1,545 +0,0 @@
//! HTTP Request message related code.
use std::cell::{Ref, RefMut};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::ops::Deref;
use std::rc::Rc;
use std::{fmt, str};
use cookie::Cookie;
use futures_cpupool::CpuPool;
use http::{header, HeaderMap, Method, StatusCode, Uri, Version};
use url::{form_urlencoded, Url};
use body::Body;
use error::{CookieParseError, UrlGenerationError};
use extensions::Extensions;
use handler::FromRequest;
use httpmessage::HttpMessage;
use httpresponse::{HttpResponse, HttpResponseBuilder};
use info::ConnectionInfo;
use param::Params;
use payload::Payload;
use router::ResourceInfo;
use server::Request;
struct Query(HashMap<String, String>);
struct Cookies(Vec<Cookie<'static>>);
/// An HTTP Request
pub struct HttpRequest<S = ()> {
req: Option<Request>,
state: Rc<S>,
resource: ResourceInfo,
}
impl<S> HttpMessage for HttpRequest<S> {
type Stream = Payload;
#[inline]
fn headers(&self) -> &HeaderMap {
self.request().headers()
}
#[inline]
fn payload(&self) -> Payload {
if let Some(payload) = self.request().inner.payload.borrow_mut().take() {
payload
} else {
Payload::empty()
}
}
}
impl<S> Deref for HttpRequest<S> {
type Target = Request;
fn deref(&self) -> &Request {
self.request()
}
}
impl<S> HttpRequest<S> {
#[inline]
pub(crate) fn new(
req: Request, state: Rc<S>, resource: ResourceInfo,
) -> HttpRequest<S> {
HttpRequest {
state,
resource,
req: Some(req),
}
}
#[inline]
/// Construct new http request with state.
pub(crate) fn with_state<NS>(&self, state: Rc<NS>) -> HttpRequest<NS> {
HttpRequest {
state,
req: self.req.as_ref().map(|r| r.clone()),
resource: self.resource.clone(),
}
}
/// Construct new http request with empty state.
pub fn drop_state(&self) -> HttpRequest {
HttpRequest {
state: Rc::new(()),
req: self.req.as_ref().map(|r| r.clone()),
resource: self.resource.clone(),
}
}
#[inline]
/// Construct new http request with new RouteInfo.
pub(crate) fn with_route_info(&self, mut resource: ResourceInfo) -> HttpRequest<S> {
resource.merge(&self.resource);
HttpRequest {
resource,
req: self.req.as_ref().map(|r| r.clone()),
state: self.state.clone(),
}
}
/// Shared application state
#[inline]
pub fn state(&self) -> &S {
&self.state
}
#[inline]
/// Server request
pub fn request(&self) -> &Request {
self.req.as_ref().unwrap()
}
/// Request extensions
#[inline]
pub fn extensions(&self) -> Ref<Extensions> {
self.request().extensions()
}
/// Mutable reference to a the request's extensions
#[inline]
pub fn extensions_mut(&self) -> RefMut<Extensions> {
self.request().extensions_mut()
}
/// Default `CpuPool`
#[inline]
#[doc(hidden)]
pub fn cpu_pool(&self) -> &CpuPool {
self.request().server_settings().cpu_pool()
}
#[inline]
/// Create http response
pub fn response(&self, status: StatusCode, body: Body) -> HttpResponse {
self.request().server_settings().get_response(status, body)
}
#[inline]
/// Create http response builder
pub fn build_response(&self, status: StatusCode) -> HttpResponseBuilder {
self.request()
.server_settings()
.get_response_builder(status)
}
/// Read the Request Uri.
#[inline]
pub fn uri(&self) -> &Uri {
self.request().inner.url.uri()
}
/// Read the Request method.
#[inline]
pub fn method(&self) -> &Method {
&self.request().inner.method
}
/// Read the Request Version.
#[inline]
pub fn version(&self) -> Version {
self.request().inner.version
}
/// The target path of this Request.
#[inline]
pub fn path(&self) -> &str {
self.request().inner.url.path()
}
/// Get *ConnectionInfo* for the correct request.
#[inline]
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
self.request().connection_info()
}
/// Generate url for named resource
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::{App, HttpRequest, HttpResponse, http};
/// #
/// fn index(req: HttpRequest) -> HttpResponse {
/// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
/// HttpResponse::Ok().into()
/// }
///
/// fn main() {
/// let app = App::new()
/// .resource("/test/{one}/{two}/{three}", |r| {
/// r.name("foo"); // <- set resource name, then it could be used in `url_for`
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// })
/// .finish();
/// }
/// ```
pub fn url_for<U, I>(
&self, name: &str, elements: U,
) -> Result<Url, UrlGenerationError>
where
U: IntoIterator<Item = I>,
I: AsRef<str>,
{
self.resource.url_for(&self, name, elements)
}
/// Generate url for named resource
///
/// This method is similar to `HttpRequest::url_for()` but it can be used
/// for urls that do not contain variable parts.
pub fn url_for_static(&self, name: &str) -> Result<Url, UrlGenerationError> {
const NO_PARAMS: [&str; 0] = [];
self.url_for(name, &NO_PARAMS)
}
/// This method returns reference to current `ResourceInfo` object.
#[inline]
pub fn resource(&self) -> &ResourceInfo {
&self.resource
}
/// Peer socket address
///
/// Peer address is actual socket address, if proxy is used in front of
/// actix http server, then peer address would be address of this proxy.
///
/// To get client connection information `connection_info()` method should
/// be used.
#[inline]
pub fn peer_addr(&self) -> Option<SocketAddr> {
self.request().inner.addr
}
/// url query parameters.
pub fn query(&self) -> Ref<HashMap<String, String>> {
if self.extensions().get::<Query>().is_none() {
let mut query = HashMap::new();
for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) {
query.insert(key.as_ref().to_string(), val.to_string());
}
self.extensions_mut().insert(Query(query));
}
Ref::map(self.extensions(), |ext| &ext.get::<Query>().unwrap().0)
}
/// The query string in the URL.
///
/// E.g., id=10
#[inline]
pub fn query_string(&self) -> &str {
if let Some(query) = self.uri().query().as_ref() {
query
} else {
""
}
}
/// Load request cookies.
#[inline]
pub fn cookies(&self) -> Result<Ref<Vec<Cookie<'static>>>, CookieParseError> {
if self.extensions().get::<Cookies>().is_none() {
let mut cookies = Vec::new();
for hdr in self.request().inner.headers.get_all(header::COOKIE) {
let s =
str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
for cookie_str in s.split(';').map(|s| s.trim()) {
if !cookie_str.is_empty() {
cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned());
}
}
}
self.extensions_mut().insert(Cookies(cookies));
}
Ok(Ref::map(self.extensions(), |ext| {
&ext.get::<Cookies>().unwrap().0
}))
}
/// Return request cookie.
#[inline]
pub fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
if let Ok(cookies) = self.cookies() {
for cookie in cookies.iter() {
if cookie.name() == name {
return Some(cookie.to_owned());
}
}
}
None
}
pub(crate) fn set_cookies(&mut self, cookies: Option<Vec<Cookie<'static>>>) {
if let Some(cookies) = cookies {
self.extensions_mut().insert(Cookies(cookies));
}
}
/// Get a reference to the Params object.
///
/// Params is a container for url parameters.
/// A variable segment is specified in the form `{identifier}`,
/// where the identifier can be used later in a request handler to
/// access the matched value for that segment.
#[inline]
pub fn match_info(&self) -> &Params {
&self.resource.match_info()
}
/// Check if request requires connection upgrade
pub(crate) fn upgrade(&self) -> bool {
self.request().upgrade()
}
/// Set read buffer capacity
///
/// Default buffer capacity is 32Kb.
pub fn set_read_buffer_capacity(&mut self, cap: usize) {
if let Some(payload) = self.request().inner.payload.borrow_mut().as_mut() {
payload.set_read_buffer_capacity(cap)
}
}
}
impl<S> Drop for HttpRequest<S> {
fn drop(&mut self) {
if let Some(req) = self.req.take() {
req.release();
}
}
}
impl<S> Clone for HttpRequest<S> {
fn clone(&self) -> HttpRequest<S> {
HttpRequest {
req: self.req.as_ref().map(|r| r.clone()),
state: self.state.clone(),
resource: self.resource.clone(),
}
}
}
impl<S> FromRequest<S> for HttpRequest<S> {
type Config = ();
type Result = Self;
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
req.clone()
}
}
impl<S> fmt::Debug for HttpRequest<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
"\nHttpRequest {:?} {}:{}",
self.version(),
self.method(),
self.path()
)?;
if !self.query_string().is_empty() {
writeln!(f, " query: ?{:?}", self.query_string())?;
}
if !self.match_info().is_empty() {
writeln!(f, " params: {:?}", self.match_info())?;
}
writeln!(f, " headers:")?;
for (key, val) in self.headers().iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use resource::Resource;
use router::{ResourceDef, Router};
use test::TestRequest;
#[test]
fn test_debug() {
let req = TestRequest::with_header("content-type", "text/plain").finish();
let dbg = format!("{:?}", req);
assert!(dbg.contains("HttpRequest"));
}
#[test]
fn test_no_request_cookies() {
let req = TestRequest::default().finish();
assert!(req.cookies().unwrap().is_empty());
}
#[test]
fn test_request_cookies() {
let req = TestRequest::default()
.header(header::COOKIE, "cookie1=value1")
.header(header::COOKIE, "cookie2=value2")
.finish();
{
let cookies = req.cookies().unwrap();
assert_eq!(cookies.len(), 2);
assert_eq!(cookies[0].name(), "cookie1");
assert_eq!(cookies[0].value(), "value1");
assert_eq!(cookies[1].name(), "cookie2");
assert_eq!(cookies[1].value(), "value2");
}
let cookie = req.cookie("cookie1");
assert!(cookie.is_some());
let cookie = cookie.unwrap();
assert_eq!(cookie.name(), "cookie1");
assert_eq!(cookie.value(), "value1");
let cookie = req.cookie("cookie-unknown");
assert!(cookie.is_none());
}
#[test]
fn test_request_query() {
let req = TestRequest::with_uri("/?id=test").finish();
assert_eq!(req.query_string(), "id=test");
let query = req.query();
assert_eq!(&query["id"], "test");
}
#[test]
fn test_request_match_info() {
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/{key}/")));
let req = TestRequest::with_uri("/value/?id=test").finish();
let info = router.recognize(&req, &(), 0);
assert_eq!(info.match_info().get("key"), Some("value"));
}
#[test]
fn test_url_for() {
let mut router = Router::<()>::default();
let mut resource = Resource::new(ResourceDef::new("/user/{name}.{ext}"));
resource.name("index");
router.register_resource(resource);
let info = router.default_route_info();
assert!(!info.has_prefixed_resource("/use/"));
assert!(info.has_resource("/user/test.html"));
assert!(info.has_prefixed_resource("/user/test.html"));
assert!(!info.has_resource("/test/unknown"));
assert!(!info.has_prefixed_resource("/test/unknown"));
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org")
.finish_with_router(router);
assert_eq!(
req.url_for("unknown", &["test"]),
Err(UrlGenerationError::ResourceNotFound)
);
assert_eq!(
req.url_for("index", &["test"]),
Err(UrlGenerationError::NotEnoughElements)
);
let url = req.url_for("index", &["test", "html"]);
assert_eq!(
url.ok().unwrap().as_str(),
"http://www.rust-lang.org/user/test.html"
);
}
#[test]
fn test_url_for_with_prefix() {
let mut resource = Resource::new(ResourceDef::new("/user/{name}.html"));
resource.name("index");
let mut router = Router::<()>::default();
router.set_prefix("/prefix");
router.register_resource(resource);
let mut info = router.default_route_info();
info.set_prefix(7);
assert!(!info.has_prefixed_resource("/use/"));
assert!(info.has_resource("/user/test.html"));
assert!(!info.has_prefixed_resource("/user/test.html"));
assert!(!info.has_resource("/prefix/user/test.html"));
assert!(info.has_prefixed_resource("/prefix/user/test.html"));
let req = TestRequest::with_uri("/prefix/test")
.prefix(7)
.header(header::HOST, "www.rust-lang.org")
.finish_with_router(router);
let url = req.url_for("index", &["test"]);
assert_eq!(
url.ok().unwrap().as_str(),
"http://www.rust-lang.org/prefix/user/test.html"
);
}
#[test]
fn test_url_for_static() {
let mut resource = Resource::new(ResourceDef::new("/index.html"));
resource.name("index");
let mut router = Router::<()>::default();
router.set_prefix("/prefix");
router.register_resource(resource);
let mut info = router.default_route_info();
info.set_prefix(7);
assert!(info.has_resource("/index.html"));
assert!(!info.has_prefixed_resource("/index.html"));
assert!(!info.has_resource("/prefix/index.html"));
assert!(info.has_prefixed_resource("/prefix/index.html"));
let req = TestRequest::with_uri("/prefix/test")
.prefix(7)
.header(header::HOST, "www.rust-lang.org")
.finish_with_router(router);
let url = req.url_for_static("index");
assert_eq!(
url.ok().unwrap().as_str(),
"http://www.rust-lang.org/prefix/index.html"
);
}
#[test]
fn test_url_for_external() {
let mut router = Router::<()>::default();
router.register_external(
"youtube",
ResourceDef::external("https://youtube.com/watch/{video_id}"),
);
let info = router.default_route_info();
assert!(!info.has_resource("https://youtube.com/watch/unknown"));
assert!(!info.has_prefixed_resource("https://youtube.com/watch/unknown"));
let req = TestRequest::default().finish_with_router(router);
let url = req.url_for("youtube", &["oHg5SJYRHA0"]);
assert_eq!(
url.ok().unwrap().as_str(),
"https://youtube.com/watch/oHg5SJYRHA0"
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,14 @@
use http::header::{self, HeaderName};
use server::Request;
use std::cell::Ref;
use crate::dev::{AppConfig, RequestHead};
use crate::http::header::{self, HeaderName};
const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for";
const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host";
const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto";
/// `HttpRequest` connection information
#[derive(Clone, Default)]
#[derive(Debug, Clone, Default)]
pub struct ConnectionInfo {
scheme: String,
host: String,
@ -16,18 +18,22 @@ pub struct ConnectionInfo {
impl ConnectionInfo {
/// Create *ConnectionInfo* instance for a request.
#[cfg_attr(
feature = "cargo-clippy",
allow(cyclomatic_complexity)
)]
pub fn update(&mut self, req: &Request) {
pub fn get<'a>(req: &'a RequestHead, cfg: &AppConfig) -> Ref<'a, Self> {
if !req.extensions().contains::<ConnectionInfo>() {
req.extensions_mut().insert(ConnectionInfo::new(req, cfg));
}
Ref::map(req.extensions(), |e| e.get().unwrap())
}
#[allow(clippy::cyclomatic_complexity)]
fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo {
let mut host = None;
let mut scheme = None;
let mut remote = None;
let mut peer = None;
let peer = None;
// load forwarded header
for hdr in req.headers().get_all(header::FORWARDED) {
for hdr in req.headers.get_all(header::FORWARDED) {
if let Ok(val) = hdr.to_str() {
for pair in val.split(';') {
for el in pair.split(',') {
@ -35,15 +41,21 @@ impl ConnectionInfo {
if let Some(name) = items.next() {
if let Some(val) = items.next() {
match &name.to_lowercase() as &str {
"for" => if remote.is_none() {
remote = Some(val.trim());
},
"proto" => if scheme.is_none() {
scheme = Some(val.trim());
},
"host" => if host.is_none() {
host = Some(val.trim());
},
"for" => {
if remote.is_none() {
remote = Some(val.trim());
}
}
"proto" => {
if scheme.is_none() {
scheme = Some(val.trim());
}
}
"host" => {
if host.is_none() {
host = Some(val.trim());
}
}
_ => (),
}
}
@ -56,7 +68,7 @@ impl ConnectionInfo {
// scheme
if scheme.is_none() {
if let Some(h) = req
.headers()
.headers
.get(HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap())
{
if let Ok(h) = h.to_str() {
@ -64,8 +76,8 @@ impl ConnectionInfo {
}
}
if scheme.is_none() {
scheme = req.uri().scheme_part().map(|a| a.as_str());
if scheme.is_none() && req.server_settings().secure() {
scheme = req.uri.scheme_part().map(|a| a.as_str());
if scheme.is_none() && cfg.secure() {
scheme = Some("https")
}
}
@ -74,7 +86,7 @@ impl ConnectionInfo {
// host
if host.is_none() {
if let Some(h) = req
.headers()
.headers
.get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap())
{
if let Ok(h) = h.to_str() {
@ -82,13 +94,13 @@ impl ConnectionInfo {
}
}
if host.is_none() {
if let Some(h) = req.headers().get(header::HOST) {
if let Some(h) = req.headers.get(header::HOST) {
host = h.to_str().ok();
}
if host.is_none() {
host = req.uri().authority_part().map(|a| a.as_str());
host = req.uri.authority_part().map(|a| a.as_str());
if host.is_none() {
host = Some(req.server_settings().host());
host = Some(cfg.host());
}
}
}
@ -97,23 +109,25 @@ impl ConnectionInfo {
// remote addr
if remote.is_none() {
if let Some(h) = req
.headers()
.headers
.get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap())
{
if let Ok(h) = h.to_str() {
remote = h.split(',').next().map(|v| v.trim());
}
}
if remote.is_none() {
// get peeraddr from socketaddr
peer = req.peer_addr().map(|addr| format!("{}", addr));
}
// if remote.is_none() {
// get peeraddr from socketaddr
// peer = req.peer_addr().map(|addr| format!("{}", addr));
// }
}
self.scheme = scheme.unwrap_or("http").to_owned();
self.host = host.unwrap_or("localhost").to_owned();
self.remote = remote.map(|s| s.to_owned());
self.peer = peer;
ConnectionInfo {
peer,
scheme: scheme.unwrap_or("http").to_owned(),
host: host.unwrap_or("localhost").to_owned(),
remote: remote.map(|s| s.to_owned()),
}
}
/// Scheme of the request.
@ -163,13 +177,12 @@ impl ConnectionInfo {
#[cfg(test)]
mod tests {
use super::*;
use test::TestRequest;
use crate::test::TestRequest;
#[test]
fn test_forwarded() {
let req = TestRequest::default().request();
let mut info = ConnectionInfo::default();
info.update(&req);
let req = TestRequest::default().to_http_request();
let info = req.connection_info();
assert_eq!(info.scheme(), "http");
assert_eq!(info.host(), "localhost:8080");
@ -177,44 +190,40 @@ mod tests {
.header(
header::FORWARDED,
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
).request();
)
.to_http_request();
let mut info = ConnectionInfo::default();
info.update(&req);
let info = req.connection_info();
assert_eq!(info.scheme(), "https");
assert_eq!(info.host(), "rust-lang.org");
assert_eq!(info.remote(), Some("192.0.2.60"));
let req = TestRequest::default()
.header(header::HOST, "rust-lang.org")
.request();
.to_http_request();
let mut info = ConnectionInfo::default();
info.update(&req);
let info = req.connection_info();
assert_eq!(info.scheme(), "http");
assert_eq!(info.host(), "rust-lang.org");
assert_eq!(info.remote(), None);
let req = TestRequest::default()
.header(X_FORWARDED_FOR, "192.0.2.60")
.request();
let mut info = ConnectionInfo::default();
info.update(&req);
.to_http_request();
let info = req.connection_info();
assert_eq!(info.remote(), Some("192.0.2.60"));
let req = TestRequest::default()
.header(X_FORWARDED_HOST, "192.0.2.60")
.request();
let mut info = ConnectionInfo::default();
info.update(&req);
.to_http_request();
let info = req.connection_info();
assert_eq!(info.host(), "192.0.2.60");
assert_eq!(info.remote(), None);
let req = TestRequest::default()
.header(X_FORWARDED_PROTO, "https")
.request();
let mut info = ConnectionInfo::default();
info.update(&req);
.to_http_request();
let info = req.connection_info();
assert_eq!(info.scheme(), "https");
}
}

View File

@ -1,519 +0,0 @@
use bytes::BytesMut;
use futures::{Future, Poll, Stream};
use http::header::CONTENT_LENGTH;
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use mime;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json;
use error::{Error, JsonPayloadError};
use handler::{FromRequest, Responder};
use http::StatusCode;
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
/// Json helper
///
/// Json can be used for two different purpose. First is for json response
/// generation and second is for extracting typed information from request's
/// payload.
///
/// To extract typed information from request's body, the type `T` must
/// implement the `Deserialize` trait from *serde*.
///
/// [**JsonConfig**](dev/struct.JsonConfig.html) allows to configure extraction
/// process.
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Json, Result, http};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// deserialize `Info` from request's body
/// fn index(info: Json<Info>) -> Result<String> {
/// Ok(format!("Welcome {}!", info.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html",
/// |r| r.method(http::Method::POST).with(index)); // <- use `with` extractor
/// }
/// ```
///
/// The `Json` type allows you to respond with well-formed JSON data: simply
/// return a value of type Json<T> where T is the type of a structure
/// to serialize into *JSON*. The type `T` must implement the `Serialize`
/// trait from *serde*.
///
/// ```rust
/// # extern crate actix_web;
/// # #[macro_use] extern crate serde_derive;
/// # use actix_web::*;
/// #
/// #[derive(Serialize)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(req: HttpRequest) -> Result<Json<MyObj>> {
/// Ok(Json(MyObj {
/// name: req.match_info().query("name")?,
/// }))
/// }
/// # fn main() {}
/// ```
pub struct Json<T>(pub T);
impl<T> Json<T> {
/// Deconstruct to an inner value
pub fn into_inner(self) -> T {
self.0
}
}
impl<T> Deref for Json<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> DerefMut for Json<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T> fmt::Debug for Json<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Json: {:?}", self.0)
}
}
impl<T> fmt::Display for Json<T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl<T: Serialize> Responder for Json<T> {
type Item = HttpResponse;
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
let body = serde_json::to_string(&self.0)?;
Ok(req
.build_response(StatusCode::OK)
.content_type("application/json")
.body(body))
}
}
impl<T, S> FromRequest<S> for Json<T>
where
T: DeserializeOwned + 'static,
S: 'static,
{
type Config = JsonConfig<S>;
type Result = Box<Future<Item = Self, Error = Error>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
let req2 = req.clone();
let err = Rc::clone(&cfg.ehandler);
Box::new(
JsonBody::new(req, Some(cfg))
.limit(cfg.limit)
.map_err(move |e| (*err)(e, &req2))
.map(Json),
)
}
}
/// Json extractor configuration
///
/// ```rust
/// # extern crate actix_web;
/// extern crate mime;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{error, http, App, HttpResponse, Json, Result};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// deserialize `Info` from request's body, max payload size is 4kb
/// fn index(info: Json<Info>) -> Result<String> {
/// Ok(format!("Welcome {}!", info.username))
/// }
///
/// fn main() {
/// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::POST)
/// .with_config(index, |cfg| {
/// cfg.0.limit(4096) // <- change json extractor configuration
/// .content_type(|mime| { // <- accept text/plain content type
/// mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
/// })
/// .error_handler(|err, req| { // <- create custom error response
/// error::InternalError::from_response(
/// err, HttpResponse::Conflict().finish()).into()
/// });
/// })
/// });
/// }
/// ```
pub struct JsonConfig<S> {
limit: usize,
ehandler: Rc<Fn(JsonPayloadError, &HttpRequest<S>) -> Error>,
content_type: Option<Box<Fn(mime::Mime) -> bool>>,
}
impl<S> JsonConfig<S> {
/// Change max size of payload. By default max size is 256Kb
pub fn limit(&mut self, limit: usize) -> &mut Self {
self.limit = limit;
self
}
/// Set custom error handler
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
where
F: Fn(JsonPayloadError, &HttpRequest<S>) -> Error + 'static,
{
self.ehandler = Rc::new(f);
self
}
/// Set predicate for allowed content types
pub fn content_type<F>(&mut self, predicate: F) -> &mut Self
where
F: Fn(mime::Mime) -> bool + 'static,
{
self.content_type = Some(Box::new(predicate));
self
}
}
impl<S> Default for JsonConfig<S> {
fn default() -> Self {
JsonConfig {
limit: 262_144,
ehandler: Rc::new(|e, _| e.into()),
content_type: None,
}
}
}
/// Request payload json parser that resolves to a deserialized `T` value.
///
/// Returns error:
///
/// * content type is not `application/json`
/// (unless specified in [`JsonConfig`](struct.JsonConfig.html))
/// * content length is greater than 256k
///
/// # Server example
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse};
/// use futures::future::Future;
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
/// println!("==== BODY ==== {:?}", val);
/// Ok(HttpResponse::Ok().into())
/// }).responder()
/// }
/// # fn main() {}
/// ```
pub struct JsonBody<T: HttpMessage, U: DeserializeOwned> {
limit: usize,
length: Option<usize>,
stream: Option<T::Stream>,
err: Option<JsonPayloadError>,
fut: Option<Box<Future<Item = U, Error = JsonPayloadError>>>,
}
impl<T: HttpMessage, U: DeserializeOwned> JsonBody<T, U> {
/// Create `JsonBody` for request.
pub fn new<S>(req: &T, cfg: Option<&JsonConfig<S>>) -> Self {
// check content-type
let json = if let Ok(Some(mime)) = req.mime_type() {
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) ||
cfg.map_or(false, |cfg| {
cfg.content_type.as_ref().map_or(false, |predicate| predicate(mime))
})
} else {
false
};
if !json {
return JsonBody {
limit: 262_144,
length: None,
stream: None,
fut: None,
err: Some(JsonPayloadError::ContentType),
};
}
let mut len = None;
if let Some(l) = req.headers().get(CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() {
len = Some(l)
}
}
}
JsonBody {
limit: 262_144,
length: len,
stream: Some(req.payload()),
fut: None,
err: None,
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
impl<T: HttpMessage + 'static, U: DeserializeOwned + 'static> Future for JsonBody<T, U> {
type Item = U;
type Error = JsonPayloadError;
fn poll(&mut self) -> Poll<U, JsonPayloadError> {
if let Some(ref mut fut) = self.fut {
return fut.poll();
}
if let Some(err) = self.err.take() {
return Err(err);
}
let limit = self.limit;
if let Some(len) = self.length.take() {
if len > limit {
return Err(JsonPayloadError::Overflow);
}
}
let fut = self
.stream
.take()
.expect("JsonBody could not be used second time")
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(JsonPayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
}).and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
self.fut = Some(Box::new(fut));
self.poll()
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
use futures::Async;
use http::header;
use handler::Handler;
use test::TestRequest;
use with::With;
impl PartialEq for JsonPayloadError {
fn eq(&self, other: &JsonPayloadError) -> bool {
match *self {
JsonPayloadError::Overflow => match *other {
JsonPayloadError::Overflow => true,
_ => false,
},
JsonPayloadError::ContentType => match *other {
JsonPayloadError::ContentType => true,
_ => false,
},
_ => false,
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct MyObject {
name: String,
}
#[test]
fn test_json() {
let json = Json(MyObject {
name: "test".to_owned(),
});
let resp = json.respond_to(&TestRequest::default().finish()).unwrap();
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"application/json"
);
}
#[test]
fn test_json_body() {
let req = TestRequest::default().finish();
let mut json = req.json::<MyObject>();
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
let req = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/text"),
).finish();
let mut json = req.json::<MyObject>();
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
let req = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000"),
).finish();
let mut json = req.json::<MyObject>().limit(100);
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow);
let req = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
let mut json = req.json::<MyObject>();
assert_eq!(
json.poll().ok().unwrap(),
Async::Ready(MyObject {
name: "test".to_owned()
})
);
}
#[test]
fn test_with_json() {
let mut cfg = JsonConfig::default();
cfg.limit(4096);
let handler = With::new(|data: Json<MyObject>| data, cfg);
let req = TestRequest::default().finish();
assert!(handler.handle(&req).as_err().is_some());
let req = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
assert!(handler.handle(&req).as_err().is_none())
}
#[test]
fn test_with_json_and_bad_content_type() {
let mut cfg = JsonConfig::default();
cfg.limit(4096);
let handler = With::new(|data: Json<MyObject>| data, cfg);
let req = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
assert!(handler.handle(&req).as_err().is_some())
}
#[test]
fn test_with_json_and_good_custom_content_type() {
let mut cfg = JsonConfig::default();
cfg.limit(4096);
cfg.content_type(|mime: mime::Mime| {
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
});
let handler = With::new(|data: Json<MyObject>| data, cfg);
let req = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
assert!(handler.handle(&req).as_err().is_none())
}
#[test]
fn test_with_json_and_bad_custom_content_type() {
let mut cfg = JsonConfig::default();
cfg.limit(4096);
cfg.content_type(|mime: mime::Mime| {
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
});
let handler = With::new(|data: Json<MyObject>| data, cfg);
let req = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/html"),
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
assert!(handler.handle(&req).as_err().is_some())
}
}

View File

@ -1,244 +1,44 @@
//! Actix web is a small, pragmatic, and extremely fast web framework
//! for Rust.
//!
//! ```rust
//! use actix_web::{server, App, Path, Responder};
//! # use std::thread;
//!
//! fn index(info: Path<(String, u32)>) -> impl Responder {
//! format!("Hello {}! id:{}", info.0, info.1)
//! }
//!
//! fn main() {
//! # thread::spawn(|| {
//! server::new(|| {
//! App::new().resource("/{name}/{id}/index.html", |r| r.with(index))
//! }).bind("127.0.0.1:8080")
//! .unwrap()
//! .run();
//! # });
//! }
//! ```
//!
//! ## Documentation & community resources
//!
//! Besides the API documentation (which you are currently looking
//! at!), several other resources are available:
//!
//! * [User Guide](https://actix.rs/docs/)
//! * [Chat on gitter](https://gitter.im/actix/actix)
//! * [GitHub repository](https://github.com/actix/actix-web)
//! * [Cargo package](https://crates.io/crates/actix-web)
//!
//! To get started navigating the API documentation you may want to
//! consider looking at the following pages:
//!
//! * [App](struct.App.html): This struct represents an actix-web
//! application and is used to configure routes and other common
//! settings.
//!
//! * [HttpServer](server/struct.HttpServer.html): This struct
//! represents an HTTP server instance and is used to instantiate and
//! configure servers.
//!
//! * [HttpRequest](struct.HttpRequest.html) and
//! [HttpResponse](struct.HttpResponse.html): These structs
//! represent HTTP requests and responses and expose various methods
//! for inspecting, creating and otherwise utilizing them.
//!
//! ## Features
//!
//! * Supported *HTTP/1.x* and *HTTP/2.0* protocols
//! * Streaming and pipelining
//! * Keep-alive and slow requests handling
//! * `WebSockets` server/client
//! * Transparent content compression/decompression (br, gzip, deflate)
//! * Configurable request routing
//! * Graceful server shutdown
//! * Multipart streams
//! * SSL support with OpenSSL or `native-tls`
//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`)
//! * Built on top of [Actix actor framework](https://github.com/actix/actix)
//! * Supported Rust version: 1.26 or later
//!
//! ## Package feature
//!
//! * `tls` - enables ssl support via `native-tls` crate
//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2`
//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2`
//! * `uds` - enables support for making client requests via Unix Domain Sockets.
//! Unix only. Not necessary for *serving* requests.
//! * `session` - enables session support, includes `ring` crate as
//! dependency
//! * `brotli` - enables `brotli` compression support, requires `c`
//! compiler
//! * `flate2-c` - enables `gzip`, `deflate` compression support, requires
//! `c` compiler
//! * `flate2-rust` - experimental rust based implementation for
//! `gzip`, `deflate` compression.
//!
#![cfg_attr(actix_nightly, feature(
specialization, // for impl ErrorResponse for std::error::Error
extern_prelude,
tool_lints,
))]
#![warn(missing_docs)]
#![allow(clippy::type_complexity, clippy::new_without_default)]
#[macro_use]
extern crate log;
extern crate base64;
extern crate byteorder;
extern crate bytes;
extern crate regex;
extern crate sha1;
extern crate time;
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate futures;
extern crate cookie;
extern crate futures_cpupool;
extern crate http as modhttp;
extern crate httparse;
extern crate language_tags;
extern crate lazycell;
extern crate mime;
extern crate mime_guess;
extern crate mio;
extern crate net2;
extern crate parking_lot;
extern crate rand;
extern crate slab;
extern crate tokio;
extern crate tokio_current_thread;
extern crate tokio_io;
extern crate tokio_reactor;
extern crate tokio_tcp;
extern crate tokio_timer;
#[cfg(all(unix, feature = "uds"))]
extern crate tokio_uds;
extern crate url;
#[macro_use]
extern crate serde;
#[cfg(feature = "brotli")]
extern crate brotli2;
extern crate encoding;
#[cfg(feature = "flate2")]
extern crate flate2;
extern crate h2 as http2;
extern crate num_cpus;
extern crate serde_urlencoded;
#[macro_use]
extern crate percent_encoding;
extern crate serde_json;
extern crate smallvec;
extern crate v_htmlescape;
extern crate actix_net;
#[macro_use]
extern crate actix as actix_inner;
#[cfg(test)]
#[macro_use]
extern crate serde_derive;
#[cfg(feature = "tls")]
extern crate native_tls;
#[cfg(feature = "tls")]
extern crate tokio_tls;
#[cfg(feature = "openssl")]
extern crate openssl;
#[cfg(feature = "openssl")]
extern crate tokio_openssl;
#[cfg(feature = "rust-tls")]
extern crate rustls;
#[cfg(feature = "rust-tls")]
extern crate tokio_rustls;
#[cfg(feature = "rust-tls")]
extern crate webpki;
#[cfg(feature = "rust-tls")]
extern crate webpki_roots;
mod application;
mod body;
mod context;
mod de;
mod extensions;
mod extractor;
mod handler;
mod header;
mod helpers;
mod httpcodes;
mod httpmessage;
mod httprequest;
mod httpresponse;
mod info;
mod json;
mod param;
mod payload;
mod pipeline;
mod resource;
mod route;
mod router;
mod scope;
mod uri;
mod with;
pub mod client;
mod app;
mod app_service;
mod config;
mod data;
pub mod error;
pub mod fs;
mod extract;
pub mod guard;
mod handler;
mod info;
pub mod middleware;
pub mod multipart;
pub mod pred;
pub mod server;
mod request;
mod resource;
mod responder;
mod rmap;
mod route;
mod scope;
mod server;
mod service;
pub mod test;
pub mod ws;
pub use application::App;
pub use body::{Binary, Body};
pub use context::HttpContext;
pub use error::{Error, ResponseError, Result};
pub use extensions::Extensions;
pub use extractor::{Form, Path, Query};
pub use handler::{
AsyncResponder, Either, FromRequest, FutureResponse, Responder, State,
};
pub use httpmessage::HttpMessage;
pub use httprequest::HttpRequest;
pub use httpresponse::HttpResponse;
pub use json::Json;
pub use scope::Scope;
pub use server::Request;
mod types;
pub mod actix {
//! Re-exports [actix's](https://docs.rs/actix/) prelude
pub use super::actix_inner::actors::resolver;
pub use super::actix_inner::actors::signal;
pub use super::actix_inner::fut;
pub use super::actix_inner::msgs;
pub use super::actix_inner::prelude::*;
pub use super::actix_inner::{run, spawn};
}
#[allow(unused_imports)]
#[macro_use]
extern crate actix_web_codegen;
#[cfg(feature = "openssl")]
pub(crate) const HAS_OPENSSL: bool = true;
#[cfg(not(feature = "openssl"))]
pub(crate) const HAS_OPENSSL: bool = false;
#[doc(hidden)]
pub use actix_web_codegen::*;
#[cfg(feature = "tls")]
pub(crate) const HAS_TLS: bool = true;
#[cfg(not(feature = "tls"))]
pub(crate) const HAS_TLS: bool = false;
// re-export for convenience
pub use actix_http::Response as HttpResponse;
pub use actix_http::{http, Error, HttpMessage, ResponseError, Result};
#[cfg(feature = "rust-tls")]
pub(crate) const HAS_RUSTLS: bool = true;
#[cfg(not(feature = "rust-tls"))]
pub(crate) const HAS_RUSTLS: bool = false;
pub use crate::app::App;
pub use crate::extract::FromRequest;
pub use crate::request::HttpRequest;
pub use crate::resource::Resource;
pub use crate::responder::{Either, Responder};
pub use crate::route::Route;
pub use crate::server::HttpServer;
pub mod dev {
//! The `actix-web` prelude for library developers
@ -251,42 +51,215 @@ pub mod dev {
//! use actix_web::dev::*;
//! ```
pub use body::BodyStream;
pub use context::Drain;
pub use extractor::{FormConfig, PayloadConfig, QueryConfig, PathConfig, EitherConfig, EitherCollisionStrategy};
pub use handler::{AsyncResult, Handler};
pub use httpmessage::{MessageBody, Readlines, UrlEncoded};
pub use httpresponse::HttpResponseBuilder;
pub use info::ConnectionInfo;
pub use json::{JsonBody, JsonConfig};
pub use param::{FromParam, Params};
pub use payload::{Payload, PayloadBuffer};
pub use pipeline::Pipeline;
pub use resource::Resource;
pub use route::Route;
pub use router::{ResourceDef, ResourceInfo, ResourceType, Router};
}
pub use crate::app::AppRouter;
pub use crate::config::{AppConfig, ServiceConfig};
pub use crate::info::ConnectionInfo;
pub use crate::rmap::ResourceMap;
pub use crate::service::{
HttpServiceFactory, ServiceFromRequest, ServiceRequest, ServiceResponse,
};
pub use crate::types::form::UrlEncoded;
pub use crate::types::json::JsonBody;
pub use crate::types::payload::HttpMessageBody;
pub use crate::types::readlines::Readlines;
pub mod http {
//! Various HTTP related types
pub use actix_http::body::{Body, BodyLength, MessageBody, ResponseBody};
pub use actix_http::ResponseBuilder as HttpResponseBuilder;
pub use actix_http::{
Extensions, Head, Payload, PayloadStream, RequestHead, ResponseHead,
};
pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
pub use actix_server::Server;
// re-exports
pub use modhttp::{Method, StatusCode, Version};
#[doc(hidden)]
pub use modhttp::{uri, Error, Extensions, HeaderMap, HttpTryFrom, Uri};
pub use cookie::{Cookie, CookieBuilder};
pub use helpers::NormalizePath;
/// Various http headers
pub mod header {
pub use header::*;
pub use header::{
Charset, ContentDisposition, DispositionParam, DispositionType, LanguageTag,
pub(crate) fn insert_slash(path: &str) -> String {
let mut path = path.to_owned();
if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/');
};
path
}
}
pub mod web {
//! Various types
use actix_http::{http::Method, Response};
use actix_rt::blocking;
use futures::{Future, IntoFuture};
pub use actix_http::Response as HttpResponse;
pub use bytes::{Bytes, BytesMut};
use crate::error::{BlockingError, Error};
use crate::extract::FromRequest;
use crate::handler::{AsyncFactory, Factory};
use crate::resource::Resource;
use crate::responder::Responder;
use crate::route::Route;
use crate::scope::Scope;
pub use crate::data::{Data, RouteData};
pub use crate::request::HttpRequest;
pub use crate::types::*;
/// Create 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::{web, http, App, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/users/{userid}/{friend}")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
pub fn resource<P: 'static>(path: &str) -> Resource<P> {
Resource::new(path)
}
/// 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::{web, App, HttpRequest, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::scope("/{project_id}")
/// .service(web::resource("/path1").to(|| HttpResponse::Ok()))
/// .service(web::resource("/path2").to(|| HttpResponse::Ok()))
/// .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
///
/// In the above example, three routes get added:
/// * /{project_id}/path1
/// * /{project_id}/path2
/// * /{project_id}/path3
///
pub fn scope<P: 'static>(path: &str) -> Scope<P> {
Scope::new(path)
}
/// Create *route* without configuration.
pub fn route<P: 'static>() -> Route<P> {
Route::new()
}
/// Create *route* with `GET` method guard.
pub fn get<P: 'static>() -> Route<P> {
Route::new().method(Method::GET)
}
/// Create *route* with `POST` method guard.
pub fn post<P: 'static>() -> Route<P> {
Route::new().method(Method::POST)
}
/// Create *route* with `PUT` method guard.
pub fn put<P: 'static>() -> Route<P> {
Route::new().method(Method::PUT)
}
/// Create *route* with `PATCH` method guard.
pub fn patch<P: 'static>() -> Route<P> {
Route::new().method(Method::PATCH)
}
/// Create *route* with `DELETE` method guard.
pub fn delete<P: 'static>() -> Route<P> {
Route::new().method(Method::DELETE)
}
/// Create *route* with `HEAD` method guard.
pub fn head<P: 'static>() -> Route<P> {
Route::new().method(Method::HEAD)
}
/// Create *route* and add method guard.
pub fn method<P: 'static>(method: Method) -> Route<P> {
Route::new().method(method)
}
/// Create a new route and add handler.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn index() -> HttpResponse {
/// unimplemented!()
/// }
///
/// App::new().service(
/// web::resource("/").route(
/// web::to(index))
/// );
/// ```
pub fn to<F, I, R, P: 'static>(handler: F) -> Route<P>
where
F: Factory<I, R> + 'static,
I: FromRequest<P> + 'static,
R: Responder + 'static,
{
Route::new().to(handler)
}
/// Create a new route and add async handler.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse, Error};
///
/// fn index() -> impl futures::Future<Item=HttpResponse, Error=Error> {
/// futures::future::ok(HttpResponse::Ok().finish())
/// }
///
/// App::new().service(web::resource("/").route(
/// web::to_async(index))
/// );
/// ```
pub fn to_async<F, I, R, P: 'static>(handler: F) -> Route<P>
where
F: AsyncFactory<I, R>,
I: FromRequest<P> + 'static,
R: IntoFuture + 'static,
R::Item: Into<Response>,
R::Error: Into<Error>,
{
Route::new().to_async(handler)
}
/// Execute blocking function on a thread pool, returns future that resolves
/// to result of the function execution.
pub fn block<F, I, E>(f: F) -> impl Future<Item = I, Error = BlockingError<E>>
where
F: FnOnce() -> Result<I, E> + Send + 'static,
I: Send + 'static,
E: Send + std::fmt::Debug + 'static,
{
blocking::run(f).from_err()
}
pub use header::ContentEncoding;
pub use httpresponse::ConnectionType;
}

458
src/middleware/compress.rs Normal file
View File

@ -0,0 +1,458 @@
use std::io::Write;
use std::marker::PhantomData;
use std::str::FromStr;
use std::{cmp, fmt, io};
use actix_http::body::{Body, BodyLength, MessageBody, ResponseBody};
use actix_http::http::header::{
ContentEncoding, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING,
};
use actix_http::http::{HttpTryFrom, StatusCode};
use actix_http::{Error, Head, ResponseHead};
use actix_service::{Service, Transform};
use bytes::{Bytes, BytesMut};
use futures::future::{ok, FutureResult};
use futures::{Async, Future, Poll};
use log::trace;
#[cfg(feature = "brotli")]
use brotli2::write::BrotliEncoder;
#[cfg(feature = "flate2")]
use flate2::write::{GzEncoder, ZlibEncoder};
use crate::service::{ServiceRequest, ServiceResponse};
#[derive(Debug, Clone)]
pub struct Compress(ContentEncoding);
impl Compress {
pub fn new(encoding: ContentEncoding) -> Self {
Compress(encoding)
}
}
impl Default for Compress {
fn default() -> Self {
Compress::new(ContentEncoding::Auto)
}
}
impl<S, P, B> Transform<S> for Compress
where
P: 'static,
B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<Encoder<B>>;
type Error = S::Error;
type InitError = ();
type Transform = CompressMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
ok(CompressMiddleware {
service,
encoding: self.0,
})
}
}
pub struct CompressMiddleware<S> {
service: S,
encoding: ContentEncoding,
}
impl<S, P, B> Service for CompressMiddleware<S>
where
P: 'static,
B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<Encoder<B>>;
type Error = S::Error;
type Future = CompressResponse<S, P, B>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
// negotiate content-encoding
let encoding = if let Some(val) = req.headers.get(ACCEPT_ENCODING) {
if let Ok(enc) = val.to_str() {
AcceptEncoding::parse(enc, self.encoding)
} else {
ContentEncoding::Identity
}
} else {
ContentEncoding::Identity
};
CompressResponse {
encoding,
fut: self.service.call(req),
_t: PhantomData,
}
}
}
#[doc(hidden)]
pub struct CompressResponse<S, P, B>
where
P: 'static,
B: MessageBody,
S: Service,
S::Future: 'static,
{
fut: S::Future,
encoding: ContentEncoding,
_t: PhantomData<(P, B)>,
}
impl<S, P, B> Future for CompressResponse<S, P, B>
where
P: 'static,
B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
{
type Item = ServiceResponse<Encoder<B>>;
type Error = S::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let resp = futures::try_ready!(self.fut.poll());
Ok(Async::Ready(resp.map_body(move |head, body| {
Encoder::body(self.encoding, head, body)
})))
}
}
enum EncoderBody<B> {
Body(B),
Other(Box<dyn MessageBody>),
}
pub struct Encoder<B> {
body: EncoderBody<B>,
encoder: Option<ContentEncoder>,
}
impl<B: MessageBody> MessageBody for Encoder<B> {
fn length(&self) -> BodyLength {
if self.encoder.is_none() {
match self.body {
EncoderBody::Body(ref b) => b.length(),
EncoderBody::Other(ref b) => b.length(),
}
} else {
BodyLength::Stream
}
}
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
loop {
let result = match self.body {
EncoderBody::Body(ref mut b) => b.poll_next()?,
EncoderBody::Other(ref mut b) => b.poll_next()?,
};
match result {
Async::NotReady => return Ok(Async::NotReady),
Async::Ready(Some(chunk)) => {
if let Some(ref mut encoder) = self.encoder {
if encoder.write(&chunk)? {
return Ok(Async::Ready(Some(encoder.take())));
}
} else {
return Ok(Async::Ready(Some(chunk)));
}
}
Async::Ready(None) => {
if let Some(encoder) = self.encoder.take() {
let chunk = encoder.finish()?;
if chunk.is_empty() {
return Ok(Async::Ready(None));
} else {
return Ok(Async::Ready(Some(chunk)));
}
} else {
return Ok(Async::Ready(None));
}
}
}
}
}
}
fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
head.headers_mut().insert(
CONTENT_ENCODING,
HeaderValue::try_from(Bytes::from_static(encoding.as_str().as_bytes())).unwrap(),
);
}
impl<B: MessageBody> Encoder<B> {
fn body(
encoding: ContentEncoding,
head: &mut ResponseHead,
body: ResponseBody<B>,
) -> ResponseBody<Encoder<B>> {
let has_ce = head.headers().contains_key(CONTENT_ENCODING);
match body {
ResponseBody::Other(b) => match b {
Body::None => ResponseBody::Other(Body::None),
Body::Empty => ResponseBody::Other(Body::Empty),
Body::Bytes(buf) => {
if !(has_ce
|| encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto)
{
let mut enc = ContentEncoder::encoder(encoding).unwrap();
// TODO return error!
let _ = enc.write(buf.as_ref());
let body = enc.finish().unwrap();
update_head(encoding, head);
ResponseBody::Other(Body::Bytes(body))
} else {
ResponseBody::Other(Body::Bytes(buf))
}
}
Body::Message(stream) => {
if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS {
ResponseBody::Body(Encoder {
body: EncoderBody::Other(stream),
encoder: None,
})
} else {
update_head(encoding, head);
head.no_chunking = false;
ResponseBody::Body(Encoder {
body: EncoderBody::Other(stream),
encoder: ContentEncoder::encoder(encoding),
})
}
}
},
ResponseBody::Body(stream) => {
if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS {
ResponseBody::Body(Encoder {
body: EncoderBody::Body(stream),
encoder: None,
})
} else {
update_head(encoding, head);
head.no_chunking = false;
ResponseBody::Body(Encoder {
body: EncoderBody::Body(stream),
encoder: ContentEncoder::encoder(encoding),
})
}
}
}
}
}
pub(crate) struct Writer {
buf: BytesMut,
}
impl Writer {
fn new() -> Writer {
Writer {
buf: BytesMut::with_capacity(8192),
}
}
fn take(&mut self) -> Bytes {
self.buf.take().freeze()
}
}
impl io::Write for Writer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buf.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
pub(crate) enum ContentEncoder {
#[cfg(feature = "flate2")]
Deflate(ZlibEncoder<Writer>),
#[cfg(feature = "flate2")]
Gzip(GzEncoder<Writer>),
#[cfg(feature = "brotli")]
Br(BrotliEncoder<Writer>),
}
impl fmt::Debug for ContentEncoder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"),
}
}
}
impl ContentEncoder {
fn encoder(encoding: ContentEncoding) -> Option<Self> {
match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new(
Writer::new(),
flate2::Compression::fast(),
))),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new(
Writer::new(),
flate2::Compression::fast(),
))),
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3)))
}
_ => None,
}
}
#[inline]
pub(crate) fn take(&mut self) -> Bytes {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
}
}
fn finish(self) -> Result<Bytes, io::Error> {
match self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err),
},
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err),
},
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err),
},
}
}
fn write(&mut self, data: &[u8]) -> Result<bool, io::Error> {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(!encoder.get_ref().buf.is_empty()),
Err(err) => {
trace!("Error decoding br encoding: {}", err);
Err(err)
}
},
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(!encoder.get_ref().buf.is_empty()),
Err(err) => {
trace!("Error decoding gzip encoding: {}", err);
Err(err)
}
},
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(!encoder.get_ref().buf.is_empty()),
Err(err) => {
trace!("Error decoding deflate encoding: {}", err);
Err(err)
}
},
}
}
}
struct AcceptEncoding {
encoding: ContentEncoding,
quality: f64,
}
impl Eq for AcceptEncoding {}
impl Ord for AcceptEncoding {
fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering {
if self.quality > other.quality {
cmp::Ordering::Less
} else if self.quality < other.quality {
cmp::Ordering::Greater
} else {
cmp::Ordering::Equal
}
}
}
impl PartialOrd for AcceptEncoding {
fn partial_cmp(&self, other: &AcceptEncoding) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for AcceptEncoding {
fn eq(&self, other: &AcceptEncoding) -> bool {
self.quality == other.quality
}
}
impl AcceptEncoding {
fn new(tag: &str) -> Option<AcceptEncoding> {
let parts: Vec<&str> = tag.split(';').collect();
let encoding = match parts.len() {
0 => return None,
_ => ContentEncoding::from(parts[0]),
};
let quality = match parts.len() {
1 => encoding.quality(),
_ => match f64::from_str(parts[1]) {
Ok(q) => q,
Err(_) => 0.0,
},
};
Some(AcceptEncoding { encoding, quality })
}
/// Parse a raw Accept-Encoding header value into an ordered list.
pub fn parse(raw: &str, encoding: ContentEncoding) -> ContentEncoding {
let mut encodings: Vec<_> = raw
.replace(' ', "")
.split(',')
.map(|l| AcceptEncoding::new(l))
.collect();
encodings.sort();
for enc in encodings {
if let Some(enc) = enc {
if encoding == ContentEncoding::Auto {
return enc.encoding;
} else if encoding == enc.encoding {
return encoding;
}
}
}
ContentEncoding::Identity
}
}

View File

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

View File

@ -1,32 +1,37 @@
//! Default response headers
use http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
use http::{HeaderMap, HttpTryFrom};
//! Middleware for setting default response headers
use std::rc::Rc;
use error::Result;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response};
use actix_service::{Service, Transform};
use futures::future::{ok, FutureResult};
use futures::{Future, Poll};
use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
use crate::http::{HeaderMap, HttpTryFrom};
use crate::service::{ServiceRequest, ServiceResponse};
/// `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};
/// use actix_web::{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();
/// .service(
/// web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
#[derive(Clone)]
pub struct DefaultHeaders {
inner: Rc<Inner>,
}
struct Inner {
ct: bool,
headers: HeaderMap,
}
@ -34,8 +39,10 @@ pub struct DefaultHeaders {
impl Default for DefaultHeaders {
fn default() -> Self {
DefaultHeaders {
ct: false,
headers: HeaderMap::new(),
inner: Rc::new(Inner {
ct: false,
headers: HeaderMap::new(),
}),
}
}
}
@ -48,16 +55,19 @@ impl DefaultHeaders {
/// Set a header.
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))]
pub fn header<K, V>(mut self, key: K, value: V) -> Self
where
HeaderName: HttpTryFrom<K>,
HeaderValue: HttpTryFrom<V>,
{
#[allow(clippy::match_wild_err_arm)]
match HeaderName::try_from(key) {
Ok(key) => match HeaderValue::try_from(value) {
Ok(value) => {
self.headers.append(key, value);
Rc::get_mut(&mut self.inner)
.expect("Multiple copies exist")
.headers
.append(key, value);
}
Err(_) => panic!("Can not create header value"),
},
@ -68,53 +78,128 @@ impl DefaultHeaders {
/// Set *CONTENT-TYPE* header if response does not contain this header.
pub fn content_type(mut self) -> Self {
self.ct = true;
Rc::get_mut(&mut self.inner)
.expect("Multiple copies exist")
.ct = true;
self
}
}
impl<S> Middleware<S> for DefaultHeaders {
fn response(&self, _: &HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> {
for (key, value) in self.headers.iter() {
if !resp.headers().contains_key(key) {
resp.headers_mut().insert(key, value.clone());
impl<S, P, B> Transform<S> for DefaultHeaders
where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = S::Error;
type InitError = ();
type Transform = DefaultHeadersMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
ok(DefaultHeadersMiddleware {
service,
inner: self.inner.clone(),
})
}
}
pub struct DefaultHeadersMiddleware<S> {
service: S,
inner: Rc<Inner>,
}
impl<S, P, B> Service for DefaultHeadersMiddleware<S>
where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = S::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
let inner = self.inner.clone();
Box::new(self.service.call(req).map(move |mut res| {
// set response headers
for (key, value) in inner.headers.iter() {
if !res.headers().contains_key(key) {
res.headers_mut().insert(key, value.clone());
}
}
}
// default content-type
if self.ct && !resp.headers().contains_key(CONTENT_TYPE) {
resp.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/octet-stream"),
);
}
Ok(Response::Done(resp))
// default content-type
if inner.ct && !res.headers().contains_key(CONTENT_TYPE) {
res.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/octet-stream"),
);
}
res
}))
}
}
#[cfg(test)]
mod tests {
use actix_service::FnService;
use super::*;
use http::header::CONTENT_TYPE;
use test::TestRequest;
use crate::dev::ServiceRequest;
use crate::http::header::CONTENT_TYPE;
use crate::test::{block_on, TestRequest};
use crate::HttpResponse;
#[test]
fn test_default_headers() {
let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001");
let srv = FnService::new(|req: ServiceRequest<_>| {
req.into_response(HttpResponse::Ok().finish())
});
let mut mw = block_on(
DefaultHeaders::new()
.header(CONTENT_TYPE, "0001")
.new_transform(srv),
)
.unwrap();
let req = TestRequest::default().finish();
let resp = HttpResponse::Ok().finish();
let resp = match mw.response(&req, resp) {
Ok(Response::Done(resp)) => resp,
_ => panic!(),
};
let req = TestRequest::default().to_service();
let resp = block_on(mw.call(req)).unwrap();
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!(),
};
let req = TestRequest::default().to_service();
let srv = FnService::new(|req: ServiceRequest<_>| {
req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish())
});
let mut mw = block_on(
DefaultHeaders::new()
.header(CONTENT_TYPE, "0001")
.new_transform(srv),
)
.unwrap();
let resp = block_on(mw.call(req)).unwrap();
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002");
}
#[test]
fn test_content_type() {
let srv = FnService::new(|req: ServiceRequest<_>| {
req.into_response(HttpResponse::Ok().finish())
});
let mut mw =
block_on(DefaultHeaders::new().content_type().new_transform(srv)).unwrap();
let req = TestRequest::default().to_service();
let resp = block_on(mw.call(req)).unwrap();
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
"application/octet-stream"
);
}
}

View File

@ -1,12 +1,23 @@
use std::collections::HashMap;
use std::rc::Rc;
use error::Result;
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response};
use actix_service::{Service, Transform};
use futures::future::{err, ok, Either, Future, FutureResult};
use futures::Poll;
use hashbrown::HashMap;
type ErrorHandler<S> = Fn(&HttpRequest<S>, HttpResponse) -> Result<Response>;
use crate::dev::{ServiceRequest, ServiceResponse};
use crate::error::{Error, Result};
use crate::http::StatusCode;
/// Error handler response
pub enum ErrorHandlerResponse<B> {
/// New http response got generated
Response(ServiceResponse<B>),
/// Result is a future that resolves to a new http response
Future(Box<Future<Item = ServiceResponse<B>, Error = Error>>),
}
type ErrorHandler<B> = Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>>;
/// `Middleware` for allowing custom handlers for responses.
///
@ -17,14 +28,14 @@ type ErrorHandler<S> = Fn(&HttpRequest<S>, HttpResponse) -> Result<Response>;
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::{ErrorHandlers, Response};
/// use actix_web::{http, App, HttpRequest, HttpResponse, Result};
/// use actix_web::middleware::{ErrorHandlers, ErrorHandlerResponse};
/// use actix_web::{web, http, dev, App, HttpRequest, HttpResponse, Result};
///
/// fn render_500<S>(_: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
/// let mut builder = resp.into_builder();
/// builder.header(http::header::CONTENT_TYPE, "application/json");
/// Ok(Response::Done(builder.into()))
/// fn render_500<B>(mut res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
/// res.response_mut()
/// .headers_mut()
/// .insert(http::header::CONTENT_TYPE, http::HeaderValue::from_static("Error"));
/// Ok(ErrorHandlerResponse::Response(res))
/// }
///
/// fn main() {
@ -33,27 +44,25 @@ type ErrorHandler<S> = Fn(&HttpRequest<S>, HttpResponse) -> Result<Response>;
/// 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();
/// .service(web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())
/// ));
/// }
/// ```
pub struct ErrorHandlers<S> {
handlers: HashMap<StatusCode, Box<ErrorHandler<S>>>,
pub struct ErrorHandlers<B> {
handlers: Rc<HashMap<StatusCode, Box<ErrorHandler<B>>>>,
}
impl<S> Default for ErrorHandlers<S> {
impl<B> Default for ErrorHandlers<B> {
fn default() -> Self {
ErrorHandlers {
handlers: HashMap::new(),
handlers: Rc::new(HashMap::new()),
}
}
}
impl<S> ErrorHandlers<S> {
impl<B> ErrorHandlers<B> {
/// Construct new `ErrorHandlers` instance
pub fn new() -> Self {
ErrorHandlers::default()
@ -62,80 +71,140 @@ impl<S> ErrorHandlers<S> {
/// Register error handler for specified status code
pub fn handler<F>(mut self, status: StatusCode, handler: F) -> Self
where
F: Fn(&HttpRequest<S>, HttpResponse) -> Result<Response> + 'static,
F: Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> + 'static,
{
self.handlers.insert(status, Box::new(handler));
Rc::get_mut(&mut self.handlers)
.unwrap()
.insert(status, Box::new(handler));
self
}
}
impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
if let Some(handler) = self.handlers.get(&resp.status()) {
handler(req, resp)
} else {
Ok(Response::Done(resp))
}
impl<S, P, B> Transform<S> for ErrorHandlers<B>
where
S: Service<
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
>,
S::Future: 'static,
S::Error: 'static,
B: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = ErrorHandlersMiddleware<S, B>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
ok(ErrorHandlersMiddleware {
service,
handlers: self.handlers.clone(),
})
}
}
pub struct ErrorHandlersMiddleware<S, B> {
service: S,
handlers: Rc<HashMap<StatusCode, Box<ErrorHandler<B>>>>,
}
impl<S, P, B> Service for ErrorHandlersMiddleware<S, B>
where
S: Service<
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
>,
S::Future: 'static,
S::Error: 'static,
B: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
let handlers = self.handlers.clone();
Box::new(self.service.call(req).and_then(move |res| {
if let Some(handler) = handlers.get(&res.status()) {
match handler(res) {
Ok(ErrorHandlerResponse::Response(res)) => Either::A(ok(res)),
Ok(ErrorHandlerResponse::Future(fut)) => Either::B(fut),
Err(e) => Either::A(err(e)),
}
} else {
Either::A(ok(res))
}
}))
}
}
#[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};
use actix_service::FnService;
use futures::future::ok;
fn render_500<S>(_: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
let mut builder = resp.into_builder();
builder.header(CONTENT_TYPE, "0001");
Ok(Response::Done(builder.into()))
use super::*;
use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
use crate::test::{self, TestRequest};
use crate::HttpResponse;
fn render_500<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
res.response_mut()
.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("0001"));
Ok(ErrorHandlerResponse::Response(res))
}
#[test]
fn test_handler() {
let mw =
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
let srv = FnService::new(|req: ServiceRequest<_>| {
req.into_response(HttpResponse::InternalServerError().finish())
});
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!(),
};
let mut mw = test::block_on(
ErrorHandlers::new()
.handler(StatusCode::INTERNAL_SERVER_ERROR, render_500)
.new_transform(srv),
)
.unwrap();
let resp = test::call_success(&mut mw, TestRequest::default().to_service());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let resp = HttpResponse::Ok().finish();
let resp = match mw.response(&mut req, resp) {
Ok(Response::Done(resp)) => resp,
_ => panic!(),
};
assert!(!resp.headers().contains_key(CONTENT_TYPE));
}
struct MiddlewareOne;
impl<S> Middleware<S> for MiddlewareOne {
fn start(&self, _: &HttpRequest<S>) -> Result<Started, Error> {
Err(ErrorInternalServerError("middleware error"))
}
fn render_500_async<B: 'static>(
mut res: ServiceResponse<B>,
) -> Result<ErrorHandlerResponse<B>> {
res.response_mut()
.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("0001"));
Ok(ErrorHandlerResponse::Future(Box::new(ok(res))))
}
#[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())
fn test_handler_async() {
let srv = FnService::new(|req: ServiceRequest<_>| {
req.into_response(HttpResponse::InternalServerError().finish())
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.headers().get(CONTENT_TYPE).unwrap(), "0001");
let mut mw = test::block_on(
ErrorHandlers::new()
.handler(StatusCode::INTERNAL_SERVER_ERROR, render_500_async)
.new_transform(srv),
)
.unwrap();
let resp = test::call_success(&mut mw, TestRequest::default().to_service());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
}
}

View File

@ -10,30 +10,29 @@
//! uses cookies as identity storage.
//!
//! To access current request identity
//! [**RequestIdentity**](trait.RequestIdentity.html) should be used.
//! *HttpRequest* implements *RequestIdentity* trait.
//! [**Identity**](trait.Identity.html) extractor should be used.
//!
//! ```rust
//! use actix_web::middleware::identity::RequestIdentity;
//! use actix_web::middleware::identity::Identity;
//! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
//! use actix_web::*;
//!
//! fn index(req: HttpRequest) -> Result<String> {
//! fn index(id: Identity) -> String {
//! // access request identity
//! if let Some(id) = req.identity() {
//! Ok(format!("Welcome! {}", id))
//! if let Some(id) = id.identity() {
//! format!("Welcome! {}", id)
//! } else {
//! Ok("Welcome Anonymous!".to_owned())
//! "Welcome Anonymous!".to_owned()
//! }
//! }
//!
//! fn login(mut req: HttpRequest) -> HttpResponse {
//! req.remember("User1".to_owned()); // <- remember identity
//! fn login(id: Identity) -> HttpResponse {
//! id.remember("User1".to_owned()); // <- remember identity
//! HttpResponse::Ok().finish()
//! }
//!
//! fn logout(mut req: HttpRequest) -> HttpResponse {
//! req.forget(); // <- remove identity
//! fn logout(id: Identity) -> HttpResponse {
//! id.forget(); // <- remove identity
//! HttpResponse::Ok().finish()
//! }
//!
@ -42,118 +41,142 @@
//! // <- create identity middleware
//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
//! .name("auth-cookie")
//! .secure(false),
//! ));
//! .secure(false)))
//! .service(web::resource("/index.html").to(index))
//! .service(web::resource("/login.html").to(login))
//! .service(web::resource("/logout.html").to(logout));
//! }
//! ```
use std::cell::RefCell;
use std::rc::Rc;
use actix_service::{Service, Transform};
use cookie::{Cookie, CookieJar, Key, SameSite};
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
use futures::Future;
use futures::future::{ok, Either, FutureResult};
use futures::{Future, IntoFuture, Poll};
use time::Duration;
use error::{Error, Result};
use http::header::{self, HeaderValue};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response, Started};
use crate::error::{Error, Result};
use crate::http::header::{self, HeaderValue};
use crate::request::HttpRequest;
use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse};
use crate::FromRequest;
use crate::HttpMessage;
/// The helper trait to obtain your identity from a request.
/// The extractor type to obtain your identity from a request.
///
/// ```rust
/// use actix_web::middleware::identity::RequestIdentity;
/// use actix_web::*;
/// use actix_web::middleware::identity::Identity;
///
/// fn index(req: HttpRequest) -> Result<String> {
/// fn index(id: Identity) -> Result<String> {
/// // access request identity
/// if let Some(id) = req.identity() {
/// if let Some(id) = id.identity() {
/// Ok(format!("Welcome! {}", id))
/// } else {
/// Ok("Welcome Anonymous!".to_owned())
/// }
/// }
///
/// fn login(mut req: HttpRequest) -> HttpResponse {
/// req.remember("User1".to_owned()); // <- remember identity
/// fn login(id: Identity) -> HttpResponse {
/// id.remember("User1".to_owned()); // <- remember identity
/// HttpResponse::Ok().finish()
/// }
///
/// fn logout(mut req: HttpRequest) -> HttpResponse {
/// req.forget(); // <- remove identity
/// fn logout(id: Identity) -> HttpResponse {
/// id.forget(); // <- remove identity
/// HttpResponse::Ok().finish()
/// }
/// # fn main() {}
/// ```
pub trait RequestIdentity {
#[derive(Clone)]
pub struct Identity(HttpRequest);
impl Identity {
/// Return the claimed identity of the user associated request or
/// ``None`` if no identity can be found associated with the request.
fn identity(&self) -> Option<String>;
pub fn identity(&self) -> Option<String> {
if let Some(id) = self.0.extensions().get::<IdentityItem>() {
id.id.clone()
} else {
None
}
}
/// Remember identity.
fn remember(&self, identity: String);
pub fn remember(&self, identity: String) {
if let Some(id) = self.0.extensions_mut().get_mut::<IdentityItem>() {
id.id = Some(identity);
id.changed = true;
}
}
/// This method is used to 'forget' the current identity on subsequent
/// requests.
fn forget(&self);
}
impl<S> RequestIdentity for HttpRequest<S> {
fn identity(&self) -> Option<String> {
if let Some(id) = self.extensions().get::<IdentityBox>() {
return id.0.identity().map(|s| s.to_owned());
}
None
}
fn remember(&self, identity: String) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.as_mut().remember(identity);
}
}
fn forget(&self) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.forget();
pub fn forget(&self) {
if let Some(id) = self.0.extensions_mut().get_mut::<IdentityItem>() {
id.id = None;
id.changed = true;
}
}
}
/// 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>;
struct IdentityItem {
id: Option<String>,
changed: bool,
}
/// Remember identity.
fn remember(&mut self, key: String);
/// Extractor implementation for Identity type.
///
/// ```rust
/// # use actix_web::*;
/// use actix_web::middleware::identity::Identity;
///
/// fn index(id: Identity) -> String {
/// // access request identity
/// if let Some(id) = id.identity() {
/// format!("Welcome! {}", id)
/// } else {
/// "Welcome Anonymous!".to_owned()
/// }
/// }
/// # fn main() {}
/// ```
impl<P> FromRequest<P> for Identity {
type Error = Error;
type Future = Result<Identity, Error>;
/// This method is used to 'forget' the current identity on subsequent
/// requests.
fn forget(&mut self);
/// Write session to storage backend.
fn write(&mut self, resp: HttpResponse) -> Result<Response>;
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
Ok(Identity(req.clone()))
}
}
/// Identity policy definition.
pub trait IdentityPolicy<S>: Sized + 'static {
/// The associated identity
type Identity: Identity;
pub trait IdentityPolicy: Sized + 'static {
/// The return type of the middleware
type Future: IntoFuture<Item = Option<String>, Error = Error>;
/// The return type of the middleware
type Future: Future<Item = Self::Identity, Error = Error>;
type ResponseFuture: IntoFuture<Item = (), Error = Error>;
/// Parse the session from request and load data from a service identity.
fn from_request(&self, request: &HttpRequest<S>) -> Self::Future;
fn from_request<P>(&self, request: &mut ServiceRequest<P>) -> Self::Future;
/// Write changes to response
fn to_response<B>(
&self,
identity: Option<String>,
changed: bool,
response: &mut ServiceResponse<B>,
) -> Self::ResponseFuture;
}
/// Request identity middleware
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
/// use actix_web::App;
/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
///
/// fn main() {
/// let app = App::new().middleware(IdentityService::new(
@ -165,68 +188,98 @@ pub trait IdentityPolicy<S>: Sized + 'static {
/// }
/// ```
pub struct IdentityService<T> {
backend: T,
backend: Rc<T>,
}
impl<T> IdentityService<T> {
/// Create new identity service with specified backend.
pub fn new(backend: T) -> Self {
IdentityService { backend }
IdentityService {
backend: Rc::new(backend),
}
}
}
struct IdentityBox(Box<Identity>);
impl<S, T, P, B> Transform<S> for IdentityService<T>
where
P: 'static,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
S::Future: 'static,
T: IdentityPolicy,
B: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = S::Error;
type InitError = ();
type Transform = IdentityServiceMiddleware<S, T>;
type Future = FutureResult<Self::Transform, Self::InitError>;
impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
let req = req.clone();
let fut = self.backend.from_request(&req).then(move |res| match res {
Ok(id) => {
req.extensions_mut().insert(IdentityBox(Box::new(id)));
FutOk(None)
}
Err(err) => FutErr(err),
});
Ok(Started::Future(Box::new(fut)))
}
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
if let Some(ref mut id) = req.extensions_mut().get_mut::<IdentityBox>() {
id.0.as_mut().write(resp)
} else {
Ok(Response::Done(resp))
}
fn new_transform(&self, service: S) -> Self::Future {
ok(IdentityServiceMiddleware {
backend: self.backend.clone(),
service: Rc::new(RefCell::new(service)),
})
}
}
#[doc(hidden)]
/// Identity that uses private cookies as identity storage.
pub struct CookieIdentity {
changed: bool,
identity: Option<String>,
inner: Rc<CookieIdentityInner>,
pub struct IdentityServiceMiddleware<S, T> {
backend: Rc<T>,
service: Rc<RefCell<S>>,
}
impl Identity for CookieIdentity {
fn identity(&self) -> Option<&str> {
self.identity.as_ref().map(|s| s.as_ref())
impl<S, T, P, B> Service for IdentityServiceMiddleware<S, T>
where
P: 'static,
B: 'static,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
S::Future: 'static,
T: IdentityPolicy,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = S::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.borrow_mut().poll_ready()
}
fn remember(&mut self, value: String) {
self.changed = true;
self.identity = Some(value);
}
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
let srv = self.service.clone();
let backend = self.backend.clone();
fn forget(&mut self) {
self.changed = true;
self.identity = None;
}
Box::new(
self.backend.from_request(&mut req).into_future().then(
move |res| match res {
Ok(id) => {
req.extensions_mut()
.insert(IdentityItem { id, changed: false });
fn write(&mut self, mut resp: HttpResponse) -> Result<Response> {
if self.changed {
let _ = self.inner.set_cookie(&mut resp, self.identity.take());
}
Ok(Response::Done(resp))
Either::A(srv.borrow_mut().call(req).and_then(move |mut res| {
let id =
res.request().extensions_mut().remove::<IdentityItem>();
if let Some(id) = id {
return Either::A(
backend
.to_response(id.id, id.changed, &mut res)
.into_future()
.then(move |t| match t {
Ok(_) => Ok(res),
Err(e) => Ok(res.error_response(e)),
}),
);
} else {
Either::B(ok(res))
}
}))
}
Err(err) => Either::B(ok(req.error_response(err))),
},
),
)
}
}
@ -253,7 +306,11 @@ impl CookieIdentityInner {
}
}
fn set_cookie(&self, resp: &mut HttpResponse, id: Option<String>) -> Result<()> {
fn set_cookie<B>(
&self,
resp: &mut ServiceResponse<B>,
id: Option<String>,
) -> Result<()> {
let some = id.is_some();
{
let id = id.unwrap_or_else(String::new);
@ -291,7 +348,7 @@ impl CookieIdentityInner {
Ok(())
}
fn load<S>(&self, req: &HttpRequest<S>) -> Option<String> {
fn load<T>(&self, req: &ServiceRequest<T>) -> Option<String> {
if let Ok(cookies) = req.cookies() {
for cookie in cookies.iter() {
if cookie.name() == self.name {
@ -384,16 +441,89 @@ impl CookieIdentityPolicy {
}
}
impl<S> IdentityPolicy<S> for CookieIdentityPolicy {
type Identity = CookieIdentity;
type Future = FutureResult<CookieIdentity, Error>;
impl IdentityPolicy for CookieIdentityPolicy {
type Future = Result<Option<String>, Error>;
type ResponseFuture = Result<(), Error>;
fn from_request(&self, req: &HttpRequest<S>) -> Self::Future {
let identity = self.0.load(req);
FutOk(CookieIdentity {
identity,
changed: false,
inner: Rc::clone(&self.0),
})
fn from_request<P>(&self, req: &mut ServiceRequest<P>) -> Self::Future {
Ok(self.0.load(req))
}
fn to_response<B>(
&self,
id: Option<String>,
changed: bool,
res: &mut ServiceResponse<B>,
) -> Self::ResponseFuture {
if changed {
let _ = self.0.set_cookie(res, id);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::http::StatusCode;
use crate::test::{self, TestRequest};
use crate::{web, App, HttpResponse};
#[test]
fn test_identity() {
let mut srv = test::init_service(
App::new()
.middleware(IdentityService::new(
CookieIdentityPolicy::new(&[0; 32])
.domain("www.rust-lang.org")
.name("actix_auth")
.path("/")
.secure(true),
))
.service(web::resource("/index").to(|id: Identity| {
if id.identity().is_some() {
HttpResponse::Created()
} else {
HttpResponse::Ok()
}
}))
.service(web::resource("/login").to(|id: Identity| {
id.remember("test".to_string());
HttpResponse::Ok()
}))
.service(web::resource("/logout").to(|id: Identity| {
if id.identity().is_some() {
id.forget();
HttpResponse::Ok()
} else {
HttpResponse::BadRequest()
}
})),
);
let resp =
test::call_success(&mut srv, TestRequest::with_uri("/index").to_request());
assert_eq!(resp.status(), StatusCode::OK);
let resp =
test::call_success(&mut srv, TestRequest::with_uri("/login").to_request());
assert_eq!(resp.status(), StatusCode::OK);
let c = resp.cookies().next().unwrap().to_owned();
let resp = test::call_success(
&mut srv,
TestRequest::with_uri("/index")
.cookie(c.clone())
.to_request(),
);
assert_eq!(resp.status(), StatusCode::CREATED);
let resp = test::call_success(
&mut srv,
TestRequest::with_uri("/logout")
.cookie(c.clone())
.to_request(),
);
assert_eq!(resp.status(), StatusCode::OK);
assert!(resp.headers().contains_key(header::SET_COOKIE))
}
}

View File

@ -2,15 +2,20 @@
use std::collections::HashSet;
use std::env;
use std::fmt::{self, Display, Formatter};
use std::marker::PhantomData;
use std::rc::Rc;
use actix_service::{Service, Transform};
use bytes::Bytes;
use futures::future::{ok, FutureResult};
use futures::{Async, Future, Poll};
use regex::Regex;
use time;
use error::Result;
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Finished, Middleware, Started};
use crate::dev::{BodyLength, MessageBody, ResponseBody};
use crate::error::{Error, Result};
use crate::service::{ServiceRequest, ServiceResponse};
use crate::{HttpMessage, HttpResponse};
/// `Middleware` for logging request and response info to the terminal.
///
@ -28,8 +33,6 @@ use middleware::{Finished, Middleware, Started};
/// %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;
///
@ -39,8 +42,7 @@ use middleware::{Finished, Middleware, Started};
///
/// let app = App::new()
/// .middleware(Logger::default())
/// .middleware(Logger::new("%a %{User-Agent}i"))
/// .finish();
/// .middleware(Logger::new("%a %{User-Agent}i"));
/// }
/// ```
///
@ -69,7 +71,9 @@ use middleware::{Finished, Middleware, Started};
///
/// `%{FOO}e` os.environ['FOO']
///
pub struct Logger {
pub struct Logger(Rc<Inner>);
struct Inner {
format: Format,
exclude: HashSet<String>,
}
@ -77,15 +81,18 @@ pub struct Logger {
impl Logger {
/// Create `Logger` middleware with the specified `format`.
pub fn new(format: &str) -> Logger {
Logger {
Logger(Rc::new(Inner {
format: Format::new(format),
exclude: HashSet::new(),
}
}))
}
/// Ignore and do not log access info for specified path.
pub fn exclude<T: Into<String>>(mut self, path: T) -> Self {
self.exclude.insert(path.into());
Rc::get_mut(&mut self.0)
.unwrap()
.exclude
.insert(path.into());
self
}
}
@ -97,40 +104,152 @@ impl Default for Logger {
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// ```
fn default() -> Logger {
Logger {
Logger(Rc::new(Inner {
format: Format::default(),
exclude: HashSet::new(),
}))
}
}
impl<S, P, B> Transform<S> for Logger
where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
B: MessageBody,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<StreamLog<B>>;
type Error = S::Error;
type InitError = ();
type Transform = LoggerMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
ok(LoggerMiddleware {
service,
inner: self.0.clone(),
})
}
}
/// Logger middleware
pub struct LoggerMiddleware<S> {
inner: Rc<Inner>,
service: S,
}
impl<S, P, B> Service for LoggerMiddleware<S>
where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
B: MessageBody,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<StreamLog<B>>;
type Error = S::Error;
type Future = LoggerResponse<S, P, B>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
if self.inner.exclude.contains(req.path()) {
LoggerResponse {
fut: self.service.call(req),
format: None,
time: time::now(),
_t: PhantomData,
}
} else {
let now = time::now();
let mut format = self.inner.format.clone();
for unit in &mut format.0 {
unit.render_request(now, &req);
}
LoggerResponse {
fut: self.service.call(req),
format: Some(format),
time: now,
_t: PhantomData,
}
}
}
}
struct StartTime(time::Tm);
#[doc(hidden)]
pub struct LoggerResponse<S, P, B>
where
B: MessageBody,
S: Service,
{
fut: S::Future,
time: time::Tm,
format: Option<Format>,
_t: PhantomData<(P, B)>,
}
impl Logger {
fn log<S>(&self, req: &HttpRequest<S>, resp: &HttpResponse) {
if let Some(entry_time) = req.extensions().get::<StartTime>() {
impl<S, P, B> Future for LoggerResponse<S, P, B>
where
B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
{
type Item = ServiceResponse<StreamLog<B>>;
type Error = S::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let res = futures::try_ready!(self.fut.poll());
if let Some(ref mut format) = self.format {
for unit in &mut format.0 {
unit.render_response(&res);
}
}
Ok(Async::Ready(res.map_body(move |_, body| {
ResponseBody::Body(StreamLog {
body,
size: 0,
time: self.time,
format: self.format.take(),
})
})))
}
}
pub struct StreamLog<B> {
body: ResponseBody<B>,
format: Option<Format>,
size: usize,
time: time::Tm,
}
impl<B> Drop for StreamLog<B> {
fn drop(&mut self) {
if let Some(ref format) = self.format {
let render = |fmt: &mut Formatter| {
for unit in &self.format.0 {
unit.render(fmt, req, resp, entry_time.0)?;
for unit in &format.0 {
unit.render(fmt, self.size, self.time)?;
}
Ok(())
};
info!("{}", FormatDisplay(&render));
log::info!("{}", FormatDisplay(&render));
}
}
}
impl<S> Middleware<S> for Logger {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
if !self.exclude.contains(req.path()) {
req.extensions_mut().insert(StartTime(time::now()));
}
Ok(Started::Done)
impl<B: MessageBody> MessageBody for StreamLog<B> {
fn length(&self) -> BodyLength {
self.body.length()
}
fn finish(&self, req: &HttpRequest<S>, resp: &HttpResponse) -> Finished {
self.log(req, resp);
Finished::Done
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
match self.body.poll_next()? {
Async::Ready(Some(chunk)) => {
self.size += chunk.len();
Ok(Async::Ready(Some(chunk)))
}
val => Ok(val),
}
}
}
@ -152,7 +271,7 @@ impl Format {
///
/// Returns `None` if the format string syntax is incorrect.
pub fn new(s: &str) -> Format {
trace!("Access log format: {}", s);
log::trace!("Access log format: {}", s);
let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbTD]?)").unwrap();
let mut idx = 0;
@ -215,33 +334,16 @@ pub enum FormatText {
}
impl FormatText {
fn render<S>(
&self, fmt: &mut Formatter, req: &HttpRequest<S>, resp: &HttpResponse,
fn render(
&self,
fmt: &mut Formatter,
size: usize,
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::ResponseSize => 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;
@ -252,17 +354,71 @@ impl FormatText {
let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0;
fmt.write_fmt(format_args!("{:.6}", rt))
}
FormatText::RemoteAddr => {
if let Some(remote) = req.connection_info().remote() {
return remote.fmt(fmt);
// FormatText::RemoteAddr => {
// if let Some(remote) = req.connection_info().remote() {
// return remote.fmt(fmt);
// } else {
// "-".fmt(fmt)
// }
// }
FormatText::EnvironHeader(ref name) => {
if let Ok(val) = env::var(name) {
fmt.write_fmt(format_args!("{}", val))
} else {
"-".fmt(fmt)
}
}
FormatText::RequestTime => entry_time
.strftime("[%d/%b/%Y:%H:%M:%S %z]")
.unwrap()
.fmt(fmt),
_ => Ok(()),
}
}
fn render_response<B>(&mut self, res: &HttpResponse<B>) {
match *self {
FormatText::ResponseStatus => {
*self = FormatText::Str(format!("{}", res.status().as_u16()))
}
FormatText::ResponseHeader(ref name) => {
let s = if let Some(val) = res.headers().get(name) {
if let Ok(s) = val.to_str() {
s
} else {
"-"
}
} else {
"-"
};
*self = FormatText::Str(s.to_string())
}
_ => (),
}
}
fn render_request<P>(&mut self, now: time::Tm, req: &ServiceRequest<P>) {
match *self {
FormatText::RequestLine => {
*self = if req.query_string().is_empty() {
FormatText::Str(format!(
"{} {} {:?}",
req.method(),
req.path(),
req.version()
))
} else {
FormatText::Str(format!(
"{} {}?{} {:?}",
req.method(),
req.path(),
req.query_string(),
req.version()
))
};
}
FormatText::RequestTime => {
*self = FormatText::Str(format!(
"{:?}",
now.strftime("[%d/%b/%Y:%H:%M:%S %z]").unwrap()
))
}
FormatText::RequestHeader(ref name) => {
let s = if let Some(val) = req.headers().get(name) {
if let Ok(s) = val.to_str() {
@ -273,27 +429,9 @@ impl FormatText {
} 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)
}
*self = FormatText::Str(s.to_string());
}
_ => (),
}
}
}
@ -308,77 +446,67 @@ impl<'a> fmt::Display for FormatDisplay<'a> {
#[cfg(test)]
mod tests {
use time;
use actix_service::{FnService, Service, Transform};
use super::*;
use http::{header, StatusCode};
use test::TestRequest;
use crate::http::{header, StatusCode};
use crate::test::{block_on, TestRequest};
#[test]
fn test_logger() {
let srv = FnService::new(|req: ServiceRequest<_>| {
req.into_response(
HttpResponse::build(StatusCode::OK)
.header("X-Test", "ttt")
.finish(),
)
});
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 mut srv = block_on(logger.new_transform(srv)).unwrap();
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"));
)
.to_service();
let _res = block_on(srv.call(req));
}
// #[test]
// fn test_default_format() {
// let format = Format::default();
// let req = TestRequest::with_header(
// header::USER_AGENT,
// header::HeaderValue::from_static("ACTIX-WEB"),
// )
// .finish();
// let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
// let entry_time = time::now();
// let render = |fmt: &mut Formatter| {
// for unit in &format.0 {
// unit.render(fmt, &req, &resp, entry_time)?;
// }
// Ok(())
// };
// let s = format!("{}", FormatDisplay(&render));
// assert!(s.contains("GET / HTTP/1.1"));
// assert!(s.contains("200 0"));
// assert!(s.contains("ACTIX-WEB"));
// let req = TestRequest::with_uri("/?test").finish();
// let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
// let entry_time = time::now();
// let render = |fmt: &mut Formatter| {
// for unit in &format.0 {
// unit.render(fmt, &req, &resp, entry_time)?;
// }
// Ok(())
// };
// let s = format!("{}", FormatDisplay(&render));
// assert!(s.contains("GET /?test HTTP/1.1"));
// }
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,715 +0,0 @@
//! Payload stream
use bytes::{Bytes, BytesMut};
#[cfg(not(test))]
use futures::task::current as current_task;
use futures::task::Task;
use futures::{Async, Poll, Stream};
use std::cell::RefCell;
use std::cmp;
use std::collections::VecDeque;
use std::rc::{Rc, Weak};
use error::PayloadError;
/// max buffer size 32k
pub(crate) const MAX_BUFFER_SIZE: usize = 32_768;
#[derive(Debug, PartialEq)]
pub(crate) enum PayloadStatus {
Read,
Pause,
Dropped,
}
/// Buffered stream of bytes chunks
///
/// Payload stores chunks in a vector. First chunk can be received with
/// `.readany()` method. Payload stream is not thread safe. Payload does not
/// notify current task when new data is available.
///
/// Payload stream can be used as `HttpResponse` body stream.
#[derive(Debug)]
pub struct Payload {
inner: Rc<RefCell<Inner>>,
}
impl Payload {
/// Create payload stream.
///
/// This method construct two objects responsible for bytes stream
/// generation.
///
/// * `PayloadSender` - *Sender* side of the stream
///
/// * `Payload` - *Receiver* side of the stream
pub fn new(eof: bool) -> (PayloadSender, Payload) {
let shared = Rc::new(RefCell::new(Inner::new(eof)));
(
PayloadSender {
inner: Rc::downgrade(&shared),
},
Payload { inner: shared },
)
}
/// Create empty payload
#[doc(hidden)]
pub fn empty() -> Payload {
Payload {
inner: Rc::new(RefCell::new(Inner::new(true))),
}
}
/// Length of the data in this payload
#[cfg(test)]
pub fn len(&self) -> usize {
self.inner.borrow().len()
}
/// Is payload empty
#[cfg(test)]
pub fn is_empty(&self) -> bool {
self.inner.borrow().len() == 0
}
/// Put unused data back to payload
#[inline]
pub fn unread_data(&mut self, data: Bytes) {
self.inner.borrow_mut().unread_data(data);
}
#[cfg(test)]
pub(crate) fn readall(&self) -> Option<Bytes> {
self.inner.borrow_mut().readall()
}
#[inline]
/// Set read buffer capacity
///
/// Default buffer capacity is 32Kb.
pub fn set_read_buffer_capacity(&mut self, cap: usize) {
self.inner.borrow_mut().capacity = cap;
}
}
impl Stream for Payload {
type Item = Bytes;
type Error = PayloadError;
#[inline]
fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
self.inner.borrow_mut().readany()
}
}
impl Clone for Payload {
fn clone(&self) -> Payload {
Payload {
inner: Rc::clone(&self.inner),
}
}
}
/// Payload writer interface.
pub(crate) trait PayloadWriter {
/// Set stream error.
fn set_error(&mut self, err: PayloadError);
/// Write eof into a stream which closes reading side of a stream.
fn feed_eof(&mut self);
/// Feed bytes into a payload stream
fn feed_data(&mut self, data: Bytes);
/// Need read data
fn need_read(&self) -> PayloadStatus;
}
/// Sender part of the payload stream
pub struct PayloadSender {
inner: Weak<RefCell<Inner>>,
}
impl PayloadWriter for PayloadSender {
#[inline]
fn set_error(&mut self, err: PayloadError) {
if let Some(shared) = self.inner.upgrade() {
shared.borrow_mut().set_error(err)
}
}
#[inline]
fn feed_eof(&mut self) {
if let Some(shared) = self.inner.upgrade() {
shared.borrow_mut().feed_eof()
}
}
#[inline]
fn feed_data(&mut self, data: Bytes) {
if let Some(shared) = self.inner.upgrade() {
shared.borrow_mut().feed_data(data)
}
}
#[inline]
fn need_read(&self) -> PayloadStatus {
// we check need_read only if Payload (other side) is alive,
// otherwise always return true (consume payload)
if let Some(shared) = self.inner.upgrade() {
if shared.borrow().need_read {
PayloadStatus::Read
} else {
#[cfg(not(test))]
{
if shared.borrow_mut().io_task.is_none() {
shared.borrow_mut().io_task = Some(current_task());
}
}
PayloadStatus::Pause
}
} else {
PayloadStatus::Dropped
}
}
}
#[derive(Debug)]
struct Inner {
len: usize,
eof: bool,
err: Option<PayloadError>,
need_read: bool,
items: VecDeque<Bytes>,
capacity: usize,
task: Option<Task>,
io_task: Option<Task>,
}
impl Inner {
fn new(eof: bool) -> Self {
Inner {
eof,
len: 0,
err: None,
items: VecDeque::new(),
need_read: true,
capacity: MAX_BUFFER_SIZE,
task: None,
io_task: None,
}
}
#[inline]
fn set_error(&mut self, err: PayloadError) {
self.err = Some(err);
}
#[inline]
fn feed_eof(&mut self) {
self.eof = true;
}
#[inline]
fn feed_data(&mut self, data: Bytes) {
self.len += data.len();
self.items.push_back(data);
self.need_read = self.len < self.capacity;
if let Some(task) = self.task.take() {
task.notify()
}
}
#[cfg(test)]
fn len(&self) -> usize {
self.len
}
#[cfg(test)]
pub(crate) fn readall(&mut self) -> Option<Bytes> {
let len = self.items.iter().map(|b| b.len()).sum();
if len > 0 {
let mut buf = BytesMut::with_capacity(len);
for item in &self.items {
buf.extend_from_slice(item);
}
self.items = VecDeque::new();
self.len = 0;
Some(buf.take().freeze())
} else {
self.need_read = true;
None
}
}
fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> {
if let Some(data) = self.items.pop_front() {
self.len -= data.len();
self.need_read = self.len < self.capacity;
#[cfg(not(test))]
{
if self.need_read && self.task.is_none() {
self.task = Some(current_task());
}
if let Some(task) = self.io_task.take() {
task.notify()
}
}
Ok(Async::Ready(Some(data)))
} else if let Some(err) = self.err.take() {
Err(err)
} else if self.eof {
Ok(Async::Ready(None))
} else {
self.need_read = true;
#[cfg(not(test))]
{
if self.task.is_none() {
self.task = Some(current_task());
}
if let Some(task) = self.io_task.take() {
task.notify()
}
}
Ok(Async::NotReady)
}
}
fn unread_data(&mut self, data: Bytes) {
self.len += data.len();
self.items.push_front(data);
}
}
/// Payload buffer
pub struct PayloadBuffer<S> {
len: usize,
items: VecDeque<Bytes>,
stream: S,
}
impl<S> PayloadBuffer<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
/// Create new `PayloadBuffer` instance
pub fn new(stream: S) -> Self {
PayloadBuffer {
len: 0,
items: VecDeque::new(),
stream,
}
}
/// Get mutable reference to an inner stream.
pub fn get_mut(&mut self) -> &mut S {
&mut self.stream
}
#[inline]
fn poll_stream(&mut self) -> Poll<bool, PayloadError> {
self.stream.poll().map(|res| match res {
Async::Ready(Some(data)) => {
self.len += data.len();
self.items.push_back(data);
Async::Ready(true)
}
Async::Ready(None) => Async::Ready(false),
Async::NotReady => Async::NotReady,
})
}
/// Read first available chunk of bytes
#[inline]
pub fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> {
if let Some(data) = self.items.pop_front() {
self.len -= data.len();
Ok(Async::Ready(Some(data)))
} else {
match self.poll_stream()? {
Async::Ready(true) => self.readany(),
Async::Ready(false) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
}
}
}
/// Check if buffer contains enough bytes
#[inline]
pub fn can_read(&mut self, size: usize) -> Poll<Option<bool>, PayloadError> {
if size <= self.len {
Ok(Async::Ready(Some(true)))
} else {
match self.poll_stream()? {
Async::Ready(true) => self.can_read(size),
Async::Ready(false) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
}
}
}
/// Return reference to the first chunk of data
#[inline]
pub fn get_chunk(&mut self) -> Poll<Option<&[u8]>, PayloadError> {
if self.items.is_empty() {
match self.poll_stream()? {
Async::Ready(true) => (),
Async::Ready(false) => return Ok(Async::Ready(None)),
Async::NotReady => return Ok(Async::NotReady),
}
}
match self.items.front().map(|c| c.as_ref()) {
Some(chunk) => Ok(Async::Ready(Some(chunk))),
None => Ok(Async::NotReady),
}
}
/// Read exact number of bytes
#[inline]
pub fn read_exact(&mut self, size: usize) -> Poll<Option<Bytes>, PayloadError> {
if size <= self.len {
self.len -= size;
let mut chunk = self.items.pop_front().unwrap();
if size < chunk.len() {
let buf = chunk.split_to(size);
self.items.push_front(chunk);
Ok(Async::Ready(Some(buf)))
} else if size == chunk.len() {
Ok(Async::Ready(Some(chunk)))
} else {
let mut buf = BytesMut::with_capacity(size);
buf.extend_from_slice(&chunk);
while buf.len() < size {
let mut chunk = self.items.pop_front().unwrap();
let rem = cmp::min(size - buf.len(), chunk.len());
buf.extend_from_slice(&chunk.split_to(rem));
if !chunk.is_empty() {
self.items.push_front(chunk);
}
}
Ok(Async::Ready(Some(buf.freeze())))
}
} else {
match self.poll_stream()? {
Async::Ready(true) => self.read_exact(size),
Async::Ready(false) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
}
}
}
/// Remove specified amount if bytes from buffer
#[inline]
pub fn drop_bytes(&mut self, size: usize) {
if size <= self.len {
self.len -= size;
let mut len = 0;
while len < size {
let mut chunk = self.items.pop_front().unwrap();
let rem = cmp::min(size - len, chunk.len());
len += rem;
if rem < chunk.len() {
chunk.split_to(rem);
self.items.push_front(chunk);
}
}
}
}
/// Copy buffered data
pub fn copy(&mut self, size: usize) -> Poll<Option<BytesMut>, PayloadError> {
if size <= self.len {
let mut buf = BytesMut::with_capacity(size);
for chunk in &self.items {
if buf.len() < size {
let rem = cmp::min(size - buf.len(), chunk.len());
buf.extend_from_slice(&chunk[..rem]);
}
if buf.len() == size {
return Ok(Async::Ready(Some(buf)));
}
}
}
match self.poll_stream()? {
Async::Ready(true) => self.copy(size),
Async::Ready(false) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
}
}
/// Read until specified ending
pub fn read_until(&mut self, line: &[u8]) -> Poll<Option<Bytes>, PayloadError> {
let mut idx = 0;
let mut num = 0;
let mut offset = 0;
let mut found = false;
let mut length = 0;
for no in 0..self.items.len() {
{
let chunk = &self.items[no];
for (pos, ch) in chunk.iter().enumerate() {
if *ch == line[idx] {
idx += 1;
if idx == line.len() {
num = no;
offset = pos + 1;
length += pos + 1;
found = true;
break;
}
} else {
idx = 0
}
}
if !found {
length += chunk.len()
}
}
if found {
let mut buf = BytesMut::with_capacity(length);
if num > 0 {
for _ in 0..num {
buf.extend_from_slice(&self.items.pop_front().unwrap());
}
}
if offset > 0 {
let mut chunk = self.items.pop_front().unwrap();
buf.extend_from_slice(&chunk.split_to(offset));
if !chunk.is_empty() {
self.items.push_front(chunk)
}
}
self.len -= length;
return Ok(Async::Ready(Some(buf.freeze())));
}
}
match self.poll_stream()? {
Async::Ready(true) => self.read_until(line),
Async::Ready(false) => Ok(Async::Ready(None)),
Async::NotReady => Ok(Async::NotReady),
}
}
/// Read bytes until new line delimiter
pub fn readline(&mut self) -> Poll<Option<Bytes>, PayloadError> {
self.read_until(b"\n")
}
/// Put unprocessed data back to the buffer
pub fn unprocessed(&mut self, data: Bytes) {
self.len += data.len();
self.items.push_front(data);
}
/// Get remaining data from the buffer
pub fn remaining(&mut self) -> Bytes {
self.items
.iter_mut()
.fold(BytesMut::new(), |mut b, c| {
b.extend_from_slice(c);
b
}).freeze()
}
}
#[cfg(test)]
mod tests {
use super::*;
use failure::Fail;
use futures::future::{lazy, result};
use std::io;
use tokio::runtime::current_thread::Runtime;
#[test]
fn test_error() {
let err: PayloadError =
io::Error::new(io::ErrorKind::Other, "ParseError").into();
assert_eq!(format!("{}", err), "ParseError");
assert_eq!(format!("{}", err.cause().unwrap()), "ParseError");
let err = PayloadError::Incomplete;
assert_eq!(
format!("{}", err),
"A payload reached EOF, but is not complete."
);
}
#[test]
fn test_basic() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (_, payload) = Payload::new(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(payload.len, 0);
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}
#[test]
fn test_eof() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
sender.feed_data(Bytes::from("data"));
sender.feed_eof();
assert_eq!(
Async::Ready(Some(Bytes::from("data"))),
payload.readany().ok().unwrap()
);
assert_eq!(payload.len, 0);
assert_eq!(Async::Ready(None), payload.readany().ok().unwrap());
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}
#[test]
fn test_err() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
sender.set_error(PayloadError::Incomplete);
payload.readany().err().unwrap();
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}
#[test]
fn test_readany() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadBuffer::new(payload);
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
assert_eq!(
Async::Ready(Some(Bytes::from("line1"))),
payload.readany().ok().unwrap()
);
assert_eq!(payload.len, 0);
assert_eq!(
Async::Ready(Some(Bytes::from("line2"))),
payload.readany().ok().unwrap()
);
assert_eq!(payload.len, 0);
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}
#[test]
fn test_readexactly() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap());
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
assert_eq!(
Async::Ready(Some(Bytes::from_static(b"li"))),
payload.read_exact(2).ok().unwrap()
);
assert_eq!(payload.len, 3);
assert_eq!(
Async::Ready(Some(Bytes::from_static(b"ne1l"))),
payload.read_exact(4).ok().unwrap()
);
assert_eq!(payload.len, 4);
sender.set_error(PayloadError::Incomplete);
payload.read_exact(10).err().unwrap();
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}
#[test]
fn test_readuntil() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap());
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
assert_eq!(
Async::Ready(Some(Bytes::from("line"))),
payload.read_until(b"ne").ok().unwrap()
);
assert_eq!(payload.len, 1);
assert_eq!(
Async::Ready(Some(Bytes::from("1line2"))),
payload.read_until(b"2").ok().unwrap()
);
assert_eq!(payload.len, 0);
sender.set_error(PayloadError::Incomplete);
payload.read_until(b"b").err().unwrap();
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}
#[test]
fn test_unread_data() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (_, mut payload) = Payload::new(false);
payload.unread_data(Bytes::from("data"));
assert!(!payload.is_empty());
assert_eq!(payload.len(), 4);
assert_eq!(
Async::Ready(Some(Bytes::from("data"))),
payload.poll().ok().unwrap()
);
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}
}

View File

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

View File

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

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