1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-21 08:36:15 +02:00

Compare commits

...

101 Commits

Author SHA1 Message Date
Nikolay Kim
6db909a3e7 update migration 2019-12-25 20:27:30 +04:00
Nikolay Kim
642ae161c0 prep actix-web release 2019-12-25 20:21:00 +04:00
Nikolay Kim
7b3c99b933 prep actix-framed release 2019-12-25 20:17:22 +04:00
Nikolay Kim
f86ce0390e allow to specify multi pattern for resources 2019-12-25 20:14:44 +04:00
Nikolay Kim
7882f545e5 Allow to gracefully stop test server via TestServer::stop() 2019-12-25 12:10:48 +04:00
Nikolay Kim
1c75e6876b update migration 2019-12-22 17:16:07 +04:00
Nikolay Kim
6a0cd2dced Rename HttpServer::start() to HttpServer::run() 2019-12-22 17:12:22 +04:00
Nikolay Kim
c7f3915779 update actix-service dep 2019-12-22 16:39:25 +04:00
Yuki Okushi
f45db1f909 Enable GitHub Actions and fix file URL behavior (#1232)
* Use GitHub Actions

* Fix unused imports on Windows

* Fix test for Windows

* Stop to run CI for i686-pc-windows-msvc for now

* Use `/` instead of `\` on Windows

* Add entry to changelog

* Prepare actix-files release
2019-12-22 16:43:41 +09:00
Darin
3751a4018e fixed test::init_service api docs (missing await) (#1230) 2019-12-21 08:47:18 +06:00
Nikolay Kim
0cb1b0642f add test server data test 2019-12-20 23:18:59 +06:00
Nikolay Kim
48476362a3 update changes 2019-12-20 17:59:34 +06:00
Nikolay Kim
2b4256baab add links to configs 2019-12-20 17:49:05 +06:00
Nikolay Kim
e5a50f423d Make web::Data deref to Arc<T> #1214 2019-12-20 17:45:35 +06:00
Nikolay Kim
8b8a9a995d bump ver 2019-12-20 17:36:48 +06:00
Nikolay Kim
74fa4060c2 fix awc tests 2019-12-20 17:27:32 +06:00
Nikolay Kim
c877840c07 rename App::register_data to App::app_data and HttpRequest::app_data returns Option<&T> instead of Option<&Data<T>> 2019-12-20 17:13:09 +06:00
Nikolay Kim
20248daeda Allow to set peer_addr for TestRequest #1074 2019-12-20 16:11:51 +06:00
Nikolay Kim
a08d8dab70 AppConfig::secure() is always false. #1202 2019-12-20 16:04:51 +06:00
tglman
fbbb4a86e9 feat: add access to the session also from immutable references (#1225) 2019-12-20 13:59:07 +06:00
Nikolay Kim
1d12ba9d5f Replace brotli with brotli2 #1224 2019-12-20 13:50:07 +06:00
Nikolay Kim
8c54054844 Use .advance() intead of .split_to() 2019-12-19 09:56:14 +06:00
Nikolay Kim
1732ae8c79 fix Bodyencoding trait usage 2019-12-18 09:30:14 +06:00
Rajasekharan Vengalil
3b860ebdc7 Fix poll_ready call for WebSockets upgrade (#1219)
* Fix poll_ready call for WebSockets upgrade

* Poll upgrade service from H1ServiceHandler too
2019-12-17 13:34:25 +06:00
Nikolay Kim
29ac6463e1 Merge branch 'master' of github.com:actix/actix-web 2019-12-16 17:22:49 +06:00
Nikolay Kim
01613f334b Move BodyEncoding to dev module #1220 2019-12-16 17:22:26 +06:00
Andrii Radyk
30dcaf9da0 fix deprecated Error::description (#1218) 2019-12-16 07:43:19 +06:00
Nikolay Kim
b0aa9395da prep actix-web alpha.6 release 2019-12-15 22:51:14 +06:00
Nikolay Kim
a153374b61 migrate actix-web-actors 2019-12-15 22:45:38 +06:00
Nikolay Kim
a791aab418 prep awc release 2019-12-15 13:36:05 +06:00
Nikolay Kim
cb705317b8 compile with default-features off 2019-12-15 13:28:54 +06:00
Nikolay Kim
e8e0f98f96 fix docs.rs features list 2019-12-13 12:41:48 +06:00
Nikolay Kim
c878f66d05 fix docs.rs features list 2019-12-13 12:40:22 +06:00
Nikolay Kim
fac6dec3c9 update deps 2019-12-13 12:36:15 +06:00
Nikolay Kim
232f71b3b5 update changes 2019-12-13 12:18:30 +06:00
Nikolay Kim
8881c13e60 update changes 2019-12-13 12:16:43 +06:00
Nikolay Kim
d006a7b31f update changes 2019-12-13 12:10:45 +06:00
Nikolay Kim
3d64d565d9 fix warnings 2019-12-13 11:46:02 +06:00
Nikolay Kim
c1deaaeb2f cleanup imports 2019-12-13 11:24:57 +06:00
Nikolay Kim
b81417c2fa fix warnings 2019-12-13 10:59:02 +06:00
Nikolay Kim
4937c9f9c2 refactor http-test server 2019-12-12 23:08:38 +06:00
Nikolay Kim
db1d6b7963 refactor test server impl 2019-12-12 22:28:47 +06:00
Nikolay Kim
fa07415721 Replace flate2-xxx features with compress 2019-12-12 15:08:08 +06:00
Nikolay Kim
b4b3350b3e Add websockets continuation frame support 2019-12-12 14:06:54 +06:00
Jonathan Speiser
4a1695f719 fixes missing import in example (#1210) 2019-12-12 07:06:22 +06:00
0x1793d1
1b8d747937 Fix extra line feed (#1209) 2019-12-12 07:05:39 +06:00
Emilio González
a43a005f59 Log path if it is not a directory (#1208) 2019-12-12 07:04:53 +06:00
Alexander Larsson
a612b74aeb actix-multipart: Fix multipart boundary reading (#1205)
* actix-multipart: Fix multipart boundary reading

If we're not ready to read the first line after the multipart field
(which should be a "\r\n" line) then return Pending instead of Ready(None)
so that we will get called again to read that line.

Without this I was getting MultipartError::Boundary from read_boundary()
because it got the "\r\n" line instead of the boundary.

Also tweaks the test_stream test to test partial reads.

This is a forward port of #1189 from 1.0

* actix-multipart: Update changes for boundary fix
2019-12-12 07:03:44 +06:00
Nikolay Kim
131c897099 upgrade to actix-net release 2019-12-11 19:20:20 +06:00
Nikolay Kim
ef3a33b9d6 use std mutext instead of parking_lot 2019-12-10 09:00:51 +06:00
Nikolay Kim
5132257b0d Fix buffer remaining capacity calcualtion 2019-12-09 21:55:22 +06:00
Nikolay Kim
0c1f5f9edc Check Upgrade service readiness before calling it 2019-12-09 17:40:15 +06:00
Sameer Dhar
e4382e4fc1 Fix broken docs (#1204)
Fixed un escaped brackets in lib.rs, and reflowed links to ConnectionInfo in app, config, and server.rs
2019-12-09 10:02:43 +06:00
Nikolay Kim
a3ce371312 ws ping and pong uses bytes #1049 2019-12-09 07:01:22 +06:00
Nikolay Kim
42258ee289 deps 2019-12-08 20:22:39 +06:00
Nikolay Kim
b92eafb839 prepare actix-http release 2019-12-08 20:15:51 +06:00
Nikolay Kim
3b2e78db47 add link to chat 2019-12-08 19:27:06 +06:00
Nikolay Kim
63da1a5560 Merge branch 'master' of github.com:actix/actix-web 2019-12-08 19:26:12 +06:00
Nikolay Kim
1f3ffe38e8 update actix-service dep 2019-12-08 19:25:24 +06:00
krircc
c23b6b3879 Merge pull request #1192 from krircc/master
Add rich project metadata
2019-12-08 16:03:39 +08:00
Yuki Okushi
909c7c8b5b Merge branch 'master' into master 2019-12-08 16:26:35 +09:00
Nikolay Kim
4a8a9ef405 update tests and clippy warnings 2019-12-08 12:31:16 +06:00
Nikolay Kim
6c9f9fff73 clippy warnings 2019-12-08 00:46:51 +06:00
Nikolay Kim
8df33f7a81 remove HttpServer::run() as it is not useful with async/await 2019-12-08 00:06:04 +06:00
Nikolay Kim
7ec5ca88a1 update changes 2019-12-07 22:01:55 +06:00
daxpedda
e5f3d88a4e Switch brotli compressor to rust. (#1197)
* Switch to a rustified version of brotli.

* Some memory optimizations.

* Make brotli not optional anymore.
2019-12-07 21:55:41 +06:00
Nikolay Kim
0ba125444a Add impl ResponseBuilder for Error 2019-12-07 21:41:34 +06:00
Nikolay Kim
6c226e47bd prepare actix-web-actors release 2019-12-07 20:10:36 +06:00
Vlad Frolov
8c3f58db9d Allow comma-separated websocket subprotocols without spaces (#1172)
* Allow comma-separated websocket subprotocols without spaces

* [CHANGES] Added an entry to CHANGES.md
2019-12-07 20:08:06 +06:00
daxpedda
4921243add Fix rustls build. (#1195) 2019-12-07 16:14:09 +06:00
daxpedda
91b3fcf85c Fix dependency features. (#1196) 2019-12-07 16:13:26 +06:00
Nikolay Kim
1729a52f8b prepare alpha.3 release 2019-12-07 13:00:03 +06:00
Nikolay Kim
ed2f3fe80d use actix-net alpha.3 release 2019-12-07 12:28:26 +06:00
Yuki Okushi
f2ba389496 Merge branch 'master' into master 2019-12-06 16:57:42 +09:00
krircc
439f02b6b1 Update README.md 2019-12-06 14:59:11 +08:00
krircc
e32da08a26 Update README.md 2019-12-06 14:34:14 +08:00
krircc
82110e0927 Update README.md 2019-12-06 14:29:10 +08:00
krircc
7b3354a9ad Update README.md 2019-12-06 14:26:23 +08:00
krircc
5243e8baca Update README.md 2019-12-06 14:23:28 +08:00
krircc
98903028c7 Update README.md 2019-12-06 14:22:29 +08:00
Nikolay Kim
7dd676439c update changes for actix-session 2019-12-06 11:24:25 +06:00
tglman
fbead137f0 feat: add access to UserSession from RequestHead (#1164)
* feat: add access to UserSession from RequestHead

* add test case for session from RequestHead and changes entry for the new feature
2019-12-06 11:21:43 +06:00
Nikolay Kim
205a964d8f upgrade to tokio 0.2 2019-12-05 23:35:43 +06:00
Nikolay Kim
b45c6cd66b replace hashbrown with std hashmap 2019-12-04 18:33:43 +06:00
Nikolay Kim
0015a204aa update version 2019-12-03 19:03:53 +06:00
Nikolay Kim
c7ed6d3428 update version 2019-12-03 16:35:31 +06:00
Nikolay Kim
cf30eafb49 update md 2019-12-03 00:49:12 +06:00
Nikolay Kim
14075ebf7f use released versions of actix-net 2019-12-02 23:33:39 +06:00
Nikolay Kim
068f047dd5 update service factory config 2019-12-02 21:37:13 +06:00
Nikolay Kim
f4c01384ec update to latest actix-net 2019-12-02 17:33:11 +06:00
krircc
b7d44d6c4c Merge pull request #1 from actix/master
git pull
2019-12-01 16:56:42 +08:00
Yuki Okushi
33574403b5 Remove rustls from package.metadata.docs.rs (#1182) 2019-11-28 06:25:21 +06:00
Nikolay Kim
dcc6efa3e6 Merge branch 'master' of github.com:actix/actix-web 2019-11-27 21:08:13 +06:00
Nikolay Kim
56b9f11c98 disable rustls 2019-11-27 21:07:49 +06:00
Folyd
f43a706364 Set name for each generated resource 2019-11-26 19:25:28 +06:00
Nikolay Kim
f2b3dc5625 update examples 2019-11-26 17:16:33 +06:00
Nikolay Kim
f73f97353b refactor ResponseError trait 2019-11-26 16:07:39 +06:00
Nikolay Kim
4dc31aac93 use actix_rt::test for test setup 2019-11-26 11:25:50 +06:00
Nikolay Kim
c1c44a7dd6 upgrade derive_more 2019-11-25 17:59:14 +06:00
Jim Blandy
c5907747ad Remove implementation of Responder for (). Fixes #1108.
Rationale:

- In Rust, one can omit a semicolon after a function's final expression to make
  its value the function's return value. It's common for people to include a
  semicolon after the last expression by mistake - common enough that the Rust
  compiler suggests removing the semicolon when there's a type mismatch between
  the function's signature and body. By implementing Responder for (), Actix makes
  this common mistake a silent error in handler functions.

- Functions returning an empty body should return HTTP status 204 ("No Content"),
  so the current Responder impl for (), which returns status 200 ("OK"), is not
  really what one wants anyway.

- It's not much of a burden to ask handlers to explicitly return
  `HttpResponse::Ok()` if that is what they want; all the examples in the
  documentation do this already.
2019-11-23 21:10:02 +06:00
Martell Malone
525c22de15 fix typos from updating to futures 0.3 2019-11-22 13:25:55 +06:00
175 changed files with 10398 additions and 9963 deletions

67
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: CI
on: [push, pull_request]
env:
VCPKGRS_DYNAMIC: 1
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
toolchain:
- x86_64-pc-windows-msvc
# - i686-pc-windows-msvc
- x86_64-apple-darwin
version:
- stable
- nightly
include:
- toolchain: x86_64-pc-windows-msvc
os: windows-latest
arch: x64
# - toolchain: i686-pc-windows-msvc
# os: windows-latest
# arch: x86
- toolchain: x86_64-apple-darwin
os: macOS-latest
name: ${{ matrix.version }} - ${{ matrix.toolchain }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@master
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-${{ matrix.toolchain }}
default: true
- name: Install OpenSSL
if: matrix.os == 'windows-latest'
run: |
vcpkg integrate install
vcpkg install openssl:${{ matrix.arch }}-windows
- name: check nightly
if: matrix.version == 'nightly'
uses: actions-rs/cargo@v1
with:
command: check
args: --all --benches --bins --examples --tests
- name: check stable
if: matrix.version == 'stable'
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
- name: tests
if: matrix.toolchain != 'x86_64-pc-windows-gnu'
uses: actions-rs/cargo@v1
with:
command: test
args: --all --all-features -- --nocapture

View File

@@ -36,9 +36,12 @@ before_script:
script: script:
- cargo update - cargo update
- cargo check --all --no-default-features - cargo check --all --no-default-features
- cargo test --all-features --all -- --nocapture - |
# - cd actix-http; cargo test --no-default-features --features="rustls" -- --nocapture; cd .. if [[ "$TRAVIS_RUST_VERSION" == "stable" || "$TRAVIS_RUST_VERSION" == "beta" ]]; then
# - cd awc; cargo test --no-default-features --features="rustls" -- --nocapture; cd .. cargo test --all-features --all -- --nocapture
cd actix-http; cargo test --no-default-features --features="rustls" -- --nocapture; cd ..
cd awc; cargo test --no-default-features --features="rustls" -- --nocapture; cd ..
fi
# Upload docs # Upload docs
after_success: after_success:
@@ -51,7 +54,7 @@ after_success:
echo "Uploaded documentation" echo "Uploaded documentation"
fi fi
- | - |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-11-20" ]]; then if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-11-20" ]]; then
taskset -c 0 cargo tarpaulin --out Xml --all --all-features taskset -c 0 cargo tarpaulin --out Xml --all --all-features
bash <(curl -s https://codecov.io/bash) bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage" echo "Uploaded code coverage"

View File

@@ -1,5 +1,68 @@
# Changes # Changes
## [2.0.0] - 2019-12-25
### Changed
* Rename `HttpServer::start()` to `HttpServer::run()`
* Allow to gracefully stop test server via `TestServer::stop()`
* Allow to specify multi-patterns for resources
## [2.0.0-rc] - 2019-12-20
### Changed
* Move `BodyEncoding` to `dev` module #1220
* Allow to set `peer_addr` for TestRequest #1074
* Make web::Data deref to Arc<T> #1214
* Rename `App::register_data()` to `App::app_data()`
* `HttpRequest::app_data<T>()` returns `Option<&T>` instead of `Option<&Data<T>>`
### Fixed
* Fix `AppConfig::secure()` is always false. #1202
## [2.0.0-alpha.6] - 2019-12-15
### Fixed
* Fixed compilation with default features off
## [2.0.0-alpha.5] - 2019-12-13
### Added
* Add test server, `test::start()` and `test::start_with()`
## [2.0.0-alpha.4] - 2019-12-08
### Deleted
* Delete HttpServer::run(), it is not useful witht async/await
## [2.0.0-alpha.3] - 2019-12-07
### Changed
* Migrate to tokio 0.2
## [2.0.0-alpha.1] - 2019-11-22
### Changed
* Migrated to `std::future`
* Remove implementation of `Responder` for `()`. (#1167)
## [1.0.9] - 2019-11-14 ## [1.0.9] - 2019-11-14
### Added ### Added

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "2.0.0-alpha.1" version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md" readme = "README.md"
@@ -12,11 +12,10 @@ categories = ["network-programming", "asynchronous",
"web-programming::http-server", "web-programming::http-server",
"web-programming::websocket"] "web-programming::websocket"]
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018" edition = "2018"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["openssl", "rustls", "brotli", "flate2-zlib", "secure-cookies", "client"] features = ["openssl", "rustls", "compress", "secure-cookies"]
[badges] [badges]
travis-ci = { repository = "actix/actix-web", branch = "master" } travis-ci = { repository = "actix/actix-web", branch = "master" }
@@ -43,76 +42,63 @@ members = [
] ]
[features] [features]
default = ["brotli", "flate2-zlib", "client", "fail"] default = ["compress", "failure"]
# http client # content-encoding support
client = ["awc"] compress = ["actix-http/compress", "awc/compress"]
# brotli encoding, requires c compiler
brotli = ["actix-http/brotli"]
# miniz-sys backend for flate2 crate
flate2-zlib = ["actix-http/flate2-zlib"]
# rust backend for flate2 crate
flate2-rust = ["actix-http/flate2-rust"]
# sessions feature, session require "ring" crate and c compiler # sessions feature, session require "ring" crate and c compiler
secure-cookies = ["actix-http/secure-cookies"] secure-cookies = ["actix-http/secure-cookies"]
fail = ["actix-http/fail"] failure = ["actix-http/failure"]
# openssl # openssl
openssl = ["open-ssl", "actix-server/openssl", "awc/openssl"] openssl = ["actix-tls/openssl", "awc/openssl", "open-ssl"]
# rustls # rustls
# rustls = ["rust-tls", "actix-server/rustls", "awc/rustls"] rustls = ["actix-tls/rustls", "awc/rustls", "rust-tls"]
[dependencies] [dependencies]
actix-codec = "0.2.0-alpha.1" actix-codec = "0.2.0"
actix-service = "1.0.0-alpha.1" actix-service = "1.0.1"
actix-utils = "0.5.0-alpha.1" actix-utils = "1.0.4"
actix-router = "0.1.5" actix-router = "0.2.1"
actix-rt = "1.0.0-alpha.1" actix-rt = "1.0.0"
actix-web-codegen = "0.2.0-alpha.1" actix-server = "1.0.0"
actix-http = "0.3.0-alpha.1" actix-testing = "1.0.0"
actix-server = "0.8.0-alpha.1" actix-macros = "0.1.0"
actix-server-config = "0.3.0-alpha.1" actix-threadpool = "0.3.1"
actix-testing = "0.3.0-alpha.1" actix-tls = "1.0.0"
actix-threadpool = "0.2.0-alpha.1"
awc = { version = "0.3.0-alpha.1", optional = true }
bytes = "0.4" actix-web-codegen = "0.2.0"
derive_more = "0.15.0" actix-http = "1.0.1"
awc = { version = "1.0.1", default-features = false }
bytes = "0.5.3"
derive_more = "0.99.2"
encoding_rs = "0.8" encoding_rs = "0.8"
futures = "0.3.1" futures = "0.3.1"
hashbrown = "0.6.3" fxhash = "0.2.1"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
net2 = "0.2.33" net2 = "0.2.33"
parking_lot = "0.9" pin-project = "0.4.6"
pin-project = "0.4.5" regex = "1.3"
regex = "1.0"
serde = { version = "1.0", features=["derive"] } serde = { version = "1.0", features=["derive"] }
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "0.6.1" serde_urlencoded = "0.6.1"
time = "0.1.42" time = "0.1.42"
url = "2.1" url = "2.1"
open-ssl = { version="0.10", package = "openssl", optional = true }
# ssl support rust-tls = { version = "0.16.0", package = "rustls", optional = true }
open-ssl = { version="0.10", package="openssl", optional = true }
rust-tls = { version = "0.16", package="rustls", optional = true }
[dev-dependencies] [dev-dependencies]
# actix = "0.8.3" actix = "0.9.0"
actix-connect = "0.3.0-alpha.1"
actix-http-test = "0.3.0-alpha.1"
rand = "0.7" rand = "0.7"
env_logger = "0.6" env_logger = "0.6"
serde_derive = "1.0" serde_derive = "1.0"
tokio-timer = "0.3.0-alpha.6"
brotli2 = "0.3.2" brotli2 = "0.3.2"
flate2 = "1.0.2" flate2 = "1.0.13"
[profile.release] [profile.release]
lto = true lto = true
@@ -124,28 +110,9 @@ actix-web = { path = "." }
actix-http = { path = "actix-http" } actix-http = { path = "actix-http" }
actix-http-test = { path = "test-server" } actix-http-test = { path = "test-server" }
actix-web-codegen = { path = "actix-web-codegen" } actix-web-codegen = { path = "actix-web-codegen" }
# actix-web-actors = { path = "actix-web-actors" } actix-cors = { path = "actix-cors" }
actix-identity = { path = "actix-identity" }
actix-session = { path = "actix-session" } actix-session = { path = "actix-session" }
actix-files = { path = "actix-files" } actix-files = { path = "actix-files" }
actix-multipart = { path = "actix-multipart" } actix-multipart = { path = "actix-multipart" }
awc = { path = "awc" } awc = { path = "awc" }
actix-codec = { git = "https://github.com/actix/actix-net.git" }
actix-connect = { git = "https://github.com/actix/actix-net.git" }
actix-rt = { git = "https://github.com/actix/actix-net.git" }
actix-server = { git = "https://github.com/actix/actix-net.git" }
actix-server-config = { git = "https://github.com/actix/actix-net.git" }
actix-service = { git = "https://github.com/actix/actix-net.git" }
actix-testing = { git = "https://github.com/actix/actix-net.git" }
actix-threadpool = { git = "https://github.com/actix/actix-net.git" }
actix-utils = { git = "https://github.com/actix/actix-net.git" }
# actix-codec = { path = "../actix-net/actix-codec" }
# actix-connect = { path = "../actix-net/actix-connect" }
# actix-rt = { path = "../actix-net/actix-rt" }
# actix-server = { path = "../actix-net/actix-server" }
# actix-server-config = { path = "../actix-net/actix-server-config" }
# actix-service = { path = "../actix-net/actix-service" }
# actix-testing = { path = "../actix-net/actix-testing" }
# actix-threadpool = { path = "../actix-net/actix-threadpool" }
# actix-utils = { path = "../actix-net/actix-utils" }

View File

@@ -1,9 +1,22 @@
## 2.0.0 ## 2.0.0
* Sync handlers has been removed. `.to_async()` methtod has been renamed to `.to()` * `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to
`.await` on `run` method result, in that case it awaits server exit.
* `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`.
Stored data is available via `HttpRequest::app_data()` method at runtime.
* Extractor configuration must be registered with `App::app_data()` instead of `App::data()`
* Sync handlers has been removed. `.to_async()` method has been renamed to `.to()`
replace `fn` with `async fn` to convert sync handler to async replace `fn` with `async fn` to convert sync handler to async
* `actix_http_test::TestServer` moved to `actix_web::test` module. To start
test server use `test::start()` or `test_start_with_config()` methods
* `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders
http response.
## 1.0.1 ## 1.0.1
@@ -41,52 +54,52 @@
* Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration * Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration
instead of instead of
```rust ```rust
#[derive(Default)] #[derive(Default)]
struct ExtractorConfig { struct ExtractorConfig {
config: String, config: String,
} }
impl FromRequest for YourExtractor { impl FromRequest for YourExtractor {
type Config = ExtractorConfig; type Config = ExtractorConfig;
type Result = Result<YourExtractor, Error>; type Result = Result<YourExtractor, Error>;
fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result {
println!("use the config: {:?}", cfg.config); println!("use the config: {:?}", cfg.config);
... ...
} }
} }
App::new().resource("/route_with_config", |r| { App::new().resource("/route_with_config", |r| {
r.post().with_config(handler_fn, |cfg| { r.post().with_config(handler_fn, |cfg| {
cfg.0.config = "test".to_string(); cfg.0.config = "test".to_string();
}) })
}) })
``` ```
use the HttpRequest to get the configuration like any other `Data` with `req.app_data::<C>()` and set it with the `data()` method on the `resource` use the HttpRequest to get the configuration like any other `Data` with `req.app_data::<C>()` and set it with the `data()` method on the `resource`
```rust ```rust
#[derive(Default)] #[derive(Default)]
struct ExtractorConfig { struct ExtractorConfig {
config: String, config: String,
} }
impl FromRequest for YourExtractor { impl FromRequest for YourExtractor {
type Error = Error; type Error = Error;
type Future = Result<Self, Self::Error>; type Future = Result<Self, Self::Error>;
type Config = ExtractorConfig; type Config = ExtractorConfig;
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let cfg = req.app_data::<ExtractorConfig>(); let cfg = req.app_data::<ExtractorConfig>();
println!("config data?: {:?}", cfg.unwrap().role); println!("config data?: {:?}", cfg.unwrap().role);
... ...
} }
} }
App::new().service( App::new().service(
resource("/route_with_config") resource("/route_with_config")
.data(ExtractorConfig { .data(ExtractorConfig {
@@ -95,7 +108,7 @@
.route(post().to(handler_fn)), .route(post().to(handler_fn)),
) )
``` ```
* Resource registration. 1.0 version uses generalized resource * Resource registration. 1.0 version uses generalized resource
registration via `.service()` method. registration via `.service()` method.
@@ -386,9 +399,9 @@
* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload * `HttpRequest` does not implement `Stream` anymore. If you need to read request payload
use `HttpMessage::payload()` method. use `HttpMessage::payload()` method.
instead of instead of
```rust ```rust
fn index(req: HttpRequest) -> impl Responder { fn index(req: HttpRequest) -> impl Responder {
req req
@@ -414,8 +427,8 @@
trait uses `&HttpRequest` instead of `&mut HttpRequest`. trait uses `&HttpRequest` instead of `&mut HttpRequest`.
* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. * Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead.
instead of instead of
```rust ```rust
fn index(query: Query<..>, info: Json<MyStruct) -> impl Responder {} fn index(query: Query<..>, info: Json<MyStruct) -> impl Responder {}
@@ -431,7 +444,7 @@
* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value * `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value
* Removed deprecated `HttpServer::threads()`, use * Removed deprecated `HttpServer::threads()`, use
[HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead.
* Renamed `client::ClientConnectorError::Connector` to * Renamed `client::ClientConnectorError::Connector` to
@@ -440,7 +453,7 @@
* `Route::with()` does not return `ExtractorConfig`, to configure * `Route::with()` does not return `ExtractorConfig`, to configure
extractor use `Route::with_config()` extractor use `Route::with_config()`
instead of instead of
```rust ```rust
fn main() { fn main() {
@@ -451,11 +464,11 @@
}); });
} }
``` ```
use use
```rust ```rust
fn main() { fn main() {
let app = App::new().resource("/index.html", |r| { let app = App::new().resource("/index.html", |r| {
r.method(http::Method::GET) r.method(http::Method::GET)
@@ -485,12 +498,12 @@
* `HttpRequest::extensions()` returns read only reference to the request's Extension * `HttpRequest::extensions()` returns read only reference to the request's Extension
`HttpRequest::extensions_mut()` returns mutable reference. `HttpRequest::extensions_mut()` returns mutable reference.
* Instead of * Instead of
`use actix_web::middleware::{ `use actix_web::middleware::{
CookieSessionBackend, CookieSessionError, RequestSession, CookieSessionBackend, CookieSessionError, RequestSession,
Session, SessionBackend, SessionImpl, SessionStorage};` Session, SessionBackend, SessionImpl, SessionStorage};`
use `actix_web::middleware::session` use `actix_web::middleware::session`
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError, `use actix_web::middleware::session{CookieSessionBackend, CookieSessionError,

View File

@@ -1,4 +1,28 @@
# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![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) <div align="center">
<p><h1>Actix web</h1> </p>
<p><strong>Actix web is a small, pragmatic, and extremely fast rust web framework</strong> </p>
<p>
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web)
[![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)
[![Documentation](https://docs.rs/actix-web/badge.svg)](https://docs.rs/actix-web)
[![Download](https://img.shields.io/crates/d/actix-web.svg)](https://crates.io/crates/actix-web)
[![Version](https://img.shields.io/badge/rustc-1.39+-lightgray.svg)](https://blog.rust-lang.org/2019/11/07/Rust-1.39.0.html)
![License](https://img.shields.io/crates/l/actix-web.svg)
</p>
<h3>
<a href="https://actix.rs">Website</a>
<span> | </span>
<a href="https://gitter.im/actix/actix">Chat</a>
<span> | </span>
<a href="https://github.com/actix/examples">Examples</a>
</h3>
</div>
<br>
Actix web is a simple, pragmatic and extremely fast web framework for Rust. Actix web is a simple, pragmatic and extremely fast web framework for Rust.
@@ -15,29 +39,22 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) * Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
* Supports [Actix actor framework](https://github.com/actix/actix) * Supports [Actix actor framework](https://github.com/actix/actix)
## Documentation & community resources
* [User Guide](https://actix.rs/docs/)
* [API Documentation (1.0)](https://docs.rs/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.39 or later
## Example ## Example
```rust ```rust
use actix_web::{web, App, HttpServer, Responder}; use actix_web::{get, web, App, HttpServer, Responder};
#[get("/{id}/{name}/index.html")]
async fn index(info: web::Path<(u32, String)>) -> impl Responder { async fn index(info: web::Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", info.1, info.0) format!("Hello {}! id:{}", info.1, info.0)
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
HttpServer::new( async fn main() -> std::io::Result<()> {
|| App::new().service( HttpServer::new(|| App::new().service(index))
web::resource("/{id}/{name}/index.html").to(index)))
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .run()
.await
} }
``` ```

View File

@@ -1,8 +1,14 @@
# Changes # Changes
## [0.1.1] - unreleased ## [0.2.0] - 2019-12-20
* Bump `derive_more` crate version to 0.15.0 * Release
## [0.2.0-alpha.3] - 2019-12-07
* Migrate to actix-web 2.0.0
* Bump `derive_more` crate version to 0.99.0
## [0.1.0] - 2019-06-15 ## [0.1.0] - 2019-06-15

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-cors" name = "actix-cors"
version = "0.2.0-alpha.1" version = "0.2.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Cross-origin resource sharing (CORS) for Actix applications." description = "Cross-origin resource sharing (CORS) for Actix applications."
readme = "README.md" readme = "README.md"
@@ -17,7 +17,10 @@ name = "actix_cors"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = "2.0.0-alpha.1" actix-web = "2.0.0-rc"
actix-service = "1.0.0-alpha.1" actix-service = "1.0.1"
derive_more = "0.15.0" derive_more = "0.99.2"
futures = "0.3.1" futures = "0.3.1"
[dev-dependencies]
actix-rt = "1.0.0"

View File

@@ -40,6 +40,7 @@
//! //!
//! Cors middleware automatically handle *OPTIONS* preflight request. //! Cors middleware automatically handle *OPTIONS* preflight request.
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::TryFrom;
use std::iter::FromIterator; use std::iter::FromIterator;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
@@ -48,7 +49,7 @@ use actix_service::{Service, Transform};
use actix_web::dev::{RequestHead, ServiceRequest, ServiceResponse}; use actix_web::dev::{RequestHead, ServiceRequest, ServiceResponse};
use actix_web::error::{Error, ResponseError, Result}; use actix_web::error::{Error, ResponseError, Result};
use actix_web::http::header::{self, HeaderName, HeaderValue}; use actix_web::http::header::{self, HeaderName, HeaderValue};
use actix_web::http::{self, HttpTryFrom, Method, StatusCode, Uri}; use actix_web::http::{self, Error as HttpError, Method, StatusCode, Uri};
use actix_web::HttpResponse; use actix_web::HttpResponse;
use derive_more::Display; use derive_more::Display;
use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready}; use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready};
@@ -93,6 +94,10 @@ pub enum CorsError {
} }
impl ResponseError for CorsError { impl ResponseError for CorsError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self).into()) HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self).into())
} }
@@ -270,7 +275,8 @@ impl Cors {
pub fn allowed_methods<U, M>(mut self, methods: U) -> Cors pub fn allowed_methods<U, M>(mut self, methods: U) -> Cors
where where
U: IntoIterator<Item = M>, U: IntoIterator<Item = M>,
Method: HttpTryFrom<M>, Method: TryFrom<M>,
<Method as TryFrom<M>>::Error: Into<HttpError>,
{ {
self.methods = true; self.methods = true;
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
@@ -292,7 +298,8 @@ impl Cors {
/// Set an allowed header /// Set an allowed header
pub fn allowed_header<H>(mut self, header: H) -> Cors pub fn allowed_header<H>(mut self, header: H) -> Cors
where where
HeaderName: HttpTryFrom<H>, HeaderName: TryFrom<H>,
<HeaderName as TryFrom<H>>::Error: Into<HttpError>,
{ {
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
match HeaderName::try_from(header) { match HeaderName::try_from(header) {
@@ -324,7 +331,8 @@ impl Cors {
pub fn allowed_headers<U, H>(mut self, headers: U) -> Cors pub fn allowed_headers<U, H>(mut self, headers: U) -> Cors
where where
U: IntoIterator<Item = H>, U: IntoIterator<Item = H>,
HeaderName: HttpTryFrom<H>, HeaderName: TryFrom<H>,
<HeaderName as TryFrom<H>>::Error: Into<HttpError>,
{ {
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
for h in headers { for h in headers {
@@ -358,7 +366,8 @@ impl Cors {
pub fn expose_headers<U, H>(mut self, headers: U) -> Cors pub fn expose_headers<U, H>(mut self, headers: U) -> Cors
where where
U: IntoIterator<Item = H>, U: IntoIterator<Item = H>,
HeaderName: HttpTryFrom<H>, HeaderName: TryFrom<H>,
<HeaderName as TryFrom<H>>::Error: Into<HttpError>,
{ {
for h in headers { for h in headers {
match HeaderName::try_from(h) { match HeaderName::try_from(h) {
@@ -805,7 +814,7 @@ where
res res
} }
} }
.boxed_local(), .boxed_local(),
) )
} }
} }
@@ -813,143 +822,137 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_service::{service_fn2, Transform}; use actix_service::{fn_service, Transform};
use actix_web::test::{self, block_on, TestRequest}; use actix_web::test::{self, TestRequest};
use super::*; use super::*;
#[test] #[actix_rt::test]
#[should_panic(expected = "Credentials are allowed, but the Origin is set to")] #[should_panic(expected = "Credentials are allowed, but the Origin is set to")]
fn cors_validates_illegal_allow_credentials() { async fn cors_validates_illegal_allow_credentials() {
let _cors = Cors::new().supports_credentials().send_wildcard().finish(); let _cors = Cors::new().supports_credentials().send_wildcard().finish();
} }
#[test] #[actix_rt::test]
fn validate_origin_allows_all_origins() { async fn validate_origin_allows_all_origins() {
block_on(async { let mut cors = Cors::new()
let mut cors = Cors::new() .finish()
.finish() .new_transform(test::ok_service())
.new_transform(test::ok_service()) .await
.await .unwrap();
.unwrap(); let req = TestRequest::with_header("Origin", "https://www.example.com")
let req = TestRequest::with_header("Origin", "https://www.example.com") .to_srv_request();
.to_srv_request();
let resp = test::call_service(&mut cors, req).await; let resp = test::call_service(&mut cors, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
})
} }
#[test] #[actix_rt::test]
fn default() { async fn default() {
block_on(async { let mut cors = Cors::default()
let mut cors = Cors::default() .new_transform(test::ok_service())
.new_transform(test::ok_service()) .await
.await .unwrap();
.unwrap(); let req = TestRequest::with_header("Origin", "https://www.example.com")
let req = TestRequest::with_header("Origin", "https://www.example.com") .to_srv_request();
.to_srv_request();
let resp = test::call_service(&mut cors, req).await; let resp = test::call_service(&mut cors, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
})
} }
#[test] #[actix_rt::test]
fn test_preflight() { async fn test_preflight() {
block_on(async { let mut cors = Cors::new()
let mut cors = Cors::new() .send_wildcard()
.send_wildcard() .max_age(3600)
.max_age(3600) .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) .allowed_header(header::CONTENT_TYPE)
.allowed_header(header::CONTENT_TYPE) .finish()
.finish() .new_transform(test::ok_service())
.new_transform(test::ok_service()) .await
.await .unwrap();
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.header(header::ACCESS_CONTROL_REQUEST_HEADERS, "X-Not-Allowed") .header(header::ACCESS_CONTROL_REQUEST_HEADERS, "X-Not-Allowed")
.to_srv_request(); .to_srv_request();
assert!(cors.inner.validate_allowed_method(req.head()).is_err()); assert!(cors.inner.validate_allowed_method(req.head()).is_err());
assert!(cors.inner.validate_allowed_headers(req.head()).is_err()); assert!(cors.inner.validate_allowed_headers(req.head()).is_err());
let resp = test::call_service(&mut cors, req).await; let resp = test::call_service(&mut cors, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "put") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
assert!(cors.inner.validate_allowed_method(req.head()).is_err()); assert!(cors.inner.validate_allowed_method(req.head()).is_err());
assert!(cors.inner.validate_allowed_headers(req.head()).is_ok()); assert!(cors.inner.validate_allowed_headers(req.head()).is_ok());
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
.header( .header(
header::ACCESS_CONTROL_REQUEST_HEADERS, header::ACCESS_CONTROL_REQUEST_HEADERS,
"AUTHORIZATION,ACCEPT", "AUTHORIZATION,ACCEPT",
) )
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req).await; let resp = test::call_service(&mut cors, req).await;
assert_eq!( assert_eq!(
&b"*"[..], &b"*"[..],
resp.headers() resp.headers()
.get(&header::ACCESS_CONTROL_ALLOW_ORIGIN) .get(&header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
);
assert_eq!(
&b"3600"[..],
resp.headers()
.get(&header::ACCESS_CONTROL_MAX_AGE)
.unwrap()
.as_bytes()
);
let hdr = resp
.headers()
.get(&header::ACCESS_CONTROL_ALLOW_HEADERS)
.unwrap() .unwrap()
.to_str() .as_bytes()
.unwrap(); );
assert!(hdr.contains("authorization")); assert_eq!(
assert!(hdr.contains("accept")); &b"3600"[..],
assert!(hdr.contains("content-type")); resp.headers()
.get(&header::ACCESS_CONTROL_MAX_AGE)
let methods = resp
.headers()
.get(header::ACCESS_CONTROL_ALLOW_METHODS)
.unwrap() .unwrap()
.to_str() .as_bytes()
.unwrap(); );
assert!(methods.contains("POST")); let hdr = resp
assert!(methods.contains("GET")); .headers()
assert!(methods.contains("OPTIONS")); .get(&header::ACCESS_CONTROL_ALLOW_HEADERS)
.unwrap()
.to_str()
.unwrap();
assert!(hdr.contains("authorization"));
assert!(hdr.contains("accept"));
assert!(hdr.contains("content-type"));
Rc::get_mut(&mut cors.inner).unwrap().preflight = false; let methods = resp
.headers()
.get(header::ACCESS_CONTROL_ALLOW_METHODS)
.unwrap()
.to_str()
.unwrap();
assert!(methods.contains("POST"));
assert!(methods.contains("GET"));
assert!(methods.contains("OPTIONS"));
let req = TestRequest::with_header("Origin", "https://www.example.com") Rc::get_mut(&mut cors.inner).unwrap().preflight = false;
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
.header(
header::ACCESS_CONTROL_REQUEST_HEADERS,
"AUTHORIZATION,ACCEPT",
)
.method(Method::OPTIONS)
.to_srv_request();
let resp = test::call_service(&mut cors, req).await; let req = TestRequest::with_header("Origin", "https://www.example.com")
assert_eq!(resp.status(), StatusCode::OK); .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
}) .header(
header::ACCESS_CONTROL_REQUEST_HEADERS,
"AUTHORIZATION,ACCEPT",
)
.method(Method::OPTIONS)
.to_srv_request();
let resp = test::call_service(&mut cors, req).await;
assert_eq!(resp.status(), StatusCode::OK);
} }
// #[test] // #[actix_rt::test]
// #[should_panic(expected = "MissingOrigin")] // #[should_panic(expected = "MissingOrigin")]
// fn test_validate_missing_origin() { // async fn test_validate_missing_origin() {
// let cors = Cors::build() // let cors = Cors::build()
// .allowed_origin("https://www.example.com") // .allowed_origin("https://www.example.com")
// .finish(); // .finish();
@@ -957,257 +960,245 @@ mod tests {
// cors.start(&req).unwrap(); // cors.start(&req).unwrap();
// } // }
#[test] #[actix_rt::test]
#[should_panic(expected = "OriginNotAllowed")] #[should_panic(expected = "OriginNotAllowed")]
fn test_validate_not_allowed_origin() { async fn test_validate_not_allowed_origin() {
block_on(async { let cors = Cors::new()
let cors = Cors::new() .allowed_origin("https://www.example.com")
.allowed_origin("https://www.example.com") .finish()
.finish() .new_transform(test::ok_service())
.new_transform(test::ok_service()) .await
.await .unwrap();
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.unknown.com") let req = TestRequest::with_header("Origin", "https://www.unknown.com")
.method(Method::GET) .method(Method::GET)
.to_srv_request(); .to_srv_request();
cors.inner.validate_origin(req.head()).unwrap(); cors.inner.validate_origin(req.head()).unwrap();
cors.inner.validate_allowed_method(req.head()).unwrap(); cors.inner.validate_allowed_method(req.head()).unwrap();
cors.inner.validate_allowed_headers(req.head()).unwrap(); cors.inner.validate_allowed_headers(req.head()).unwrap();
})
} }
#[test] #[actix_rt::test]
fn test_validate_origin() { async fn test_validate_origin() {
block_on(async { let mut cors = Cors::new()
let mut cors = Cors::new() .allowed_origin("https://www.example.com")
.allowed_origin("https://www.example.com") .finish()
.finish() .new_transform(test::ok_service())
.new_transform(test::ok_service()) .await
.await .unwrap();
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::GET) .method(Method::GET)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req).await; let resp = test::call_service(&mut cors, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
})
} }
#[test] #[actix_rt::test]
fn test_no_origin_response() { async fn test_no_origin_response() {
block_on(async { let mut cors = Cors::new()
let mut cors = Cors::new() .disable_preflight()
.disable_preflight() .finish()
.finish() .new_transform(test::ok_service())
.new_transform(test::ok_service()) .await
.await .unwrap();
.unwrap();
let req = TestRequest::default().method(Method::GET).to_srv_request(); let req = TestRequest::default().method(Method::GET).to_srv_request();
let resp = test::call_service(&mut cors, req).await; let resp = test::call_service(&mut cors, req).await;
assert!(resp assert!(resp
.headers() .headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN) .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.is_none()); .is_none());
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req).await; let resp = test::call_service(&mut cors, req).await;
assert_eq!( assert_eq!(
&b"https://www.example.com"[..], &b"https://www.example.com"[..],
resp.headers() resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
);
})
}
#[test]
fn test_response() {
block_on(async {
let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
let mut cors = Cors::new()
.send_wildcard()
.disable_preflight()
.max_age(3600)
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
.allowed_headers(exposed_headers.clone())
.expose_headers(exposed_headers.clone())
.allowed_header(header::CONTENT_TYPE)
.finish()
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.to_srv_request();
let resp = test::call_service(&mut cors, req).await;
assert_eq!(
&b"*"[..],
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
);
assert_eq!(
&b"Origin"[..],
resp.headers().get(header::VARY).unwrap().as_bytes()
);
{
let headers = resp
.headers()
.get(header::ACCESS_CONTROL_EXPOSE_HEADERS)
.unwrap()
.to_str()
.unwrap()
.split(',')
.map(|s| s.trim())
.collect::<Vec<&str>>();
for h in exposed_headers {
assert!(headers.contains(&h.as_str()));
}
}
let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
let mut cors = Cors::new()
.send_wildcard()
.disable_preflight()
.max_age(3600)
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
.allowed_headers(exposed_headers.clone())
.expose_headers(exposed_headers.clone())
.allowed_header(header::CONTENT_TYPE)
.finish()
.new_transform(service_fn2(|req: ServiceRequest| {
ok(req.into_response(
HttpResponse::Ok().header(header::VARY, "Accept").finish(),
))
}))
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.to_srv_request();
let resp = test::call_service(&mut cors, req).await;
assert_eq!(
&b"Accept, Origin"[..],
resp.headers().get(header::VARY).unwrap().as_bytes()
);
let mut cors = Cors::new()
.disable_vary_header()
.allowed_origin("https://www.example.com")
.allowed_origin("https://www.google.com")
.finish()
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
.to_srv_request();
let resp = test::call_service(&mut cors, req).await;
let origins_str = resp
.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN) .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap() .unwrap()
.as_bytes()
);
}
#[actix_rt::test]
async fn test_response() {
let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
let mut cors = Cors::new()
.send_wildcard()
.disable_preflight()
.max_age(3600)
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
.allowed_headers(exposed_headers.clone())
.expose_headers(exposed_headers.clone())
.allowed_header(header::CONTENT_TYPE)
.finish()
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.to_srv_request();
let resp = test::call_service(&mut cors, req).await;
assert_eq!(
&b"*"[..],
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
);
assert_eq!(
&b"Origin"[..],
resp.headers().get(header::VARY).unwrap().as_bytes()
);
{
let headers = resp
.headers()
.get(header::ACCESS_CONTROL_EXPOSE_HEADERS)
.unwrap()
.to_str() .to_str()
.unwrap(); .unwrap()
.split(',')
.map(|s| s.trim())
.collect::<Vec<&str>>();
assert_eq!("https://www.example.com", origins_str); for h in exposed_headers {
}) assert!(headers.contains(&h.as_str()));
}
}
let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
let mut cors = Cors::new()
.send_wildcard()
.disable_preflight()
.max_age(3600)
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
.allowed_headers(exposed_headers.clone())
.expose_headers(exposed_headers.clone())
.allowed_header(header::CONTENT_TYPE)
.finish()
.new_transform(fn_service(|req: ServiceRequest| {
ok(req.into_response(
HttpResponse::Ok().header(header::VARY, "Accept").finish(),
))
}))
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.to_srv_request();
let resp = test::call_service(&mut cors, req).await;
assert_eq!(
&b"Accept, Origin"[..],
resp.headers().get(header::VARY).unwrap().as_bytes()
);
let mut cors = Cors::new()
.disable_vary_header()
.allowed_origin("https://www.example.com")
.allowed_origin("https://www.google.com")
.finish()
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
.to_srv_request();
let resp = test::call_service(&mut cors, req).await;
let origins_str = resp
.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.to_str()
.unwrap();
assert_eq!("https://www.example.com", origins_str);
} }
#[test] #[actix_rt::test]
fn test_multiple_origins() { async fn test_multiple_origins() {
block_on(async { let mut cors = Cors::new()
let mut cors = Cors::new() .allowed_origin("https://example.com")
.allowed_origin("https://example.com") .allowed_origin("https://example.org")
.allowed_origin("https://example.org") .allowed_methods(vec![Method::GET])
.allowed_methods(vec![Method::GET]) .finish()
.finish() .new_transform(test::ok_service())
.new_transform(test::ok_service()) .await
.await .unwrap();
.unwrap();
let req = TestRequest::with_header("Origin", "https://example.com") let req = TestRequest::with_header("Origin", "https://example.com")
.method(Method::GET) .method(Method::GET)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req).await; let resp = test::call_service(&mut cors, req).await;
assert_eq!( assert_eq!(
&b"https://example.com"[..], &b"https://example.com"[..],
resp.headers() resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN) .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap() .unwrap()
.as_bytes() .as_bytes()
); );
let req = TestRequest::with_header("Origin", "https://example.org") let req = TestRequest::with_header("Origin", "https://example.org")
.method(Method::GET) .method(Method::GET)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req).await; let resp = test::call_service(&mut cors, req).await;
assert_eq!( assert_eq!(
&b"https://example.org"[..], &b"https://example.org"[..],
resp.headers() resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN) .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap() .unwrap()
.as_bytes() .as_bytes()
); );
})
} }
#[test] #[actix_rt::test]
fn test_multiple_origins_preflight() { async fn test_multiple_origins_preflight() {
block_on(async { let mut cors = Cors::new()
let mut cors = Cors::new() .allowed_origin("https://example.com")
.allowed_origin("https://example.com") .allowed_origin("https://example.org")
.allowed_origin("https://example.org") .allowed_methods(vec![Method::GET])
.allowed_methods(vec![Method::GET]) .finish()
.finish() .new_transform(test::ok_service())
.new_transform(test::ok_service()) .await
.await .unwrap();
.unwrap();
let req = TestRequest::with_header("Origin", "https://example.com") let req = TestRequest::with_header("Origin", "https://example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req).await; let resp = test::call_service(&mut cors, req).await;
assert_eq!( assert_eq!(
&b"https://example.com"[..], &b"https://example.com"[..],
resp.headers() resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN) .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap() .unwrap()
.as_bytes() .as_bytes()
); );
let req = TestRequest::with_header("Origin", "https://example.org") let req = TestRequest::with_header("Origin", "https://example.org")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req).await; let resp = test::call_service(&mut cors, req).await;
assert_eq!( assert_eq!(
&b"https://example.org"[..], &b"https://example.org"[..],
resp.headers() resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN) .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap() .unwrap()
.as_bytes() .as_bytes()
); );
})
} }
} }

View File

@@ -1,5 +1,17 @@
# Changes # Changes
## [0.2.1] - 2019-12-22
* Use the same format for file URLs regardless of platforms
## [0.2.0] - 2019-12-20
* Fix BodyEncoding trait import #1220
## [0.2.0-alpha.1] - 2019-12-07
* Migrate to `std::future`
## [0.1.7] - 2019-11-06 ## [0.1.7] - 2019-11-06
* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) * Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151)

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-files" name = "actix-files"
version = "0.2.0-alpha.1" version = "0.2.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for actix web." description = "Static files support for actix web."
readme = "README.md" readme = "README.md"
@@ -18,13 +18,13 @@ name = "actix_files"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = { version = "2.0.0-alpha.1", default-features = false } actix-web = { version = "2.0.0-rc", default-features = false }
actix-http = "0.3.0-alpha.1" actix-http = "1.0.1"
actix-service = "1.0.0-alpha.1" actix-service = "1.0.1"
bitflags = "1" bitflags = "1"
bytes = "0.4" bytes = "0.5.3"
futures = "0.3.1" futures = "0.3.1"
derive_more = "0.15.0" derive_more = "0.99.2"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
mime_guess = "2.0.1" mime_guess = "2.0.1"
@@ -32,4 +32,5 @@ percent-encoding = "2.1"
v_htmlescape = "0.4" v_htmlescape = "0.4"
[dev-dependencies] [dev-dependencies]
actix-web = { version = "2.0.0-alpha.1", features=["openssl"] } actix-rt = "1.0.0"
actix-web = { version = "2.0.0-rc", features=["openssl"] }

View File

@@ -35,7 +35,7 @@ pub enum UriSegmentError {
/// Return `BadRequest` for `UriSegmentError` /// Return `BadRequest` for `UriSegmentError`
impl ResponseError for UriSegmentError { impl ResponseError for UriSegmentError {
fn error_response(&self) -> HttpResponse { fn status_code(&self) -> StatusCode {
HttpResponse::new(StatusCode::BAD_REQUEST) StatusCode::BAD_REQUEST
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -12,11 +12,11 @@ use mime;
use mime_guess::from_path; use mime_guess::from_path;
use actix_http::body::SizedStream; use actix_http::body::SizedStream;
use actix_web::dev::BodyEncoding;
use actix_web::http::header::{ use actix_web::http::header::{
self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue, self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue,
}; };
use actix_web::http::{ContentEncoding, StatusCode}; use actix_web::http::{ContentEncoding, StatusCode};
use actix_web::middleware::BodyEncoding;
use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder};
use futures::future::{ready, Ready}; use futures::future::{ready, Ready};

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-framed" name = "actix-framed"
version = "0.3.0-alpha.1" version = "0.3.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix framed app server" description = "Actix framed app server"
readme = "README.md" readme = "README.md"
@@ -13,27 +13,25 @@ categories = ["network-programming", "asynchronous",
"web-programming::websocket"] "web-programming::websocket"]
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
edition = "2018" edition = "2018"
workspace =".."
[lib] [lib]
name = "actix_framed" name = "actix_framed"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-codec = "0.2.0-alpha.1" actix-codec = "0.2.0"
actix-service = "1.0.0-alpha.1" actix-service = "1.0.1"
actix-router = "0.1.2" actix-router = "0.2.1"
actix-rt = "1.0.0-alpha.1" actix-rt = "1.0.0"
actix-http = "0.3.0-alpha.1" actix-http = "1.0.1"
actix-server-config = "0.3.0-alpha.1"
bytes = "0.4" bytes = "0.5.3"
futures = "0.3.1" futures = "0.3.1"
pin-project = "0.4.6" pin-project = "0.4.6"
log = "0.4" log = "0.4"
[dev-dependencies] [dev-dependencies]
actix-server = { version = "0.8.0-alpha.1", features=["openssl"] } actix-server = "1.0.0"
actix-connect = { version = "0.3.0-alpha.1", features=["openssl"] } actix-connect = { version = "1.0.0", features=["openssl"] }
actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } actix-http-test = { version = "1.0.0", features=["openssl"] }
actix-utils = "0.5.0-alpha.1" actix-utils = "1.0.3"

View File

@@ -1,5 +1,9 @@
# Changes # Changes
## [0.3.0] - 2019-12-25
* Migrate to actix-http 1.0
## [0.2.1] - 2019-07-20 ## [0.2.1] - 2019-07-20
* Remove unneeded actix-utils dependency * Remove unneeded actix-utils dependency

View File

@@ -7,7 +7,6 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::h1::{Codec, SendResponse}; use actix_http::h1::{Codec, SendResponse};
use actix_http::{Error, Request, Response}; use actix_http::{Error, Request, Response};
use actix_router::{Path, Router, Url}; use actix_router::{Path, Router, Url};
use actix_server_config::ServerConfig;
use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use actix_service::{IntoServiceFactory, Service, ServiceFactory};
use futures::future::{ok, FutureExt, LocalBoxFuture}; use futures::future::{ok, FutureExt, LocalBoxFuture};
@@ -97,7 +96,7 @@ where
T: AsyncRead + AsyncWrite + Unpin + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,
S: 'static, S: 'static,
{ {
type Config = ServerConfig; type Config = ();
type Request = (Request, Framed<T, Codec>); type Request = (Request, Framed<T, Codec>);
type Response = (); type Response = ();
type Error = Error; type Error = Error;
@@ -105,7 +104,7 @@ where
type Service = FramedAppService<T, S>; type Service = FramedAppService<T, S>;
type Future = CreateService<T, S>; type Future = CreateService<T, S>;
fn new_service(&self, _: &ServerConfig) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
CreateService { CreateService {
fut: self fut: self
.services .services
@@ -113,7 +112,7 @@ where
.map(|(path, service)| { .map(|(path, service)| {
CreateServiceItem::Future( CreateServiceItem::Future(
Some(path.clone()), Some(path.clone()),
service.new_service(&()), service.new_service(()),
) )
}) })
.collect(), .collect(),

View File

@@ -56,8 +56,8 @@ where
type Service = BoxedHttpService<T::Request>; type Service = BoxedHttpService<T::Request>;
type Future = LocalBoxFuture<'static, Result<Self::Service, ()>>; type Future = LocalBoxFuture<'static, Result<Self::Service, ()>>;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
let fut = self.0.new_service(&()); let fut = self.0.new_service(());
async move { async move {
fut.await.map_err(|_| ()).map(|service| { fut.await.map_err(|_| ()).map(|service| {
@@ -66,7 +66,7 @@ where
service service
}) })
} }
.boxed_local() .boxed_local()
} }
} }

View File

@@ -123,7 +123,9 @@ impl<Io, S> FramedRequest<Io, S> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_http::http::{HeaderName, HeaderValue, HttpTryFrom}; use std::convert::TryFrom;
use actix_http::http::{HeaderName, HeaderValue};
use actix_http::test::{TestBuffer, TestRequest}; use actix_http::test::{TestBuffer, TestRequest};
use super::*; use super::*;

View File

@@ -113,7 +113,7 @@ where
type Service = FramedRouteService<Io, S, F, R, E>; type Service = FramedRouteService<Io, S, F, R, E>;
type Future = Ready<Result<Self::Service, Self::InitError>>; type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
ok(FramedRouteService { ok(FramedRouteService {
handler: self.handler.clone(), handler: self.handler.clone(),
methods: self.methods.clone(), methods: self.methods.clone(),
@@ -154,6 +154,6 @@ where
} }
Ok(()) Ok(())
} }
.boxed_local() .boxed_local()
} }
} }

View File

@@ -33,7 +33,7 @@ impl<T, C> ServiceFactory for VerifyWebSockets<T, C> {
type Service = VerifyWebSockets<T, C>; type Service = VerifyWebSockets<T, C>;
type Future = Ready<Result<Self::Service, Self::InitError>>; type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &C) -> Self::Future { fn new_service(&self, _: C) -> Self::Future {
ok(VerifyWebSockets { _t: PhantomData }) ok(VerifyWebSockets { _t: PhantomData })
} }
} }
@@ -83,7 +83,7 @@ where
type Service = SendError<T, R, E, C>; type Service = SendError<T, R, E, C>;
type Future = Ready<Result<Self::Service, Self::InitError>>; type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &C) -> Self::Future { fn new_service(&self, _: C) -> Self::Future {
ok(SendError(PhantomData)) ok(SendError(PhantomData))
} }
} }

View File

@@ -1,13 +1,13 @@
//! Various helpers for Actix applications to use during testing. //! Various helpers for Actix applications to use during testing.
use std::convert::TryFrom;
use std::future::Future; use std::future::Future;
use actix_codec::Framed; use actix_codec::Framed;
use actix_http::h1::Codec; use actix_http::h1::Codec;
use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue};
use actix_http::http::{HttpTryFrom, Method, Uri, Version}; use actix_http::http::{Error as HttpError, Method, Uri, Version};
use actix_http::test::{TestBuffer, TestRequest as HttpTestRequest}; use actix_http::test::{TestBuffer, TestRequest as HttpTestRequest};
use actix_router::{Path, Url}; use actix_router::{Path, Url};
use actix_rt::Runtime;
use crate::{FramedRequest, State}; use crate::{FramedRequest, State};
@@ -42,7 +42,8 @@ impl TestRequest<()> {
/// Create TestRequest and set header /// Create TestRequest and set header
pub fn with_header<K, V>(key: K, value: V) -> Self pub fn with_header<K, V>(key: K, value: V) -> Self
where where
HeaderName: HttpTryFrom<K>, HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
Self::default().header(key, value) Self::default().header(key, value)
@@ -97,7 +98,8 @@ impl<S> TestRequest<S> {
/// Set a header /// Set a header
pub fn header<K, V>(mut self, key: K, value: V) -> Self pub fn header<K, V>(mut self, key: K, value: V) -> Self
where where
HeaderName: HttpTryFrom<K>, HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
self.req.header(key, value); self.req.header(key, value);
@@ -119,13 +121,12 @@ impl<S> TestRequest<S> {
} }
/// This method generates `FramedRequest` instance and executes async handler /// This method generates `FramedRequest` instance and executes async handler
pub fn run<F, R, I, E>(self, f: F) -> Result<I, E> pub async fn run<F, R, I, E>(self, f: F) -> Result<I, E>
where where
F: FnOnce(FramedRequest<TestBuffer, S>) -> R, F: FnOnce(FramedRequest<TestBuffer, S>) -> R,
R: Future<Output = Result<I, E>>, R: Future<Output = Result<I, E>>,
{ {
let mut rt = Runtime::new().unwrap(); f(self.finish()).await
rt.block_on(f(self.finish()))
} }
} }

View File

@@ -1,9 +1,9 @@
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_http::{body, http::StatusCode, ws, Error, HttpService, Response}; use actix_http::{body, http::StatusCode, ws, Error, HttpService, Response};
use actix_http_test::{block_on, TestServer}; use actix_http_test::test_server;
use actix_service::{pipeline_factory, IntoServiceFactory, ServiceFactory}; use actix_service::{pipeline_factory, IntoServiceFactory, ServiceFactory};
use actix_utils::framed::FramedTransport; use actix_utils::framed::Dispatcher;
use bytes::{Bytes, BytesMut}; use bytes::Bytes;
use futures::{future, SinkExt, StreamExt}; use futures::{future, SinkExt, StreamExt};
use actix_framed::{FramedApp, FramedRequest, FramedRoute, SendError, VerifyWebSockets}; use actix_framed::{FramedApp, FramedRequest, FramedRoute, SendError, VerifyWebSockets};
@@ -18,7 +18,7 @@ async fn ws_service<T: AsyncRead + AsyncWrite>(
.send((res, body::BodySize::None).into()) .send((res, body::BodySize::None).into())
.await .await
.unwrap(); .unwrap();
FramedTransport::new(framed.into_framed(ws::Codec::new()), service) Dispatcher::new(framed.into_framed(ws::Codec::new()), service)
.await .await
.unwrap(); .unwrap();
@@ -29,135 +29,131 @@ async fn service(msg: ws::Frame) -> Result<ws::Message, Error> {
let msg = match msg { let msg = match msg {
ws::Frame::Ping(msg) => ws::Message::Pong(msg), ws::Frame::Ping(msg) => ws::Message::Pong(msg),
ws::Frame::Text(text) => { ws::Frame::Text(text) => {
ws::Message::Text(String::from_utf8_lossy(&text.unwrap()).to_string()) ws::Message::Text(String::from_utf8_lossy(&text).to_string())
} }
ws::Frame::Binary(bin) => ws::Message::Binary(bin.unwrap().freeze()), ws::Frame::Binary(bin) => ws::Message::Binary(bin),
ws::Frame::Close(reason) => ws::Message::Close(reason), ws::Frame::Close(reason) => ws::Message::Close(reason),
_ => panic!(), _ => panic!(),
}; };
Ok(msg) Ok(msg)
} }
#[test] #[actix_rt::test]
fn test_simple() { async fn test_simple() {
block_on(async { let mut srv = test_server(|| {
let mut srv = TestServer::start(|| { HttpService::build()
HttpService::build() .upgrade(
.upgrade( FramedApp::new().service(FramedRoute::get("/index.html").to(ws_service)),
FramedApp::new()
.service(FramedRoute::get("/index.html").to(ws_service)),
)
.finish(|_| future::ok::<_, Error>(Response::NotFound()))
});
assert!(srv.ws_at("/test").await.is_err());
// client service
let mut framed = srv.ws_at("/index.html").await.unwrap();
framed
.send(ws::Message::Text("text".to_string()))
.await
.unwrap();
let (item, mut framed) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Text(Some(BytesMut::from("text")))
);
framed
.send(ws::Message::Binary("text".into()))
.await
.unwrap();
let (item, mut framed) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))
);
framed.send(ws::Message::Ping("text".into())).await.unwrap();
let (item, mut framed) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Pong("text".to_string().into())
);
framed
.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
.await
.unwrap();
let (item, _) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Close(Some(ws::CloseCode::Normal.into()))
);
})
}
#[test]
fn test_service() {
block_on(async {
let mut srv = TestServer::start(|| {
pipeline_factory(actix_http::h1::OneRequest::new().map_err(|_| ())).and_then(
pipeline_factory(
pipeline_factory(VerifyWebSockets::default())
.then(SendError::default())
.map_err(|_| ()),
)
.and_then(
FramedApp::new()
.service(FramedRoute::get("/index.html").to(ws_service))
.into_factory()
.map_err(|_| ()),
),
) )
}); .finish(|_| future::ok::<_, Error>(Response::NotFound()))
.tcp()
});
// non ws request assert!(srv.ws_at("/test").await.is_err());
let res = srv.get("/index.html").send().await.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
// not found // client service
assert!(srv.ws_at("/test").await.is_err()); let mut framed = srv.ws_at("/index.html").await.unwrap();
framed
.send(ws::Message::Text("text".to_string()))
.await
.unwrap();
let (item, mut framed) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Text(Bytes::from_static(b"text"))
);
// client service framed
let mut framed = srv.ws_at("/index.html").await.unwrap(); .send(ws::Message::Binary("text".into()))
framed .await
.send(ws::Message::Text("text".to_string())) .unwrap();
.await let (item, mut framed) = framed.into_future().await;
.unwrap(); assert_eq!(
let (item, mut framed) = framed.into_future().await; item.unwrap().unwrap(),
assert_eq!( ws::Frame::Binary(Bytes::from_static(b"text"))
item.unwrap().unwrap(), );
ws::Frame::Text(Some(BytesMut::from("text")))
);
framed framed.send(ws::Message::Ping("text".into())).await.unwrap();
.send(ws::Message::Binary("text".into())) let (item, mut framed) = framed.into_future().await;
.await assert_eq!(
.unwrap(); item.unwrap().unwrap(),
let (item, mut framed) = framed.into_future().await; ws::Frame::Pong("text".to_string().into())
assert_eq!( );
item.unwrap().unwrap(),
ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))
);
framed.send(ws::Message::Ping("text".into())).await.unwrap(); framed
let (item, mut framed) = framed.into_future().await; .send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
assert_eq!( .await
item.unwrap().unwrap(), .unwrap();
ws::Frame::Pong("text".to_string().into())
);
framed let (item, _) = framed.into_future().await;
.send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) assert_eq!(
.await item.unwrap().unwrap(),
.unwrap(); ws::Frame::Close(Some(ws::CloseCode::Normal.into()))
);
let (item, _) = framed.into_future().await; }
assert_eq!(
item.unwrap().unwrap(), #[actix_rt::test]
ws::Frame::Close(Some(ws::CloseCode::Normal.into())) async fn test_service() {
); let mut srv = test_server(|| {
}) pipeline_factory(actix_http::h1::OneRequest::new().map_err(|_| ())).and_then(
pipeline_factory(
pipeline_factory(VerifyWebSockets::default())
.then(SendError::default())
.map_err(|_| ()),
)
.and_then(
FramedApp::new()
.service(FramedRoute::get("/index.html").to(ws_service))
.into_factory()
.map_err(|_| ()),
),
)
});
// non ws request
let res = srv.get("/index.html").send().await.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
// not found
assert!(srv.ws_at("/test").await.is_err());
// client service
let mut framed = srv.ws_at("/index.html").await.unwrap();
framed
.send(ws::Message::Text("text".to_string()))
.await
.unwrap();
let (item, mut framed) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Text(Bytes::from_static(b"text"))
);
framed
.send(ws::Message::Binary("text".into()))
.await
.unwrap();
let (item, mut framed) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Binary(Bytes::from_static(b"text"))
);
framed.send(ws::Message::Ping("text".into())).await.unwrap();
let (item, mut framed) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Pong("text".to_string().into())
);
framed
.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
.await
.unwrap();
let (item, _) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Close(Some(ws::CloseCode::Normal.into()))
);
} }

View File

@@ -1,5 +1,54 @@
# Changes # Changes
## [1.0.1] - 2019-12-20
### Fixed
* Poll upgrade service's readiness from HTTP service handlers
* Replace brotli with brotli2 #1224
## [1.0.0] - 2019-12-13
### Added
* Add websockets continuation frame support
### Changed
* Replace `flate2-xxx` features with `compress`
## [1.0.0-alpha.5] - 2019-12-09
### Fixed
* Check `Upgrade` service readiness before calling it
* Fix buffer remaining capacity calcualtion
### Changed
* Websockets: Ping and Pong should have binary data #1049
## [1.0.0-alpha.4] - 2019-12-08
### Added
* Add impl ResponseBuilder for Error
### Changed
* Use rust based brotli compression library
## [1.0.0-alpha.3] - 2019-12-07
### Changed
* Migrate to tokio 0.2
* Migrate to `std::future`
## [0.2.11] - 2019-11-06 ## [0.2.11] - 2019-11-06
### Added ### Added

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "0.3.0-alpha.1" version = "1.0.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http primitives" description = "Actix http primitives"
readme = "README.md" readme = "README.md"
@@ -13,10 +13,9 @@ categories = ["network-programming", "asynchronous",
"web-programming::websocket"] "web-programming::websocket"]
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
edition = "2018" edition = "2018"
workspace = ".."
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["openssl", "fail", "brotli", "flate2-zlib", "secure-cookies"] features = ["openssl", "rustls", "failure", "compress", "secure-cookies"]
[lib] [lib]
name = "actix_http" name = "actix_http"
@@ -26,56 +25,53 @@ path = "src/lib.rs"
default = [] default = []
# openssl # openssl
openssl = ["open-ssl", "actix-connect/openssl", "tokio-openssl"] openssl = ["actix-tls/openssl", "actix-connect/openssl"]
# rustls support # rustls support
# rustls = ["rust-tls", "webpki-roots", "actix-connect/rustls"] rustls = ["actix-tls/rustls", "actix-connect/rustls"]
# brotli encoding, requires c compiler # enable compressison support
brotli = ["brotli2"] compress = ["flate2", "brotli2"]
# miniz-sys backend for flate2 crate
flate2-zlib = ["flate2/miniz-sys"]
# rust backend for flate2 crate
flate2-rust = ["flate2/rust_backend"]
# failure integration. actix does not use failure anymore # failure integration. actix does not use failure anymore
fail = ["failure"] failure = ["fail-ure"]
# support for secure cookies # support for secure cookies
secure-cookies = ["ring"] secure-cookies = ["ring"]
[dependencies] [dependencies]
actix-service = "1.0.0-alpha.1" actix-service = "1.0.1"
actix-codec = "0.2.0-alpha.1" actix-codec = "0.2.0"
actix-connect = "1.0.0-alpha.1" actix-connect = "1.0.1"
actix-utils = "0.5.0-alpha.1" actix-utils = "1.0.3"
actix-server-config = "0.3.0-alpha.1" actix-rt = "1.0.0"
actix-threadpool = "0.2.0-alpha.1" actix-threadpool = "0.3.1"
actix-tls = { version = "1.0.0", optional = true }
base64 = "0.10" base64 = "0.11"
bitflags = "1.0" bitflags = "1.2"
bytes = "0.4" bytes = "0.5.3"
copyless = "0.1.4" copyless = "0.1.4"
chrono = "0.4.6" chrono = "0.4.6"
derive_more = "0.15.0" derive_more = "0.99.2"
either = "1.5.2" either = "1.5.3"
encoding_rs = "0.8" encoding_rs = "0.8"
futures = "0.3.1" futures-core = "0.3.1"
hashbrown = "0.6.3" futures-util = "0.3.1"
h2 = "0.2.0-alpha.3" futures-channel = "0.3.1"
http = "0.1.17" fxhash = "0.2.1"
h2 = "0.2.1"
http = "0.2.0"
httparse = "1.3" httparse = "1.3"
indexmap = "1.2" indexmap = "1.3"
lazy_static = "1.0" lazy_static = "1.4"
language-tags = "0.2" language-tags = "0.2"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
percent-encoding = "2.1" percent-encoding = "2.1"
pin-project = "0.4.5" pin-project = "0.4.6"
rand = "0.7" rand = "0.7"
regex = "1.0" regex = "1.3"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
sha1 = "0.6" sha1 = "0.6"
@@ -83,33 +79,23 @@ slab = "0.4"
serde_urlencoded = "0.6.1" serde_urlencoded = "0.6.1"
time = "0.1.42" time = "0.1.42"
tokio = "=0.2.0-alpha.6"
tokio-io = "=0.2.0-alpha.6"
tokio-net = "=0.2.0-alpha.6"
tokio-timer = "0.3.0-alpha.6"
tokio-executor = "=0.2.0-alpha.6"
trust-dns-resolver = { version="0.18.0-alpha.1", default-features = false }
# for secure cookie # for secure cookie
ring = { version = "0.16.9", optional = true } ring = { version = "0.16.9", optional = true }
# compression # compression
brotli2 = { version="0.3.2", optional = true } brotli2 = { version="0.3.2", optional = true }
flate2 = { version="1.0.7", optional = true, default-features = false } flate2 = { version = "1.0.13", optional = true }
# optional deps # optional deps
failure = { version = "0.1.5", optional = true } fail-ure = { version = "0.1.5", package="failure", optional = true }
open-ssl = { version="0.10", package="openssl", optional = true }
tokio-openssl = { version = "0.4.0-alpha.6", optional = true }
rust-tls = { version = "0.16.0", package="rustls", optional = true }
webpki-roots = { version = "0.18", optional = true }
[dev-dependencies] [dev-dependencies]
actix-rt = "1.0.0-alpha.1" actix-server = "1.0.0"
actix-server = { version = "0.8.0-alpha.1", features=["openssl"] } actix-connect = { version = "1.0.0", features=["openssl"] }
actix-connect = { version = "1.0.0-alpha.1", features=["openssl"] } actix-http-test = { version = "1.0.0", features=["openssl"] }
actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } actix-tls = { version = "1.0.0", features=["openssl"] }
futures = "0.3.1"
env_logger = "0.6" env_logger = "0.6"
serde_derive = "1.0" serde_derive = "1.0"
open-ssl = { version="0.10", package="openssl" } open-ssl = { version="0.10", package = "openssl" }
rust-tls = { version="0.16", package = "rustls" }

View File

@@ -34,6 +34,7 @@ fn main() -> io::Result<()> {
) )
} }
}) })
.tcp()
})? })?
.run() .run()
} }

View File

@@ -25,7 +25,7 @@ fn main() -> io::Result<()> {
Server::build() Server::build()
.bind("echo", "127.0.0.1:8080", || { .bind("echo", "127.0.0.1:8080", || {
HttpService::build().finish(handle_request) HttpService::build().finish(handle_request).tcp()
})? })?
.run() .run()
} }

View File

@@ -21,6 +21,7 @@ fn main() -> io::Result<()> {
res.header("x-head", HeaderValue::from_static("dummy value!")); res.header("x-head", HeaderValue::from_static("dummy value!"));
future::ok::<_, ()>(res.body("Hello world!")) future::ok::<_, ()>(res.body("Hello world!"))
}) })
.tcp()
})? })?
.run() .run()
} }

View File

@@ -4,7 +4,7 @@ use std::task::{Context, Poll};
use std::{fmt, mem}; use std::{fmt, mem};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::Stream; use futures_core::Stream;
use pin_project::{pin_project, project}; use pin_project::{pin_project, project};
use crate::error::Error; use crate::error::Error;
@@ -35,7 +35,7 @@ impl BodySize {
pub trait MessageBody { pub trait MessageBody {
fn size(&self) -> BodySize; fn size(&self) -> BodySize;
fn poll_next(&mut self, cx: &mut Context) -> Poll<Option<Result<Bytes, Error>>>; fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>>;
} }
impl MessageBody for () { impl MessageBody for () {
@@ -43,7 +43,7 @@ impl MessageBody for () {
BodySize::Empty BodySize::Empty
} }
fn poll_next(&mut self, _: &mut Context) -> Poll<Option<Result<Bytes, Error>>> { fn poll_next(&mut self, _: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
Poll::Ready(None) Poll::Ready(None)
} }
} }
@@ -53,7 +53,7 @@ impl<T: MessageBody> MessageBody for Box<T> {
self.as_ref().size() self.as_ref().size()
} }
fn poll_next(&mut self, cx: &mut Context) -> Poll<Option<Result<Bytes, Error>>> { fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
self.as_mut().poll_next(cx) self.as_mut().poll_next(cx)
} }
} }
@@ -97,7 +97,7 @@ impl<B: MessageBody> MessageBody for ResponseBody<B> {
} }
} }
fn poll_next(&mut self, cx: &mut Context) -> Poll<Option<Result<Bytes, Error>>> { fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
match self { match self {
ResponseBody::Body(ref mut body) => body.poll_next(cx), ResponseBody::Body(ref mut body) => body.poll_next(cx),
ResponseBody::Other(ref mut body) => body.poll_next(cx), ResponseBody::Other(ref mut body) => body.poll_next(cx),
@@ -109,7 +109,10 @@ impl<B: MessageBody> Stream for ResponseBody<B> {
type Item = Result<Bytes, Error>; type Item = Result<Bytes, Error>;
#[project] #[project]
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> { fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
#[project] #[project]
match self.project() { match self.project() {
ResponseBody::Body(ref mut body) => body.poll_next(cx), ResponseBody::Body(ref mut body) => body.poll_next(cx),
@@ -133,7 +136,7 @@ pub enum Body {
impl Body { impl Body {
/// Create body from slice (copy) /// Create body from slice (copy)
pub fn from_slice(s: &[u8]) -> Body { pub fn from_slice(s: &[u8]) -> Body {
Body::Bytes(Bytes::from(s)) Body::Bytes(Bytes::copy_from_slice(s))
} }
/// Create body from generic message body. /// Create body from generic message body.
@@ -152,7 +155,7 @@ impl MessageBody for Body {
} }
} }
fn poll_next(&mut self, cx: &mut Context) -> Poll<Option<Result<Bytes, Error>>> { fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
match self { match self {
Body::None => Poll::Ready(None), Body::None => Poll::Ready(None),
Body::Empty => Poll::Ready(None), Body::Empty => Poll::Ready(None),
@@ -190,7 +193,7 @@ impl PartialEq for Body {
} }
impl fmt::Debug for Body { impl fmt::Debug for Body {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
Body::None => write!(f, "Body::None"), Body::None => write!(f, "Body::None"),
Body::Empty => write!(f, "Body::Empty"), Body::Empty => write!(f, "Body::Empty"),
@@ -226,7 +229,7 @@ impl From<String> for Body {
impl<'a> From<&'a String> for Body { impl<'a> From<&'a String> for Body {
fn from(s: &'a String) -> Body { fn from(s: &'a String) -> Body {
Body::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s))) Body::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&s)))
} }
} }
@@ -272,7 +275,7 @@ impl MessageBody for Bytes {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
fn poll_next(&mut self, _: &mut Context) -> Poll<Option<Result<Bytes, Error>>> { fn poll_next(&mut self, _: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
if self.is_empty() { if self.is_empty() {
Poll::Ready(None) Poll::Ready(None)
} else { } else {
@@ -286,7 +289,7 @@ impl MessageBody for BytesMut {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
fn poll_next(&mut self, _: &mut Context) -> Poll<Option<Result<Bytes, Error>>> { fn poll_next(&mut self, _: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
if self.is_empty() { if self.is_empty() {
Poll::Ready(None) Poll::Ready(None)
} else { } else {
@@ -300,7 +303,7 @@ impl MessageBody for &'static str {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
fn poll_next(&mut self, _: &mut Context) -> Poll<Option<Result<Bytes, Error>>> { fn poll_next(&mut self, _: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
if self.is_empty() { if self.is_empty() {
Poll::Ready(None) Poll::Ready(None)
} else { } else {
@@ -316,7 +319,7 @@ impl MessageBody for &'static [u8] {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
fn poll_next(&mut self, _: &mut Context) -> Poll<Option<Result<Bytes, Error>>> { fn poll_next(&mut self, _: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
if self.is_empty() { if self.is_empty() {
Poll::Ready(None) Poll::Ready(None)
} else { } else {
@@ -330,7 +333,7 @@ impl MessageBody for Vec<u8> {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
fn poll_next(&mut self, _: &mut Context) -> Poll<Option<Result<Bytes, Error>>> { fn poll_next(&mut self, _: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
if self.is_empty() { if self.is_empty() {
Poll::Ready(None) Poll::Ready(None)
} else { } else {
@@ -344,7 +347,7 @@ impl MessageBody for String {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
fn poll_next(&mut self, _: &mut Context) -> Poll<Option<Result<Bytes, Error>>> { fn poll_next(&mut self, _: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
if self.is_empty() { if self.is_empty() {
Poll::Ready(None) Poll::Ready(None)
} else { } else {
@@ -386,7 +389,7 @@ where
BodySize::Stream BodySize::Stream
} }
fn poll_next(&mut self, cx: &mut Context) -> Poll<Option<Result<Bytes, Error>>> { fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
unsafe { Pin::new_unchecked(self) } unsafe { Pin::new_unchecked(self) }
.project() .project()
.stream .stream
@@ -421,7 +424,7 @@ where
BodySize::Sized64(self.size) BodySize::Sized64(self.size)
} }
fn poll_next(&mut self, cx: &mut Context) -> Poll<Option<Result<Bytes, Error>>> { fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
unsafe { Pin::new_unchecked(self) } unsafe { Pin::new_unchecked(self) }
.project() .project()
.stream .stream
@@ -432,8 +435,7 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use actix_http_test::block_on; use futures_util::future::poll_fn;
use futures::future::{lazy, poll_fn};
impl Body { impl Body {
pub(crate) fn get_ref(&self) -> &[u8] { pub(crate) fn get_ref(&self) -> &[u8] {
@@ -453,21 +455,21 @@ mod tests {
} }
} }
#[test] #[actix_rt::test]
fn test_static_str() { async fn test_static_str() {
assert_eq!(Body::from("").size(), BodySize::Sized(0)); assert_eq!(Body::from("").size(), BodySize::Sized(0));
assert_eq!(Body::from("test").size(), BodySize::Sized(4)); assert_eq!(Body::from("test").size(), BodySize::Sized(4));
assert_eq!(Body::from("test").get_ref(), b"test"); assert_eq!(Body::from("test").get_ref(), b"test");
assert_eq!("test".size(), BodySize::Sized(4)); assert_eq!("test".size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
block_on(poll_fn(|cx| "test".poll_next(cx))).unwrap().ok(), poll_fn(|cx| "test".poll_next(cx)).await.unwrap().ok(),
Some(Bytes::from("test")) Some(Bytes::from("test"))
); );
} }
#[test] #[actix_rt::test]
fn test_static_bytes() { async fn test_static_bytes() {
assert_eq!(Body::from(b"test".as_ref()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b"test".as_ref()).size(), BodySize::Sized(4));
assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test"); assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test");
assert_eq!( assert_eq!(
@@ -478,55 +480,57 @@ mod tests {
assert_eq!((&b"test"[..]).size(), BodySize::Sized(4)); assert_eq!((&b"test"[..]).size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
block_on(poll_fn(|cx| (&b"test"[..]).poll_next(cx))) poll_fn(|cx| (&b"test"[..]).poll_next(cx))
.await
.unwrap() .unwrap()
.ok(), .ok(),
Some(Bytes::from("test")) Some(Bytes::from("test"))
); );
} }
#[test] #[actix_rt::test]
fn test_vec() { async fn test_vec() {
assert_eq!(Body::from(Vec::from("test")).size(), BodySize::Sized(4)); assert_eq!(Body::from(Vec::from("test")).size(), BodySize::Sized(4));
assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test"); assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test");
assert_eq!(Vec::from("test").size(), BodySize::Sized(4)); assert_eq!(Vec::from("test").size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
block_on(poll_fn(|cx| Vec::from("test").poll_next(cx))) poll_fn(|cx| Vec::from("test").poll_next(cx))
.await
.unwrap() .unwrap()
.ok(), .ok(),
Some(Bytes::from("test")) Some(Bytes::from("test"))
); );
} }
#[test] #[actix_rt::test]
fn test_bytes() { async fn test_bytes() {
let mut b = Bytes::from("test"); let mut b = Bytes::from("test");
assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4));
assert_eq!(Body::from(b.clone()).get_ref(), b"test"); assert_eq!(Body::from(b.clone()).get_ref(), b"test");
assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!(b.size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
block_on(poll_fn(|cx| b.poll_next(cx))).unwrap().ok(), poll_fn(|cx| b.poll_next(cx)).await.unwrap().ok(),
Some(Bytes::from("test")) Some(Bytes::from("test"))
); );
} }
#[test] #[actix_rt::test]
fn test_bytes_mut() { async fn test_bytes_mut() {
let mut b = BytesMut::from("test"); let mut b = BytesMut::from("test");
assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4));
assert_eq!(Body::from(b.clone()).get_ref(), b"test"); assert_eq!(Body::from(b.clone()).get_ref(), b"test");
assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!(b.size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
block_on(poll_fn(|cx| b.poll_next(cx))).unwrap().ok(), poll_fn(|cx| b.poll_next(cx)).await.unwrap().ok(),
Some(Bytes::from("test")) Some(Bytes::from("test"))
); );
} }
#[test] #[actix_rt::test]
fn test_string() { async fn test_string() {
let mut b = "test".to_owned(); let mut b = "test".to_owned();
assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4));
assert_eq!(Body::from(b.clone()).get_ref(), b"test"); assert_eq!(Body::from(b.clone()).get_ref(), b"test");
@@ -535,26 +539,26 @@ mod tests {
assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!(b.size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
block_on(poll_fn(|cx| b.poll_next(cx))).unwrap().ok(), poll_fn(|cx| b.poll_next(cx)).await.unwrap().ok(),
Some(Bytes::from("test")) Some(Bytes::from("test"))
); );
} }
#[test] #[actix_rt::test]
fn test_unit() { async fn test_unit() {
assert_eq!(().size(), BodySize::Empty); assert_eq!(().size(), BodySize::Empty);
assert!(block_on(poll_fn(|cx| ().poll_next(cx))).is_none()); assert!(poll_fn(|cx| ().poll_next(cx)).await.is_none());
} }
#[test] #[actix_rt::test]
fn test_box() { async fn test_box() {
let mut val = Box::new(()); let mut val = Box::new(());
assert_eq!(val.size(), BodySize::Empty); assert_eq!(val.size(), BodySize::Empty);
assert!(block_on(poll_fn(|cx| val.poll_next(cx))).is_none()); assert!(poll_fn(|cx| val.poll_next(cx)).await.is_none());
} }
#[test] #[actix_rt::test]
fn test_body_eq() { async fn test_body_eq() {
assert!(Body::None == Body::None); assert!(Body::None == Body::None);
assert!(Body::None != Body::Empty); assert!(Body::None != Body::Empty);
assert!(Body::Empty == Body::Empty); assert!(Body::Empty == Body::Empty);
@@ -566,15 +570,15 @@ mod tests {
assert!(Body::Bytes(Bytes::from_static(b"1")) != Body::None); assert!(Body::Bytes(Bytes::from_static(b"1")) != Body::None);
} }
#[test] #[actix_rt::test]
fn test_body_debug() { async fn test_body_debug() {
assert!(format!("{:?}", Body::None).contains("Body::None")); assert!(format!("{:?}", Body::None).contains("Body::None"));
assert!(format!("{:?}", Body::Empty).contains("Body::Empty")); assert!(format!("{:?}", Body::Empty).contains("Body::Empty"));
assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains("1")); assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains("1"));
} }
#[test] #[actix_rt::test]
fn test_serde_json() { async fn test_serde_json() {
use serde_json::json; use serde_json::json;
assert_eq!( assert_eq!(
Body::from(serde_json::Value::String("test".into())).size(), Body::from(serde_json::Value::String("test".into())).size(),

View File

@@ -1,9 +1,8 @@
use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc; use std::rc::Rc;
use std::{fmt, net};
use actix_codec::Framed; use actix_codec::Framed;
use actix_server_config::ServerConfig as SrvConfig;
use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use actix_service::{IntoServiceFactory, Service, ServiceFactory};
use crate::body::MessageBody; use crate::body::MessageBody;
@@ -24,6 +23,8 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
keep_alive: KeepAlive, keep_alive: KeepAlive,
client_timeout: u64, client_timeout: u64,
client_disconnect: u64, client_disconnect: u64,
secure: bool,
local_addr: Option<net::SocketAddr>,
expect: X, expect: X,
upgrade: Option<U>, upgrade: Option<U>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
@@ -32,7 +33,7 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler<T>> impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler<T>>
where where
S: ServiceFactory<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
@@ -43,6 +44,8 @@ where
keep_alive: KeepAlive::Timeout(5), keep_alive: KeepAlive::Timeout(5),
client_timeout: 5000, client_timeout: 5000,
client_disconnect: 0, client_disconnect: 0,
secure: false,
local_addr: None,
expect: ExpectHandler, expect: ExpectHandler,
upgrade: None, upgrade: None,
on_connect: None, on_connect: None,
@@ -53,19 +56,15 @@ where
impl<T, S, X, U> HttpServiceBuilder<T, S, X, U> impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
where where
S: ServiceFactory<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
X: ServiceFactory<Config = SrvConfig, Request = Request, Response = Request>, X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
<X::Service as Service>::Future: 'static, <X::Service as Service>::Future: 'static,
U: ServiceFactory< U: ServiceFactory<Config = (), Request = (Request, Framed<T, Codec>), Response = ()>,
Config = SrvConfig,
Request = (Request, Framed<T, Codec>),
Response = (),
>,
U::Error: fmt::Display, U::Error: fmt::Display,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static, <U::Service as Service>::Future: 'static,
@@ -78,6 +77,18 @@ where
self self
} }
/// Set connection secure state
pub fn secure(mut self) -> Self {
self.secure = true;
self
}
/// Set the local address that this service is bound to.
pub fn local_addr(mut self, addr: net::SocketAddr) -> Self {
self.local_addr = Some(addr);
self
}
/// Set server client timeout in milliseconds for first request. /// Set server client timeout in milliseconds for first request.
/// ///
/// Defines a timeout for reading client request header. If a client does not transmit /// Defines a timeout for reading client request header. If a client does not transmit
@@ -113,7 +124,7 @@ where
pub fn expect<F, X1>(self, expect: F) -> HttpServiceBuilder<T, S, X1, U> pub fn expect<F, X1>(self, expect: F) -> HttpServiceBuilder<T, S, X1, U>
where where
F: IntoServiceFactory<X1>, F: IntoServiceFactory<X1>,
X1: ServiceFactory<Config = SrvConfig, Request = Request, Response = Request>, X1: ServiceFactory<Config = (), Request = Request, Response = Request>,
X1::Error: Into<Error>, X1::Error: Into<Error>,
X1::InitError: fmt::Debug, X1::InitError: fmt::Debug,
<X1::Service as Service>::Future: 'static, <X1::Service as Service>::Future: 'static,
@@ -122,6 +133,8 @@ where
keep_alive: self.keep_alive, keep_alive: self.keep_alive,
client_timeout: self.client_timeout, client_timeout: self.client_timeout,
client_disconnect: self.client_disconnect, client_disconnect: self.client_disconnect,
secure: self.secure,
local_addr: self.local_addr,
expect: expect.into_factory(), expect: expect.into_factory(),
upgrade: self.upgrade, upgrade: self.upgrade,
on_connect: self.on_connect, on_connect: self.on_connect,
@@ -137,7 +150,7 @@ where
where where
F: IntoServiceFactory<U1>, F: IntoServiceFactory<U1>,
U1: ServiceFactory< U1: ServiceFactory<
Config = SrvConfig, Config = (),
Request = (Request, Framed<T, Codec>), Request = (Request, Framed<T, Codec>),
Response = (), Response = (),
>, >,
@@ -149,6 +162,8 @@ where
keep_alive: self.keep_alive, keep_alive: self.keep_alive,
client_timeout: self.client_timeout, client_timeout: self.client_timeout,
client_disconnect: self.client_disconnect, client_disconnect: self.client_disconnect,
secure: self.secure,
local_addr: self.local_addr,
expect: self.expect, expect: self.expect,
upgrade: Some(upgrade.into_factory()), upgrade: Some(upgrade.into_factory()),
on_connect: self.on_connect, on_connect: self.on_connect,
@@ -170,7 +185,7 @@ where
} }
/// Finish service configuration and create *http service* for HTTP/1 protocol. /// Finish service configuration and create *http service* for HTTP/1 protocol.
pub fn h1<F, P, B>(self, service: F) -> H1Service<T, P, S, B, X, U> pub fn h1<F, B>(self, service: F) -> H1Service<T, S, B, X, U>
where where
B: MessageBody, B: MessageBody,
F: IntoServiceFactory<S>, F: IntoServiceFactory<S>,
@@ -182,6 +197,8 @@ where
self.keep_alive, self.keep_alive,
self.client_timeout, self.client_timeout,
self.client_disconnect, self.client_disconnect,
self.secure,
self.local_addr,
); );
H1Service::with_config(cfg, service.into_factory()) H1Service::with_config(cfg, service.into_factory())
.expect(self.expect) .expect(self.expect)
@@ -190,7 +207,7 @@ where
} }
/// Finish service configuration and create *http service* for HTTP/2 protocol. /// Finish service configuration and create *http service* for HTTP/2 protocol.
pub fn h2<F, P, B>(self, service: F) -> H2Service<T, P, S, B> pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
where where
B: MessageBody + 'static, B: MessageBody + 'static,
F: IntoServiceFactory<S>, F: IntoServiceFactory<S>,
@@ -203,12 +220,14 @@ where
self.keep_alive, self.keep_alive,
self.client_timeout, self.client_timeout,
self.client_disconnect, self.client_disconnect,
self.secure,
self.local_addr,
); );
H2Service::with_config(cfg, service.into_factory()).on_connect(self.on_connect) H2Service::with_config(cfg, service.into_factory()).on_connect(self.on_connect)
} }
/// Finish service configuration and create `HttpService` instance. /// Finish service configuration and create `HttpService` instance.
pub fn finish<F, P, B>(self, service: F) -> HttpService<T, P, S, B, X, U> pub fn finish<F, B>(self, service: F) -> HttpService<T, S, B, X, U>
where where
B: MessageBody + 'static, B: MessageBody + 'static,
F: IntoServiceFactory<S>, F: IntoServiceFactory<S>,
@@ -221,6 +240,8 @@ where
self.keep_alive, self.keep_alive,
self.client_timeout, self.client_timeout,
self.client_disconnect, self.client_disconnect,
self.secure,
self.local_addr,
); );
HttpService::with_config(cfg, service.into_factory()) HttpService::with_config(cfg, service.into_factory())
.expect(self.expect) .expect(self.expect)

View File

@@ -1,10 +1,10 @@
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::{fmt, io, time}; use std::{fmt, io, mem, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use bytes::{Buf, Bytes}; use bytes::{Buf, Bytes};
use futures::future::{err, Either, Future, FutureExt, LocalBoxFuture, Ready}; use futures_util::future::{err, Either, Future, FutureExt, LocalBoxFuture, Ready};
use h2::client::SendRequest; use h2::client::SendRequest;
use pin_project::{pin_project, project}; use pin_project::{pin_project, project};
@@ -63,7 +63,7 @@ impl<T> fmt::Debug for IoConnection<T>
where where
T: fmt::Debug, T: fmt::Debug,
{ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.io { match self.io {
Some(ConnectionType::H1(ref io)) => write!(f, "H1Connection({:?})", io), Some(ConnectionType::H1(ref io)) => write!(f, "H1Connection({:?})", io),
Some(ConnectionType::H2(_)) => write!(f, "H2Connection"), Some(ConnectionType::H2(_)) => write!(f, "H2Connection"),
@@ -228,7 +228,10 @@ where
} }
} }
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { unsafe fn prepare_uninitialized_buffer(
&self,
buf: &mut [mem::MaybeUninit<u8>],
) -> bool {
match self { match self {
EitherIo::A(ref val) => val.prepare_uninitialized_buffer(buf), EitherIo::A(ref val) => val.prepare_uninitialized_buffer(buf),
EitherIo::B(ref val) => val.prepare_uninitialized_buffer(buf), EitherIo::B(ref val) => val.prepare_uninitialized_buffer(buf),
@@ -244,7 +247,7 @@ where
#[project] #[project]
fn poll_write( fn poll_write(
self: Pin<&mut Self>, self: Pin<&mut Self>,
cx: &mut Context, cx: &mut Context<'_>,
buf: &[u8], buf: &[u8],
) -> Poll<io::Result<usize>> { ) -> Poll<io::Result<usize>> {
#[project] #[project]

View File

@@ -1,19 +1,15 @@
use std::fmt; use std::fmt;
use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration; use std::time::Duration;
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_connect::{ use actix_connect::{
default_connector, Connect as TcpConnect, Connection as TcpConnection, default_connector, Connect as TcpConnect, Connection as TcpConnection,
}; };
use actix_rt::net::TcpStream;
use actix_service::{apply_fn, Service}; use actix_service::{apply_fn, Service};
use actix_utils::timeout::{TimeoutError, TimeoutService}; use actix_utils::timeout::{TimeoutError, TimeoutService};
use futures::future::Ready;
use http::Uri; use http::Uri;
use tokio_net::tcp::TcpStream;
use super::connection::Connection; use super::connection::Connection;
use super::error::ConnectError; use super::error::ConnectError;
@@ -21,10 +17,10 @@ use super::pool::{ConnectionPool, Protocol};
use super::Connect; use super::Connect;
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
use open_ssl::ssl::SslConnector as OpensslConnector; use actix_connect::ssl::openssl::SslConnector as OpensslConnector;
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
use rust_tls::ClientConfig; use actix_connect::ssl::rustls::ClientConfig;
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
use std::sync::Arc; use std::sync::Arc;
@@ -66,7 +62,7 @@ trait Io: AsyncRead + AsyncWrite + Unpin {}
impl<T: AsyncRead + AsyncWrite + Unpin> Io for T {} impl<T: AsyncRead + AsyncWrite + Unpin> Io for T {}
impl Connector<(), ()> { impl Connector<(), ()> {
#[allow(clippy::new_ret_no_self)] #[allow(clippy::new_ret_no_self, clippy::let_unit_value)]
pub fn new() -> Connector< pub fn new() -> Connector<
impl Service< impl Service<
Request = TcpConnect<Uri>, Request = TcpConnect<Uri>,
@@ -78,7 +74,7 @@ impl Connector<(), ()> {
let ssl = { let ssl = {
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
{ {
use open_ssl::ssl::SslMethod; use actix_connect::ssl::openssl::SslMethod;
let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap(); let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap();
let _ = ssl let _ = ssl
@@ -93,7 +89,7 @@ impl Connector<(), ()> {
config.set_protocols(&protos); config.set_protocols(&protos);
config config
.root_store .root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); .add_server_trust_anchors(&actix_tls::rustls::TLS_SERVER_ROOTS);
SslConnector::Rustls(Arc::new(config)) SslConnector::Rustls(Arc::new(config))
} }
#[cfg(not(any(feature = "openssl", feature = "rustls")))] #[cfg(not(any(feature = "openssl", feature = "rustls")))]
@@ -246,12 +242,10 @@ where
{ {
const H2: &[u8] = b"h2"; const H2: &[u8] = b"h2";
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
use actix_connect::ssl::OpensslConnector; use actix_connect::ssl::openssl::OpensslConnector;
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
use actix_connect::ssl::RustlsConnector; use actix_connect::ssl::rustls::{RustlsConnector, Session};
use actix_service::{boxed::service, pipeline}; use actix_service::{boxed::service, pipeline};
#[cfg(feature = "rustls")]
use rust_tls::Session;
let ssl_service = TimeoutService::new( let ssl_service = TimeoutService::new(
self.timeout, self.timeout,
@@ -343,8 +337,7 @@ where
mod connect_impl { mod connect_impl {
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use futures::future::{err, Either, Ready}; use futures_util::future::{err, Either, Ready};
use futures::ready;
use super::*; use super::*;
use crate::client::connection::IoConnection; use crate::client::connection::IoConnection;
@@ -385,7 +378,7 @@ mod connect_impl {
Ready<Result<IoConnection<Io>, ConnectError>>, Ready<Result<IoConnection<Io>, ConnectError>>,
>; >;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.tcp_pool.poll_ready(cx) self.tcp_pool.poll_ready(cx)
} }
@@ -402,10 +395,13 @@ mod connect_impl {
#[cfg(any(feature = "openssl", feature = "rustls"))] #[cfg(any(feature = "openssl", feature = "rustls"))]
mod connect_impl { mod connect_impl {
use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures::future::Either; use futures_core::ready;
use futures::ready; use futures_util::future::Either;
use super::*; use super::*;
use crate::client::connection::EitherConnection; use crate::client::connection::EitherConnection;
@@ -455,7 +451,7 @@ mod connect_impl {
InnerConnectorResponseB<T2, Io1, Io2>, InnerConnectorResponseB<T2, Io1, Io2>,
>; >;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.tcp_pool.poll_ready(cx) self.tcp_pool.poll_ready(cx)
} }
@@ -494,10 +490,10 @@ mod connect_impl {
{ {
type Output = Result<EitherConnection<Io1, Io2>, ConnectError>; type Output = Result<EitherConnection<Io1, Io2>, ConnectError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready( Poll::Ready(
ready!(Pin::new(&mut self.get_mut().fut).poll(cx)) ready!(Pin::new(&mut self.get_mut().fut).poll(cx))
.map(|res| EitherConnection::A(res)), .map(EitherConnection::A),
) )
} }
} }
@@ -523,10 +519,10 @@ mod connect_impl {
{ {
type Output = Result<EitherConnection<Io1, Io2>, ConnectError>; type Output = Result<EitherConnection<Io1, Io2>, ConnectError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready( Poll::Ready(
ready!(Pin::new(&mut self.get_mut().fut).poll(cx)) ready!(Pin::new(&mut self.get_mut().fut).poll(cx))
.map(|res| EitherConnection::B(res)), .map(EitherConnection::B),
) )
} }
} }

View File

@@ -1,14 +1,13 @@
use std::io; use std::io;
use actix_connect::resolver::ResolveError;
use derive_more::{Display, From}; use derive_more::{Display, From};
use trust_dns_resolver::error::ResolveError;
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
use open_ssl::ssl::{Error as SslError, HandshakeError}; use actix_connect::ssl::openssl::{HandshakeError, SslError};
use crate::error::{Error, ParseError, ResponseError}; use crate::error::{Error, ParseError, ResponseError};
use crate::http::Error as HttpError; use crate::http::{Error as HttpError, StatusCode};
use crate::response::Response;
/// A set of errors that can occur while connecting to an HTTP host /// A set of errors that can occur while connecting to an HTTP host
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
@@ -22,6 +21,11 @@ pub enum ConnectError {
#[display(fmt = "{}", _0)] #[display(fmt = "{}", _0)]
SslError(SslError), SslError(SslError),
/// SSL Handshake error
#[cfg(feature = "openssl")]
#[display(fmt = "{}", _0)]
SslHandshakeError(String),
/// Failed to resolve the hostname /// Failed to resolve the hostname
#[display(fmt = "Failed resolving hostname: {}", _0)] #[display(fmt = "Failed resolving hostname: {}", _0)]
Resolver(ResolveError), Resolver(ResolveError),
@@ -64,13 +68,9 @@ impl From<actix_connect::ConnectError> for ConnectError {
} }
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
impl<T> From<HandshakeError<T>> for ConnectError { impl<T: std::fmt::Debug> From<HandshakeError<T>> for ConnectError {
fn from(err: HandshakeError<T>) -> ConnectError { fn from(err: HandshakeError<T>) -> ConnectError {
match err { ConnectError::SslHandshakeError(format!("{:?}", err))
HandshakeError::SetupFailure(stack) => SslError::from(stack).into(),
HandshakeError::Failure(stream) => stream.into_error().into(),
HandshakeError::WouldBlock(stream) => stream.into_error().into(),
}
} }
} }
@@ -117,15 +117,14 @@ pub enum SendRequestError {
/// Convert `SendRequestError` to a server `Response` /// Convert `SendRequestError` to a server `Response`
impl ResponseError for SendRequestError { impl ResponseError for SendRequestError {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
match *self { match *self {
SendRequestError::Connect(ConnectError::Timeout) => { SendRequestError::Connect(ConnectError::Timeout) => {
Response::GatewayTimeout() StatusCode::GATEWAY_TIMEOUT
} }
SendRequestError::Connect(_) => Response::BadGateway(), SendRequestError::Connect(_) => StatusCode::BAD_REQUEST,
_ => Response::InternalServerError(), _ => StatusCode::INTERNAL_SERVER_ERROR,
} }
.into()
} }
} }

View File

@@ -1,13 +1,14 @@
use std::future::Future;
use std::io::Write; use std::io::Write;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::{io, time}; use std::{io, mem, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use bytes::{BufMut, Bytes, BytesMut}; use bytes::buf::BufMutExt;
use futures::future::{ok, poll_fn, Either}; use bytes::{Bytes, BytesMut};
use futures::{Sink, SinkExt, Stream, StreamExt}; use futures_core::Stream;
use futures_util::future::poll_fn;
use futures_util::{SinkExt, StreamExt};
use crate::error::PayloadError; use crate::error::PayloadError;
use crate::h1; use crate::h1;
@@ -44,7 +45,7 @@ where
Some(port) => write!(wrt, "{}:{}", host, port), Some(port) => write!(wrt, "{}:{}", host, port),
}; };
match wrt.get_mut().take().freeze().try_into() { match wrt.get_mut().split().freeze().try_into() {
Ok(value) => match head { Ok(value) => match head {
RequestHeadType::Owned(ref mut head) => { RequestHeadType::Owned(ref mut head) => {
head.headers.insert(HOST, value) head.headers.insert(HOST, value)
@@ -200,7 +201,10 @@ where
} }
impl<T: AsyncRead + AsyncWrite + Unpin + 'static> AsyncRead for H1Connection<T> { impl<T: AsyncRead + AsyncWrite + Unpin + 'static> AsyncRead for H1Connection<T> {
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { unsafe fn prepare_uninitialized_buffer(
&self,
buf: &mut [mem::MaybeUninit<u8>],
) -> bool {
self.io.as_ref().unwrap().prepare_uninitialized_buffer(buf) self.io.as_ref().unwrap().prepare_uninitialized_buffer(buf)
} }
@@ -231,7 +235,7 @@ impl<T: AsyncRead + AsyncWrite + Unpin + 'static> AsyncWrite for H1Connection<T>
fn poll_shutdown( fn poll_shutdown(
mut self: Pin<&mut Self>, mut self: Pin<&mut Self>,
cx: &mut Context, cx: &mut Context<'_>,
) -> Poll<Result<(), io::Error>> { ) -> Poll<Result<(), io::Error>> {
Pin::new(self.io.as_mut().unwrap()).poll_shutdown(cx) Pin::new(self.io.as_mut().unwrap()).poll_shutdown(cx)
} }
@@ -252,7 +256,10 @@ impl<Io: ConnectionLifetime> PlStream<Io> {
impl<Io: ConnectionLifetime> Stream for PlStream<Io> { impl<Io: ConnectionLifetime> Stream for PlStream<Io> {
type Item = Result<Bytes, PayloadError>; type Item = Result<Bytes, PayloadError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> { fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
let this = self.get_mut(); let this = self.get_mut();
match this.framed.as_mut().unwrap().next_item(cx)? { match this.framed.as_mut().unwrap().next_item(cx)? {

View File

@@ -1,14 +1,12 @@
use std::future::Future; use std::convert::TryFrom;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time; use std::time;
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use bytes::Bytes; use bytes::Bytes;
use futures::future::{err, poll_fn, Either}; use futures_util::future::poll_fn;
use h2::{client::SendRequest, SendStream}; use h2::{client::SendRequest, SendStream};
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING};
use http::{request::Request, HttpTryFrom, Method, Version}; use http::{request::Request, Method, Version};
use crate::body::{BodySize, MessageBody}; use crate::body::{BodySize, MessageBody};
use crate::header::HeaderMap; use crate::header::HeaderMap;

View File

@@ -1,23 +1,22 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::future::Future; use std::future::Future;
use std::io;
use std::pin::Pin; use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::time::{delay_for, Delay};
use actix_service::Service; use actix_service::Service;
use actix_utils::{oneshot, task::LocalWaker}; use actix_utils::{oneshot, task::LocalWaker};
use bytes::Bytes; use bytes::Bytes;
use futures::future::{err, ok, poll_fn, Either, FutureExt, LocalBoxFuture, Ready}; use futures_util::future::{poll_fn, FutureExt, LocalBoxFuture};
use fxhash::FxHashMap;
use h2::client::{handshake, Connection, SendRequest}; use h2::client::{handshake, Connection, SendRequest};
use hashbrown::HashMap;
use http::uri::Authority; use http::uri::Authority;
use indexmap::IndexSet; use indexmap::IndexSet;
use slab::Slab; use slab::Slab;
use tokio_timer::{delay_for, Delay};
use super::connection::{ConnectionType, IoConnection}; use super::connection::{ConnectionType, IoConnection};
use super::error::ConnectError; use super::error::ConnectError;
@@ -67,7 +66,7 @@ where
acquired: 0, acquired: 0,
waiters: Slab::new(), waiters: Slab::new(),
waiters_queue: IndexSet::new(), waiters_queue: IndexSet::new(),
available: HashMap::new(), available: FxHashMap::default(),
waker: LocalWaker::new(), waker: LocalWaker::new(),
})), })),
) )
@@ -94,13 +93,13 @@ where
type Error = ConnectError; type Error = ConnectError;
type Future = LocalBoxFuture<'static, Result<IoConnection<Io>, ConnectError>>; type Future = LocalBoxFuture<'static, Result<IoConnection<Io>, ConnectError>>;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx) self.0.poll_ready(cx)
} }
fn call(&mut self, req: Connect) -> Self::Future { fn call(&mut self, req: Connect) -> Self::Future {
// start support future // start support future
tokio_executor::current_thread::spawn(ConnectorPoolSupport { actix_rt::spawn(ConnectorPoolSupport {
connector: self.0.clone(), connector: self.0.clone(),
inner: self.1.clone(), inner: self.1.clone(),
}); });
@@ -109,7 +108,7 @@ where
let inner = self.1.clone(); let inner = self.1.clone();
let fut = async move { let fut = async move {
let key = if let Some(authority) = req.uri.authority_part() { let key = if let Some(authority) = req.uri.authority() {
authority.clone().into() authority.clone().into()
} else { } else {
return Err(ConnectError::Unresolverd); return Err(ConnectError::Unresolverd);
@@ -139,7 +138,7 @@ where
)) ))
} else { } else {
let (snd, connection) = handshake(io).await?; let (snd, connection) = handshake(io).await?;
tokio_executor::current_thread::spawn(connection.map(|_| ())); actix_rt::spawn(connection.map(|_| ()));
Ok(IoConnection::new( Ok(IoConnection::new(
ConnectionType::H2(snd), ConnectionType::H2(snd),
Instant::now(), Instant::now(),
@@ -260,7 +259,7 @@ pub(crate) struct Inner<Io> {
disconnect_timeout: Option<Duration>, disconnect_timeout: Option<Duration>,
limit: usize, limit: usize,
acquired: usize, acquired: usize,
available: HashMap<Key, VecDeque<AvailableConnection<Io>>>, available: FxHashMap<Key, VecDeque<AvailableConnection<Io>>>,
waiters: Slab< waiters: Slab<
Option<( Option<(
Connect, Connect,
@@ -300,7 +299,7 @@ where
) { ) {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let key: Key = connect.uri.authority_part().unwrap().clone().into(); let key: Key = connect.uri.authority().unwrap().clone().into();
let entry = self.waiters.vacant_entry(); let entry = self.waiters.vacant_entry();
let token = entry.key(); let token = entry.key();
entry.insert(Some((connect, tx))); entry.insert(Some((connect, tx)));
@@ -309,7 +308,7 @@ where
(rx, token) (rx, token)
} }
fn acquire(&mut self, key: &Key, cx: &mut Context) -> Acquire<Io> { fn acquire(&mut self, key: &Key, cx: &mut Context<'_>) -> Acquire<Io> {
// check limits // check limits
if self.limit > 0 && self.acquired >= self.limit { if self.limit > 0 && self.acquired >= self.limit {
return Acquire::NotAvailable; return Acquire::NotAvailable;
@@ -328,9 +327,7 @@ where
{ {
if let Some(timeout) = self.disconnect_timeout { if let Some(timeout) = self.disconnect_timeout {
if let ConnectionType::H1(io) = conn.io { if let ConnectionType::H1(io) = conn.io {
tokio_executor::current_thread::spawn(CloseConnection::new( actix_rt::spawn(CloseConnection::new(io, timeout))
io, timeout,
))
} }
} }
} else { } else {
@@ -342,9 +339,9 @@ where
Poll::Ready(Ok(n)) if n > 0 => { Poll::Ready(Ok(n)) if n > 0 => {
if let Some(timeout) = self.disconnect_timeout { if let Some(timeout) = self.disconnect_timeout {
if let ConnectionType::H1(io) = io { if let ConnectionType::H1(io) = io {
tokio_executor::current_thread::spawn( actix_rt::spawn(CloseConnection::new(
CloseConnection::new(io, timeout), io, timeout,
) ))
} }
} }
continue; continue;
@@ -376,7 +373,7 @@ where
self.acquired -= 1; self.acquired -= 1;
if let Some(timeout) = self.disconnect_timeout { if let Some(timeout) = self.disconnect_timeout {
if let ConnectionType::H1(io) = io { if let ConnectionType::H1(io) = io {
tokio_executor::current_thread::spawn(CloseConnection::new(io, timeout)) actix_rt::spawn(CloseConnection::new(io, timeout))
} }
} }
self.check_availibility(); self.check_availibility();
@@ -412,7 +409,7 @@ where
{ {
type Output = (); type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
let this = self.get_mut(); let this = self.get_mut();
match Pin::new(&mut this.timeout).poll(cx) { match Pin::new(&mut this.timeout).poll(cx) {
@@ -441,7 +438,7 @@ where
{ {
type Output = (); type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() }; let this = unsafe { self.get_unchecked_mut() };
let mut inner = this.inner.as_ref().borrow_mut(); let mut inner = this.inner.as_ref().borrow_mut();
@@ -518,7 +515,7 @@ where
inner: Rc<RefCell<Inner<Io>>>, inner: Rc<RefCell<Inner<Io>>>,
fut: F, fut: F,
) { ) {
tokio_executor::current_thread::spawn(OpenWaitingConnection { actix_rt::spawn(OpenWaitingConnection {
key, key,
fut, fut,
h2: None, h2: None,
@@ -548,13 +545,13 @@ where
{ {
type Output = (); type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() }; let this = unsafe { self.get_unchecked_mut() };
if let Some(ref mut h2) = this.h2 { if let Some(ref mut h2) = this.h2 {
return match Pin::new(h2).poll(cx) { return match Pin::new(h2).poll(cx) {
Poll::Ready(Ok((snd, connection))) => { Poll::Ready(Ok((snd, connection))) => {
tokio_executor::current_thread::spawn(connection.map(|_| ())); actix_rt::spawn(connection.map(|_| ()));
let rx = this.rx.take().unwrap(); let rx = this.rx.take().unwrap();
let _ = rx.send(Ok(IoConnection::new( let _ = rx.send(Ok(IoConnection::new(
ConnectionType::H2(snd), ConnectionType::H2(snd),

View File

@@ -32,7 +32,7 @@ where
type Error = T::Error; type Error = T::Error;
type Future = T::Future; type Future = T::Future;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
unsafe { &mut *self.0.as_ref().get() }.poll_ready(cx) unsafe { &mut *self.0.as_ref().get() }.poll_ready(cx)
} }

View File

@@ -1,13 +1,13 @@
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::fmt;
use std::fmt::Write; use std::fmt::Write;
use std::rc::Rc; use std::rc::Rc;
use std::time::{Duration, Instant}; use std::time::Duration;
use std::{fmt, net};
use actix_rt::time::{delay_for, delay_until, Delay, Instant};
use bytes::BytesMut; use bytes::BytesMut;
use futures::{future, Future, FutureExt}; use futures_util::{future, FutureExt};
use time; use time;
use tokio_timer::{delay, delay_for, Delay};
// "Sun, 06 Nov 1994 08:49:37 GMT".len() // "Sun, 06 Nov 1994 08:49:37 GMT".len()
const DATE_VALUE_LENGTH: usize = 29; const DATE_VALUE_LENGTH: usize = 29;
@@ -47,6 +47,8 @@ struct Inner {
client_timeout: u64, client_timeout: u64,
client_disconnect: u64, client_disconnect: u64,
ka_enabled: bool, ka_enabled: bool,
secure: bool,
local_addr: Option<std::net::SocketAddr>,
timer: DateService, timer: DateService,
} }
@@ -58,7 +60,7 @@ impl Clone for ServiceConfig {
impl Default for ServiceConfig { impl Default for ServiceConfig {
fn default() -> Self { fn default() -> Self {
Self::new(KeepAlive::Timeout(5), 0, 0) Self::new(KeepAlive::Timeout(5), 0, 0, false, None)
} }
} }
@@ -68,6 +70,8 @@ impl ServiceConfig {
keep_alive: KeepAlive, keep_alive: KeepAlive,
client_timeout: u64, client_timeout: u64,
client_disconnect: u64, client_disconnect: u64,
secure: bool,
local_addr: Option<net::SocketAddr>,
) -> ServiceConfig { ) -> ServiceConfig {
let (keep_alive, ka_enabled) = match keep_alive { let (keep_alive, ka_enabled) = match keep_alive {
KeepAlive::Timeout(val) => (val as u64, true), KeepAlive::Timeout(val) => (val as u64, true),
@@ -85,10 +89,24 @@ impl ServiceConfig {
ka_enabled, ka_enabled,
client_timeout, client_timeout,
client_disconnect, client_disconnect,
secure,
local_addr,
timer: DateService::new(), timer: DateService::new(),
})) }))
} }
#[inline]
/// Returns true if connection is secure(https)
pub fn secure(&self) -> bool {
self.0.secure
}
#[inline]
/// Returns the local address that this server is bound to.
pub fn local_addr(&self) -> Option<net::SocketAddr> {
self.0.local_addr
}
#[inline] #[inline]
/// Keep alive duration if configured. /// Keep alive duration if configured.
pub fn keep_alive(&self) -> Option<Duration> { pub fn keep_alive(&self) -> Option<Duration> {
@@ -106,7 +124,7 @@ impl ServiceConfig {
pub fn client_timer(&self) -> Option<Delay> { pub fn client_timer(&self) -> Option<Delay> {
let delay_time = self.0.client_timeout; let delay_time = self.0.client_timeout;
if delay_time != 0 { if delay_time != 0 {
Some(delay( Some(delay_until(
self.0.timer.now() + Duration::from_millis(delay_time), self.0.timer.now() + Duration::from_millis(delay_time),
)) ))
} else { } else {
@@ -138,7 +156,7 @@ impl ServiceConfig {
/// Return keep-alive timer delay is configured. /// Return keep-alive timer delay is configured.
pub fn keep_alive_timer(&self) -> Option<Delay> { pub fn keep_alive_timer(&self) -> Option<Delay> {
if let Some(ka) = self.0.keep_alive { if let Some(ka) = self.0.keep_alive {
Some(delay(self.0.timer.now() + ka)) Some(delay_until(self.0.timer.now() + ka))
} else { } else {
None None
} }
@@ -242,12 +260,10 @@ impl DateService {
// periodic date update // periodic date update
let s = self.clone(); let s = self.clone();
tokio_executor::current_thread::spawn( actix_rt::spawn(delay_for(Duration::from_millis(500)).then(move |_| {
delay_for(Duration::from_millis(500)).then(move |_| { s.0.reset();
s.0.reset(); future::ready(())
future::ready(()) }));
}),
);
} }
} }
@@ -265,26 +281,19 @@ impl DateService {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use actix_rt::System;
use futures::future;
#[test] #[test]
fn test_date_len() { fn test_date_len() {
assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len()); assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
} }
#[test] #[actix_rt::test]
fn test_date() { async fn test_date() {
let mut rt = System::new("test"); let settings = ServiceConfig::new(KeepAlive::Os, 0, 0, false, None);
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
let _ = rt.block_on(future::lazy(|_| { settings.set_date(&mut buf1);
let settings = ServiceConfig::new(KeepAlive::Os, 0, 0); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); settings.set_date(&mut buf2);
settings.set_date(&mut buf1); assert_eq!(buf1, buf2);
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.set_date(&mut buf2);
assert_eq!(buf1, buf2);
future::ok::<_, ()>(())
}));
} }
} }

View File

@@ -18,7 +18,6 @@ use super::{Cookie, SameSite};
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// ///
/// # fn main() {
/// let cookie: Cookie = Cookie::build("name", "value") /// let cookie: Cookie = Cookie::build("name", "value")
/// .domain("www.rust-lang.org") /// .domain("www.rust-lang.org")
/// .path("/") /// .path("/")
@@ -26,7 +25,6 @@ use super::{Cookie, SameSite};
/// .http_only(true) /// .http_only(true)
/// .max_age(84600) /// .max_age(84600)
/// .finish(); /// .finish();
/// # }
/// ``` /// ```
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CookieBuilder { pub struct CookieBuilder {
@@ -65,13 +63,11 @@ impl CookieBuilder {
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// ///
/// # fn main() {
/// let c = Cookie::build("foo", "bar") /// let c = Cookie::build("foo", "bar")
/// .expires(time::now()) /// .expires(time::now())
/// .finish(); /// .finish();
/// ///
/// assert!(c.expires().is_some()); /// assert!(c.expires().is_some());
/// # }
/// ``` /// ```
#[inline] #[inline]
pub fn expires(mut self, when: Tm) -> CookieBuilder { pub fn expires(mut self, when: Tm) -> CookieBuilder {
@@ -86,13 +82,11 @@ impl CookieBuilder {
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// ///
/// # fn main() {
/// let c = Cookie::build("foo", "bar") /// let c = Cookie::build("foo", "bar")
/// .max_age(1800) /// .max_age(1800)
/// .finish(); /// .finish();
/// ///
/// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60))); /// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60)));
/// # }
/// ``` /// ```
#[inline] #[inline]
pub fn max_age(self, seconds: i64) -> CookieBuilder { pub fn max_age(self, seconds: i64) -> CookieBuilder {
@@ -106,13 +100,11 @@ impl CookieBuilder {
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// ///
/// # fn main() {
/// let c = Cookie::build("foo", "bar") /// let c = Cookie::build("foo", "bar")
/// .max_age_time(time::Duration::minutes(30)) /// .max_age_time(time::Duration::minutes(30))
/// .finish(); /// .finish();
/// ///
/// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60))); /// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60)));
/// # }
/// ``` /// ```
#[inline] #[inline]
pub fn max_age_time(mut self, value: Duration) -> CookieBuilder { pub fn max_age_time(mut self, value: Duration) -> CookieBuilder {
@@ -222,14 +214,12 @@ impl CookieBuilder {
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// use chrono::Duration; /// use chrono::Duration;
/// ///
/// # fn main() {
/// let c = Cookie::build("foo", "bar") /// let c = Cookie::build("foo", "bar")
/// .permanent() /// .permanent()
/// .finish(); /// .finish();
/// ///
/// assert_eq!(c.max_age(), Some(Duration::days(365 * 20))); /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
/// # assert!(c.expires().is_some()); /// # assert!(c.expires().is_some());
/// # }
/// ``` /// ```
#[inline] #[inline]
pub fn permanent(mut self) -> CookieBuilder { pub fn permanent(mut self) -> CookieBuilder {

View File

@@ -88,7 +88,7 @@ impl SameSite {
} }
impl fmt::Display for SameSite { impl fmt::Display for SameSite {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
SameSite::Strict => write!(f, "Strict"), SameSite::Strict => write!(f, "Strict"),
SameSite::Lax => write!(f, "Lax"), SameSite::Lax => write!(f, "Lax"),

View File

@@ -190,7 +190,6 @@ impl CookieJar {
/// use actix_http::cookie::{CookieJar, Cookie}; /// use actix_http::cookie::{CookieJar, Cookie};
/// use chrono::Duration; /// use chrono::Duration;
/// ///
/// # fn main() {
/// let mut jar = CookieJar::new(); /// let mut jar = CookieJar::new();
/// ///
/// // Assume this cookie originally had a path of "/" and domain of "a.b". /// // Assume this cookie originally had a path of "/" and domain of "a.b".
@@ -204,7 +203,6 @@ impl CookieJar {
/// assert_eq!(delta.len(), 1); /// assert_eq!(delta.len(), 1);
/// assert_eq!(delta[0].name(), "name"); /// assert_eq!(delta[0].name(), "name");
/// assert_eq!(delta[0].max_age(), Some(Duration::seconds(0))); /// assert_eq!(delta[0].max_age(), Some(Duration::seconds(0)));
/// # }
/// ``` /// ```
/// ///
/// Removing a new cookie does not result in a _removal_ cookie: /// Removing a new cookie does not result in a _removal_ cookie:
@@ -243,7 +241,6 @@ impl CookieJar {
/// use actix_http::cookie::{CookieJar, Cookie}; /// use actix_http::cookie::{CookieJar, Cookie};
/// use chrono::Duration; /// use chrono::Duration;
/// ///
/// # fn main() {
/// let mut jar = CookieJar::new(); /// let mut jar = CookieJar::new();
/// ///
/// // Add an original cookie and a new cookie. /// // Add an original cookie and a new cookie.
@@ -261,7 +258,6 @@ impl CookieJar {
/// jar.force_remove(Cookie::new("key", "value")); /// jar.force_remove(Cookie::new("key", "value"));
/// assert_eq!(jar.delta().count(), 0); /// assert_eq!(jar.delta().count(), 0);
/// assert_eq!(jar.iter().count(), 0); /// assert_eq!(jar.iter().count(), 0);
/// # }
/// ``` /// ```
pub fn force_remove<'a>(&mut self, cookie: Cookie<'a>) { pub fn force_remove<'a>(&mut self, cookie: Cookie<'a>) {
self.original_cookies.remove(cookie.name()); self.original_cookies.remove(cookie.name());
@@ -307,7 +303,7 @@ impl CookieJar {
/// // Delta contains two new cookies ("new", "yac") and a removal ("name"). /// // Delta contains two new cookies ("new", "yac") and a removal ("name").
/// assert_eq!(jar.delta().count(), 3); /// assert_eq!(jar.delta().count(), 3);
/// ``` /// ```
pub fn delta(&self) -> Delta { pub fn delta(&self) -> Delta<'_> {
Delta { Delta {
iter: self.delta_cookies.iter(), iter: self.delta_cookies.iter(),
} }
@@ -343,7 +339,7 @@ impl CookieJar {
/// } /// }
/// } /// }
/// ``` /// ```
pub fn iter(&self) -> Iter { pub fn iter(&self) -> Iter<'_> {
Iter { Iter {
delta_cookies: self delta_cookies: self
.delta_cookies .delta_cookies
@@ -386,7 +382,7 @@ impl CookieJar {
/// assert!(jar.get("private").is_some()); /// assert!(jar.get("private").is_some());
/// ``` /// ```
#[cfg(feature = "secure-cookies")] #[cfg(feature = "secure-cookies")]
pub fn private(&mut self, key: &Key) -> PrivateJar { pub fn private(&mut self, key: &Key) -> PrivateJar<'_> {
PrivateJar::new(self, key) PrivateJar::new(self, key)
} }
@@ -424,7 +420,7 @@ impl CookieJar {
/// assert!(jar.get("signed").is_some()); /// assert!(jar.get("signed").is_some());
/// ``` /// ```
#[cfg(feature = "secure-cookies")] #[cfg(feature = "secure-cookies")]
pub fn signed(&mut self, key: &Key) -> SignedJar { pub fn signed(&mut self, key: &Key) -> SignedJar<'_> {
SignedJar::new(self, key) SignedJar::new(self, key)
} }
} }

View File

@@ -110,7 +110,7 @@ impl CookieStr {
/// # Panics /// # Panics
/// ///
/// Panics if `self` is an indexed string and `string` is None. /// Panics if `self` is an indexed string and `string` is None.
fn to_str<'s>(&'s self, string: Option<&'s Cow<str>>) -> &'s str { fn to_str<'s>(&'s self, string: Option<&'s Cow<'_, str>>) -> &'s str {
match *self { match *self {
CookieStr::Indexed(i, j) => { CookieStr::Indexed(i, j) => {
let s = string.expect( let s = string.expect(
@@ -647,13 +647,11 @@ impl<'c> Cookie<'c> {
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// use chrono::Duration; /// use chrono::Duration;
/// ///
/// # fn main() {
/// let mut c = Cookie::new("name", "value"); /// let mut c = Cookie::new("name", "value");
/// assert_eq!(c.max_age(), None); /// assert_eq!(c.max_age(), None);
/// ///
/// c.set_max_age(Duration::hours(10)); /// c.set_max_age(Duration::hours(10));
/// assert_eq!(c.max_age(), Some(Duration::hours(10))); /// assert_eq!(c.max_age(), Some(Duration::hours(10)));
/// # }
/// ``` /// ```
#[inline] #[inline]
pub fn set_max_age(&mut self, value: Duration) { pub fn set_max_age(&mut self, value: Duration) {
@@ -701,7 +699,6 @@ impl<'c> Cookie<'c> {
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// ///
/// # fn main() {
/// let mut c = Cookie::new("name", "value"); /// let mut c = Cookie::new("name", "value");
/// assert_eq!(c.expires(), None); /// assert_eq!(c.expires(), None);
/// ///
@@ -710,7 +707,6 @@ impl<'c> Cookie<'c> {
/// ///
/// c.set_expires(now); /// c.set_expires(now);
/// assert!(c.expires().is_some()) /// assert!(c.expires().is_some())
/// # }
/// ``` /// ```
#[inline] #[inline]
pub fn set_expires(&mut self, time: Tm) { pub fn set_expires(&mut self, time: Tm) {
@@ -726,7 +722,6 @@ impl<'c> Cookie<'c> {
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// use chrono::Duration; /// use chrono::Duration;
/// ///
/// # fn main() {
/// let mut c = Cookie::new("foo", "bar"); /// let mut c = Cookie::new("foo", "bar");
/// assert!(c.expires().is_none()); /// assert!(c.expires().is_none());
/// assert!(c.max_age().is_none()); /// assert!(c.max_age().is_none());
@@ -734,7 +729,6 @@ impl<'c> Cookie<'c> {
/// c.make_permanent(); /// c.make_permanent();
/// assert!(c.expires().is_some()); /// assert!(c.expires().is_some());
/// assert_eq!(c.max_age(), Some(Duration::days(365 * 20))); /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
/// # }
/// ``` /// ```
pub fn make_permanent(&mut self) { pub fn make_permanent(&mut self) {
let twenty_years = Duration::days(365 * 20); let twenty_years = Duration::days(365 * 20);
@@ -742,7 +736,7 @@ impl<'c> Cookie<'c> {
self.set_expires(time::now() + twenty_years); self.set_expires(time::now() + twenty_years);
} }
fn fmt_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt_parameters(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(true) = self.http_only() { if let Some(true) = self.http_only() {
write!(f, "; HttpOnly")?; write!(f, "; HttpOnly")?;
} }
@@ -924,10 +918,10 @@ impl<'c> Cookie<'c> {
/// let mut c = Cookie::new("my name", "this; value?"); /// let mut c = Cookie::new("my name", "this; value?");
/// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F"); /// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F");
/// ``` /// ```
pub struct EncodedCookie<'a, 'c: 'a>(&'a Cookie<'c>); pub struct EncodedCookie<'a, 'c>(&'a Cookie<'c>);
impl<'a, 'c: 'a> fmt::Display for EncodedCookie<'a, 'c> { impl<'a, 'c: 'a> fmt::Display for EncodedCookie<'a, 'c> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Percent-encode the name and value. // Percent-encode the name and value.
let name = percent_encode(self.0.name().as_bytes(), USERINFO); let name = percent_encode(self.0.name().as_bytes(), USERINFO);
let value = percent_encode(self.0.value().as_bytes(), USERINFO); let value = percent_encode(self.0.value().as_bytes(), USERINFO);
@@ -952,7 +946,7 @@ impl<'c> fmt::Display for Cookie<'c> {
/// ///
/// assert_eq!(&cookie.to_string(), "foo=bar; Path=/"); /// assert_eq!(&cookie.to_string(), "foo=bar; Path=/");
/// ``` /// ```
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}={}", self.name(), self.value())?; write!(f, "{}={}", self.name(), self.value())?;
self.fmt_parameters(f) self.fmt_parameters(f)
} }

View File

@@ -40,7 +40,7 @@ impl ParseError {
} }
impl fmt::Display for ParseError { impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str()) write!(f, "{}", self.as_str())
} }
} }
@@ -51,11 +51,7 @@ impl From<Utf8Error> for ParseError {
} }
} }
impl Error for ParseError { impl Error for ParseError {}
fn description(&self) -> &str {
self.as_str()
}
}
fn indexes_of(needle: &str, haystack: &str) -> Option<(usize, usize)> { fn indexes_of(needle: &str, haystack: &str) -> Option<(usize, usize)> {
let haystack_start = haystack.as_ptr() as usize; let haystack_start = haystack.as_ptr() as usize;

View File

@@ -1,5 +1,4 @@
use ring::hkdf::{Algorithm, KeyType, Prk, HKDF_SHA256}; use ring::hkdf::{Algorithm, KeyType, Prk, HKDF_SHA256};
use ring::hmac;
use ring::rand::{SecureRandom, SystemRandom}; use ring::rand::{SecureRandom, SystemRandom};
use super::private::KEY_LEN as PRIVATE_KEY_LEN; use super::private::KEY_LEN as PRIVATE_KEY_LEN;

View File

@@ -10,7 +10,7 @@ use crate::cookie::{Cookie, CookieJar};
// Keep these in sync, and keep the key len synced with the `private` docs as // Keep these in sync, and keep the key len synced with the `private` docs as
// well as the `KEYS_INFO` const in secure::Key. // well as the `KEYS_INFO` const in secure::Key.
static ALGO: &'static Algorithm = &AES_256_GCM; static ALGO: &Algorithm = &AES_256_GCM;
const NONCE_LEN: usize = 12; const NONCE_LEN: usize = 12;
pub const KEY_LEN: usize = 32; pub const KEY_LEN: usize = 32;
@@ -159,7 +159,7 @@ Please change it as soon as possible."
/// Encrypts the cookie's value with /// Encrypts the cookie's value with
/// authenticated encryption assuring confidentiality, integrity, and authenticity. /// authenticated encryption assuring confidentiality, integrity, and authenticity.
fn encrypt_cookie(&self, cookie: &mut Cookie) { fn encrypt_cookie(&self, cookie: &mut Cookie<'_>) {
let name = cookie.name().as_bytes(); let name = cookie.name().as_bytes();
let value = cookie.value().as_bytes(); let value = cookie.value().as_bytes();
let data = encrypt_name_value(name, value, &self.key); let data = encrypt_name_value(name, value, &self.key);

View File

@@ -129,7 +129,7 @@ impl<'a> SignedJar<'a> {
} }
/// Signs the cookie's value assuring integrity and authenticity. /// Signs the cookie's value assuring integrity and authenticity.
fn sign_cookie(&self, cookie: &mut Cookie) { fn sign_cookie(&self, cookie: &mut Cookie<'_>) {
let digest = sign(&self.key, cookie.value().as_bytes()); let digest = sign(&self.key, cookie.value().as_bytes());
let mut new_value = base64::encode(digest.as_ref()); let mut new_value = base64::encode(digest.as_ref());
new_value.push_str(cookie.value()); new_value.push_str(cookie.value());

View File

@@ -4,12 +4,10 @@ use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_threadpool::{run, CpuFuture}; use actix_threadpool::{run, CpuFuture};
#[cfg(feature = "brotli")]
use brotli2::write::BrotliDecoder; use brotli2::write::BrotliDecoder;
use bytes::Bytes; use bytes::Bytes;
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
use flate2::write::{GzDecoder, ZlibDecoder}; use flate2::write::{GzDecoder, ZlibDecoder};
use futures::{ready, Stream}; use futures_core::{ready, Stream};
use super::Writer; use super::Writer;
use crate::error::PayloadError; use crate::error::PayloadError;
@@ -21,7 +19,7 @@ pub struct Decoder<S> {
decoder: Option<ContentDecoder>, decoder: Option<ContentDecoder>,
stream: S, stream: S,
eof: bool, eof: bool,
fut: Option<CpuFuture<Result<(Option<Bytes>, ContentDecoder), io::Error>>>, fut: Option<CpuFuture<(Option<Bytes>, ContentDecoder), io::Error>>,
} }
impl<S> Decoder<S> impl<S> Decoder<S>
@@ -32,15 +30,12 @@ where
#[inline] #[inline]
pub fn new(stream: S, encoding: ContentEncoding) -> Decoder<S> { pub fn new(stream: S, encoding: ContentEncoding) -> Decoder<S> {
let decoder = match encoding { let decoder = match encoding {
#[cfg(feature = "brotli")]
ContentEncoding::Br => Some(ContentDecoder::Br(Box::new( ContentEncoding::Br => Some(ContentDecoder::Br(Box::new(
BrotliDecoder::new(Writer::new()), BrotliDecoder::new(Writer::new()),
))), ))),
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new( ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
ZlibDecoder::new(Writer::new()), ZlibDecoder::new(Writer::new()),
))), ))),
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new( ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(
GzDecoder::new(Writer::new()), GzDecoder::new(Writer::new()),
))), ))),
@@ -80,13 +75,12 @@ where
fn poll_next( fn poll_next(
mut self: Pin<&mut Self>, mut self: Pin<&mut Self>,
cx: &mut Context, cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> { ) -> Poll<Option<Self::Item>> {
loop { loop {
if let Some(ref mut fut) = self.fut { if let Some(ref mut fut) = self.fut {
let (chunk, decoder) = match ready!(Pin::new(fut).poll(cx)) { let (chunk, decoder) = match ready!(Pin::new(fut).poll(cx)) {
Ok(Ok(item)) => item, Ok(item) => item,
Ok(Err(e)) => return Poll::Ready(Some(Err(e.into()))),
Err(e) => return Poll::Ready(Some(Err(e.into()))), Err(e) => return Poll::Ready(Some(Err(e.into()))),
}; };
self.decoder = Some(decoder); self.decoder = Some(decoder);
@@ -141,22 +135,17 @@ where
} }
enum ContentDecoder { enum ContentDecoder {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
Deflate(Box<ZlibDecoder<Writer>>), Deflate(Box<ZlibDecoder<Writer>>),
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
Gzip(Box<GzDecoder<Writer>>), Gzip(Box<GzDecoder<Writer>>),
#[cfg(feature = "brotli")]
Br(Box<BrotliDecoder<Writer>>), Br(Box<BrotliDecoder<Writer>>),
} }
impl ContentDecoder { impl ContentDecoder {
#[allow(unreachable_patterns)]
fn feed_eof(&mut self) -> io::Result<Option<Bytes>> { fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
match self { match self {
#[cfg(feature = "brotli")] ContentDecoder::Br(ref mut decoder) => match decoder.flush() {
ContentDecoder::Br(ref mut decoder) => match decoder.finish() { Ok(()) => {
Ok(mut writer) => { let b = decoder.get_mut().take();
let b = writer.take();
if !b.is_empty() { if !b.is_empty() {
Ok(Some(b)) Ok(Some(b))
} else { } else {
@@ -165,7 +154,6 @@ impl ContentDecoder {
} }
Err(e) => Err(e), Err(e) => Err(e),
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() { ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() {
Ok(_) => { Ok(_) => {
let b = decoder.get_mut().take(); let b = decoder.get_mut().take();
@@ -177,7 +165,6 @@ impl ContentDecoder {
} }
Err(e) => Err(e), Err(e) => Err(e),
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() { ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() {
Ok(_) => { Ok(_) => {
let b = decoder.get_mut().take(); let b = decoder.get_mut().take();
@@ -189,14 +176,11 @@ impl ContentDecoder {
} }
Err(e) => Err(e), Err(e) => Err(e),
}, },
_ => Ok(None),
} }
} }
#[allow(unreachable_patterns)]
fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> { fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
match self { match self {
#[cfg(feature = "brotli")]
ContentDecoder::Br(ref mut decoder) => match decoder.write_all(&data) { ContentDecoder::Br(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => { Ok(_) => {
decoder.flush()?; decoder.flush()?;
@@ -209,7 +193,6 @@ impl ContentDecoder {
} }
Err(e) => Err(e), Err(e) => Err(e),
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) { ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => { Ok(_) => {
decoder.flush()?; decoder.flush()?;
@@ -222,7 +205,6 @@ impl ContentDecoder {
} }
Err(e) => Err(e), Err(e) => Err(e),
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => { Ok(_) => {
decoder.flush()?; decoder.flush()?;
@@ -235,7 +217,6 @@ impl ContentDecoder {
} }
Err(e) => Err(e), Err(e) => Err(e),
}, },
_ => Ok(Some(data)),
} }
} }
} }

View File

@@ -5,26 +5,25 @@ use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_threadpool::{run, CpuFuture}; use actix_threadpool::{run, CpuFuture};
#[cfg(feature = "brotli")]
use brotli2::write::BrotliEncoder; use brotli2::write::BrotliEncoder;
use bytes::Bytes; use bytes::Bytes;
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
use flate2::write::{GzEncoder, ZlibEncoder}; use flate2::write::{GzEncoder, ZlibEncoder};
use futures_core::ready;
use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::body::{Body, BodySize, MessageBody, ResponseBody};
use crate::http::header::{ContentEncoding, CONTENT_ENCODING}; use crate::http::header::{ContentEncoding, CONTENT_ENCODING};
use crate::http::{HeaderValue, HttpTryFrom, StatusCode}; use crate::http::{HeaderValue, StatusCode};
use crate::{Error, ResponseHead}; use crate::{Error, ResponseHead};
use super::Writer; use super::Writer;
const INPLACE: usize = 2049; const INPLACE: usize = 1024;
pub struct Encoder<B> { pub struct Encoder<B> {
eof: bool, eof: bool,
body: EncoderBody<B>, body: EncoderBody<B>,
encoder: Option<ContentEncoder>, encoder: Option<ContentEncoder>,
fut: Option<CpuFuture<Result<ContentEncoder, io::Error>>>, fut: Option<CpuFuture<ContentEncoder, io::Error>>,
} }
impl<B: MessageBody> Encoder<B> { impl<B: MessageBody> Encoder<B> {
@@ -96,16 +95,15 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
} }
} }
fn poll_next(&mut self, cx: &mut Context) -> Poll<Option<Result<Bytes, Error>>> { fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
loop { loop {
if self.eof { if self.eof {
return Poll::Ready(None); return Poll::Ready(None);
} }
if let Some(ref mut fut) = self.fut { if let Some(ref mut fut) = self.fut {
let mut encoder = match futures::ready!(Pin::new(fut).poll(cx)) { let mut encoder = match ready!(Pin::new(fut).poll(cx)) {
Ok(Ok(item)) => item, Ok(item) => item,
Ok(Err(e)) => return Poll::Ready(Some(Err(e.into()))),
Err(e) => return Poll::Ready(Some(Err(e.into()))), Err(e) => return Poll::Ready(Some(Err(e.into()))),
}; };
let chunk = encoder.take(); let chunk = encoder.take();
@@ -169,33 +167,27 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
head.headers_mut().insert( head.headers_mut().insert(
CONTENT_ENCODING, CONTENT_ENCODING,
HeaderValue::try_from(Bytes::from_static(encoding.as_str().as_bytes())).unwrap(), HeaderValue::from_static(encoding.as_str()),
); );
} }
enum ContentEncoder { enum ContentEncoder {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
Deflate(ZlibEncoder<Writer>), Deflate(ZlibEncoder<Writer>),
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
Gzip(GzEncoder<Writer>), Gzip(GzEncoder<Writer>),
#[cfg(feature = "brotli")]
Br(BrotliEncoder<Writer>), Br(BrotliEncoder<Writer>),
} }
impl ContentEncoder { impl ContentEncoder {
fn encoder(encoding: ContentEncoding) -> Option<Self> { fn encoder(encoding: ContentEncoding) -> Option<Self> {
match encoding { match encoding {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new( ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new(
Writer::new(), Writer::new(),
flate2::Compression::fast(), flate2::Compression::fast(),
))), ))),
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new( ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new(
Writer::new(), Writer::new(),
flate2::Compression::fast(), flate2::Compression::fast(),
))), ))),
#[cfg(feature = "brotli")]
ContentEncoding::Br => { ContentEncoding::Br => {
Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3))) Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3)))
} }
@@ -206,28 +198,22 @@ impl ContentEncoder {
#[inline] #[inline]
pub(crate) fn take(&mut self) -> Bytes { pub(crate) fn take(&mut self) -> Bytes {
match *self { match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(),
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
} }
} }
fn finish(self) -> Result<Bytes, io::Error> { fn finish(self) -> Result<Bytes, io::Error> {
match self { match self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(encoder) => match encoder.finish() { ContentEncoder::Br(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()), Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err), Err(err) => Err(err),
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoder::Gzip(encoder) => match encoder.finish() { ContentEncoder::Gzip(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()), Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err), Err(err) => Err(err),
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoder::Deflate(encoder) => match encoder.finish() { ContentEncoder::Deflate(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()), Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err), Err(err) => Err(err),
@@ -237,7 +223,6 @@ impl ContentEncoder {
fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
match *self { match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) { ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(err) => { Err(err) => {
@@ -245,7 +230,6 @@ impl ContentEncoder {
Err(err) Err(err)
} }
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(err) => { Err(err) => {
@@ -253,7 +237,6 @@ impl ContentEncoder {
Err(err) Err(err)
} }
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(err) => { Err(err) => {

View File

@@ -19,8 +19,9 @@ impl Writer {
buf: BytesMut::with_capacity(8192), buf: BytesMut::with_capacity(8192),
} }
} }
fn take(&mut self) -> Bytes { fn take(&mut self) -> Bytes {
self.buf.take().freeze() self.buf.split().freeze()
} }
} }
@@ -29,6 +30,7 @@ impl io::Write for Writer {
self.buf.extend_from_slice(buf); self.buf.extend_from_slice(buf);
Ok(buf.len()) Ok(buf.len())
} }
fn flush(&mut self) -> io::Result<()> { fn flush(&mut self) -> io::Result<()> {
Ok(()) Ok(())
} }

View File

@@ -6,23 +6,25 @@ use std::str::Utf8Error;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use std::{fmt, io, result}; use std::{fmt, io, result};
use actix_codec::{Decoder, Encoder};
pub use actix_threadpool::BlockingError;
use actix_utils::framed::DispatcherError as FramedDispatcherError;
use actix_utils::timeout::TimeoutError; use actix_utils::timeout::TimeoutError;
use bytes::BytesMut; use bytes::BytesMut;
use derive_more::{Display, From}; use derive_more::{Display, From};
pub use futures::channel::oneshot::Canceled; pub use futures_channel::oneshot::Canceled;
use http::uri::InvalidUri; use http::uri::InvalidUri;
use http::{header, Error as HttpError, StatusCode}; use http::{header, Error as HttpError, StatusCode};
use httparse; use httparse;
use serde::de::value::Error as DeError; use serde::de::value::Error as DeError;
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use serde_urlencoded::ser::Error as FormError; use serde_urlencoded::ser::Error as FormError;
use tokio_timer::Error as TimerError;
// re-export for convinience // re-export for convinience
use crate::body::Body; use crate::body::Body;
pub use crate::cookie::ParseError as CookieParseError; pub use crate::cookie::ParseError as CookieParseError;
use crate::helpers::Writer; use crate::helpers::Writer;
use crate::response::Response; use crate::response::{Response, ResponseBuilder};
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
/// for actix web operations /// for actix web operations
@@ -60,16 +62,18 @@ impl Error {
/// Error that can be converted to `Response` /// Error that can be converted to `Response`
pub trait ResponseError: fmt::Debug + fmt::Display { pub trait ResponseError: fmt::Debug + fmt::Display {
/// Response's status code
///
/// Internal server error is generated by default.
fn status_code(&self) -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
}
/// Create response for error /// Create response for error
/// ///
/// Internal server error is generated by default. /// Internal server error is generated by default.
fn error_response(&self) -> Response { fn error_response(&self) -> Response {
Response::new(StatusCode::INTERNAL_SERVER_ERROR) let mut resp = Response::new(self.status_code());
}
/// Constructs an error response
fn render_response(&self) -> Response {
let mut resp = self.error_response();
let mut buf = BytesMut::new(); let mut buf = BytesMut::new();
let _ = write!(Writer(&mut buf), "{}", self); let _ = write!(Writer(&mut buf), "{}", self);
resp.headers_mut().insert( resp.headers_mut().insert(
@@ -100,28 +104,18 @@ impl dyn ResponseError + 'static {
} }
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.cause, f) fmt::Display::fmt(&self.cause, f)
} }
} }
impl fmt::Debug for Error { impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{:?}", &self.cause) write!(f, "{:?}", &self.cause)
}
}
impl From<()> for Error {
fn from(_: ()) -> Self {
Error::from(UnitError)
} }
} }
impl std::error::Error for Error { impl std::error::Error for Error {
fn description(&self) -> &str {
"actix-http::Error"
}
fn cause(&self) -> Option<&dyn std::error::Error> { fn cause(&self) -> Option<&dyn std::error::Error> {
None None
} }
@@ -131,6 +125,12 @@ impl std::error::Error for Error {
} }
} }
impl From<()> for Error {
fn from(_: ()) -> Self {
Error::from(UnitError)
}
}
impl From<std::convert::Infallible> for Error { impl From<std::convert::Infallible> for Error {
fn from(_: std::convert::Infallible) -> Self { fn from(_: std::convert::Infallible) -> Self {
// `std::convert::Infallible` indicates an error // `std::convert::Infallible` indicates an error
@@ -155,12 +155,26 @@ impl<T: ResponseError + 'static> From<T> for Error {
} }
} }
/// Convert Response to a Error
impl From<Response> for Error {
fn from(res: Response) -> Error {
InternalError::from_response("", res).into()
}
}
/// Convert ResponseBuilder to a Error
impl From<ResponseBuilder> for Error {
fn from(mut res: ResponseBuilder) -> Error {
InternalError::from_response("", res.finish()).into()
}
}
/// Return `GATEWAY_TIMEOUT` for `TimeoutError` /// Return `GATEWAY_TIMEOUT` for `TimeoutError`
impl<E: ResponseError> ResponseError for TimeoutError<E> { impl<E: ResponseError> ResponseError for TimeoutError<E> {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
match self { match self {
TimeoutError::Service(e) => e.error_response(), TimeoutError::Service(e) => e.status_code(),
TimeoutError::Timeout => Response::new(StatusCode::GATEWAY_TIMEOUT), TimeoutError::Timeout => StatusCode::GATEWAY_TIMEOUT,
} }
} }
} }
@@ -178,31 +192,31 @@ impl ResponseError for JsonError {}
/// `InternalServerError` for `FormError` /// `InternalServerError` for `FormError`
impl ResponseError for FormError {} impl ResponseError for FormError {}
/// `InternalServerError` for `TimerError`
impl ResponseError for TimerError {}
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
/// `InternalServerError` for `openssl::ssl::Error` /// `InternalServerError` for `openssl::ssl::Error`
impl ResponseError for open_ssl::ssl::Error {} impl ResponseError for actix_connect::ssl::openssl::SslError {}
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
/// `InternalServerError` for `openssl::ssl::HandshakeError` /// `InternalServerError` for `openssl::ssl::HandshakeError`
impl<T: std::fmt::Debug> ResponseError for open_ssl::ssl::HandshakeError<T> {} impl<T: std::fmt::Debug> ResponseError for actix_tls::openssl::HandshakeError<T> {}
/// Return `BAD_REQUEST` for `de::value::Error` /// Return `BAD_REQUEST` for `de::value::Error`
impl ResponseError for DeError { impl ResponseError for DeError {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
Response::new(StatusCode::BAD_REQUEST) StatusCode::BAD_REQUEST
} }
} }
/// `InternalServerError` for `Canceled` /// `InternalServerError` for `Canceled`
impl ResponseError for Canceled {} impl ResponseError for Canceled {}
/// `InternalServerError` for `BlockingError`
impl<E: fmt::Debug> ResponseError for BlockingError<E> {}
/// Return `BAD_REQUEST` for `Utf8Error` /// Return `BAD_REQUEST` for `Utf8Error`
impl ResponseError for Utf8Error { impl ResponseError for Utf8Error {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
Response::new(StatusCode::BAD_REQUEST) StatusCode::BAD_REQUEST
} }
} }
@@ -212,26 +226,19 @@ impl ResponseError for HttpError {}
/// Return `InternalServerError` for `io::Error` /// Return `InternalServerError` for `io::Error`
impl ResponseError for io::Error { impl ResponseError for io::Error {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
match self.kind() { match self.kind() {
io::ErrorKind::NotFound => Response::new(StatusCode::NOT_FOUND), io::ErrorKind::NotFound => StatusCode::NOT_FOUND,
io::ErrorKind::PermissionDenied => Response::new(StatusCode::FORBIDDEN), io::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN,
_ => Response::new(StatusCode::INTERNAL_SERVER_ERROR), _ => StatusCode::INTERNAL_SERVER_ERROR,
} }
} }
} }
/// `BadRequest` for `InvalidHeaderValue` /// `BadRequest` for `InvalidHeaderValue`
impl ResponseError for header::InvalidHeaderValue { impl ResponseError for header::InvalidHeaderValue {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
Response::new(StatusCode::BAD_REQUEST) StatusCode::BAD_REQUEST
}
}
/// `BadRequest` for `InvalidHeaderValue`
impl ResponseError for header::InvalidHeaderValueBytes {
fn error_response(&self) -> Response {
Response::new(StatusCode::BAD_REQUEST)
} }
} }
@@ -274,8 +281,8 @@ pub enum ParseError {
/// Return `BadRequest` for `ParseError` /// Return `BadRequest` for `ParseError`
impl ResponseError for ParseError { impl ResponseError for ParseError {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
Response::new(StatusCode::BAD_REQUEST) StatusCode::BAD_REQUEST
} }
} }
@@ -361,12 +368,15 @@ impl From<io::Error> for PayloadError {
} }
} }
impl From<Canceled> for PayloadError { impl From<BlockingError<io::Error>> for PayloadError {
fn from(_: Canceled) -> Self { fn from(err: BlockingError<io::Error>) -> Self {
PayloadError::Io(io::Error::new( match err {
io::ErrorKind::Other, BlockingError::Error(e) => PayloadError::Io(e),
"Operation is canceled", BlockingError::Canceled => PayloadError::Io(io::Error::new(
)) io::ErrorKind::Other,
"Operation is canceled",
)),
}
} }
} }
@@ -375,18 +385,18 @@ impl From<Canceled> for PayloadError {
/// - `Overflow` returns `PayloadTooLarge` /// - `Overflow` returns `PayloadTooLarge`
/// - Other errors returns `BadRequest` /// - Other errors returns `BadRequest`
impl ResponseError for PayloadError { impl ResponseError for PayloadError {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
match *self { match *self {
PayloadError::Overflow => Response::new(StatusCode::PAYLOAD_TOO_LARGE), PayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE,
_ => Response::new(StatusCode::BAD_REQUEST), _ => StatusCode::BAD_REQUEST,
} }
} }
} }
/// Return `BadRequest` for `cookie::ParseError` /// Return `BadRequest` for `cookie::ParseError`
impl ResponseError for crate::cookie::ParseError { impl ResponseError for crate::cookie::ParseError {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
Response::new(StatusCode::BAD_REQUEST) StatusCode::BAD_REQUEST
} }
} }
@@ -450,11 +460,19 @@ pub enum ContentTypeError {
/// Return `BadRequest` for `ContentTypeError` /// Return `BadRequest` for `ContentTypeError`
impl ResponseError for ContentTypeError { impl ResponseError for ContentTypeError {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
Response::new(StatusCode::BAD_REQUEST) StatusCode::BAD_REQUEST
} }
} }
impl<E, U: Encoder + Decoder> ResponseError for FramedDispatcherError<E, U>
where
E: fmt::Debug + fmt::Display,
<U as Encoder>::Error: fmt::Debug,
<U as Decoder>::Error: fmt::Debug,
{
}
/// Helper type that can wrap any error and generate custom response. /// Helper type that can wrap any error and generate custom response.
/// ///
/// In following example any `io::Error` will be converted into "BAD REQUEST" /// In following example any `io::Error` will be converted into "BAD REQUEST"
@@ -462,14 +480,12 @@ impl ResponseError for ContentTypeError {
/// default. /// default.
/// ///
/// ```rust /// ```rust
/// # extern crate actix_http;
/// # use std::io; /// # use std::io;
/// # use actix_http::*; /// # use actix_http::*;
/// ///
/// fn index(req: Request) -> Result<&'static str> { /// fn index(req: Request) -> Result<&'static str> {
/// Err(error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "error"))) /// Err(error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "error")))
/// } /// }
/// # fn main() {}
/// ``` /// ```
pub struct InternalError<T> { pub struct InternalError<T> {
cause: T, cause: T,
@@ -503,7 +519,7 @@ impl<T> fmt::Debug for InternalError<T>
where where
T: fmt::Debug + 'static, T: fmt::Debug + 'static,
{ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.cause, f) fmt::Debug::fmt(&self.cause, f)
} }
} }
@@ -512,7 +528,7 @@ impl<T> fmt::Display for InternalError<T>
where where
T: fmt::Display + 'static, T: fmt::Display + 'static,
{ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.cause, f) fmt::Display::fmt(&self.cause, f)
} }
} }
@@ -521,6 +537,19 @@ impl<T> ResponseError for InternalError<T>
where where
T: fmt::Debug + fmt::Display + 'static, T: fmt::Debug + fmt::Display + 'static,
{ {
fn status_code(&self) -> StatusCode {
match self.status {
InternalErrorType::Status(st) => st,
InternalErrorType::Response(ref resp) => {
if let Some(resp) = resp.borrow().as_ref() {
resp.head().status
} else {
StatusCode::INTERNAL_SERVER_ERROR
}
}
}
}
fn error_response(&self) -> Response { fn error_response(&self) -> Response {
match self.status { match self.status {
InternalErrorType::Status(st) => { InternalErrorType::Status(st) => {
@@ -542,18 +571,6 @@ where
} }
} }
} }
/// Constructs an error response
fn render_response(&self) -> Response {
self.error_response()
}
}
/// Convert Response to a Error
impl From<Response> for Error {
fn from(res: Response) -> Error {
InternalError::from_response("", res).into()
}
} }
/// Helper function that creates wrapper of any error and generate *BAD /// Helper function that creates wrapper of any error and generate *BAD
@@ -946,24 +963,15 @@ where
InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into() InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into()
} }
#[cfg(feature = "fail")] #[cfg(feature = "failure")]
mod failure_integration { /// Compatibility for `failure::Error`
use super::*; impl ResponseError for fail_ure::Error {}
/// Compatibility for `failure::Error`
impl ResponseError for failure::Error {
fn error_response(&self) -> Response {
Response::new(StatusCode::INTERNAL_SERVER_ERROR)
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use http::{Error as HttpError, StatusCode}; use http::{Error as HttpError, StatusCode};
use httparse; use httparse;
use std::error::Error as StdError;
use std::io; use std::io;
#[test] #[test]
@@ -992,7 +1000,7 @@ mod tests {
#[test] #[test]
fn test_error_cause() { fn test_error_cause() {
let orig = io::Error::new(io::ErrorKind::Other, "other"); let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned(); let desc = orig.to_string();
let e = Error::from(orig); let e = Error::from(orig);
assert_eq!(format!("{}", e.as_response_error()), desc); assert_eq!(format!("{}", e.as_response_error()), desc);
} }
@@ -1000,7 +1008,7 @@ mod tests {
#[test] #[test]
fn test_error_display() { fn test_error_display() {
let orig = io::Error::new(io::ErrorKind::Other, "other"); let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned(); let desc = orig.to_string();
let e = Error::from(orig); let e = Error::from(orig);
assert_eq!(format!("{}", e), desc); assert_eq!(format!("{}", e), desc);
} }
@@ -1042,7 +1050,7 @@ mod tests {
match ParseError::from($from) { match ParseError::from($from) {
e @ $error => { e @ $error => {
let desc = format!("{}", e); let desc = format!("{}", e);
assert_eq!(desc, format!("IO error: {}", $from.description())); assert_eq!(desc, format!("IO error: {}", $from));
} }
_ => unreachable!("{:?}", $from), _ => unreachable!("{:?}", $from),
} }

View File

@@ -1,12 +1,12 @@
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use std::fmt; use std::fmt;
use hashbrown::HashMap; use fxhash::FxHashMap;
#[derive(Default)] #[derive(Default)]
/// A type map of request extensions. /// A type map of request extensions.
pub struct Extensions { pub struct Extensions {
map: HashMap<TypeId, Box<dyn Any>>, map: FxHashMap<TypeId, Box<dyn Any>>,
} }
impl Extensions { impl Extensions {
@@ -14,7 +14,7 @@ impl Extensions {
#[inline] #[inline]
pub fn new() -> Extensions { pub fn new() -> Extensions {
Extensions { Extensions {
map: HashMap::default(), map: FxHashMap::default(),
} }
} }
@@ -65,7 +65,7 @@ impl Extensions {
} }
impl fmt::Debug for Extensions { impl fmt::Debug for Extensions {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Extensions").finish() f.debug_struct("Extensions").finish()
} }
} }

View File

@@ -1,13 +1,8 @@
#![allow(unused_imports, unused_variables, dead_code)] use std::io;
use std::io::{self, Write};
use std::rc::Rc;
use actix_codec::{Decoder, Encoder}; use actix_codec::{Decoder, Encoder};
use bitflags::bitflags; use bitflags::bitflags;
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use http::header::{
HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE,
};
use http::{Method, Version}; use http::{Method, Version};
use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType};
@@ -16,11 +11,7 @@ use super::{Message, MessageType};
use crate::body::BodySize; use crate::body::BodySize;
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
use crate::error::{ParseError, PayloadError}; use crate::error::{ParseError, PayloadError};
use crate::header::HeaderMap; use crate::message::{ConnectionType, RequestHeadType, ResponseHead};
use crate::helpers;
use crate::message::{
ConnectionType, Head, MessagePool, RequestHead, RequestHeadType, ResponseHead,
};
bitflags! { bitflags! {
struct Flags: u8 { struct Flags: u8 {
@@ -30,8 +21,6 @@ bitflags! {
} }
} }
const AVERAGE_HEADER_SIZE: usize = 30;
/// HTTP/1 Codec /// HTTP/1 Codec
pub struct ClientCodec { pub struct ClientCodec {
inner: ClientCodecInner, inner: ClientCodecInner,
@@ -51,7 +40,6 @@ struct ClientCodecInner {
// encoder part // encoder part
flags: Flags, flags: Flags,
headers_size: u32,
encoder: encoder::MessageEncoder<RequestHeadType>, encoder: encoder::MessageEncoder<RequestHeadType>,
} }
@@ -80,7 +68,6 @@ impl ClientCodec {
ctype: ConnectionType::Close, ctype: ConnectionType::Close,
flags, flags,
headers_size: 0,
encoder: encoder::MessageEncoder::default(), encoder: encoder::MessageEncoder::default(),
}, },
} }

View File

@@ -1,12 +1,9 @@
#![allow(unused_imports, unused_variables, dead_code)] use std::{fmt, io};
use std::io::Write;
use std::{fmt, io, net};
use actix_codec::{Decoder, Encoder}; use actix_codec::{Decoder, Encoder};
use bitflags::bitflags; use bitflags::bitflags;
use bytes::{BufMut, Bytes, BytesMut}; use bytes::BytesMut;
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, Version};
use http::{Method, StatusCode, Version};
use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType};
use super::{decoder, encoder}; use super::{decoder, encoder};
@@ -14,8 +11,7 @@ use super::{Message, MessageType};
use crate::body::BodySize; use crate::body::BodySize;
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
use crate::error::ParseError; use crate::error::ParseError;
use crate::helpers; use crate::message::ConnectionType;
use crate::message::{ConnectionType, Head, ResponseHead};
use crate::request::Request; use crate::request::Request;
use crate::response::Response; use crate::response::Response;
@@ -27,8 +23,6 @@ bitflags! {
} }
} }
const AVERAGE_HEADER_SIZE: usize = 30;
/// HTTP/1 Codec /// HTTP/1 Codec
pub struct Codec { pub struct Codec {
config: ServiceConfig, config: ServiceConfig,
@@ -49,7 +43,7 @@ impl Default for Codec {
} }
impl fmt::Debug for Codec { impl fmt::Debug for Codec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "h1::Codec({:?})", self.flags) write!(f, "h1::Codec({:?})", self.flags)
} }
} }
@@ -176,7 +170,6 @@ impl Encoder for Codec {
}; };
// encode message // encode message
let len = dst.len();
self.encoder.encode( self.encoder.encode(
dst, dst,
&mut res, &mut res,
@@ -202,17 +195,11 @@ impl Encoder for Codec {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{cmp, io}; use bytes::BytesMut;
use http::Method;
use actix_codec::{AsyncRead, AsyncWrite};
use bytes::{Buf, Bytes, BytesMut};
use http::{Method, Version};
use super::*; use super::*;
use crate::error::ParseError;
use crate::h1::Message;
use crate::httpmessage::HttpMessage; use crate::httpmessage::HttpMessage;
use crate::request::Request;
#[test] #[test]
fn test_http_request_chunked_payload_and_next_message() { fn test_http_request_chunked_payload_and_next_message() {

View File

@@ -1,14 +1,13 @@
use std::future::Future; use std::convert::TryFrom;
use std::io; use std::io;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::pin::Pin; use std::task::Poll;
use std::task::{Context, Poll};
use actix_codec::Decoder; use actix_codec::Decoder;
use bytes::{Bytes, BytesMut}; use bytes::{Buf, Bytes, BytesMut};
use http::header::{HeaderName, HeaderValue}; use http::header::{HeaderName, HeaderValue};
use http::{header, HttpTryFrom, Method, StatusCode, Uri, Version}; use http::{header, Method, StatusCode, Uri, Version};
use httparse; use httparse;
use log::{debug, error, trace}; use log::{debug, error, trace};
@@ -81,8 +80,8 @@ pub(crate) trait MessageType: Sized {
// Unsafe: httparse check header value for valid utf-8 // Unsafe: httparse check header value for valid utf-8
let value = unsafe { let value = unsafe {
HeaderValue::from_shared_unchecked( HeaderValue::from_maybe_shared_unchecked(
slice.slice(idx.value.0, idx.value.1), slice.slice(idx.value.0..idx.value.1),
) )
}; };
match name { match name {
@@ -186,6 +185,7 @@ impl MessageType for Request {
&mut self.head_mut().headers &mut self.head_mut().headers
} }
#[allow(clippy::uninit_assumed_init)]
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> { fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> {
// Unsafe: we read only this data only after httparse parses headers into. // Unsafe: we read only this data only after httparse parses headers into.
// performance bump for pipeline benchmarks. // performance bump for pipeline benchmarks.
@@ -193,7 +193,7 @@ impl MessageType for Request {
unsafe { MaybeUninit::uninit().assume_init() }; unsafe { MaybeUninit::uninit().assume_init() };
let (len, method, uri, ver, h_len) = { let (len, method, uri, ver, h_len) = {
let mut parsed: [httparse::Header; MAX_HEADERS] = let mut parsed: [httparse::Header<'_>; MAX_HEADERS] =
unsafe { MaybeUninit::uninit().assume_init() }; unsafe { MaybeUninit::uninit().assume_init() };
let mut req = httparse::Request::new(&mut parsed); let mut req = httparse::Request::new(&mut parsed);
@@ -261,6 +261,7 @@ impl MessageType for ResponseHead {
&mut self.headers &mut self.headers
} }
#[allow(clippy::uninit_assumed_init)]
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> { fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> {
// Unsafe: we read only this data only after httparse parses headers into. // Unsafe: we read only this data only after httparse parses headers into.
// performance bump for pipeline benchmarks. // performance bump for pipeline benchmarks.
@@ -268,7 +269,7 @@ impl MessageType for ResponseHead {
unsafe { MaybeUninit::uninit().assume_init() }; unsafe { MaybeUninit::uninit().assume_init() };
let (len, ver, status, h_len) = { let (len, ver, status, h_len) = {
let mut parsed: [httparse::Header; MAX_HEADERS] = let mut parsed: [httparse::Header<'_>; MAX_HEADERS] =
unsafe { MaybeUninit::uninit().assume_init() }; unsafe { MaybeUninit::uninit().assume_init() };
let mut res = httparse::Response::new(&mut parsed); let mut res = httparse::Response::new(&mut parsed);
@@ -327,7 +328,7 @@ pub(crate) struct HeaderIndex {
impl HeaderIndex { impl HeaderIndex {
pub(crate) fn record( pub(crate) fn record(
bytes: &[u8], bytes: &[u8],
headers: &[httparse::Header], headers: &[httparse::Header<'_>],
indices: &mut [HeaderIndex], indices: &mut [HeaderIndex],
) { ) {
let bytes_ptr = bytes.as_ptr() as usize; let bytes_ptr = bytes.as_ptr() as usize;
@@ -430,7 +431,7 @@ impl Decoder for PayloadDecoder {
let len = src.len() as u64; let len = src.len() as u64;
let buf; let buf;
if *remaining > len { if *remaining > len {
buf = src.take().freeze(); buf = src.split().freeze();
*remaining -= len; *remaining -= len;
} else { } else {
buf = src.split_to(*remaining as usize).freeze(); buf = src.split_to(*remaining as usize).freeze();
@@ -465,7 +466,7 @@ impl Decoder for PayloadDecoder {
if src.is_empty() { if src.is_empty() {
Ok(None) Ok(None)
} else { } else {
Ok(Some(PayloadItem::Chunk(src.take().freeze()))) Ok(Some(PayloadItem::Chunk(src.split().freeze())))
} }
} }
} }
@@ -476,7 +477,7 @@ macro_rules! byte (
($rdr:ident) => ({ ($rdr:ident) => ({
if $rdr.len() > 0 { if $rdr.len() > 0 {
let b = $rdr[0]; let b = $rdr[0];
$rdr.split_to(1); $rdr.advance(1);
b b
} else { } else {
return Poll::Pending return Poll::Pending
@@ -583,7 +584,7 @@ impl ChunkedState {
} else { } else {
let slice; let slice;
if *rem > len { if *rem > len {
slice = rdr.take().freeze(); slice = rdr.split().freeze();
*rem -= len; *rem -= len;
} else { } else {
slice = rdr.split_to(*rem as usize).freeze(); slice = rdr.split_to(*rem as usize).freeze();

View File

@@ -2,16 +2,14 @@ use std::collections::VecDeque;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::time::Instant; use std::{fmt, io, net};
use std::{fmt, io, io::Write, net};
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts}; use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts};
use actix_server_config::IoStream; use actix_rt::time::{delay_until, Delay, Instant};
use actix_service::Service; use actix_service::Service;
use bitflags::bitflags; use bitflags::bitflags;
use bytes::{BufMut, BytesMut}; use bytes::{Buf, BytesMut};
use log::{error, trace}; use log::{error, trace};
use tokio_timer::{delay, Delay};
use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::body::{Body, BodySize, MessageBody, ResponseBody};
use crate::cloneable::CloneableService; use crate::cloneable::CloneableService;
@@ -168,7 +166,7 @@ impl PartialEq for PollResponse {
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U> impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@@ -186,6 +184,7 @@ where
expect: CloneableService<X>, expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>, upgrade: Option<CloneableService<U>>,
on_connect: Option<Box<dyn DataFactory>>, on_connect: Option<Box<dyn DataFactory>>,
peer_addr: Option<net::SocketAddr>,
) -> Self { ) -> Self {
Dispatcher::with_timeout( Dispatcher::with_timeout(
stream, stream,
@@ -197,6 +196,7 @@ where
expect, expect,
upgrade, upgrade,
on_connect, on_connect,
peer_addr,
) )
} }
@@ -211,6 +211,7 @@ where
expect: CloneableService<X>, expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>, upgrade: Option<CloneableService<U>>,
on_connect: Option<Box<dyn DataFactory>>, on_connect: Option<Box<dyn DataFactory>>,
peer_addr: Option<net::SocketAddr>,
) -> Self { ) -> Self {
let keepalive = config.keep_alive_enabled(); let keepalive = config.keep_alive_enabled();
let flags = if keepalive { let flags = if keepalive {
@@ -234,7 +235,6 @@ where
payload: None, payload: None,
state: State::None, state: State::None,
error: None, error: None,
peer_addr: io.peer_addr(),
messages: VecDeque::new(), messages: VecDeque::new(),
io, io,
codec, codec,
@@ -244,6 +244,7 @@ where
upgrade, upgrade,
on_connect, on_connect,
flags, flags,
peer_addr,
ka_expire, ka_expire,
ka_timer, ka_timer,
}), }),
@@ -253,7 +254,7 @@ where
impl<T, S, B, X, U> InnerDispatcher<T, S, B, X, U> impl<T, S, B, X, U> InnerDispatcher<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@@ -263,7 +264,7 @@ where
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>, U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
{ {
fn can_read(&self, cx: &mut Context) -> bool { fn can_read(&self, cx: &mut Context<'_>) -> bool {
if self if self
.flags .flags
.intersects(Flags::READ_DISCONNECT | Flags::UPGRADE) .intersects(Flags::READ_DISCONNECT | Flags::UPGRADE)
@@ -289,7 +290,7 @@ where
/// ///
/// true - got whouldblock /// true - got whouldblock
/// false - didnt get whouldblock /// false - didnt get whouldblock
fn poll_flush(&mut self, cx: &mut Context) -> Result<bool, DispatchError> { fn poll_flush(&mut self, cx: &mut Context<'_>) -> Result<bool, DispatchError> {
if self.write_buf.is_empty() { if self.write_buf.is_empty() {
return Ok(false); return Ok(false);
} }
@@ -311,7 +312,7 @@ where
} }
Poll::Pending => { Poll::Pending => {
if written > 0 { if written > 0 {
let _ = self.write_buf.split_to(written); self.write_buf.advance(written);
} }
return Ok(true); return Ok(true);
} }
@@ -321,7 +322,7 @@ where
if written == self.write_buf.len() { if written == self.write_buf.len() {
unsafe { self.write_buf.set_len(0) } unsafe { self.write_buf.set_len(0) }
} else { } else {
let _ = self.write_buf.split_to(written); self.write_buf.advance(written);
} }
Ok(false) Ok(false)
} }
@@ -354,7 +355,7 @@ where
fn poll_response( fn poll_response(
&mut self, &mut self,
cx: &mut Context, cx: &mut Context<'_>,
) -> Result<PollResponse, DispatchError> { ) -> Result<PollResponse, DispatchError> {
loop { loop {
let state = match self.state { let state = match self.state {
@@ -458,7 +459,7 @@ where
fn handle_request( fn handle_request(
&mut self, &mut self,
req: Request, req: Request,
cx: &mut Context, cx: &mut Context<'_>,
) -> Result<State<S, B, X>, DispatchError> { ) -> Result<State<S, B, X>, DispatchError> {
// Handle `EXPECT: 100-Continue` header // Handle `EXPECT: 100-Continue` header
let req = if req.head().expect() { let req = if req.head().expect() {
@@ -499,7 +500,7 @@ where
/// Process one incoming requests /// Process one incoming requests
pub(self) fn poll_request( pub(self) fn poll_request(
&mut self, &mut self,
cx: &mut Context, cx: &mut Context<'_>,
) -> Result<bool, DispatchError> { ) -> Result<bool, DispatchError> {
// limit a mount of non processed requests // limit a mount of non processed requests
if self.messages.len() >= MAX_PIPELINED_MESSAGES || !self.can_read(cx) { if self.messages.len() >= MAX_PIPELINED_MESSAGES || !self.can_read(cx) {
@@ -603,12 +604,12 @@ where
} }
/// keep-alive timer /// keep-alive timer
fn poll_keepalive(&mut self, cx: &mut Context) -> Result<(), DispatchError> { fn poll_keepalive(&mut self, cx: &mut Context<'_>) -> Result<(), DispatchError> {
if self.ka_timer.is_none() { if self.ka_timer.is_none() {
// shutdown timeout // shutdown timeout
if self.flags.contains(Flags::SHUTDOWN) { if self.flags.contains(Flags::SHUTDOWN) {
if let Some(interval) = self.codec.config().client_disconnect_timer() { if let Some(interval) = self.codec.config().client_disconnect_timer() {
self.ka_timer = Some(delay(interval)); self.ka_timer = Some(delay_until(interval));
} else { } else {
self.flags.insert(Flags::READ_DISCONNECT); self.flags.insert(Flags::READ_DISCONNECT);
if let Some(mut payload) = self.payload.take() { if let Some(mut payload) = self.payload.take() {
@@ -682,7 +683,7 @@ where
impl<T, S, B, X, U> Unpin for Dispatcher<T, S, B, X, U> impl<T, S, B, X, U> Unpin for Dispatcher<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@@ -696,7 +697,7 @@ where
impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U> impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@@ -709,7 +710,7 @@ where
type Output = Result<(), DispatchError>; type Output = Result<(), DispatchError>;
#[inline] #[inline]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.as_mut().inner { match self.as_mut().inner {
DispatcherState::Normal(ref mut inner) => { DispatcherState::Normal(ref mut inner) => {
inner.poll_keepalive(cx)?; inner.poll_keepalive(cx)?;
@@ -749,8 +750,10 @@ where
}; };
loop { loop {
if inner.write_buf.remaining_mut() < LW_BUFFER_SIZE { let remaining =
inner.write_buf.reserve(HW_BUFFER_SIZE); inner.write_buf.capacity() - inner.write_buf.len();
if remaining < LW_BUFFER_SIZE {
inner.write_buf.reserve(HW_BUFFER_SIZE - remaining);
} }
let result = inner.poll_response(cx)?; let result = inner.poll_response(cx)?;
let drain = result == PollResponse::DrainWriteBuf; let drain = result == PollResponse::DrainWriteBuf;
@@ -831,7 +834,7 @@ where
} }
fn read_available<T>( fn read_available<T>(
cx: &mut Context, cx: &mut Context<'_>,
io: &mut T, io: &mut T,
buf: &mut BytesMut, buf: &mut BytesMut,
) -> Result<Option<bool>, io::Error> ) -> Result<Option<bool>, io::Error>
@@ -840,8 +843,9 @@ where
{ {
let mut read_some = false; let mut read_some = false;
loop { loop {
if buf.remaining_mut() < LW_BUFFER_SIZE { let remaining = buf.capacity() - buf.len();
buf.reserve(HW_BUFFER_SIZE); if remaining < LW_BUFFER_SIZE {
buf.reserve(HW_BUFFER_SIZE - remaining);
} }
match read(cx, io, buf) { match read(cx, io, buf) {
@@ -873,7 +877,7 @@ where
} }
fn read<T>( fn read<T>(
cx: &mut Context, cx: &mut Context<'_>,
io: &mut T, io: &mut T,
buf: &mut BytesMut, buf: &mut BytesMut,
) -> Poll<Result<usize, io::Error>> ) -> Poll<Result<usize, io::Error>>
@@ -886,17 +890,16 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_service::IntoService; use actix_service::IntoService;
use futures::future::{lazy, ok}; use futures_util::future::{lazy, ok};
use super::*; use super::*;
use crate::error::Error; use crate::error::Error;
use crate::h1::{ExpectHandler, UpgradeHandler}; use crate::h1::{ExpectHandler, UpgradeHandler};
use crate::test::TestBuffer; use crate::test::TestBuffer;
#[test] #[actix_rt::test]
fn test_req_parse_err() { async fn test_req_parse_err() {
let mut sys = actix_rt::System::new("test"); lazy(|cx| {
let _ = sys.block_on(lazy(|cx| {
let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n");
let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<TestBuffer>>::new( let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<TestBuffer>>::new(
@@ -908,6 +911,7 @@ mod tests {
CloneableService::new(ExpectHandler), CloneableService::new(ExpectHandler),
None, None,
None, None,
None,
); );
match Pin::new(&mut h1).poll(cx) { match Pin::new(&mut h1).poll(cx) {
Poll::Pending => panic!(), Poll::Pending => panic!(),
@@ -918,7 +922,7 @@ mod tests {
assert!(inner.flags.contains(Flags::READ_DISCONNECT)); assert!(inner.flags.contains(Flags::READ_DISCONNECT));
assert_eq!(&inner.io.write_buf[..26], b"HTTP/1.1 400 Bad Request\r\n"); assert_eq!(&inner.io.write_buf[..26], b"HTTP/1.1 400 Bad Request\r\n");
} }
ok::<_, ()>(()) })
})); .await;
} }
} }

View File

@@ -1,23 +1,18 @@
#![allow(unused_imports, unused_variables, dead_code)]
use std::fmt::Write as FmtWrite;
use std::io::Write; use std::io::Write;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc; use std::ptr::copy_nonoverlapping;
use std::str::FromStr; use std::slice::from_raw_parts_mut;
use std::{cmp, fmt, io, mem}; use std::{cmp, io};
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{buf::BufMutExt, BufMut, BytesMut};
use crate::body::BodySize; use crate::body::BodySize;
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
use crate::header::{map, ContentEncoding}; use crate::header::map;
use crate::helpers; use crate::helpers;
use crate::http::header::{ use crate::http::header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, use crate::http::{HeaderMap, StatusCode, Version};
}; use crate::message::{ConnectionType, RequestHeadType};
use crate::http::{HeaderMap, Method, StatusCode, Version};
use crate::message::{ConnectionType, Head, RequestHead, RequestHeadType, ResponseHead};
use crate::request::Request;
use crate::response::Response; use crate::response::Response;
const AVERAGE_HEADER_SIZE: usize = 30; const AVERAGE_HEADER_SIZE: usize = 30;
@@ -106,6 +101,7 @@ pub(crate) trait MessageType: Sized {
} else { } else {
dst.put_slice(b"\r\ncontent-length: "); dst.put_slice(b"\r\ncontent-length: ");
} }
#[allow(clippy::write_with_newline)]
write!(dst.writer(), "{}\r\n", len)?; write!(dst.writer(), "{}\r\n", len)?;
} }
BodySize::None => dst.put_slice(b"\r\n"), BodySize::None => dst.put_slice(b"\r\n"),
@@ -144,8 +140,8 @@ pub(crate) trait MessageType: Sized {
// write headers // write headers
let mut pos = 0; let mut pos = 0;
let mut has_date = false; let mut has_date = false;
let mut remaining = dst.remaining_mut(); let mut remaining = dst.capacity() - dst.len();
let mut buf = unsafe { &mut *(dst.bytes_mut() as *mut [u8]) }; let mut buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
for (key, value) in headers { for (key, value) in headers {
match *key { match *key {
CONNECTION => continue, CONNECTION => continue,
@@ -159,61 +155,67 @@ pub(crate) trait MessageType: Sized {
match value { match value {
map::Value::One(ref val) => { map::Value::One(ref val) => {
let v = val.as_ref(); let v = val.as_ref();
let len = k.len() + v.len() + 4; let v_len = v.len();
let k_len = k.len();
let len = k_len + v_len + 4;
if len > remaining { if len > remaining {
unsafe { unsafe {
dst.advance_mut(pos); dst.advance_mut(pos);
} }
pos = 0; pos = 0;
dst.reserve(len * 2); dst.reserve(len * 2);
remaining = dst.remaining_mut(); remaining = dst.capacity() - dst.len();
unsafe { buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
buf = &mut *(dst.bytes_mut() as *mut _);
}
} }
// use upper Camel-Case // use upper Camel-Case
if camel_case { unsafe {
write_camel_case(k, &mut buf[pos..pos + k.len()]); if camel_case {
} else { write_camel_case(k, from_raw_parts_mut(buf, k_len))
buf[pos..pos + k.len()].copy_from_slice(k); } else {
write_data(k, buf, k_len)
}
buf = buf.add(k_len);
write_data(b": ", buf, 2);
buf = buf.add(2);
write_data(v, buf, v_len);
buf = buf.add(v_len);
write_data(b"\r\n", buf, 2);
buf = buf.add(2);
pos += len;
remaining -= len;
} }
pos += k.len();
buf[pos..pos + 2].copy_from_slice(b": ");
pos += 2;
buf[pos..pos + v.len()].copy_from_slice(v);
pos += v.len();
buf[pos..pos + 2].copy_from_slice(b"\r\n");
pos += 2;
remaining -= len;
} }
map::Value::Multi(ref vec) => { map::Value::Multi(ref vec) => {
for val in vec { for val in vec {
let v = val.as_ref(); let v = val.as_ref();
let len = k.len() + v.len() + 4; let v_len = v.len();
let k_len = k.len();
let len = k_len + v_len + 4;
if len > remaining { if len > remaining {
unsafe { unsafe {
dst.advance_mut(pos); dst.advance_mut(pos);
} }
pos = 0; pos = 0;
dst.reserve(len * 2); dst.reserve(len * 2);
remaining = dst.remaining_mut(); remaining = dst.capacity() - dst.len();
unsafe { buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
buf = &mut *(dst.bytes_mut() as *mut _);
}
} }
// use upper Camel-Case // use upper Camel-Case
if camel_case { unsafe {
write_camel_case(k, &mut buf[pos..pos + k.len()]); if camel_case {
} else { write_camel_case(k, from_raw_parts_mut(buf, k_len));
buf[pos..pos + k.len()].copy_from_slice(k); } else {
} write_data(k, buf, k_len);
pos += k.len(); }
buf[pos..pos + 2].copy_from_slice(b": "); buf = buf.add(k_len);
pos += 2; write_data(b": ", buf, 2);
buf[pos..pos + v.len()].copy_from_slice(v); buf = buf.add(2);
pos += v.len(); write_data(v, buf, v_len);
buf[pos..pos + 2].copy_from_slice(b"\r\n"); buf = buf.add(v_len);
pos += 2; write_data(b"\r\n", buf, 2);
buf = buf.add(2);
};
pos += len;
remaining -= len; remaining -= len;
} }
} }
@@ -298,6 +300,12 @@ impl MessageType for RequestHeadType {
Version::HTTP_10 => "HTTP/1.0", Version::HTTP_10 => "HTTP/1.0",
Version::HTTP_11 => "HTTP/1.1", Version::HTTP_11 => "HTTP/1.1",
Version::HTTP_2 => "HTTP/2.0", Version::HTTP_2 => "HTTP/2.0",
Version::HTTP_3 => "HTTP/3.0",
_ =>
return Err(io::Error::new(
io::ErrorKind::Other,
"unsupported version"
)),
} }
) )
.map_err(|e| io::Error::new(io::ErrorKind::Other, e)) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))
@@ -479,6 +487,10 @@ impl<'a> io::Write for Writer<'a> {
} }
} }
unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) {
copy_nonoverlapping(value.as_ptr(), buf, len);
}
fn write_camel_case(value: &[u8], buffer: &mut [u8]) { fn write_camel_case(value: &[u8], buffer: &mut [u8]) {
let mut index = 0; let mut index = 0;
let key = value; let key = value;
@@ -509,12 +521,14 @@ fn write_camel_case(value: &[u8], buffer: &mut [u8]) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::rc::Rc;
use bytes::Bytes; use bytes::Bytes;
//use std::rc::Rc; use http::header::AUTHORIZATION;
use super::*; use super::*;
use crate::http::header::{HeaderValue, CONTENT_TYPE}; use crate::http::header::{HeaderValue, CONTENT_TYPE};
use http::header::AUTHORIZATION; use crate::RequestHead;
#[test] #[test]
fn test_chunked_te() { fn test_chunked_te() {
@@ -525,7 +539,7 @@ mod tests {
assert!(enc.encode(b"", &mut bytes).ok().unwrap()); assert!(enc.encode(b"", &mut bytes).ok().unwrap());
} }
assert_eq!( assert_eq!(
bytes.take().freeze(), bytes.split().freeze(),
Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n") Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n")
); );
} }
@@ -548,7 +562,8 @@ mod tests {
ConnectionType::Close, ConnectionType::Close,
&ServiceConfig::default(), &ServiceConfig::default(),
); );
let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap(); let data =
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(data.contains("Content-Length: 0\r\n")); assert!(data.contains("Content-Length: 0\r\n"));
assert!(data.contains("Connection: close\r\n")); assert!(data.contains("Connection: close\r\n"));
assert!(data.contains("Content-Type: plain/text\r\n")); assert!(data.contains("Content-Type: plain/text\r\n"));
@@ -561,7 +576,8 @@ mod tests {
ConnectionType::KeepAlive, ConnectionType::KeepAlive,
&ServiceConfig::default(), &ServiceConfig::default(),
); );
let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap(); let data =
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(data.contains("Transfer-Encoding: chunked\r\n")); assert!(data.contains("Transfer-Encoding: chunked\r\n"));
assert!(data.contains("Content-Type: plain/text\r\n")); assert!(data.contains("Content-Type: plain/text\r\n"));
assert!(data.contains("Date: date\r\n")); assert!(data.contains("Date: date\r\n"));
@@ -573,7 +589,8 @@ mod tests {
ConnectionType::KeepAlive, ConnectionType::KeepAlive,
&ServiceConfig::default(), &ServiceConfig::default(),
); );
let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap(); let data =
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(data.contains("Content-Length: 100\r\n")); assert!(data.contains("Content-Length: 100\r\n"));
assert!(data.contains("Content-Type: plain/text\r\n")); assert!(data.contains("Content-Type: plain/text\r\n"));
assert!(data.contains("Date: date\r\n")); assert!(data.contains("Date: date\r\n"));
@@ -594,7 +611,8 @@ mod tests {
ConnectionType::KeepAlive, ConnectionType::KeepAlive,
&ServiceConfig::default(), &ServiceConfig::default(),
); );
let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap(); let data =
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(data.contains("transfer-encoding: chunked\r\n")); assert!(data.contains("transfer-encoding: chunked\r\n"));
assert!(data.contains("content-type: xml\r\n")); assert!(data.contains("content-type: xml\r\n"));
assert!(data.contains("content-type: plain/text\r\n")); assert!(data.contains("content-type: plain/text\r\n"));
@@ -627,7 +645,8 @@ mod tests {
ConnectionType::Close, ConnectionType::Close,
&ServiceConfig::default(), &ServiceConfig::default(),
); );
let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap(); let data =
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(data.contains("content-length: 0\r\n")); assert!(data.contains("content-length: 0\r\n"));
assert!(data.contains("connection: close\r\n")); assert!(data.contains("connection: close\r\n"));
assert!(data.contains("authorization: another authorization\r\n")); assert!(data.contains("authorization: another authorization\r\n"));

View File

@@ -1,10 +1,7 @@
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_server_config::ServerConfig;
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use futures::future::{ok, Ready}; use futures_util::future::{ok, Ready};
use crate::error::Error; use crate::error::Error;
use crate::request::Request; use crate::request::Request;
@@ -12,7 +9,7 @@ use crate::request::Request;
pub struct ExpectHandler; pub struct ExpectHandler;
impl ServiceFactory for ExpectHandler { impl ServiceFactory for ExpectHandler {
type Config = ServerConfig; type Config = ();
type Request = Request; type Request = Request;
type Response = Request; type Response = Request;
type Error = Error; type Error = Error;
@@ -20,7 +17,7 @@ impl ServiceFactory for ExpectHandler {
type InitError = Error; type InitError = Error;
type Future = Ready<Result<Self::Service, Self::InitError>>; type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &ServerConfig) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
ok(ExpectHandler) ok(ExpectHandler)
} }
} }
@@ -31,7 +28,7 @@ impl Service for ExpectHandler {
type Error = Error; type Error = Error;
type Future = Ready<Result<Self::Response, Self::Error>>; type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} }

View File

@@ -1,14 +1,13 @@
//! Payload stream //! Payload stream
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_utils::task::LocalWaker; use actix_utils::task::LocalWaker;
use bytes::Bytes; use bytes::Bytes;
use futures::Stream; use futures_core::Stream;
use crate::error::PayloadError; use crate::error::PayloadError;
@@ -83,7 +82,7 @@ impl Payload {
#[inline] #[inline]
pub fn readany( pub fn readany(
&mut self, &mut self,
cx: &mut Context, cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, PayloadError>>> { ) -> Poll<Option<Result<Bytes, PayloadError>>> {
self.inner.borrow_mut().readany(cx) self.inner.borrow_mut().readany(cx)
} }
@@ -94,7 +93,7 @@ impl Stream for Payload {
fn poll_next( fn poll_next(
self: Pin<&mut Self>, self: Pin<&mut Self>,
cx: &mut Context, cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, PayloadError>>> { ) -> Poll<Option<Result<Bytes, PayloadError>>> {
self.inner.borrow_mut().readany(cx) self.inner.borrow_mut().readany(cx)
} }
@@ -128,7 +127,7 @@ impl PayloadSender {
} }
#[inline] #[inline]
pub fn need_read(&self, cx: &mut Context) -> PayloadStatus { pub fn need_read(&self, cx: &mut Context<'_>) -> PayloadStatus {
// we check need_read only if Payload (other side) is alive, // we check need_read only if Payload (other side) is alive,
// otherwise always return true (consume payload) // otherwise always return true (consume payload)
if let Some(shared) = self.inner.upgrade() { if let Some(shared) = self.inner.upgrade() {
@@ -195,7 +194,7 @@ impl Inner {
fn readany( fn readany(
&mut self, &mut self,
cx: &mut Context, cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, PayloadError>>> { ) -> Poll<Option<Result<Bytes, PayloadError>>> {
if let Some(data) = self.items.pop_front() { if let Some(data) = self.items.pop_front() {
self.len -= data.len(); self.len -= data.len();
@@ -227,24 +226,19 @@ impl Inner {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use actix_rt::Runtime; use futures_util::future::poll_fn;
use futures::future::{poll_fn, ready};
#[test] #[actix_rt::test]
fn test_unread_data() { async fn test_unread_data() {
Runtime::new().unwrap().block_on(async { let (_, mut payload) = Payload::create(false);
let (_, mut payload) = Payload::create(false);
payload.unread_data(Bytes::from("data")); payload.unread_data(Bytes::from("data"));
assert!(!payload.is_empty()); assert!(!payload.is_empty());
assert_eq!(payload.len(), 4); assert_eq!(payload.len(), 4);
assert_eq!( assert_eq!(
Bytes::from("data"), Bytes::from("data"),
poll_fn(|cx| payload.readany(cx)).await.unwrap().unwrap() poll_fn(|cx| payload.readany(cx)).await.unwrap().unwrap()
); );
ready(())
});
} }
} }

View File

@@ -1,19 +1,19 @@
use std::fmt;
use std::future::Future; use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin; use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::{fmt, net};
use actix_codec::Framed; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; use actix_rt::net::TcpStream;
use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
use futures::future::{ok, Ready}; use futures_core::ready;
use futures::{ready, Stream}; use futures_util::future::{ok, Ready};
use crate::body::MessageBody; use crate::body::MessageBody;
use crate::cloneable::CloneableService; use crate::cloneable::CloneableService;
use crate::config::{KeepAlive, ServiceConfig}; use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error, ParseError}; use crate::error::{DispatchError, Error, ParseError};
use crate::helpers::DataFactory; use crate::helpers::DataFactory;
use crate::request::Request; use crate::request::Request;
@@ -24,39 +24,25 @@ use super::dispatcher::Dispatcher;
use super::{ExpectHandler, Message, UpgradeHandler}; use super::{ExpectHandler, Message, UpgradeHandler};
/// `ServiceFactory` implementation for HTTP1 transport /// `ServiceFactory` implementation for HTTP1 transport
pub struct H1Service<T, P, S, B, X = ExpectHandler, U = UpgradeHandler<T>> { pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler<T>> {
srv: S, srv: S,
cfg: ServiceConfig, cfg: ServiceConfig,
expect: X, expect: X,
upgrade: Option<U>, upgrade: Option<U>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, P, S, B> H1Service<T, P, S, B> impl<T, S, B> H1Service<T, S, B>
where where
S: ServiceFactory<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
B: MessageBody, B: MessageBody,
{ {
/// Create new `HttpService` instance with default config.
pub fn new<F: IntoServiceFactory<S>>(service: F) -> Self {
let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0);
H1Service {
cfg,
srv: service.into_factory(),
expect: ExpectHandler,
upgrade: None,
on_connect: None,
_t: PhantomData,
}
}
/// Create new `HttpService` instance with config. /// Create new `HttpService` instance with config.
pub fn with_config<F: IntoServiceFactory<S>>( pub(crate) fn with_config<F: IntoServiceFactory<S>>(
cfg: ServiceConfig, cfg: ServiceConfig,
service: F, service: F,
) -> Self { ) -> Self {
@@ -71,15 +57,151 @@ where
} }
} }
impl<T, P, S, B, X, U> H1Service<T, P, S, B, X, U> impl<S, B, X, U> H1Service<TcpStream, S, B, X, U>
where where
S: ServiceFactory<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: ServiceFactory<
Config = (),
Request = (Request, Framed<TcpStream, Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
{
/// Create simple tcp stream service
pub fn tcp(
self,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = DispatchError,
InitError = (),
> {
pipeline_factory(|io: TcpStream| {
let peer_addr = io.peer_addr().ok();
ok((io, peer_addr))
})
.and_then(self)
}
}
#[cfg(feature = "openssl")]
mod openssl {
use super::*;
use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream};
use actix_tls::{openssl::HandshakeError, SslError};
impl<S, B, X, U> H1Service<SslStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: ServiceFactory<
Config = (),
Request = (Request, Framed<SslStream<TcpStream>, Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
{
/// Create openssl based service
pub fn openssl(
self,
acceptor: SslAcceptor,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = SslError<HandshakeError<TcpStream>, DispatchError>,
InitError = (),
> {
pipeline_factory(
Acceptor::new(acceptor)
.map_err(SslError::Ssl)
.map_init_err(|_| panic!()),
)
.and_then(|io: SslStream<TcpStream>| {
let peer_addr = io.get_ref().peer_addr().ok();
ok((io, peer_addr))
})
.and_then(self.map_err(SslError::Service))
}
}
}
#[cfg(feature = "rustls")]
mod rustls {
use super::*;
use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream};
use actix_tls::SslError;
use std::{fmt, io};
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: ServiceFactory<
Config = (),
Request = (Request, Framed<TlsStream<TcpStream>, Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
{
/// Create rustls based service
pub fn rustls(
self,
config: ServerConfig,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = SslError<io::Error, DispatchError>,
InitError = (),
> {
pipeline_factory(
Acceptor::new(config)
.map_err(SslError::Ssl)
.map_init_err(|_| panic!()),
)
.and_then(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok();
ok((io, peer_addr))
})
.and_then(self.map_err(SslError::Service))
}
}
}
impl<T, S, B, X, U> H1Service<T, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
B: MessageBody, B: MessageBody,
{ {
pub fn expect<X1>(self, expect: X1) -> H1Service<T, P, S, B, X1, U> pub fn expect<X1>(self, expect: X1) -> H1Service<T, S, B, X1, U>
where where
X1: ServiceFactory<Request = Request, Response = Request>, X1: ServiceFactory<Request = Request, Response = Request>,
X1::Error: Into<Error>, X1::Error: Into<Error>,
@@ -95,7 +217,7 @@ where
} }
} }
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> H1Service<T, P, S, B, X, U1> pub fn upgrade<U1>(self, upgrade: Option<U1>) -> H1Service<T, S, B, X, U1>
where where
U1: ServiceFactory<Request = (Request, Framed<T, Codec>), Response = ()>, U1: ServiceFactory<Request = (Request, Framed<T, Codec>), Response = ()>,
U1::Error: fmt::Display, U1::Error: fmt::Display,
@@ -121,38 +243,34 @@ where
} }
} }
impl<T, P, S, B, X, U> ServiceFactory for H1Service<T, P, S, B, X, U> impl<T, S, B, X, U> ServiceFactory for H1Service<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
B: MessageBody, B: MessageBody,
X: ServiceFactory<Config = SrvConfig, Request = Request, Response = Request>, X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: ServiceFactory< U: ServiceFactory<Config = (), Request = (Request, Framed<T, Codec>), Response = ()>,
Config = SrvConfig, U::Error: fmt::Display + Into<Error>,
Request = (Request, Framed<T, Codec>),
Response = (),
>,
U::Error: fmt::Display,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
type Config = SrvConfig; type Config = ();
type Request = Io<T, P>; type Request = (T, Option<net::SocketAddr>);
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type InitError = (); type InitError = ();
type Service = H1ServiceHandler<T, P, S::Service, B, X::Service, U::Service>; type Service = H1ServiceHandler<T, S::Service, B, X::Service, U::Service>;
type Future = H1ServiceResponse<T, P, S, B, X, U>; type Future = H1ServiceResponse<T, S, B, X, U>;
fn new_service(&self, cfg: &SrvConfig) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
H1ServiceResponse { H1ServiceResponse {
fut: self.srv.new_service(cfg), fut: self.srv.new_service(()),
fut_ex: Some(self.expect.new_service(cfg)), fut_ex: Some(self.expect.new_service(())),
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(cfg)), fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())),
expect: None, expect: None,
upgrade: None, upgrade: None,
on_connect: self.on_connect.clone(), on_connect: self.on_connect.clone(),
@@ -164,7 +282,7 @@ where
#[doc(hidden)] #[doc(hidden)]
#[pin_project::pin_project] #[pin_project::pin_project]
pub struct H1ServiceResponse<T, P, S, B, X, U> pub struct H1ServiceResponse<T, S, B, X, U>
where where
S: ServiceFactory<Request = Request>, S: ServiceFactory<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
@@ -186,12 +304,12 @@ where
upgrade: Option<U::Service>, upgrade: Option<U::Service>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
cfg: Option<ServiceConfig>, cfg: Option<ServiceConfig>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, P, S, B, X, U> Future for H1ServiceResponse<T, P, S, B, X, U> impl<T, S, B, X, U> Future for H1ServiceResponse<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Request = Request>, S: ServiceFactory<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@@ -204,10 +322,9 @@ where
U::Error: fmt::Display, U::Error: fmt::Display,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
type Output = type Output = Result<H1ServiceHandler<T, S::Service, B, X::Service, U::Service>, ()>;
Result<H1ServiceHandler<T, P, S::Service, B, X::Service, U::Service>, ()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project(); let mut this = self.as_mut().project();
if let Some(fut) = this.fut_ex.as_pin_mut() { if let Some(fut) = this.fut_ex.as_pin_mut() {
@@ -247,16 +364,16 @@ where
} }
/// `Service` implementation for HTTP1 transport /// `Service` implementation for HTTP1 transport
pub struct H1ServiceHandler<T, P, S, B, X, U> { pub struct H1ServiceHandler<T, S, B, X, U> {
srv: CloneableService<S>, srv: CloneableService<S>,
expect: CloneableService<X>, expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>, upgrade: Option<CloneableService<U>>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
cfg: ServiceConfig, cfg: ServiceConfig,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, P, S, B, X, U> H1ServiceHandler<T, P, S, B, X, U> impl<T, S, B, X, U> H1ServiceHandler<T, S, B, X, U>
where where
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
@@ -273,7 +390,7 @@ where
expect: X, expect: X,
upgrade: Option<U>, upgrade: Option<U>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
) -> H1ServiceHandler<T, P, S, B, X, U> { ) -> H1ServiceHandler<T, S, B, X, U> {
H1ServiceHandler { H1ServiceHandler {
srv: CloneableService::new(srv), srv: CloneableService::new(srv),
expect: CloneableService::new(expect), expect: CloneableService::new(expect),
@@ -285,9 +402,9 @@ where
} }
} }
impl<T, P, S, B, X, U> Service for H1ServiceHandler<T, P, S, B, X, U> impl<T, S, B, X, U> Service for H1ServiceHandler<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@@ -295,14 +412,14 @@ where
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>, U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display + Into<Error>,
{ {
type Request = Io<T, P>; type Request = (T, Option<net::SocketAddr>);
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type Future = Dispatcher<T, S, B, X, U>; type Future = Dispatcher<T, S, B, X, U>;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let ready = self let ready = self
.expect .expect
.poll_ready(cx) .poll_ready(cx)
@@ -324,6 +441,19 @@ where
.is_ready() .is_ready()
&& ready; && ready;
let ready = if let Some(ref mut upg) = self.upgrade {
upg.poll_ready(cx)
.map_err(|e| {
let e = e.into();
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service(e)
})?
.is_ready()
&& ready
} else {
ready
};
if ready { if ready {
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} else { } else {
@@ -331,9 +461,7 @@ where
} }
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
let io = req.into_parts().0;
let on_connect = if let Some(ref on_connect) = self.on_connect { let on_connect = if let Some(ref on_connect) = self.on_connect {
Some(on_connect(&io)) Some(on_connect(&io))
} else { } else {
@@ -347,20 +475,21 @@ where
self.expect.clone(), self.expect.clone(),
self.upgrade.clone(), self.upgrade.clone(),
on_connect, on_connect,
addr,
) )
} }
} }
/// `ServiceFactory` implementation for `OneRequestService` service /// `ServiceFactory` implementation for `OneRequestService` service
#[derive(Default)] #[derive(Default)]
pub struct OneRequest<T, P> { pub struct OneRequest<T> {
config: ServiceConfig, config: ServiceConfig,
_t: PhantomData<(T, P)>, _t: PhantomData<T>,
} }
impl<T, P> OneRequest<T, P> impl<T> OneRequest<T>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
{ {
/// Create new `H1SimpleService` instance. /// Create new `H1SimpleService` instance.
pub fn new() -> Self { pub fn new() -> Self {
@@ -371,52 +500,49 @@ where
} }
} }
impl<T, P> ServiceFactory for OneRequest<T, P> impl<T> ServiceFactory for OneRequest<T>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
{ {
type Config = SrvConfig; type Config = ();
type Request = Io<T, P>; type Request = T;
type Response = (Request, Framed<T, Codec>); type Response = (Request, Framed<T, Codec>);
type Error = ParseError; type Error = ParseError;
type InitError = (); type InitError = ();
type Service = OneRequestService<T, P>; type Service = OneRequestService<T>;
type Future = Ready<Result<Self::Service, Self::InitError>>; type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &SrvConfig) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
ok(OneRequestService { ok(OneRequestService {
config: self.config.clone(),
_t: PhantomData, _t: PhantomData,
config: self.config.clone(),
}) })
} }
} }
/// `Service` implementation for HTTP1 transport. Reads one request and returns /// `Service` implementation for HTTP1 transport. Reads one request and returns
/// request and framed object. /// request and framed object.
pub struct OneRequestService<T, P> { pub struct OneRequestService<T> {
_t: PhantomData<T>,
config: ServiceConfig, config: ServiceConfig,
_t: PhantomData<(T, P)>,
} }
impl<T, P> Service for OneRequestService<T, P> impl<T> Service for OneRequestService<T>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
{ {
type Request = Io<T, P>; type Request = T;
type Response = (Request, Framed<T, Codec>); type Response = (Request, Framed<T, Codec>);
type Error = ParseError; type Error = ParseError;
type Future = OneRequestServiceResponse<T>; type Future = OneRequestServiceResponse<T>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, req: Self::Request) -> Self::Future {
OneRequestServiceResponse { OneRequestServiceResponse {
framed: Some(Framed::new( framed: Some(Framed::new(req, Codec::new(self.config.clone()))),
req.into_parts().0,
Codec::new(self.config.clone()),
)),
} }
} }
} }
@@ -424,18 +550,18 @@ where
#[doc(hidden)] #[doc(hidden)]
pub struct OneRequestServiceResponse<T> pub struct OneRequestServiceResponse<T>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
{ {
framed: Option<Framed<T, Codec>>, framed: Option<Framed<T, Codec>>,
} }
impl<T> Future for OneRequestServiceResponse<T> impl<T> Future for OneRequestServiceResponse<T>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
{ {
type Output = Result<(Request, Framed<T, Codec>), ParseError>; type Output = Result<(Request, Framed<T, Codec>), ParseError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.framed.as_mut().unwrap().next_item(cx) { match self.framed.as_mut().unwrap().next_item(cx) {
Poll::Ready(Some(Ok(req))) => match req { Poll::Ready(Some(Ok(req))) => match req {
Message::Item(req) => { Message::Item(req) => {

View File

@@ -1,12 +1,9 @@
use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_codec::Framed; use actix_codec::Framed;
use actix_server_config::ServerConfig;
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use futures::future::Ready; use futures_util::future::Ready;
use crate::error::Error; use crate::error::Error;
use crate::h1::Codec; use crate::h1::Codec;
@@ -15,7 +12,7 @@ use crate::request::Request;
pub struct UpgradeHandler<T>(PhantomData<T>); pub struct UpgradeHandler<T>(PhantomData<T>);
impl<T> ServiceFactory for UpgradeHandler<T> { impl<T> ServiceFactory for UpgradeHandler<T> {
type Config = ServerConfig; type Config = ();
type Request = (Request, Framed<T, Codec>); type Request = (Request, Framed<T, Codec>);
type Response = (); type Response = ();
type Error = Error; type Error = Error;
@@ -23,7 +20,7 @@ impl<T> ServiceFactory for UpgradeHandler<T> {
type InitError = Error; type InitError = Error;
type Future = Ready<Result<Self::Service, Self::InitError>>; type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &ServerConfig) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
unimplemented!() unimplemented!()
} }
} }
@@ -34,7 +31,7 @@ impl<T> Service for UpgradeHandler<T> {
type Error = Error; type Error = Error;
type Future = Ready<Result<Self::Response, Self::Error>>; type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} }

View File

@@ -3,7 +3,6 @@ use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use futures::Sink;
use crate::body::{BodySize, MessageBody, ResponseBody}; use crate::body::{BodySize, MessageBody, ResponseBody};
use crate::error::Error; use crate::error::Error;
@@ -40,7 +39,7 @@ where
{ {
type Output = Result<Framed<T, Codec>, Error>; type Output = Result<Framed<T, Codec>, Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut(); let this = self.get_mut();
loop { loop {

View File

@@ -1,30 +1,23 @@
use std::collections::VecDeque; use std::convert::TryFrom;
use std::future::Future; use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::net;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::time::Instant;
use std::{fmt, mem, net};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_server_config::IoStream; use actix_rt::time::{Delay, Instant};
use actix_service::Service; use actix_service::Service;
use bitflags::bitflags;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::{ready, Sink, Stream};
use h2::server::{Connection, SendResponse}; use h2::server::{Connection, SendResponse};
use h2::{RecvStream, SendStream}; use h2::SendStream;
use http::header::{ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, use log::{error, trace};
};
use http::HttpTryFrom;
use log::{debug, error, trace};
use tokio_timer::Delay;
use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::body::{BodySize, MessageBody, ResponseBody};
use crate::cloneable::CloneableService; use crate::cloneable::CloneableService;
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error, ParseError, PayloadError, ResponseError}; use crate::error::{DispatchError, Error};
use crate::helpers::DataFactory; use crate::helpers::DataFactory;
use crate::httpmessage::HttpMessage; use crate::httpmessage::HttpMessage;
use crate::message::ResponseHead; use crate::message::ResponseHead;
@@ -36,7 +29,10 @@ const CHUNK_SIZE: usize = 16_384;
/// Dispatcher for HTTP/2 protocol /// Dispatcher for HTTP/2 protocol
#[pin_project::pin_project] #[pin_project::pin_project]
pub struct Dispatcher<T: IoStream, S: Service<Request = Request>, B: MessageBody> { pub struct Dispatcher<T, S: Service<Request = Request>, B: MessageBody>
where
T: AsyncRead + AsyncWrite + Unpin,
{
service: CloneableService<S>, service: CloneableService<S>,
connection: Connection<T, Bytes>, connection: Connection<T, Bytes>,
on_connect: Option<Box<dyn DataFactory>>, on_connect: Option<Box<dyn DataFactory>>,
@@ -49,7 +45,7 @@ pub struct Dispatcher<T: IoStream, S: Service<Request = Request>, B: MessageBody
impl<T, S, B> Dispatcher<T, S, B> impl<T, S, B> Dispatcher<T, S, B>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
// S::Future: 'static, // S::Future: 'static,
@@ -95,7 +91,7 @@ where
impl<T, S, B> Future for Dispatcher<T, S, B> impl<T, S, B> Future for Dispatcher<T, S, B>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
@@ -105,7 +101,7 @@ where
type Output = Result<(), DispatchError>; type Output = Result<(), DispatchError>;
#[inline] #[inline]
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut(); let this = self.get_mut();
loop { loop {
@@ -139,7 +135,7 @@ where
on_connect.set(&mut req.extensions_mut()); on_connect.set(&mut req.extensions_mut());
} }
tokio_executor::current_thread::spawn(ServiceResponse::< actix_rt::spawn(ServiceResponse::<
S::Future, S::Future,
S::Response, S::Response,
S::Error, S::Error,
@@ -233,8 +229,9 @@ where
if !has_date { if !has_date {
let mut bytes = BytesMut::with_capacity(29); let mut bytes = BytesMut::with_capacity(29);
self.config.set_date_header(&mut bytes); self.config.set_date_header(&mut bytes);
res.headers_mut() res.headers_mut().insert(DATE, unsafe {
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); HeaderValue::from_maybe_shared_unchecked(bytes.freeze())
});
} }
res res
@@ -250,7 +247,7 @@ where
{ {
type Output = (); type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project(); let mut this = self.as_mut().project();
match this.state { match this.state {

View File

@@ -1,11 +1,9 @@
#![allow(dead_code, unused_imports)] //! HTTP/2 implementation
use std::fmt;
use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use bytes::Bytes; use bytes::Bytes;
use futures::Stream; use futures_core::Stream;
use h2::RecvStream; use h2::RecvStream;
mod dispatcher; mod dispatcher;
@@ -29,13 +27,16 @@ impl Payload {
impl Stream for Payload { impl Stream for Payload {
type Item = Result<Bytes, PayloadError>; type Item = Result<Bytes, PayloadError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> { fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
let this = self.get_mut(); let this = self.get_mut();
match Pin::new(&mut this.pl).poll_data(cx) { match Pin::new(&mut this.pl).poll_data(cx) {
Poll::Ready(Some(Ok(chunk))) => { Poll::Ready(Some(Ok(chunk))) => {
let len = chunk.len(); let len = chunk.len();
if let Err(err) = this.pl.release_capacity().release_capacity(len) { if let Err(err) = this.pl.flow_control().release_capacity(len) {
Poll::Ready(Some(Err(err.into()))) Poll::Ready(Some(Err(err.into())))
} else { } else {
Poll::Ready(Some(Ok(chunk))) Poll::Ready(Some(Ok(chunk)))

View File

@@ -1,61 +1,49 @@
use std::fmt::Debug;
use std::future::Future; use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::{io, net, rc}; use std::{net, rc};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; use actix_rt::net::TcpStream;
use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use actix_service::{
fn_factory, fn_service, pipeline_factory, IntoServiceFactory, Service,
ServiceFactory,
};
use bytes::Bytes; use bytes::Bytes;
use futures::future::{ok, Ready}; use futures_core::ready;
use futures::{ready, Stream}; use futures_util::future::ok;
use h2::server::{self, Connection, Handshake}; use h2::server::{self, Handshake};
use h2::RecvStream;
use log::error; use log::error;
use crate::body::MessageBody; use crate::body::MessageBody;
use crate::cloneable::CloneableService; use crate::cloneable::CloneableService;
use crate::config::{KeepAlive, ServiceConfig}; use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error, ParseError, ResponseError}; use crate::error::{DispatchError, Error};
use crate::helpers::DataFactory; use crate::helpers::DataFactory;
use crate::payload::Payload;
use crate::request::Request; use crate::request::Request;
use crate::response::Response; use crate::response::Response;
use super::dispatcher::Dispatcher; use super::dispatcher::Dispatcher;
/// `ServiceFactory` implementation for HTTP2 transport /// `ServiceFactory` implementation for HTTP2 transport
pub struct H2Service<T, P, S, B> { pub struct H2Service<T, S, B> {
srv: S, srv: S,
cfg: ServiceConfig, cfg: ServiceConfig,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, P, S, B> H2Service<T, P, S, B> impl<T, S, B> H2Service<T, S, B>
where where
S: ServiceFactory<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
/// Create new `HttpService` instance.
pub fn new<F: IntoServiceFactory<S>>(service: F) -> Self {
let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0);
H2Service {
cfg,
on_connect: None,
srv: service.into_factory(),
_t: PhantomData,
}
}
/// Create new `HttpService` instance with config. /// Create new `HttpService` instance with config.
pub fn with_config<F: IntoServiceFactory<S>>( pub(crate) fn with_config<F: IntoServiceFactory<S>>(
cfg: ServiceConfig, cfg: ServiceConfig,
service: F, service: F,
) -> Self { ) -> Self {
@@ -77,26 +65,144 @@ where
} }
} }
impl<T, P, S, B> ServiceFactory for H2Service<T, P, S, B> impl<S, B> H2Service<TcpStream, S, B>
where where
T: IoStream, S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Config = SrvConfig, Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
type Config = SrvConfig; /// Create simple tcp based service
type Request = Io<T, P>; pub fn tcp(
self,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = DispatchError,
InitError = S::InitError,
> {
pipeline_factory(fn_factory(|| {
async {
Ok::<_, S::InitError>(fn_service(|io: TcpStream| {
let peer_addr = io.peer_addr().ok();
ok::<_, DispatchError>((io, peer_addr))
}))
}
}))
.and_then(self)
}
}
#[cfg(feature = "openssl")]
mod openssl {
use actix_service::{fn_factory, fn_service};
use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream};
use actix_tls::{openssl::HandshakeError, SslError};
use super::*;
impl<S, B> H2Service<SslStream<TcpStream>, S, B>
where
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
{
/// Create ssl based service
pub fn openssl(
self,
acceptor: SslAcceptor,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = SslError<HandshakeError<TcpStream>, DispatchError>,
InitError = S::InitError,
> {
pipeline_factory(
Acceptor::new(acceptor)
.map_err(SslError::Ssl)
.map_init_err(|_| panic!()),
)
.and_then(fn_factory(|| {
ok::<_, S::InitError>(fn_service(|io: SslStream<TcpStream>| {
let peer_addr = io.get_ref().peer_addr().ok();
ok((io, peer_addr))
}))
}))
.and_then(self.map_err(SslError::Service))
}
}
}
#[cfg(feature = "rustls")]
mod rustls {
use super::*;
use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream};
use actix_tls::SslError;
use std::io;
impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
where
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
{
/// Create openssl based service
pub fn rustls(
self,
mut config: ServerConfig,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = SslError<io::Error, DispatchError>,
InitError = S::InitError,
> {
let protos = vec!["h2".to_string().into()];
config.set_protocols(&protos);
pipeline_factory(
Acceptor::new(config)
.map_err(SslError::Ssl)
.map_init_err(|_| panic!()),
)
.and_then(fn_factory(|| {
ok::<_, S::InitError>(fn_service(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok();
ok((io, peer_addr))
}))
}))
.and_then(self.map_err(SslError::Service))
}
}
}
impl<T, S, B> ServiceFactory for H2Service<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
{
type Config = ();
type Request = (T, Option<net::SocketAddr>);
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type InitError = S::InitError; type InitError = S::InitError;
type Service = H2ServiceHandler<T, P, S::Service, B>; type Service = H2ServiceHandler<T, S::Service, B>;
type Future = H2ServiceResponse<T, P, S, B>; type Future = H2ServiceResponse<T, S, B>;
fn new_service(&self, cfg: &SrvConfig) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
H2ServiceResponse { H2ServiceResponse {
fut: self.srv.new_service(cfg), fut: self.srv.new_service(()),
cfg: Some(self.cfg.clone()), cfg: Some(self.cfg.clone()),
on_connect: self.on_connect.clone(), on_connect: self.on_connect.clone(),
_t: PhantomData, _t: PhantomData,
@@ -106,26 +212,26 @@ where
#[doc(hidden)] #[doc(hidden)]
#[pin_project::pin_project] #[pin_project::pin_project]
pub struct H2ServiceResponse<T, P, S: ServiceFactory, B> { pub struct H2ServiceResponse<T, S: ServiceFactory, B> {
#[pin] #[pin]
fut: S::Future, fut: S::Future,
cfg: Option<ServiceConfig>, cfg: Option<ServiceConfig>,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, P, S, B> Future for H2ServiceResponse<T, P, S, B> impl<T, S, B> Future for H2ServiceResponse<T, S, B>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
type Output = Result<H2ServiceHandler<T, P, S::Service, B>, S::InitError>; type Output = Result<H2ServiceHandler<T, S::Service, B>, S::InitError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.as_mut().project(); let this = self.as_mut().project();
Poll::Ready(ready!(this.fut.poll(cx)).map(|service| { Poll::Ready(ready!(this.fut.poll(cx)).map(|service| {
@@ -140,14 +246,14 @@ where
} }
/// `Service` implementation for http/2 transport /// `Service` implementation for http/2 transport
pub struct H2ServiceHandler<T, P, S, B> { pub struct H2ServiceHandler<T, S, B> {
srv: CloneableService<S>, srv: CloneableService<S>,
cfg: ServiceConfig, cfg: ServiceConfig,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, P, S, B> H2ServiceHandler<T, P, S, B> impl<T, S, B> H2ServiceHandler<T, S, B>
where where
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
@@ -159,7 +265,7 @@ where
cfg: ServiceConfig, cfg: ServiceConfig,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
srv: S, srv: S,
) -> H2ServiceHandler<T, P, S, B> { ) -> H2ServiceHandler<T, S, B> {
H2ServiceHandler { H2ServiceHandler {
cfg, cfg,
on_connect, on_connect,
@@ -169,21 +275,21 @@ where
} }
} }
impl<T, P, S, B> Service for H2ServiceHandler<T, P, S, B> impl<T, S, B> Service for H2ServiceHandler<T, S, B>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
type Request = Io<T, P>; type Request = (T, Option<net::SocketAddr>);
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type Future = H2ServiceHandlerResponse<T, S, B>; type Future = H2ServiceHandlerResponse<T, S, B>;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.srv.poll_ready(cx).map_err(|e| { self.srv.poll_ready(cx).map_err(|e| {
let e = e.into(); let e = e.into();
error!("Service readiness error: {:?}", e); error!("Service readiness error: {:?}", e);
@@ -191,9 +297,7 @@ where
}) })
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
let io = req.into_parts().0;
let peer_addr = io.peer_addr();
let on_connect = if let Some(ref on_connect) = self.on_connect { let on_connect = if let Some(ref on_connect) = self.on_connect {
Some(on_connect(&io)) Some(on_connect(&io))
} else { } else {
@@ -204,7 +308,7 @@ where
state: State::Handshake( state: State::Handshake(
Some(self.srv.clone()), Some(self.srv.clone()),
Some(self.cfg.clone()), Some(self.cfg.clone()),
peer_addr, addr,
on_connect, on_connect,
server::handshake(io), server::handshake(io),
), ),
@@ -212,8 +316,9 @@ where
} }
} }
enum State<T: IoStream, S: Service<Request = Request>, B: MessageBody> enum State<T, S: Service<Request = Request>, B: MessageBody>
where where
T: AsyncRead + AsyncWrite + Unpin,
S::Future: 'static, S::Future: 'static,
{ {
Incoming(Dispatcher<T, S, B>), Incoming(Dispatcher<T, S, B>),
@@ -228,7 +333,7 @@ where
pub struct H2ServiceHandlerResponse<T, S, B> pub struct H2ServiceHandlerResponse<T, S, B>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
@@ -240,7 +345,7 @@ where
impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B> impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
@@ -249,7 +354,7 @@ where
{ {
type Output = Result<(), DispatchError>; type Output = Result<(), DispatchError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.state { match self.state {
State::Incoming(ref mut disp) => Pin::new(disp).poll(cx), State::Incoming(ref mut disp) => Pin::new(disp).poll(cx),
State::Handshake( State::Handshake(

View File

@@ -74,18 +74,18 @@ impl Header for CacheControl {
} }
impl fmt::Display for CacheControl { impl fmt::Display for CacheControl {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_comma_delimited(f, &self[..]) fmt_comma_delimited(f, &self[..])
} }
} }
impl IntoHeaderValue for CacheControl { impl IntoHeaderValue for CacheControl {
type Error = header::InvalidHeaderValueBytes; type Error = header::InvalidHeaderValue;
fn try_into(self) -> Result<header::HeaderValue, Self::Error> { fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
let mut writer = Writer::new(); let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
header::HeaderValue::from_shared(writer.take()) header::HeaderValue::from_maybe_shared(writer.take())
} }
} }
@@ -126,7 +126,7 @@ pub enum CacheDirective {
} }
impl fmt::Display for CacheDirective { impl fmt::Display for CacheDirective {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use self::CacheDirective::*; use self::CacheDirective::*;
fmt::Display::fmt( fmt::Display::fmt(
match *self { match *self {

View File

@@ -462,12 +462,12 @@ impl ContentDisposition {
} }
impl IntoHeaderValue for ContentDisposition { impl IntoHeaderValue for ContentDisposition {
type Error = header::InvalidHeaderValueBytes; type Error = header::InvalidHeaderValue;
fn try_into(self) -> Result<header::HeaderValue, Self::Error> { fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
let mut writer = Writer::new(); let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
header::HeaderValue::from_shared(writer.take()) header::HeaderValue::from_maybe_shared(writer.take())
} }
} }
@@ -486,7 +486,7 @@ impl Header for ContentDisposition {
} }
impl fmt::Display for DispositionType { impl fmt::Display for DispositionType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
DispositionType::Inline => write!(f, "inline"), DispositionType::Inline => write!(f, "inline"),
DispositionType::Attachment => write!(f, "attachment"), DispositionType::Attachment => write!(f, "attachment"),
@@ -497,7 +497,7 @@ impl fmt::Display for DispositionType {
} }
impl fmt::Display for DispositionParam { impl fmt::Display for DispositionParam {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// All ASCII control characters (0-30, 127) including horizontal tab, double quote, and // All ASCII control characters (0-30, 127) including horizontal tab, double quote, and
// backslash should be escaped in quoted-string (i.e. "foobar"). // backslash should be escaped in quoted-string (i.e. "foobar").
// Ref: RFC6266 S4.1 -> RFC2616 S3.6 // Ref: RFC6266 S4.1 -> RFC2616 S3.6
@@ -555,7 +555,7 @@ impl fmt::Display for DispositionParam {
} }
impl fmt::Display for ContentDisposition { impl fmt::Display for ContentDisposition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.disposition)?; write!(f, "{}", self.disposition)?;
self.parameters self.parameters
.iter() .iter()
@@ -768,9 +768,8 @@ mod tests {
Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above. Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above.
(And now, only UTF-8 is handled by this implementation.) (And now, only UTF-8 is handled by this implementation.)
*/ */
let a = let a = HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"")
HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"") .unwrap();
.unwrap();
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition { let b = ContentDisposition {
disposition: DispositionType::FormData, disposition: DispositionType::FormData,
@@ -884,7 +883,11 @@ mod tests {
assert!(ContentDisposition::from_raw(&a).is_err()); assert!(ContentDisposition::from_raw(&a).is_err());
let a = HeaderValue::from_static("inline; filename=\"\""); let a = HeaderValue::from_static("inline; filename=\"\"");
assert!(ContentDisposition::from_raw(&a).expect("parse cd").get_filename().expect("filename").is_empty()); assert!(ContentDisposition::from_raw(&a)
.expect("parse cd")
.get_filename()
.expect("filename")
.is_empty());
} }
#[test] #[test]

View File

@@ -3,7 +3,7 @@ use std::str::FromStr;
use crate::error::ParseError; use crate::error::ParseError;
use crate::header::{ use crate::header::{
HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer, CONTENT_RANGE, HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE,
}; };
header! { header! {
@@ -166,7 +166,7 @@ impl FromStr for ContentRangeSpec {
} }
impl Display for ContentRangeSpec { impl Display for ContentRangeSpec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
ContentRangeSpec::Bytes { ContentRangeSpec::Bytes {
range, range,
@@ -198,11 +198,11 @@ impl Display for ContentRangeSpec {
} }
impl IntoHeaderValue for ContentRangeSpec { impl IntoHeaderValue for ContentRangeSpec {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut writer = Writer::new(); let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
HeaderValue::from_shared(writer.take()) HeaderValue::from_maybe_shared(writer.take())
} }
} }

View File

@@ -3,7 +3,7 @@ use std::fmt::{self, Display, Write};
use crate::error::ParseError; use crate::error::ParseError;
use crate::header::{ use crate::header::{
self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate,
IntoHeaderValue, InvalidHeaderValueBytes, Writer, IntoHeaderValue, InvalidHeaderValue, Writer,
}; };
use crate::httpmessage::HttpMessage; use crate::httpmessage::HttpMessage;
@@ -87,7 +87,7 @@ impl Header for IfRange {
} }
impl Display for IfRange { impl Display for IfRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
IfRange::EntityTag(ref x) => Display::fmt(x, f), IfRange::EntityTag(ref x) => Display::fmt(x, f),
IfRange::Date(ref x) => Display::fmt(x, f), IfRange::Date(ref x) => Display::fmt(x, f),
@@ -96,12 +96,12 @@ impl Display for IfRange {
} }
impl IntoHeaderValue for IfRange { impl IntoHeaderValue for IfRange {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut writer = Writer::new(); let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
HeaderValue::from_shared(writer.take()) HeaderValue::from_maybe_shared(writer.take())
} }
} }

View File

@@ -159,18 +159,18 @@ macro_rules! header {
} }
impl std::fmt::Display for $id { impl std::fmt::Display for $id {
#[inline] #[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> ::std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> ::std::fmt::Result {
$crate::http::header::fmt_comma_delimited(f, &self.0[..]) $crate::http::header::fmt_comma_delimited(f, &self.0[..])
} }
} }
impl $crate::http::header::IntoHeaderValue for $id { impl $crate::http::header::IntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValueBytes; type Error = $crate::http::header::InvalidHeaderValue;
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
use std::fmt::Write; use std::fmt::Write;
let mut writer = $crate::http::header::Writer::new(); let mut writer = $crate::http::header::Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
$crate::http::header::HeaderValue::from_shared(writer.take()) $crate::http::header::HeaderValue::from_maybe_shared(writer.take())
} }
} }
}; };
@@ -195,18 +195,18 @@ macro_rules! header {
} }
impl std::fmt::Display for $id { impl std::fmt::Display for $id {
#[inline] #[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
$crate::http::header::fmt_comma_delimited(f, &self.0[..]) $crate::http::header::fmt_comma_delimited(f, &self.0[..])
} }
} }
impl $crate::http::header::IntoHeaderValue for $id { impl $crate::http::header::IntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValueBytes; type Error = $crate::http::header::InvalidHeaderValue;
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
use std::fmt::Write; use std::fmt::Write;
let mut writer = $crate::http::header::Writer::new(); let mut writer = $crate::http::header::Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
$crate::http::header::HeaderValue::from_shared(writer.take()) $crate::http::header::HeaderValue::from_maybe_shared(writer.take())
} }
} }
}; };
@@ -231,12 +231,12 @@ macro_rules! header {
} }
impl std::fmt::Display for $id { impl std::fmt::Display for $id {
#[inline] #[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f) std::fmt::Display::fmt(&self.0, f)
} }
} }
impl $crate::http::header::IntoHeaderValue for $id { impl $crate::http::header::IntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValueBytes; type Error = $crate::http::header::InvalidHeaderValue;
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
self.0.try_into() self.0.try_into()
@@ -276,7 +276,7 @@ macro_rules! header {
} }
impl std::fmt::Display for $id { impl std::fmt::Display for $id {
#[inline] #[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self { match *self {
$id::Any => f.write_str("*"), $id::Any => f.write_str("*"),
$id::Items(ref fields) => $crate::http::header::fmt_comma_delimited( $id::Items(ref fields) => $crate::http::header::fmt_comma_delimited(
@@ -285,13 +285,13 @@ macro_rules! header {
} }
} }
impl $crate::http::header::IntoHeaderValue for $id { impl $crate::http::header::IntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValueBytes; type Error = $crate::http::header::InvalidHeaderValue;
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
use std::fmt::Write; use std::fmt::Write;
let mut writer = $crate::http::header::Writer::new(); let mut writer = $crate::http::header::Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
$crate::http::header::HeaderValue::from_shared(writer.take()) $crate::http::header::HeaderValue::from_maybe_shared(writer.take())
} }
} }
}; };

View File

@@ -1,8 +1,9 @@
use std::collections::hash_map::{self, Entry};
use std::convert::TryFrom;
use either::Either; use either::Either;
use hashbrown::hash_map::{self, Entry}; use fxhash::FxHashMap;
use hashbrown::HashMap;
use http::header::{HeaderName, HeaderValue}; use http::header::{HeaderName, HeaderValue};
use http::HttpTryFrom;
/// A set of HTTP headers /// A set of HTTP headers
/// ///
@@ -11,7 +12,7 @@ use http::HttpTryFrom;
/// [`HeaderName`]: struct.HeaderName.html /// [`HeaderName`]: struct.HeaderName.html
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct HeaderMap { pub struct HeaderMap {
pub(crate) inner: HashMap<HeaderName, Value>, pub(crate) inner: FxHashMap<HeaderName, Value>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -56,7 +57,7 @@ impl HeaderMap {
/// allocate. /// allocate.
pub fn new() -> Self { pub fn new() -> Self {
HeaderMap { HeaderMap {
inner: HashMap::new(), inner: FxHashMap::default(),
} }
} }
@@ -70,7 +71,7 @@ impl HeaderMap {
/// More capacity than requested may be allocated. /// More capacity than requested may be allocated.
pub fn with_capacity(capacity: usize) -> HeaderMap { pub fn with_capacity(capacity: usize) -> HeaderMap {
HeaderMap { HeaderMap {
inner: HashMap::with_capacity(capacity), inner: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
} }
} }
@@ -142,7 +143,7 @@ impl HeaderMap {
/// Returns `None` if there are no values associated with the key. /// Returns `None` if there are no values associated with the key.
/// ///
/// [`GetAll`]: struct.GetAll.html /// [`GetAll`]: struct.GetAll.html
pub fn get_all<N: AsName>(&self, name: N) -> GetAll { pub fn get_all<N: AsName>(&self, name: N) -> GetAll<'_> {
GetAll { GetAll {
idx: 0, idx: 0,
item: self.get2(name), item: self.get2(name),
@@ -186,7 +187,7 @@ impl HeaderMap {
/// The iteration order is arbitrary, but consistent across platforms for /// The iteration order is arbitrary, but consistent across platforms for
/// the same crate version. Each key will be yielded once per associated /// the same crate version. Each key will be yielded once per associated
/// value. So, if a key has 3 associated values, it will be yielded 3 times. /// value. So, if a key has 3 associated values, it will be yielded 3 times.
pub fn iter(&self) -> Iter { pub fn iter(&self) -> Iter<'_> {
Iter::new(self.inner.iter()) Iter::new(self.inner.iter())
} }
@@ -195,7 +196,7 @@ impl HeaderMap {
/// The iteration order is arbitrary, but consistent across platforms for /// The iteration order is arbitrary, but consistent across platforms for
/// the same crate version. Each key will be yielded only once even if it /// the same crate version. Each key will be yielded only once even if it
/// has multiple associated values. /// has multiple associated values.
pub fn keys(&self) -> Keys { pub fn keys(&self) -> Keys<'_> {
Keys(self.inner.keys()) Keys(self.inner.keys())
} }

View File

@@ -1,6 +1,7 @@
//! Various http headers //! Various http headers
// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header) // This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header)
use std::convert::TryFrom;
use std::{fmt, str::FromStr}; use std::{fmt, str::FromStr};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
@@ -73,58 +74,58 @@ impl<'a> IntoHeaderValue for &'a [u8] {
} }
impl IntoHeaderValue for Bytes { impl IntoHeaderValue for Bytes {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_shared(self) HeaderValue::from_maybe_shared(self)
} }
} }
impl IntoHeaderValue for Vec<u8> { impl IntoHeaderValue for Vec<u8> {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_shared(Bytes::from(self)) HeaderValue::try_from(self)
} }
} }
impl IntoHeaderValue for String { impl IntoHeaderValue for String {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_shared(Bytes::from(self)) HeaderValue::try_from(self)
} }
} }
impl IntoHeaderValue for usize { impl IntoHeaderValue for usize {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let s = format!("{}", self); let s = format!("{}", self);
HeaderValue::from_shared(Bytes::from(s)) HeaderValue::try_from(s)
} }
} }
impl IntoHeaderValue for u64 { impl IntoHeaderValue for u64 {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let s = format!("{}", self); let s = format!("{}", self);
HeaderValue::from_shared(Bytes::from(s)) HeaderValue::try_from(s)
} }
} }
impl IntoHeaderValue for Mime { impl IntoHeaderValue for Mime {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_shared(Bytes::from(format!("{}", self))) HeaderValue::try_from(format!("{}", self))
} }
} }
@@ -204,7 +205,7 @@ impl Writer {
} }
} }
fn take(&mut self) -> Bytes { fn take(&mut self) -> Bytes {
self.buf.take().freeze() self.buf.split().freeze()
} }
} }
@@ -216,7 +217,7 @@ impl fmt::Write for Writer {
} }
#[inline] #[inline]
fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
fmt::write(self, args) fmt::write(self, args)
} }
} }
@@ -258,7 +259,7 @@ pub fn from_one_raw_str<T: FromStr>(val: Option<&HeaderValue>) -> Result<T, Pars
#[inline] #[inline]
#[doc(hidden)] #[doc(hidden)]
/// Format an array into a comma-delimited string. /// Format an array into a comma-delimited string.
pub fn fmt_comma_delimited<T>(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result pub fn fmt_comma_delimited<T>(f: &mut fmt::Formatter<'_>, parts: &[T]) -> fmt::Result
where where
T: fmt::Display, T: fmt::Display,
{ {
@@ -360,7 +361,7 @@ pub fn parse_extended_value(
} }
impl fmt::Display for ExtendedValue { impl fmt::Display for ExtendedValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let encoded_value = let encoded_value =
percent_encoding::percent_encode(&self.value[..], HTTP_VALUE); percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
if let Some(ref lang) = self.language_tag { if let Some(ref lang) = self.language_tag {
@@ -375,7 +376,7 @@ impl fmt::Display for ExtendedValue {
/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] /// [https://tools.ietf.org/html/rfc5987#section-3.2][url]
/// ///
/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 /// [url]: https://tools.ietf.org/html/rfc5987#section-3.2
pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE); let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
fmt::Display::fmt(&encoded, f) fmt::Display::fmt(&encoded, f)
} }

View File

@@ -98,7 +98,7 @@ impl Charset {
} }
impl Display for Charset { impl Display for Charset {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.label()) f.write_str(self.label())
} }
} }

View File

@@ -27,7 +27,7 @@ pub enum Encoding {
} }
impl fmt::Display for Encoding { impl fmt::Display for Encoding {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match *self { f.write_str(match *self {
Chunked => "chunked", Chunked => "chunked",
Brotli => "br", Brotli => "br",

View File

@@ -1,7 +1,7 @@
use std::fmt::{self, Display, Write}; use std::fmt::{self, Display, Write};
use std::str::FromStr; use std::str::FromStr;
use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer}; use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer};
/// check that each char in the slice is either: /// check that each char in the slice is either:
/// 1. `%x21`, or /// 1. `%x21`, or
@@ -113,7 +113,7 @@ impl EntityTag {
} }
impl Display for EntityTag { impl Display for EntityTag {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.weak { if self.weak {
write!(f, "W/\"{}\"", self.tag) write!(f, "W/\"{}\"", self.tag)
} else { } else {
@@ -157,12 +157,12 @@ impl FromStr for EntityTag {
} }
impl IntoHeaderValue for EntityTag { impl IntoHeaderValue for EntityTag {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut wrt = Writer::new(); let mut wrt = Writer::new();
write!(wrt, "{}", self).unwrap(); write!(wrt, "{}", self).unwrap();
HeaderValue::from_shared(wrt.take()) HeaderValue::from_maybe_shared(wrt.take())
} }
} }

View File

@@ -3,8 +3,8 @@ use std::io::Write;
use std::str::FromStr; use std::str::FromStr;
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
use bytes::{BufMut, BytesMut}; use bytes::{buf::BufMutExt, BytesMut};
use http::header::{HeaderValue, InvalidHeaderValueBytes}; use http::header::{HeaderValue, InvalidHeaderValue};
use crate::error::ParseError; use crate::error::ParseError;
use crate::header::IntoHeaderValue; use crate::header::IntoHeaderValue;
@@ -28,7 +28,7 @@ impl FromStr for HttpDate {
} }
impl Display for HttpDate { impl Display for HttpDate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0.to_utc().rfc822(), f) fmt::Display::fmt(&self.0.to_utc().rfc822(), f)
} }
} }
@@ -58,12 +58,12 @@ impl From<SystemTime> for HttpDate {
} }
impl IntoHeaderValue for HttpDate { impl IntoHeaderValue for HttpDate {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut wrt = BytesMut::with_capacity(29).writer(); let mut wrt = BytesMut::with_capacity(29).writer();
write!(wrt, "{}", self.0.rfc822()).unwrap(); write!(wrt, "{}", self.0.rfc822()).unwrap();
HeaderValue::from_shared(wrt.get_mut().take().freeze()) HeaderValue::from_maybe_shared(wrt.get_mut().split().freeze())
} }
} }

View File

@@ -53,7 +53,7 @@ impl<T: PartialEq> cmp::PartialOrd for QualityItem<T> {
} }
impl<T: fmt::Display> fmt::Display for QualityItem<T> { impl<T: fmt::Display> fmt::Display for QualityItem<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.item, f)?; fmt::Display::fmt(&self.item, f)?;
match self.quality.0 { match self.quality.0 {
1000 => Ok(()), 1000 => Ok(()),

View File

@@ -60,7 +60,7 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM
bytes.put_slice(&buf); bytes.put_slice(&buf);
if four { if four {
bytes.put(b' '); bytes.put_u8(b' ');
} }
} }
@@ -203,33 +203,33 @@ mod tests {
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
bytes.reserve(50); bytes.reserve(50);
write_content_length(0, &mut bytes); write_content_length(0, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 0\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(9, &mut bytes); write_content_length(9, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 9\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(10, &mut bytes); write_content_length(10, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 10\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(99, &mut bytes); write_content_length(99, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 99\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(100, &mut bytes); write_content_length(100, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 100\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(101, &mut bytes); write_content_length(101, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 101\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(998, &mut bytes); write_content_length(998, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 998\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(1000, &mut bytes); write_content_length(1000, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 1000\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(1001, &mut bytes); write_content_length(1001, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 1001\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(5909, &mut bytes); write_content_length(5909, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 5909\r\n"[..]);
} }
} }

View File

@@ -25,10 +25,10 @@ pub trait HttpMessage: Sized {
fn take_payload(&mut self) -> Payload<Self::Stream>; fn take_payload(&mut self) -> Payload<Self::Stream>;
/// Request's extensions container /// Request's extensions container
fn extensions(&self) -> Ref<Extensions>; fn extensions(&self) -> Ref<'_, Extensions>;
/// Mutable reference to a the request's extensions container /// Mutable reference to a the request's extensions container
fn extensions_mut(&self) -> RefMut<Extensions>; fn extensions_mut(&self) -> RefMut<'_, Extensions>;
#[doc(hidden)] #[doc(hidden)]
/// Get a header /// Get a header
@@ -105,7 +105,7 @@ pub trait HttpMessage: Sized {
/// Load request cookies. /// Load request cookies.
#[inline] #[inline]
fn cookies(&self) -> Result<Ref<Vec<Cookie<'static>>>, CookieParseError> { fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
if self.extensions().get::<Cookies>().is_none() { if self.extensions().get::<Cookies>().is_none() {
let mut cookies = Vec::new(); let mut cookies = Vec::new();
for hdr in self.headers().get_all(header::COOKIE) { for hdr in self.headers().get_all(header::COOKIE) {
@@ -153,12 +153,12 @@ where
} }
/// Request's extensions container /// Request's extensions container
fn extensions(&self) -> Ref<Extensions> { fn extensions(&self) -> Ref<'_, Extensions> {
(**self).extensions() (**self).extensions()
} }
/// Mutable reference to a the request's extensions container /// Mutable reference to a the request's extensions container
fn extensions_mut(&self) -> RefMut<Extensions> { fn extensions_mut(&self) -> RefMut<'_, Extensions> {
(**self).extensions_mut() (**self).extensions_mut()
} }
} }

View File

@@ -1,11 +1,10 @@
//! Basic http primitives for actix-net framework. //! Basic http primitives for actix-net framework.
#![deny(rust_2018_idioms, warnings)]
#![allow( #![allow(
clippy::type_complexity, clippy::type_complexity,
clippy::too_many_arguments, clippy::too_many_arguments,
clippy::new_without_default, clippy::new_without_default,
clippy::borrow_interior_mutable_const, clippy::borrow_interior_mutable_const
clippy::write_with_newline,
unused_imports
)] )]
#[macro_use] #[macro_use]
@@ -16,6 +15,7 @@ mod builder;
pub mod client; pub mod client;
mod cloneable; mod cloneable;
mod config; mod config;
#[cfg(feature = "compress")]
pub mod encoding; pub mod encoding;
mod extensions; mod extensions;
mod header; mod header;
@@ -52,7 +52,7 @@ pub mod http {
// re-exports // re-exports
pub use http::header::{HeaderName, HeaderValue}; pub use http::header::{HeaderName, HeaderValue};
pub use http::uri::PathAndQuery; pub use http::uri::PathAndQuery;
pub use http::{uri, Error, HttpTryFrom, Uri}; pub use http::{uri, Error, Uri};
pub use http::{Method, StatusCode, Version}; pub use http::{Method, StatusCode, Version};
pub use crate::cookie::{Cookie, CookieBuilder}; pub use crate::cookie::{Cookie, CookieBuilder};
@@ -65,3 +65,10 @@ pub mod http {
pub use crate::header::ContentEncoding; pub use crate::header::ContentEncoding;
pub use crate::message::ConnectionType; pub use crate::message::ConnectionType;
} }
/// Http protocol
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Protocol {
Http1,
Http2,
}

View File

@@ -78,13 +78,13 @@ impl Head for RequestHead {
impl RequestHead { impl RequestHead {
/// Message extensions /// Message extensions
#[inline] #[inline]
pub fn extensions(&self) -> Ref<Extensions> { pub fn extensions(&self) -> Ref<'_, Extensions> {
self.extensions.borrow() self.extensions.borrow()
} }
/// Mutable reference to a the message's extensions /// Mutable reference to a the message's extensions
#[inline] #[inline]
pub fn extensions_mut(&self) -> RefMut<Extensions> { pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.extensions.borrow_mut() self.extensions.borrow_mut()
} }
@@ -237,13 +237,13 @@ impl ResponseHead {
/// Message extensions /// Message extensions
#[inline] #[inline]
pub fn extensions(&self) -> Ref<Extensions> { pub fn extensions(&self) -> Ref<'_, Extensions> {
self.extensions.borrow() self.extensions.borrow()
} }
/// Mutable reference to a the message's extensions /// Mutable reference to a the message's extensions
#[inline] #[inline]
pub fn extensions_mut(&self) -> RefMut<Extensions> { pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.extensions.borrow_mut() self.extensions.borrow_mut()
} }

View File

@@ -1,9 +1,8 @@
use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use bytes::Bytes; use bytes::Bytes;
use futures::Stream; use futures_core::Stream;
use h2::RecvStream; use h2::RecvStream;
use crate::error::PayloadError; use crate::error::PayloadError;
@@ -57,7 +56,10 @@ where
type Item = Result<Bytes, PayloadError>; type Item = Result<Bytes, PayloadError>;
#[inline] #[inline]
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> { fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
match self.get_mut() { match self.get_mut() {
Payload::None => Poll::Ready(None), Payload::None => Poll::Ready(None),
Payload::H1(ref mut pl) => pl.readany(cx), Payload::H1(ref mut pl) => pl.readany(cx),

View File

@@ -25,13 +25,13 @@ impl<P> HttpMessage for Request<P> {
/// Request extensions /// Request extensions
#[inline] #[inline]
fn extensions(&self) -> Ref<Extensions> { fn extensions(&self) -> Ref<'_, Extensions> {
self.head.extensions() self.head.extensions()
} }
/// Mutable reference to a the request's extensions /// Mutable reference to a the request's extensions
#[inline] #[inline]
fn extensions_mut(&self) -> RefMut<Extensions> { fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.head.extensions_mut() self.head.extensions_mut()
} }
@@ -165,7 +165,7 @@ impl<P> Request<P> {
} }
impl<P> fmt::Debug for Request<P> { impl<P> fmt::Debug for Request<P> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!( writeln!(
f, f,
"\nRequest {:?} {}:{}", "\nRequest {:?} {}:{}",
@@ -187,7 +187,7 @@ impl<P> fmt::Debug for Request<P> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use http::HttpTryFrom; use std::convert::TryFrom;
#[test] #[test]
fn test_basics() { fn test_basics() {

View File

@@ -1,14 +1,13 @@
//! Http response //! Http response
use std::cell::{Ref, RefMut}; use std::cell::{Ref, RefMut};
use std::convert::TryFrom;
use std::future::Future; use std::future::Future;
use std::io::Write;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::{fmt, str}; use std::{fmt, str};
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::future::{ok, Ready}; use futures_core::Stream;
use futures::stream::Stream;
use serde::Serialize; use serde::Serialize;
use serde_json; use serde_json;
@@ -18,7 +17,7 @@ use crate::error::Error;
use crate::extensions::Extensions; use crate::extensions::Extensions;
use crate::header::{Header, IntoHeaderValue}; use crate::header::{Header, IntoHeaderValue};
use crate::http::header::{self, HeaderName, HeaderValue}; use crate::http::header::{self, HeaderName, HeaderValue};
use crate::http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode}; use crate::http::{Error as HttpError, HeaderMap, StatusCode};
use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead};
/// An HTTP Response /// An HTTP Response
@@ -54,7 +53,7 @@ impl Response<Body> {
/// Constructs an error response /// Constructs an error response
#[inline] #[inline]
pub fn from_error(error: Error) -> Response { pub fn from_error(error: Error) -> Response {
let mut resp = error.as_response_error().render_response(); let mut resp = error.as_response_error().error_response();
if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR { if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR {
error!("Internal Server Error: {:?}", error); error!("Internal Server Error: {:?}", error);
} }
@@ -131,7 +130,7 @@ impl<B> Response<B> {
/// Get an iterator for the cookies set by this response /// Get an iterator for the cookies set by this response
#[inline] #[inline]
pub fn cookies(&self) -> CookieIter { pub fn cookies(&self) -> CookieIter<'_> {
CookieIter { CookieIter {
iter: self.head.headers.get_all(header::SET_COOKIE), iter: self.head.headers.get_all(header::SET_COOKIE),
} }
@@ -139,7 +138,7 @@ impl<B> Response<B> {
/// Add a cookie to this response /// Add a cookie to this response
#[inline] #[inline]
pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> { pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> {
let h = &mut self.head.headers; let h = &mut self.head.headers;
HeaderValue::from_str(&cookie.to_string()) HeaderValue::from_str(&cookie.to_string())
.map(|c| { .map(|c| {
@@ -187,13 +186,13 @@ impl<B> Response<B> {
/// Responses extensions /// Responses extensions
#[inline] #[inline]
pub fn extensions(&self) -> Ref<Extensions> { pub fn extensions(&self) -> Ref<'_, Extensions> {
self.head.extensions.borrow() self.head.extensions.borrow()
} }
/// Mutable reference to a the response's extensions /// Mutable reference to a the response's extensions
#[inline] #[inline]
pub fn extensions_mut(&mut self) -> RefMut<Extensions> { pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
self.head.extensions.borrow_mut() self.head.extensions.borrow_mut()
} }
@@ -266,7 +265,7 @@ impl<B> Response<B> {
} }
impl<B: MessageBody> fmt::Debug for Response<B> { impl<B: MessageBody> fmt::Debug for Response<B> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let res = writeln!( let res = writeln!(
f, f,
"\nResponse {:?} {}{}", "\nResponse {:?} {}{}",
@@ -286,7 +285,7 @@ impl<B: MessageBody> fmt::Debug for Response<B> {
impl Future for Response { impl Future for Response {
type Output = Result<Response, Error>; type Output = Result<Response, Error>;
fn poll(mut self: Pin<&mut Self>, _: &mut Context) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(Ok(Response { Poll::Ready(Ok(Response {
head: self.head.take(), head: self.head.take(),
body: self.body.take_body(), body: self.body.take_body(),
@@ -355,7 +354,6 @@ impl ResponseBuilder {
/// )) /// ))
/// .finish()) /// .finish())
/// } /// }
/// fn main() {}
/// ``` /// ```
#[doc(hidden)] #[doc(hidden)]
pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self { pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
@@ -381,11 +379,11 @@ impl ResponseBuilder {
/// .header(http::header::CONTENT_TYPE, "application/json") /// .header(http::header::CONTENT_TYPE, "application/json")
/// .finish() /// .finish()
/// } /// }
/// fn main() {}
/// ``` /// ```
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
where where
HeaderName: HttpTryFrom<K>, HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
if let Some(parts) = parts(&mut self.head, &self.err) { if let Some(parts) = parts(&mut self.head, &self.err) {
@@ -413,11 +411,11 @@ impl ResponseBuilder {
/// .set_header(http::header::CONTENT_TYPE, "application/json") /// .set_header(http::header::CONTENT_TYPE, "application/json")
/// .finish() /// .finish()
/// } /// }
/// fn main() {}
/// ``` /// ```
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
where where
HeaderName: HttpTryFrom<K>, HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
if let Some(parts) = parts(&mut self.head, &self.err) { if let Some(parts) = parts(&mut self.head, &self.err) {
@@ -486,7 +484,8 @@ impl ResponseBuilder {
#[inline] #[inline]
pub fn content_type<V>(&mut self, value: V) -> &mut Self pub fn content_type<V>(&mut self, value: V) -> &mut Self
where where
HeaderValue: HttpTryFrom<V>, HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
{ {
if let Some(parts) = parts(&mut self.head, &self.err) { if let Some(parts) = parts(&mut self.head, &self.err) {
match HeaderValue::try_from(value) { match HeaderValue::try_from(value) {
@@ -502,9 +501,7 @@ impl ResponseBuilder {
/// Set content length /// Set content length
#[inline] #[inline]
pub fn content_length(&mut self, len: u64) -> &mut Self { pub fn content_length(&mut self, len: u64) -> &mut Self {
let mut wrt = BytesMut::new().writer(); self.header(header::CONTENT_LENGTH, len)
let _ = write!(wrt, "{}", len);
self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze())
} }
/// Set a cookie /// Set a cookie
@@ -588,14 +585,14 @@ impl ResponseBuilder {
/// Responses extensions /// Responses extensions
#[inline] #[inline]
pub fn extensions(&self) -> Ref<Extensions> { pub fn extensions(&self) -> Ref<'_, Extensions> {
let head = self.head.as_ref().expect("cannot reuse response builder"); let head = self.head.as_ref().expect("cannot reuse response builder");
head.extensions.borrow() head.extensions.borrow()
} }
/// Mutable reference to a the response's extensions /// Mutable reference to a the response's extensions
#[inline] #[inline]
pub fn extensions_mut(&mut self) -> RefMut<Extensions> { pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
let head = self.head.as_ref().expect("cannot reuse response builder"); let head = self.head.as_ref().expect("cannot reuse response builder");
head.extensions.borrow_mut() head.extensions.borrow_mut()
} }
@@ -765,13 +762,13 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
impl Future for ResponseBuilder { impl Future for ResponseBuilder {
type Output = Result<Response, Error>; type Output = Result<Response, Error>;
fn poll(mut self: Pin<&mut Self>, _: &mut Context) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(Ok(self.finish())) Poll::Ready(Ok(self.finish()))
} }
} }
impl fmt::Debug for ResponseBuilder { impl fmt::Debug for ResponseBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let head = self.head.as_ref().unwrap(); let head = self.head.as_ref().unwrap();
let res = writeln!( let res = writeln!(

View File

@@ -1,15 +1,14 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::{fmt, io, net, rc}; use std::{fmt, net, rc};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_server_config::{ use actix_rt::net::TcpStream;
Io as ServerIo, IoStream, Protocol, ServerConfig as SrvConfig, use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
}; use bytes::Bytes;
use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use futures_core::{ready, Future};
use bytes::{Buf, BufMut, Bytes, BytesMut}; use futures_util::future::ok;
use futures::{ready, Future};
use h2::server::{self, Handshake}; use h2::server::{self, Handshake};
use pin_project::{pin_project, project}; use pin_project::{pin_project, project};
@@ -21,21 +20,21 @@ use crate::error::{DispatchError, Error};
use crate::helpers::DataFactory; use crate::helpers::DataFactory;
use crate::request::Request; use crate::request::Request;
use crate::response::Response; use crate::response::Response;
use crate::{h1, h2::Dispatcher}; use crate::{h1, h2::Dispatcher, Protocol};
/// `ServiceFactory` HTTP1.1/HTTP2 transport implementation /// `ServiceFactory` HTTP1.1/HTTP2 transport implementation
pub struct HttpService<T, P, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler<T>> { pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler<T>> {
srv: S, srv: S,
cfg: ServiceConfig, cfg: ServiceConfig,
expect: X, expect: X,
upgrade: Option<U>, upgrade: Option<U>,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, S, B> HttpService<T, (), S, B> impl<T, S, B> HttpService<T, S, B>
where where
S: ServiceFactory<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
@@ -48,9 +47,9 @@ where
} }
} }
impl<T, P, S, B> HttpService<T, P, S, B> impl<T, S, B> HttpService<T, S, B>
where where
S: ServiceFactory<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
@@ -59,7 +58,7 @@ where
{ {
/// Create new `HttpService` instance. /// Create new `HttpService` instance.
pub fn new<F: IntoServiceFactory<S>>(service: F) -> Self { pub fn new<F: IntoServiceFactory<S>>(service: F) -> Self {
let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0, false, None);
HttpService { HttpService {
cfg, cfg,
@@ -87,9 +86,9 @@ where
} }
} }
impl<T, P, S, B, X, U> HttpService<T, P, S, B, X, U> impl<T, S, B, X, U> HttpService<T, S, B, X, U>
where where
S: ServiceFactory<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
@@ -101,9 +100,9 @@ where
/// Service get called with request that contains `EXPECT` header. /// Service get called with request that contains `EXPECT` header.
/// Service must return request in case of success, in that case /// Service must return request in case of success, in that case
/// request will be forwarded to main service. /// request will be forwarded to main service.
pub fn expect<X1>(self, expect: X1) -> HttpService<T, P, S, B, X1, U> pub fn expect<X1>(self, expect: X1) -> HttpService<T, S, B, X1, U>
where where
X1: ServiceFactory<Config = SrvConfig, Request = Request, Response = Request>, X1: ServiceFactory<Config = (), Request = Request, Response = Request>,
X1::Error: Into<Error>, X1::Error: Into<Error>,
X1::InitError: fmt::Debug, X1::InitError: fmt::Debug,
<X1::Service as Service>::Future: 'static, <X1::Service as Service>::Future: 'static,
@@ -122,10 +121,10 @@ where
/// ///
/// If service is provided then normal requests handling get halted /// If service is provided then normal requests handling get halted
/// and this service get called with original request and framed object. /// and this service get called with original request and framed object.
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> HttpService<T, P, S, B, X, U1> pub fn upgrade<U1>(self, upgrade: Option<U1>) -> HttpService<T, S, B, X, U1>
where where
U1: ServiceFactory< U1: ServiceFactory<
Config = SrvConfig, Config = (),
Request = (Request, Framed<T, h1::Codec>), Request = (Request, Framed<T, h1::Codec>),
Response = (), Response = (),
>, >,
@@ -153,45 +152,210 @@ where
} }
} }
impl<T, P, S, B, X, U> ServiceFactory for HttpService<T, P, S, B, X, U> impl<S, B, X, U> HttpService<TcpStream, S, B, X, U>
where where
T: IoStream, S: ServiceFactory<Config = (), Request = Request>,
S: ServiceFactory<Config = SrvConfig, Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
X: ServiceFactory<Config = SrvConfig, Request = Request, Response = Request>, X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
<X::Service as Service>::Future: 'static, <X::Service as Service>::Future: 'static,
U: ServiceFactory< U: ServiceFactory<
Config = SrvConfig, Config = (),
Request = (Request, Framed<T, h1::Codec>), Request = (Request, Framed<TcpStream, h1::Codec>),
Response = (), Response = (),
>, >,
U::Error: fmt::Display, U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static, <U::Service as Service>::Future: 'static,
{ {
type Config = SrvConfig; /// Create simple tcp stream service
type Request = ServerIo<T, P>; pub fn tcp(
self,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = DispatchError,
InitError = (),
> {
pipeline_factory(|io: TcpStream| {
let peer_addr = io.peer_addr().ok();
ok((io, Protocol::Http1, peer_addr))
})
.and_then(self)
}
}
#[cfg(feature = "openssl")]
mod openssl {
use super::*;
use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream};
use actix_tls::{openssl::HandshakeError, SslError};
impl<S, B, X, U> HttpService<SslStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
<X::Service as Service>::Future: 'static,
U: ServiceFactory<
Config = (),
Request = (Request, Framed<SslStream<TcpStream>, h1::Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
{
/// Create openssl based service
pub fn openssl(
self,
acceptor: SslAcceptor,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = SslError<HandshakeError<TcpStream>, DispatchError>,
InitError = (),
> {
pipeline_factory(
Acceptor::new(acceptor)
.map_err(SslError::Ssl)
.map_init_err(|_| panic!()),
)
.and_then(|io: SslStream<TcpStream>| {
let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() {
if protos.windows(2).any(|window| window == b"h2") {
Protocol::Http2
} else {
Protocol::Http1
}
} else {
Protocol::Http1
};
let peer_addr = io.get_ref().peer_addr().ok();
ok((io, proto, peer_addr))
})
.and_then(self.map_err(SslError::Service))
}
}
}
#[cfg(feature = "rustls")]
mod rustls {
use super::*;
use actix_tls::rustls::{Acceptor, ServerConfig, Session, TlsStream};
use actix_tls::SslError;
use std::io;
impl<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
<X::Service as Service>::Future: 'static,
U: ServiceFactory<
Config = (),
Request = (Request, Framed<TlsStream<TcpStream>, h1::Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
{
/// Create openssl based service
pub fn rustls(
self,
mut config: ServerConfig,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = SslError<io::Error, DispatchError>,
InitError = (),
> {
let protos = vec!["h2".to_string().into(), "http/1.1".to_string().into()];
config.set_protocols(&protos);
pipeline_factory(
Acceptor::new(config)
.map_err(SslError::Ssl)
.map_init_err(|_| panic!()),
)
.and_then(|io: TlsStream<TcpStream>| {
let proto = if let Some(protos) = io.get_ref().1.get_alpn_protocol() {
if protos.windows(2).any(|window| window == b"h2") {
Protocol::Http2
} else {
Protocol::Http1
}
} else {
Protocol::Http1
};
let peer_addr = io.get_ref().0.peer_addr().ok();
ok((io, proto, peer_addr))
})
.and_then(self.map_err(SslError::Service))
}
}
}
impl<T, S, B, X, U> ServiceFactory for HttpService<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
<X::Service as Service>::Future: 'static,
U: ServiceFactory<
Config = (),
Request = (Request, Framed<T, h1::Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
{
type Config = ();
type Request = (T, Protocol, Option<net::SocketAddr>);
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type InitError = (); type InitError = ();
type Service = HttpServiceHandler<T, P, S::Service, B, X::Service, U::Service>; type Service = HttpServiceHandler<T, S::Service, B, X::Service, U::Service>;
type Future = HttpServiceResponse<T, P, S, B, X, U>; type Future = HttpServiceResponse<T, S, B, X, U>;
fn new_service(&self, cfg: &SrvConfig) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
HttpServiceResponse { HttpServiceResponse {
fut: self.srv.new_service(cfg), fut: self.srv.new_service(()),
fut_ex: Some(self.expect.new_service(cfg)), fut_ex: Some(self.expect.new_service(())),
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(cfg)), fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())),
expect: None, expect: None,
upgrade: None, upgrade: None,
on_connect: self.on_connect.clone(), on_connect: self.on_connect.clone(),
cfg: Some(self.cfg.clone()), cfg: self.cfg.clone(),
_t: PhantomData, _t: PhantomData,
} }
} }
@@ -201,7 +365,6 @@ where
#[pin_project] #[pin_project]
pub struct HttpServiceResponse< pub struct HttpServiceResponse<
T, T,
P,
S: ServiceFactory, S: ServiceFactory,
B, B,
X: ServiceFactory, X: ServiceFactory,
@@ -216,13 +379,13 @@ pub struct HttpServiceResponse<
expect: Option<X::Service>, expect: Option<X::Service>,
upgrade: Option<U::Service>, upgrade: Option<U::Service>,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
cfg: Option<ServiceConfig>, cfg: ServiceConfig,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, P, S, B, X, U> Future for HttpServiceResponse<T, P, S, B, X, U> impl<T, S, B, X, U> Future for HttpServiceResponse<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Request = Request>, S: ServiceFactory<Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
@@ -239,9 +402,9 @@ where
<U::Service as Service>::Future: 'static, <U::Service as Service>::Future: 'static,
{ {
type Output = type Output =
Result<HttpServiceHandler<T, P, S::Service, B, X::Service, U::Service>, ()>; Result<HttpServiceHandler<T, S::Service, B, X::Service, U::Service>, ()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project(); let mut this = self.as_mut().project();
if let Some(fut) = this.fut_ex.as_pin_mut() { if let Some(fut) = this.fut_ex.as_pin_mut() {
@@ -269,7 +432,7 @@ where
Poll::Ready(result.map(|service| { Poll::Ready(result.map(|service| {
let this = self.as_mut().project(); let this = self.as_mut().project();
HttpServiceHandler::new( HttpServiceHandler::new(
this.cfg.take().unwrap(), this.cfg.clone(),
service, service,
this.expect.take().unwrap(), this.expect.take().unwrap(),
this.upgrade.take(), this.upgrade.take(),
@@ -280,16 +443,16 @@ where
} }
/// `Service` implementation for http transport /// `Service` implementation for http transport
pub struct HttpServiceHandler<T, P, S, B, X, U> { pub struct HttpServiceHandler<T, S, B, X, U> {
srv: CloneableService<S>, srv: CloneableService<S>,
expect: CloneableService<X>, expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>, upgrade: Option<CloneableService<U>>,
cfg: ServiceConfig, cfg: ServiceConfig,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
_t: PhantomData<(T, P, B, X)>, _t: PhantomData<(T, B, X)>,
} }
impl<T, P, S, B, X, U> HttpServiceHandler<T, P, S, B, X, U> impl<T, S, B, X, U> HttpServiceHandler<T, S, B, X, U>
where where
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
@@ -307,7 +470,7 @@ where
expect: X, expect: X,
upgrade: Option<U>, upgrade: Option<U>,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
) -> HttpServiceHandler<T, P, S, B, X, U> { ) -> HttpServiceHandler<T, S, B, X, U> {
HttpServiceHandler { HttpServiceHandler {
cfg, cfg,
on_connect, on_connect,
@@ -319,9 +482,9 @@ where
} }
} }
impl<T, P, S, B, X, U> Service for HttpServiceHandler<T, P, S, B, X, U> impl<T, S, B, X, U> Service for HttpServiceHandler<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
@@ -330,14 +493,14 @@ where
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>, U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display + Into<Error>,
{ {
type Request = ServerIo<T, P>; type Request = (T, Protocol, Option<net::SocketAddr>);
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type Future = HttpServiceHandlerResponse<T, S, B, X, U>; type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let ready = self let ready = self
.expect .expect
.poll_ready(cx) .poll_ready(cx)
@@ -359,6 +522,19 @@ where
.is_ready() .is_ready()
&& ready; && ready;
let ready = if let Some(ref mut upg) = self.upgrade {
upg.poll_ready(cx)
.map_err(|e| {
let e = e.into();
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service(e)
})?
.is_ready()
&& ready
} else {
ready
};
if ready { if ready {
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} else { } else {
@@ -366,9 +542,7 @@ where
} }
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future {
let (io, _, proto) = req.into_parts();
let on_connect = if let Some(ref on_connect) = self.on_connect { let on_connect = if let Some(ref on_connect) = self.on_connect {
Some(on_connect(&io)) Some(on_connect(&io))
} else { } else {
@@ -376,23 +550,16 @@ where
}; };
match proto { match proto {
Protocol::Http2 => { Protocol::Http2 => HttpServiceHandlerResponse {
let peer_addr = io.peer_addr(); state: State::H2Handshake(Some((
let io = Io { server::handshake(io),
inner: io, self.cfg.clone(),
unread: None, self.srv.clone(),
}; on_connect,
HttpServiceHandlerResponse { peer_addr,
state: State::Handshake(Some(( ))),
server::handshake(io), },
self.cfg.clone(), Protocol::Http1 => HttpServiceHandlerResponse {
self.srv.clone(),
peer_addr,
on_connect,
))),
}
}
Protocol::Http10 | Protocol::Http11 => HttpServiceHandlerResponse {
state: State::H1(h1::Dispatcher::new( state: State::H1(h1::Dispatcher::new(
io, io,
self.cfg.clone(), self.cfg.clone(),
@@ -400,19 +567,9 @@ where
self.expect.clone(), self.expect.clone(),
self.upgrade.clone(), self.upgrade.clone(),
on_connect, on_connect,
peer_addr,
)), )),
}, },
_ => HttpServiceHandlerResponse {
state: State::Unknown(Some((
io,
BytesMut::with_capacity(14),
self.cfg.clone(),
self.srv.clone(),
self.expect.clone(),
self.upgrade.clone(),
on_connect,
))),
},
} }
} }
} }
@@ -423,7 +580,7 @@ where
S: Service<Request = Request>, S: Service<Request = Request>,
S::Future: 'static, S::Future: 'static,
S::Error: Into<Error>, S::Error: Into<Error>,
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
B: MessageBody, B: MessageBody,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
@@ -431,25 +588,14 @@ where
U::Error: fmt::Display, U::Error: fmt::Display,
{ {
H1(#[pin] h1::Dispatcher<T, S, B, X, U>), H1(#[pin] h1::Dispatcher<T, S, B, X, U>),
H2(#[pin] Dispatcher<Io<T>, S, B>), H2(#[pin] Dispatcher<T, S, B>),
Unknown( H2Handshake(
Option<( Option<(
T, Handshake<T, Bytes>,
BytesMut,
ServiceConfig, ServiceConfig,
CloneableService<S>, CloneableService<S>,
CloneableService<X>,
Option<CloneableService<U>>,
Option<Box<dyn DataFactory>>, Option<Box<dyn DataFactory>>,
)>,
),
Handshake(
Option<(
Handshake<Io<T>, Bytes>,
ServiceConfig,
CloneableService<S>,
Option<net::SocketAddr>, Option<net::SocketAddr>,
Option<Box<dyn DataFactory>>,
)>, )>,
), ),
} }
@@ -457,7 +603,7 @@ where
#[pin_project] #[pin_project]
pub struct HttpServiceHandlerResponse<T, S, B, X, U> pub struct HttpServiceHandlerResponse<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
@@ -472,11 +618,9 @@ where
state: State<T, S, B, X, U>, state: State<T, S, B, X, U>,
} }
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U> impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
@@ -489,14 +633,14 @@ where
{ {
type Output = Result<(), DispatchError>; type Output = Result<(), DispatchError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.project().state.poll(cx) self.project().state.poll(cx)
} }
} }
impl<T, S, B, X, U> State<T, S, B, X, U> impl<T, S, B, X, U> State<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error> + 'static, S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static, S::Response: Into<Response<B>> + 'static,
@@ -509,63 +653,13 @@ where
#[project] #[project]
fn poll( fn poll(
mut self: Pin<&mut Self>, mut self: Pin<&mut Self>,
cx: &mut Context, cx: &mut Context<'_>,
) -> Poll<Result<(), DispatchError>> { ) -> Poll<Result<(), DispatchError>> {
#[project] #[project]
match self.as_mut().project() { match self.as_mut().project() {
State::H1(disp) => disp.poll(cx), State::H1(disp) => disp.poll(cx),
State::H2(disp) => disp.poll(cx), State::H2(disp) => disp.poll(cx),
State::Unknown(ref mut data) => { State::H2Handshake(ref mut data) => {
if let Some(ref mut item) = data {
loop {
// Safety - we only write to the returned slice.
let b = unsafe { item.1.bytes_mut() };
let n = ready!(Pin::new(&mut item.0).poll_read(cx, b))?;
if n == 0 {
return Poll::Ready(Ok(()));
}
// Safety - we know that 'n' bytes have
// been initialized via the contract of
// 'poll_read'
unsafe { item.1.advance_mut(n) };
if item.1.len() >= HTTP2_PREFACE.len() {
break;
}
}
} else {
panic!()
}
let (io, buf, cfg, srv, expect, upgrade, on_connect) =
data.take().unwrap();
if buf[..14] == HTTP2_PREFACE[..] {
let peer_addr = io.peer_addr();
let io = Io {
inner: io,
unread: Some(buf),
};
self.set(State::Handshake(Some((
server::handshake(io),
cfg,
srv,
peer_addr,
on_connect,
))));
} else {
self.set(State::H1(h1::Dispatcher::with_timeout(
io,
h1::Codec::new(cfg.clone()),
cfg,
buf,
None,
srv,
expect,
upgrade,
on_connect,
)))
}
self.poll(cx)
}
State::Handshake(ref mut data) => {
let conn = if let Some(ref mut item) = data { let conn = if let Some(ref mut item) = data {
match Pin::new(&mut item.0).poll(cx) { match Pin::new(&mut item.0).poll(cx) {
Poll::Ready(Ok(conn)) => conn, Poll::Ready(Ok(conn)) => conn,
@@ -578,7 +672,7 @@ where
} else { } else {
panic!() panic!()
}; };
let (_, cfg, srv, peer_addr, on_connect) = data.take().unwrap(); let (_, cfg, srv, on_connect, peer_addr) = data.take().unwrap();
self.set(State::H2(Dispatcher::new( self.set(State::H2(Dispatcher::new(
srv, conn, on_connect, cfg, None, peer_addr, srv, conn, on_connect, cfg, None, peer_addr,
))); )));
@@ -587,117 +681,3 @@ where
} }
} }
} }
/// Wrapper for `AsyncRead + AsyncWrite` types
#[pin_project::pin_project]
struct Io<T> {
unread: Option<BytesMut>,
#[pin]
inner: T,
}
impl<T: io::Read> io::Read for Io<T> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if let Some(mut bytes) = self.unread.take() {
let size = std::cmp::min(buf.len(), bytes.len());
buf[..size].copy_from_slice(&bytes[..size]);
if bytes.len() > size {
bytes.split_to(size);
self.unread = Some(bytes);
}
Ok(size)
} else {
self.inner.read(buf)
}
}
}
impl<T: io::Write> io::Write for Io<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl<T: AsyncRead> AsyncRead for Io<T> {
// unsafe fn initializer(&self) -> io::Initializer {
// self.get_mut().inner.initializer()
// }
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
self.inner.prepare_uninitialized_buffer(buf)
}
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
let this = self.project();
if let Some(mut bytes) = this.unread.take() {
let size = std::cmp::min(buf.len(), bytes.len());
buf[..size].copy_from_slice(&bytes[..size]);
if bytes.len() > size {
bytes.split_to(size);
*this.unread = Some(bytes);
}
Poll::Ready(Ok(size))
} else {
this.inner.poll_read(cx, buf)
}
}
// fn poll_read_vectored(
// self: Pin<&mut Self>,
// cx: &mut Context<'_>,
// bufs: &mut [io::IoSliceMut<'_>],
// ) -> Poll<io::Result<usize>> {
// self.get_mut().inner.poll_read_vectored(cx, bufs)
// }
}
impl<T: AsyncWrite> tokio_io::AsyncWrite for Io<T> {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
self.project().inner.poll_write(cx, buf)
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.project().inner.poll_flush(cx)
}
fn poll_shutdown(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>> {
self.project().inner.poll_shutdown(cx)
}
}
impl<T: IoStream> actix_server_config::IoStream for Io<T> {
#[inline]
fn peer_addr(&self) -> Option<net::SocketAddr> {
self.inner.peer_addr()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.inner.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<std::time::Duration>) -> io::Result<()> {
self.inner.set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<std::time::Duration>) -> io::Result<()> {
self.inner.set_keepalive(dur)
}
}

View File

@@ -1,4 +1,5 @@
//! Test Various helpers for Actix applications to use during testing. //! Test Various helpers for Actix applications to use during testing.
use std::convert::TryFrom;
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use std::pin::Pin; use std::pin::Pin;
@@ -6,10 +7,9 @@ use std::str::FromStr;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_server_config::IoStream; use bytes::{Bytes, BytesMut};
use bytes::{Buf, Bytes, BytesMut};
use http::header::{self, HeaderName, HeaderValue}; use http::header::{self, HeaderName, HeaderValue};
use http::{HttpTryFrom, Method, Uri, Version}; use http::{Error as HttpError, Method, Uri, Version};
use percent_encoding::percent_encode; use percent_encoding::percent_encode;
use crate::cookie::{Cookie, CookieJar, USERINFO}; use crate::cookie::{Cookie, CookieJar, USERINFO};
@@ -21,8 +21,6 @@ use crate::Request;
/// Test `Request` builder /// Test `Request` builder
/// ///
/// ```rust,ignore /// ```rust,ignore
/// # extern crate http;
/// # extern crate actix_web;
/// # use http::{header, StatusCode}; /// # use http::{header, StatusCode};
/// # use actix_web::*; /// # use actix_web::*;
/// use actix_web::test::TestRequest; /// use actix_web::test::TestRequest;
@@ -35,15 +33,13 @@ use crate::Request;
/// } /// }
/// } /// }
/// ///
/// fn main() { /// let resp = TestRequest::with_header("content-type", "text/plain")
/// let resp = TestRequest::with_header("content-type", "text/plain") /// .run(&index)
/// .run(&index) /// .unwrap();
/// .unwrap(); /// assert_eq!(resp.status(), StatusCode::OK);
/// assert_eq!(resp.status(), StatusCode::OK);
/// ///
/// let resp = TestRequest::default().run(&index).unwrap(); /// let resp = TestRequest::default().run(&index).unwrap();
/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
/// }
/// ``` /// ```
pub struct TestRequest(Option<Inner>); pub struct TestRequest(Option<Inner>);
@@ -83,7 +79,8 @@ impl TestRequest {
/// Create TestRequest and set header /// Create TestRequest and set header
pub fn with_header<K, V>(key: K, value: V) -> TestRequest pub fn with_header<K, V>(key: K, value: V) -> TestRequest
where where
HeaderName: HttpTryFrom<K>, HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
TestRequest::default().header(key, value).take() TestRequest::default().header(key, value).take()
@@ -119,7 +116,8 @@ impl TestRequest {
/// Set a header /// Set a header
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
where where
HeaderName: HttpTryFrom<K>, HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
if let Ok(key) = HeaderName::try_from(key) { if let Ok(key) = HeaderName::try_from(key) {
@@ -272,17 +270,3 @@ impl AsyncWrite for TestBuffer {
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} }
} }
impl IoStream for TestBuffer {
fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> {
Ok(())
}
fn set_linger(&mut self, _dur: Option<std::time::Duration>) -> io::Result<()> {
Ok(())
}
fn set_keepalive(&mut self, _dur: Option<std::time::Duration>) -> io::Result<()> {
Ok(())
}
}

View File

@@ -12,10 +12,12 @@ pub enum Message {
Text(String), Text(String),
/// Binary message /// Binary message
Binary(Bytes), Binary(Bytes),
/// Continuation
Continuation(Item),
/// Ping message /// Ping message
Ping(String), Ping(Bytes),
/// Pong message /// Pong message
Pong(String), Pong(Bytes),
/// Close message with optional reason /// Close message with optional reason
Close(Option<CloseReason>), Close(Option<CloseReason>),
/// No-op. Useful for actix-net services /// No-op. Useful for actix-net services
@@ -26,22 +28,41 @@ pub enum Message {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Frame { pub enum Frame {
/// Text frame, codec does not verify utf8 encoding /// Text frame, codec does not verify utf8 encoding
Text(Option<BytesMut>), Text(Bytes),
/// Binary frame /// Binary frame
Binary(Option<BytesMut>), Binary(Bytes),
/// Continuation
Continuation(Item),
/// Ping message /// Ping message
Ping(String), Ping(Bytes),
/// Pong message /// Pong message
Pong(String), Pong(Bytes),
/// Close message with optional reason /// Close message with optional reason
Close(Option<CloseReason>), Close(Option<CloseReason>),
} }
/// `WebSocket` continuation item
#[derive(Debug, PartialEq)]
pub enum Item {
FirstText(Bytes),
FirstBinary(Bytes),
Continue(Bytes),
Last(Bytes),
}
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
/// WebSockets protocol codec /// WebSockets protocol codec
pub struct Codec { pub struct Codec {
flags: Flags,
max_size: usize, max_size: usize,
server: bool, }
bitflags::bitflags! {
struct Flags: u8 {
const SERVER = 0b0000_0001;
const CONTINUATION = 0b0000_0010;
const W_CONTINUATION = 0b0000_0100;
}
} }
impl Codec { impl Codec {
@@ -49,7 +70,7 @@ impl Codec {
pub fn new() -> Codec { pub fn new() -> Codec {
Codec { Codec {
max_size: 65_536, max_size: 65_536,
server: true, flags: Flags::SERVER,
} }
} }
@@ -65,7 +86,7 @@ impl Codec {
/// ///
/// By default decoder works in server mode. /// By default decoder works in server mode.
pub fn client_mode(mut self) -> Self { pub fn client_mode(mut self) -> Self {
self.server = false; self.flags.remove(Flags::SERVER);
self self
} }
} }
@@ -76,19 +97,94 @@ impl Encoder for Codec {
fn encode(&mut self, item: Message, dst: &mut BytesMut) -> Result<(), Self::Error> { fn encode(&mut self, item: Message, dst: &mut BytesMut) -> Result<(), Self::Error> {
match item { match item {
Message::Text(txt) => { Message::Text(txt) => Parser::write_message(
Parser::write_message(dst, txt, OpCode::Text, true, !self.server) dst,
txt,
OpCode::Text,
true,
!self.flags.contains(Flags::SERVER),
),
Message::Binary(bin) => Parser::write_message(
dst,
bin,
OpCode::Binary,
true,
!self.flags.contains(Flags::SERVER),
),
Message::Ping(txt) => Parser::write_message(
dst,
txt,
OpCode::Ping,
true,
!self.flags.contains(Flags::SERVER),
),
Message::Pong(txt) => Parser::write_message(
dst,
txt,
OpCode::Pong,
true,
!self.flags.contains(Flags::SERVER),
),
Message::Close(reason) => {
Parser::write_close(dst, reason, !self.flags.contains(Flags::SERVER))
} }
Message::Binary(bin) => { Message::Continuation(cont) => match cont {
Parser::write_message(dst, bin, OpCode::Binary, true, !self.server) Item::FirstText(data) => {
} if self.flags.contains(Flags::W_CONTINUATION) {
Message::Ping(txt) => { return Err(ProtocolError::ContinuationStarted);
Parser::write_message(dst, txt, OpCode::Ping, true, !self.server) } else {
} self.flags.insert(Flags::W_CONTINUATION);
Message::Pong(txt) => { Parser::write_message(
Parser::write_message(dst, txt, OpCode::Pong, true, !self.server) dst,
} &data[..],
Message::Close(reason) => Parser::write_close(dst, reason, !self.server), OpCode::Binary,
false,
!self.flags.contains(Flags::SERVER),
)
}
}
Item::FirstBinary(data) => {
if self.flags.contains(Flags::W_CONTINUATION) {
return Err(ProtocolError::ContinuationStarted);
} else {
self.flags.insert(Flags::W_CONTINUATION);
Parser::write_message(
dst,
&data[..],
OpCode::Text,
false,
!self.flags.contains(Flags::SERVER),
)
}
}
Item::Continue(data) => {
if self.flags.contains(Flags::W_CONTINUATION) {
Parser::write_message(
dst,
&data[..],
OpCode::Continue,
false,
!self.flags.contains(Flags::SERVER),
)
} else {
return Err(ProtocolError::ContinuationNotStarted);
}
}
Item::Last(data) => {
if self.flags.contains(Flags::W_CONTINUATION) {
self.flags.remove(Flags::W_CONTINUATION);
Parser::write_message(
dst,
&data[..],
OpCode::Continue,
true,
!self.flags.contains(Flags::SERVER),
)
} else {
return Err(ProtocolError::ContinuationNotStarted);
}
}
},
Message::Nop => (), Message::Nop => (),
} }
Ok(()) Ok(())
@@ -100,15 +196,64 @@ impl Decoder for Codec {
type Error = ProtocolError; type Error = ProtocolError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> { fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
match Parser::parse(src, self.server, self.max_size) { match Parser::parse(src, self.flags.contains(Flags::SERVER), self.max_size) {
Ok(Some((finished, opcode, payload))) => { Ok(Some((finished, opcode, payload))) => {
// continuation is not supported // continuation is not supported
if !finished { if !finished {
return Err(ProtocolError::NoContinuation); return match opcode {
OpCode::Continue => {
if self.flags.contains(Flags::CONTINUATION) {
Ok(Some(Frame::Continuation(Item::Continue(
payload
.map(|pl| pl.freeze())
.unwrap_or_else(Bytes::new),
))))
} else {
Err(ProtocolError::ContinuationNotStarted)
}
}
OpCode::Binary => {
if !self.flags.contains(Flags::CONTINUATION) {
self.flags.insert(Flags::CONTINUATION);
Ok(Some(Frame::Continuation(Item::FirstBinary(
payload
.map(|pl| pl.freeze())
.unwrap_or_else(Bytes::new),
))))
} else {
Err(ProtocolError::ContinuationStarted)
}
}
OpCode::Text => {
if !self.flags.contains(Flags::CONTINUATION) {
self.flags.insert(Flags::CONTINUATION);
Ok(Some(Frame::Continuation(Item::FirstText(
payload
.map(|pl| pl.freeze())
.unwrap_or_else(Bytes::new),
))))
} else {
Err(ProtocolError::ContinuationStarted)
}
}
_ => {
error!("Unfinished fragment {:?}", opcode);
Err(ProtocolError::ContinuationFragment(opcode))
}
};
} }
match opcode { match opcode {
OpCode::Continue => Err(ProtocolError::NoContinuation), OpCode::Continue => {
if self.flags.contains(Flags::CONTINUATION) {
self.flags.remove(Flags::CONTINUATION);
Ok(Some(Frame::Continuation(Item::Last(
payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
))))
} else {
Err(ProtocolError::ContinuationNotStarted)
}
}
OpCode::Bad => Err(ProtocolError::BadOpCode), OpCode::Bad => Err(ProtocolError::BadOpCode),
OpCode::Close => { OpCode::Close => {
if let Some(ref pl) = payload { if let Some(ref pl) = payload {
@@ -118,29 +263,18 @@ impl Decoder for Codec {
Ok(Some(Frame::Close(None))) Ok(Some(Frame::Close(None)))
} }
} }
OpCode::Ping => { OpCode::Ping => Ok(Some(Frame::Ping(
if let Some(ref pl) = payload { payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
Ok(Some(Frame::Ping(String::from_utf8_lossy(pl).into()))) ))),
} else { OpCode::Pong => Ok(Some(Frame::Pong(
Ok(Some(Frame::Ping(String::new()))) payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
} ))),
} OpCode::Binary => Ok(Some(Frame::Binary(
OpCode::Pong => { payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
if let Some(ref pl) = payload { ))),
Ok(Some(Frame::Pong(String::from_utf8_lossy(pl).into()))) OpCode::Text => Ok(Some(Frame::Text(
} else { payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
Ok(Some(Frame::Pong(String::new()))) ))),
}
}
OpCode::Binary => Ok(Some(Frame::Binary(payload))),
OpCode::Text => {
Ok(Some(Frame::Text(payload)))
//let tmp = Vec::from(payload.as_ref());
//match String::from_utf8(tmp) {
// Ok(s) => Ok(Some(Message::Text(s))),
// Err(_) => Err(ProtocolError::BadEncoding),
//}
}
} }
} }
Ok(None) => Ok(None), Ok(None) => Ok(None),

View File

@@ -4,19 +4,19 @@ use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_service::{IntoService, Service}; use actix_service::{IntoService, Service};
use actix_utils::framed::{FramedTransport, FramedTransportError}; use actix_utils::framed;
use super::{Codec, Frame, Message}; use super::{Codec, Frame, Message};
pub struct Transport<S, T> pub struct Dispatcher<S, T>
where where
S: Service<Request = Frame, Response = Message> + 'static, S: Service<Request = Frame, Response = Message> + 'static,
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite,
{ {
inner: FramedTransport<S, T, Codec>, inner: framed::Dispatcher<S, T, Codec>,
} }
impl<S, T> Transport<S, T> impl<S, T> Dispatcher<S, T>
where where
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite,
S: Service<Request = Frame, Response = Message>, S: Service<Request = Frame, Response = Message>,
@@ -24,28 +24,28 @@ where
S::Error: 'static, S::Error: 'static,
{ {
pub fn new<F: IntoService<S>>(io: T, service: F) -> Self { pub fn new<F: IntoService<S>>(io: T, service: F) -> Self {
Transport { Dispatcher {
inner: FramedTransport::new(Framed::new(io, Codec::new()), service), inner: framed::Dispatcher::new(Framed::new(io, Codec::new()), service),
} }
} }
pub fn with<F: IntoService<S>>(framed: Framed<T, Codec>, service: F) -> Self { pub fn with<F: IntoService<S>>(framed: Framed<T, Codec>, service: F) -> Self {
Transport { Dispatcher {
inner: FramedTransport::new(framed, service), inner: framed::Dispatcher::new(framed, service),
} }
} }
} }
impl<S, T> Future for Transport<S, T> impl<S, T> Future for Dispatcher<S, T>
where where
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite,
S: Service<Request = Frame, Response = Message>, S: Service<Request = Frame, Response = Message>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static, S::Error: 'static,
{ {
type Output = Result<(), FramedTransportError<S::Error, Codec>>; type Output = Result<(), framed::DispatcherError<S::Error, Codec>>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner).poll(cx) Pin::new(&mut self.inner).poll(cx)
} }
} }

View File

@@ -1,6 +1,6 @@
use std::convert::TryFrom; use std::convert::TryFrom;
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{Buf, BufMut, BytesMut};
use log::debug; use log::debug;
use rand; use rand;
@@ -108,7 +108,7 @@ impl Parser {
} }
// remove prefix // remove prefix
src.split_to(idx); src.advance(idx);
// no need for body // no need for body
if length == 0 { if length == 0 {
@@ -154,14 +154,14 @@ impl Parser {
} }
/// Generate binary representation /// Generate binary representation
pub fn write_message<B: Into<Bytes>>( pub fn write_message<B: AsRef<[u8]>>(
dst: &mut BytesMut, dst: &mut BytesMut,
pl: B, pl: B,
op: OpCode, op: OpCode,
fin: bool, fin: bool,
mask: bool, mask: bool,
) { ) {
let payload = pl.into(); let payload = pl.as_ref();
let one: u8 = if fin { let one: u8 = if fin {
0x80 | Into::<u8>::into(op) 0x80 | Into::<u8>::into(op)
} else { } else {
@@ -180,11 +180,11 @@ impl Parser {
} else if payload_len <= 65_535 { } else if payload_len <= 65_535 {
dst.reserve(p_len + 4 + if mask { 4 } else { 0 }); dst.reserve(p_len + 4 + if mask { 4 } else { 0 });
dst.put_slice(&[one, two | 126]); dst.put_slice(&[one, two | 126]);
dst.put_u16_be(payload_len as u16); dst.put_u16(payload_len as u16);
} else { } else {
dst.reserve(p_len + 10 + if mask { 4 } else { 0 }); dst.reserve(p_len + 10 + if mask { 4 } else { 0 });
dst.put_slice(&[one, two | 127]); dst.put_slice(&[one, two | 127]);
dst.put_u64_be(payload_len as u64); dst.put_u64(payload_len as u64);
}; };
if mask { if mask {

View File

@@ -51,7 +51,7 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) {
// inefficient, it could be done better. The compiler does not understand that // inefficient, it could be done better. The compiler does not understand that
// a `ShortSlice` must be smaller than a u64. // a `ShortSlice` must be smaller than a u64.
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
fn xor_short(buf: ShortSlice, mask: u64) { fn xor_short(buf: ShortSlice<'_>, mask: u64) {
// Unsafe: we know that a `ShortSlice` fits in a u64 // Unsafe: we know that a `ShortSlice` fits in a u64
unsafe { unsafe {
let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len()); let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len());
@@ -77,7 +77,7 @@ unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] {
#[inline] #[inline]
// Splits a slice into three parts: an unaligned short head and tail, plus an aligned // Splits a slice into three parts: an unaligned short head and tail, plus an aligned
// u64 mid section. // u64 mid section.
fn align_buf(buf: &mut [u8]) -> (ShortSlice, &mut [u64], ShortSlice) { fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) {
let start_ptr = buf.as_ptr() as usize; let start_ptr = buf.as_ptr() as usize;
let end_ptr = start_ptr + buf.len(); let end_ptr = start_ptr + buf.len();

View File

@@ -13,15 +13,15 @@ use crate::message::RequestHead;
use crate::response::{Response, ResponseBuilder}; use crate::response::{Response, ResponseBuilder};
mod codec; mod codec;
mod dispatcher;
mod frame; mod frame;
mod mask; mod mask;
mod proto; mod proto;
mod transport;
pub use self::codec::{Codec, Frame, Message}; pub use self::codec::{Codec, Frame, Item, Message};
pub use self::dispatcher::Dispatcher;
pub use self::frame::Parser; pub use self::frame::Parser;
pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode}; pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode};
pub use self::transport::Transport;
/// Websocket protocol errors /// Websocket protocol errors
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
@@ -44,12 +44,15 @@ pub enum ProtocolError {
/// A payload reached size limit. /// A payload reached size limit.
#[display(fmt = "A payload reached size limit.")] #[display(fmt = "A payload reached size limit.")]
Overflow, Overflow,
/// Continuation is not supported /// Continuation is not started
#[display(fmt = "Continuation is not supported.")] #[display(fmt = "Continuation is not started.")]
NoContinuation, ContinuationNotStarted,
/// Bad utf-8 encoding /// Received new continuation but it is already started
#[display(fmt = "Bad utf-8 encoding.")] #[display(fmt = "Received new continuation but it is already started")]
BadEncoding, ContinuationStarted,
/// Unknown continuation fragment
#[display(fmt = "Unknown continuation fragment.")]
ContinuationFragment(OpCode),
/// Io error /// Io error
#[display(fmt = "io error: {}", _0)] #[display(fmt = "io error: {}", _0)]
Io(io::Error), Io(io::Error),

View File

@@ -24,7 +24,7 @@ pub enum OpCode {
} }
impl fmt::Display for OpCode { impl fmt::Display for OpCode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
Continue => write!(f, "CONTINUE"), Continue => write!(f, "CONTINUE"),
Text => write!(f, "TEXT"), Text => write!(f, "TEXT"),
@@ -95,7 +95,7 @@ pub enum CloseCode {
Abnormal, Abnormal,
/// Indicates that an endpoint is terminating the connection /// Indicates that an endpoint is terminating the connection
/// because it has received data within a message that was not /// because it has received data within a message that was not
/// consistent with the type of the message (e.g., non-UTF-8 [RFC3629] /// consistent with the type of the message (e.g., non-UTF-8 \[RFC3629\]
/// data within a text message). /// data within a text message).
Invalid, Invalid,
/// Indicates that an endpoint is terminating the connection /// Indicates that an endpoint is terminating the connection

View File

@@ -3,7 +3,7 @@ use bytes::Bytes;
use futures::future::{self, ok}; use futures::future::{self, ok};
use actix_http::{http, HttpService, Request, Response}; use actix_http::{http, HttpService, Request, Response};
use actix_http_test::{block_on, TestServer}; use actix_http_test::test_server;
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \
@@ -27,65 +27,62 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World"; Hello World Hello World Hello World Hello World Hello World";
#[test] #[actix_rt::test]
fn test_h1_v2() { async fn test_h1_v2() {
block_on(async { let srv = test_server(move || {
let srv = TestServer::start(move || { HttpService::build()
HttpService::build() .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR)))
.finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .tcp()
}); });
let response = srv.get("/").send().await.unwrap(); let response = srv.get("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
let request = srv.get("/").header("x-test", "111").send(); let request = srv.get("/").header("x-test", "111").send();
let mut response = request.await.unwrap(); let mut response = request.await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
let bytes = response.body().await.unwrap(); let bytes = response.body().await.unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
let mut response = srv.post("/").send().await.unwrap(); let mut response = srv.post("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
let bytes = response.body().await.unwrap(); let bytes = response.body().await.unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
})
} }
#[test] #[actix_rt::test]
fn test_connection_close() { async fn test_connection_close() {
block_on(async { let srv = test_server(move || {
let srv = TestServer::start(move || { HttpService::build()
HttpService::build() .finish(|_| ok::<_, ()>(Response::Ok().body(STR)))
.finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .tcp()
.map(|_| ()) .map(|_| ())
}); });
let response = srv.get("/").force_close().send().await.unwrap(); let response = srv.get("/").force_close().send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
})
} }
#[test] #[actix_rt::test]
fn test_with_query_parameter() { async fn test_with_query_parameter() {
block_on(async { let srv = test_server(move || {
let srv = TestServer::start(move || { HttpService::build()
HttpService::build() .finish(|req: Request| {
.finish(|req: Request| { if req.uri().query().unwrap().contains("qp=") {
if req.uri().query().unwrap().contains("qp=") { ok::<_, ()>(Response::Ok().finish())
ok::<_, ()>(Response::Ok().finish()) } else {
} else { ok::<_, ()>(Response::BadRequest().finish())
ok::<_, ()>(Response::BadRequest().finish()) }
} })
}) .tcp()
.map(|_| ()) .map(|_| ())
}); });
let request = srv.request(http::Method::GET, srv.url("/?qp=5")); let request = srv.request(http::Method::GET, srv.url("/?qp=5"));
let response = request.send().await.unwrap(); let response = request.send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
})
} }

View File

@@ -1,11 +1,8 @@
#![cfg(feature = "openssl")] #![cfg(feature = "openssl")]
use std::io; use std::io;
use actix_codec::{AsyncRead, AsyncWrite}; use actix_http_test::test_server;
use actix_http_test::{block_on, TestServer}; use actix_service::{fn_service, ServiceFactory};
use actix_server::ssl::OpensslAcceptor;
use actix_server_config::ServerConfig;
use actix_service::{factory_fn_cfg, pipeline_factory, service_fn2, ServiceFactory};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::future::{err, ok, ready}; use futures::future::{err, ok, ready};
@@ -36,7 +33,7 @@ where
Ok(body) Ok(body)
} }
fn ssl_acceptor<T: AsyncRead + AsyncWrite>() -> io::Result<OpensslAcceptor<T, ()>> { fn ssl_acceptor() -> SslAcceptor {
// load ssl keys // load ssl keys
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder builder
@@ -47,169 +44,132 @@ fn ssl_acceptor<T: AsyncRead + AsyncWrite>() -> io::Result<OpensslAcceptor<T, ()
.unwrap(); .unwrap();
builder.set_alpn_select_callback(|_, protos| { builder.set_alpn_select_callback(|_, protos| {
const H2: &[u8] = b"\x02h2"; const H2: &[u8] = b"\x02h2";
const H11: &[u8] = b"\x08http/1.1";
if protos.windows(3).any(|window| window == H2) { if protos.windows(3).any(|window| window == H2) {
Ok(b"h2") Ok(b"h2")
} else if protos.windows(9).any(|window| window == H11) {
Ok(b"http/1.1")
} else { } else {
Err(AlpnError::NOACK) Err(AlpnError::NOACK)
} }
}); });
builder.set_alpn_protos(b"\x02h2")?; builder
Ok(OpensslAcceptor::new(builder.build())) .set_alpn_protos(b"\x08http/1.1\x02h2")
.expect("Can not contrust SslAcceptor");
builder.build()
} }
#[test] #[actix_rt::test]
fn test_h2() -> io::Result<()> { async fn test_h2() -> io::Result<()> {
block_on(async { let srv = test_server(move || {
let openssl = ssl_acceptor()?; HttpService::build()
let srv = TestServer::start(move || { .h2(|_| ok::<_, Error>(Response::Ok().finish()))
pipeline_factory( .openssl(ssl_acceptor())
openssl .map_err(|_| ())
.clone() });
.map_err(|e| println!("Openssl error: {}", e)),
)
.and_then(
HttpService::build()
.h2(|_| ok::<_, Error>(Response::Ok().finish()))
.map_err(|_| ()),
)
});
let response = srv.sget("/").send().await.unwrap(); let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
Ok(()) Ok(())
})
} }
#[test] #[actix_rt::test]
fn test_h2_1() -> io::Result<()> { async fn test_h2_1() -> io::Result<()> {
block_on(async { let srv = test_server(move || {
let openssl = ssl_acceptor()?; HttpService::build()
let srv = TestServer::start(move || { .finish(|req: Request| {
pipeline_factory( assert!(req.peer_addr().is_some());
openssl assert_eq!(req.version(), Version::HTTP_2);
.clone() ok::<_, Error>(Response::Ok().finish())
.map_err(|e| println!("Openssl error: {}", e)), })
) .openssl(ssl_acceptor())
.and_then( .map_err(|_| ())
HttpService::build() });
.finish(|req: Request| {
assert!(req.peer_addr().is_some());
assert_eq!(req.version(), Version::HTTP_2);
ok::<_, Error>(Response::Ok().finish())
})
.map_err(|_| ()),
)
});
let response = srv.sget("/").send().await.unwrap(); let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
Ok(()) Ok(())
})
} }
#[test] #[actix_rt::test]
fn test_h2_body() -> io::Result<()> { async fn test_h2_body() -> io::Result<()> {
block_on(async { let data = "HELLOWORLD".to_owned().repeat(64 * 1024);
let data = "HELLOWORLD".to_owned().repeat(64 * 1024); let mut srv = test_server(move || {
let openssl = ssl_acceptor()?; HttpService::build()
let mut srv = TestServer::start(move || { .h2(|mut req: Request<_>| {
pipeline_factory( async move {
openssl let body = load_body(req.take_payload()).await?;
.clone() Ok::<_, Error>(Response::Ok().body(body))
.map_err(|e| println!("Openssl error: {}", e)), }
) })
.and_then( .openssl(ssl_acceptor())
HttpService::build() .map_err(|_| ())
.h2(|mut req: Request<_>| { });
async move {
let body = load_body(req.take_payload()).await?;
Ok::<_, Error>(Response::Ok().body(body))
}
})
.map_err(|_| ()),
)
});
let response = srv.sget("/").send_body(data.clone()).await.unwrap(); let response = srv.sget("/").send_body(data.clone()).await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
let body = srv.load_body(response).await.unwrap(); let body = srv.load_body(response).await.unwrap();
assert_eq!(&body, data.as_bytes()); assert_eq!(&body, data.as_bytes());
Ok(()) Ok(())
})
} }
#[test] #[actix_rt::test]
fn test_h2_content_length() { async fn test_h2_content_length() {
block_on(async { let srv = test_server(move || {
let openssl = ssl_acceptor().unwrap(); HttpService::build()
.h2(|req: Request| {
let indx: usize = req.uri().path()[1..].parse().unwrap();
let statuses = [
StatusCode::NO_CONTENT,
StatusCode::CONTINUE,
StatusCode::SWITCHING_PROTOCOLS,
StatusCode::PROCESSING,
StatusCode::OK,
StatusCode::NOT_FOUND,
];
ok::<_, ()>(Response::new(statuses[indx]))
})
.openssl(ssl_acceptor())
.map_err(|_| ())
});
let srv = TestServer::start(move || { let header = HeaderName::from_static("content-length");
pipeline_factory( let value = HeaderValue::from_static("0");
openssl
.clone()
.map_err(|e| println!("Openssl error: {}", e)),
)
.and_then(
HttpService::build()
.h2(|req: Request| {
let indx: usize = req.uri().path()[1..].parse().unwrap();
let statuses = [
StatusCode::NO_CONTENT,
StatusCode::CONTINUE,
StatusCode::SWITCHING_PROTOCOLS,
StatusCode::PROCESSING,
StatusCode::OK,
StatusCode::NOT_FOUND,
];
ok::<_, ()>(Response::new(statuses[indx]))
})
.map_err(|_| ()),
)
});
let header = HeaderName::from_static("content-length"); {
let value = HeaderValue::from_static("0"); for i in 0..4 {
let req = srv
.request(Method::GET, srv.surl(&format!("/{}", i)))
.send();
let response = req.await.unwrap();
assert_eq!(response.headers().get(&header), None);
{ let req = srv
for i in 0..4 { .request(Method::HEAD, srv.surl(&format!("/{}", i)))
let req = srv .send();
.request(Method::GET, srv.surl(&format!("/{}", i))) let response = req.await.unwrap();
.send(); assert_eq!(response.headers().get(&header), None);
let response = req.await.unwrap();
assert_eq!(response.headers().get(&header), None);
let req = srv
.request(Method::HEAD, srv.surl(&format!("/{}", i)))
.send();
let response = req.await.unwrap();
assert_eq!(response.headers().get(&header), None);
}
for i in 4..6 {
let req = srv
.request(Method::GET, srv.surl(&format!("/{}", i)))
.send();
let response = req.await.unwrap();
assert_eq!(response.headers().get(&header), Some(&value));
}
} }
})
for i in 4..6 {
let req = srv
.request(Method::GET, srv.surl(&format!("/{}", i)))
.send();
let response = req.await.unwrap();
assert_eq!(response.headers().get(&header), Some(&value));
}
}
} }
#[test] #[actix_rt::test]
fn test_h2_headers() { async fn test_h2_headers() {
block_on(async { let data = STR.repeat(10);
let data = STR.repeat(10); let data2 = data.clone();
let data2 = data.clone();
let openssl = ssl_acceptor().unwrap();
let mut srv = TestServer::start(move || { let mut srv = test_server(move || {
let data = data.clone(); let data = data.clone();
pipeline_factory(openssl
.clone()
.map_err(|e| println!("Openssl error: {}", e)))
.and_then(
HttpService::build().h2(move |_| { HttpService::build().h2(move |_| {
let mut builder = Response::Ok(); let mut builder = Response::Ok();
for idx in 0..90 { for idx in 0..90 {
@@ -231,16 +191,17 @@ fn test_h2_headers() {
); );
} }
ok::<_, ()>(builder.body(data.clone())) ok::<_, ()>(builder.body(data.clone()))
}).map_err(|_| ())) })
}); .openssl(ssl_acceptor())
.map_err(|_| ())
});
let response = srv.sget("/").send().await.unwrap(); let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from(data2)); assert_eq!(bytes, Bytes::from(data2));
})
} }
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
@@ -265,281 +226,191 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World"; Hello World Hello World Hello World Hello World Hello World";
#[test] #[actix_rt::test]
fn test_h2_body2() { async fn test_h2_body2() {
block_on(async { let mut srv = test_server(move || {
let openssl = ssl_acceptor().unwrap(); HttpService::build()
let mut srv = TestServer::start(move || { .h2(|_| ok::<_, ()>(Response::Ok().body(STR)))
pipeline_factory( .openssl(ssl_acceptor())
openssl .map_err(|_| ())
.clone() });
.map_err(|e| println!("Openssl error: {}", e)),
)
.and_then(
HttpService::build()
.h2(|_| ok::<_, ()>(Response::Ok().body(STR)))
.map_err(|_| ()),
)
});
let response = srv.sget("/").send().await.unwrap(); let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
})
} }
#[test] #[actix_rt::test]
fn test_h2_head_empty() { async fn test_h2_head_empty() {
block_on(async { let mut srv = test_server(move || {
let openssl = ssl_acceptor().unwrap(); HttpService::build()
let mut srv = TestServer::start(move || { .finish(|_| ok::<_, ()>(Response::Ok().body(STR)))
pipeline_factory( .openssl(ssl_acceptor())
openssl .map_err(|_| ())
.clone() });
.map_err(|e| println!("Openssl error: {}", e)),
)
.and_then(
HttpService::build()
.finish(|_| ok::<_, ()>(Response::Ok().body(STR)))
.map_err(|_| ()),
)
});
let response = srv.shead("/").send().await.unwrap(); let response = srv.shead("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
assert_eq!(response.version(), Version::HTTP_2); assert_eq!(response.version(), Version::HTTP_2);
{ {
let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); let len = response.headers().get(header::CONTENT_LENGTH).unwrap();
assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap());
} }
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert!(bytes.is_empty()); assert!(bytes.is_empty());
})
} }
#[test] #[actix_rt::test]
fn test_h2_head_binary() { async fn test_h2_head_binary() {
block_on(async { let mut srv = test_server(move || {
let openssl = ssl_acceptor().unwrap(); HttpService::build()
let mut srv = TestServer::start(move || { .h2(|_| {
pipeline_factory( ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR))
openssl })
.clone() .openssl(ssl_acceptor())
.map_err(|e| println!("Openssl error: {}", e)), .map_err(|_| ())
) });
.and_then(
HttpService::build()
.h2(|_| {
ok::<_, ()>(
Response::Ok().content_length(STR.len() as u64).body(STR),
)
})
.map_err(|_| ()),
)
});
let response = srv.shead("/").send().await.unwrap(); let response = srv.shead("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
{ {
let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); let len = response.headers().get(header::CONTENT_LENGTH).unwrap();
assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap());
} }
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert!(bytes.is_empty()); assert!(bytes.is_empty());
})
} }
#[test] #[actix_rt::test]
fn test_h2_head_binary2() { async fn test_h2_head_binary2() {
block_on(async { let srv = test_server(move || {
let openssl = ssl_acceptor().unwrap(); HttpService::build()
let srv = TestServer::start(move || { .h2(|_| ok::<_, ()>(Response::Ok().body(STR)))
pipeline_factory( .openssl(ssl_acceptor())
openssl .map_err(|_| ())
.clone() });
.map_err(|e| println!("Openssl error: {}", e)),
)
.and_then(
HttpService::build()
.h2(|_| ok::<_, ()>(Response::Ok().body(STR)))
.map_err(|_| ()),
)
});
let response = srv.shead("/").send().await.unwrap(); let response = srv.shead("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
{ {
let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); let len = response.headers().get(header::CONTENT_LENGTH).unwrap();
assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap());
} }
})
} }
#[test] #[actix_rt::test]
fn test_h2_body_length() { async fn test_h2_body_length() {
block_on(async { let mut srv = test_server(move || {
let openssl = ssl_acceptor().unwrap(); HttpService::build()
let mut srv = TestServer::start(move || { .h2(|_| {
pipeline_factory( let body = once(ok(Bytes::from_static(STR.as_ref())));
openssl ok::<_, ()>(
.clone() Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)),
.map_err(|e| println!("Openssl error: {}", e)), )
) })
.and_then( .openssl(ssl_acceptor())
HttpService::build() .map_err(|_| ())
.h2(|_| { });
let body = once(ok(Bytes::from_static(STR.as_ref())));
ok::<_, ()>(
Response::Ok()
.body(body::SizedStream::new(STR.len() as u64, body)),
)
})
.map_err(|_| ()),
)
});
let response = srv.sget("/").send().await.unwrap(); let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
})
} }
#[test] #[actix_rt::test]
fn test_h2_body_chunked_explicit() { async fn test_h2_body_chunked_explicit() {
block_on(async { let mut srv = test_server(move || {
let openssl = ssl_acceptor().unwrap(); HttpService::build()
let mut srv = TestServer::start(move || { .h2(|_| {
pipeline_factory( let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
openssl ok::<_, ()>(
.clone() Response::Ok()
.map_err(|e| println!("Openssl error: {}", e)), .header(header::TRANSFER_ENCODING, "chunked")
) .streaming(body),
.and_then( )
HttpService::build() })
.h2(|_| { .openssl(ssl_acceptor())
let body = .map_err(|_| ())
once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); });
ok::<_, ()>(
Response::Ok()
.header(header::TRANSFER_ENCODING, "chunked")
.streaming(body),
)
})
.map_err(|_| ()),
)
});
let response = srv.sget("/").send().await.unwrap(); let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); assert!(!response.headers().contains_key(header::TRANSFER_ENCODING));
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
// decode // decode
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
})
} }
#[test] #[actix_rt::test]
fn test_h2_response_http_error_handling() { async fn test_h2_response_http_error_handling() {
block_on(async { let mut srv = test_server(move || {
let openssl = ssl_acceptor().unwrap(); HttpService::build()
.h2(fn_service(|_| {
let broken_header = Bytes::from_static(b"\0\0\0");
ok::<_, ()>(
Response::Ok()
.header(header::CONTENT_TYPE, broken_header)
.body(STR),
)
}))
.openssl(ssl_acceptor())
.map_err(|_| ())
});
let mut srv = TestServer::start(move || { let response = srv.sget("/").send().await.unwrap();
pipeline_factory( assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
openssl
.clone()
.map_err(|e| println!("Openssl error: {}", e)),
)
.and_then(
HttpService::build()
.h2(factory_fn_cfg(|_: &ServerConfig| {
ok::<_, ()>(service_fn2(|_| {
let broken_header = Bytes::from_static(b"\0\0\0");
ok::<_, ()>(
Response::Ok()
.header(header::CONTENT_TYPE, broken_header)
.body(STR),
)
}))
}))
.map_err(|_| ()),
)
});
let response = srv.sget("/").send().await.unwrap(); // read response
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(b"failed to parse header value"));
// read response
let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(b"failed to parse header value"));
})
} }
#[test] #[actix_rt::test]
fn test_h2_service_error() { async fn test_h2_service_error() {
block_on(async { let mut srv = test_server(move || {
let openssl = ssl_acceptor().unwrap(); HttpService::build()
.h2(|_| err::<Response, Error>(ErrorBadRequest("error")))
.openssl(ssl_acceptor())
.map_err(|_| ())
});
let mut srv = TestServer::start(move || { let response = srv.sget("/").send().await.unwrap();
pipeline_factory( assert_eq!(response.status(), StatusCode::BAD_REQUEST);
openssl
.clone()
.map_err(|e| println!("Openssl error: {}", e)),
)
.and_then(
HttpService::build()
.h2(|_| err::<Response, Error>(ErrorBadRequest("error")))
.map_err(|_| ()),
)
});
let response = srv.sget("/").send().await.unwrap(); // read response
assert_eq!(response.status(), StatusCode::BAD_REQUEST); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(b"error"));
// read response
let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(b"error"));
})
} }
#[test] #[actix_rt::test]
fn test_h2_on_connect() { async fn test_h2_on_connect() {
block_on(async { let srv = test_server(move || {
let openssl = ssl_acceptor().unwrap(); HttpService::build()
.on_connect(|_| 10usize)
.h2(|req: Request| {
assert!(req.extensions().contains::<usize>());
ok::<_, ()>(Response::Ok().finish())
})
.openssl(ssl_acceptor())
.map_err(|_| ())
});
let srv = TestServer::start(move || { let response = srv.sget("/").send().await.unwrap();
pipeline_factory( assert!(response.status().is_success());
openssl
.clone()
.map_err(|e| println!("Openssl error: {}", e)),
)
.and_then(
HttpService::build()
.on_connect(|_| 10usize)
.h2(|req: Request| {
assert!(req.extensions().contains::<usize>());
ok::<_, ()>(Response::Ok().finish())
})
.map_err(|_| ()),
)
});
let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success());
})
} }

View File

@@ -1,13 +1,10 @@
#![cfg(feature = "rustls")] #![cfg(feature = "rustls")]
use actix_codec::{AsyncRead, AsyncWrite};
use actix_http::error::PayloadError; use actix_http::error::PayloadError;
use actix_http::http::header::{self, HeaderName, HeaderValue}; use actix_http::http::header::{self, HeaderName, HeaderValue};
use actix_http::http::{Method, StatusCode, Version}; use actix_http::http::{Method, StatusCode, Version};
use actix_http::{body, error, Error, HttpService, Request, Response}; use actix_http::{body, error, Error, HttpService, Request, Response};
use actix_http_test::{block_on, TestServer}; use actix_http_test::test_server;
use actix_server::ssl::RustlsAcceptor; use actix_service::{fn_factory_with_config, fn_service};
use actix_server_config::ServerConfig;
use actix_service::{factory_fn_cfg, pipeline_factory, service_fn2, ServiceFactory};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::future::{self, err, ok}; use futures::future::{self, err, ok};
@@ -31,7 +28,7 @@ where
Ok(body) Ok(body)
} }
fn ssl_acceptor<T: AsyncRead + AsyncWrite>() -> io::Result<RustlsAcceptor<T, ()>> { fn ssl_acceptor() -> RustlsServerConfig {
// load ssl keys // load ssl keys
let mut config = RustlsServerConfig::new(NoClientAuth::new()); let mut config = RustlsServerConfig::new(NoClientAuth::new());
let cert_file = &mut BufReader::new(File::open("../tests/cert.pem").unwrap()); let cert_file = &mut BufReader::new(File::open("../tests/cert.pem").unwrap());
@@ -39,149 +36,144 @@ fn ssl_acceptor<T: AsyncRead + AsyncWrite>() -> io::Result<RustlsAcceptor<T, ()>
let cert_chain = certs(cert_file).unwrap(); let cert_chain = certs(cert_file).unwrap();
let mut keys = pkcs8_private_keys(key_file).unwrap(); let mut keys = pkcs8_private_keys(key_file).unwrap();
config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
config
let protos = vec![b"h2".to_vec()];
config.set_protocols(&protos);
Ok(RustlsAcceptor::new(config))
} }
#[test] #[actix_rt::test]
fn test_h2() -> io::Result<()> { async fn test_h1() -> io::Result<()> {
block_on(async { let srv = test_server(move || {
let rustls = ssl_acceptor()?; HttpService::build()
let srv = TestServer::start(move || { .h1(|_| future::ok::<_, Error>(Response::Ok().finish()))
pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) .rustls(ssl_acceptor())
.and_then( });
HttpService::build()
.h2(|_| future::ok::<_, Error>(Response::Ok().finish()))
.map_err(|_| ()),
)
});
let response = srv.sget("/").send().await.unwrap(); let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
Ok(()) Ok(())
})
} }
#[test] #[actix_rt::test]
fn test_h2_1() -> io::Result<()> { async fn test_h2() -> io::Result<()> {
block_on(async { let srv = test_server(move || {
let rustls = ssl_acceptor()?; HttpService::build()
let srv = TestServer::start(move || { .h2(|_| future::ok::<_, Error>(Response::Ok().finish()))
pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) .rustls(ssl_acceptor())
.and_then( });
HttpService::build()
.finish(|req: Request| {
assert!(req.peer_addr().is_some());
assert_eq!(req.version(), Version::HTTP_2);
future::ok::<_, Error>(Response::Ok().finish())
})
.map_err(|_| ()),
)
});
let response = srv.sget("/").send().await.unwrap(); let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
Ok(()) Ok(())
})
} }
#[test] #[actix_rt::test]
fn test_h2_body1() -> io::Result<()> { async fn test_h1_1() -> io::Result<()> {
block_on(async { let srv = test_server(move || {
let data = "HELLOWORLD".to_owned().repeat(64 * 1024); HttpService::build()
let rustls = ssl_acceptor()?; .h1(|req: Request| {
let mut srv = TestServer::start(move || { assert!(req.peer_addr().is_some());
pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) assert_eq!(req.version(), Version::HTTP_11);
.and_then( future::ok::<_, Error>(Response::Ok().finish())
HttpService::build() })
.h2(|mut req: Request<_>| { .rustls(ssl_acceptor())
async move { });
let body = load_body(req.take_payload()).await?;
Ok::<_, Error>(Response::Ok().body(body))
}
})
.map_err(|_| ()),
)
});
let response = srv.sget("/").send_body(data.clone()).await.unwrap(); let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
Ok(())
let body = srv.load_body(response).await.unwrap();
assert_eq!(&body, data.as_bytes());
Ok(())
})
} }
#[test] #[actix_rt::test]
fn test_h2_content_length() { async fn test_h2_1() -> io::Result<()> {
block_on(async { let srv = test_server(move || {
let rustls = ssl_acceptor().unwrap(); HttpService::build()
.finish(|req: Request| {
assert!(req.peer_addr().is_some());
assert_eq!(req.version(), Version::HTTP_2);
future::ok::<_, Error>(Response::Ok().finish())
})
.rustls(ssl_acceptor())
});
let srv = TestServer::start(move || { let response = srv.sget("/").send().await.unwrap();
pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) assert!(response.status().is_success());
.and_then( Ok(())
HttpService::build() }
.h2(|req: Request| {
let indx: usize = req.uri().path()[1..].parse().unwrap();
let statuses = [
StatusCode::NO_CONTENT,
StatusCode::CONTINUE,
StatusCode::SWITCHING_PROTOCOLS,
StatusCode::PROCESSING,
StatusCode::OK,
StatusCode::NOT_FOUND,
];
future::ok::<_, ()>(Response::new(statuses[indx]))
})
.map_err(|_| ()),
)
});
let header = HeaderName::from_static("content-length"); #[actix_rt::test]
let value = HeaderValue::from_static("0"); async fn test_h2_body1() -> io::Result<()> {
let data = "HELLOWORLD".to_owned().repeat(64 * 1024);
let mut srv = test_server(move || {
HttpService::build()
.h2(|mut req: Request<_>| {
async move {
let body = load_body(req.take_payload()).await?;
Ok::<_, Error>(Response::Ok().body(body))
}
})
.rustls(ssl_acceptor())
});
{ let response = srv.sget("/").send_body(data.clone()).await.unwrap();
for i in 0..4 { assert!(response.status().is_success());
let req = srv
.request(Method::GET, srv.surl(&format!("/{}", i)))
.send();
let response = req.await.unwrap();
assert_eq!(response.headers().get(&header), None);
let req = srv let body = srv.load_body(response).await.unwrap();
.request(Method::HEAD, srv.surl(&format!("/{}", i))) assert_eq!(&body, data.as_bytes());
.send(); Ok(())
let response = req.await.unwrap(); }
assert_eq!(response.headers().get(&header), None);
}
for i in 4..6 { #[actix_rt::test]
let req = srv async fn test_h2_content_length() {
.request(Method::GET, srv.surl(&format!("/{}", i))) let srv = test_server(move || {
.send(); HttpService::build()
let response = req.await.unwrap(); .h2(|req: Request| {
assert_eq!(response.headers().get(&header), Some(&value)); let indx: usize = req.uri().path()[1..].parse().unwrap();
} let statuses = [
StatusCode::NO_CONTENT,
StatusCode::CONTINUE,
StatusCode::SWITCHING_PROTOCOLS,
StatusCode::PROCESSING,
StatusCode::OK,
StatusCode::NOT_FOUND,
];
future::ok::<_, ()>(Response::new(statuses[indx]))
})
.rustls(ssl_acceptor())
});
let header = HeaderName::from_static("content-length");
let value = HeaderValue::from_static("0");
{
for i in 0..4 {
let req = srv
.request(Method::GET, srv.surl(&format!("/{}", i)))
.send();
let response = req.await.unwrap();
assert_eq!(response.headers().get(&header), None);
let req = srv
.request(Method::HEAD, srv.surl(&format!("/{}", i)))
.send();
let response = req.await.unwrap();
assert_eq!(response.headers().get(&header), None);
} }
})
for i in 4..6 {
let req = srv
.request(Method::GET, srv.surl(&format!("/{}", i)))
.send();
let response = req.await.unwrap();
assert_eq!(response.headers().get(&header), Some(&value));
}
}
} }
#[test] #[actix_rt::test]
fn test_h2_headers() { async fn test_h2_headers() {
block_on(async { let data = STR.repeat(10);
let data = STR.repeat(10); let data2 = data.clone();
let data2 = data.clone();
let rustls = ssl_acceptor().unwrap();
let mut srv = TestServer::start(move || { let mut srv = test_server(move || {
let data = data.clone(); let data = data.clone();
pipeline_factory(rustls
.clone()
.map_err(|e| println!("Rustls error: {}", e)))
.and_then(
HttpService::build().h2(move |_| { HttpService::build().h2(move |_| {
let mut config = Response::Ok(); let mut config = Response::Ok();
for idx in 0..90 { for idx in 0..90 {
@@ -203,16 +195,16 @@ fn test_h2_headers() {
); );
} }
future::ok::<_, ()>(config.body(data.clone())) future::ok::<_, ()>(config.body(data.clone()))
}).map_err(|_| ())) })
}); .rustls(ssl_acceptor())
});
let response = srv.sget("/").send().await.unwrap(); let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from(data2)); assert_eq!(bytes, Bytes::from(data2));
})
} }
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
@@ -237,238 +229,193 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World"; Hello World Hello World Hello World Hello World Hello World";
#[test] #[actix_rt::test]
fn test_h2_body2() { async fn test_h2_body2() {
block_on(async { let mut srv = test_server(move || {
let rustls = ssl_acceptor().unwrap(); HttpService::build()
let mut srv = TestServer::start(move || { .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR)))
pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) .rustls(ssl_acceptor())
.and_then( });
HttpService::build()
.h2(|_| future::ok::<_, ()>(Response::Ok().body(STR)))
.map_err(|_| ()),
)
});
let response = srv.sget("/").send().await.unwrap(); let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
})
} }
#[test] #[actix_rt::test]
fn test_h2_head_empty() { async fn test_h2_head_empty() {
block_on(async { let mut srv = test_server(move || {
let rustls = ssl_acceptor().unwrap(); HttpService::build()
let mut srv = TestServer::start(move || { .finish(|_| ok::<_, ()>(Response::Ok().body(STR)))
pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) .rustls(ssl_acceptor())
.and_then( });
HttpService::build()
.finish(|_| ok::<_, ()>(Response::Ok().body(STR)))
.map_err(|_| ()),
)
});
let response = srv.shead("/").send().await.unwrap(); let response = srv.shead("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
assert_eq!(response.version(), Version::HTTP_2); assert_eq!(response.version(), Version::HTTP_2);
{ {
let len = response let len = response
.headers() .headers()
.get(http::header::CONTENT_LENGTH) .get(http::header::CONTENT_LENGTH)
.unwrap(); .unwrap();
assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap());
} }
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert!(bytes.is_empty()); assert!(bytes.is_empty());
})
} }
#[test] #[actix_rt::test]
fn test_h2_head_binary() { async fn test_h2_head_binary() {
block_on(async { let mut srv = test_server(move || {
let rustls = ssl_acceptor().unwrap(); HttpService::build()
let mut srv = TestServer::start(move || { .h2(|_| {
pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR))
.and_then( })
HttpService::build() .rustls(ssl_acceptor())
.h2(|_| { });
ok::<_, ()>(
Response::Ok()
.content_length(STR.len() as u64)
.body(STR),
)
})
.map_err(|_| ()),
)
});
let response = srv.shead("/").send().await.unwrap(); let response = srv.shead("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
{ {
let len = response let len = response
.headers() .headers()
.get(http::header::CONTENT_LENGTH) .get(http::header::CONTENT_LENGTH)
.unwrap(); .unwrap();
assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap());
} }
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert!(bytes.is_empty()); assert!(bytes.is_empty());
})
} }
#[test] #[actix_rt::test]
fn test_h2_head_binary2() { async fn test_h2_head_binary2() {
block_on(async { let srv = test_server(move || {
let rustls = ssl_acceptor().unwrap(); HttpService::build()
let srv = TestServer::start(move || { .h2(|_| ok::<_, ()>(Response::Ok().body(STR)))
pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) .rustls(ssl_acceptor())
.and_then( });
HttpService::build()
.h2(|_| ok::<_, ()>(Response::Ok().body(STR)))
.map_err(|_| ()),
)
});
let response = srv.shead("/").send().await.unwrap(); let response = srv.shead("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
{ {
let len = response let len = response
.headers() .headers()
.get(http::header::CONTENT_LENGTH) .get(http::header::CONTENT_LENGTH)
.unwrap(); .unwrap();
assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap());
} }
})
} }
#[test] #[actix_rt::test]
fn test_h2_body_length() { async fn test_h2_body_length() {
block_on(async { let mut srv = test_server(move || {
let rustls = ssl_acceptor().unwrap(); HttpService::build()
let mut srv = TestServer::start(move || { .h2(|_| {
pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) let body = once(ok(Bytes::from_static(STR.as_ref())));
.and_then( ok::<_, ()>(
HttpService::build() Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)),
.h2(|_| {
let body = once(ok(Bytes::from_static(STR.as_ref())));
ok::<_, ()>(
Response::Ok().body(body::SizedStream::new(
STR.len() as u64,
body,
)),
)
})
.map_err(|_| ()),
) )
}); })
.rustls(ssl_acceptor())
});
let response = srv.sget("/").send().await.unwrap(); let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
})
} }
#[test] #[actix_rt::test]
fn test_h2_body_chunked_explicit() { async fn test_h2_body_chunked_explicit() {
block_on(async { let mut srv = test_server(move || {
let rustls = ssl_acceptor().unwrap(); HttpService::build()
let mut srv = TestServer::start(move || { .h2(|_| {
pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
.and_then( ok::<_, ()>(
HttpService::build() Response::Ok()
.h2(|_| { .header(header::TRANSFER_ENCODING, "chunked")
let body = .streaming(body),
once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
ok::<_, ()>(
Response::Ok()
.header(header::TRANSFER_ENCODING, "chunked")
.streaming(body),
)
})
.map_err(|_| ()),
) )
}); })
.rustls(ssl_acceptor())
});
let response = srv.sget("/").send().await.unwrap(); let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); assert!(!response.headers().contains_key(header::TRANSFER_ENCODING));
// read response // read response
let bytes = srv.load_body(response).await.unwrap(); let bytes = srv.load_body(response).await.unwrap();
// decode // decode
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
})
} }
#[test] #[actix_rt::test]
fn test_h2_response_http_error_handling() { async fn test_h2_response_http_error_handling() {
block_on(async { let mut srv = test_server(move || {
let rustls = ssl_acceptor().unwrap(); HttpService::build()
.h2(fn_factory_with_config(|_: ()| {
ok::<_, ()>(fn_service(|_| {
let broken_header = Bytes::from_static(b"\0\0\0");
ok::<_, ()>(
Response::Ok()
.header(http::header::CONTENT_TYPE, broken_header)
.body(STR),
)
}))
}))
.rustls(ssl_acceptor())
});
let mut srv = TestServer::start(move || { let response = srv.sget("/").send().await.unwrap();
pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
.and_then(
HttpService::build()
.h2(factory_fn_cfg(|_: &ServerConfig| {
ok::<_, ()>(service_fn2(|_| {
let broken_header = Bytes::from_static(b"\0\0\0");
ok::<_, ()>(
Response::Ok()
.header(
http::header::CONTENT_TYPE,
broken_header,
)
.body(STR),
)
}))
}))
.map_err(|_| ()),
)
});
let response = srv.sget("/").send().await.unwrap(); // read response
assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(b"failed to parse header value"));
// read response
let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(b"failed to parse header value"));
})
} }
#[test] #[actix_rt::test]
fn test_h2_service_error() { async fn test_h2_service_error() {
block_on(async { let mut srv = test_server(move || {
let rustls = ssl_acceptor().unwrap(); HttpService::build()
.h2(|_| err::<Response, Error>(error::ErrorBadRequest("error")))
.rustls(ssl_acceptor())
});
let mut srv = TestServer::start(move || { let response = srv.sget("/").send().await.unwrap();
pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
.and_then(
HttpService::build()
.h2(|_| err::<Response, Error>(error::ErrorBadRequest("error")))
.map_err(|_| ()),
)
});
let response = srv.sget("/").send().await.unwrap(); // read response
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(b"error"));
// read response }
let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(b"error")); #[actix_rt::test]
}) async fn test_h1_service_error() {
let mut srv = test_server(move || {
HttpService::build()
.h1(|_| err::<Response, Error>(error::ErrorBadRequest("error")))
.rustls(ssl_acceptor())
});
let response = srv.sget("/").send().await.unwrap();
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
// read response
let bytes = srv.load_body(response).await.unwrap();
assert_eq!(bytes, Bytes::from_static(b"error"));
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,86 +1,194 @@
use std::cell::Cell;
use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::{body, h1, ws, Error, HttpService, Request, Response}; use actix_http::{body, h1, ws, Error, HttpService, Request, Response};
use actix_http_test::{block_on, TestServer}; use actix_http_test::test_server;
use actix_utils::framed::FramedTransport; use actix_service::{fn_factory, Service};
use bytes::{Bytes, BytesMut}; use actix_utils::framed::Dispatcher;
use bytes::Bytes;
use futures::future; use futures::future;
use futures::{SinkExt, StreamExt}; use futures::task::{Context, Poll};
use futures::{Future, SinkExt, StreamExt};
async fn ws_service<T: AsyncRead + AsyncWrite + Unpin>( struct WsService<T>(Arc<Mutex<(PhantomData<T>, Cell<bool>)>>);
(req, mut framed): (Request, Framed<T, h1::Codec>),
) -> Result<(), Error> {
let res = ws::handshake(req.head()).unwrap().message_body(());
framed impl<T> WsService<T> {
.send((res, body::BodySize::None).into()) fn new() -> Self {
.await WsService(Arc::new(Mutex::new((PhantomData, Cell::new(false)))))
.unwrap(); }
FramedTransport::new(framed.into_framed(ws::Codec::new()), service) fn set_polled(&mut self) {
.await *self.0.lock().unwrap().1.get_mut() = true;
.map_err(|_| panic!()) }
fn was_polled(&self) -> bool {
self.0.lock().unwrap().1.get()
}
}
impl<T> Clone for WsService<T> {
fn clone(&self) -> Self {
WsService(self.0.clone())
}
}
impl<T> Service for WsService<T>
where
T: AsyncRead + AsyncWrite + Unpin + 'static,
{
type Request = (Request, Framed<T, h1::Codec>);
type Response = ();
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<(), Error>>>>;
fn poll_ready(&mut self, _ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.set_polled();
Poll::Ready(Ok(()))
}
fn call(&mut self, (req, mut framed): Self::Request) -> Self::Future {
let fut = async move {
let res = ws::handshake(req.head()).unwrap().message_body(());
framed
.send((res, body::BodySize::None).into())
.await
.unwrap();
Dispatcher::new(framed.into_framed(ws::Codec::new()), service)
.await
.map_err(|_| panic!())
};
Box::pin(fut)
}
} }
async fn service(msg: ws::Frame) -> Result<ws::Message, Error> { async fn service(msg: ws::Frame) -> Result<ws::Message, Error> {
let msg = match msg { let msg = match msg {
ws::Frame::Ping(msg) => ws::Message::Pong(msg), ws::Frame::Ping(msg) => ws::Message::Pong(msg),
ws::Frame::Text(text) => { ws::Frame::Text(text) => {
ws::Message::Text(String::from_utf8_lossy(&text.unwrap()).to_string()) ws::Message::Text(String::from_utf8_lossy(&text).to_string())
} }
ws::Frame::Binary(bin) => ws::Message::Binary(bin.unwrap().freeze()), ws::Frame::Binary(bin) => ws::Message::Binary(bin),
ws::Frame::Continuation(item) => ws::Message::Continuation(item),
ws::Frame::Close(reason) => ws::Message::Close(reason), ws::Frame::Close(reason) => ws::Message::Close(reason),
_ => panic!(), _ => panic!(),
}; };
Ok(msg) Ok(msg)
} }
#[test] #[actix_rt::test]
fn test_simple() { async fn test_simple() {
block_on(async { let ws_service = WsService::new();
let mut srv = TestServer::start(|| { let mut srv = test_server({
let ws_service = ws_service.clone();
move || {
let ws_service = ws_service.clone();
HttpService::build() HttpService::build()
.upgrade(actix_service::service_fn(ws_service)) .upgrade(fn_factory(move || future::ok::<_, ()>(ws_service.clone())))
.finish(|_| future::ok::<_, ()>(Response::NotFound())) .finish(|_| future::ok::<_, ()>(Response::NotFound()))
}); .tcp()
}
});
// client service // client service
let mut framed = srv.ws().await.unwrap(); let mut framed = srv.ws().await.unwrap();
framed framed
.send(ws::Message::Text("text".to_string())) .send(ws::Message::Text("text".to_string()))
.await .await
.unwrap(); .unwrap();
let (item, mut framed) = framed.into_future().await; let (item, mut framed) = framed.into_future().await;
assert_eq!( assert_eq!(
item.unwrap().unwrap(), item.unwrap().unwrap(),
ws::Frame::Text(Some(BytesMut::from("text"))) ws::Frame::Text(Bytes::from_static(b"text"))
); );
framed framed
.send(ws::Message::Binary("text".into())) .send(ws::Message::Binary("text".into()))
.await .await
.unwrap(); .unwrap();
let (item, mut framed) = framed.into_future().await; let (item, mut framed) = framed.into_future().await;
assert_eq!( assert_eq!(
item.unwrap().unwrap(), item.unwrap().unwrap(),
ws::Frame::Binary(Some(Bytes::from_static(b"text").into())) ws::Frame::Binary(Bytes::from_static(&b"text"[..]))
); );
framed.send(ws::Message::Ping("text".into())).await.unwrap(); framed.send(ws::Message::Ping("text".into())).await.unwrap();
let (item, mut framed) = framed.into_future().await; let (item, mut framed) = framed.into_future().await;
assert_eq!( assert_eq!(
item.unwrap().unwrap(), item.unwrap().unwrap(),
ws::Frame::Pong("text".to_string().into()) ws::Frame::Pong("text".to_string().into())
); );
framed framed
.send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) .send(ws::Message::Continuation(ws::Item::FirstText(
.await "text".into(),
.unwrap(); )))
.await
.unwrap();
let (item, mut framed) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Continuation(ws::Item::FirstText(Bytes::from_static(b"text")))
);
let (item, _framed) = framed.into_future().await; assert!(framed
assert_eq!( .send(ws::Message::Continuation(ws::Item::FirstText(
item.unwrap().unwrap(), "text".into()
ws::Frame::Close(Some(ws::CloseCode::Normal.into())) )))
); .await
}) .is_err());
assert!(framed
.send(ws::Message::Continuation(ws::Item::FirstBinary(
"text".into()
)))
.await
.is_err());
framed
.send(ws::Message::Continuation(ws::Item::Continue("text".into())))
.await
.unwrap();
let (item, mut framed) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Continuation(ws::Item::Continue(Bytes::from_static(b"text")))
);
framed
.send(ws::Message::Continuation(ws::Item::Last("text".into())))
.await
.unwrap();
let (item, mut framed) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Continuation(ws::Item::Last(Bytes::from_static(b"text")))
);
assert!(framed
.send(ws::Message::Continuation(ws::Item::Continue("text".into())))
.await
.is_err());
assert!(framed
.send(ws::Message::Continuation(ws::Item::Last("text".into())))
.await
.is_err());
framed
.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
.await
.unwrap();
let (item, _framed) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Close(Some(ws::CloseCode::Normal.into()))
);
assert!(ws_service.was_polled());
} }

View File

@@ -1,5 +1,9 @@
# Changes # Changes
## [0.2.0] - 2019-12-20
* Use actix-web 2.0
## [0.1.0] - 2019-06-xx ## [0.1.0] - 2019-06-xx
* Move identity middleware to separate crate * Move identity middleware to separate crate

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-identity" name = "actix-identity"
version = "0.2.0-alpha.1" version = "0.2.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Identity service for actix web framework." description = "Identity service for actix web framework."
readme = "README.md" readme = "README.md"
@@ -17,14 +17,14 @@ name = "actix_identity"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = { version = "2.0.0-alpha.1", default-features = false, features = ["secure-cookies"] } actix-web = { version = "2.0.0-rc", default-features = false, features = ["secure-cookies"] }
actix-service = "1.0.0-alpha.1" actix-service = "1.0.1"
futures = "0.3.1" futures = "0.3.1"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
time = "0.1.42" time = "0.1.42"
[dev-dependencies] [dev-dependencies]
actix-rt = "1.0.0-alpha.1" actix-rt = "1.0.0"
actix-http = "0.3.0-alpha.1" actix-http = "1.0.1"
bytes = "0.4" bytes = "0.5.3"

View File

@@ -294,7 +294,7 @@ where
Err(err) => Ok(req.error_response(err)), Err(err) => Ok(req.error_response(err)),
} }
} }
.boxed_local() .boxed_local()
} }
} }
@@ -607,144 +607,130 @@ mod tests {
use super::*; use super::*;
use actix_web::http::StatusCode; use actix_web::http::StatusCode;
use actix_web::test::{self, block_on, TestRequest}; use actix_web::test::{self, TestRequest};
use actix_web::{web, App, Error, HttpResponse}; use actix_web::{web, App, Error, HttpResponse};
const COOKIE_KEY_MASTER: [u8; 32] = [0; 32]; const COOKIE_KEY_MASTER: [u8; 32] = [0; 32];
const COOKIE_NAME: &'static str = "actix_auth"; const COOKIE_NAME: &'static str = "actix_auth";
const COOKIE_LOGIN: &'static str = "test"; const COOKIE_LOGIN: &'static str = "test";
#[test] #[actix_rt::test]
fn test_identity() { async fn test_identity() {
block_on(async { let mut srv = test::init_service(
let mut srv = test::init_service( App::new()
App::new() .wrap(IdentityService::new(
.wrap(IdentityService::new( CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) .domain("www.rust-lang.org")
.domain("www.rust-lang.org") .name(COOKIE_NAME)
.name(COOKIE_NAME) .path("/")
.path("/") .secure(true),
.secure(true), ))
)) .service(web::resource("/index").to(|id: Identity| {
.service(web::resource("/index").to(|id: Identity| { if id.identity().is_some() {
if id.identity().is_some() { HttpResponse::Created()
HttpResponse::Created() } else {
} else {
HttpResponse::Ok()
}
}))
.service(web::resource("/login").to(|id: Identity| {
id.remember(COOKIE_LOGIN.to_string());
HttpResponse::Ok() HttpResponse::Ok()
})) }
.service(web::resource("/logout").to(|id: Identity| { }))
if id.identity().is_some() { .service(web::resource("/login").to(|id: Identity| {
id.forget(); id.remember(COOKIE_LOGIN.to_string());
HttpResponse::Ok() HttpResponse::Ok()
} else { }))
HttpResponse::BadRequest() .service(web::resource("/logout").to(|id: Identity| {
} if id.identity().is_some() {
})), id.forget();
) HttpResponse::Ok()
.await; } else {
let resp = test::call_service( HttpResponse::BadRequest()
&mut srv, }
TestRequest::with_uri("/index").to_request(), })),
) )
.await; .await;
assert_eq!(resp.status(), StatusCode::OK); let resp =
test::call_service(&mut srv, TestRequest::with_uri("/index").to_request())
.await;
assert_eq!(resp.status(), StatusCode::OK);
let resp = test::call_service( let resp =
&mut srv, test::call_service(&mut srv, TestRequest::with_uri("/login").to_request())
TestRequest::with_uri("/login").to_request(), .await;
) assert_eq!(resp.status(), StatusCode::OK);
.await; let c = resp.response().cookies().next().unwrap().to_owned();
assert_eq!(resp.status(), StatusCode::OK);
let c = resp.response().cookies().next().unwrap().to_owned();
let resp = test::call_service( let resp = test::call_service(
&mut srv, &mut srv,
TestRequest::with_uri("/index") TestRequest::with_uri("/index")
.cookie(c.clone()) .cookie(c.clone())
.to_request(), .to_request(),
) )
.await; .await;
assert_eq!(resp.status(), StatusCode::CREATED); assert_eq!(resp.status(), StatusCode::CREATED);
let resp = test::call_service( let resp = test::call_service(
&mut srv, &mut srv,
TestRequest::with_uri("/logout") TestRequest::with_uri("/logout")
.cookie(c.clone()) .cookie(c.clone())
.to_request(), .to_request(),
) )
.await; .await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert!(resp.headers().contains_key(header::SET_COOKIE)) assert!(resp.headers().contains_key(header::SET_COOKIE))
})
} }
#[test] #[actix_rt::test]
fn test_identity_max_age_time() { async fn test_identity_max_age_time() {
block_on(async { let duration = Duration::days(1);
let duration = Duration::days(1); let mut srv = test::init_service(
let mut srv = test::init_service( App::new()
App::new() .wrap(IdentityService::new(
.wrap(IdentityService::new( CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) .domain("www.rust-lang.org")
.domain("www.rust-lang.org") .name(COOKIE_NAME)
.name(COOKIE_NAME) .path("/")
.path("/") .max_age_time(duration)
.max_age_time(duration) .secure(true),
.secure(true), ))
)) .service(web::resource("/login").to(|id: Identity| {
.service(web::resource("/login").to(|id: Identity| { id.remember("test".to_string());
id.remember("test".to_string()); HttpResponse::Ok()
HttpResponse::Ok() })),
})), )
) .await;
.await; let resp =
let resp = test::call_service( test::call_service(&mut srv, TestRequest::with_uri("/login").to_request())
&mut srv, .await;
TestRequest::with_uri("/login").to_request(), assert_eq!(resp.status(), StatusCode::OK);
) assert!(resp.headers().contains_key(header::SET_COOKIE));
.await; let c = resp.response().cookies().next().unwrap().to_owned();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(duration, c.max_age().unwrap());
assert!(resp.headers().contains_key(header::SET_COOKIE));
let c = resp.response().cookies().next().unwrap().to_owned();
assert_eq!(duration, c.max_age().unwrap());
})
} }
#[test] #[actix_rt::test]
fn test_identity_max_age() { async fn test_identity_max_age() {
block_on(async { let seconds = 60;
let seconds = 60; let mut srv = test::init_service(
let mut srv = test::init_service( App::new()
App::new() .wrap(IdentityService::new(
.wrap(IdentityService::new( CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) .domain("www.rust-lang.org")
.domain("www.rust-lang.org") .name(COOKIE_NAME)
.name(COOKIE_NAME) .path("/")
.path("/") .max_age(seconds)
.max_age(seconds) .secure(true),
.secure(true), ))
)) .service(web::resource("/login").to(|id: Identity| {
.service(web::resource("/login").to(|id: Identity| { id.remember("test".to_string());
id.remember("test".to_string()); HttpResponse::Ok()
HttpResponse::Ok() })),
})), )
) .await;
.await; let resp =
let resp = test::call_service( test::call_service(&mut srv, TestRequest::with_uri("/login").to_request())
&mut srv, .await;
TestRequest::with_uri("/login").to_request(), assert_eq!(resp.status(), StatusCode::OK);
) assert!(resp.headers().contains_key(header::SET_COOKIE));
.await; let c = resp.response().cookies().next().unwrap().to_owned();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap());
assert!(resp.headers().contains_key(header::SET_COOKIE));
let c = resp.response().cookies().next().unwrap().to_owned();
assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap());
})
} }
async fn create_identity_server< async fn create_identity_server<
@@ -885,223 +871,202 @@ mod tests {
assert!(cookies.get(COOKIE_NAME).is_none()); assert!(cookies.get(COOKIE_NAME).is_none());
} }
#[test] #[actix_rt::test]
fn test_identity_legacy_cookie_is_set() { async fn test_identity_legacy_cookie_is_set() {
block_on(async { let mut srv = create_identity_server(|c| c).await;
let mut srv = create_identity_server(|c| c).await; let mut resp =
let mut resp = test::call_service(&mut srv, TestRequest::with_uri("/").to_request()).await;
test::call_service(&mut srv, TestRequest::with_uri("/").to_request()) assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN);
.await; assert_logged_in(resp, None).await;
assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN);
assert_logged_in(resp, None).await;
})
} }
#[test] #[actix_rt::test]
fn test_identity_legacy_cookie_works() { async fn test_identity_legacy_cookie_works() {
block_on(async { let mut srv = create_identity_server(|c| c).await;
let mut srv = create_identity_server(|c| c).await; let cookie = legacy_login_cookie(COOKIE_LOGIN);
let cookie = legacy_login_cookie(COOKIE_LOGIN); let mut resp = test::call_service(
let mut resp = test::call_service( &mut srv,
&mut srv, TestRequest::with_uri("/")
TestRequest::with_uri("/") .cookie(cookie.clone())
.cookie(cookie.clone()) .to_request(),
.to_request(), )
) .await;
.await; assert_no_login_cookie(&mut resp);
assert_no_login_cookie(&mut resp); assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
})
} }
#[test] #[actix_rt::test]
fn test_identity_legacy_cookie_rejected_if_visit_timestamp_needed() { async fn test_identity_legacy_cookie_rejected_if_visit_timestamp_needed() {
block_on(async { let mut srv =
let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
create_identity_server(|c| c.visit_deadline(Duration::days(90))).await; let cookie = legacy_login_cookie(COOKIE_LOGIN);
let cookie = legacy_login_cookie(COOKIE_LOGIN); let mut resp = test::call_service(
let mut resp = test::call_service( &mut srv,
&mut srv, TestRequest::with_uri("/")
TestRequest::with_uri("/") .cookie(cookie.clone())
.cookie(cookie.clone()) .to_request(),
.to_request(), )
) .await;
.await; assert_login_cookie(
assert_login_cookie( &mut resp,
&mut resp, COOKIE_LOGIN,
COOKIE_LOGIN, LoginTimestampCheck::NoTimestamp,
LoginTimestampCheck::NoTimestamp, VisitTimeStampCheck::NewTimestamp,
VisitTimeStampCheck::NewTimestamp, );
); assert_logged_in(resp, None).await;
assert_logged_in(resp, None).await;
})
} }
#[test] #[actix_rt::test]
fn test_identity_legacy_cookie_rejected_if_login_timestamp_needed() { async fn test_identity_legacy_cookie_rejected_if_login_timestamp_needed() {
block_on(async { let mut srv =
let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
create_identity_server(|c| c.login_deadline(Duration::days(90))).await; let cookie = legacy_login_cookie(COOKIE_LOGIN);
let cookie = legacy_login_cookie(COOKIE_LOGIN); let mut resp = test::call_service(
let mut resp = test::call_service( &mut srv,
&mut srv, TestRequest::with_uri("/")
TestRequest::with_uri("/") .cookie(cookie.clone())
.cookie(cookie.clone()) .to_request(),
.to_request(), )
) .await;
.await; assert_login_cookie(
assert_login_cookie( &mut resp,
&mut resp, COOKIE_LOGIN,
COOKIE_LOGIN, LoginTimestampCheck::NewTimestamp,
LoginTimestampCheck::NewTimestamp, VisitTimeStampCheck::NoTimestamp,
VisitTimeStampCheck::NoTimestamp, );
); assert_logged_in(resp, None).await;
assert_logged_in(resp, None).await;
})
} }
#[test] #[actix_rt::test]
fn test_identity_cookie_rejected_if_login_timestamp_needed() { async fn test_identity_cookie_rejected_if_login_timestamp_needed() {
block_on(async { let mut srv =
let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
create_identity_server(|c| c.login_deadline(Duration::days(90))).await; let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now()));
let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now())); let mut resp = test::call_service(
let mut resp = test::call_service( &mut srv,
&mut srv, TestRequest::with_uri("/")
TestRequest::with_uri("/") .cookie(cookie.clone())
.cookie(cookie.clone()) .to_request(),
.to_request(), )
) .await;
.await; assert_login_cookie(
assert_login_cookie( &mut resp,
&mut resp, COOKIE_LOGIN,
COOKIE_LOGIN, LoginTimestampCheck::NewTimestamp,
LoginTimestampCheck::NewTimestamp, VisitTimeStampCheck::NoTimestamp,
VisitTimeStampCheck::NoTimestamp, );
); assert_logged_in(resp, None).await;
assert_logged_in(resp, None).await;
})
} }
#[test] #[actix_rt::test]
fn test_identity_cookie_rejected_if_visit_timestamp_needed() { async fn test_identity_cookie_rejected_if_visit_timestamp_needed() {
block_on(async { let mut srv =
let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
create_identity_server(|c| c.visit_deadline(Duration::days(90))).await; let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None);
let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None); let mut resp = test::call_service(
let mut resp = test::call_service( &mut srv,
&mut srv, TestRequest::with_uri("/")
TestRequest::with_uri("/") .cookie(cookie.clone())
.cookie(cookie.clone()) .to_request(),
.to_request(), )
) .await;
.await; assert_login_cookie(
assert_login_cookie( &mut resp,
&mut resp, COOKIE_LOGIN,
COOKIE_LOGIN, LoginTimestampCheck::NoTimestamp,
LoginTimestampCheck::NoTimestamp, VisitTimeStampCheck::NewTimestamp,
VisitTimeStampCheck::NewTimestamp, );
); assert_logged_in(resp, None).await;
assert_logged_in(resp, None).await;
})
} }
#[test] #[actix_rt::test]
fn test_identity_cookie_rejected_if_login_timestamp_too_old() { async fn test_identity_cookie_rejected_if_login_timestamp_too_old() {
block_on(async { let mut srv =
let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
create_identity_server(|c| c.login_deadline(Duration::days(90))).await; let cookie = login_cookie(
let cookie = login_cookie( COOKIE_LOGIN,
COOKIE_LOGIN, Some(SystemTime::now() - Duration::days(180).to_std().unwrap()),
Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), None,
None, );
); let mut resp = test::call_service(
let mut resp = test::call_service( &mut srv,
&mut srv, TestRequest::with_uri("/")
TestRequest::with_uri("/") .cookie(cookie.clone())
.cookie(cookie.clone()) .to_request(),
.to_request(), )
) .await;
.await; assert_login_cookie(
assert_login_cookie( &mut resp,
&mut resp, COOKIE_LOGIN,
COOKIE_LOGIN, LoginTimestampCheck::NewTimestamp,
LoginTimestampCheck::NewTimestamp, VisitTimeStampCheck::NoTimestamp,
VisitTimeStampCheck::NoTimestamp, );
); assert_logged_in(resp, None).await;
assert_logged_in(resp, None).await;
})
} }
#[test] #[actix_rt::test]
fn test_identity_cookie_rejected_if_visit_timestamp_too_old() { async fn test_identity_cookie_rejected_if_visit_timestamp_too_old() {
block_on(async { let mut srv =
let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
create_identity_server(|c| c.visit_deadline(Duration::days(90))).await; let cookie = login_cookie(
let cookie = login_cookie( COOKIE_LOGIN,
COOKIE_LOGIN, None,
None, Some(SystemTime::now() - Duration::days(180).to_std().unwrap()),
Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), );
); let mut resp = test::call_service(
let mut resp = test::call_service( &mut srv,
&mut srv, TestRequest::with_uri("/")
TestRequest::with_uri("/") .cookie(cookie.clone())
.cookie(cookie.clone()) .to_request(),
.to_request(), )
) .await;
.await; assert_login_cookie(
assert_login_cookie( &mut resp,
&mut resp, COOKIE_LOGIN,
COOKIE_LOGIN, LoginTimestampCheck::NoTimestamp,
LoginTimestampCheck::NoTimestamp, VisitTimeStampCheck::NewTimestamp,
VisitTimeStampCheck::NewTimestamp, );
); assert_logged_in(resp, None).await;
assert_logged_in(resp, None).await;
})
} }
#[test] #[actix_rt::test]
fn test_identity_cookie_not_updated_on_login_deadline() { async fn test_identity_cookie_not_updated_on_login_deadline() {
block_on(async { let mut srv =
let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
create_identity_server(|c| c.login_deadline(Duration::days(90))).await; let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None);
let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None); let mut resp = test::call_service(
let mut resp = test::call_service( &mut srv,
&mut srv, TestRequest::with_uri("/")
TestRequest::with_uri("/") .cookie(cookie.clone())
.cookie(cookie.clone()) .to_request(),
.to_request(), )
) .await;
.await; assert_no_login_cookie(&mut resp);
assert_no_login_cookie(&mut resp); assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
})
} }
#[test] #[actix_rt::test]
fn test_identity_cookie_updated_on_visit_deadline() { async fn test_identity_cookie_updated_on_visit_deadline() {
block_on(async { let mut srv = create_identity_server(|c| {
let mut srv = create_identity_server(|c| { c.visit_deadline(Duration::days(90))
c.visit_deadline(Duration::days(90)) .login_deadline(Duration::days(90))
.login_deadline(Duration::days(90))
})
.await;
let timestamp = SystemTime::now() - Duration::days(1).to_std().unwrap();
let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp));
let mut resp = test::call_service(
&mut srv,
TestRequest::with_uri("/")
.cookie(cookie.clone())
.to_request(),
)
.await;
assert_login_cookie(
&mut resp,
COOKIE_LOGIN,
LoginTimestampCheck::OldTimestamp(timestamp),
VisitTimeStampCheck::NewTimestamp,
);
assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
}) })
.await;
let timestamp = SystemTime::now() - Duration::days(1).to_std().unwrap();
let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp));
let mut resp = test::call_service(
&mut srv,
TestRequest::with_uri("/")
.cookie(cookie.clone())
.to_request(),
)
.await;
assert_login_cookie(
&mut resp,
COOKIE_LOGIN,
LoginTimestampCheck::OldTimestamp(timestamp),
VisitTimeStampCheck::NewTimestamp,
);
assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
} }
} }

View File

@@ -1,5 +1,17 @@
# Changes # Changes
## [0.2.0] - 2019-12-20
* Release
## [0.2.0-alpha.4] - 2019-12-xx
* Multipart handling now handles Pending during read of boundary #1205
## [0.2.0-alpha.2] - 2019-12-03
* Migrate to `std::future`
## [0.1.4] - 2019-09-12 ## [0.1.4] - 2019-09-12
* Multipart handling now parses requests which do not end in CRLF #1038 * Multipart handling now parses requests which do not end in CRLF #1038

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