1
0
mirror of https://github.com/fafhrd91/actix-net synced 2025-08-14 05:20:31 +02:00

Compare commits

...

104 Commits

Author SHA1 Message Date
Rob Ede
a09f9abfcb prepare utils release 3.0.0-beta.1 2020-12-28 03:32:28 +00:00
Rob Ede
e4a44b77e6 prepare codec release 0.4.0-beta.1 2020-12-28 03:24:43 +00:00
fakeshadow
2ee8f45f5d update actix-codec and actix-utils to tokio 1.0 (#237) 2020-12-28 03:16:37 +00:00
Rob Ede
f48e3f4cb0 prepare release for rt and service 2020-12-28 01:58:31 +00:00
Rob Ede
3d3bd60368 fix rt override 2020-12-28 01:53:11 +00:00
Rob Ede
d684128831 fix rt override 2020-12-28 01:48:19 +00:00
fakeshadow
0c12930796 update to tokio 1.0 for actix-rt (#236) 2020-12-28 01:40:22 +00:00
Rob Ede
ba44ea7d0b remove futures-util from service deps (#235) 2020-12-27 18:24:57 +00:00
Rob Ede
8a58a341a4 service improvements (#233) 2020-12-27 14:15:42 +00:00
Rob Ede
33c9aa6988 bump msrv to 1.46 2020-12-27 04:36:08 +00:00
Rob Ede
3ab8c3eb69 service trait takes request type parameter (#232) 2020-12-27 04:28:00 +00:00
fakeshadow
518bf3f6a6 remove RUNNING Q PENDING thread locals from actix-rt (#207) 2020-12-26 23:26:02 +00:00
fakeshadow
43ce25cda1 Remove unused mods in actix-utils (#229) 2020-12-26 21:27:59 +00:00
Yuki Okushi
4e4122b702 Disable PR comment from codecov 2020-12-17 21:42:21 +09:00
Aravinth Manivannan
b296d0f254 Intradoc links conversion (#227)
* intra doc conversion

* rm trailing blank comment
2020-12-14 08:22:30 +00:00
Juan Aguilar
02a902068f Refactor LocalWaker (#224) 2020-12-13 19:26:57 +00:00
fakeshadow
049795662f remove ServerMessage type. remove one unused InternalServiceFactory impl (#225) 2020-12-13 00:46:32 +00:00
Rob Ede
4e43216b99 standardise compiler lints across all crates (#226) 2020-12-12 23:24:00 +00:00
Rob Ede
93889776c4 prevent double registration of sockets when backpressure is resolved (#223) 2020-12-12 17:19:20 +00:00
Yuki Okushi
ab496a71b5 Fix release date 2020-12-03 08:59:59 +09:00
Yuki Okushi
76d956e25c macros: Add actix-reexport feature (#218) 2020-12-03 08:59:13 +09:00
Ivan Babrou
89e56cf661 Notify about paused accept loop (#215) 2020-11-29 15:30:13 +00:00
Rob Ede
8aca8d4d07 fix clippy warnings (#214)
and make my spelling checker happy
2020-11-25 01:41:14 +00:00
fakeshadow
e0dd2a3d76 remove actix-threadpool re-export from actix-rt (#212) 2020-11-24 17:03:09 +00:00
Rob Ede
59e976aaca address clippy error (#213) 2020-11-24 16:35:47 +00:00
Zura Benashvili
4cc1c87724 docs(transform): remove extra generic parameter (#211) 2020-11-20 22:45:57 +00:00
Yuki Okushi
ca39917d2c Update CoC contact information 2020-10-31 12:08:06 +09:00
ghizzo01
704af672b9 Bump pin-project to 1.0 (#202) 2020-10-25 19:42:40 +09:00
Rob Ede
242bef269f delete ioframe removed package readme
closes #199
2020-09-22 12:29:07 +01:00
Rob Ede
6c65e2a79f prepare router 0.2.5 release (#198) 2020-09-21 22:46:59 +01:00
nujz
e5ca271764 actix-router: fix from_hex error (#196) 2020-09-20 18:04:18 +01:00
nujz
98a2197a09 fix doc error (#195) 2020-09-19 23:12:41 +09:00
Rob Ede
fb0aa02b3c move and update server+tls examples (#190) 2020-09-13 10:12:07 +01:00
Rob Ede
681eeb497d prepare server release 1.0.4 (#188) 2020-09-12 15:28:17 +01:00
Igor Aleksanov
3e04b87311 actix-service: Fix broken link in readme (#189) 2020-09-12 15:08:03 +01:00
Rob Ede
77b7826658 prepare tls v2 release (#186) 2020-09-08 18:00:07 +01:00
Igor Aleksanov
b7a9cb7bb4 actix-rt: Make the process of running System in existing Runtime more clear (#173) 2020-09-06 11:01:24 +01:00
Robert Gabriel Jakabosky
88d99ac89c Fix clippy errors. (#187) 2020-09-06 10:41:42 +01:00
Rob Ede
7632f51509 prepare connect v2 stable release (#185) 2020-09-02 22:14:07 +01:00
Rob Ede
d28687d0d7 promote codec/utils out of beta (#184) 2020-08-24 09:18:37 +01:00
Rob Ede
27c6be9881 remove unused type parameter from Framed::replace_codec (#183) 2020-08-20 00:30:26 +01:00
Rob Ede
119dc39f5b prepare codec and utils betas (#182) 2020-08-19 11:00:12 +01:00
Rob Ede
b3010c13e0 solve framed integration with actix-http (#179) 2020-08-18 23:27:37 +01:00
Adrian Wechner
fecdfcd8d4 assert workers greater than zero (#167) 2020-08-18 16:44:22 +01:00
Yuki Okushi
578a560853 connect,tls: Bump up to next alpha versions (#181) 2020-08-17 15:39:17 +01:00
Rob Ede
fb098536ee bump MSRV to 1.42 (#180) 2020-08-17 15:37:57 +01:00
Rob Ede
5d28be9ad6 fix actix-service readme reference (#176) 2020-08-11 12:20:09 +01:00
Rob Ede
a5a6b6704c prepare actix-service 1.0.6 release (#175) 2020-08-09 16:10:58 +01:00
Igor Aleksanov
afb0a3c9fc actix-service: Fix clippy warning in benches (#174) 2020-08-07 17:16:45 +09:00
Miloas
02aaa75591 fix actix-service doc error (#172) 2020-08-06 11:21:51 +01:00
Yuki Okushi
ed4b708c66 Fix CI on MSRV check (#171) 2020-08-05 09:02:41 +09:00
Yuki Okushi
235a76dcd4 GHA: Switch action to the official setup-msys2 (#169) 2020-07-29 08:47:32 +09:00
Matt Kantor
0c5f1da625 Remove garbled doc comment for actix_router::IntoPattern::is_single (#168) 2020-07-29 05:46:53 +09:00
Yuki Okushi
8ace9264b7 Check code style with rustfmt on CI (#164) 2020-07-22 12:32:13 +09:00
Yuki Okushi
0dca1a705a actix-utils: Remove unsound custom Cell as well (#161) 2020-07-22 01:14:32 +01:00
Juan Aguilar
5d6d309e66 Simplify bcodec decode (#162) 2020-07-20 23:09:24 +09:00
Juan Aguilar
8d0bd7ce1c Improve bcodec encode performance (#157) 2020-07-19 22:36:51 +01:00
Sergey "Shnatsel" Davidoff
a67e38b4a0 Remove unsound custom Cell (#158) 2020-07-20 06:05:36 +09:00
Rob Ede
334c98575a Upgrade tokio utils to 0.3 (#138) 2020-07-20 05:44:26 +09:00
Rob Ede
a9b5a7b070 Create PULL_REQUEST_TEMPLATE.md (#159) 2020-07-20 03:01:09 +09:00
Yuki Okushi
61176f6410 Update rustls-related dependencies (#154) 2020-07-14 11:14:06 +01:00
Yuki Okushi
10b4c30a06 Use OR instead of deprecated / in license field (#155) 2020-07-14 11:11:30 +01:00
Yuki Okushi
7f550bcf0f threadpool: Bump up to 0.3.3 (#156) 2020-07-14 11:10:15 +01:00
Yuki Okushi
887f11f787 Merge pull request #153 from actix/tweak-actions
Tweak actions trigger events
2020-07-08 09:04:05 +09:00
Yuki Okushi
e2a6d352b0 Tweak actions trigger events 2020-07-08 08:38:24 +09:00
Yuki Okushi
f6c697a2dd Merge pull request #152 from paolobarbolini/pl-011
Update parking_lot to 0.11
2020-07-04 03:20:08 +09:00
Paolo Barbolini
5ecdfd684a Update parking_lot to 0.11 2020-07-03 17:37:10 +02:00
Yuki Okushi
7140c04c44 Merge pull request #149 from taiki-e/pin-project
Remove uses of pin_project::project attribute
2020-06-07 02:01:08 +09:00
Taiki Endo
9528df4486 Remove uses of pin_project::project attribute
pin-project will deprecate the project attribute due to some unfixable
limitations.

Refs: https://github.com/taiki-e/pin-project/issues/225
2020-06-06 06:42:45 +09:00
Pen Tree
755a8bb9d1 fix codec doc links (#148) 2020-06-02 18:05:39 +01:00
Yuki Okushi
f3cb6efc30 Merge pull request #146 from actix/cache-v2
Update `actions/cache` to v2
2020-05-28 04:59:34 +09:00
Yuki Okushi
87b857705c Update actions/cache to v2 2020-05-28 03:14:01 +09:00
Yuki Okushi
c897c5d3eb Merge pull request #145 from JohnTitor/new-threalpool
threadpool: Bump up to 0.3.2
2020-05-20 15:24:39 +09:00
Yuki Okushi
134e76b8b4 threadpool: Bump up to 0.3.2 2020-05-20 14:19:16 +09:00
Yuki Okushi
f3a401c23b Merge pull request #144 from JohnTitor/codecov-config
Add codecov config
2020-05-20 11:03:31 +09:00
Yuki Okushi
f7e8a912b3 Add codecov config 2020-05-19 14:45:39 +09:00
Yuki Okushi
11a1e11858 Merge pull request #143 from JohnTitor/new-testing
testing: Bump up to 1.0.1
2020-05-19 14:37:54 +09:00
Yuki Okushi
d0b27ee7e6 testing: Bump up to 1.0.1 2020-05-19 14:08:08 +09:00
Yuki Okushi
2d2b0591a2 Merge pull request #142 from JohnTitor/new-server
server: Bump up to 1.0.3
2020-05-19 13:58:39 +09:00
Yuki Okushi
abbc5f715f server: Bump up to 1.0.3 2020-05-19 10:23:17 +09:00
Yuki Okushi
140a6c76e3 Merge pull request #141 from actix/fix-ci
Only check compilation on mingw CI
2020-05-19 09:39:03 +09:00
Yuki Okushi
2395b28c5e Only check compilation on mingw CI
Disabled to run tests since somehow linking with OpenSSL is broken.
2020-05-19 09:11:27 +09:00
Yuki Okushi
aad4812ba6 Merge pull request #140 from JohnTitor/replace-net2
Replace deprecated `net2` crate with `socket2`
2020-05-19 08:58:40 +09:00
Yuki Okushi
ac6c78c476 testing: Replace net2 crate with socket2 2020-05-19 08:21:40 +09:00
Yuki Okushi
8218a098e8 server: Replace net2 crate with socket2 2020-05-19 08:17:44 +09:00
Yuki Okushi
49a6f525be Merge pull request #139 from JohnTitor/next-macros
macros: Bump up to 0.1.2
2020-05-19 07:50:46 +09:00
Yuki Okushi
f59ff82395 macros: Bump up to 0.1.2 2020-05-18 15:36:23 +09:00
Yuki Okushi
f7cc62564d Merge pull request #136 from JohnTitor/connect-alpha-3
actix-connect: Bump up to 2.0.0-alpha.3
2020-05-08 01:36:16 +09:00
Yuki Okushi
b125e2bdce actix-connect: Bump up to 2.0.0-alpha.3 2020-05-08 01:07:57 +09:00
Yuki Okushi
a5c185e80e Merge pull request #135 from actix/fix/unresolverd
correct spelling of ConnectError::Unresolved
2020-05-06 14:45:30 +09:00
Rob Ede
523cee0351 correct spelling of ConnectError::Unresolved 2020-05-03 23:14:22 +01:00
Yuki Okushi
343b3c09fc Merge pull request #134 from JohnTitor/new-rt
Bump up `actix-rt` to 1.1.1
2020-04-30 14:34:17 +09:00
Yuki Okushi
8a10580663 Bump up actix-rt to 1.1.1 2020-04-30 03:07:12 +09:00
Yuki Okushi
1b4a117063 Merge pull request #128 from Jonathas-Conceicao/topic/fix_memory_leak
actix-rt: Spawn future to cleanup pending JoinHandles
2020-04-30 02:58:13 +09:00
Yuki Okushi
700997fe48 Merge pull request #133 from actix/macro-compile-testing
add macro compile tests
2020-04-29 15:33:00 +09:00
Rob Ede
4c5568ed70 add trybuild compile tests 2020-04-26 20:11:16 +01:00
Yuki Okushi
7d0cfe1b4d Merge pull request #131 from danpintara/pull-1
actix-macros: Simplify test macros by using original signature
2020-04-23 02:33:52 +09:00
Daniel Pintara
e35c261c9f actix-macros: test: Simplify by using #sig instead of #name(#inputs) #ret 2020-04-22 00:13:32 +07:00
Yuki Okushi
115ef3fcb3 Merge pull request #130 from JohnTitor/dont-clone
Remove unnecessary clone usage
2020-04-20 08:37:10 +09:00
Yuki Okushi
c0482e2532 Remove unnecessary clone usage 2020-04-20 08:02:08 +09:00
Jonathas-Conceicao
6906f25e01 actix-rt: Set threshold size for arbiter's pending futures list
Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-04-16 03:12:05 -03:00
Jonathas-Conceicao
06bca19524 actix-rt: Spawn future to cleanup pending JoinHandles
Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-04-09 20:36:44 -03:00
Yuki Okushi
e9e2185296 Merge pull request #127 from rubdos/test-fixture-integration
Forward actix_rt::test arguments to test function.
2020-04-09 17:45:17 +09:00
Ruben De Smet
aae52a80ab Forward actix_rt::test arguments to test function.
Previously,

```rust
async fn foo(_a: u32) {}
```

would compile to

```rust
fn foo() {/* something */}
```

This patches changes this behaviour to

```rust
fn foo(_a: u32) {/* something */}
```

by simply forwarding the input arguments.

This allows any test fixture library (e.g. `rstest`, cfr.
https://github.com/la10736/rstest/issues/85) to integrate with
actix::test.
2020-04-08 16:48:10 +02:00
137 changed files with 3244 additions and 6122 deletions

24
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,24 @@
## PR Type
<!-- What kind of change does this PR make? -->
<!-- Bug Fix / Feature / Refactor / Code Style / Other -->
INSERT_PR_TYPE
## PR Checklist
Check your PR fulfills the following:
<!-- For draft PRs check the boxes as you complete them. -->
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] A changelog entry has been made for the appropriate packages.
- [ ] Format code with the latest stable rustfmt
## Overview
<!-- Describe the current and new behavior. -->
<!-- Emphasize any breaking changes. -->
<!-- If this PR fixes or closes an issue, reference it here. -->
<!-- Closes #000 -->

View File

@@ -1,23 +0,0 @@
name: Benchmark (Linux)
on: [push, pull_request]
jobs:
check_benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true
- name: Check benchmark
uses: actions-rs/cargo@v1
with:
command: bench
args: --package=actix-service

34
.github/workflows/clippy-fmt.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
on:
pull_request:
types: [opened, synchronize, reopened]
name: Clippy and rustfmt Check
jobs:
clippy_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt
profile: minimal
override: true
- name: Check with rustfmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: clippy
profile: minimal
override: true
- name: Check with Clippy
uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace --tests

View File

@@ -1,18 +0,0 @@
on: pull_request
name: Clippy Check
jobs:
clippy_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: clippy
profile: minimal
override: true
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features --all --tests

View File

@@ -1,6 +1,12 @@
name: CI (Linux) name: CI (Linux)
on: [push, pull_request] on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs: jobs:
build_and_test: build_and_test:
@@ -8,7 +14,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
version: version:
- 1.39.0 - 1.46.0
- stable - stable
- nightly - nightly
@@ -16,7 +22,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@v2
- name: Install ${{ matrix.version }} - name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -29,18 +35,16 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: generate-lockfile command: generate-lockfile
- name: Cache cargo registry - name: Cache cargo dirs
uses: actions/cache@v1 uses: actions/cache@v2
with: with:
path: ~/.cargo/registry path:
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }} ~/.cargo/registry
- name: Cache cargo index ~/.cargo/git
uses: actions/cache@v1 ~/.cargo/bin
with: key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-trimmed-${{ hashFiles('**/Cargo.lock') }}
path: ~/.cargo/git
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build - name: Cache cargo build
uses: actions/cache@v1 uses: actions/cache@v2
with: with:
path: target path: target
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }} key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }}
@@ -49,20 +53,20 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: check command: check
args: --all --bins --examples --tests args: --workspace --bins --examples --tests
- name: tests - name: tests
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
timeout-minutes: 40 timeout-minutes: 40
with: with:
command: test command: test
args: --all --all-features --no-fail-fast -- --nocapture args: --workspace --exclude=actix-tls --no-fail-fast -- --nocapture
- name: Generate coverage file - name: Generate coverage file
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request') if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
run: | run: |
cargo install cargo-tarpaulin cargo install cargo-tarpaulin
cargo tarpaulin --out Xml --workspace --all-features cargo tarpaulin --out Xml --workspace
- name: Upload to Codecov - name: Upload to Codecov
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request') if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
@@ -72,5 +76,7 @@ jobs:
- name: Clear the cargo caches - name: Clear the cargo caches
run: | run: |
rustup update stable
rustup override set stable
cargo install cargo-cache --no-default-features --features ci-autoclean cargo install cargo-cache --no-default-features --features ci-autoclean
cargo-cache cargo-cache

View File

@@ -1,6 +1,12 @@
name: CI (macOS) name: CI (macOS)
on: [push, pull_request] on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs: jobs:
build_and_test: build_and_test:
@@ -15,7 +21,7 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@v2
- name: Install ${{ matrix.version }} - name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -28,10 +34,10 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: check command: check
args: --all --bins --examples --tests args: --workspace --bins --examples --tests
- name: tests - name: tests
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: test command: test
args: --all --all-features --no-fail-fast -- --nocapture args: --workspace --exclude=actix-tls --no-fail-fast -- --nocapture

View File

@@ -1,9 +1,12 @@
name: CI (Windows-mingw) name: CI (Windows-mingw)
on: [push, pull_request] on:
pull_request:
env: types: [opened, synchronize, reopened]
OPENSSL_DIR: d:\a\_temp\msys\msys64\usr push:
branches:
- master
- '1.0'
jobs: jobs:
build_and_test: build_and_test:
@@ -18,7 +21,7 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@v2
- name: Install ${{ matrix.version }} - name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -28,27 +31,15 @@ jobs:
override: true override: true
- name: Install MSYS2 - name: Install MSYS2
uses: numworks/setup-msys2@v1 uses: msys2/setup-msys2@v2
- name: Install OpenSSL - name: Install packages
run: | run: |
msys2do pacman --noconfirm -S openssl-devel pkg-config msys2 -c 'pacman -Sy --noconfirm pacman'
msys2 -c 'pacman --noconfirm -S base-devel pkg-config'
- name: Copy and check libs
run: |
Copy-Item d:\a\_temp\msys\msys64\usr\lib\libssl.dll.a d:\a\_temp\msys\msys64\usr\lib\libssl.dll
Copy-Item d:\a\_temp\msys\msys64\usr\lib\libcrypto.dll.a d:\a\_temp\msys\msys64\usr\lib\libcrypto.dll
Get-ChildItem d:\a\_temp\msys\msys64\usr\lib
Get-ChildItem d:\a\_temp\msys\msys64\usr
- name: check build - name: check build
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: check command: check
args: --all --bins --examples --tests args: --workspace --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture

View File

@@ -1,6 +1,12 @@
name: CI (Windows) name: CI (Windows)
on: [push, pull_request] on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
env: env:
VCPKGRS_DYNAMIC: 1 VCPKGRS_DYNAMIC: 1
@@ -21,7 +27,7 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@v2
- name: Install ${{ matrix.version }} - name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -54,10 +60,10 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: check command: check
args: --all --bins --examples --tests args: --workspace --bins --examples --tests
- name: tests - name: tests
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: test command: test
args: --all --all-features --no-fail-fast -- --nocapture args: --workspace --exclude=actix-tls --no-fail-fast -- --nocapture

View File

@@ -34,10 +34,13 @@ This Code of Conduct applies both within project spaces and in public spaces whe
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at robjtede@icloud.com ([@robjtede]) or huyuumi@neet.club ([@JohnTitor]). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
[@robjtede]: https://github.com/robjtede
[@JohnTitor]: https://github.com/JohnTitor
## Attribution ## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]

View File

@@ -2,7 +2,6 @@
members = [ members = [
"actix-codec", "actix-codec",
"actix-connect", "actix-connect",
"actix-ioframe",
"actix-rt", "actix-rt",
"actix-macros", "actix-macros",
"actix-service", "actix-service",
@@ -17,17 +16,16 @@ members = [
] ]
[patch.crates-io] [patch.crates-io]
actix-codec = { path = "actix-codec" } actix-codec = { git = "https://github.com/actix/actix-net.git", rev = "ba44ea7d0bafaf5fccb9a34003d503e1910943ee" }
actix-connect = { path = "actix-connect" } actix-connect = { path = "actix-connect" }
actix-ioframe = { path = "actix-ioframe" } actix-rt = { git = "https://github.com/actix/actix-net.git", rev = "ba44ea7d0bafaf5fccb9a34003d503e1910943ee" }
actix-rt = { path = "actix-rt" }
actix-macros = { path = "actix-macros" } actix-macros = { path = "actix-macros" }
actix-server = { path = "actix-server" } actix-server = { path = "actix-server" }
actix-service = { path = "actix-service" } actix-service = { git = "https://github.com/actix/actix-net.git", rev = "ba44ea7d0bafaf5fccb9a34003d503e1910943ee" }
actix-testing = { path = "actix-testing" } actix-testing = { path = "actix-testing" }
actix-threadpool = { path = "actix-threadpool" } actix-threadpool = { path = "actix-threadpool" }
actix-tls = { path = "actix-tls" } actix-tls = { path = "actix-tls" }
actix-tracing = { path = "actix-tracing" } actix-tracing = { path = "actix-tracing" }
actix-utils = { path = "actix-utils" } actix-utils = { git = "https://github.com/actix/actix-net.git", rev = "ba44ea7d0bafaf5fccb9a34003d503e1910943ee" }
actix-router = { path = "router" } actix-router = { path = "router" }
bytestring = { path = "string" } bytestring = { path = "string" }

View File

@@ -13,8 +13,8 @@ Actix net - framework for composable network services
## Documentation & community resources ## Documentation & community resources
* [Chat on gitter](https://gitter.im/actix/actix) * [Chat on Gitter](https://gitter.im/actix/actix)
* Minimum supported Rust version: 1.39 or later * Minimum supported Rust version: 1.46 or later
## Example ## Example

View File

@@ -1,33 +1,59 @@
# Changes # Changes
* Use `.advance()` intead of `.split_to()` ## Unreleased - 2020-xx-xx
## [0.2.0] - 2019-12-10
## 0.4.0-beta.1 - 2020-12-28
* Replace `pin-project` with `pin-project-lite`. [#237]
* Upgrade `tokio` dependency to `1`. [#237]
* Upgrade `tokio-util` dependency to `0.6`. [#237]
* Upgrade `bytes` dependency to `1`. [#237]
[#237]: https://github.com/actix/actix-net/pull/237
## 0.3.0 - 2020-08-23
* No changes from beta 2.
## 0.3.0-beta.2 - 2020-08-19
* Remove unused type parameter from `Framed::replace_codec`.
## 0.3.0-beta.1 - 2020-08-19
* Use `.advance()` instead of `.split_to()`.
* Upgrade `tokio-util` to `0.3`.
* Improve `BytesCodec` `.encode()` performance
* Simplify `BytesCodec` `.decode()`
* Rename methods on `Framed` to better describe their use.
* Add method on `Framed` to get a pinned reference to the underlying I/O.
* Add method on `Framed` check emptiness of read buffer.
## 0.2.0 - 2019-12-10
* Use specific futures dependencies * Use specific futures dependencies
## [0.2.0-alpha.4]
* Fix buffer remaining capacity calcualtion ## 0.2.0-alpha.4
* Fix buffer remaining capacity calculation
## [0.2.0-alpha.3]
## 0.2.0-alpha.3
* Use tokio 0.2 * Use tokio 0.2
* Fix low/high watermark for write/read buffers * Fix low/high watermark for write/read buffers
## [0.2.0-alpha.2]
## 0.2.0-alpha.2
* Migrated to `std::future` * Migrated to `std::future`
## [0.1.2] - 2019-03-27
## 0.1.2 - 2019-03-27
* Added `Framed::map_io()` method. * Added `Framed::map_io()` method.
## [0.1.1] - 2019-03-06
## 0.1.1 - 2019-03-06
* Added `FramedParts::with_read_buffer()` method. * Added `FramedParts::with_read_buffer()` method.
## [0.1.0] - 2018-12-09
## 0.1.0 - 2018-12-09
* Move codec to separate crate * Move codec to separate crate

View File

@@ -1,16 +1,15 @@
[package] [package]
name = "actix-codec" name = "actix-codec"
version = "0.2.0" version = "0.4.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Utilities for encoding and decoding frames" description = "Codec utilities for working with framed protocols"
keywords = ["network", "framework", "async", "futures"] keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-codec/" documentation = "https://docs.rs/actix-codec/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
workspace = ".."
[lib] [lib]
name = "actix_codec" name = "actix_codec"
@@ -18,10 +17,10 @@ path = "src/lib.rs"
[dependencies] [dependencies]
bitflags = "1.2.1" bitflags = "1.2.1"
bytes = "0.5.2" bytes = "1"
futures-core = { version = "0.3.4", default-features = false } futures-core = { version = "0.3.7", default-features = false }
futures-sink = { version = "0.3.4", default-features = false } futures-sink = { version = "0.3.7", default-features = false }
tokio = { version = "0.2.4", default-features=false }
tokio-util = { version = "0.2.0", default-features=false, features=["codec"] }
log = "0.4" log = "0.4"
pin-project = "0.4.8" pin-project-lite = "0.2"
tokio = "1"
tokio-util = { version = "0.6", features = ["codec", "io"] }

View File

@@ -1,4 +1,4 @@
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{Buf, Bytes, BytesMut};
use std::io; use std::io;
use super::{Decoder, Encoder}; use super::{Decoder, Encoder};
@@ -9,13 +9,12 @@ use super::{Decoder, Encoder};
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct BytesCodec; pub struct BytesCodec;
impl Encoder for BytesCodec { impl Encoder<Bytes> for BytesCodec {
type Item = Bytes;
type Error = io::Error; type Error = io::Error;
#[inline]
fn encode(&mut self, item: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> { fn encode(&mut self, item: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> {
dst.reserve(item.len()); dst.extend_from_slice(item.chunk());
dst.put(item);
Ok(()) Ok(())
} }
} }
@@ -28,8 +27,7 @@ impl Decoder for BytesCodec {
if src.is_empty() { if src.is_empty() {
Ok(None) Ok(None)
} else { } else {
let len = src.len(); Ok(Some(src.split()))
Ok(Some(src.split_to(len)))
} }
} }
} }

View File

@@ -5,11 +5,12 @@ use std::{fmt, io};
use bytes::{Buf, BytesMut}; use bytes::{Buf, BytesMut};
use futures_core::{ready, Stream}; use futures_core::{ready, Stream};
use futures_sink::Sink; use futures_sink::Sink;
use pin_project::pin_project;
use crate::{AsyncRead, AsyncWrite, Decoder, Encoder}; use crate::{AsyncRead, AsyncWrite, Decoder, Encoder};
/// Low-water mark
const LW: usize = 1024; const LW: usize = 1024;
/// High-water mark
const HW: usize = 8 * 1024; const HW: usize = 8 * 1024;
bitflags::bitflags! { bitflags::bitflags! {
@@ -19,32 +20,30 @@ bitflags::bitflags! {
} }
} }
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using pin_project_lite::pin_project! {
/// the `Encoder` and `Decoder` traits to encode and decode frames. /// A unified `Stream` and `Sink` interface to an underlying I/O object, using
#[pin_project] /// the `Encoder` and `Decoder` traits to encode and decode frames.
pub struct Framed<T, U> { ///
#[pin] /// Raw I/O objects work with byte sequences, but higher-level code usually
io: T, /// wants to batch these into meaningful chunks, called "frames". This
codec: U, /// method layers framing on top of an I/O object, by using the `Encoder`/`Decoder`
flags: Flags, /// traits to handle encoding and decoding of message frames. Note that
read_buf: BytesMut, /// the incoming and outgoing frame types may be distinct.
write_buf: BytesMut, pub struct Framed<T, U> {
#[pin]
io: T,
codec: U,
flags: Flags,
read_buf: BytesMut,
write_buf: BytesMut,
}
} }
impl<T, U> Framed<T, U> impl<T, U> Framed<T, U>
where where
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite,
U: Decoder + Encoder, U: Decoder,
{ {
/// Provides a `Stream` and `Sink` interface for reading and writing to this
/// `Io` object, using `Decode` and `Encode` to read and write the raw data.
///
/// Raw I/O objects work with byte sequences, but higher-level code usually
/// wants to batch these into meaningful chunks, called "frames". This
/// method layers framing on top of an I/O object, by using the `Codec`
/// traits to handle encoding and decoding of messages frames. Note that
/// the incoming and outgoing frame types may be distinct.
///
/// This function returns a *single* object that is both `Stream` and /// This function returns a *single* object that is both `Stream` and
/// `Sink`; grouping this into a single object is often useful for layering /// `Sink`; grouping this into a single object is often useful for layering
/// things like gzip or TLS, which require both read and write access to the /// things like gzip or TLS, which require both read and write access to the
@@ -61,40 +60,13 @@ where
} }
impl<T, U> Framed<T, U> { impl<T, U> Framed<T, U> {
/// Provides a `Stream` and `Sink` interface for reading and writing to this
/// `Io` object, using `Decode` and `Encode` to read and write the raw data.
///
/// Raw I/O objects work with byte sequences, but higher-level code usually
/// wants to batch these into meaningful chunks, called "frames". This
/// method layers framing on top of an I/O object, by using the `Codec`
/// traits to handle encoding and decoding of messages frames. Note that
/// the incoming and outgoing frame types may be distinct.
///
/// This function returns a *single* object that is both `Stream` and
/// `Sink`; grouping this into a single object is often useful for layering
/// things like gzip or TLS, which require both read and write access to the
/// underlying object.
///
/// This objects takes a stream and a readbuffer and a writebuffer. These
/// field can be obtained from an existing `Framed` with the
/// `into_parts` method.
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
Framed {
io: parts.io,
codec: parts.codec,
flags: parts.flags,
write_buf: parts.write_buf,
read_buf: parts.read_buf,
}
}
/// Returns a reference to the underlying codec. /// Returns a reference to the underlying codec.
pub fn get_codec(&self) -> &U { pub fn codec_ref(&self) -> &U {
&self.codec &self.codec
} }
/// Returns a mutable reference to the underlying codec. /// Returns a mutable reference to the underlying codec.
pub fn get_codec_mut(&mut self) -> &mut U { pub fn codec_mut(&mut self) -> &mut U {
&mut self.codec &mut self.codec
} }
@@ -104,20 +76,29 @@ impl<T, U> Framed<T, U> {
/// Note that care should be taken to not tamper with the underlying stream /// Note that care should be taken to not tamper with the underlying stream
/// of data coming in as it may corrupt the stream of frames otherwise /// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with. /// being worked with.
pub fn get_ref(&self) -> &T { pub fn io_ref(&self) -> &T {
&self.io &self.io
} }
/// Returns a mutable reference to the underlying I/O stream wrapped by /// Returns a mutable reference to the underlying I/O stream.
/// `Frame`.
/// ///
/// Note that care should be taken to not tamper with the underlying stream /// Note that care should be taken to not tamper with the underlying stream
/// of data coming in as it may corrupt the stream of frames otherwise /// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with. /// being worked with.
pub fn get_mut(&mut self) -> &mut T { pub fn io_mut(&mut self) -> &mut T {
&mut self.io &mut self.io
} }
/// Returns a `Pin` of a mutable reference to the underlying I/O stream.
pub fn io_pin(self: Pin<&mut Self>) -> Pin<&mut T> {
self.project().io
}
/// Check if read buffer is empty.
pub fn is_read_buf_empty(&self) -> bool {
self.read_buf.is_empty()
}
/// Check if write buffer is empty. /// Check if write buffer is empty.
pub fn is_write_buf_empty(&self) -> bool { pub fn is_write_buf_empty(&self) -> bool {
self.write_buf.is_empty() self.write_buf.is_empty()
@@ -128,8 +109,15 @@ impl<T, U> Framed<T, U> {
self.write_buf.len() >= HW self.write_buf.len() >= HW
} }
/// Check if framed is able to write more data.
///
/// `Framed` object considers ready if there is free space in write buffer.
pub fn is_write_ready(&self) -> bool {
self.write_buf.len() < HW
}
/// Consume the `Frame`, returning `Frame` with different codec. /// Consume the `Frame`, returning `Frame` with different codec.
pub fn into_framed<U2>(self, codec: U2) -> Framed<T, U2> { pub fn replace_codec<U2>(self, codec: U2) -> Framed<T, U2> {
Framed { Framed {
codec, codec,
io: self.io, io: self.io,
@@ -140,7 +128,7 @@ impl<T, U> Framed<T, U> {
} }
/// Consume the `Frame`, returning `Frame` with different io. /// Consume the `Frame`, returning `Frame` with different io.
pub fn map_io<F, T2>(self, f: F) -> Framed<T2, U> pub fn into_map_io<F, T2>(self, f: F) -> Framed<T2, U>
where where
F: Fn(T) -> T2, F: Fn(T) -> T2,
{ {
@@ -154,7 +142,7 @@ impl<T, U> Framed<T, U> {
} }
/// Consume the `Frame`, returning `Frame` with different codec. /// Consume the `Frame`, returning `Frame` with different codec.
pub fn map_codec<F, U2>(self, f: F) -> Framed<T, U2> pub fn into_map_codec<F, U2>(self, f: F) -> Framed<T, U2>
where where
F: Fn(U) -> U2, F: Fn(U) -> U2,
{ {
@@ -166,30 +154,14 @@ impl<T, U> Framed<T, U> {
write_buf: self.write_buf, write_buf: self.write_buf,
} }
} }
/// Consumes the `Frame`, returning its underlying I/O stream, the buffer
/// with unprocessed data, and the codec.
///
/// Note that care should be taken to not tamper with the underlying stream
/// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with.
pub fn into_parts(self) -> FramedParts<T, U> {
FramedParts {
io: self.io,
codec: self.codec,
flags: self.flags,
read_buf: self.read_buf,
write_buf: self.write_buf,
}
}
} }
impl<T, U> Framed<T, U> { impl<T, U> Framed<T, U> {
/// Serialize item and Write to the inner buffer /// Serialize item and Write to the inner buffer
pub fn write(mut self: Pin<&mut Self>, item: <U as Encoder>::Item) -> Result<(), <U as Encoder>::Error> pub fn write<I>(mut self: Pin<&mut Self>, item: I) -> Result<(), <U as Encoder<I>>::Error>
where where
T: AsyncWrite, T: AsyncWrite,
U: Encoder, U: Encoder<I>,
{ {
let this = self.as_mut().project(); let this = self.as_mut().project();
let remaining = this.write_buf.capacity() - this.write_buf.len(); let remaining = this.write_buf.capacity() - this.write_buf.len();
@@ -201,15 +173,11 @@ impl<T, U> Framed<T, U> {
Ok(()) Ok(())
} }
/// Check if framed is able to write more data.
///
/// `Framed` object considers ready if there is free space in write buffer.
pub fn is_write_ready(&self) -> bool {
self.write_buf.len() < HW
}
/// Try to read underlying I/O stream and decode item. /// Try to read underlying I/O stream and decode item.
pub fn next_item(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Result<U::Item, U::Error>>> pub fn next_item(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<<U as Decoder>::Item, U::Error>>>
where where
T: AsyncRead, T: AsyncRead,
U: Decoder, U: Decoder,
@@ -252,7 +220,8 @@ impl<T, U> Framed<T, U> {
if remaining < LW { if remaining < LW {
this.read_buf.reserve(HW - remaining) this.read_buf.reserve(HW - remaining)
} }
let cnt = match this.io.poll_read_buf(cx, &mut this.read_buf) {
let cnt = match tokio_util::io::poll_read_buf(this.io, cx, this.read_buf) {
Poll::Pending => return Poll::Pending, Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))), Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))),
Poll::Ready(Ok(cnt)) => cnt, Poll::Ready(Ok(cnt)) => cnt,
@@ -266,10 +235,13 @@ impl<T, U> Framed<T, U> {
} }
/// Flush write buffer to underlying I/O stream. /// Flush write buffer to underlying I/O stream.
pub fn flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), U::Error>> pub fn flush<I>(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), U::Error>>
where where
T: AsyncWrite, T: AsyncWrite,
U: Encoder, U: Encoder<I>,
{ {
let mut this = self.as_mut().project(); let mut this = self.as_mut().project();
log::trace!("flushing framed transport"); log::trace!("flushing framed transport");
@@ -277,9 +249,7 @@ impl<T, U> Framed<T, U> {
while !this.write_buf.is_empty() { while !this.write_buf.is_empty() {
log::trace!("writing; remaining={}", this.write_buf.len()); log::trace!("writing; remaining={}", this.write_buf.len());
let n = ready!( let n = ready!(this.io.as_mut().poll_write(cx, this.write_buf))?;
this.io.as_mut().poll_write(cx, this.write_buf)
)?;
if n == 0 { if n == 0 {
return Poll::Ready(Err(io::Error::new( return Poll::Ready(Err(io::Error::new(
@@ -301,10 +271,13 @@ impl<T, U> Framed<T, U> {
} }
/// Flush write buffer and shutdown underlying I/O stream. /// Flush write buffer and shutdown underlying I/O stream.
pub fn close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), U::Error>> pub fn close<I>(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), U::Error>>
where where
T: AsyncWrite, T: AsyncWrite,
U: Encoder, U: Encoder<I>,
{ {
let mut this = self.as_mut().project(); let mut this = self.as_mut().project();
ready!(this.io.as_mut().poll_flush(cx))?; ready!(this.io.as_mut().poll_flush(cx))?;
@@ -325,10 +298,10 @@ where
} }
} }
impl<T, U> Sink<U::Item> for Framed<T, U> impl<T, U, I> Sink<I> for Framed<T, U>
where where
T: AsyncWrite, T: AsyncWrite,
U: Encoder, U: Encoder<I>,
U::Error: From<io::Error>, U::Error: From<io::Error>,
{ {
type Error = U::Error; type Error = U::Error;
@@ -341,24 +314,15 @@ where
} }
} }
fn start_send( fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
self: Pin<&mut Self>,
item: <U as Encoder>::Item,
) -> Result<(), Self::Error> {
self.write(item) self.write(item)
} }
fn poll_flush( fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
self.flush(cx) self.flush(cx)
} }
fn poll_close( fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
self.close(cx) self.close(cx)
} }
} }
@@ -376,6 +340,41 @@ where
} }
} }
impl<T, U> Framed<T, U> {
/// This function returns a *single* object that is both `Stream` and
/// `Sink`; grouping this into a single object is often useful for layering
/// things like gzip or TLS, which require both read and write access to the
/// underlying object.
///
/// These objects take a stream, a read buffer and a write buffer. These
/// fields can be obtained from an existing `Framed` with the `into_parts` method.
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
Framed {
io: parts.io,
codec: parts.codec,
flags: parts.flags,
write_buf: parts.write_buf,
read_buf: parts.read_buf,
}
}
/// Consumes the `Frame`, returning its underlying I/O stream, the buffer
/// with unprocessed data, and the codec.
///
/// Note that care should be taken to not tamper with the underlying stream
/// of data coming in as it may corrupt the stream of frames otherwise
/// being worked with.
pub fn into_parts(self) -> FramedParts<T, U> {
FramedParts {
io: self.io,
codec: self.codec,
flags: self.flags,
read_buf: self.read_buf,
write_buf: self.write_buf,
}
}
}
/// `FramedParts` contains an export of the data of a Framed transport. /// `FramedParts` contains an export of the data of a Framed transport.
/// It can be used to construct a new `Framed` with a different codec. /// It can be used to construct a new `Framed` with a different codec.
/// It contains all current buffers and the inner transport. /// It contains all current buffers and the inner transport.

View File

@@ -1,12 +1,16 @@
//! Utilities for encoding and decoding frames. //! Codec utilities for working with framed protocols.
//! //!
//! Contains adapters to go from streams of bytes, [`AsyncRead`] and //! Contains adapters to go from streams of bytes, [`AsyncRead`] and
//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`]. //! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`].
//! Framed streams are also known as [transports]. //! Framed streams are also known as `transports`.
//! //!
//! [`AsyncRead`]: # //! [`Sink`]: futures_sink::Sink
//! [`AsyncWrite`]: # //! [`Stream`]: futures_core::Stream
#![deny(rust_2018_idioms, warnings)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(missing_docs)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
mod bcodec; mod bcodec;
mod framed; mod framed;
@@ -14,5 +18,6 @@ mod framed;
pub use self::bcodec::BytesCodec; pub use self::bcodec::BytesCodec;
pub use self::framed::{Framed, FramedParts}; pub use self::framed::{Framed, FramedParts};
pub use tokio::io::{AsyncRead, AsyncWrite}; pub use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
pub use tokio_util::codec::{Decoder, Encoder}; pub use tokio_util::codec::{Decoder, Encoder};
pub use tokio_util::io::poll_read_buf;

View File

@@ -1,5 +1,25 @@
# Changes # Changes
## Unreleased - 2020-xx-xx
## 2.0.0 - 2020-09-02
- No significant changes from `2.0.0-alpha.4`.
## 2.0.0-alpha.4 - 2020-08-17
### Changed
* Update `rustls` dependency to 0.18
* Update `tokio-rustls` dependency to 0.14
## [2.0.0-alpha.3] - 2020-05-08
### Fixed
* Corrected spelling of `ConnectError::Unresolverd` to `ConnectError::Unresolved`
## [2.0.0-alpha.2] - 2020-03-08 ## [2.0.0-alpha.2] - 2020-03-08
### Changed ### Changed

View File

@@ -1,14 +1,14 @@
[package] [package]
name = "actix-connect" name = "actix-connect"
version = "2.0.0-alpha.2" version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix connect - tcp connector service" description = "TCP connector service for Actix ecosystem."
keywords = ["network", "framework", "async", "futures"] keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-connect/" documentation = "https://docs.rs/actix-connect/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[package.metadata.docs.rs] [package.metadata.docs.rs]
@@ -31,10 +31,11 @@ rustls = ["rust-tls", "tokio-rustls", "webpki"]
uri = ["http"] uri = ["http"]
[dependencies] [dependencies]
actix-service = "1.0.3" actix-service = "1.0.6"
actix-codec = "0.2.0" actix-codec = "0.3.0"
actix-utils = "1.0.6" actix-utils = "2.0.0"
actix-rt = "1.0.0" actix-rt = "1.1.1"
derive_more = "0.99.2" derive_more = "0.99.2"
either = "1.5.3" either = "1.5.3"
futures-util = { version = "0.3.4", default-features = false } futures-util = { version = "0.3.4", default-features = false }
@@ -44,14 +45,14 @@ trust-dns-proto = { version = "0.19", default-features = false, features = ["tok
trust-dns-resolver = { version = "0.19", default-features = false, features = ["tokio-runtime", "system-config"] } trust-dns-resolver = { version = "0.19", default-features = false, features = ["tokio-runtime", "system-config"] }
# openssl # openssl
open-ssl = { version="0.10", package = "openssl", optional = true } open-ssl = { package = "openssl", version = "0.10", optional = true }
tokio-openssl = { version = "0.4.0", optional = true } tokio-openssl = { version = "0.4.0", optional = true }
# rustls # rustls
rust-tls = { version = "0.17.0", package = "rustls", optional = true } rust-tls = { package = "rustls", version = "0.18.0", optional = true }
tokio-rustls = { version = "0.13.0", optional = true } tokio-rustls = { version = "0.14.0", optional = true }
webpki = { version = "0.21", optional = true } webpki = { version = "0.21", optional = true }
[dev-dependencies] [dev-dependencies]
bytes = "0.5.3" bytes = "0.5.3"
actix-testing = { version="1.0.0" } actix-testing = "1.0.0"

View File

@@ -43,7 +43,7 @@ pub struct Connect<T> {
} }
impl<T: Address> Connect<T> { impl<T: Address> Connect<T> {
/// Create `Connect` instance by spliting the string by ':' and convert the second part to u16 /// Create `Connect` instance by splitting the string by ':' and convert the second part to u16
pub fn new(req: T) -> Connect<T> { pub fn new(req: T) -> Connect<T> {
let (_, port) = parse(req.host()); let (_, port) = parse(req.host());
Connect { Connect {
@@ -53,7 +53,8 @@ impl<T: Address> Connect<T> {
} }
} }
/// Create new `Connect` instance from host and address. Connector skips name resolution stage for such connect messages. /// Create new `Connect` instance from host and address. Connector skips name resolution stage
/// for such connect messages.
pub fn with(req: T, addr: SocketAddr) -> Connect<T> { pub fn with(req: T, addr: SocketAddr) -> Connect<T> {
Connect { Connect {
req, req,
@@ -102,7 +103,7 @@ impl<T: Address> Connect<T> {
self.req.port().unwrap_or(self.port) self.req.port().unwrap_or(self.port)
} }
/// Preresolved addresses of the request. /// Pre-resolved addresses of the request.
pub fn addrs(&self) -> ConnectAddrsIter<'_> { pub fn addrs(&self) -> ConnectAddrsIter<'_> {
let inner = match self.addr { let inner = match self.addr {
None => Either::Left(None), None => Either::Left(None),
@@ -113,7 +114,7 @@ impl<T: Address> Connect<T> {
ConnectAddrsIter { inner } ConnectAddrsIter { inner }
} }
/// Takes preresolved addresses of the request. /// Takes pre-resolved addresses of the request.
pub fn take_addrs(&mut self) -> ConnectTakeAddrsIter { pub fn take_addrs(&mut self) -> ConnectTakeAddrsIter {
let inner = match self.addr.take() { let inner = match self.addr.take() {
None => Either::Left(None), None => Either::Left(None),
@@ -137,7 +138,7 @@ impl<T: Address> fmt::Display for Connect<T> {
} }
} }
/// Iterator over addresses in a [`Connect`](struct.Connect.html) request. /// Iterator over addresses in a [`Connect`] request.
#[derive(Clone)] #[derive(Clone)]
pub struct ConnectAddrsIter<'a> { pub struct ConnectAddrsIter<'a> {
inner: Either<Option<SocketAddr>, vec_deque::Iter<'a, SocketAddr>>, inner: Either<Option<SocketAddr>, vec_deque::Iter<'a, SocketAddr>>,
@@ -172,7 +173,7 @@ impl ExactSizeIterator for ConnectAddrsIter<'_> {}
impl FusedIterator for ConnectAddrsIter<'_> {} impl FusedIterator for ConnectAddrsIter<'_> {}
/// Owned iterator over addresses in a [`Connect`](struct.Connect.html) request. /// Owned iterator over addresses in a [`Connect`] request.
#[derive(Debug)] #[derive(Debug)]
pub struct ConnectTakeAddrsIter { pub struct ConnectTakeAddrsIter {
inner: Either<Option<SocketAddr>, vec_deque::IntoIter<SocketAddr>>, inner: Either<Option<SocketAddr>, vec_deque::IntoIter<SocketAddr>>,

View File

@@ -13,7 +13,7 @@ use futures_util::future::{err, ok, BoxFuture, Either, FutureExt, Ready};
use super::connect::{Address, Connect, Connection}; use super::connect::{Address, Connect, Connection};
use super::error::ConnectError; use super::error::ConnectError;
/// Tcp connector service factory /// TCP connector service factory
#[derive(Debug)] #[derive(Debug)]
pub struct TcpConnectorFactory<T>(PhantomData<T>); pub struct TcpConnectorFactory<T>(PhantomData<T>);
@@ -22,7 +22,7 @@ impl<T> TcpConnectorFactory<T> {
TcpConnectorFactory(PhantomData) TcpConnectorFactory(PhantomData)
} }
/// Create tcp connector service /// Create TCP connector service
pub fn service(&self) -> TcpConnector<T> { pub fn service(&self) -> TcpConnector<T> {
TcpConnector(PhantomData) TcpConnector(PhantomData)
} }
@@ -40,8 +40,7 @@ impl<T> Clone for TcpConnectorFactory<T> {
} }
} }
impl<T: Address> ServiceFactory for TcpConnectorFactory<T> { impl<T: Address> ServiceFactory<Connect<T>> for TcpConnectorFactory<T> {
type Request = Connect<T>;
type Response = Connection<T, TcpStream>; type Response = Connection<T, TcpStream>;
type Error = ConnectError; type Error = ConnectError;
type Config = (); type Config = ();
@@ -54,7 +53,7 @@ impl<T: Address> ServiceFactory for TcpConnectorFactory<T> {
} }
} }
/// Tcp connector service /// TCP connector service
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct TcpConnector<T>(PhantomData<T>); pub struct TcpConnector<T>(PhantomData<T>);
@@ -70,15 +69,13 @@ impl<T> Clone for TcpConnector<T> {
} }
} }
impl<T: Address> Service for TcpConnector<T> { impl<T: Address> Service<Connect<T>> for TcpConnector<T> {
type Request = Connect<T>;
type Response = Connection<T, TcpStream>; type Response = Connection<T, TcpStream>;
type Error = ConnectError; type Error = ConnectError;
#[allow(clippy::type_complexity)]
type Future = Either<TcpConnectorResponse<T>, Ready<Result<Self::Response, Self::Error>>>; type Future = Either<TcpConnectorResponse<T>, Ready<Result<Self::Response, Self::Error>>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Connect<T>) -> Self::Future { fn call(&mut self, req: Connect<T>) -> Self::Future {
let port = req.port(); let port = req.port();
@@ -88,13 +85,13 @@ impl<T: Address> Service for TcpConnector<T> {
Either::Left(TcpConnectorResponse::new(req, port, addr)) Either::Left(TcpConnectorResponse::new(req, port, addr))
} else { } else {
error!("TCP connector: got unresolved address"); error!("TCP connector: got unresolved address");
Either::Right(err(ConnectError::Unresolverd)) Either::Right(err(ConnectError::Unresolved))
} }
} }
} }
#[doc(hidden)] #[doc(hidden)]
/// Tcp stream connector response future /// TCP stream connector response future
pub struct TcpConnectorResponse<T> { pub struct TcpConnectorResponse<T> {
req: Option<T>, req: Option<T>,
port: u16, port: u16,

View File

@@ -18,9 +18,9 @@ pub enum ConnectError {
/// Unresolved host name /// Unresolved host name
#[display(fmt = "Connector received `Connect` method with unresolved host")] #[display(fmt = "Connector received `Connect` method with unresolved host")]
Unresolverd, Unresolved,
/// Connection io error /// Connection IO error
#[display(fmt = "{}", _0)] #[display(fmt = "{}", _0)]
Io(io::Error), Io(io::Error),
} }

View File

@@ -1,12 +1,14 @@
//! Actix connect - tcp connector service //! TCP connector service for Actix ecosystem.
//! //!
//! ## Package feature //! ## Package feature
//! //!
//! * `openssl` - enables ssl support via `openssl` crate //! * `openssl` - enables TLS support via `openssl` crate
//! * `rustls` - enables ssl support via `rustls` crate //! * `rustls` - enables TLS support via `rustls` crate
#![deny(rust_2018_idioms, warnings)]
#![allow(clippy::type_complexity)] #![deny(rust_2018_idioms, nonstandard_style)]
#![recursion_limit = "128"] #![recursion_limit = "128"]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#[macro_use] #[macro_use]
extern crate log; extern crate log;
@@ -71,20 +73,20 @@ pub async fn start_default_resolver() -> Result<AsyncResolver, ConnectError> {
get_default_resolver().await get_default_resolver().await
} }
/// Create tcp connector service /// Create TCP connector service.
pub fn new_connector<T: Address + 'static>( pub fn new_connector<T: Address + 'static>(
resolver: AsyncResolver, resolver: AsyncResolver,
) -> impl Service<Request = Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError> ) -> impl Service<Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError> + Clone
+ Clone { {
pipeline(Resolver::new(resolver)).and_then(TcpConnector::new()) pipeline(Resolver::new(resolver)).and_then(TcpConnector::new())
} }
/// Create tcp connector service /// Create TCP connector service factory.
pub fn new_connector_factory<T: Address + 'static>( pub fn new_connector_factory<T: Address + 'static>(
resolver: AsyncResolver, resolver: AsyncResolver,
) -> impl ServiceFactory< ) -> impl ServiceFactory<
Connect<T>,
Config = (), Config = (),
Request = Connect<T>,
Response = Connection<T, TcpStream>, Response = Connection<T, TcpStream>,
Error = ConnectError, Error = ConnectError,
InitError = (), InitError = (),
@@ -92,17 +94,17 @@ pub fn new_connector_factory<T: Address + 'static>(
pipeline_factory(ResolverFactory::new(resolver)).and_then(TcpConnectorFactory::new()) pipeline_factory(ResolverFactory::new(resolver)).and_then(TcpConnectorFactory::new())
} }
/// Create connector service with default parameters /// Create connector service with default parameters.
pub fn default_connector<T: Address + 'static>( pub fn default_connector<T: Address + 'static>(
) -> impl Service<Request = Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError> ) -> impl Service<Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError> + Clone
+ Clone { {
pipeline(Resolver::default()).and_then(TcpConnector::new()) pipeline(Resolver::default()).and_then(TcpConnector::new())
} }
/// Create connector service factory with default parameters /// Create connector service factory with default parameters.
pub fn default_connector_factory<T: Address + 'static>() -> impl ServiceFactory< pub fn default_connector_factory<T: Address + 'static>() -> impl ServiceFactory<
Connect<T>,
Config = (), Config = (),
Request = Connect<T>,
Response = Connection<T, TcpStream>, Response = Connection<T, TcpStream>,
Error = ConnectError, Error = ConnectError,
InitError = (), InitError = (),

View File

@@ -54,8 +54,7 @@ impl<T> Clone for ResolverFactory<T> {
} }
} }
impl<T: Address> ServiceFactory for ResolverFactory<T> { impl<T: Address> ServiceFactory<Connect<T>> for ResolverFactory<T> {
type Request = Connect<T>;
type Response = Connect<T>; type Response = Connect<T>;
type Error = ConnectError; type Error = ConnectError;
type Config = (); type Config = ();
@@ -102,18 +101,16 @@ impl<T> Clone for Resolver<T> {
} }
} }
impl<T: Address> Service for Resolver<T> { impl<T: Address> Service<Connect<T>> for Resolver<T> {
type Request = Connect<T>;
type Response = Connect<T>; type Response = Connect<T>;
type Error = ConnectError; type Error = ConnectError;
#[allow(clippy::type_complexity)]
type Future = Either< type Future = Either<
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>, Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>,
Ready<Result<Connect<T>, Self::Error>>, Ready<Result<Connect<T>, Self::Error>>,
>; >;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, mut req: Connect<T>) -> Self::Future { fn call(&mut self, mut req: Connect<T>) -> Self::Future {
if req.addr.is_some() { if req.addr.is_some() {

View File

@@ -70,8 +70,7 @@ impl<T> Clone for ConnectServiceFactory<T> {
} }
} }
impl<T: Address> ServiceFactory for ConnectServiceFactory<T> { impl<T: Address> ServiceFactory<Connect<T>> for ConnectServiceFactory<T> {
type Request = Connect<T>;
type Response = Connection<T, TcpStream>; type Response = Connection<T, TcpStream>;
type Error = ConnectError; type Error = ConnectError;
type Config = (); type Config = ();
@@ -90,15 +89,12 @@ pub struct ConnectService<T> {
resolver: Resolver<T>, resolver: Resolver<T>,
} }
impl<T: Address> Service for ConnectService<T> { impl<T: Address> Service<Connect<T>> for ConnectService<T> {
type Request = Connect<T>;
type Response = Connection<T, TcpStream>; type Response = Connection<T, TcpStream>;
type Error = ConnectError; type Error = ConnectError;
type Future = ConnectServiceResponse<T>; type Future = ConnectServiceResponse<T>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Connect<T>) -> Self::Future { fn call(&mut self, req: Connect<T>) -> Self::Future {
ConnectServiceResponse { ConnectServiceResponse {
@@ -109,11 +105,12 @@ impl<T: Address> Service for ConnectService<T> {
} }
enum ConnectState<T: Address> { enum ConnectState<T: Address> {
Resolve(<Resolver<T> as Service>::Future), Resolve(<Resolver<T> as Service<Connect<T>>>::Future),
Connect(<TcpConnector<T> as Service>::Future), Connect(<TcpConnector<T> as Service<Connect<T>>>::Future),
} }
impl<T: Address> ConnectState<T> { impl<T: Address> ConnectState<T> {
#[allow(clippy::type_complexity)]
fn poll( fn poll(
&mut self, &mut self,
cx: &mut Context<'_>, cx: &mut Context<'_>,
@@ -159,15 +156,12 @@ pub struct TcpConnectService<T> {
resolver: Resolver<T>, resolver: Resolver<T>,
} }
impl<T: Address + 'static> Service for TcpConnectService<T> { impl<T: Address + 'static> Service<Connect<T>> for TcpConnectService<T> {
type Request = Connect<T>;
type Response = TcpStream; type Response = TcpStream;
type Error = ConnectError; type Error = ConnectError;
type Future = TcpConnectServiceResponse<T>; type Future = TcpConnectServiceResponse<T>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Connect<T>) -> Self::Future { fn call(&mut self, req: Connect<T>) -> Self::Future {
TcpConnectServiceResponse { TcpConnectServiceResponse {
@@ -178,8 +172,8 @@ impl<T: Address + 'static> Service for TcpConnectService<T> {
} }
enum TcpConnectState<T: Address> { enum TcpConnectState<T: Address> {
Resolve(<Resolver<T> as Service>::Future), Resolve(<Resolver<T> as Service<Connect<T>>>::Future),
Connect(<TcpConnector<T> as Service>::Future), Connect(<TcpConnector<T> as Service<Connect<T>>>::Future),
} }
impl<T: Address> TcpConnectState<T> { impl<T: Address> TcpConnectState<T> {

View File

@@ -17,7 +17,7 @@ use crate::{
Address, Connect, ConnectError, ConnectService, ConnectServiceFactory, Connection, Address, Connect, ConnectError, ConnectService, ConnectServiceFactory, Connection,
}; };
/// Openssl connector factory /// OpenSSL connector factory
pub struct OpensslConnector<T, U> { pub struct OpensslConnector<T, U> {
connector: SslConnector, connector: SslConnector,
_t: PhantomData<(T, U)>, _t: PhantomData<(T, U)>,
@@ -97,11 +97,10 @@ where
type Request = Connection<T, U>; type Request = Connection<T, U>;
type Response = Connection<T, SslStream<U>>; type Response = Connection<T, SslStream<U>>;
type Error = io::Error; type Error = io::Error;
#[allow(clippy::type_complexity)]
type Future = Either<ConnectAsyncExt<T, U>, Ready<Result<Self::Response, Self::Error>>>; type Future = Either<ConnectAsyncExt<T, U>, Ready<Result<Self::Response, Self::Error>>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, stream: Connection<T, U>) -> Self::Future { fn call(&mut self, stream: Connection<T, U>) -> Self::Future {
trace!("SSL Handshake start for: {:?}", stream.host()); trace!("SSL Handshake start for: {:?}", stream.host());
@@ -164,7 +163,7 @@ impl<T> OpensslConnectServiceFactory<T> {
} }
} }
/// Construct new connect service with custom dns resolver /// Construct new connect service with custom DNS resolver
pub fn with_resolver(connector: SslConnector, resolver: AsyncResolver) -> Self { pub fn with_resolver(connector: SslConnector, resolver: AsyncResolver) -> Self {
OpensslConnectServiceFactory { OpensslConnectServiceFactory {
tcp: ConnectServiceFactory::with_resolver(resolver), tcp: ConnectServiceFactory::with_resolver(resolver),
@@ -172,7 +171,7 @@ impl<T> OpensslConnectServiceFactory<T> {
} }
} }
/// Construct openssl connect service /// Construct OpenSSL connect service
pub fn service(&self) -> OpensslConnectService<T> { pub fn service(&self) -> OpensslConnectService<T> {
OpensslConnectService { OpensslConnectService {
tcp: self.tcp.service(), tcp: self.tcp.service(),
@@ -219,9 +218,7 @@ impl<T: Address + 'static> Service for OpensslConnectService<T> {
type Error = ConnectError; type Error = ConnectError;
type Future = OpensslConnectServiceResponse<T>; type Future = OpensslConnectServiceResponse<T>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Connect<T>) -> Self::Future { fn call(&mut self, req: Connect<T>) -> Self::Future {
OpensslConnectServiceResponse { OpensslConnectServiceResponse {

View File

@@ -96,9 +96,7 @@ where
type Error = std::io::Error; type Error = std::io::Error;
type Future = ConnectAsyncExt<T, U>; type Future = ConnectAsyncExt<T, U>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, stream: Connection<T, U>) -> Self::Future { fn call(&mut self, stream: Connection<T, U>) -> Self::Future {
trace!("SSL Handshake start for: {:?}", stream.host()); trace!("SSL Handshake start for: {:?}", stream.host());

View File

@@ -88,9 +88,9 @@ async fn test_new_service() {
assert_eq!(con.peer_addr().unwrap(), srv.addr()); assert_eq!(con.peer_addr().unwrap(), srv.addr());
} }
#[cfg(feature = "openssl")] #[cfg(all(feature = "openssl", feature = "uri"))]
#[actix_rt::test] #[actix_rt::test]
async fn test_uri() { async fn test_openssl_uri() {
use std::convert::TryFrom; use std::convert::TryFrom;
let srv = TestServer::with(|| { let srv = TestServer::with(|| {
@@ -107,7 +107,7 @@ async fn test_uri() {
assert_eq!(con.peer_addr().unwrap(), srv.addr()); assert_eq!(con.peer_addr().unwrap(), srv.addr());
} }
#[cfg(feature = "rustls")] #[cfg(all(feature = "rustls", feature = "uri"))]
#[actix_rt::test] #[actix_rt::test]
async fn test_rustls_uri() { async fn test_rustls_uri() {
use std::convert::TryFrom; use std::convert::TryFrom;

View File

@@ -1,33 +0,0 @@
# Changes
## [0.5.0] - 2019-12-29
* Simplify state management
* Allow to set custom output stream
* Removed disconnect callback
## [0.4.1] - 2019-12-11
* Disconnect callback accepts owned state
## [0.4.0] - 2019-12-11
* Remove `E` param
## [0.3.0-alpha.3] - 2019-12-07
* Migrate to tokio 0.2
## [0.3.0-alpha.2] - 2019-12-02
* Migrate to `std::future`
## [0.1.1] - 2019-10-14
* Re-register task on every dispatcher poll.
## [0.1.0] - 2019-09-25
* Initial release

View File

@@ -1,33 +0,0 @@
[package]
name = "actix-ioframe"
version = "0.5.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix framed service"
keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-ioframe/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
edition = "2018"
[lib]
name = "actix_ioframe"
path = "src/lib.rs"
[dependencies]
actix-service = "1.0.1"
actix-codec = "0.2.0"
actix-utils = "1.0.4"
actix-rt = "1.0.0"
bytes = "0.5.3"
either = "1.5.3"
futures-sink = { version = "0.3.4", default-features = false }
futures-core = { version = "0.3.4", default-features = false }
pin-project = "0.4.6"
log = "0.4"
[dev-dependencies]
actix-connect = "2.0.0-alpha.2"
actix-testing = "1.0.0"
futures-util = { version = "0.3.4", default-features = false }

View File

@@ -1 +0,0 @@
../LICENSE-APACHE

View File

@@ -1 +0,0 @@
../LICENSE-MIT

View File

@@ -1,123 +0,0 @@
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
use actix_utils::mpsc::Receiver;
use futures_core::stream::Stream;
pub struct Connect<Io, Codec>
where
Codec: Encoder + Decoder,
{
io: Io,
_t: PhantomData<Codec>,
}
impl<Io, Codec> Connect<Io, Codec>
where
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
{
pub(crate) fn new(io: Io) -> Self {
Self {
io,
_t: PhantomData,
}
}
pub fn codec(
self,
codec: Codec,
) -> ConnectResult<Io, (), Codec, Receiver<<Codec as Encoder>::Item>> {
ConnectResult {
state: (),
out: None,
framed: Framed::new(self.io, codec),
}
}
}
#[pin_project::pin_project]
pub struct ConnectResult<Io, St, Codec: Encoder + Decoder, Out> {
pub(crate) state: St,
pub(crate) out: Option<Out>,
#[pin]
pub(crate) framed: Framed<Io, Codec>,
}
impl<Io, St, Codec: Encoder + Decoder, Out: Unpin> ConnectResult<Io, St, Codec, Out> {
#[inline]
pub fn get_ref(&self) -> &Io {
self.framed.get_ref()
}
#[inline]
pub fn get_mut(&mut self) -> &mut Io {
self.framed.get_mut()
}
pub fn out<U>(self, out: U) -> ConnectResult<Io, St, Codec, U>
where
U: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
ConnectResult {
state: self.state,
framed: self.framed,
out: Some(out),
}
}
#[inline]
pub fn state<S>(self, state: S) -> ConnectResult<Io, S, Codec, Out> {
ConnectResult {
state,
framed: self.framed,
out: self.out,
}
}
}
impl<Io, St, Codec, Out> Stream for ConnectResult<Io, St, Codec, Out>
where
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
{
type Item = Result<<Codec as Decoder>::Item, <Codec as Decoder>::Error>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.project().framed.next_item(cx)
}
}
impl<Io, St, Codec, Out> futures_sink::Sink<<Codec as Encoder>::Item>
for ConnectResult<Io, St, Codec, Out>
where
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
{
type Error = <Codec as Encoder>::Error;
fn poll_ready(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if self.as_mut().project().framed.is_write_ready() {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
fn start_send(
self: Pin<&mut Self>,
item: <Codec as Encoder>::Item,
) -> Result<(), Self::Error> {
self.project().framed.write(item)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.as_mut().project().framed.flush(cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.as_mut().project().framed.close(cx)
}
}

View File

@@ -1,248 +0,0 @@
//! Framed dispatcher service and related utilities
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
use actix_service::Service;
use actix_utils::mpsc;
use futures_core::stream::Stream;
use pin_project::pin_project;
use log::debug;
use crate::error::ServiceError;
type Request<U> = <U as Decoder>::Item;
type Response<U> = <U as Encoder>::Item;
/// FramedTransport - is a future that reads frames from Framed object
/// and pass then to the service.
#[pin_project]
pub(crate) struct Dispatcher<S, T, U, Out>
where
S: Service<Request = Request<U>, Response = Option<Response<U>>>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead + AsyncWrite,
U: Encoder + Decoder,
<U as Encoder>::Item: 'static,
<U as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <U as Encoder>::Item> + Unpin,
{
service: S,
sink: Option<Out>,
state: FramedState<S, U>,
#[pin]
framed: Framed<T, U>,
rx: mpsc::Receiver<Result<<U as Encoder>::Item, S::Error>>,
}
impl<S, T, U, Out> Dispatcher<S, T, U, Out>
where
S: Service<Request = Request<U>, Response = Option<Response<U>>>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead + AsyncWrite,
U: Decoder + Encoder,
<U as Encoder>::Item: 'static,
<U as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <U as Encoder>::Item> + Unpin,
{
pub(crate) fn new(framed: Framed<T, U>, service: S, sink: Option<Out>) -> Self {
Dispatcher {
sink,
service,
framed,
rx: mpsc::channel().1,
state: FramedState::Processing,
}
}
}
enum FramedState<S: Service, U: Encoder + Decoder> {
Processing,
Error(ServiceError<S::Error, U>),
FramedError(ServiceError<S::Error, U>),
FlushAndStop,
Stopping,
}
impl<S: Service, U: Encoder + Decoder> FramedState<S, U> {
fn take_error(&mut self) -> ServiceError<S::Error, U> {
match std::mem::replace(self, FramedState::Processing) {
FramedState::Error(err) => err,
_ => panic!(),
}
}
fn take_framed_error(&mut self) -> ServiceError<S::Error, U> {
match std::mem::replace(self, FramedState::Processing) {
FramedState::FramedError(err) => err,
_ => panic!(),
}
}
}
impl<S, T, U, Out> Dispatcher<S, T, U, Out>
where
S: Service<Request = Request<U>, Response = Option<Response<U>>>,
S::Error: 'static,
S::Future: 'static,
T: AsyncRead + AsyncWrite,
U: Decoder + Encoder,
<U as Encoder>::Item: 'static,
<U as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <U as Encoder>::Item> + Unpin,
{
fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool {
loop {
let this = self.as_mut().project();
match this.service.poll_ready(cx) {
Poll::Ready(Ok(_)) => {
let item = match this.framed.next_item(cx) {
Poll::Ready(Some(Ok(el))) => el,
Poll::Ready(Some(Err(err))) => {
*this.state = FramedState::FramedError(ServiceError::Decoder(err));
return true;
}
Poll::Pending => return false,
Poll::Ready(None) => {
log::trace!("Client disconnected");
*this.state = FramedState::Stopping;
return true;
}
};
let tx = this.rx.sender();
let fut = this.service.call(item);
actix_rt::spawn(async move {
let item = fut.await;
let item = match item {
Ok(Some(item)) => Ok(item),
Ok(None) => return,
Err(err) => Err(err),
};
let _ = tx.send(item);
});
}
Poll::Pending => return false,
Poll::Ready(Err(err)) => {
*this.state = FramedState::Error(ServiceError::Service(err));
return true;
}
}
}
}
/// write to framed object
fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool {
loop {
let mut this = self.as_mut().project();
while !this.framed.is_write_buf_full() {
match Pin::new(&mut this.rx).poll_next(cx) {
Poll::Ready(Some(Ok(msg))) => {
if let Err(err) = this.framed.as_mut().write(msg) {
*this.state = FramedState::FramedError(ServiceError::Encoder(err));
return true;
}
continue;
}
Poll::Ready(Some(Err(err))) => {
*this.state = FramedState::Error(ServiceError::Service(err));
return true;
}
Poll::Ready(None) | Poll::Pending => (),
}
if this.sink.is_some() {
match Pin::new(this.sink.as_mut().unwrap()).poll_next(cx) {
Poll::Ready(Some(msg)) => {
if let Err(err) = this.framed.as_mut().write(msg) {
*this.state =
FramedState::FramedError(ServiceError::Encoder(err));
return true;
}
continue;
}
Poll::Ready(None) => {
let _ = this.sink.take();
*this.state = FramedState::FlushAndStop;
return true;
}
Poll::Pending => (),
}
}
break;
}
if !this.framed.is_write_buf_empty() {
match this.framed.as_mut().flush(cx) {
Poll::Pending => break,
Poll::Ready(Ok(_)) => (),
Poll::Ready(Err(err)) => {
debug!("Error sending data: {:?}", err);
*this.state = FramedState::FramedError(ServiceError::Encoder(err));
return true;
}
}
} else {
break;
}
}
false
}
pub(crate) fn poll(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), ServiceError<S::Error, U>>> {
let mut this = self.as_mut().project();
match this.state {
FramedState::Processing => loop {
let read = self.as_mut().poll_read(cx);
let write = self.as_mut().poll_write(cx);
if read || write {
continue;
} else {
return Poll::Pending;
}
},
FramedState::Error(_) => {
// flush write buffer
if !this.framed.is_write_buf_empty() {
if let Poll::Pending = this.framed.flush(cx) {
return Poll::Pending;
}
}
Poll::Ready(Err(this.state.take_error()))
}
FramedState::FlushAndStop => {
// drain service responses
match Pin::new(this.rx).poll_next(cx) {
Poll::Ready(Some(Ok(msg))) => {
if this.framed.as_mut().write(msg).is_err() {
return Poll::Ready(Ok(()));
}
}
Poll::Ready(Some(Err(_))) => return Poll::Ready(Ok(())),
Poll::Ready(None) | Poll::Pending => (),
}
// flush io
if !this.framed.is_write_buf_empty() {
match this.framed.flush(cx) {
Poll::Ready(Err(err)) => {
debug!("Error sending data: {:?}", err);
}
Poll::Pending => {
return Poll::Pending;
}
Poll::Ready(_) => (),
}
};
Poll::Ready(Ok(()))
}
FramedState::FramedError(_) => Poll::Ready(Err(this.state.take_framed_error())),
FramedState::Stopping => Poll::Ready(Ok(())),
}
}
}

View File

@@ -1,49 +0,0 @@
use std::fmt;
use actix_codec::{Decoder, Encoder};
/// Framed service errors
pub enum ServiceError<E, U: Encoder + Decoder> {
/// Inner service error
Service(E),
/// Encoder parse error
Encoder(<U as Encoder>::Error),
/// Decoder parse error
Decoder(<U as Decoder>::Error),
}
impl<E, U: Encoder + Decoder> From<E> for ServiceError<E, U> {
fn from(err: E) -> Self {
ServiceError::Service(err)
}
}
impl<E, U: Encoder + Decoder> fmt::Debug for ServiceError<E, U>
where
E: fmt::Debug,
<U as Encoder>::Error: fmt::Debug,
<U as Decoder>::Error: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ServiceError::Service(ref e) => write!(fmt, "ServiceError::Service({:?})", e),
ServiceError::Encoder(ref e) => write!(fmt, "ServiceError::Encoder({:?})", e),
ServiceError::Decoder(ref e) => write!(fmt, "ServiceError::Encoder({:?})", e),
}
}
}
impl<E, U: Encoder + Decoder> fmt::Display for ServiceError<E, U>
where
E: fmt::Display,
<U as Encoder>::Error: fmt::Debug,
<U as Decoder>::Error: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ServiceError::Service(ref e) => write!(fmt, "{}", e),
ServiceError::Encoder(ref e) => write!(fmt, "{:?}", e),
ServiceError::Decoder(ref e) => write!(fmt, "{:?}", e),
}
}
}

View File

@@ -1,11 +0,0 @@
// #![deny(rust_2018_idioms, warnings)]
#![allow(clippy::type_complexity, clippy::too_many_arguments)]
mod connect;
mod dispatcher;
mod error;
mod service;
pub use self::connect::{Connect, ConnectResult};
pub use self::error::ServiceError;
pub use self::service::{Builder, FactoryBuilder};

View File

@@ -1,416 +0,0 @@
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory};
use either::Either;
use futures_core::{ready, stream::Stream};
use pin_project::project;
use crate::connect::{Connect, ConnectResult};
use crate::dispatcher::Dispatcher;
use crate::error::ServiceError;
type RequestItem<U> = <U as Decoder>::Item;
type ResponseItem<U> = Option<<U as Encoder>::Item>;
/// Service builder - structure that follows the builder pattern
/// for building instances for framed services.
pub struct Builder<St, C, Io, Codec, Out> {
connect: C,
_t: PhantomData<(St, Io, Codec, Out)>,
}
impl<St, C, Io, Codec, Out> Builder<St, C, Io, Codec, Out>
where
C: Service<Request = Connect<Io, Codec>, Response = ConnectResult<Io, St, Codec, Out>>,
Io: AsyncRead + AsyncWrite,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
/// Construct framed handler service with specified connect service
pub fn new<F>(connect: F) -> Builder<St, C, Io, Codec, Out>
where
F: IntoService<C>,
Io: AsyncRead + AsyncWrite,
C: Service<Request = Connect<Io, Codec>, Response = ConnectResult<Io, St, Codec, Out>>,
Codec: Decoder + Encoder,
Out: Stream<Item = <Codec as Encoder>::Item>,
{
Builder {
connect: connect.into_service(),
_t: PhantomData,
}
}
/// Provide stream items handler service and construct service factory.
pub fn build<F, T>(self, service: F) -> FramedServiceImpl<St, C, T, Io, Codec, Out>
where
F: IntoServiceFactory<T>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
{
FramedServiceImpl {
connect: self.connect,
handler: Rc::new(service.into_factory()),
_t: PhantomData,
}
}
}
/// Service builder - structure that follows the builder pattern
/// for building instances for framed services.
pub struct FactoryBuilder<St, C, Io, Codec, Out> {
connect: C,
_t: PhantomData<(St, Io, Codec, Out)>,
}
impl<St, C, Io, Codec, Out> FactoryBuilder<St, C, Io, Codec, Out>
where
Io: AsyncRead + AsyncWrite,
C: ServiceFactory<
Config = (),
Request = Connect<Io, Codec>,
Response = ConnectResult<Io, St, Codec, Out>,
>,
Codec: Decoder + Encoder,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
/// Construct framed handler new service with specified connect service
pub fn new<F>(connect: F) -> FactoryBuilder<St, C, Io, Codec, Out>
where
F: IntoServiceFactory<C>,
Io: AsyncRead + AsyncWrite,
C: ServiceFactory<
Config = (),
Request = Connect<Io, Codec>,
Response = ConnectResult<Io, St, Codec, Out>,
>,
Codec: Decoder + Encoder,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
FactoryBuilder {
connect: connect.into_factory(),
_t: PhantomData,
}
}
pub fn build<F, T, Cfg>(self, service: F) -> FramedService<St, C, T, Io, Codec, Out, Cfg>
where
F: IntoServiceFactory<T>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
{
FramedService {
connect: self.connect,
handler: Rc::new(service.into_factory()),
_t: PhantomData,
}
}
}
pub struct FramedService<St, C, T, Io, Codec, Out, Cfg> {
connect: C,
handler: Rc<T>,
_t: PhantomData<(St, Io, Codec, Out, Cfg)>,
}
impl<St, C, T, Io, Codec, Out, Cfg> ServiceFactory
for FramedService<St, C, T, Io, Codec, Out, Cfg>
where
Io: AsyncRead + AsyncWrite,
C: ServiceFactory<
Config = (),
Request = Connect<Io, Codec>,
Response = ConnectResult<Io, St, Codec, Out>,
>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
type Config = Cfg;
type Request = Io;
type Response = ();
type Error = ServiceError<C::Error, Codec>;
type InitError = C::InitError;
type Service = FramedServiceImpl<St, C::Service, T, Io, Codec, Out>;
type Future = FramedServiceResponse<St, C, T, Io, Codec, Out>;
fn new_service(&self, _: Cfg) -> Self::Future {
// create connect service and then create service impl
FramedServiceResponse {
fut: self.connect.new_service(()),
handler: self.handler.clone(),
}
}
}
#[pin_project::pin_project]
pub struct FramedServiceResponse<St, C, T, Io, Codec, Out>
where
Io: AsyncRead + AsyncWrite,
C: ServiceFactory<
Config = (),
Request = Connect<Io, Codec>,
Response = ConnectResult<Io, St, Codec, Out>,
>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
#[pin]
fut: C::Future,
handler: Rc<T>,
}
impl<St, C, T, Io, Codec, Out> Future for FramedServiceResponse<St, C, T, Io, Codec, Out>
where
Io: AsyncRead + AsyncWrite,
C: ServiceFactory<
Config = (),
Request = Connect<Io, Codec>,
Response = ConnectResult<Io, St, Codec, Out>,
>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
type Output = Result<FramedServiceImpl<St, C::Service, T, Io, Codec, Out>, C::InitError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let connect = ready!(this.fut.poll(cx))?;
Poll::Ready(Ok(FramedServiceImpl {
connect,
handler: this.handler.clone(),
_t: PhantomData,
}))
}
}
pub struct FramedServiceImpl<St, C, T, Io, Codec, Out> {
connect: C,
handler: Rc<T>,
_t: PhantomData<(St, Io, Codec, Out)>,
}
impl<St, C, T, Io, Codec, Out> Service for FramedServiceImpl<St, C, T, Io, Codec, Out>
where
Io: AsyncRead + AsyncWrite,
C: Service<Request = Connect<Io, Codec>, Response = ConnectResult<Io, St, Codec, Out>>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
type Request = Io;
type Response = ();
type Error = ServiceError<C::Error, Codec>;
type Future = FramedServiceImplResponse<St, Io, Codec, Out, C, T>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.connect.poll_ready(cx).map_err(|e| e.into())
}
fn call(&mut self, req: Io) -> Self::Future {
FramedServiceImplResponse {
inner: FramedServiceImplResponseInner::Connect(
self.connect.call(Connect::new(req)),
self.handler.clone(),
),
}
}
}
#[pin_project::pin_project]
pub struct FramedServiceImplResponse<St, Io, Codec, Out, C, T>
where
C: Service<Request = Connect<Io, Codec>, Response = ConnectResult<Io, St, Codec, Out>>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
#[pin]
inner: FramedServiceImplResponseInner<St, Io, Codec, Out, C, T>,
}
impl<St, Io, Codec, Out, C, T> Future for FramedServiceImplResponse<St, Io, Codec, Out, C, T>
where
C: Service<Request = Connect<Io, Codec>, Response = ConnectResult<Io, St, Codec, Out>>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
type Output = Result<(), ServiceError<C::Error, Codec>>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
loop {
match this.inner.poll(cx) {
Either::Left(new) => {
this = self.as_mut().project();
this.inner.set(new)
}
Either::Right(poll) => return poll,
};
}
}
}
#[pin_project::pin_project]
enum FramedServiceImplResponseInner<St, Io, Codec, Out, C, T>
where
C: Service<Request = Connect<Io, Codec>, Response = ConnectResult<Io, St, Codec, Out>>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
Connect(#[pin] C::Future, Rc<T>),
Handler(#[pin] T::Future, Option<Framed<Io, Codec>>, Option<Out>),
Dispatcher(#[pin] Dispatcher<T::Service, Io, Codec, Out>),
}
impl<St, Io, Codec, Out, C, T> FramedServiceImplResponseInner<St, Io, Codec, Out, C, T>
where
C: Service<Request = Connect<Io, Codec>, Response = ConnectResult<Io, St, Codec, Out>>,
T: ServiceFactory<
Config = St,
Request = RequestItem<Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Error: 'static,
<T::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
Out: Stream<Item = <Codec as Encoder>::Item> + Unpin,
{
#[project]
fn poll(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Either<
FramedServiceImplResponseInner<St, Io, Codec, Out, C, T>,
Poll<Result<(), ServiceError<C::Error, Codec>>>,
> {
#[project]
match self.project() {
FramedServiceImplResponseInner::Connect(fut, handler) => match fut.poll(cx) {
Poll::Ready(Ok(res)) => Either::Left(FramedServiceImplResponseInner::Handler(
handler.new_service(res.state),
Some(res.framed),
res.out,
)),
Poll::Pending => Either::Right(Poll::Pending),
Poll::Ready(Err(e)) => Either::Right(Poll::Ready(Err(e.into()))),
},
FramedServiceImplResponseInner::Handler(fut, framed, out) => {
match fut.poll(cx) {
Poll::Ready(Ok(handler)) => {
Either::Left(FramedServiceImplResponseInner::Dispatcher(
Dispatcher::new(framed.take().unwrap(), handler, out.take()),
))
}
Poll::Pending => Either::Right(Poll::Pending),
Poll::Ready(Err(e)) => Either::Right(Poll::Ready(Err(e.into()))),
}
}
FramedServiceImplResponseInner::Dispatcher(fut) => {
Either::Right(fut.poll(cx))
}
}
}
}

View File

@@ -1,55 +0,0 @@
use std::cell::Cell;
use std::rc::Rc;
use actix_codec::BytesCodec;
use actix_service::{fn_factory_with_config, fn_service, IntoService, Service};
use actix_testing::TestServer;
use actix_utils::mpsc;
use bytes::{Bytes, BytesMut};
use futures_util::future::ok;
use actix_ioframe::{Builder, Connect, FactoryBuilder};
#[derive(Clone)]
struct State(Option<mpsc::Sender<Bytes>>);
#[actix_rt::test]
async fn test_basic() {
let client_item = Rc::new(Cell::new(false));
let srv = TestServer::with(move || {
FactoryBuilder::new(fn_service(|conn: Connect<_, _>| {
ok(conn.codec(BytesCodec).state(State(None)))
}))
// echo
.build(fn_service(|t: BytesMut| ok(Some(t.freeze()))))
});
let item = client_item.clone();
let mut client = Builder::new(fn_service(move |conn: Connect<_, _>| {
async move {
let (tx, rx) = mpsc::channel();
let _ = tx.send(Bytes::from_static(b"Hello"));
Ok(conn.codec(BytesCodec).out(rx).state(State(Some(tx))))
}
}))
.build(fn_factory_with_config(move |mut cfg: State| {
let item = item.clone();
ok((move |t: BytesMut| {
assert_eq!(t.freeze(), Bytes::from_static(b"Hello"));
item.set(true);
// drop Sender, which will close connection
cfg.0.take();
ok::<_, ()>(None)
})
.into_service())
}));
let conn = actix_connect::default_connector()
.call(actix_connect::Connect::with(String::new(), srv.addr()))
.await
.unwrap();
client.call(conn.into_parts().0).await.unwrap();
assert!(client_item.get());
}

1
actix-macros/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/wip

13
actix-macros/CHANGES.md Normal file
View File

@@ -0,0 +1,13 @@
# CHANGES
## 0.1.3 - 2020-12-3
* Add `actix-reexport` feature
## 0.1.2 - 2020-05-18
### Changed
* Forward actix_rt::test arguments to test function [#127]
[#127]: https://github.com/actix/actix-net/pull/127

View File

@@ -1,14 +1,13 @@
[package] [package]
name = "actix-macros" name = "actix-macros"
version = "0.1.1" version = "0.1.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix runtime macros" description = "Actix runtime macros"
repository = "https://github.com/actix/actix-net" repository = "https://github.com/actix/actix-net"
documentation = "https://docs.rs/actix-macros/" documentation = "https://docs.rs/actix-macros/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
workspace = ".."
[lib] [lib]
proc-macro = true proc-macro = true
@@ -17,5 +16,11 @@ proc-macro = true
quote = "1.0.3" quote = "1.0.3"
syn = { version = "^1", features = ["full"] } syn = { version = "^1", features = ["full"] }
[features]
actix-reexport = []
[dev-dependencies] [dev-dependencies]
actix-rt = { version = "1.0.0" } actix-rt = "1.0"
futures-util = { version = "0.3", default-features = false }
trybuild = "1"

View File

@@ -1,5 +1,8 @@
//! Macros for use with Tokio //! Macros for use with Tokio
extern crate proc_macro;
#![deny(rust_2018_idioms, nonstandard_style)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
@@ -33,14 +36,25 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
sig.asyncness = None; sig.asyncness = None;
(quote! { if cfg!(feature = "actix-reexport") {
#(#attrs)* (quote! {
#vis #sig { #(#attrs)*
actix_rt::System::new(stringify!(#name)) #vis #sig {
.block_on(async move { #body }) actix::System::new(stringify!(#name))
} .block_on(async move { #body })
}) }
.into() })
.into()
} else {
(quote! {
#(#attrs)*
#vis #sig {
actix_rt::System::new(stringify!(#name))
.block_on(async move { #body })
}
})
.into()
}
} }
/// Marks async test function to be executed by actix runtime. /// Marks async test function to be executed by actix runtime.
@@ -55,12 +69,11 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
/// ``` /// ```
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn test(_: TokenStream, item: TokenStream) -> TokenStream { pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as syn::ItemFn); let mut input = syn::parse_macro_input!(item as syn::ItemFn);
let ret = &input.sig.output;
let name = &input.sig.ident;
let body = &input.block;
let attrs = &input.attrs; let attrs = &input.attrs;
let vis = &input.vis;
let sig = &mut input.sig;
let body = &input.block;
let mut has_test_attr = false; let mut has_test_attr = false;
for attr in attrs { for attr in attrs {
@@ -69,7 +82,7 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
} }
} }
if input.sig.asyncness.is_none() { if sig.asyncness.is_none() {
return syn::Error::new_spanned( return syn::Error::new_spanned(
input.sig.fn_token, input.sig.fn_token,
format!("only async fn is supported, {}", input.sig.ident), format!("only async fn is supported, {}", input.sig.ident),
@@ -78,10 +91,12 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
.into(); .into();
} }
sig.asyncness = None;
let result = if has_test_attr { let result = if has_test_attr {
quote! { quote! {
#(#attrs)* #(#attrs)*
fn #name() #ret { #vis #sig {
actix_rt::System::new("test") actix_rt::System::new("test")
.block_on(async { #body }) .block_on(async { #body })
} }
@@ -90,7 +105,7 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
quote! { quote! {
#[test] #[test]
#(#attrs)* #(#attrs)*
fn #name() #ret { #vis #sig {
actix_rt::System::new("test") actix_rt::System::new("test")
.block_on(async { #body }) .block_on(async { #body })
} }

View File

@@ -0,0 +1,9 @@
#[test]
fn compile_macros() {
let t = trybuild::TestCases::new();
t.pass("tests/trybuild/main-01-basic.rs");
t.compile_fail("tests/trybuild/main-02-only-async.rs");
t.pass("tests/trybuild/test-01-basic.rs");
t.pass("tests/trybuild/test-02-keep-attrs.rs");
}

View File

@@ -0,0 +1,4 @@
#[actix_rt::main]
async fn main() {
println!("Hello world");
}

View File

@@ -0,0 +1,4 @@
#[actix_rt::main]
fn main() {
futures_util::future::ready(()).await
}

View File

@@ -0,0 +1,14 @@
error: only async fn is supported
--> $DIR/main-02-only-async.rs:2:1
|
2 | fn main() {
| ^^
error[E0601]: `main` function not found in crate `$CRATE`
--> $DIR/main-02-only-async.rs:1:1
|
1 | / #[actix_rt::main]
2 | | fn main() {
3 | | futures_util::future::ready(()).await
4 | | }
| |_^ consider adding a `main` function to `$DIR/tests/trybuild/main-02-only-async.rs`

View File

@@ -0,0 +1,6 @@
#[actix_rt::test]
async fn my_test() {
assert!(true);
}
fn main() {}

View File

@@ -0,0 +1,7 @@
#[actix_rt::test]
#[should_panic]
async fn my_test() {
todo!()
}
fn main() {}

View File

@@ -1,7 +1,39 @@
# Changes # Changes
## Unreleased - 2020-xx-xx
## 2.0.0-beta.1 - 2020-12-28
### Added
* Add `System::attach_to_tokio` method. [#173]
### Changed
* Update `tokio` dependency to `1.0`. [#236]
* Rename `time` module `delay_for` to `sleep`, `delay_until` to `sleep_until`, `Delay` to `Sleep`
to stay aligned with Tokio's naming. [#236]
* Remove `'static` lifetime requirement for `Runtime::block_on` and `SystemRunner::block_on`.
* These methods now accept `&self` when calling. [#236]
* Remove `'static` lifetime requirement for `System::run` and `Builder::run`. [#236]
* `Arbiter::spawn` now panics when `System` is not in scope. [#207]
### Fixed
* Fix work load issue by removing `PENDING` thread local. [#207]
[#207]: https://github.com/actix/actix-net/pull/207
[#236]: https://github.com/actix/actix-net/pull/236
## [1.1.1] - 2020-04-30
### Fixed
* Fix memory leak due to [#94] (see [#129] for more detail)
[#129]: https://github.com/actix/actix-net/issues/129
## [1.1.0] - 2020-04-08 ## [1.1.0] - 2020-04-08
**This version has been yanked.**
### Added ### Added
* Expose `System::is_set` to check if current system has ben started [#99] * Expose `System::is_set` to check if current system has ben started [#99]

View File

@@ -1,14 +1,14 @@
[package] [package]
name = "actix-rt" name = "actix-rt"
version = "1.1.0" version = "2.0.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix runtime" description = "Tokio-based single-thread async runtime for the Actix ecosystem"
keywords = ["network", "framework", "async", "futures"] keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-rt/" documentation = "https://docs.rs/actix-rt/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[lib] [lib]
@@ -17,8 +17,5 @@ path = "src/lib.rs"
[dependencies] [dependencies]
actix-macros = "0.1.0" actix-macros = "0.1.0"
actix-threadpool = "0.3"
futures-channel = { version = "0.3.4", default-features = false } tokio = { version = "1", features = ["rt", "net", "signal", "sync", "time"] }
futures-util = { version = "0.3.4", default-features = false, features = ["alloc"] }
copyless = "0.1.4"
tokio = { version = "0.2.6", default-features = false, features = ["rt-core", "rt-util", "io-driver", "tcp", "uds", "udp", "time", "signal", "stream"] }

View File

@@ -1,30 +1,30 @@
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use std::cell::{Cell, RefCell}; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::{fmt, thread}; use std::{fmt, thread};
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use futures_channel::oneshot::{channel, Canceled, Sender}; use tokio::sync::oneshot::{channel, error::RecvError as Canceled, Sender};
use futures_util::{ // use futures_util::stream::FuturesUnordered;
future::{self, Future, FutureExt}, // use tokio::task::JoinHandle;
stream::Stream, // use tokio::stream::StreamExt;
}; use tokio::task::LocalSet;
use crate::runtime::Runtime; use crate::runtime::Runtime;
use crate::system::System; use crate::system::System;
use copyless::BoxHelper;
pub use tokio::task::JoinHandle;
thread_local!( thread_local!(
static ADDR: RefCell<Option<Arbiter>> = RefCell::new(None); static ADDR: RefCell<Option<Arbiter>> = RefCell::new(None);
static RUNNING: Cell<bool> = Cell::new(false); // TODO: Commented out code are for Arbiter::local_join function.
static Q: RefCell<Vec<Pin<Box<dyn Future<Output = ()>>>>> = RefCell::new(Vec::new()); // It can be safely removed if this function is not used in actix-*.
static PENDING: RefCell<Vec<JoinHandle<()>>> = RefCell::new(Vec::new()); //
// /// stores join handle for spawned async tasks.
// static HANDLE: RefCell<FuturesUnordered<JoinHandle<()>>> =
// RefCell::new(FuturesUnordered::new());
static STORAGE: RefCell<HashMap<TypeId, Box<dyn Any>>> = RefCell::new(HashMap::new()); static STORAGE: RefCell<HashMap<TypeId, Box<dyn Any>>> = RefCell::new(HashMap::new());
); );
@@ -68,14 +68,14 @@ impl Default for Arbiter {
} }
impl Arbiter { impl Arbiter {
pub(crate) fn new_system() -> Self { pub(crate) fn new_system(local: &LocalSet) -> Self {
let (tx, rx) = unbounded(); let (tx, rx) = unbounded_channel();
let arb = Arbiter::with_sender(tx); let arb = Arbiter::with_sender(tx);
ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone())); ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone()));
RUNNING.with(|cell| cell.set(false));
STORAGE.with(|cell| cell.borrow_mut().clear()); STORAGE.with(|cell| cell.borrow_mut().clear());
Arbiter::spawn(ArbiterController { stop: None, rx });
local.spawn_local(ArbiterController { rx });
arb arb
} }
@@ -90,13 +90,14 @@ impl Arbiter {
} }
/// Check if current arbiter is running. /// Check if current arbiter is running.
#[deprecated(note = "Thread local variables for running state of Arbiter is removed")]
pub fn is_running() -> bool { pub fn is_running() -> bool {
RUNNING.with(|cell| cell.get()) false
} }
/// Stop arbiter from continuing it's event loop. /// Stop arbiter from continuing it's event loop.
pub fn stop(&self) { pub fn stop(&self) {
let _ = self.sender.unbounded_send(ArbiterCommand::Stop); let _ = self.sender.send(ArbiterCommand::Stop);
} }
/// Spawn new thread and run event loop in spawned thread. /// Spawn new thread and run event loop in spawned thread.
@@ -105,72 +106,47 @@ impl Arbiter {
let id = COUNT.fetch_add(1, Ordering::Relaxed); let id = COUNT.fetch_add(1, Ordering::Relaxed);
let name = format!("actix-rt:worker:{}", id); let name = format!("actix-rt:worker:{}", id);
let sys = System::current(); let sys = System::current();
let (arb_tx, arb_rx) = unbounded(); let (tx, rx) = unbounded_channel();
let arb_tx2 = arb_tx.clone();
let handle = thread::Builder::new() let handle = thread::Builder::new()
.name(name.clone()) .name(name.clone())
.spawn(move || { .spawn({
let mut rt = Runtime::new().expect("Can not create Runtime"); let tx = tx.clone();
let arb = Arbiter::with_sender(arb_tx); move || {
let rt = Runtime::new().expect("Can not create Runtime");
let arb = Arbiter::with_sender(tx);
let (stop, stop_rx) = channel(); STORAGE.with(|cell| cell.borrow_mut().clear());
RUNNING.with(|cell| cell.set(true));
STORAGE.with(|cell| cell.borrow_mut().clear());
System::set_current(sys); System::set_current(sys);
// start arbiter controller ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone()));
rt.spawn(ArbiterController {
stop: Some(stop),
rx: arb_rx,
});
ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone()));
// register arbiter // register arbiter
let _ = System::current() let _ = System::current()
.sys() .sys()
.unbounded_send(SystemCommand::RegisterArbiter(id, arb)); .send(SystemCommand::RegisterArbiter(id, arb));
// run loop // start arbiter controller
let _ = match rt.block_on(stop_rx) { // run loop
Ok(code) => code, rt.block_on(ArbiterController { rx });
Err(_) => 1,
};
// unregister arbiter // unregister arbiter
let _ = System::current() let _ = System::current()
.sys() .sys()
.unbounded_send(SystemCommand::UnregisterArbiter(id)); .send(SystemCommand::UnregisterArbiter(id));
}
}) })
.unwrap_or_else(|err| { .unwrap_or_else(|err| {
panic!("Cannot spawn an arbiter's thread {:?}: {:?}", &name, err) panic!("Cannot spawn an arbiter's thread {:?}: {:?}", &name, err)
}); });
Arbiter { Arbiter {
sender: arb_tx2, sender: tx,
thread_handle: Some(handle), thread_handle: Some(handle),
} }
} }
pub(crate) fn run_system(rt: Option<&Runtime>) {
RUNNING.with(|cell| cell.set(true));
Q.with(|cell| {
let mut v = cell.borrow_mut();
for fut in v.drain(..) {
if let Some(rt) = rt {
rt.spawn(fut);
} else {
tokio::task::spawn_local(fut);
}
}
});
}
pub(crate) fn stop_system() {
RUNNING.with(|cell| cell.set(false));
}
/// Spawn a future on the current thread. This does not create a new Arbiter /// Spawn a future on the current thread. This does not create a new Arbiter
/// or Arbiter address, it is simply a helper for spawning futures on the current /// or Arbiter address, it is simply a helper for spawning futures on the current
/// thread. /// thread.
@@ -178,20 +154,12 @@ impl Arbiter {
where where
F: Future<Output = ()> + 'static, F: Future<Output = ()> + 'static,
{ {
RUNNING.with(move |cell| { // HANDLE.with(|handle| {
if cell.get() { // let handle = handle.borrow();
// Spawn the future on running executor // handle.push(tokio::task::spawn_local(future));
PENDING.with(move |cell| { // });
cell.borrow_mut().push(tokio::task::spawn_local(future)); // let _ = tokio::task::spawn_local(CleanupPending);
}) let _ = tokio::task::spawn_local(future);
} else {
// Box the future and push it to the queue, this results in double boxing
// because the executor boxes the future again, but works for now
Q.with(move |cell| {
cell.borrow_mut().push(Pin::from(Box::alloc().init(future)))
});
}
});
} }
/// Executes a future on the current thread. This does not create a new Arbiter /// Executes a future on the current thread. This does not create a new Arbiter
@@ -202,7 +170,9 @@ impl Arbiter {
F: FnOnce() -> R + 'static, F: FnOnce() -> R + 'static,
R: Future<Output = ()> + 'static, R: Future<Output = ()> + 'static,
{ {
Arbiter::spawn(future::lazy(|_| f()).flatten()) Arbiter::spawn(async {
f();
})
} }
/// Send a future to the Arbiter's thread, and spawn it. /// Send a future to the Arbiter's thread, and spawn it.
@@ -210,9 +180,7 @@ impl Arbiter {
where where
F: Future<Output = ()> + Send + Unpin + 'static, F: Future<Output = ()> + Send + Unpin + 'static,
{ {
let _ = self let _ = self.sender.send(ArbiterCommand::Execute(Box::new(future)));
.sender
.unbounded_send(ArbiterCommand::Execute(Box::new(future)));
} }
/// Send a function to the Arbiter's thread, and execute it. Any result from the function /// Send a function to the Arbiter's thread, and execute it. Any result from the function
@@ -223,7 +191,7 @@ impl Arbiter {
{ {
let _ = self let _ = self
.sender .sender
.unbounded_send(ArbiterCommand::ExecuteFn(Box::new(move || { .send(ArbiterCommand::ExecuteFn(Box::new(move || {
f(); f();
}))); })));
} }
@@ -239,8 +207,8 @@ impl Arbiter {
let (tx, rx) = channel(); let (tx, rx) = channel();
let _ = self let _ = self
.sender .sender
.unbounded_send(ArbiterCommand::ExecuteFn(Box::new(move || { .send(ArbiterCommand::ExecuteFn(Box::new(move || {
if !tx.is_canceled() { if !tx.is_closed() {
let _ = tx.send(f()); let _ = tx.send(f());
} }
}))); })));
@@ -309,16 +277,33 @@ impl Arbiter {
/// Returns a future that will be completed once all currently spawned futures /// Returns a future that will be completed once all currently spawned futures
/// have completed. /// have completed.
pub fn local_join() -> impl Future<Output = ()> { #[deprecated(since = "1.2.0", note = "Arbiter::local_join function is removed.")]
PENDING.with(move |cell| { pub async fn local_join() {
let current = cell.replace(Vec::new()); // let handle = HANDLE.with(|fut| std::mem::take(&mut *fut.borrow_mut()));
future::join_all(current).map(|_| ()) // async move {
}) // handle.collect::<Vec<_>>().await;
// }
unimplemented!("Arbiter::local_join function is removed.")
} }
} }
// /// Future used for cleaning-up already finished `JoinHandle`s
// /// from the `PENDING` list so the vector doesn't grow indefinitely
// struct CleanupPending;
//
// impl Future for CleanupPending {
// type Output = ();
//
// fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// HANDLE.with(move |handle| {
// recycle_join_handle(&mut *handle.borrow_mut(), cx);
// });
//
// Poll::Ready(())
// }
// }
struct ArbiterController { struct ArbiterController {
stop: Option<Sender<i32>>,
rx: UnboundedReceiver<ArbiterCommand>, rx: UnboundedReceiver<ArbiterCommand>,
} }
@@ -340,19 +325,17 @@ impl Future for ArbiterController {
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> {
loop { loop {
match Pin::new(&mut self.rx).poll_next(cx) { match Pin::new(&mut self.rx).poll_recv(cx) {
Poll::Ready(None) => return Poll::Ready(()), Poll::Ready(None) => return Poll::Ready(()),
Poll::Ready(Some(item)) => match item { Poll::Ready(Some(item)) => match item {
ArbiterCommand::Stop => { ArbiterCommand::Stop => return Poll::Ready(()),
if let Some(stop) = self.stop.take() {
let _ = stop.send(0);
};
return Poll::Ready(());
}
ArbiterCommand::Execute(fut) => { ArbiterCommand::Execute(fut) => {
PENDING.with(move |cell| { // HANDLE.with(|handle| {
cell.borrow_mut().push(tokio::task::spawn_local(fut)); // let mut handle = handle.borrow_mut();
}); // handle.push(tokio::task::spawn_local(fut));
// recycle_join_handle(&mut *handle, cx);
// });
tokio::task::spawn_local(fut);
} }
ArbiterCommand::ExecuteFn(f) => { ArbiterCommand::ExecuteFn(f) => {
f.call_box(); f.call_box();
@@ -364,6 +347,20 @@ impl Future for ArbiterController {
} }
} }
// fn recycle_join_handle(handle: &mut FuturesUnordered<JoinHandle<()>>, cx: &mut Context<'_>) {
// let _ = Pin::new(&mut *handle).poll_next(cx);
//
// // Try to recycle more join handles and free up memory.
// //
// // this is a guess. The yield limit for FuturesUnordered is 32.
// // So poll an extra 3 times would make the total poll below 128.
// if handle.len() > 64 {
// (0..3).for_each(|_| {
// let _ = Pin::new(&mut *handle).poll_next(cx);
// })
// }
// }
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum SystemCommand { pub(crate) enum SystemCommand {
Exit(i32), Exit(i32),
@@ -393,7 +390,7 @@ impl Future for SystemArbiter {
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> {
loop { loop {
match Pin::new(&mut self.commands).poll_next(cx) { match Pin::new(&mut self.commands).poll_recv(cx) {
Poll::Ready(None) => return Poll::Ready(()), Poll::Ready(None) => return Poll::Ready(()),
Poll::Ready(Some(cmd)) => match cmd { Poll::Ready(Some(cmd)) => match cmd {
SystemCommand::Exit(code) => { SystemCommand::Exit(code) => {

View File

@@ -1,9 +1,9 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::future::Future;
use std::io; use std::io;
use futures_channel::mpsc::unbounded; use tokio::sync::mpsc::unbounded_channel;
use futures_channel::oneshot::{channel, Receiver}; use tokio::sync::oneshot::{channel, Receiver};
use futures_util::future::{lazy, Future, FutureExt};
use tokio::task::LocalSet; use tokio::task::LocalSet;
use crate::arbiter::{Arbiter, SystemArbiter}; use crate::arbiter::{Arbiter, SystemArbiter};
@@ -65,16 +65,17 @@ impl Builder {
/// Function `f` get called within tokio runtime context. /// Function `f` get called within tokio runtime context.
pub fn run<F>(self, f: F) -> io::Result<()> pub fn run<F>(self, f: F) -> io::Result<()>
where where
F: FnOnce() + 'static, F: FnOnce(),
{ {
self.create_runtime(f).run() self.create_runtime(f).run()
} }
fn create_async_runtime(self, local: &LocalSet) -> AsyncSystemRunner { fn create_async_runtime(self, local: &LocalSet) -> AsyncSystemRunner {
let (stop_tx, stop) = channel(); let (stop_tx, stop) = channel();
let (sys_sender, sys_receiver) = unbounded(); let (sys_sender, sys_receiver) = unbounded_channel();
let system = System::construct(sys_sender, Arbiter::new_system(), self.stop_on_panic); let system =
System::construct(sys_sender, Arbiter::new_system(local), self.stop_on_panic);
// system arbiter // system arbiter
let arb = SystemArbiter::new(stop_tx, sys_receiver); let arb = SystemArbiter::new(stop_tx, sys_receiver);
@@ -87,21 +88,26 @@ impl Builder {
fn create_runtime<F>(self, f: F) -> SystemRunner fn create_runtime<F>(self, f: F) -> SystemRunner
where where
F: FnOnce() + 'static, F: FnOnce(),
{ {
let (stop_tx, stop) = channel(); let (stop_tx, stop) = channel();
let (sys_sender, sys_receiver) = unbounded(); let (sys_sender, sys_receiver) = unbounded_channel();
let system = System::construct(sys_sender, Arbiter::new_system(), self.stop_on_panic); let rt = Runtime::new().unwrap();
let system = System::construct(
sys_sender,
Arbiter::new_system(rt.local()),
self.stop_on_panic,
);
// system arbiter // system arbiter
let arb = SystemArbiter::new(stop_tx, sys_receiver); let arb = SystemArbiter::new(stop_tx, sys_receiver);
let mut rt = Runtime::new().unwrap();
rt.spawn(arb); rt.spawn(arb);
// init system arbiter and run configuration method // init system arbiter and run configuration method
rt.block_on(lazy(move |_| f())); rt.block_on(async { f() });
SystemRunner { rt, stop, system } SystemRunner { rt, stop, system }
} }
@@ -120,27 +126,21 @@ impl AsyncSystemRunner {
let AsyncSystemRunner { stop, .. } = self; let AsyncSystemRunner { stop, .. } = self;
// run loop // run loop
lazy(|_| { async {
Arbiter::run_system(None); match stop.await {
async { Ok(code) => {
let res = match stop.await { if code != 0 {
Ok(code) => { Err(io::Error::new(
if code != 0 { io::ErrorKind::Other,
Err(io::Error::new( format!("Non-zero exit code: {}", code),
io::ErrorKind::Other, ))
format!("Non-zero exit code: {}", code), } else {
)) Ok(())
} else {
Ok(())
}
} }
Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)), }
}; Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
Arbiter::stop_system();
return res;
} }
}) }
.flatten()
} }
} }
@@ -157,11 +157,10 @@ impl SystemRunner {
/// This function will start event loop and will finish once the /// This function will start event loop and will finish once the
/// `System::stop()` function is called. /// `System::stop()` function is called.
pub fn run(self) -> io::Result<()> { pub fn run(self) -> io::Result<()> {
let SystemRunner { mut rt, stop, .. } = self; let SystemRunner { rt, stop, .. } = self;
// run loop // run loop
Arbiter::run_system(Some(&rt)); match rt.block_on(stop) {
let result = match rt.block_on(stop) {
Ok(code) => { Ok(code) => {
if code != 0 { if code != 0 {
Err(io::Error::new( Err(io::Error::new(
@@ -173,19 +172,12 @@ impl SystemRunner {
} }
} }
Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)), Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
}; }
Arbiter::stop_system();
result
} }
/// Execute a future and wait for result. /// Execute a future and wait for result.
pub fn block_on<F, O>(&mut self, fut: F) -> O #[inline]
where pub fn block_on<F: Future>(&self, fut: F) -> F::Output {
F: Future<Output = O> + 'static, self.rt.block_on(fut)
{
Arbiter::run_system(Some(&self.rt));
let res = self.rt.block_on(fut);
Arbiter::stop_system();
res
} }
} }

View File

@@ -1,6 +1,11 @@
//! A runtime implementation that runs everything on the current thread. //! Tokio-based single-thread async runtime for the Actix ecosystem.
#![deny(rust_2018_idioms, warnings)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
use std::future::Future;
#[cfg(not(test))] // Work around for rust-lang/rust#62127 #[cfg(not(test))] // Work around for rust-lang/rust#62127
pub use actix_macros::{main, test}; pub use actix_macros::{main, test};
@@ -15,23 +20,17 @@ pub use self::builder::{Builder, SystemRunner};
pub use self::runtime::Runtime; pub use self::runtime::Runtime;
pub use self::system::System; pub use self::system::System;
#[doc(hidden)]
pub use actix_threadpool as blocking;
/// Spawns a future on the current arbiter. /// Spawns a future on the current arbiter.
/// ///
/// # Panics /// # Panics
/// ///
/// This function panics if actix system is not running. /// This function panics if actix system is not running.
#[inline]
pub fn spawn<F>(f: F) pub fn spawn<F>(f: F)
where where
F: futures_util::future::Future<Output = ()> + 'static, F: Future<Output = ()> + 'static,
{ {
if !System::is_set() { Arbiter::spawn(f)
panic!("System is not running");
}
Arbiter::spawn(f);
} }
/// Asynchronous signal handling /// Asynchronous signal handling
@@ -60,7 +59,7 @@ pub mod net {
/// Utilities for tracking time. /// Utilities for tracking time.
pub mod time { pub mod time {
pub use tokio::time::Instant; pub use tokio::time::Instant;
pub use tokio::time::{delay_for, delay_until, Delay};
pub use tokio::time::{interval, interval_at, Interval}; pub use tokio::time::{interval, interval_at, Interval};
pub use tokio::time::{sleep, sleep_until, Sleep};
pub use tokio::time::{timeout, Timeout}; pub use tokio::time::{timeout, Timeout};
} }

View File

@@ -7,7 +7,7 @@ use tokio::{runtime, task::LocalSet};
/// ///
/// See [module level][mod] documentation for more details. /// See [module level][mod] documentation for more details.
/// ///
/// [mod]: index.html /// [mod]: crate
#[derive(Debug)] #[derive(Debug)]
pub struct Runtime { pub struct Runtime {
local: LocalSet, local: LocalSet,
@@ -18,10 +18,9 @@ impl Runtime {
#[allow(clippy::new_ret_no_self)] #[allow(clippy::new_ret_no_self)]
/// Returns a new runtime initialized with default configuration values. /// Returns a new runtime initialized with default configuration values.
pub fn new() -> io::Result<Runtime> { pub fn new() -> io::Result<Runtime> {
let rt = runtime::Builder::new() let rt = runtime::Builder::new_current_thread()
.enable_io() .enable_io()
.enable_time() .enable_time()
.basic_scheduler()
.build()?; .build()?;
Ok(Runtime { Ok(Runtime {
@@ -30,11 +29,15 @@ impl Runtime {
}) })
} }
pub(super) fn local(&self) -> &LocalSet {
&self.local
}
/// Spawn a future onto the single-threaded runtime. /// Spawn a future onto the single-threaded runtime.
/// ///
/// See [module level][mod] documentation for more details. /// See [module level][mod] documentation for more details.
/// ///
/// [mod]: index.html /// [mod]: crate
/// ///
/// # Examples /// # Examples
/// ///
@@ -44,7 +47,7 @@ impl Runtime {
/// ///
/// # fn dox() { /// # fn dox() {
/// // Create the runtime /// // Create the runtime
/// let mut rt = Runtime::new().unwrap(); /// let rt = Runtime::new().unwrap();
/// ///
/// // Spawn a future onto the runtime /// // Spawn a future onto the runtime
/// rt.spawn(future::lazy(|_| { /// rt.spawn(future::lazy(|_| {
@@ -82,10 +85,10 @@ impl Runtime {
/// ///
/// The caller is responsible for ensuring that other spawned futures /// The caller is responsible for ensuring that other spawned futures
/// complete execution by calling `block_on` or `run`. /// complete execution by calling `block_on` or `run`.
pub fn block_on<F>(&mut self, f: F) -> F::Output pub fn block_on<F>(&self, f: F) -> F::Output
where where
F: Future + 'static, F: Future,
{ {
self.local.block_on(&mut self.rt, f) self.local.block_on(&self.rt, f)
} }
} }

View File

@@ -3,7 +3,7 @@ use std::future::Future;
use std::io; use std::io;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use futures_channel::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use tokio::task::LocalSet; use tokio::task::LocalSet;
use crate::arbiter::{Arbiter, SystemCommand}; use crate::arbiter::{Arbiter, SystemCommand};
@@ -57,10 +57,57 @@ impl System {
Self::builder().name(name).build() Self::builder().name(name).build()
} }
#[allow(clippy::new_ret_no_self)] /// Create new system using provided tokio `LocalSet`.
/// Create new system using provided tokio Handle.
/// ///
/// This method panics if it can not spawn system arbiter /// This method panics if it can not spawn system arbiter
///
/// Note: This method uses provided `LocalSet` to create a `System` future only.
/// All the [`Arbiter`]s will be started in separate threads using their own tokio `Runtime`s.
/// It means that using this method currently it is impossible to make `actix-rt` work in the
/// alternative `tokio` `Runtime`s (e.g. provided by [`tokio_compat`]).
///
/// [`tokio_compat`]: https://crates.io/crates/tokio-compat
///
/// # Examples
///
/// ```rust,ignore
/// use tokio::{runtime::Runtime, task::LocalSet};
/// use actix_rt::System;
/// use futures_util::future::try_join_all;
///
/// async fn run_application() {
/// let first_task = tokio::spawn(async {
/// // ...
/// # println!("One task");
/// # Ok::<(),()>(())
/// });
///
/// let second_task = tokio::spawn(async {
/// // ...
/// # println!("Another task");
/// # Ok::<(),()>(())
/// });
///
/// try_join_all(vec![first_task, second_task])
/// .await
/// .expect("Some of the futures finished unexpectedly");
/// }
///
///
/// let runtime = tokio::runtime::Builder::new_multi_thread()
/// .worker_threads(2)
/// .enable_all()
/// .build()
/// .unwrap();
///
///
/// let actix_system_task = LocalSet::new();
/// let sys = System::run_in_tokio("actix-main-system", &actix_system_task);
/// actix_system_task.spawn_local(sys);
///
/// let rest_operations = run_application();
/// runtime.block_on(actix_system_task.run_until(rest_operations));
/// ```
pub fn run_in_tokio<T: Into<String>>( pub fn run_in_tokio<T: Into<String>>(
name: T, name: T,
local: &LocalSet, local: &LocalSet,
@@ -71,6 +118,75 @@ impl System {
.run_nonblocking() .run_nonblocking()
} }
/// Consume the provided tokio Runtime and start the `System` in it.
/// This method will create a `LocalSet` object and occupy the current thread
/// for the created `System` exclusively. All the other asynchronous tasks that
/// should be executed as well must be aggregated into one future, provided as the last
/// argument to this method.
///
/// Note: This method uses provided `Runtime` to create a `System` future only.
/// All the [`Arbiter`]s will be started in separate threads using their own tokio `Runtime`s.
/// It means that using this method currently it is impossible to make `actix-rt` work in the
/// alternative `tokio` `Runtime`s (e.g. provided by `tokio_compat`).
///
/// [`tokio_compat`]: https://crates.io/crates/tokio-compat
///
/// # Arguments
///
/// - `name`: Name of the System
/// - `runtime`: A tokio Runtime to run the system in.
/// - `rest_operations`: A future to be executed in the runtime along with the System.
///
/// # Examples
///
/// ```rust,ignore
/// use tokio::runtime::Runtime;
/// use actix_rt::System;
/// use futures_util::future::try_join_all;
///
/// async fn run_application() {
/// let first_task = tokio::spawn(async {
/// // ...
/// # println!("One task");
/// # Ok::<(),()>(())
/// });
///
/// let second_task = tokio::spawn(async {
/// // ...
/// # println!("Another task");
/// # Ok::<(),()>(())
/// });
///
/// try_join_all(vec![first_task, second_task])
/// .await
/// .expect("Some of the futures finished unexpectedly");
/// }
///
///
/// let runtime = tokio::runtime::Builder::new_multi_thread()
/// .worker_threads(2)
/// .enable_all()
/// .build()
/// .unwrap();
///
/// let rest_operations = run_application();
/// System::attach_to_tokio("actix-main-system", runtime, rest_operations);
/// ```
pub fn attach_to_tokio<Fut, R>(
name: impl Into<String>,
runtime: tokio::runtime::Runtime,
rest_operations: Fut,
) -> R
where
Fut: std::future::Future<Output = R>,
{
let actix_system_task = LocalSet::new();
let sys = System::run_in_tokio(name.into(), &actix_system_task);
actix_system_task.spawn_local(sys);
runtime.block_on(actix_system_task.run_until(rest_operations))
}
/// Get current running system. /// Get current running system.
pub fn current() -> System { pub fn current() -> System {
CURRENT.with(|cell| match *cell.borrow() { CURRENT.with(|cell| match *cell.borrow() {
@@ -115,7 +231,7 @@ impl System {
/// Stop the system with a particular exit code. /// Stop the system with a particular exit code.
pub fn stop_with_code(&self, code: i32) { pub fn stop_with_code(&self, code: i32) {
let _ = self.sys.unbounded_send(SystemCommand::Exit(code)); let _ = self.sys.send(SystemCommand::Exit(code));
} }
pub(crate) fn sys(&self) -> &UnboundedSender<SystemCommand> { pub(crate) fn sys(&self) -> &UnboundedSender<SystemCommand> {
@@ -138,7 +254,7 @@ impl System {
/// Function `f` get called within tokio runtime context. /// Function `f` get called within tokio runtime context.
pub fn run<F>(f: F) -> io::Result<()> pub fn run<F>(f: F) -> io::Result<()>
where where
F: FnOnce() + 'static, F: FnOnce(),
{ {
Self::builder().run(f) Self::builder().run(f)
} }

View File

@@ -1,25 +1,11 @@
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
#[test]
fn start_and_stop() {
actix_rt::System::new("start_and_stop").block_on(async move {
assert!(
actix_rt::Arbiter::is_running(),
"System doesn't seem to have started"
);
});
assert!(
!actix_rt::Arbiter::is_running(),
"System doesn't seem to have stopped"
);
}
#[test] #[test]
fn await_for_timer() { fn await_for_timer() {
let time = Duration::from_secs(2); let time = Duration::from_secs(2);
let instant = Instant::now(); let instant = Instant::now();
actix_rt::System::new("test_wait_timer").block_on(async move { actix_rt::System::new("test_wait_timer").block_on(async move {
tokio::time::delay_for(time).await; tokio::time::sleep(time).await;
}); });
assert!( assert!(
instant.elapsed() >= time, instant.elapsed() >= time,
@@ -34,7 +20,7 @@ fn join_another_arbiter() {
actix_rt::System::new("test_join_another_arbiter").block_on(async move { actix_rt::System::new("test_join_another_arbiter").block_on(async move {
let mut arbiter = actix_rt::Arbiter::new(); let mut arbiter = actix_rt::Arbiter::new();
arbiter.send(Box::pin(async move { arbiter.send(Box::pin(async move {
tokio::time::delay_for(time).await; tokio::time::sleep(time).await;
actix_rt::Arbiter::current().stop(); actix_rt::Arbiter::current().stop();
})); }));
arbiter.join().unwrap(); arbiter.join().unwrap();
@@ -49,7 +35,7 @@ fn join_another_arbiter() {
let mut arbiter = actix_rt::Arbiter::new(); let mut arbiter = actix_rt::Arbiter::new();
arbiter.exec_fn(move || { arbiter.exec_fn(move || {
actix_rt::spawn(async move { actix_rt::spawn(async move {
tokio::time::delay_for(time).await; tokio::time::sleep(time).await;
actix_rt::Arbiter::current().stop(); actix_rt::Arbiter::current().stop();
}); });
}); });
@@ -64,7 +50,7 @@ fn join_another_arbiter() {
actix_rt::System::new("test_join_another_arbiter").block_on(async move { actix_rt::System::new("test_join_another_arbiter").block_on(async move {
let mut arbiter = actix_rt::Arbiter::new(); let mut arbiter = actix_rt::Arbiter::new();
arbiter.send(Box::pin(async move { arbiter.send(Box::pin(async move {
tokio::time::delay_for(time).await; tokio::time::sleep(time).await;
actix_rt::Arbiter::current().stop(); actix_rt::Arbiter::current().stop();
})); }));
arbiter.stop(); arbiter.stop();
@@ -76,39 +62,65 @@ fn join_another_arbiter() {
); );
} }
// #[test]
// fn join_current_arbiter() {
// let time = Duration::from_secs(2);
//
// let instant = Instant::now();
// actix_rt::System::new("test_join_current_arbiter").block_on(async move {
// actix_rt::spawn(async move {
// tokio::time::delay_for(time).await;
// actix_rt::Arbiter::current().stop();
// });
// actix_rt::Arbiter::local_join().await;
// });
// assert!(
// instant.elapsed() >= time,
// "Join on current arbiter should wait for all spawned futures"
// );
//
// let large_timer = Duration::from_secs(20);
// let instant = Instant::now();
// actix_rt::System::new("test_join_current_arbiter").block_on(async move {
// actix_rt::spawn(async move {
// tokio::time::delay_for(time).await;
// actix_rt::Arbiter::current().stop();
// });
// let f = actix_rt::Arbiter::local_join();
// actix_rt::spawn(async move {
// tokio::time::delay_for(large_timer).await;
// actix_rt::Arbiter::current().stop();
// });
// f.await;
// });
// assert!(
// instant.elapsed() < large_timer,
// "local_join should await only for the already spawned futures"
// );
// }
#[test] #[test]
fn join_current_arbiter() { fn non_static_block_on() {
let time = Duration::from_secs(2); let string = String::from("test_str");
let str = string.as_str();
let instant = Instant::now(); let sys = actix_rt::System::new("borrow some");
actix_rt::System::new("test_join_current_arbiter").block_on(async move {
actix_rt::spawn(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
});
actix_rt::Arbiter::local_join().await;
});
assert!(
instant.elapsed() >= time,
"Join on current arbiter should wait for all spawned futures"
);
let large_timer = Duration::from_secs(20); sys.block_on(async {
let instant = Instant::now(); actix_rt::time::sleep(Duration::from_millis(1)).await;
actix_rt::System::new("test_join_current_arbiter").block_on(async move { assert_eq!("test_str", str);
actix_rt::spawn(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
});
let f = actix_rt::Arbiter::local_join();
actix_rt::spawn(async move {
tokio::time::delay_for(large_timer).await;
actix_rt::Arbiter::current().stop();
});
f.await;
}); });
assert!(
instant.elapsed() < large_timer, let rt = actix_rt::Runtime::new().unwrap();
"local_join should await only for the already spawned futures"
); rt.block_on(async {
actix_rt::time::sleep(Duration::from_millis(1)).await;
assert_eq!("test_str", str);
});
actix_rt::System::run(|| {
assert_eq!("test_str", str);
actix_rt::System::current().stop();
})
.unwrap();
} }

View File

@@ -1,202 +1,133 @@
# Changes # Changes
## [1.0.2] - 2020-02-26 ## Unreleased - 2020-xx-xx
* Added explicit info log message on accept queue pause. [#215]
* Prevent double registration of sockets when back-pressure is resolved. [#223]
### Fixed [#215]: https://github.com/actix/actix-net/pull/215
[#223]: https://github.com/actix/actix-net/pull/223
## 1.0.4 - 2020-09-12
* Update actix-codec to 0.3.0.
* Workers must be greater than 0. [#167]
[#167]: https://github.com/actix/actix-net/pull/167
## 1.0.3 - 2020-05-19
* Replace deprecated `net2` crate with `socket2` [#140]
[#140]: https://github.com/actix/actix-net/pull/140
## 1.0.2 - 2020-02-26
* Avoid error by calling `reregister()` on Windows [#103] * Avoid error by calling `reregister()` on Windows [#103]
[#103]: https://github.com/actix/actix-net/pull/103 [#103]: https://github.com/actix/actix-net/pull/103
## [1.0.1] - 2019-12-29
### Changed
## 1.0.1 - 2019-12-29
* Rename `.start()` method to `.run()` * Rename `.start()` method to `.run()`
## [1.0.0] - 2019-12-11
### Changed
## 1.0.0 - 2019-12-11
* Use actix-net releases * Use actix-net releases
## [1.0.0-alpha.4] - 2019-12-08 ## 1.0.0-alpha.4 - 2019-12-08
### Changed
* Use actix-service 1.0.0-alpha.4 * Use actix-service 1.0.0-alpha.4
## [1.0.0-alpha.3] - 2019-12-07
### Changed
## 1.0.0-alpha.3 - 2019-12-07
* Migrate to tokio 0.2 * Migrate to tokio 0.2
### Fixed
* Fix compilation on non-unix platforms * Fix compilation on non-unix platforms
* Better handling server configuration * Better handling server configuration
## [1.0.0-alpha.2] - 2019-12-02 ## 1.0.0-alpha.2 - 2019-12-02
### Changed
* Simplify server service (remove actix-server-config) * Simplify server service (remove actix-server-config)
* Allow to wait on `Server` until server stops * Allow to wait on `Server` until server stops
## [0.8.0-alpha.1] - 2019-11-22 ## 0.8.0-alpha.1 - 2019-11-22
### Changed
* Migrate to `std::future` * Migrate to `std::future`
## [0.7.0] - 2019-10-04 ## 0.7.0 - 2019-10-04
### Changed
* Update `rustls` to 0.16 * Update `rustls` to 0.16
* Minimum required Rust version upped to 1.37.0 * Minimum required Rust version upped to 1.37.0
## [0.6.1] - 2019-09-25 ## 0.6.1 - 2019-09-25
### Added
* Add UDS listening support to `ServerBuilder` * Add UDS listening support to `ServerBuilder`
## [0.6.0] - 2019-07-18 ## 0.6.0 - 2019-07-18
### Added
* Support Unix domain sockets #3 * Support Unix domain sockets #3
## [0.5.1] - 2019-05-18 ## 0.5.1 - 2019-05-18
### Changed
* ServerBuilder::shutdown_timeout() accepts u64 * ServerBuilder::shutdown_timeout() accepts u64
## [0.5.0] - 2019-05-12 ## 0.5.0 - 2019-05-12
### Added
* Add `Debug` impl for `SslError` * Add `Debug` impl for `SslError`
* Derive debug for `Server` and `ServerCommand` * Derive debug for `Server` and `ServerCommand`
### Changed
* Upgrade to actix-service 0.4 * Upgrade to actix-service 0.4
## [0.4.3] - 2019-04-16 ## 0.4.3 - 2019-04-16
### Added
* Re-export `IoStream` trait * Re-export `IoStream` trait
* Depend on `ssl` and `rust-tls` features from actix-server-config
### Changed
* Deppend on `ssl` and `rust-tls` features from actix-server-config
## [0.4.2] - 2019-03-30 ## 0.4.2 - 2019-03-30
### Fixed
* Fix SIGINT force shutdown * Fix SIGINT force shutdown
## [0.4.1] - 2019-03-14 ## 0.4.1 - 2019-03-14
### Added
* `SystemRuntime::on_start()` - allow to run future before server service initialization * `SystemRuntime::on_start()` - allow to run future before server service initialization
## [0.4.0] - 2019-03-12 ## 0.4.0 - 2019-03-12
### Changed
* Use `ServerConfig` for service factory * Use `ServerConfig` for service factory
* Wrap tcp socket to `Io` type * Wrap tcp socket to `Io` type
* Upgrade actix-service * Upgrade actix-service
## [0.3.1] - 2019-03-04 ## 0.3.1 - 2019-03-04
### Added
* Add `ServerBuilder::maxconnrate` sets the maximum per-worker number of concurrent connections * Add `ServerBuilder::maxconnrate` sets the maximum per-worker number of concurrent connections
* Add helper ssl error `SslError` * Add helper ssl error `SslError`
### Changed
* Rename `StreamServiceFactory` to `ServiceFactory` * Rename `StreamServiceFactory` to `ServiceFactory`
* Deprecate `StreamServiceFactory` * Deprecate `StreamServiceFactory`
## [0.3.0] - 2019-03-02 ## 0.3.0 - 2019-03-02
### Changed
* Use new `NewService` trait * Use new `NewService` trait
## [0.2.1] - 2019-02-09 ## 0.2.1 - 2019-02-09
### Changed
* Drop service response * Drop service response
## [0.2.0] - 2019-02-01 ## 0.2.0 - 2019-02-01
### Changed
* Migrate to actix-service 0.2 * Migrate to actix-service 0.2
* Updated rustls dependency * Updated rustls dependency
## [0.1.3] - 2018-12-21 ## 0.1.3 - 2018-12-21
### Fixed
* Fix max concurrent connections handling * Fix max concurrent connections handling
## [0.1.2] - 2018-12-12 ## 0.1.2 - 2018-12-12
### Changed
* rename ServiceConfig::rt() to ServiceConfig::apply() * rename ServiceConfig::rt() to ServiceConfig::apply()
### Fixed
* Fix back-pressure for concurrent ssl handshakes * Fix back-pressure for concurrent ssl handshakes
## [0.1.1] - 2018-12-11 ## 0.1.1 - 2018-12-11
* Fix signal handling on windows * Fix signal handling on windows
## [0.1.0] - 2018-12-09 ## 0.1.0 - 2018-12-09
* Move server to separate crate * Move server to separate crate

View File

@@ -1,17 +1,16 @@
[package] [package]
name = "actix-server" name = "actix-server"
version = "1.0.2" version = "1.0.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix server - General purpose tcp server" description = "General purpose TCP server built for the Actix ecosystem"
keywords = ["network", "framework", "async", "futures"] keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-server/" documentation = "https://docs.rs/actix-server/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] exclude = [".gitignore", ".cargo/config"]
edition = "2018" edition = "2018"
workspace = ".."
[lib] [lib]
name = "actix_server" name = "actix_server"
@@ -21,15 +20,15 @@ path = "src/lib.rs"
default = [] default = []
[dependencies] [dependencies]
actix-service = "1.0.1" actix-service = "1.0.6"
actix-rt = "1.0.0" actix-rt = "1.1.1"
actix-codec = "0.2.0" actix-codec = "0.3.0"
actix-utils = "1.0.4" actix-utils = "2.0.0"
log = "0.4" log = "0.4"
num_cpus = "1.11" num_cpus = "1.13"
mio = "0.6.19" mio = "0.6.19"
net2 = "0.2" socket2 = "0.3"
futures-channel = { version = "0.3.4", default-features = false } futures-channel = { version = "0.3.4", default-features = false }
futures-util = { version = "0.3.4", default-features = false, features = ["sink"] } futures-util = { version = "0.3.4", default-features = false, features = ["sink"] }
slab = "0.4" slab = "0.4"
@@ -42,3 +41,4 @@ mio-uds = { version = "0.6.7" }
bytes = "0.5" bytes = "0.5"
env_logger = "0.7" env_logger = "0.7"
actix-testing = "1.0.0" actix-testing = "1.0.0"
tokio = { version = "0.2", features = ["io-util"] }

View File

@@ -0,0 +1,88 @@
//! Simple composite-service TCP echo server.
//!
//! Using the following command:
//!
//! ```sh
//! nc 127.0.0.1 8080
//! ```
//!
//! Start typing. When you press enter the typed line will be echoed back. The server will log
//! the length of each line it echos and the total size of data sent when the connection is closed.
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use std::{env, io};
use actix_rt::net::TcpStream;
use actix_server::Server;
use actix_service::pipeline_factory;
use bytes::BytesMut;
use futures_util::future::ok;
use log::{error, info};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[actix_rt::main]
async fn main() -> io::Result<()> {
env::set_var("RUST_LOG", "actix=trace,basic=trace");
env_logger::init();
let count = Arc::new(AtomicUsize::new(0));
let addr = ("127.0.0.1", 8080);
info!("starting server on port: {}", &addr.0);
// Bind socket address and start worker(s). By default, the server uses the number of available
// logical CPU cores as the worker count. For this reason, the closure passed to bind needs
// to return a service *factory*; so it can be created once per worker.
Server::build()
.bind("echo", addr, move || {
let count = Arc::clone(&count);
let num2 = Arc::clone(&count);
pipeline_factory(move |mut stream: TcpStream| {
let count = Arc::clone(&count);
async move {
let num = count.fetch_add(1, Ordering::SeqCst);
let num = num + 1;
let mut size = 0;
let mut buf = BytesMut::new();
loop {
match stream.read_buf(&mut buf).await {
// end of stream; bail from loop
Ok(0) => break,
// more bytes to process
Ok(bytes_read) => {
info!("[{}] read {} bytes", num, bytes_read);
stream.write_all(&buf[size..]).await.unwrap();
size += bytes_read;
}
// stream error; bail from loop with error
Err(err) => {
error!("Stream Error: {:?}", err);
return Err(());
}
}
}
// send data down service pipeline
Ok((buf.freeze(), size))
}
})
.map_err(|err| error!("Service Error: {:?}", err))
.and_then(move |(_, size)| {
let num = num2.load(Ordering::SeqCst);
info!("[{}] total bytes read: {}", num, size);
ok(size)
})
})?
.workers(1)
.run()
.await
}

View File

@@ -370,6 +370,11 @@ impl Accept {
if !on { if !on {
self.backpressure = false; self.backpressure = false;
for (token, info) in self.sockets.iter() { for (token, info) in self.sockets.iter() {
if info.timeout.is_some() {
// socket will attempt to re-register itself when its timeout completes
continue;
}
if let Err(err) = self.register(token, info) { if let Err(err) = self.register(token, info) {
error!("Can not resume socket accept process: {}", err); error!("Can not resume socket accept process: {}", err);
} else { } else {
@@ -381,6 +386,7 @@ impl Accept {
self.backpressure = true; self.backpressure = true;
for (_, info) in self.sockets.iter() { for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock); let _ = self.poll.deregister(&info.sock);
info!("Accepting connections on {} has been paused", info.addr);
} }
} }
} }

View File

@@ -10,9 +10,9 @@ use futures_channel::mpsc::{unbounded, UnboundedReceiver};
use futures_channel::oneshot; use futures_channel::oneshot;
use futures_util::future::ready; use futures_util::future::ready;
use futures_util::stream::FuturesUnordered; use futures_util::stream::FuturesUnordered;
use futures_util::{ready, future::Future, FutureExt, stream::Stream, StreamExt}; use futures_util::{future::Future, ready, stream::Stream, FutureExt, StreamExt};
use log::{error, info}; use log::{error, info};
use net2::TcpBuilder; use socket2::{Domain, Protocol, Socket, Type};
use crate::accept::{AcceptLoop, AcceptNotify, Command}; use crate::accept::{AcceptLoop, AcceptNotify, Command};
use crate::config::{ConfiguredService, ServiceConfig}; use crate::config::{ConfiguredService, ServiceConfig};
@@ -72,8 +72,9 @@ impl ServerBuilder {
/// Set number of workers to start. /// Set number of workers to start.
/// ///
/// By default server uses number of available logical cpu as workers /// By default server uses number of available logical cpu as workers
/// count. /// count. Workers must be greater than 0.
pub fn workers(mut self, num: usize) -> Self { pub fn workers(mut self, num: usize) -> Self {
assert_ne!(num, 0, "workers must be greater than 0");
self.threads = num; self.threads = num;
self self
} }
@@ -276,7 +277,7 @@ impl ServerBuilder {
info!("Starting \"{}\" service on {}", sock.1, sock.2); info!("Starting \"{}\" service on {}", sock.1, sock.2);
} }
self.accept.start( self.accept.start(
mem::replace(&mut self.sockets, Vec::new()) mem::take(&mut self.sockets)
.into_iter() .into_iter()
.map(|t| (t.0, t.2)) .map(|t| (t.0, t.2))
.collect(), .collect(),
@@ -285,7 +286,7 @@ impl ServerBuilder {
// handle signals // handle signals
if !self.no_signals { if !self.no_signals {
Signals::start(self.server.clone()).unwrap(); Signals::start(self.server.clone());
} }
// start http server actor // start http server actor
@@ -355,7 +356,7 @@ impl ServerBuilder {
// stop accept thread // stop accept thread
self.accept.send(Command::Stop); self.accept.send(Command::Stop);
let notify = std::mem::replace(&mut self.notify, Vec::new()); let notify = std::mem::take(&mut self.notify);
// stop workers // stop workers
if !self.workers.is_empty() && graceful { if !self.workers.is_empty() && graceful {
@@ -381,7 +382,7 @@ impl ServerBuilder {
.await; .await;
System::current().stop(); System::current().stop();
} }
.boxed(), .boxed(),
); );
} }
ready(()) ready(())
@@ -487,11 +488,13 @@ pub(super) fn bind_addr<S: net::ToSocketAddrs>(
} }
fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result<net::TcpListener> { fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result<net::TcpListener> {
let builder = match addr { let domain = match addr {
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?, net::SocketAddr::V4(_) => Domain::ipv4(),
net::SocketAddr::V6(_) => TcpBuilder::new_v6()?, net::SocketAddr::V6(_) => Domain::ipv6(),
}; };
builder.reuse_address(true)?; let socket = Socket::new(domain, Type::stream(), Some(Protocol::tcp()))?;
builder.bind(addr)?; socket.set_reuse_address(true)?;
Ok(builder.listen(backlog)?) socket.bind(&addr.into())?;
socket.listen(backlog)?;
Ok(socket.into_tcp_listener())
} }

View File

@@ -2,16 +2,18 @@ use std::collections::HashMap;
use std::{fmt, io, net}; use std::{fmt, io, net};
use actix_rt::net::TcpStream; use actix_rt::net::TcpStream;
use actix_service as actix; use actix_service::{
fn_service, IntoServiceFactory as IntoBaseServiceFactory,
ServiceFactory as BaseServiceFactory,
};
use actix_utils::counter::CounterGuard; use actix_utils::counter::CounterGuard;
use futures_util::future::{ok, Future, FutureExt, LocalBoxFuture}; use futures_util::future::{ok, Future, FutureExt, LocalBoxFuture};
use log::error; use log::error;
use super::builder::bind_addr; use super::builder::bind_addr;
use super::service::{ use super::service::{BoxedServerService, InternalServiceFactory, StreamService};
BoxedServerService, InternalServiceFactory, ServerMessage, StreamService,
};
use super::Token; use super::Token;
use crate::socket::StdStream;
pub struct ServiceConfig { pub struct ServiceConfig {
pub(crate) services: Vec<(String, net::TcpListener)>, pub(crate) services: Vec<(String, net::TcpListener)>,
@@ -142,16 +144,14 @@ impl InternalServiceFactory for ConfiguredService {
let name = names.remove(&token).unwrap().0; let name = names.remove(&token).unwrap().0;
res.push(( res.push((
token, token,
Box::new(StreamService::new(actix::fn_service( Box::new(StreamService::new(fn_service(move |_: TcpStream| {
move |_: TcpStream| { error!("Service {:?} is not configured", name);
error!("Service {:?} is not configured", name); ok::<_, ()>(())
ok::<_, ()>(()) }))),
},
))),
)); ));
}; };
} }
return Ok(res); Ok(res)
} }
.boxed_local() .boxed_local()
} }
@@ -209,8 +209,8 @@ impl ServiceRuntime {
/// *ServiceConfig::bind()* or *ServiceConfig::listen()* methods. /// *ServiceConfig::bind()* or *ServiceConfig::listen()* methods.
pub fn service<T, F>(&mut self, name: &str, service: F) pub fn service<T, F>(&mut self, name: &str, service: F)
where where
F: actix::IntoServiceFactory<T>, F: IntoBaseServiceFactory<T, TcpStream>,
T: actix::ServiceFactory<Config = (), Request = TcpStream> + 'static, T: BaseServiceFactory<TcpStream, Config = ()> + 'static,
T::Future: 'static, T::Future: 'static,
T::Service: 'static, T::Service: 'static,
T::InitError: fmt::Debug, T::InitError: fmt::Debug,
@@ -218,7 +218,7 @@ impl ServiceRuntime {
// let name = name.to_owned(); // let name = name.to_owned();
if let Some(token) = self.names.get(name) { if let Some(token) = self.names.get(name) {
self.services.insert( self.services.insert(
token.clone(), *token,
Box::new(ServiceFactory { Box::new(ServiceFactory {
inner: service.into_factory(), inner: service.into_factory(),
}), }),
@@ -238,8 +238,8 @@ impl ServiceRuntime {
} }
type BoxedNewService = Box< type BoxedNewService = Box<
dyn actix::ServiceFactory< dyn BaseServiceFactory<
Request = (Option<CounterGuard>, ServerMessage), (Option<CounterGuard>, StdStream),
Response = (), Response = (),
Error = (), Error = (),
InitError = (), InitError = (),
@@ -253,32 +253,31 @@ struct ServiceFactory<T> {
inner: T, inner: T,
} }
impl<T> actix::ServiceFactory for ServiceFactory<T> impl<T> BaseServiceFactory<(Option<CounterGuard>, StdStream)> for ServiceFactory<T>
where where
T: actix::ServiceFactory<Config = (), Request = TcpStream>, T: BaseServiceFactory<TcpStream, Config = ()>,
T::Future: 'static, T::Future: 'static,
T::Service: 'static, T::Service: 'static,
T::Error: 'static, T::Error: 'static,
T::InitError: fmt::Debug + 'static, T::InitError: fmt::Debug + 'static,
{ {
type Request = (Option<CounterGuard>, ServerMessage);
type Response = (); type Response = ();
type Error = (); type Error = ();
type InitError = ();
type Config = (); type Config = ();
type Service = BoxedServerService; type Service = BoxedServerService;
type InitError = ();
type Future = LocalBoxFuture<'static, Result<BoxedServerService, ()>>; type Future = LocalBoxFuture<'static, Result<BoxedServerService, ()>>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
let fut = self.inner.new_service(()); let fut = self.inner.new_service(());
async move { async move {
return match fut.await { match fut.await {
Ok(s) => Ok(Box::new(StreamService::new(s)) as BoxedServerService), Ok(s) => Ok(Box::new(StreamService::new(s)) as BoxedServerService),
Err(e) => { Err(e) => {
error!("Can not construct service: {:?}", e); error!("Can not construct service: {:?}", e);
Err(()) Err(())
} }
}; }
} }
.boxed_local() .boxed_local()
} }

View File

@@ -1,6 +1,8 @@
//! General purpose tcp server //! General purpose TCP server.
#![deny(rust_2018_idioms, warnings)]
#![allow(clippy::type_complexity)] #![deny(rust_2018_idioms, nonstandard_style)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
mod accept; mod accept;
mod builder; mod builder;
@@ -19,7 +21,7 @@ pub use self::service::ServiceFactory;
#[doc(hidden)] #[doc(hidden)]
pub use self::socket::FromStream; pub use self::socket::FromStream;
/// Socket id token /// Socket ID token
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct Token(usize); pub(crate) struct Token(usize);

View File

@@ -1,10 +1,9 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::time::Duration;
use actix_rt::spawn; use actix_rt::spawn;
use actix_service::{self as actix, Service, ServiceFactory as ActixServiceFactory}; use actix_service::{Service, ServiceFactory as BaseServiceFactory};
use actix_utils::counter::CounterGuard; use actix_utils::counter::CounterGuard;
use futures_util::future::{err, ok, LocalBoxFuture, Ready}; use futures_util::future::{err, ok, LocalBoxFuture, Ready};
use futures_util::{FutureExt, TryFutureExt}; use futures_util::{FutureExt, TryFutureExt};
@@ -13,18 +12,8 @@ use log::error;
use super::Token; use super::Token;
use crate::socket::{FromStream, StdStream}; use crate::socket::{FromStream, StdStream};
/// Server message
pub(crate) enum ServerMessage {
/// New stream
Connect(StdStream),
/// Gracefull shutdown
Shutdown(Duration),
/// Force shutdown
ForceShutdown,
}
pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static { pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static {
type Factory: actix::ServiceFactory<Config = (), Request = Stream>; type Factory: BaseServiceFactory<Stream, Config = ()>;
fn create(&self) -> Self::Factory; fn create(&self) -> Self::Factory;
} }
@@ -39,31 +28,34 @@ pub(crate) trait InternalServiceFactory: Send {
pub(crate) type BoxedServerService = Box< pub(crate) type BoxedServerService = Box<
dyn Service< dyn Service<
Request = (Option<CounterGuard>, ServerMessage), (Option<CounterGuard>, StdStream),
Response = (), Response = (),
Error = (), Error = (),
Future = Ready<Result<(), ()>>, Future = Ready<Result<(), ()>>,
>, >,
>; >;
pub(crate) struct StreamService<T> { pub(crate) struct StreamService<S, I> {
service: T, service: S,
_phantom: PhantomData<I>,
} }
impl<T> StreamService<T> { impl<S, I> StreamService<S, I> {
pub(crate) fn new(service: T) -> Self { pub(crate) fn new(service: S) -> Self {
StreamService { service } StreamService {
service,
_phantom: PhantomData,
}
} }
} }
impl<T, I> Service for StreamService<T> impl<S, I> Service<(Option<CounterGuard>, StdStream)> for StreamService<S, I>
where where
T: Service<Request = I>, S: Service<I>,
T::Future: 'static, S::Future: 'static,
T::Error: 'static, S::Error: 'static,
I: FromStream, I: FromStream,
{ {
type Request = (Option<CounterGuard>, ServerMessage);
type Response = (); type Response = ();
type Error = (); type Error = ();
type Future = Ready<Result<(), ()>>; type Future = Ready<Result<(), ()>>;
@@ -72,25 +64,20 @@ where
self.service.poll_ready(ctx).map_err(|_| ()) self.service.poll_ready(ctx).map_err(|_| ())
} }
fn call(&mut self, (guard, req): (Option<CounterGuard>, ServerMessage)) -> Self::Future { fn call(&mut self, (guard, req): (Option<CounterGuard>, StdStream)) -> Self::Future {
match req { match FromStream::from_stdstream(req) {
ServerMessage::Connect(stream) => { Ok(stream) => {
let stream = FromStream::from_stdstream(stream).map_err(|e| { let f = self.service.call(stream);
error!("Can not convert to an async tcp stream: {}", e); spawn(async move {
let _ = f.await;
drop(guard);
}); });
ok(())
if let Ok(stream) = stream { }
let f = self.service.call(stream); Err(e) => {
spawn(async move { error!("Can not convert to an async tcp stream: {}", e);
let _ = f.await; err(())
drop(guard);
});
ok(())
} else {
err(())
}
} }
_ => ok(()),
} }
} }
} }
@@ -157,24 +144,10 @@ where
} }
} }
impl InternalServiceFactory for Box<dyn InternalServiceFactory> {
fn name(&self, token: Token) -> &str {
self.as_ref().name(token)
}
fn clone_factory(&self) -> Box<dyn InternalServiceFactory> {
self.as_ref().clone_factory()
}
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> {
self.as_ref().create()
}
}
impl<F, T, I> ServiceFactory<I> for F impl<F, T, I> ServiceFactory<I> for F
where where
F: Fn() -> T + Send + Clone + 'static, F: Fn() -> T + Send + Clone + 'static,
T: actix::ServiceFactory<Config = (), Request = I>, T: BaseServiceFactory<I, Config = ()>,
I: FromStream, I: FromStream,
{ {
type Factory = T; type Factory = T;

View File

@@ -1,5 +1,4 @@
use std::future::Future; use std::future::Future;
use std::io;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
@@ -24,13 +23,13 @@ pub(crate) enum Signal {
pub(crate) struct Signals { pub(crate) struct Signals {
srv: Server, srv: Server,
#[cfg(not(unix))] #[cfg(not(unix))]
stream: Pin<Box<dyn Future<Output = io::Result<()>>>>, stream: Pin<Box<dyn Future<Output = std::io::Result<()>>>>,
#[cfg(unix)] #[cfg(unix)]
streams: Vec<(Signal, actix_rt::signal::unix::Signal)>, streams: Vec<(Signal, actix_rt::signal::unix::Signal)>,
} }
impl Signals { impl Signals {
pub(crate) fn start(srv: Server) -> io::Result<()> { pub(crate) fn start(srv: Server) {
actix_rt::spawn(lazy(|_| { actix_rt::spawn(lazy(|_| {
#[cfg(not(unix))] #[cfg(not(unix))]
{ {
@@ -66,8 +65,6 @@ impl Signals {
actix_rt::spawn(Signals { srv, streams }) actix_rt::spawn(Signals { srv, streams })
} }
})); }));
Ok(())
} }
} }

View File

@@ -10,11 +10,11 @@ use actix_utils::counter::Counter;
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures_channel::oneshot; use futures_channel::oneshot;
use futures_util::future::{join_all, LocalBoxFuture, MapOk}; use futures_util::future::{join_all, LocalBoxFuture, MapOk};
use futures_util::{future::Future, FutureExt, stream::Stream, TryFutureExt}; use futures_util::{future::Future, stream::Stream, FutureExt, TryFutureExt};
use log::{error, info, trace}; use log::{error, info, trace};
use crate::accept::AcceptNotify; use crate::accept::AcceptNotify;
use crate::service::{BoxedServerService, InternalServiceFactory, ServerMessage}; use crate::service::{BoxedServerService, InternalServiceFactory};
use crate::socket::{SocketAddr, StdStream}; use crate::socket::{SocketAddr, StdStream};
use crate::Token; use crate::Token;
@@ -228,23 +228,12 @@ impl Worker {
self.services.iter_mut().for_each(|srv| { self.services.iter_mut().for_each(|srv| {
if srv.status == WorkerServiceStatus::Available { if srv.status == WorkerServiceStatus::Available {
srv.status = WorkerServiceStatus::Stopped; srv.status = WorkerServiceStatus::Stopped;
actix_rt::spawn(
srv.service
.call((None, ServerMessage::ForceShutdown))
.map(|_| ()),
);
} }
}); });
} else { } else {
let timeout = self.shutdown_timeout;
self.services.iter_mut().for_each(move |srv| { self.services.iter_mut().for_each(move |srv| {
if srv.status == WorkerServiceStatus::Available { if srv.status == WorkerServiceStatus::Available {
srv.status = WorkerServiceStatus::Stopping; srv.status = WorkerServiceStatus::Stopping;
actix_rt::spawn(
srv.service
.call((None, ServerMessage::Shutdown(timeout)))
.map(|_| ()),
);
} }
}); });
} }
@@ -303,6 +292,7 @@ enum WorkerState {
Restarting( Restarting(
usize, usize,
Token, Token,
#[allow(clippy::type_complexity)]
Pin<Box<dyn Future<Output = Result<Vec<(Token, BoxedServerService)>, ()>>>>, Pin<Box<dyn Future<Output = Result<Vec<(Token, BoxedServerService)>, ()>>>>,
), ),
Shutdown( Shutdown(
@@ -360,7 +350,7 @@ impl Future for Worker {
let guard = self.conns.get(); let guard = self.conns.get();
let _ = self.services[conn.token.0] let _ = self.services[conn.token.0]
.service .service
.call((Some(guard), ServerMessage::Connect(conn.io))); .call((Some(guard), conn.io));
} else { } else {
self.state = WorkerState::Available; self.state = WorkerState::Available;
self.availability.set(true); self.availability.set(true);
@@ -454,7 +444,7 @@ impl Future for Worker {
let guard = self.conns.get(); let guard = self.conns.get();
let _ = self.services[msg.token.0] let _ = self.services[msg.token.0]
.service .service
.call((Some(guard), ServerMessage::Connect(msg.io))); .call((Some(guard), msg.io));
continue; continue;
} }
Ok(false) => { Ok(false) => {

View File

@@ -5,14 +5,14 @@ use std::{net, thread, time};
use actix_server::Server; use actix_server::Server;
use actix_service::fn_service; use actix_service::fn_service;
use futures_util::future::{lazy, ok}; use futures_util::future::{lazy, ok};
use net2::TcpBuilder; use socket2::{Domain, Protocol, Socket, Type};
fn unused_addr() -> net::SocketAddr { fn unused_addr() -> net::SocketAddr {
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
let socket = TcpBuilder::new_v4().unwrap(); let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap();
socket.bind(&addr).unwrap(); socket.bind(&addr.into()).unwrap();
socket.reuse_address(true).unwrap(); socket.set_reuse_address(true).unwrap();
let tcp = socket.to_tcp_listener().unwrap(); let tcp = socket.into_tcp_listener();
tcp.local_addr().unwrap() tcp.local_addr().unwrap()
} }
@@ -22,13 +22,16 @@ fn test_bind() {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let h = thread::spawn(move || { let h = thread::spawn(move || {
let sys = actix_rt::System::new("test"); let mut sys = actix_rt::System::new("test");
let srv = Server::build()
.workers(1) let srv = sys.block_on(lazy(|_| {
.disable_signals() Server::build()
.bind("test", addr, move || fn_service(|_| ok::<_, ()>(()))) .workers(1)
.unwrap() .disable_signals()
.start(); .bind("test", addr, move || fn_service(|_| ok::<_, ()>(())))
.unwrap()
.start()
}));
let _ = tx.send((srv, actix_rt::System::current())); let _ = tx.send((srv, actix_rt::System::current()));
let _ = sys.run(); let _ = sys.run();
}); });
@@ -46,14 +49,16 @@ fn test_listen() {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let h = thread::spawn(move || { let h = thread::spawn(move || {
let sys = actix_rt::System::new("test"); let mut sys = actix_rt::System::new("test");
let lst = net::TcpListener::bind(addr).unwrap(); let lst = net::TcpListener::bind(addr).unwrap();
Server::build() sys.block_on(lazy(|_| {
.disable_signals() Server::build()
.workers(1) .disable_signals()
.listen("test", lst, move || fn_service(|_| ok::<_, ()>(()))) .workers(1)
.unwrap() .listen("test", lst, move || fn_service(|_| ok::<_, ()>(())))
.start(); .unwrap()
.start()
}));
let _ = tx.send(actix_rt::System::current()); let _ = tx.send(actix_rt::System::current());
let _ = sys.run(); let _ = sys.run();
}); });
@@ -78,21 +83,21 @@ fn test_start() {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let h = thread::spawn(move || { let h = thread::spawn(move || {
let sys = actix_rt::System::new("test"); let mut sys = actix_rt::System::new("test");
let srv: Server = Server::build() let srv = sys.block_on(lazy(|_| {
.backlog(100) Server::build()
.disable_signals() .backlog(100)
.bind("test", addr, move || { .disable_signals()
fn_service(|io: TcpStream| { .bind("test", addr, move || {
async move { fn_service(|io: TcpStream| async move {
let mut f = Framed::new(io, BytesCodec); let mut f = Framed::new(io, BytesCodec);
f.send(Bytes::from_static(b"test")).await.unwrap(); f.send(Bytes::from_static(b"test")).await.unwrap();
Ok::<_, ()>(()) Ok::<_, ()>(())
} })
}) })
}) .unwrap()
.unwrap() .start()
.start(); }));
let _ = tx.send((srv, actix_rt::System::current())); let _ = tx.send((srv, actix_rt::System::current()));
let _ = sys.run(); let _ = sys.run();
@@ -146,29 +151,31 @@ fn test_configure() {
let h = thread::spawn(move || { let h = thread::spawn(move || {
let num = num2.clone(); let num = num2.clone();
let sys = actix_rt::System::new("test"); let mut sys = actix_rt::System::new("test");
let srv = Server::build() let srv = sys.block_on(lazy(|_| {
.disable_signals() Server::build()
.configure(move |cfg| { .disable_signals()
let num = num.clone(); .configure(move |cfg| {
let lst = net::TcpListener::bind(addr3).unwrap(); let num = num.clone();
cfg.bind("addr1", addr1) let lst = net::TcpListener::bind(addr3).unwrap();
.unwrap() cfg.bind("addr1", addr1)
.bind("addr2", addr2) .unwrap()
.unwrap() .bind("addr2", addr2)
.listen("addr3", lst) .unwrap()
.apply(move |rt| { .listen("addr3", lst)
let num = num.clone(); .apply(move |rt| {
rt.service("addr1", fn_service(|_| ok::<_, ()>(()))); let num = num.clone();
rt.service("addr3", fn_service(|_| ok::<_, ()>(()))); rt.service("addr1", fn_service(|_| ok::<_, ()>(())));
rt.on_start(lazy(move |_| { rt.service("addr3", fn_service(|_| ok::<_, ()>(())));
let _ = num.fetch_add(1, Relaxed); rt.on_start(lazy(move |_| {
})) let _ = num.fetch_add(1, Relaxed);
}) }))
}) })
.unwrap() })
.workers(1) .unwrap()
.start(); .workers(1)
.start()
}));
let _ = tx.send((srv, actix_rt::System::current())); let _ = tx.send((srv, actix_rt::System::current()));
let _ = sys.run(); let _ = sys.run();
}); });

View File

@@ -1,5 +1,32 @@
# Changes # Changes
## Unreleased - 2020-xx-xx
## 2.0.0-beta.1 - 2020-12-28
* `Service`, other traits, and many type signatures now take the the request type as a type
parameter instead of an associated type. [#232]
* Add `always_ready!` and `forward_ready!` macros. [#233]
* Crate is now `no_std`. [#233]
* Migrate pin projections to `pin-project-lite`. [#233]
* Remove `AndThenApplyFn` and Pipeline `and_then_apply_fn`. Use the
`.and_then(apply_fn(...))` construction. [#233]
* Move non-vital methods to `ServiceExt` and `ServiceFactoryExt` extension traits. [#235]
[#232]: https://github.com/actix/actix-net/pull/232
[#233]: https://github.com/actix/actix-net/pull/233
[#235]: https://github.com/actix/actix-net/pull/235
## 1.0.6 - 2020-08-09
### Fixed
* Removed unsound custom Cell implementation that allowed obtaining several mutable references to
the same data, which is undefined behavior in Rust and could lead to violations of memory safety. External code could obtain several mutable references to the same data through
service combinators. Attempts to acquire several mutable references to the same data will instead
result in a panic.
## [1.0.5] - 2020-01-16 ## [1.0.5] - 2020-01-16
### Fixed ### Fixed

View File

@@ -1,14 +1,18 @@
[package] [package]
name = "actix-service" name = "actix-service"
version = "1.0.5" version = "2.0.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = [
description = "Actix service" "Nikolay Kim <fafhrd91@gmail.com>",
keywords = ["network", "framework", "async", "futures"] "Rob Ede <robjtede@icloud.com>",
]
description = "Service trait and combinators for representing asynchronous request/response operations."
keywords = ["network", "framework", "async", "futures", "service"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-service/" documentation = "https://docs.rs/actix-service"
readme = "README.md"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[lib] [lib]
@@ -16,17 +20,9 @@ name = "actix_service"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
futures-util = "0.3.1" futures-core = { version = "0.3.7", default-features = false }
pin-project = "0.4.6" pin-project-lite = "0.2"
[dev-dependencies] [dev-dependencies]
actix-rt = "1.0.0" actix-rt = "1.0.0"
criterion = "0.3" futures-util = { version = "0.3.7", default-features = false }
[[bench]]
name = "unsafecell_vs_refcell"
harness = false
[[bench]]
name = "and_then"
harness = false

7
actix-service/README.md Normal file
View File

@@ -0,0 +1,7 @@
# actix-service
> Service trait and combinators for representing asynchronous request/response operations.
See documentation for detailed explanations these components: [https://docs.rs/actix-service][docs].
[docs]: https://docs.rs/actix-service

View File

@@ -1,324 +0,0 @@
/// Benchmark various implementations of and_then
use criterion::{criterion_main, Criterion};
use futures_util::future::join_all;
use std::cell::{RefCell, UnsafeCell};
use std::task::{Context, Poll};
use std::rc::Rc;
use actix_service::{Service};
use actix_service::IntoService;
use std::future::Future;
use std::pin::Pin;
use futures_util::future::TryFutureExt;
use actix_service::boxed::BoxFuture;
/*
* Test services A,B for AndThen service implementations
*/
async fn svc1(_: ()) -> Result<usize, ()> {
Ok(1)
}
async fn svc2(req: usize) -> Result<usize, ()> {
Ok(req + 1)
}
/*
* AndThenUC - original AndThen service based on UnsafeCell
* Cut down version of actix_service::AndThenService based on actix-service::Cell
*/
struct AndThenUC<A,B>(Rc<UnsafeCell<(A, B)>>);
impl<A,B> AndThenUC<A,B> {
fn new(a: A, b: B) -> Self
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
Self(Rc::new(UnsafeCell::new((a,b))))
}
}
impl<A,B> Clone for AndThenUC<A,B> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<A,B> Service for AndThenUC<A,B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>
{
type Request = A::Request;
type Response = B::Response;
type Error = A::Error;
type Future = AndThenServiceResponse<A,B>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = unsafe { &mut *(*self.0).get() }.0.call(req);
AndThenServiceResponse {
state: State::A(fut, Some(self.0.clone()))
}
}
}
#[pin_project::pin_project]
pub(crate) struct AndThenServiceResponse<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
#[pin]
state: State<A, B>,
}
#[pin_project::pin_project]
enum State<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
A(#[pin] A::Future, Option<Rc<UnsafeCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
impl<A, B> Future for AndThenServiceResponse<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
type Output = Result<B::Response, A::Error>;
#[pin_project::project]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
#[project]
match this.state.as_mut().project() {
State::A(fut, b) => match fut.poll(cx)? {
Poll::Ready(res) => {
let b = b.take().unwrap();
this.state.set(State::Empty); // drop fut A
let fut = unsafe { &mut (*b.get()).1 }.call(res);
this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
State::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
State::Empty => panic!("future must not be polled after it returned `Poll::Ready`"),
}
}
}
/*
* AndThenRC - AndThen service based on RefCell
*/
struct AndThenRC<A,B>(Rc<RefCell<(A, B)>>);
impl<A,B> AndThenRC<A,B> {
fn new(a: A, b: B) -> Self
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
Self(Rc::new(RefCell::new((a,b))))
}
}
impl<A,B> Clone for AndThenRC<A,B> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<A,B> Service for AndThenRC<A,B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>
{
type Request = A::Request;
type Response = B::Response;
type Error = A::Error;
type Future = AndThenServiceResponseRC<A,B>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = self.0.borrow_mut().0.call(req);
AndThenServiceResponseRC {
state: StateRC::A(fut, Some(self.0.clone()))
}
}
}
#[pin_project::pin_project]
pub(crate) struct AndThenServiceResponseRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
#[pin]
state: StateRC<A, B>,
}
#[pin_project::pin_project]
enum StateRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
A(#[pin] A::Future, Option<Rc<RefCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
impl<A, B> Future for AndThenServiceResponseRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
type Output = Result<B::Response, A::Error>;
#[pin_project::project]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
#[project]
match this.state.as_mut().project() {
StateRC::A(fut, b) => match fut.poll(cx)? {
Poll::Ready(res) => {
let b = b.take().unwrap();
this.state.set(StateRC::Empty); // drop fut A
let fut = b.borrow_mut().1.call(res);
this.state.set(StateRC::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
StateRC::B(fut) => fut.poll(cx).map(|r| {
this.state.set(StateRC::Empty);
r
}),
StateRC::Empty => panic!("future must not be polled after it returned `Poll::Ready`"),
}
}
}
/*
* AndThenRCFuture - AndThen service based on RefCell
* and standard futures::future::and_then combinator in a Box
*/
struct AndThenRCFuture<A,B>(Rc<RefCell<(A, B)>>);
impl<A,B> AndThenRCFuture<A,B> {
fn new(a: A, b: B) -> Self
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
Self(Rc::new(RefCell::new((a,b))))
}
}
impl<A,B> Clone for AndThenRCFuture<A,B> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<A,B> Service for AndThenRCFuture<A,B>
where
A: Service + 'static,
A::Future: 'static,
B: Service<Request = A::Response, Error = A::Error> + 'static,
B::Future: 'static
{
type Request = A::Request;
type Response = B::Response;
type Error = A::Error;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = self.0.borrow_mut().0.call(req);
let core = self.0.clone();
let fut2 = move |res| (*core).borrow_mut().1.call(res);
Box::pin(
fut.and_then(fut2)
)
}
}
/// Criterion Benchmark for async Service
/// Should be used from within criterion group:
/// ```rust,ignore
/// let mut criterion: ::criterion::Criterion<_> =
/// ::criterion::Criterion::default().configure_from_args();
/// bench_async_service(&mut criterion, ok_service(), "async_service_direct");
/// ```
///
/// Usable for benching Service wrappers:
/// Using minimum service code implementation we first measure
/// time to run minimum service, then measure time with wrapper.
///
/// Sample output
/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us]
pub fn bench_async_service<S>(c: &mut Criterion, srv: S, name: &str)
where
S: Service<Request = (), Response = usize, Error = ()> + Clone + 'static,
{
let mut rt = actix_rt::System::new("test");
// start benchmark loops
c.bench_function(name, move |b| {
b.iter_custom(|iters| {
let mut srvs: Vec<_> = (1..iters).map(|_| srv.clone()).collect();
// exclude request generation, it appears it takes significant time vs call (3us vs 1us)
let start = std::time::Instant::now();
// benchmark body
rt.block_on(async move {
join_all(srvs.iter_mut().map(|srv| srv.call(()))).await
});
let elapsed = start.elapsed();
// check that at least first request succeeded
elapsed
})
});
}
pub fn service_benches() {
let mut criterion: ::criterion::Criterion<_> =
::criterion::Criterion::default().configure_from_args();
bench_async_service(&mut criterion, AndThenUC::new(svc1.into_service(), svc2.into_service()), "AndThen with UnsafeCell");
bench_async_service(&mut criterion, AndThenRC::new(svc1.into_service(), svc2.into_service()), "AndThen with RefCell");
bench_async_service(&mut criterion, AndThenUC::new(svc1.into_service(), svc2.into_service()), "AndThen with UnsafeCell");
bench_async_service(&mut criterion, AndThenRC::new(svc1.into_service(), svc2.into_service()), "AndThen with RefCell");
bench_async_service(&mut criterion, AndThenRCFuture::new(svc1.into_service(), svc2.into_service()), "AndThen with RefCell via future::and_then");
}
criterion_main!(service_benches);

View File

@@ -1,117 +0,0 @@
use criterion::{criterion_main, Criterion};
use futures_util::future::join_all;
use std::cell::{RefCell, UnsafeCell};
use std::task::{Context, Poll};
use std::rc::Rc;
use actix_service::{Service};
use futures_util::future::{ok, Ready};
struct SrvUC(Rc<UnsafeCell<usize>>);
impl Default for SrvUC {
fn default() -> Self {
Self(Rc::new(UnsafeCell::new(0)))
}
}
impl Clone for SrvUC {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl Service for SrvUC {
type Request = ();
type Response = usize;
type Error = ();
type Future = Ready<Result<Self::Response, ()>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: ()) -> Self::Future {
unsafe { *(*self.0).get() = *(*self.0).get() + 1 };
ok(unsafe { *self.0.get() })
}
}
struct SrvRC(Rc<RefCell<usize>>);
impl Default for SrvRC {
fn default() -> Self {
Self(Rc::new(RefCell::new(0)))
}
}
impl Clone for SrvRC {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl Service for SrvRC {
type Request = ();
type Response = usize;
type Error = ();
type Future = Ready<Result<Self::Response, ()>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: ()) -> Self::Future {
let prev = *self.0.borrow();
*(*self.0).borrow_mut() = prev + 1;
ok(*self.0.borrow())
}
}
/// Criterion Benchmark for async Service
/// Should be used from within criterion group:
/// ```rust,ignore
/// let mut criterion: ::criterion::Criterion<_> =
/// ::criterion::Criterion::default().configure_from_args();
/// bench_async_service(&mut criterion, ok_service(), "async_service_direct");
/// ```
///
/// Usable for benching Service wrappers:
/// Using minimum service code implementation we first measure
/// time to run minimum service, then measure time with wrapper.
///
/// Sample output
/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us]
pub fn bench_async_service<S>(c: &mut Criterion, srv: S, name: &str)
where
S: Service<Request = (), Response = usize, Error = ()> + Clone + 'static,
{
let mut rt = actix_rt::System::new("test");
// start benchmark loops
c.bench_function(name, move |b| {
b.iter_custom(|iters| {
let mut srvs: Vec<_> = (1..iters).map(|_| srv.clone()).collect();
// exclude request generation, it appears it takes significant time vs call (3us vs 1us)
let start = std::time::Instant::now();
// benchmark body
rt.block_on(async move {
join_all(srvs.iter_mut().map(|srv| srv.call(()))).await
});
let elapsed = start.elapsed();
// check that at least first request succeeded
elapsed
})
});
}
pub fn service_benches() {
let mut criterion: ::criterion::Criterion<_> =
::criterion::Criterion::default().configure_from_args();
bench_async_service(&mut criterion, SrvUC::default(), "Service with UnsafeCell");
bench_async_service(&mut criterion, SrvRC::default(), "Service with RefCell");
bench_async_service(&mut criterion, SrvUC::default(), "Service with UnsafeCell");
bench_async_service(&mut criterion, SrvRC::default(), "Service with RefCell");
}
criterion_main!(service_benches);

View File

@@ -1,46 +1,50 @@
use std::future::Future; use alloc::rc::Rc;
use std::pin::Pin; use core::{
use std::rc::Rc; cell::RefCell,
use std::task::{Context, Poll}; future::Future,
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use super::{Service, ServiceFactory}; use super::{Service, ServiceFactory};
use crate::cell::Cell;
/// Service for the `and_then` combinator, chaining a computation onto the end /// Service for the `and_then` combinator, chaining a computation onto the end
/// of another service which completes successfully. /// of another service which completes successfully.
/// ///
/// This is created by the `ServiceExt::and_then` method. /// This is created by the `Pipeline::and_then` method.
pub(crate) struct AndThenService<A, B>(Cell<(A, B)>); pub(crate) struct AndThenService<A, B, Req>(Rc<RefCell<(A, B)>>, PhantomData<Req>);
impl<A, B> AndThenService<A, B> { impl<A, B, Req> AndThenService<A, B, Req> {
/// Create new `AndThen` combinator /// Create new `AndThen` combinator
pub(crate) fn new(a: A, b: B) -> Self pub(crate) fn new(a: A, b: B) -> Self
where where
A: Service, A: Service<Req>,
B: Service<Request = A::Response, Error = A::Error>, B: Service<A::Response, Error = A::Error>,
{ {
Self(Cell::new((a, b))) Self(Rc::new(RefCell::new((a, b))), PhantomData)
} }
} }
impl<A, B> Clone for AndThenService<A, B> { impl<A, B, Req> Clone for AndThenService<A, B, Req> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
AndThenService(self.0.clone()) AndThenService(self.0.clone(), PhantomData)
} }
} }
impl<A, B> Service for AndThenService<A, B> impl<A, B, Req> Service<Req> for AndThenService<A, B, Req>
where where
A: Service, A: Service<Req>,
B: Service<Request = A::Response, Error = A::Error>, B: Service<A::Response, Error = A::Error>,
{ {
type Request = A::Request;
type Response = B::Response; type Response = B::Response;
type Error = A::Error; type Error = A::Error;
type Future = AndThenServiceResponse<A, B>; type Future = AndThenServiceResponse<A, B, Req>;
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 srv = self.0.get_mut(); let mut srv = self.0.borrow_mut();
let not_ready = !srv.0.poll_ready(cx)?.is_ready(); let not_ready = !srv.0.poll_ready(cx)?.is_ready();
if !srv.1.poll_ready(cx)?.is_ready() || not_ready { if !srv.1.poll_ready(cx)?.is_ready() || not_ready {
Poll::Pending Poll::Pending
@@ -49,88 +53,102 @@ where
} }
} }
fn call(&mut self, req: A::Request) -> Self::Future { fn call(&mut self, req: Req) -> Self::Future {
AndThenServiceResponse { AndThenServiceResponse {
state: State::A(self.0.get_mut().0.call(req), Some(self.0.clone())), state: State::A {
fut: self.0.borrow_mut().0.call(req),
b: Some(self.0.clone()),
},
} }
} }
} }
#[pin_project::pin_project] pin_project! {
pub(crate) struct AndThenServiceResponse<A, B> pub(crate) struct AndThenServiceResponse<A, B, Req>
where where
A: Service, A: Service<Req>,
B: Service<Request = A::Response, Error = A::Error>, B: Service<A::Response, Error = A::Error>,
{ {
#[pin] #[pin]
state: State<A, B>, state: State<A, B, Req>,
}
} }
#[pin_project::pin_project] pin_project! {
enum State<A, B> #[project = StateProj]
where enum State<A, B, Req>
A: Service, where
B: Service<Request = A::Response, Error = A::Error>, A: Service<Req>,
{ B: Service<A::Response, Error = A::Error>,
A(#[pin] A::Future, Option<Cell<(A, B)>>), {
B(#[pin] B::Future), A {
Empty, #[pin]
fut: A::Future,
b: Option<Rc<RefCell<(A, B)>>>,
},
B {
#[pin]
fut: B::Future,
},
Empty,
}
} }
impl<A, B> Future for AndThenServiceResponse<A, B> impl<A, B, Req> Future for AndThenServiceResponse<A, B, Req>
where where
A: Service, A: Service<Req>,
B: Service<Request = A::Response, Error = A::Error>, B: Service<A::Response, Error = A::Error>,
{ {
type Output = Result<B::Response, A::Error>; type Output = Result<B::Response, A::Error>;
#[pin_project::project]
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();
#[project]
match this.state.as_mut().project() { match this.state.as_mut().project() {
State::A(fut, b) => match fut.poll(cx)? { StateProj::A { fut, b } => match fut.poll(cx)? {
Poll::Ready(res) => { Poll::Ready(res) => {
let mut b = b.take().unwrap(); let b = b.take().unwrap();
this.state.set(State::Empty); // drop fut A this.state.set(State::Empty); // drop fut A
let fut = b.get_mut().1.call(res); let fut = b.borrow_mut().1.call(res);
this.state.set(State::B(fut)); this.state.set(State::B { fut });
self.poll(cx) self.poll(cx)
} }
Poll::Pending => Poll::Pending, Poll::Pending => Poll::Pending,
}, },
State::B(fut) => fut.poll(cx).map(|r| { StateProj::B { fut } => fut.poll(cx).map(|r| {
this.state.set(State::Empty); this.state.set(State::Empty);
r r
}), }),
State::Empty => panic!("future must not be polled after it returned `Poll::Ready`"), StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
}
} }
} }
} }
/// `.and_then()` service factory combinator /// `.and_then()` service factory combinator
pub(crate) struct AndThenServiceFactory<A, B> pub(crate) struct AndThenServiceFactory<A, B, Req>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
A::Config: Clone, A::Config: Clone,
B: ServiceFactory< B: ServiceFactory<
A::Response,
Config = A::Config, Config = A::Config,
Request = A::Response,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
{ {
inner: Rc<(A, B)>, inner: Rc<(A, B)>,
_phantom: PhantomData<Req>,
} }
impl<A, B> AndThenServiceFactory<A, B> impl<A, B, Req> AndThenServiceFactory<A, B, Req>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
A::Config: Clone, A::Config: Clone,
B: ServiceFactory< B: ServiceFactory<
A::Response,
Config = A::Config, Config = A::Config,
Request = A::Response,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
@@ -139,29 +157,29 @@ where
pub(crate) fn new(a: A, b: B) -> Self { pub(crate) fn new(a: A, b: B) -> Self {
Self { Self {
inner: Rc::new((a, b)), inner: Rc::new((a, b)),
_phantom: PhantomData,
} }
} }
} }
impl<A, B> ServiceFactory for AndThenServiceFactory<A, B> impl<A, B, Req> ServiceFactory<Req> for AndThenServiceFactory<A, B, Req>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
A::Config: Clone, A::Config: Clone,
B: ServiceFactory< B: ServiceFactory<
A::Response,
Config = A::Config, Config = A::Config,
Request = A::Response,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
{ {
type Request = A::Request;
type Response = B::Response; type Response = B::Response;
type Error = A::Error; type Error = A::Error;
type Config = A::Config; type Config = A::Config;
type Service = AndThenService<A::Service, B::Service>; type Service = AndThenService<A::Service, B::Service, Req>;
type InitError = A::InitError; type InitError = A::InitError;
type Future = AndThenServiceFactoryResponse<A, B>; type Future = AndThenServiceFactoryResponse<A, B, Req>;
fn new_service(&self, cfg: A::Config) -> Self::Future { fn new_service(&self, cfg: A::Config) -> Self::Future {
let inner = &*self.inner; let inner = &*self.inner;
@@ -172,13 +190,13 @@ where
} }
} }
impl<A, B> Clone for AndThenServiceFactory<A, B> impl<A, B, Req> Clone for AndThenServiceFactory<A, B, Req>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
A::Config: Clone, A::Config: Clone,
B: ServiceFactory< B: ServiceFactory<
A::Response,
Config = A::Config, Config = A::Config,
Request = A::Response,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
@@ -186,29 +204,31 @@ where
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
inner: self.inner.clone(), inner: self.inner.clone(),
_phantom: PhantomData,
} }
} }
} }
#[pin_project::pin_project] pin_project! {
pub(crate) struct AndThenServiceFactoryResponse<A, B> pub(crate) struct AndThenServiceFactoryResponse<A, B, Req>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
B: ServiceFactory<Request = A::Response>, B: ServiceFactory<A::Response>,
{ {
#[pin] #[pin]
fut_a: A::Future, fut_a: A::Future,
#[pin] #[pin]
fut_b: B::Future, fut_b: B::Future,
a: Option<A::Service>, a: Option<A::Service>,
b: Option<B::Service>, b: Option<B::Service>,
}
} }
impl<A, B> AndThenServiceFactoryResponse<A, B> impl<A, B, Req> AndThenServiceFactoryResponse<A, B, Req>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
B: ServiceFactory<Request = A::Response>, B: ServiceFactory<A::Response>,
{ {
fn new(fut_a: A::Future, fut_b: B::Future) -> Self { fn new(fut_a: A::Future, fut_b: B::Future) -> Self {
AndThenServiceFactoryResponse { AndThenServiceFactoryResponse {
@@ -220,12 +240,12 @@ where
} }
} }
impl<A, B> Future for AndThenServiceFactoryResponse<A, B> impl<A, B, Req> Future for AndThenServiceFactoryResponse<A, B, Req>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
B: ServiceFactory<Request = A::Response, Error = A::Error, InitError = A::InitError>, B: ServiceFactory<A::Response, Error = A::Error, InitError = A::InitError>,
{ {
type Output = Result<AndThenService<A::Service, B::Service>, A::InitError>; type Output = Result<AndThenService<A::Service, B::Service, Req>, A::InitError>;
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.project(); let this = self.project();
@@ -253,18 +273,21 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::cell::Cell; use alloc::rc::Rc;
use std::rc::Rc; use core::{
use std::task::{Context, Poll}; cell::Cell,
task::{Context, Poll},
};
use futures_util::future::{lazy, ok, ready, Ready}; use futures_util::future::lazy;
use crate::{fn_factory, pipeline, pipeline_factory, Service, ServiceFactory}; use crate::{
fn_factory, ok, pipeline, pipeline_factory, ready, Ready, Service, ServiceFactory,
};
struct Srv1(Rc<Cell<usize>>); struct Srv1(Rc<Cell<usize>>);
impl Service for Srv1 { impl Service<&'static str> for Srv1 {
type Request = &'static str;
type Response = &'static str; type Response = &'static str;
type Error = (); type Error = ();
type Future = Ready<Result<Self::Response, ()>>; type Future = Ready<Result<Self::Response, ()>>;
@@ -282,8 +305,7 @@ mod tests {
#[derive(Clone)] #[derive(Clone)]
struct Srv2(Rc<Cell<usize>>); struct Srv2(Rc<Cell<usize>>);
impl Service for Srv2 { impl Service<&'static str> for Srv2 {
type Request = &'static str;
type Response = (&'static str, &'static str); type Response = (&'static str, &'static str);
type Error = (); type Error = ();
type Future = Ready<Result<Self::Response, ()>>; type Future = Ready<Result<Self::Response, ()>>;

View File

@@ -1,328 +0,0 @@
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use crate::cell::Cell;
use crate::{Service, ServiceFactory};
/// `Apply` service combinator
pub(crate) struct AndThenApplyFn<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
srv: Cell<(A, B, F)>,
r: PhantomData<(Fut, Res, Err)>,
}
impl<A, B, F, Fut, Res, Err> AndThenApplyFn<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
/// Create new `Apply` combinator
pub(crate) fn new(a: A, b: B, f: F) -> Self {
Self {
srv: Cell::new((a, b, f)),
r: PhantomData,
}
}
}
impl<A, B, F, Fut, Res, Err> Clone for AndThenApplyFn<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
fn clone(&self) -> Self {
AndThenApplyFn {
srv: self.srv.clone(),
r: PhantomData,
}
}
}
impl<A, B, F, Fut, Res, Err> Service for AndThenApplyFn<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
type Request = A::Request;
type Response = Res;
type Error = Err;
type Future = AndThenApplyFnFuture<A, B, F, Fut, Res, Err>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let inner = self.srv.get_mut();
let not_ready = inner.0.poll_ready(cx)?.is_pending();
if inner.1.poll_ready(cx)?.is_pending() || not_ready {
Poll::Pending
} else {
Poll::Ready(Ok(()))
}
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = self.srv.get_mut().0.call(req);
AndThenApplyFnFuture {
state: State::A(fut, Some(self.srv.clone())),
}
}
}
#[pin_project::pin_project]
pub(crate) struct AndThenApplyFnFuture<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error>,
Err: From<B::Error>,
{
#[pin]
state: State<A, B, F, Fut, Res, Err>,
}
#[pin_project::pin_project]
enum State<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error>,
Err: From<B::Error>,
{
A(#[pin] A::Future, Option<Cell<(A, B, F)>>),
B(#[pin] Fut),
Empty,
}
impl<A, B, F, Fut, Res, Err> Future for AndThenApplyFnFuture<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
type Output = Result<Res, Err>;
#[pin_project::project]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
#[project]
match this.state.as_mut().project() {
State::A(fut, b) => match fut.poll(cx)? {
Poll::Ready(res) => {
let mut b = b.take().unwrap();
this.state.set(State::Empty);
let b = b.get_mut();
let fut = (&mut b.2)(res, &mut b.1);
this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
State::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
State::Empty => panic!("future must not be polled after it returned `Poll::Ready`"),
}
}
}
/// `AndThenApplyFn` service factory
pub(crate) struct AndThenApplyFnFactory<A, B, F, Fut, Res, Err> {
srv: Rc<(A, B, F)>,
r: PhantomData<(Fut, Res, Err)>,
}
impl<A, B, F, Fut, Res, Err> AndThenApplyFnFactory<A, B, F, Fut, Res, Err>
where
A: ServiceFactory,
B: ServiceFactory<Config = A::Config, InitError = A::InitError>,
F: FnMut(A::Response, &mut B::Service) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
/// Create new `ApplyNewService` new service instance
pub(crate) fn new(a: A, b: B, f: F) -> Self {
Self {
srv: Rc::new((a, b, f)),
r: PhantomData,
}
}
}
impl<A, B, F, Fut, Res, Err> Clone for AndThenApplyFnFactory<A, B, F, Fut, Res, Err> {
fn clone(&self) -> Self {
Self {
srv: self.srv.clone(),
r: PhantomData,
}
}
}
impl<A, B, F, Fut, Res, Err> ServiceFactory for AndThenApplyFnFactory<A, B, F, Fut, Res, Err>
where
A: ServiceFactory,
A::Config: Clone,
B: ServiceFactory<Config = A::Config, InitError = A::InitError>,
F: FnMut(A::Response, &mut B::Service) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
type Request = A::Request;
type Response = Res;
type Error = Err;
type Service = AndThenApplyFn<A::Service, B::Service, F, Fut, Res, Err>;
type Config = A::Config;
type InitError = A::InitError;
type Future = AndThenApplyFnFactoryResponse<A, B, F, Fut, Res, Err>;
fn new_service(&self, cfg: A::Config) -> Self::Future {
let srv = &*self.srv;
AndThenApplyFnFactoryResponse {
a: None,
b: None,
f: srv.2.clone(),
fut_a: srv.0.new_service(cfg.clone()),
fut_b: srv.1.new_service(cfg),
}
}
}
#[pin_project::pin_project]
pub(crate) struct AndThenApplyFnFactoryResponse<A, B, F, Fut, Res, Err>
where
A: ServiceFactory,
B: ServiceFactory<Config = A::Config, InitError = A::InitError>,
F: FnMut(A::Response, &mut B::Service) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error>,
Err: From<B::Error>,
{
#[pin]
fut_b: B::Future,
#[pin]
fut_a: A::Future,
f: F,
a: Option<A::Service>,
b: Option<B::Service>,
}
impl<A, B, F, Fut, Res, Err> Future for AndThenApplyFnFactoryResponse<A, B, F, Fut, Res, Err>
where
A: ServiceFactory,
B: ServiceFactory<Config = A::Config, InitError = A::InitError>,
F: FnMut(A::Response, &mut B::Service) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
type Output =
Result<AndThenApplyFn<A::Service, B::Service, F, Fut, Res, Err>, A::InitError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
if this.a.is_none() {
if let Poll::Ready(service) = this.fut_a.poll(cx)? {
*this.a = Some(service);
}
}
if this.b.is_none() {
if let Poll::Ready(service) = this.fut_b.poll(cx)? {
*this.b = Some(service);
}
}
if this.a.is_some() && this.b.is_some() {
Poll::Ready(Ok(AndThenApplyFn {
srv: Cell::new((
this.a.take().unwrap(),
this.b.take().unwrap(),
this.f.clone(),
)),
r: PhantomData,
}))
} else {
Poll::Pending
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::future::{lazy, ok, Ready, TryFutureExt};
use crate::{fn_service, pipeline, pipeline_factory, Service, ServiceFactory};
#[derive(Clone)]
struct Srv;
impl Service for Srv {
type Request = ();
type Response = ();
type Error = ();
type Future = Ready<Result<(), ()>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
#[allow(clippy::unit_arg)]
fn call(&mut self, req: Self::Request) -> Self::Future {
ok(req)
}
}
#[actix_rt::test]
async fn test_service() {
let mut srv = pipeline(ok)
.and_then_apply_fn(Srv, |req: &'static str, s| {
s.call(()).map_ok(move |res| (req, res))
});
let res = lazy(|cx| srv.poll_ready(cx)).await;
assert_eq!(res, Poll::Ready(Ok(())));
let res = srv.call("srv").await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), ("srv", ()));
}
#[actix_rt::test]
async fn test_service_factory() {
let new_srv = pipeline_factory(|| ok::<_, ()>(fn_service(ok)))
.and_then_apply_fn(
|| ok(Srv),
|req: &'static str, s| s.call(()).map_ok(move |res| (req, res)),
);
let mut srv = new_srv.new_service(()).await.unwrap();
let res = lazy(|cx| srv.poll_ready(cx)).await;
assert_eq!(res, Poll::Ready(Ok(())));
let res = srv.call("srv").await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), ("srv", ()));
}
}

View File

@@ -1,227 +1,230 @@
use std::future::Future; use core::{
use std::marker::PhantomData; future::Future,
use std::pin::Pin; marker::PhantomData,
use std::task::{Context, Poll}; pin::Pin,
task::{Context, Poll},
};
use futures_core::ready;
use pin_project_lite::pin_project;
use super::{IntoService, IntoServiceFactory, Service, ServiceFactory}; use super::{IntoService, IntoServiceFactory, Service, ServiceFactory};
/// Apply tranform function to a service. /// Apply transform function to a service.
pub fn apply_fn<T, F, R, In, Out, Err, U>(service: U, f: F) -> Apply<T, F, R, In, Out, Err> ///
/// The In and Out type params refer to the request and response types for the wrapped service.
pub fn apply_fn<I, S, F, Fut, Req, In, Res, Err>(
service: I,
wrap_fn: F,
) -> Apply<S, F, Req, In, Res, Err>
where where
T: Service<Error = Err>, I: IntoService<S, In>,
F: FnMut(In, &mut T) -> R, S: Service<In, Error = Err>,
R: Future<Output = Result<Out, Err>>, F: FnMut(Req, &mut S) -> Fut,
U: IntoService<T>, Fut: Future<Output = Result<Res, Err>>,
{ {
Apply::new(service.into_service(), f) Apply::new(service.into_service(), wrap_fn)
} }
/// Service factory that prodices `apply_fn` service. /// Service factory that produces `apply_fn` service.
pub fn apply_fn_factory<T, F, R, In, Out, Err, U>( ///
service: U, /// The In and Out type params refer to the request and response types for the wrapped service.
pub fn apply_fn_factory<I, SF, F, Fut, Req, In, Res, Err>(
service: I,
f: F, f: F,
) -> ApplyServiceFactory<T, F, R, In, Out, Err> ) -> ApplyFactory<SF, F, Req, In, Res, Err>
where where
T: ServiceFactory<Error = Err>, I: IntoServiceFactory<SF, In>,
F: FnMut(In, &mut T::Service) -> R + Clone, SF: ServiceFactory<In, Error = Err>,
R: Future<Output = Result<Out, Err>>, F: FnMut(Req, &mut SF::Service) -> Fut + Clone,
U: IntoServiceFactory<T>, Fut: Future<Output = Result<Res, Err>>,
{ {
ApplyServiceFactory::new(service.into_factory(), f) ApplyFactory::new(service.into_factory(), f)
} }
/// `Apply` service combinator /// `Apply` service combinator.
pub struct Apply<T, F, R, In, Out, Err> ///
/// The In and Out type params refer to the request and response types for the wrapped service.
pub struct Apply<S, F, Req, In, Res, Err>
where where
T: Service<Error = Err>, S: Service<In, Error = Err>,
{ {
service: T, service: S,
f: F, wrap_fn: F,
r: PhantomData<(In, Out, R)>, _phantom: PhantomData<(Req, In, Res, Err)>,
} }
impl<T, F, R, In, Out, Err> Apply<T, F, R, In, Out, Err> impl<S, F, Fut, Req, In, Res, Err> Apply<S, F, Req, In, Res, Err>
where where
T: Service<Error = Err>, S: Service<In, Error = Err>,
F: FnMut(In, &mut T) -> R, F: FnMut(Req, &mut S) -> Fut,
R: Future<Output = Result<Out, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
/// Create new `Apply` combinator /// Create new `Apply` combinator
fn new(service: T, f: F) -> Self { fn new(service: S, wrap_fn: F) -> Self {
Self { Self {
service, service,
f, wrap_fn,
r: PhantomData, _phantom: PhantomData,
} }
} }
} }
impl<T, F, R, In, Out, Err> Clone for Apply<T, F, R, In, Out, Err> impl<S, F, Fut, Req, In, Res, Err> Clone for Apply<S, F, Req, In, Res, Err>
where where
T: Service<Error = Err> + Clone, S: Service<In, Error = Err> + Clone,
F: FnMut(In, &mut T) -> R + Clone, F: FnMut(Req, &mut S) -> Fut + Clone,
R: Future<Output = Result<Out, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Apply { Apply {
service: self.service.clone(), service: self.service.clone(),
f: self.f.clone(), wrap_fn: self.wrap_fn.clone(),
r: PhantomData, _phantom: PhantomData,
} }
} }
} }
impl<T, F, R, In, Out, Err> Service for Apply<T, F, R, In, Out, Err> impl<S, F, Fut, Req, In, Res, Err> Service<Req> for Apply<S, F, Req, In, Res, Err>
where where
T: Service<Error = Err>, S: Service<In, Error = Err>,
F: FnMut(In, &mut T) -> R, F: FnMut(Req, &mut S) -> Fut,
R: Future<Output = Result<Out, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
type Request = In; type Response = Res;
type Response = Out;
type Error = Err; type Error = Err;
type Future = R; type Future = Fut;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { crate::forward_ready!(service);
Poll::Ready(futures_util::ready!(self.service.poll_ready(cx)))
}
fn call(&mut self, req: In) -> Self::Future { fn call(&mut self, req: Req) -> Self::Future {
(self.f)(req, &mut self.service) (self.wrap_fn)(req, &mut self.service)
} }
} }
/// `apply()` service factory /// `ApplyFactory` service factory combinator.
pub struct ApplyServiceFactory<T, F, R, In, Out, Err> pub struct ApplyFactory<SF, F, Req, In, Res, Err> {
where factory: SF,
T: ServiceFactory<Error = Err>, wrap_fn: F,
F: FnMut(In, &mut T::Service) -> R + Clone, _phantom: PhantomData<(Req, In, Res, Err)>,
R: Future<Output = Result<Out, Err>>,
{
service: T,
f: F,
r: PhantomData<(R, In, Out)>,
} }
impl<T, F, R, In, Out, Err> ApplyServiceFactory<T, F, R, In, Out, Err> impl<SF, F, Fut, Req, In, Res, Err> ApplyFactory<SF, F, Req, In, Res, Err>
where where
T: ServiceFactory<Error = Err>, SF: ServiceFactory<In, Error = Err>,
F: FnMut(In, &mut T::Service) -> R + Clone, F: FnMut(Req, &mut SF::Service) -> Fut + Clone,
R: Future<Output = Result<Out, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
/// Create new `ApplyNewService` new service instance /// Create new `ApplyFactory` new service instance
fn new(service: T, f: F) -> Self { fn new(factory: SF, wrap_fn: F) -> Self {
Self { Self {
f, factory,
service, wrap_fn,
r: PhantomData, _phantom: PhantomData,
} }
} }
} }
impl<T, F, R, In, Out, Err> Clone for ApplyServiceFactory<T, F, R, In, Out, Err> impl<SF, F, Fut, Req, In, Res, Err> Clone for ApplyFactory<SF, F, Req, In, Res, Err>
where where
T: ServiceFactory<Error = Err> + Clone, SF: ServiceFactory<In, Error = Err> + Clone,
F: FnMut(In, &mut T::Service) -> R + Clone, F: FnMut(Req, &mut SF::Service) -> Fut + Clone,
R: Future<Output = Result<Out, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
service: self.service.clone(), factory: self.factory.clone(),
f: self.f.clone(), wrap_fn: self.wrap_fn.clone(),
r: PhantomData, _phantom: PhantomData,
} }
} }
} }
impl<T, F, R, In, Out, Err> ServiceFactory for ApplyServiceFactory<T, F, R, In, Out, Err> impl<SF, F, Fut, Req, In, Res, Err> ServiceFactory<Req>
for ApplyFactory<SF, F, Req, In, Res, Err>
where where
T: ServiceFactory<Error = Err>, SF: ServiceFactory<In, Error = Err>,
F: FnMut(In, &mut T::Service) -> R + Clone, F: FnMut(Req, &mut SF::Service) -> Fut + Clone,
R: Future<Output = Result<Out, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
type Request = In; type Response = Res;
type Response = Out;
type Error = Err; type Error = Err;
type Config = T::Config; type Config = SF::Config;
type Service = Apply<T::Service, F, R, In, Out, Err>; type Service = Apply<SF::Service, F, Req, In, Res, Err>;
type InitError = T::InitError; type InitError = SF::InitError;
type Future = ApplyServiceFactoryResponse<T, F, R, In, Out, Err>; type Future = ApplyServiceFactoryResponse<SF, F, Fut, Req, In, Res, Err>;
fn new_service(&self, cfg: T::Config) -> Self::Future { fn new_service(&self, cfg: SF::Config) -> Self::Future {
ApplyServiceFactoryResponse::new(self.service.new_service(cfg), self.f.clone()) let svc = self.factory.new_service(cfg);
ApplyServiceFactoryResponse::new(svc, self.wrap_fn.clone())
} }
} }
#[pin_project::pin_project] pin_project! {
pub struct ApplyServiceFactoryResponse<T, F, R, In, Out, Err> pub struct ApplyServiceFactoryResponse<SF, F, Fut, Req, In, Res, Err>
where where
T: ServiceFactory<Error = Err>, SF: ServiceFactory<In, Error = Err>,
F: FnMut(In, &mut T::Service) -> R, F: FnMut(Req, &mut SF::Service) -> Fut,
R: Future<Output = Result<Out, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
#[pin] #[pin]
fut: T::Future, fut: SF::Future,
f: Option<F>, wrap_fn: Option<F>,
r: PhantomData<(In, Out)>, _phantom: PhantomData<(Req, Res)>,
}
} }
impl<T, F, R, In, Out, Err> ApplyServiceFactoryResponse<T, F, R, In, Out, Err> impl<SF, F, Fut, Req, In, Res, Err> ApplyServiceFactoryResponse<SF, F, Fut, Req, In, Res, Err>
where where
T: ServiceFactory<Error = Err>, SF: ServiceFactory<In, Error = Err>,
F: FnMut(In, &mut T::Service) -> R, F: FnMut(Req, &mut SF::Service) -> Fut,
R: Future<Output = Result<Out, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
fn new(fut: T::Future, f: F) -> Self { fn new(fut: SF::Future, wrap_fn: F) -> Self {
Self { Self {
f: Some(f),
fut, fut,
r: PhantomData, wrap_fn: Some(wrap_fn),
_phantom: PhantomData,
} }
} }
} }
impl<T, F, R, In, Out, Err> Future for ApplyServiceFactoryResponse<T, F, R, In, Out, Err> impl<SF, F, Fut, Req, In, Res, Err> Future
for ApplyServiceFactoryResponse<SF, F, Fut, Req, In, Res, Err>
where where
T: ServiceFactory<Error = Err>, SF: ServiceFactory<In, Error = Err>,
F: FnMut(In, &mut T::Service) -> R, F: FnMut(Req, &mut SF::Service) -> Fut,
R: Future<Output = Result<Out, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
type Output = Result<Apply<T::Service, F, R, In, Out, Err>, T::InitError>; type Output = Result<Apply<SF::Service, F, Req, In, Res, Err>, SF::InitError>;
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.project(); let this = self.project();
if let Poll::Ready(svc) = this.fut.poll(cx)? { let svc = ready!(this.fut.poll(cx))?;
Poll::Ready(Ok(Apply::new(svc, this.f.take().unwrap()))) Poll::Ready(Ok(Apply::new(svc, this.wrap_fn.take().unwrap())))
} else {
Poll::Pending
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::task::{Context, Poll}; use core::task::Poll;
use futures_util::future::{lazy, ok, Ready}; use futures_util::future::lazy;
use super::*; use super::*;
use crate::{pipeline, pipeline_factory, Service, ServiceFactory}; use crate::{ok, pipeline, pipeline_factory, Ready, Service, ServiceFactory};
#[derive(Clone)] #[derive(Clone)]
struct Srv; struct Srv;
impl Service for Srv { impl Service<()> for Srv {
type Request = ();
type Response = (); type Response = ();
type Error = (); type Error = ();
type Future = Ready<Result<(), ()>>; type Future = Ready<Result<(), ()>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { crate::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, _: ()) -> Self::Future { fn call(&mut self, _: ()) -> Self::Future {
ok(()) ok(())

View File

@@ -1,227 +1,237 @@
use std::future::Future; use alloc::rc::Rc;
use std::marker::PhantomData; use core::{
use std::pin::Pin; cell::RefCell,
use std::task::{Context, Poll}; future::Future,
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use crate::cell::Cell;
use crate::{Service, ServiceFactory}; use crate::{Service, ServiceFactory};
/// Convert `Fn(Config, &mut Service1) -> Future<Service2>` fn to a service factory /// Convert `Fn(Config, &mut Service1) -> Future<Service2>` fn to a service factory.
pub fn apply_cfg<F, C, T, R, S, E>( pub fn apply_cfg<S1, Req, F, Cfg, Fut, S2, Err>(
srv: T, srv: S1,
f: F, f: F,
) -> impl ServiceFactory< ) -> impl ServiceFactory<
Config = C, Req,
Request = S::Request, Config = Cfg,
Response = S::Response, Response = S2::Response,
Error = S::Error, Error = S2::Error,
Service = S, Service = S2,
InitError = E, InitError = Err,
Future = R, Future = Fut,
> + Clone > + Clone
where where
F: FnMut(C, &mut T) -> R, S1: Service<Req>,
T: Service, F: FnMut(Cfg, &mut S1) -> Fut,
R: Future<Output = Result<S, E>>, Fut: Future<Output = Result<S2, Err>>,
S: Service, S2: Service<Req>,
{ {
ApplyConfigService { ApplyConfigService {
srv: Cell::new((srv, f)), srv: Rc::new(RefCell::new((srv, f))),
_t: PhantomData, _phantom: PhantomData,
} }
} }
/// Convert `Fn(Config, &mut Service1) -> Future<Service2>` fn to a service factory /// Convert `Fn(Config, &mut ServiceFactory1) -> Future<ServiceFactory2>` fn to a service factory.
/// ///
/// Service1 get constructed from `T` factory. /// Service1 get constructed from `T` factory.
pub fn apply_cfg_factory<F, C, T, R, S>( pub fn apply_cfg_factory<SF, Req, F, Cfg, Fut, S>(
factory: T, factory: SF,
f: F, f: F,
) -> impl ServiceFactory< ) -> impl ServiceFactory<
Config = C, Req,
Request = S::Request, Config = Cfg,
Response = S::Response, Response = S::Response,
Error = S::Error, Error = S::Error,
Service = S, Service = S,
InitError = T::InitError, InitError = SF::InitError,
> + Clone > + Clone
where where
F: FnMut(C, &mut T::Service) -> R, SF: ServiceFactory<Req, Config = ()>,
T: ServiceFactory<Config = ()>, F: FnMut(Cfg, &mut SF::Service) -> Fut,
T::InitError: From<T::Error>, SF::InitError: From<SF::Error>,
R: Future<Output = Result<S, T::InitError>>, Fut: Future<Output = Result<S, SF::InitError>>,
S: Service, S: Service<Req>,
{ {
ApplyConfigServiceFactory { ApplyConfigServiceFactory {
srv: Cell::new((factory, f)), srv: Rc::new(RefCell::new((factory, f))),
_t: PhantomData, _phantom: PhantomData,
} }
} }
/// Convert `Fn(Config, &mut Server) -> Future<Service>` fn to NewService\ /// Convert `Fn(Config, &mut Server) -> Future<Service>` fn to NewService\
struct ApplyConfigService<F, C, T, R, S, E> struct ApplyConfigService<S1, Req, F, Cfg, Fut, S2, Err>
where where
F: FnMut(C, &mut T) -> R, S1: Service<Req>,
T: Service, F: FnMut(Cfg, &mut S1) -> Fut,
R: Future<Output = Result<S, E>>, Fut: Future<Output = Result<S2, Err>>,
S: Service, S2: Service<Req>,
{ {
srv: Cell<(T, F)>, srv: Rc<RefCell<(S1, F)>>,
_t: PhantomData<(C, R, S)>, _phantom: PhantomData<(Cfg, Req, Fut, S2)>,
} }
impl<F, C, T, R, S, E> Clone for ApplyConfigService<F, C, T, R, S, E> impl<S1, Req, F, Cfg, Fut, S2, Err> Clone for ApplyConfigService<S1, Req, F, Cfg, Fut, S2, Err>
where where
F: FnMut(C, &mut T) -> R, S1: Service<Req>,
T: Service, F: FnMut(Cfg, &mut S1) -> Fut,
R: Future<Output = Result<S, E>>, Fut: Future<Output = Result<S2, Err>>,
S: Service, S2: Service<Req>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
ApplyConfigService { ApplyConfigService {
srv: self.srv.clone(), srv: self.srv.clone(),
_t: PhantomData, _phantom: PhantomData,
} }
} }
} }
impl<F, C, T, R, S, E> ServiceFactory for ApplyConfigService<F, C, T, R, S, E> impl<S1, Req, F, Cfg, Fut, S2, Err> ServiceFactory<Req>
for ApplyConfigService<S1, Req, F, Cfg, Fut, S2, Err>
where where
F: FnMut(C, &mut T) -> R, S1: Service<Req>,
T: Service, F: FnMut(Cfg, &mut S1) -> Fut,
R: Future<Output = Result<S, E>>, Fut: Future<Output = Result<S2, Err>>,
S: Service, S2: Service<Req>,
{ {
type Config = C; type Config = Cfg;
type Request = S::Request; type Response = S2::Response;
type Response = S::Response; type Error = S2::Error;
type Error = S::Error; type Service = S2;
type Service = S;
type InitError = E; type InitError = Err;
type Future = R; type Future = Fut;
fn new_service(&self, cfg: C) -> Self::Future { fn new_service(&self, cfg: Cfg) -> Self::Future {
unsafe { let (t, f) = &mut *self.srv.borrow_mut();
let srv = self.srv.get_mut_unsafe(); f(cfg, t)
(srv.1)(cfg, &mut srv.0)
}
} }
} }
/// Convert `Fn(&Config) -> Future<Service>` fn to NewService /// Convert `Fn(&Config) -> Future<Service>` fn to NewService
struct ApplyConfigServiceFactory<F, C, T, R, S> struct ApplyConfigServiceFactory<SF, Req, F, Cfg, Fut, S>
where where
F: FnMut(C, &mut T::Service) -> R, SF: ServiceFactory<Req, Config = ()>,
T: ServiceFactory<Config = ()>, F: FnMut(Cfg, &mut SF::Service) -> Fut,
R: Future<Output = Result<S, T::InitError>>, Fut: Future<Output = Result<S, SF::InitError>>,
S: Service, S: Service<Req>,
{ {
srv: Cell<(T, F)>, srv: Rc<RefCell<(SF, F)>>,
_t: PhantomData<(C, R, S)>, _phantom: PhantomData<(Cfg, Req, Fut, S)>,
} }
impl<F, C, T, R, S> Clone for ApplyConfigServiceFactory<F, C, T, R, S> impl<SF, Req, F, Cfg, Fut, S> Clone for ApplyConfigServiceFactory<SF, Req, F, Cfg, Fut, S>
where where
F: FnMut(C, &mut T::Service) -> R, SF: ServiceFactory<Req, Config = ()>,
T: ServiceFactory<Config = ()>, F: FnMut(Cfg, &mut SF::Service) -> Fut,
R: Future<Output = Result<S, T::InitError>>, Fut: Future<Output = Result<S, SF::InitError>>,
S: Service, S: Service<Req>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
srv: self.srv.clone(), srv: self.srv.clone(),
_t: PhantomData, _phantom: PhantomData,
} }
} }
} }
impl<F, C, T, R, S> ServiceFactory for ApplyConfigServiceFactory<F, C, T, R, S> impl<SF, Req, F, Cfg, Fut, S> ServiceFactory<Req>
for ApplyConfigServiceFactory<SF, Req, F, Cfg, Fut, S>
where where
F: FnMut(C, &mut T::Service) -> R, SF: ServiceFactory<Req, Config = ()>,
T: ServiceFactory<Config = ()>, SF::InitError: From<SF::Error>,
T::InitError: From<T::Error>, F: FnMut(Cfg, &mut SF::Service) -> Fut,
R: Future<Output = Result<S, T::InitError>>, Fut: Future<Output = Result<S, SF::InitError>>,
S: Service, S: Service<Req>,
{ {
type Config = C; type Config = Cfg;
type Request = S::Request;
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Service = S; type Service = S;
type InitError = T::InitError; type InitError = SF::InitError;
type Future = ApplyConfigServiceFactoryResponse<F, C, T, R, S>; type Future = ApplyConfigServiceFactoryResponse<SF, Req, F, Cfg, Fut, S>;
fn new_service(&self, cfg: C) -> Self::Future { fn new_service(&self, cfg: Cfg) -> Self::Future {
ApplyConfigServiceFactoryResponse { ApplyConfigServiceFactoryResponse {
cfg: Some(cfg), cfg: Some(cfg),
store: self.srv.clone(), store: self.srv.clone(),
state: State::A(self.srv.get_ref().0.new_service(())), state: State::A {
fut: self.srv.borrow().0.new_service(()),
},
} }
} }
} }
#[pin_project::pin_project] pin_project! {
struct ApplyConfigServiceFactoryResponse<F, C, T, R, S> struct ApplyConfigServiceFactoryResponse<SF, Req, F, Cfg, Fut, S>
where where
F: FnMut(C, &mut T::Service) -> R, SF: ServiceFactory<Req, Config = ()>,
T: ServiceFactory<Config = ()>, SF::InitError: From<SF::Error>,
T::InitError: From<T::Error>, F: FnMut(Cfg, &mut SF::Service) -> Fut,
R: Future<Output = Result<S, T::InitError>>, Fut: Future<Output = Result<S, SF::InitError>>,
S: Service, S: Service<Req>,
{ {
cfg: Option<C>, cfg: Option<Cfg>,
store: Cell<(T, F)>, store: Rc<RefCell<(SF, F)>>,
#[pin] #[pin]
state: State<T, R, S>, state: State<SF, Fut, S, Req>,
}
} }
#[pin_project::pin_project] pin_project! {
enum State<T, R, S> #[project = StateProj]
where enum State<SF, Fut, S, Req>
T: ServiceFactory<Config = ()>, where
T::InitError: From<T::Error>, SF: ServiceFactory<Req, Config = ()>,
R: Future<Output = Result<S, T::InitError>>, SF::InitError: From<SF::Error>,
S: Service, Fut: Future<Output = Result<S, SF::InitError>>,
{ S: Service<Req>,
A(#[pin] T::Future), {
B(T::Service), A { #[pin] fut: SF::Future },
C(#[pin] R), B { svc: SF::Service },
C { #[pin] fut: Fut },
}
} }
impl<F, C, T, R, S> Future for ApplyConfigServiceFactoryResponse<F, C, T, R, S> impl<SF, Req, F, Cfg, Fut, S> Future
for ApplyConfigServiceFactoryResponse<SF, Req, F, Cfg, Fut, S>
where where
F: FnMut(C, &mut T::Service) -> R, SF: ServiceFactory<Req, Config = ()>,
T: ServiceFactory<Config = ()>, SF::InitError: From<SF::Error>,
T::InitError: From<T::Error>, F: FnMut(Cfg, &mut SF::Service) -> Fut,
R: Future<Output = Result<S, T::InitError>>, Fut: Future<Output = Result<S, SF::InitError>>,
S: Service, S: Service<Req>,
{ {
type Output = Result<S, T::InitError>; type Output = Result<S, SF::InitError>;
#[pin_project::project]
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();
#[project]
match this.state.as_mut().project() { match this.state.as_mut().project() {
State::A(fut) => match fut.poll(cx)? { StateProj::A { fut } => match fut.poll(cx)? {
Poll::Pending => Poll::Pending, Poll::Pending => Poll::Pending,
Poll::Ready(srv) => { Poll::Ready(svc) => {
this.state.set(State::B(srv)); this.state.set(State::B { svc });
self.poll(cx) self.poll(cx)
} }
}, },
State::B(srv) => match srv.poll_ready(cx)? { StateProj::B { svc } => match svc.poll_ready(cx)? {
Poll::Ready(_) => { Poll::Ready(_) => {
let fut = (this.store.get_mut().1)(this.cfg.take().unwrap(), srv); {
this.state.set(State::C(fut)); let (_, f) = &mut *this.store.borrow_mut();
let fut = f(this.cfg.take().unwrap(), svc);
this.state.set(State::C { fut });
}
self.poll(cx) self.poll(cx)
} }
Poll::Pending => Poll::Pending, Poll::Pending => Poll::Pending,
}, },
State::C(fut) => fut.poll(cx), StateProj::C { fut } => fut.poll(cx),
} }
} }
} }

View File

@@ -1,145 +1,141 @@
use std::future::Future; use alloc::boxed::Box;
use std::pin::Pin; use core::{
use std::task::{Context, Poll}; future::Future,
marker::PhantomData,
use futures_util::future::FutureExt; pin::Pin,
task::{Context, Poll},
};
use crate::{Service, ServiceFactory}; use crate::{Service, ServiceFactory};
pub type BoxFuture<I, E> = Pin<Box<dyn Future<Output = Result<I, E>>>>; pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T>>>;
pub type BoxService<Req, Res, Err> = pub type BoxService<Req, Res, Err> =
Box<dyn Service<Request = Req, Response = Res, Error = Err, Future = BoxFuture<Res, Err>>>; Box<dyn Service<Req, Response = Res, Error = Err, Future = BoxFuture<Result<Res, Err>>>>;
pub struct BoxServiceFactory<C, Req, Res, Err, InitErr>(Inner<C, Req, Res, Err, InitErr>); pub struct BoxServiceFactory<Cfg, Req, Res, Err, InitErr>(Inner<Cfg, Req, Res, Err, InitErr>);
/// Create boxed service factory /// Create boxed service factory
pub fn factory<T>( pub fn factory<SF, Req>(
factory: T, factory: SF,
) -> BoxServiceFactory<T::Config, T::Request, T::Response, T::Error, T::InitError> ) -> BoxServiceFactory<SF::Config, Req, SF::Response, SF::Error, SF::InitError>
where where
T: ServiceFactory + 'static, SF: ServiceFactory<Req> + 'static,
T::Request: 'static, Req: 'static,
T::Response: 'static, SF::Response: 'static,
T::Service: 'static, SF::Service: 'static,
T::Future: 'static, SF::Future: 'static,
T::Error: 'static, SF::Error: 'static,
T::InitError: 'static, SF::InitError: 'static,
{ {
BoxServiceFactory(Box::new(FactoryWrapper { BoxServiceFactory(Box::new(FactoryWrapper {
factory, factory,
_t: std::marker::PhantomData, _t: PhantomData,
})) }))
} }
/// Create boxed service /// Create boxed service
pub fn service<T>(service: T) -> BoxService<T::Request, T::Response, T::Error> pub fn service<S, Req>(service: S) -> BoxService<Req, S::Response, S::Error>
where where
T: Service + 'static, S: Service<Req> + 'static,
T::Future: 'static, Req: 'static,
S::Future: 'static,
{ {
Box::new(ServiceWrapper(service)) Box::new(ServiceWrapper(service, PhantomData))
} }
type Inner<C, Req, Res, Err, InitErr> = Box< type Inner<C, Req, Res, Err, InitErr> = Box<
dyn ServiceFactory< dyn ServiceFactory<
Req,
Config = C, Config = C,
Request = Req,
Response = Res, Response = Res,
Error = Err, Error = Err,
InitError = InitErr, InitError = InitErr,
Service = BoxService<Req, Res, Err>, Service = BoxService<Req, Res, Err>,
Future = BoxFuture<BoxService<Req, Res, Err>, InitErr>, Future = BoxFuture<Result<BoxService<Req, Res, Err>, InitErr>>,
>, >,
>; >;
impl<C, Req, Res, Err, InitErr> ServiceFactory for BoxServiceFactory<C, Req, Res, Err, InitErr> impl<C, Req, Res, Err, InitErr> ServiceFactory<Req>
for BoxServiceFactory<C, Req, Res, Err, InitErr>
where where
Req: 'static, Req: 'static,
Res: 'static, Res: 'static,
Err: 'static, Err: 'static,
InitErr: 'static, InitErr: 'static,
{ {
type Request = Req;
type Response = Res; type Response = Res;
type Error = Err; type Error = Err;
type InitError = InitErr; type InitError = InitErr;
type Config = C; type Config = C;
type Service = BoxService<Req, Res, Err>; type Service = BoxService<Req, Res, Err>;
type Future = BoxFuture<Self::Service, InitErr>; type Future = BoxFuture<Result<Self::Service, InitErr>>;
fn new_service(&self, cfg: C) -> Self::Future { fn new_service(&self, cfg: C) -> Self::Future {
self.0.new_service(cfg) self.0.new_service(cfg)
} }
} }
struct FactoryWrapper<C, T: ServiceFactory> { struct FactoryWrapper<SF, Req, Cfg> {
factory: T, factory: SF,
_t: std::marker::PhantomData<C>, _t: PhantomData<(Req, Cfg)>,
} }
impl<C, T, Req, Res, Err, InitErr> ServiceFactory for FactoryWrapper<C, T> impl<SF, Req, Cfg, Res, Err, InitErr> ServiceFactory<Req> for FactoryWrapper<SF, Req, Cfg>
where where
Req: 'static, Req: 'static,
Res: 'static, Res: 'static,
Err: 'static, Err: 'static,
InitErr: 'static, InitErr: 'static,
T: ServiceFactory< SF: ServiceFactory<Req, Config = Cfg, Response = Res, Error = Err, InitError = InitErr>,
Config = C, SF::Future: 'static,
Request = Req, SF::Service: 'static,
Response = Res, <SF::Service as Service<Req>>::Future: 'static,
Error = Err,
InitError = InitErr,
>,
T::Future: 'static,
T::Service: 'static,
<T::Service as Service>::Future: 'static,
{ {
type Request = Req;
type Response = Res; type Response = Res;
type Error = Err; type Error = Err;
type InitError = InitErr; type InitError = InitErr;
type Config = C; type Config = Cfg;
type Service = BoxService<Req, Res, Err>; type Service = BoxService<Req, Res, Err>;
type Future = BoxFuture<Self::Service, Self::InitError>; type Future = BoxFuture<Result<Self::Service, Self::InitError>>;
fn new_service(&self, cfg: C) -> Self::Future { fn new_service(&self, cfg: Cfg) -> Self::Future {
Box::pin( let fut = self.factory.new_service(cfg);
self.factory Box::pin(async {
.new_service(cfg) let res = fut.await;
.map(|res| res.map(ServiceWrapper::boxed)), res.map(ServiceWrapper::boxed)
) })
} }
} }
struct ServiceWrapper<T: Service>(T); struct ServiceWrapper<S: Service<Req>, Req>(S, PhantomData<Req>);
impl<T> ServiceWrapper<T> impl<S, Req> ServiceWrapper<S, Req>
where where
T: Service + 'static, S: Service<Req> + 'static,
T::Future: 'static, Req: 'static,
S::Future: 'static,
{ {
fn boxed(service: T) -> BoxService<T::Request, T::Response, T::Error> { fn boxed(service: S) -> BoxService<Req, S::Response, S::Error> {
Box::new(ServiceWrapper(service)) Box::new(ServiceWrapper(service, PhantomData))
} }
} }
impl<T, Req, Res, Err> Service for ServiceWrapper<T> impl<S, Req, Res, Err> Service<Req> for ServiceWrapper<S, Req>
where where
T: Service<Request = Req, Response = Res, Error = Err>, S: Service<Req, Response = Res, Error = Err>,
T::Future: 'static, S::Future: 'static,
{ {
type Request = Req;
type Response = Res; type Response = Res;
type Error = Err; type Error = Err;
type Future = BoxFuture<Res, Err>; type Future = BoxFuture<Result<Res, Err>>;
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(ctx) self.0.poll_ready(ctx)
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, req: Req) -> Self::Future {
Box::pin(self.0.call(req)) Box::pin(self.0.call(req))
} }
} }

View File

@@ -1,57 +0,0 @@
//! Custom cell impl, internal use only
use std::task::{Context, Poll};
use std::{cell::UnsafeCell, fmt, rc::Rc};
pub(crate) struct Cell<T> {
inner: Rc<UnsafeCell<T>>,
}
impl<T> Clone for Cell<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<T: fmt::Debug> fmt::Debug for Cell<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}
impl<T> Cell<T> {
pub(crate) fn new(inner: T) -> Self {
Self {
inner: Rc::new(UnsafeCell::new(inner)),
}
}
pub(crate) fn get_ref(&self) -> &T {
unsafe { &*self.inner.as_ref().get() }
}
pub(crate) fn get_mut(&mut self) -> &mut T {
unsafe { &mut *self.inner.as_ref().get() }
}
#[allow(clippy::mut_from_ref)]
pub(crate) unsafe fn get_mut_unsafe(&self) -> &mut T {
&mut *self.inner.as_ref().get()
}
}
impl<T: crate::Service> crate::Service for Cell<T> {
type Request = T::Request;
type Response = T::Response;
type Error = T::Error;
type Future = T::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.get_mut().poll_ready(cx)
}
fn call(&mut self, req: Self::Request) -> Self::Future {
self.get_mut().call(req)
}
}

70
actix-service/src/ext.rs Normal file
View File

@@ -0,0 +1,70 @@
use crate::{dev, Service, ServiceFactory};
pub trait ServiceExt<Req>: Service<Req> {
/// Map this service's output to a different type, returning a new service
/// of the resulting type.
///
/// This function is similar to the `Option::map` or `Iterator::map` where
/// it will change the type of the underlying service.
///
/// Note that this function consumes the receiving service and returns a
/// wrapped version of it, similar to the existing `map` methods in the
/// standard library.
fn map<F, R>(self, f: F) -> dev::Map<Self, F, Req, R>
where
Self: Sized,
F: FnMut(Self::Response) -> R,
{
dev::Map::new(self, f)
}
/// Map this service's error to a different error, returning a new service.
///
/// This function is similar to the `Result::map_err` where it will change
/// the error type of the underlying service. For example, this can be useful to
/// ensure that services have the same error type.
///
/// Note that this function consumes the receiving service and returns a
/// wrapped version of it.
fn map_err<F, E>(self, f: F) -> dev::MapErr<Self, Req, F, E>
where
Self: Sized,
F: Fn(Self::Error) -> E,
{
dev::MapErr::new(self, f)
}
}
impl<S, Req> ServiceExt<Req> for S where S: Service<Req> {}
pub trait ServiceFactoryExt<Req>: ServiceFactory<Req> {
/// Map this service's output to a different type, returning a new service
/// of the resulting type.
fn map<F, R>(self, f: F) -> crate::map::MapServiceFactory<Self, F, Req, R>
where
Self: Sized,
F: FnMut(Self::Response) -> R + Clone,
{
crate::map::MapServiceFactory::new(self, f)
}
/// Map this service's error to a different error, returning a new service.
fn map_err<F, E>(self, f: F) -> crate::map_err::MapErrServiceFactory<Self, Req, F, E>
where
Self: Sized,
F: Fn(Self::Error) -> E + Clone,
{
crate::map_err::MapErrServiceFactory::new(self, f)
}
/// Map this factory's init error to a different error, returning a new service.
fn map_init_err<F, E>(self, f: F) -> crate::map_init_err::MapInitErr<Self, F, Req, E>
where
Self: Sized,
F: Fn(Self::InitError) -> E + Clone,
{
crate::map_init_err::MapInitErr::new(self, f)
}
}
impl<S, Req> ServiceFactoryExt<Req> for S where S: ServiceFactory<Req> {}

View File

@@ -1,10 +1,6 @@
use std::future::Future; use core::{future::Future, marker::PhantomData, task::Poll};
use std::marker::PhantomData;
use std::task::{Context, Poll};
use futures_util::future::{ok, Ready}; use crate::{ok, IntoService, IntoServiceFactory, Ready, Service, ServiceFactory};
use crate::{IntoService, IntoServiceFactory, Service, ServiceFactory};
/// Create `ServiceFactory` for function that can act as a `Service` /// Create `ServiceFactory` for function that can act as a `Service`
pub fn fn_service<F, Fut, Req, Res, Err, Cfg>( pub fn fn_service<F, Fut, Req, Res, Err, Cfg>(
@@ -29,7 +25,7 @@ where
/// /// Service that divides two usize values. /// /// Service that divides two usize values.
/// async fn div((x, y): (usize, usize)) -> Result<usize, io::Error> { /// async fn div((x, y): (usize, usize)) -> Result<usize, io::Error> {
/// if y == 0 { /// if y == 0 {
/// Err(io::Error::new(io::ErrorKind::Other, "divide by zdro")) /// Err(io::Error::new(io::ErrorKind::Other, "divide by zero"))
/// } else { /// } else {
/// Ok(x / y) /// Ok(x / y)
/// } /// }
@@ -53,9 +49,11 @@ where
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```
pub fn fn_factory<F, Cfg, Srv, Fut, Err>(f: F) -> FnServiceNoConfig<F, Cfg, Srv, Fut, Err> pub fn fn_factory<F, Cfg, Srv, Req, Fut, Err>(
f: F,
) -> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
where where
Srv: Service, Srv: Service<Req>,
F: Fn() -> Fut, F: Fn() -> Fut,
Fut: Future<Output = Result<Srv, Err>>, Fut: Future<Output = Result<Srv, Err>>,
{ {
@@ -92,13 +90,13 @@ where
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```
pub fn fn_factory_with_config<F, Fut, Cfg, Srv, Err>( pub fn fn_factory_with_config<F, Fut, Cfg, Srv, Req, Err>(
f: F, f: F,
) -> FnServiceConfig<F, Fut, Cfg, Srv, Err> ) -> FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
where where
F: Fn(Cfg) -> Fut, F: Fn(Cfg) -> Fut,
Fut: Future<Output = Result<Srv, Err>>, Fut: Future<Output = Result<Srv, Err>>,
Srv: Service, Srv: Service<Req>,
{ {
FnServiceConfig::new(f) FnServiceConfig::new(f)
} }
@@ -132,26 +130,23 @@ where
} }
} }
impl<F, Fut, Req, Res, Err> Service for FnService<F, Fut, Req, Res, Err> impl<F, Fut, Req, Res, Err> Service<Req> for FnService<F, Fut, Req, Res, Err>
where where
F: FnMut(Req) -> Fut, F: FnMut(Req) -> Fut,
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
type Request = Req;
type Response = Res; type Response = Res;
type Error = Err; type Error = Err;
type Future = Fut; type Future = Fut;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { crate::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Req) -> Self::Future { fn call(&mut self, req: Req) -> Self::Future {
(self.f)(req) (self.f)(req)
} }
} }
impl<F, Fut, Req, Res, Err> IntoService<FnService<F, Fut, Req, Res, Err>> for F impl<F, Fut, Req, Res, Err> IntoService<FnService<F, Fut, Req, Res, Err>, Req> for F
where where
F: FnMut(Req) -> Fut, F: FnMut(Req) -> Fut,
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
@@ -190,31 +185,28 @@ where
} }
} }
impl<F, Fut, Req, Res, Err> Service for FnServiceFactory<F, Fut, Req, Res, Err, ()> impl<F, Fut, Req, Res, Err> Service<Req> for FnServiceFactory<F, Fut, Req, Res, Err, ()>
where where
F: FnMut(Req) -> Fut + Clone, F: FnMut(Req) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
type Request = Req;
type Response = Res; type Response = Res;
type Error = Err; type Error = Err;
type Future = Fut; type Future = Fut;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { crate::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, req: Req) -> Self::Future {
(self.f)(req) (self.f)(req)
} }
} }
impl<F, Fut, Req, Res, Err, Cfg> ServiceFactory for FnServiceFactory<F, Fut, Req, Res, Err, Cfg> impl<F, Fut, Req, Res, Err, Cfg> ServiceFactory<Req>
for FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
where where
F: FnMut(Req) -> Fut + Clone, F: FnMut(Req) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
type Request = Req;
type Response = Res; type Response = Res;
type Error = Err; type Error = Err;
@@ -229,7 +221,7 @@ where
} }
impl<F, Fut, Req, Res, Err, Cfg> impl<F, Fut, Req, Res, Err, Cfg>
IntoServiceFactory<FnServiceFactory<F, Fut, Req, Res, Err, Cfg>> for F IntoServiceFactory<FnServiceFactory<F, Fut, Req, Res, Err, Cfg>, Req> for F
where where
F: Fn(Req) -> Fut + Clone, F: Fn(Req) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
@@ -240,32 +232,32 @@ where
} }
/// Convert `Fn(&Config) -> Future<Service>` fn to NewService /// Convert `Fn(&Config) -> Future<Service>` fn to NewService
pub struct FnServiceConfig<F, Fut, Cfg, Srv, Err> pub struct FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
where where
F: Fn(Cfg) -> Fut, F: Fn(Cfg) -> Fut,
Fut: Future<Output = Result<Srv, Err>>, Fut: Future<Output = Result<Srv, Err>>,
Srv: Service, Srv: Service<Req>,
{ {
f: F, f: F,
_t: PhantomData<(Fut, Cfg, Srv, Err)>, _t: PhantomData<(Fut, Cfg, Req, Srv, Err)>,
} }
impl<F, Fut, Cfg, Srv, Err> FnServiceConfig<F, Fut, Cfg, Srv, Err> impl<F, Fut, Cfg, Srv, Req, Err> FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
where where
F: Fn(Cfg) -> Fut, F: Fn(Cfg) -> Fut,
Fut: Future<Output = Result<Srv, Err>>, Fut: Future<Output = Result<Srv, Err>>,
Srv: Service, Srv: Service<Req>,
{ {
fn new(f: F) -> Self { fn new(f: F) -> Self {
FnServiceConfig { f, _t: PhantomData } FnServiceConfig { f, _t: PhantomData }
} }
} }
impl<F, Fut, Cfg, Srv, Err> Clone for FnServiceConfig<F, Fut, Cfg, Srv, Err> impl<F, Fut, Cfg, Srv, Req, Err> Clone for FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
where where
F: Fn(Cfg) -> Fut + Clone, F: Fn(Cfg) -> Fut + Clone,
Fut: Future<Output = Result<Srv, Err>>, Fut: Future<Output = Result<Srv, Err>>,
Srv: Service, Srv: Service<Req>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
FnServiceConfig { FnServiceConfig {
@@ -275,13 +267,13 @@ where
} }
} }
impl<F, Fut, Cfg, Srv, Err> ServiceFactory for FnServiceConfig<F, Fut, Cfg, Srv, Err> impl<F, Fut, Cfg, Srv, Req, Err> ServiceFactory<Req>
for FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
where where
F: Fn(Cfg) -> Fut, F: Fn(Cfg) -> Fut,
Fut: Future<Output = Result<Srv, Err>>, Fut: Future<Output = Result<Srv, Err>>,
Srv: Service, Srv: Service<Req>,
{ {
type Request = Srv::Request;
type Response = Srv::Response; type Response = Srv::Response;
type Error = Srv::Error; type Error = Srv::Error;
@@ -296,76 +288,77 @@ where
} }
/// Converter for `Fn() -> Future<Service>` fn /// Converter for `Fn() -> Future<Service>` fn
pub struct FnServiceNoConfig<F, C, S, R, E> pub struct FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
where where
F: Fn() -> R, F: Fn() -> Fut,
S: Service, Srv: Service<Req>,
R: Future<Output = Result<S, E>>, Fut: Future<Output = Result<Srv, Err>>,
{ {
f: F, f: F,
_t: PhantomData<C>, _t: PhantomData<(Cfg, Req)>,
} }
impl<F, C, S, R, E> FnServiceNoConfig<F, C, S, R, E> impl<F, Cfg, Srv, Req, Fut, Err> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
where where
F: Fn() -> R, F: Fn() -> Fut,
R: Future<Output = Result<S, E>>, Fut: Future<Output = Result<Srv, Err>>,
S: Service, Srv: Service<Req>,
{ {
fn new(f: F) -> Self { fn new(f: F) -> Self {
Self { f, _t: PhantomData } Self { f, _t: PhantomData }
} }
} }
impl<F, C, S, R, E> ServiceFactory for FnServiceNoConfig<F, C, S, R, E> impl<F, Cfg, Srv, Req, Fut, Err> ServiceFactory<Req>
for FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
where where
F: Fn() -> R, F: Fn() -> Fut,
R: Future<Output = Result<S, E>>, Fut: Future<Output = Result<Srv, Err>>,
S: Service, Srv: Service<Req>,
{ {
type Request = S::Request; type Response = Srv::Response;
type Response = S::Response; type Error = Srv::Error;
type Error = S::Error; type Service = Srv;
type Service = S; type Config = Cfg;
type Config = C; type InitError = Err;
type InitError = E; type Future = Fut;
type Future = R;
fn new_service(&self, _: C) -> Self::Future { fn new_service(&self, _: Cfg) -> Self::Future {
(self.f)() (self.f)()
} }
} }
impl<F, C, S, R, E> Clone for FnServiceNoConfig<F, C, S, R, E> impl<F, Cfg, Srv, Req, Fut, Err> Clone for FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
where where
F: Fn() -> R + Clone, F: Fn() -> Fut + Clone,
R: Future<Output = Result<S, E>>, Fut: Future<Output = Result<Srv, Err>>,
S: Service, Srv: Service<Req>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self::new(self.f.clone()) Self::new(self.f.clone())
} }
} }
impl<F, C, S, R, E> IntoServiceFactory<FnServiceNoConfig<F, C, S, R, E>> for F impl<F, Cfg, Srv, Req, Fut, Err>
IntoServiceFactory<FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>, Req> for F
where where
F: Fn() -> R, F: Fn() -> Fut,
R: Future<Output = Result<S, E>>, Fut: Future<Output = Result<Srv, Err>>,
S: Service, Srv: Service<Req>,
{ {
fn into_factory(self) -> FnServiceNoConfig<F, C, S, R, E> { fn into_factory(self) -> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err> {
FnServiceNoConfig::new(self) FnServiceNoConfig::new(self)
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::task::Poll; use core::task::Poll;
use futures_util::future::{lazy, ok}; use futures_util::future::lazy;
use super::*; use super::*;
use crate::{Service, ServiceFactory}; use crate::{ok, Service, ServiceFactory};
#[actix_rt::test] #[actix_rt::test]
async fn test_fn_service() { async fn test_fn_service() {

View File

@@ -1,50 +1,64 @@
#![deny(rust_2018_idioms, warnings)] //! See [`Service`] docs for information on this crate's foundational trait.
#![allow(clippy::type_complexity)]
use std::cell::RefCell; #![no_std]
use std::future::Future; #![deny(rust_2018_idioms, nonstandard_style)]
use std::rc::Rc; #![allow(clippy::type_complexity)]
use std::sync::Arc; #![doc(html_logo_url = "https://actix.rs/img/logo.png")]
use std::task::{self, Context, Poll}; #![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
extern crate alloc;
use alloc::{boxed::Box, rc::Rc, sync::Arc};
use core::{
cell::RefCell,
future::Future,
task::{self, Context, Poll},
};
mod and_then; mod and_then;
mod and_then_apply_fn;
mod apply; mod apply;
mod apply_cfg; mod apply_cfg;
pub mod boxed; pub mod boxed;
mod cell; mod ext;
mod fn_service; mod fn_service;
mod map; mod map;
mod map_config; mod map_config;
mod map_err; mod map_err;
mod map_init_err; mod map_init_err;
mod pipeline; mod pipeline;
mod ready;
mod then; mod then;
mod transform; mod transform;
mod transform_err; mod transform_err;
pub use self::apply::{apply_fn, apply_fn_factory}; pub use self::apply::{apply_fn, apply_fn_factory};
pub use self::apply_cfg::{apply_cfg, apply_cfg_factory}; pub use self::apply_cfg::{apply_cfg, apply_cfg_factory};
pub use self::ext::{ServiceExt, ServiceFactoryExt};
pub use self::fn_service::{fn_factory, fn_factory_with_config, fn_service}; pub use self::fn_service::{fn_factory, fn_factory_with_config, fn_service};
pub use self::map_config::{map_config, unit_config}; pub use self::map_config::{map_config, unit_config};
pub use self::pipeline::{pipeline, pipeline_factory, Pipeline, PipelineFactory}; pub use self::pipeline::{pipeline, pipeline_factory, Pipeline, PipelineFactory};
pub use self::transform::{apply, Transform}; pub use self::transform::{apply, Transform};
/// An asynchronous function from `Request` to a `Response`. #[allow(unused_imports)]
use self::ready::{err, ok, ready, Ready};
/// An asynchronous operation from `Request` to a `Response`.
/// ///
/// `Service` represents a service that represanting interation, taking requests and giving back /// The `Service` trait models a request/response interaction, receiving requests and returning
/// replies. You can think about service as a function with one argument and result as a return /// replies. You can think about a service as a function with one argument that returns some result
/// type. In general form it looks like `async fn(Req) -> Result<Res, Err>`. `Service` /// asynchronously. Conceptually, the operation looks like this:
/// trait just generalizing form of this function. Each parameter described as an assotiated type.
/// ///
/// Services provides a symmetric and uniform API, same abstractions represents /// ```rust,ignore
/// clients and servers. Services describe only `transforamtion` operation /// async fn(Request) -> Result<Response, Err>
/// which encorouge to simplify api surface and phrases `value transformation`. /// ```
/// That leads to simplier design of each service. That also allows better testability
/// and better composition.
/// ///
/// Services could be represented in several different forms. In general, /// The `Service` trait just generalizes this form where each parameter is described as an
/// Service is a type that implements `Service` trait. /// associated type on the trait. Services can also have mutable state that influence computation.
///
/// `Service` provides a symmetric and uniform API; the same abstractions can be used to represent
/// both clients and servers. Services describe only _transformation_ operations which encourage
/// simple API surfaces. This leads to simpler design of each service, improves test-ability and
/// makes composition easier.
/// ///
/// ```rust,ignore /// ```rust,ignore
/// struct MyService; /// struct MyService;
@@ -53,7 +67,7 @@ pub use self::transform::{apply, Transform};
/// type Request = u8; /// type Request = u8;
/// type Response = u64; /// type Response = u64;
/// type Error = MyError; /// type Error = MyError;
/// type Future = Pin<Box<Future<Output=Result<Self::Response, Self::Error>>>; /// type Future = Pin<Box<Future<Output=Result<Self::Response, Self::Error>>>>;
/// ///
/// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { ... } /// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { ... }
/// ///
@@ -61,20 +75,17 @@ pub use self::transform::{apply, Transform};
/// } /// }
/// ``` /// ```
/// ///
/// Service can have mutable state that influence computation. /// Sometimes it is not necessary to implement the Service trait. For example, the above service
/// This service could be rewritten as a simple function: /// could be rewritten as a simple function and passed to [fn_service](fn_service()).
/// ///
/// ```rust,ignore /// ```rust,ignore
/// async fn my_service(req: u8) -> Result<u64, MyError>; /// async fn my_service(req: u8) -> Result<u64, MyError>;
/// ``` /// ```
pub trait Service { pub trait Service<Req> {
/// Requests handled by the service.
type Request;
/// Responses given by the service. /// Responses given by the service.
type Response; type Response;
/// Errors produced by the service. /// Errors produced by the service when polling readiness or executing call.
type Error; type Error;
/// The future response value. /// The future response value.
@@ -90,11 +101,9 @@ pub trait Service {
/// It is permitted for the service to return `Ready` from a `poll_ready` /// It is permitted for the service to return `Ready` from a `poll_ready`
/// call and the next invocation of `call` results in an error. /// call and the next invocation of `call` results in an error.
/// ///
/// There are several notes to consider: /// # Notes
///
/// 1. `.poll_ready()` might be called on different task from actual service call. /// 1. `.poll_ready()` might be called on different task from actual service call.
/// /// 1. In case of chained services, `.poll_ready()` get called for all services at once.
/// 2. In case of chained services, `.poll_ready()` get called for all services at once.
fn poll_ready(&mut self, ctx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>; fn poll_ready(&mut self, ctx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>;
/// Process the request and return the response asynchronously. /// Process the request and return the response asynchronously.
@@ -106,114 +115,45 @@ pub trait Service {
/// ///
/// Calling `call` without calling `poll_ready` is permitted. The /// Calling `call` without calling `poll_ready` is permitted. The
/// implementation must be resilient to this fact. /// implementation must be resilient to this fact.
fn call(&mut self, req: Self::Request) -> Self::Future; fn call(&mut self, req: Req) -> Self::Future;
/// Map this service's output to a different type, returning a new service
/// of the resulting type.
///
/// This function is similar to the `Option::map` or `Iterator::map` where
/// it will change the type of the underlying service.
///
/// Note that this function consumes the receiving service and returns a
/// wrapped version of it, similar to the existing `map` methods in the
/// standard library.
fn map<F, R>(self, f: F) -> crate::dev::Map<Self, F, R>
where
Self: Sized,
F: FnMut(Self::Response) -> R,
{
crate::dev::Map::new(self, f)
}
/// Map this service's error to a different error, returning a new service.
///
/// This function is similar to the `Result::map_err` where it will change
/// the error type of the underlying service. This is useful for example to
/// ensure that services have the same error type.
///
/// Note that this function consumes the receiving service and returns a
/// wrapped version of it.
fn map_err<F, E>(self, f: F) -> crate::dev::MapErr<Self, F, E>
where
Self: Sized,
F: Fn(Self::Error) -> E,
{
crate::dev::MapErr::new(self, f)
}
} }
/// Creates new `Service` values. /// Factory for creating `Service`s.
/// ///
/// Acts as a service factory. This is useful for cases where new `Service` /// Acts as a service factory. This is useful for cases where new `Service`s
/// values must be produced. One case is a TCP server listener. The listener /// must be produced. One case is a TCP server listener. The listener
/// accepts new TCP streams, obtains a new `Service` value using the /// accepts new TCP streams, obtains a new `Service` using the
/// `ServiceFactory` trait, and uses that new `Service` value to process inbound /// `ServiceFactory` trait, and uses the new `Service` to process inbound
/// requests on that new TCP stream. /// requests on that new TCP stream.
/// ///
/// `Config` is a service factory configuration type. /// `Config` is a service factory configuration type.
pub trait ServiceFactory { pub trait ServiceFactory<Req> {
/// Requests handled by the service. /// Responses given by the created services.
type Request;
/// Responses given by the service
type Response; type Response;
/// Errors produced by the service /// Errors produced by the created services.
type Error; type Error;
/// Service factory configuration /// Service factory configuration.
type Config; type Config;
/// The `Service` value created by this factory /// The kind of `Service` created by this factory.
type Service: Service< type Service: Service<Req, Response = Self::Response, Error = Self::Error>;
Request = Self::Request,
Response = Self::Response,
Error = Self::Error,
>;
/// Errors produced while building a service. /// Errors potentially raised while building a service.
type InitError; type InitError;
/// The future of the `Service` instance. /// The future of the `Service` instance.
type Future: Future<Output = Result<Self::Service, Self::InitError>>; type Future: Future<Output = Result<Self::Service, Self::InitError>>;
/// Create and return a new service value asynchronously. /// Create and return a new service asynchronously.
fn new_service(&self, cfg: Self::Config) -> Self::Future; fn new_service(&self, cfg: Self::Config) -> Self::Future;
/// Map this service's output to a different type, returning a new service
/// of the resulting type.
fn map<F, R>(self, f: F) -> crate::map::MapServiceFactory<Self, F, R>
where
Self: Sized,
F: FnMut(Self::Response) -> R + Clone,
{
crate::map::MapServiceFactory::new(self, f)
}
/// Map this service's error to a different error, returning a new service.
fn map_err<F, E>(self, f: F) -> crate::map_err::MapErrServiceFactory<Self, F, E>
where
Self: Sized,
F: Fn(Self::Error) -> E + Clone,
{
crate::map_err::MapErrServiceFactory::new(self, f)
}
/// Map this factory's init error to a different error, returning a new service.
fn map_init_err<F, E>(self, f: F) -> crate::map_init_err::MapInitErr<Self, F, E>
where
Self: Sized,
F: Fn(Self::InitError) -> E + Clone,
{
crate::map_init_err::MapInitErr::new(self, f)
}
} }
impl<'a, S> Service for &'a mut S impl<'a, S, Req> Service<Req> for &'a mut S
where where
S: Service + 'a, S: Service<Req> + 'a,
{ {
type Request = S::Request;
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Future = S::Future; type Future = S::Future;
@@ -222,16 +162,15 @@ where
(**self).poll_ready(ctx) (**self).poll_ready(ctx)
} }
fn call(&mut self, request: Self::Request) -> S::Future { fn call(&mut self, request: Req) -> S::Future {
(**self).call(request) (**self).call(request)
} }
} }
impl<S> Service for Box<S> impl<S, Req> Service<Req> for Box<S>
where where
S: Service + ?Sized, S: Service<Req> + ?Sized,
{ {
type Request = S::Request;
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Future = S::Future; type Future = S::Future;
@@ -240,16 +179,15 @@ where
(**self).poll_ready(ctx) (**self).poll_ready(ctx)
} }
fn call(&mut self, request: Self::Request) -> S::Future { fn call(&mut self, request: Req) -> S::Future {
(**self).call(request) (**self).call(request)
} }
} }
impl<S> Service for RefCell<S> impl<S, Req> Service<Req> for RefCell<S>
where where
S: Service, S: Service<Req>,
{ {
type Request = S::Request;
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Future = S::Future; type Future = S::Future;
@@ -258,16 +196,15 @@ where
self.borrow_mut().poll_ready(ctx) self.borrow_mut().poll_ready(ctx)
} }
fn call(&mut self, request: Self::Request) -> S::Future { fn call(&mut self, request: Req) -> S::Future {
self.borrow_mut().call(request) self.borrow_mut().call(request)
} }
} }
impl<S> Service for Rc<RefCell<S>> impl<S, Req> Service<Req> for Rc<RefCell<S>>
where where
S: Service, S: Service<Req>,
{ {
type Request = S::Request;
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Future = S::Future; type Future = S::Future;
@@ -276,16 +213,15 @@ where
self.borrow_mut().poll_ready(ctx) self.borrow_mut().poll_ready(ctx)
} }
fn call(&mut self, request: Self::Request) -> S::Future { fn call(&mut self, request: Req) -> S::Future {
(&mut (**self).borrow_mut()).call(request) (&mut (**self).borrow_mut()).call(request)
} }
} }
impl<S> ServiceFactory for Rc<S> impl<S, Req> ServiceFactory<Req> for Rc<S>
where where
S: ServiceFactory, S: ServiceFactory<Req>,
{ {
type Request = S::Request;
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Config = S::Config; type Config = S::Config;
@@ -298,11 +234,10 @@ where
} }
} }
impl<S> ServiceFactory for Arc<S> impl<S, Req> ServiceFactory<Req> for Arc<S>
where where
S: ServiceFactory, S: ServiceFactory<Req>,
{ {
type Request = S::Request;
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Config = S::Config; type Config = S::Config;
@@ -316,52 +251,52 @@ where
} }
/// Trait for types that can be converted to a `Service` /// Trait for types that can be converted to a `Service`
pub trait IntoService<T> pub trait IntoService<S, Req>
where where
T: Service, S: Service<Req>,
{ {
/// Convert to a `Service` /// Convert to a `Service`
fn into_service(self) -> T; fn into_service(self) -> S;
} }
/// Trait for types that can be converted to a `ServiceFactory` /// Trait for types that can be converted to a `ServiceFactory`
pub trait IntoServiceFactory<T> pub trait IntoServiceFactory<SF, Req>
where where
T: ServiceFactory, SF: ServiceFactory<Req>,
{ {
/// Convert `Self` to a `ServiceFactory` /// Convert `Self` to a `ServiceFactory`
fn into_factory(self) -> T; fn into_factory(self) -> SF;
} }
impl<T> IntoService<T> for T impl<S, Req> IntoService<S, Req> for S
where where
T: Service, S: Service<Req>,
{ {
fn into_service(self) -> T { fn into_service(self) -> S {
self self
} }
} }
impl<T> IntoServiceFactory<T> for T impl<SF, Req> IntoServiceFactory<SF, Req> for SF
where where
T: ServiceFactory, SF: ServiceFactory<Req>,
{ {
fn into_factory(self) -> T { fn into_factory(self) -> SF {
self self
} }
} }
/// Convert object of type `T` to a service `S` /// Convert object of type `U` to a service `S`
pub fn into_service<T, S>(tp: T) -> S pub fn into_service<I, S, Req>(tp: I) -> S
where where
S: Service, I: IntoService<S, Req>,
T: IntoService<S>, S: Service<Req>,
{ {
tp.into_service() tp.into_service()
} }
pub mod dev { pub mod dev {
pub use crate::apply::{Apply, ApplyServiceFactory}; pub use crate::apply::{Apply, ApplyFactory};
pub use crate::fn_service::{ pub use crate::fn_service::{
FnService, FnServiceConfig, FnServiceFactory, FnServiceNoConfig, FnService, FnServiceConfig, FnServiceFactory, FnServiceNoConfig,
}; };
@@ -372,3 +307,27 @@ pub mod dev {
pub use crate::transform::ApplyTransform; pub use crate::transform::ApplyTransform;
pub use crate::transform_err::TransformMapInitErr; pub use crate::transform_err::TransformMapInitErr;
} }
#[macro_export]
macro_rules! always_ready {
() => {
fn poll_ready(
&mut self,
_: &mut ::core::task::Context<'_>,
) -> ::core::task::Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
};
}
#[macro_export]
macro_rules! forward_ready {
($field:ident) => {
fn poll_ready(
&mut self,
cx: &mut ::core::task::Context<'_>,
) -> ::core::task::Poll<Result<(), Self::Error>> {
self.$field.poll_ready(cx)
}
};
}

View File

@@ -1,25 +1,29 @@
use std::future::Future; use core::{
use std::marker::PhantomData; future::Future,
use std::pin::Pin; marker::PhantomData,
use std::task::{Context, Poll}; pin::Pin,
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use super::{Service, ServiceFactory}; use super::{Service, ServiceFactory};
/// Service for the `map` combinator, changing the type of a service's response. /// Service for the `map` combinator, changing the type of a service's response.
/// ///
/// This is created by the `ServiceExt::map` method. /// This is created by the `ServiceExt::map` method.
pub struct Map<A, F, Response> { pub struct Map<A, F, Req, Res> {
service: A, service: A,
f: F, f: F,
_t: PhantomData<Response>, _t: PhantomData<(Req, Res)>,
} }
impl<A, F, Response> Map<A, F, Response> { impl<A, F, Req, Res> Map<A, F, Req, Res> {
/// Create new `Map` combinator /// Create new `Map` combinator
pub(crate) fn new(service: A, f: F) -> Self pub(crate) fn new(service: A, f: F) -> Self
where where
A: Service, A: Service<Req>,
F: FnMut(A::Response) -> Response, F: FnMut(A::Response) -> Res,
{ {
Self { Self {
service, service,
@@ -29,7 +33,7 @@ impl<A, F, Response> Map<A, F, Response> {
} }
} }
impl<A, F, Response> Clone for Map<A, F, Response> impl<A, F, Req, Res> Clone for Map<A, F, Req, Res>
where where
A: Clone, A: Clone,
F: Clone, F: Clone,
@@ -43,52 +47,50 @@ where
} }
} }
impl<A, F, Response> Service for Map<A, F, Response> impl<A, F, Req, Res> Service<Req> for Map<A, F, Req, Res>
where where
A: Service, A: Service<Req>,
F: FnMut(A::Response) -> Response + Clone, F: FnMut(A::Response) -> Res + Clone,
{ {
type Request = A::Request; type Response = Res;
type Response = Response;
type Error = A::Error; type Error = A::Error;
type Future = MapFuture<A, F, Response>; type Future = MapFuture<A, F, Req, Res>;
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { crate::forward_ready!(service);
self.service.poll_ready(ctx)
}
fn call(&mut self, req: A::Request) -> Self::Future { fn call(&mut self, req: Req) -> Self::Future {
MapFuture::new(self.service.call(req), self.f.clone()) MapFuture::new(self.service.call(req), self.f.clone())
} }
} }
#[pin_project::pin_project] pin_project! {
pub struct MapFuture<A, F, Response> pub struct MapFuture<A, F, Req, Res>
where where
A: Service, A: Service<Req>,
F: FnMut(A::Response) -> Response, F: FnMut(A::Response) -> Res,
{ {
f: F, f: F,
#[pin] #[pin]
fut: A::Future, fut: A::Future,
}
} }
impl<A, F, Response> MapFuture<A, F, Response> impl<A, F, Req, Res> MapFuture<A, F, Req, Res>
where where
A: Service, A: Service<Req>,
F: FnMut(A::Response) -> Response, F: FnMut(A::Response) -> Res,
{ {
fn new(fut: A::Future, f: F) -> Self { fn new(fut: A::Future, f: F) -> Self {
MapFuture { f, fut } MapFuture { f, fut }
} }
} }
impl<A, F, Response> Future for MapFuture<A, F, Response> impl<A, F, Req, Res> Future for MapFuture<A, F, Req, Res>
where where
A: Service, A: Service<Req>,
F: FnMut(A::Response) -> Response, F: FnMut(A::Response) -> Res,
{ {
type Output = Result<Response, A::Error>; type Output = Result<Res, A::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.project(); let this = self.project();
@@ -102,17 +104,17 @@ where
} }
/// `MapNewService` new service combinator /// `MapNewService` new service combinator
pub struct MapServiceFactory<A, F, Res> { pub struct MapServiceFactory<A, F, Req, Res> {
a: A, a: A,
f: F, f: F,
r: PhantomData<Res>, r: PhantomData<(Res, Req)>,
} }
impl<A, F, Res> MapServiceFactory<A, F, Res> { impl<A, F, Req, Res> MapServiceFactory<A, F, Req, Res> {
/// Create new `Map` new service instance /// Create new `Map` new service instance
pub(crate) fn new(a: A, f: F) -> Self pub(crate) fn new(a: A, f: F) -> Self
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: FnMut(A::Response) -> Res, F: FnMut(A::Response) -> Res,
{ {
Self { Self {
@@ -123,7 +125,7 @@ impl<A, F, Res> MapServiceFactory<A, F, Res> {
} }
} }
impl<A, F, Res> Clone for MapServiceFactory<A, F, Res> impl<A, F, Req, Res> Clone for MapServiceFactory<A, F, Req, Res>
where where
A: Clone, A: Clone,
F: Clone, F: Clone,
@@ -137,39 +139,39 @@ where
} }
} }
impl<A, F, Res> ServiceFactory for MapServiceFactory<A, F, Res> impl<A, F, Req, Res> ServiceFactory<Req> for MapServiceFactory<A, F, Req, Res>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: FnMut(A::Response) -> Res + Clone, F: FnMut(A::Response) -> Res + Clone,
{ {
type Request = A::Request;
type Response = Res; type Response = Res;
type Error = A::Error; type Error = A::Error;
type Config = A::Config; type Config = A::Config;
type Service = Map<A::Service, F, Res>; type Service = Map<A::Service, F, Req, Res>;
type InitError = A::InitError; type InitError = A::InitError;
type Future = MapServiceFuture<A, F, Res>; type Future = MapServiceFuture<A, F, Req, Res>;
fn new_service(&self, cfg: A::Config) -> Self::Future { fn new_service(&self, cfg: A::Config) -> Self::Future {
MapServiceFuture::new(self.a.new_service(cfg), self.f.clone()) MapServiceFuture::new(self.a.new_service(cfg), self.f.clone())
} }
} }
#[pin_project::pin_project] pin_project! {
pub struct MapServiceFuture<A, F, Res> pub struct MapServiceFuture<A, F, Req, Res>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: FnMut(A::Response) -> Res, F: FnMut(A::Response) -> Res,
{ {
#[pin] #[pin]
fut: A::Future, fut: A::Future,
f: Option<F>, f: Option<F>,
}
} }
impl<A, F, Res> MapServiceFuture<A, F, Res> impl<A, F, Req, Res> MapServiceFuture<A, F, Req, Res>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: FnMut(A::Response) -> Res, F: FnMut(A::Response) -> Res,
{ {
fn new(fut: A::Future, f: F) -> Self { fn new(fut: A::Future, f: F) -> Self {
@@ -177,12 +179,12 @@ where
} }
} }
impl<A, F, Res> Future for MapServiceFuture<A, F, Res> impl<A, F, Req, Res> Future for MapServiceFuture<A, F, Req, Res>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: FnMut(A::Response) -> Res, F: FnMut(A::Response) -> Res,
{ {
type Output = Result<Map<A::Service, F, Res>, A::InitError>; type Output = Result<Map<A::Service, F, Req, Res>, A::InitError>;
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.project(); let this = self.project();
@@ -197,22 +199,21 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use futures_util::future::{lazy, ok, Ready}; use futures_util::future::lazy;
use super::*; use super::*;
use crate::{IntoServiceFactory, Service, ServiceFactory}; use crate::{
ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory, ServiceFactoryExt,
};
struct Srv; struct Srv;
impl Service for Srv { impl Service<()> for Srv {
type Request = ();
type Response = (); type Response = ();
type Error = (); type Error = ();
type Future = Ready<Result<(), ()>>; type Future = Ready<Result<(), ()>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { crate::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, _: ()) -> Self::Future { fn call(&mut self, _: ()) -> Self::Future {
ok(()) ok(())

View File

@@ -1,4 +1,4 @@
use std::marker::PhantomData; use core::marker::PhantomData;
use super::{IntoServiceFactory, ServiceFactory}; use super::{IntoServiceFactory, ServiceFactory};
@@ -6,121 +6,123 @@ use super::{IntoServiceFactory, ServiceFactory};
/// ///
/// Note that this function consumes the receiving service factory and returns /// Note that this function consumes the receiving service factory and returns
/// a wrapped version of it. /// a wrapped version of it.
pub fn map_config<T, U, F, C>(factory: U, f: F) -> MapConfig<T, F, C> pub fn map_config<I, SF, S, Req, F, Cfg>(factory: I, f: F) -> MapConfig<SF, Req, F, Cfg>
where where
T: ServiceFactory, I: IntoServiceFactory<SF, Req>,
U: IntoServiceFactory<T>, SF: ServiceFactory<Req>,
F: Fn(C) -> T::Config, F: Fn(Cfg) -> SF::Config,
{ {
MapConfig::new(factory.into_factory(), f) MapConfig::new(factory.into_factory(), f)
} }
/// Replace config with unit /// Replace config with unit.
pub fn unit_config<T, U, C>(factory: U) -> UnitConfig<T, C> pub fn unit_config<I, SF, Cfg, Req>(factory: I) -> UnitConfig<SF, Cfg, Req>
where where
T: ServiceFactory<Config = ()>, I: IntoServiceFactory<SF, Req>,
U: IntoServiceFactory<T>, SF: ServiceFactory<Req, Config = ()>,
{ {
UnitConfig::new(factory.into_factory()) UnitConfig::new(factory.into_factory())
} }
/// `map_config()` adapter service factory /// `map_config()` adapter service factory
pub struct MapConfig<A, F, C> { pub struct MapConfig<SF, Req, F, Cfg> {
a: A, factory: SF,
f: F, cfg_mapper: F,
e: PhantomData<C>, e: PhantomData<(Cfg, Req)>,
} }
impl<A, F, C> MapConfig<A, F, C> { impl<SF, Req, F, Cfg> MapConfig<SF, Req, F, Cfg> {
/// Create new `MapConfig` combinator /// Create new `MapConfig` combinator
pub(crate) fn new(a: A, f: F) -> Self pub(crate) fn new(factory: SF, cfg_mapper: F) -> Self
where where
A: ServiceFactory, SF: ServiceFactory<Req>,
F: Fn(C) -> A::Config, F: Fn(Cfg) -> SF::Config,
{ {
Self { Self {
a, factory,
f, cfg_mapper,
e: PhantomData, e: PhantomData,
} }
} }
} }
impl<A, F, C> Clone for MapConfig<A, F, C> impl<SF, Req, F, Cfg> Clone for MapConfig<SF, Req, F, Cfg>
where where
A: Clone, SF: Clone,
F: Clone, F: Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
a: self.a.clone(), factory: self.factory.clone(),
f: self.f.clone(), cfg_mapper: self.cfg_mapper.clone(),
e: PhantomData, e: PhantomData,
} }
} }
} }
impl<A, F, C> ServiceFactory for MapConfig<A, F, C> impl<SF, Req, F, Cfg> ServiceFactory<Req> for MapConfig<SF, Req, F, Cfg>
where where
A: ServiceFactory, SF: ServiceFactory<Req>,
F: Fn(C) -> A::Config, F: Fn(Cfg) -> SF::Config,
{ {
type Request = A::Request; type Response = SF::Response;
type Response = A::Response; type Error = SF::Error;
type Error = A::Error;
type Config = C; type Config = Cfg;
type Service = A::Service; type Service = SF::Service;
type InitError = A::InitError; type InitError = SF::InitError;
type Future = A::Future; type Future = SF::Future;
fn new_service(&self, cfg: C) -> Self::Future { fn new_service(&self, cfg: Self::Config) -> Self::Future {
self.a.new_service((self.f)(cfg)) let mapped_cfg = (self.cfg_mapper)(cfg);
self.factory.new_service(mapped_cfg)
} }
} }
/// `unit_config()` config combinator /// `unit_config()` config combinator
pub struct UnitConfig<A, C> { pub struct UnitConfig<SF, Cfg, Req> {
a: A, factory: SF,
e: PhantomData<C>, _phantom: PhantomData<(Cfg, Req)>,
} }
impl<A, C> UnitConfig<A, C> impl<SF, Cfg, Req> UnitConfig<SF, Cfg, Req>
where where
A: ServiceFactory<Config = ()>, SF: ServiceFactory<Req, Config = ()>,
{ {
/// Create new `UnitConfig` combinator /// Create new `UnitConfig` combinator
pub(crate) fn new(a: A) -> Self { pub(crate) fn new(factory: SF) -> Self {
Self { a, e: PhantomData }
}
}
impl<A, C> Clone for UnitConfig<A, C>
where
A: Clone,
{
fn clone(&self) -> Self {
Self { Self {
a: self.a.clone(), factory,
e: PhantomData, _phantom: PhantomData,
} }
} }
} }
impl<A, C> ServiceFactory for UnitConfig<A, C> impl<SF, Cfg, Req> Clone for UnitConfig<SF, Cfg, Req>
where where
A: ServiceFactory<Config = ()>, SF: Clone,
{ {
type Request = A::Request; fn clone(&self) -> Self {
type Response = A::Response; Self {
type Error = A::Error; factory: self.factory.clone(),
_phantom: PhantomData,
type Config = C; }
type Service = A::Service; }
type InitError = A::InitError; }
type Future = A::Future;
impl<SF, Cfg, Req> ServiceFactory<Req> for UnitConfig<SF, Cfg, Req>
fn new_service(&self, _: C) -> Self::Future { where
self.a.new_service(()) SF: ServiceFactory<Req, Config = ()>,
{
type Response = SF::Response;
type Error = SF::Error;
type Config = Cfg;
type Service = SF::Service;
type InitError = SF::InitError;
type Future = SF::Future;
fn new_service(&self, _: Cfg) -> Self::Future {
self.factory.new_service(())
} }
} }

View File

@@ -1,7 +1,11 @@
use std::future::Future; use core::{
use std::marker::PhantomData; future::Future,
use std::pin::Pin; marker::PhantomData,
use std::task::{Context, Poll}; pin::Pin,
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use super::{Service, ServiceFactory}; use super::{Service, ServiceFactory};
@@ -9,18 +13,18 @@ use super::{Service, ServiceFactory};
/// error. /// error.
/// ///
/// This is created by the `ServiceExt::map_err` method. /// This is created by the `ServiceExt::map_err` method.
pub struct MapErr<A, F, E> { pub struct MapErr<S, Req, F, E> {
service: A, service: S,
f: F, f: F,
_t: PhantomData<E>, _t: PhantomData<(E, Req)>,
} }
impl<A, F, E> MapErr<A, F, E> { impl<S, Req, F, E> MapErr<S, Req, F, E> {
/// Create new `MapErr` combinator /// Create new `MapErr` combinator
pub(crate) fn new(service: A, f: F) -> Self pub(crate) fn new(service: S, f: F) -> Self
where where
A: Service, S: Service<Req>,
F: Fn(A::Error) -> E, F: Fn(S::Error) -> E,
{ {
Self { Self {
service, service,
@@ -30,9 +34,9 @@ impl<A, F, E> MapErr<A, F, E> {
} }
} }
impl<A, F, E> Clone for MapErr<A, F, E> impl<S, Req, F, E> Clone for MapErr<S, Req, F, E>
where where
A: Clone, S: Clone,
F: Clone, F: Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
@@ -44,39 +48,39 @@ where
} }
} }
impl<A, F, E> Service for MapErr<A, F, E> impl<A, Req, F, E> Service<Req> for MapErr<A, Req, F, E>
where where
A: Service, A: Service<Req>,
F: Fn(A::Error) -> E + Clone, F: Fn(A::Error) -> E + Clone,
{ {
type Request = A::Request;
type Response = A::Response; type Response = A::Response;
type Error = E; type Error = E;
type Future = MapErrFuture<A, F, E>; type Future = MapErrFuture<A, Req, F, E>;
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(ctx).map_err(&self.f) self.service.poll_ready(ctx).map_err(&self.f)
} }
fn call(&mut self, req: A::Request) -> Self::Future { fn call(&mut self, req: Req) -> Self::Future {
MapErrFuture::new(self.service.call(req), self.f.clone()) MapErrFuture::new(self.service.call(req), self.f.clone())
} }
} }
#[pin_project::pin_project] pin_project! {
pub struct MapErrFuture<A, F, E> pub struct MapErrFuture<A, Req, F, E>
where where
A: Service, A: Service<Req>,
F: Fn(A::Error) -> E, F: Fn(A::Error) -> E,
{ {
f: F, f: F,
#[pin] #[pin]
fut: A::Future, fut: A::Future,
}
} }
impl<A, F, E> MapErrFuture<A, F, E> impl<A, Req, F, E> MapErrFuture<A, Req, F, E>
where where
A: Service, A: Service<Req>,
F: Fn(A::Error) -> E, F: Fn(A::Error) -> E,
{ {
fn new(fut: A::Future, f: F) -> Self { fn new(fut: A::Future, f: F) -> Self {
@@ -84,9 +88,9 @@ where
} }
} }
impl<A, F, E> Future for MapErrFuture<A, F, E> impl<A, Req, F, E> Future for MapErrFuture<A, Req, F, E>
where where
A: Service, A: Service<Req>,
F: Fn(A::Error) -> E, F: Fn(A::Error) -> E,
{ {
type Output = Result<A::Response, E>; type Output = Result<A::Response, E>;
@@ -101,19 +105,19 @@ where
/// service's error. /// service's error.
/// ///
/// This is created by the `NewServiceExt::map_err` method. /// This is created by the `NewServiceExt::map_err` method.
pub struct MapErrServiceFactory<A, F, E> pub struct MapErrServiceFactory<A, Req, F, E>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: Fn(A::Error) -> E + Clone, F: Fn(A::Error) -> E + Clone,
{ {
a: A, a: A,
f: F, f: F,
e: PhantomData<E>, e: PhantomData<(E, Req)>,
} }
impl<A, F, E> MapErrServiceFactory<A, F, E> impl<A, Req, F, E> MapErrServiceFactory<A, Req, F, E>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: Fn(A::Error) -> E + Clone, F: Fn(A::Error) -> E + Clone,
{ {
/// Create new `MapErr` new service instance /// Create new `MapErr` new service instance
@@ -126,9 +130,9 @@ where
} }
} }
impl<A, F, E> Clone for MapErrServiceFactory<A, F, E> impl<A, Req, F, E> Clone for MapErrServiceFactory<A, Req, F, E>
where where
A: ServiceFactory + Clone, A: ServiceFactory<Req> + Clone,
F: Fn(A::Error) -> E + Clone, F: Fn(A::Error) -> E + Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
@@ -140,39 +144,39 @@ where
} }
} }
impl<A, F, E> ServiceFactory for MapErrServiceFactory<A, F, E> impl<A, Req, F, E> ServiceFactory<Req> for MapErrServiceFactory<A, Req, F, E>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: Fn(A::Error) -> E + Clone, F: Fn(A::Error) -> E + Clone,
{ {
type Request = A::Request;
type Response = A::Response; type Response = A::Response;
type Error = E; type Error = E;
type Config = A::Config; type Config = A::Config;
type Service = MapErr<A::Service, F, E>; type Service = MapErr<A::Service, Req, F, E>;
type InitError = A::InitError; type InitError = A::InitError;
type Future = MapErrServiceFuture<A, F, E>; type Future = MapErrServiceFuture<A, Req, F, E>;
fn new_service(&self, cfg: A::Config) -> Self::Future { fn new_service(&self, cfg: A::Config) -> Self::Future {
MapErrServiceFuture::new(self.a.new_service(cfg), self.f.clone()) MapErrServiceFuture::new(self.a.new_service(cfg), self.f.clone())
} }
} }
#[pin_project::pin_project] pin_project! {
pub struct MapErrServiceFuture<A, F, E> pub struct MapErrServiceFuture<A, Req, F, E>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: Fn(A::Error) -> E, F: Fn(A::Error) -> E,
{ {
#[pin] #[pin]
fut: A::Future, fut: A::Future,
f: F, f: F,
}
} }
impl<A, F, E> MapErrServiceFuture<A, F, E> impl<A, Req, F, E> MapErrServiceFuture<A, Req, F, E>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: Fn(A::Error) -> E, F: Fn(A::Error) -> E,
{ {
fn new(fut: A::Future, f: F) -> Self { fn new(fut: A::Future, f: F) -> Self {
@@ -180,12 +184,12 @@ where
} }
} }
impl<A, F, E> Future for MapErrServiceFuture<A, F, E> impl<A, Req, F, E> Future for MapErrServiceFuture<A, Req, F, E>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: Fn(A::Error) -> E + Clone, F: Fn(A::Error) -> E + Clone,
{ {
type Output = Result<MapErr<A::Service, F, E>, A::InitError>; type Output = Result<MapErr<A::Service, Req, F, E>, A::InitError>;
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.project(); let this = self.project();
@@ -199,15 +203,17 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use futures_util::future::{err, lazy, ok, Ready}; use futures_util::future::lazy;
use super::*; use super::*;
use crate::{IntoServiceFactory, Service, ServiceFactory}; use crate::{
err, ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory,
ServiceFactoryExt,
};
struct Srv; struct Srv;
impl Service for Srv { impl Service<()> for Srv {
type Request = ();
type Response = (); type Response = ();
type Error = (); type Error = ();
type Future = Ready<Result<(), ()>>; type Future = Ready<Result<(), ()>>;

View File

@@ -1,21 +1,25 @@
use std::future::Future; use core::{
use std::marker::PhantomData; future::Future,
use std::pin::Pin; marker::PhantomData,
use std::task::{Context, Poll}; pin::Pin,
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use super::ServiceFactory; use super::ServiceFactory;
/// `MapInitErr` service combinator /// `MapInitErr` service combinator
pub struct MapInitErr<A, F, E> { pub struct MapInitErr<A, F, Req, Err> {
a: A, a: A,
f: F, f: F,
e: PhantomData<E>, e: PhantomData<(Req, Err)>,
} }
impl<A, F, E> MapInitErr<A, F, E> impl<A, F, Req, Err> MapInitErr<A, F, Req, Err>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: Fn(A::InitError) -> E, F: Fn(A::InitError) -> Err,
{ {
/// Create new `MapInitErr` combinator /// Create new `MapInitErr` combinator
pub(crate) fn new(a: A, f: F) -> Self { pub(crate) fn new(a: A, f: F) -> Self {
@@ -27,7 +31,7 @@ where
} }
} }
impl<A, F, E> Clone for MapInitErr<A, F, E> impl<A, F, Req, E> Clone for MapInitErr<A, F, Req, E>
where where
A: Clone, A: Clone,
F: Clone, F: Clone,
@@ -41,39 +45,39 @@ where
} }
} }
impl<A, F, E> ServiceFactory for MapInitErr<A, F, E> impl<A, F, Req, E> ServiceFactory<Req> for MapInitErr<A, F, Req, E>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: Fn(A::InitError) -> E + Clone, F: Fn(A::InitError) -> E + Clone,
{ {
type Request = A::Request;
type Response = A::Response; type Response = A::Response;
type Error = A::Error; type Error = A::Error;
type Config = A::Config; type Config = A::Config;
type Service = A::Service; type Service = A::Service;
type InitError = E; type InitError = E;
type Future = MapInitErrFuture<A, F, E>; type Future = MapInitErrFuture<A, F, Req, E>;
fn new_service(&self, cfg: A::Config) -> Self::Future { fn new_service(&self, cfg: A::Config) -> Self::Future {
MapInitErrFuture::new(self.a.new_service(cfg), self.f.clone()) MapInitErrFuture::new(self.a.new_service(cfg), self.f.clone())
} }
} }
#[pin_project::pin_project] pin_project! {
pub struct MapInitErrFuture<A, F, E> pub struct MapInitErrFuture<A, F, Req, E>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: Fn(A::InitError) -> E, F: Fn(A::InitError) -> E,
{ {
f: F, f: F,
#[pin] #[pin]
fut: A::Future, fut: A::Future,
}
} }
impl<A, F, E> MapInitErrFuture<A, F, E> impl<A, F, Req, E> MapInitErrFuture<A, F, Req, E>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: Fn(A::InitError) -> E, F: Fn(A::InitError) -> E,
{ {
fn new(fut: A::Future, f: F) -> Self { fn new(fut: A::Future, f: F) -> Self {
@@ -81,9 +85,9 @@ where
} }
} }
impl<A, F, E> Future for MapInitErrFuture<A, F, E> impl<A, F, Req, E> Future for MapInitErrFuture<A, F, Req, E>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
F: Fn(A::InitError) -> E, F: Fn(A::InitError) -> E,
{ {
type Output = Result<A::Service, E>; type Output = Result<A::Service, E>;

View File

@@ -1,42 +1,49 @@
use std::future::Future; use core::{
use std::task::{Context, Poll}; marker::PhantomData,
task::{Context, Poll},
};
use crate::and_then::{AndThenService, AndThenServiceFactory}; use crate::and_then::{AndThenService, AndThenServiceFactory};
use crate::and_then_apply_fn::{AndThenApplyFn, AndThenApplyFnFactory};
use crate::map::{Map, MapServiceFactory}; use crate::map::{Map, MapServiceFactory};
use crate::map_err::{MapErr, MapErrServiceFactory}; use crate::map_err::{MapErr, MapErrServiceFactory};
use crate::map_init_err::MapInitErr; use crate::map_init_err::MapInitErr;
use crate::then::{ThenService, ThenServiceFactory}; use crate::then::{ThenService, ThenServiceFactory};
use crate::{IntoService, IntoServiceFactory, Service, ServiceFactory}; use crate::{IntoService, IntoServiceFactory, Service, ServiceFactory};
/// Contruct new pipeline with one service in pipeline chain. /// Construct new pipeline with one service in pipeline chain.
pub fn pipeline<F, T>(service: F) -> Pipeline<T> pub fn pipeline<I, S, Req>(service: I) -> Pipeline<S, Req>
where where
F: IntoService<T>, I: IntoService<S, Req>,
T: Service, S: Service<Req>,
{ {
Pipeline { Pipeline {
service: service.into_service(), service: service.into_service(),
_phantom: PhantomData,
} }
} }
/// Contruct new pipeline factory with one service factory. /// Construct new pipeline factory with one service factory.
pub fn pipeline_factory<T, F>(factory: F) -> PipelineFactory<T> pub fn pipeline_factory<I, SF, Req>(factory: I) -> PipelineFactory<SF, Req>
where where
T: ServiceFactory, I: IntoServiceFactory<SF, Req>,
F: IntoServiceFactory<T>, SF: ServiceFactory<Req>,
{ {
PipelineFactory { PipelineFactory {
factory: factory.into_factory(), factory: factory.into_factory(),
_phantom: PhantomData,
} }
} }
/// Pipeline service - pipeline allows to compose multiple service into one service. /// Pipeline service - pipeline allows to compose multiple service into one service.
pub struct Pipeline<T> { pub struct Pipeline<S, Req> {
service: T, service: S,
_phantom: PhantomData<Req>,
} }
impl<T: Service> Pipeline<T> { impl<S, Req> Pipeline<S, Req>
where
S: Service<Req>,
{
/// Call another service after call to this one has resolved successfully. /// Call another service after call to this one has resolved successfully.
/// ///
/// This function can be used to chain two services together and ensure that /// This function can be used to chain two services together and ensure that
@@ -46,41 +53,18 @@ impl<T: Service> Pipeline<T> {
/// ///
/// Note that this function consumes the receiving service and returns a /// Note that this function consumes the receiving service and returns a
/// wrapped version of it. /// wrapped version of it.
pub fn and_then<F, U>( pub fn and_then<I, S1>(
self, self,
service: F, service: I,
) -> Pipeline< ) -> Pipeline<impl Service<Req, Response = S1::Response, Error = S::Error> + Clone, Req>
impl Service<Request = T::Request, Response = U::Response, Error = T::Error> + Clone,
>
where where
Self: Sized, Self: Sized,
F: IntoService<U>, I: IntoService<S1, S::Response>,
U: Service<Request = T::Response, Error = T::Error>, S1: Service<S::Response, Error = S::Error>,
{ {
Pipeline { Pipeline {
service: AndThenService::new(self.service, service.into_service()), service: AndThenService::new(self.service, service.into_service()),
} _phantom: PhantomData,
}
/// Apply function to specified service and use it as a next service in
/// chain.
///
/// Short version of `pipeline_factory(...).and_then(apply_fn_factory(...))`
pub fn and_then_apply_fn<U, I, F, Fut, Res, Err>(
self,
service: I,
f: F,
) -> Pipeline<impl Service<Request = T::Request, Response = Res, Error = Err> + Clone>
where
Self: Sized,
I: IntoService<U>,
U: Service,
F: FnMut(T::Response, &mut U) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<T::Error> + From<U::Error>,
{
Pipeline {
service: AndThenApplyFn::new(self.service, service.into_service(), f),
} }
} }
@@ -89,19 +73,18 @@ impl<T: Service> Pipeline<T> {
/// ///
/// Note that this function consumes the receiving pipeline and returns a /// Note that this function consumes the receiving pipeline and returns a
/// wrapped version of it. /// wrapped version of it.
pub fn then<F, U>( pub fn then<F, S1>(
self, self,
service: F, service: F,
) -> Pipeline< ) -> Pipeline<impl Service<Req, Response = S1::Response, Error = S::Error> + Clone, Req>
impl Service<Request = T::Request, Response = U::Response, Error = T::Error> + Clone,
>
where where
Self: Sized, Self: Sized,
F: IntoService<U>, F: IntoService<S1, Result<S::Response, S::Error>>,
U: Service<Request = Result<T::Response, T::Error>, Error = T::Error>, S1: Service<Result<S::Response, S::Error>, Error = S::Error>,
{ {
Pipeline { Pipeline {
service: ThenService::new(self.service, service.into_service()), service: ThenService::new(self.service, service.into_service()),
_phantom: PhantomData,
} }
} }
@@ -114,13 +97,14 @@ impl<T: Service> Pipeline<T> {
/// Note that this function consumes the receiving service and returns a /// Note that this function consumes the receiving service and returns a
/// wrapped version of it, similar to the existing `map` methods in the /// wrapped version of it, similar to the existing `map` methods in the
/// standard library. /// standard library.
pub fn map<F, R>(self, f: F) -> Pipeline<Map<T, F, R>> pub fn map<F, R>(self, f: F) -> Pipeline<Map<S, F, Req, R>, Req>
where where
Self: Sized, Self: Sized,
F: FnMut(T::Response) -> R, F: FnMut(S::Response) -> R,
{ {
Pipeline { Pipeline {
service: Map::new(self.service, f), service: Map::new(self.service, f),
_phantom: PhantomData,
} }
} }
@@ -132,114 +116,85 @@ impl<T: Service> Pipeline<T> {
/// ///
/// Note that this function consumes the receiving service and returns a /// Note that this function consumes the receiving service and returns a
/// wrapped version of it. /// wrapped version of it.
pub fn map_err<F, E>(self, f: F) -> Pipeline<MapErr<T, F, E>> pub fn map_err<F, E>(self, f: F) -> Pipeline<MapErr<S, Req, F, E>, Req>
where where
Self: Sized, Self: Sized,
F: Fn(T::Error) -> E, F: Fn(S::Error) -> E,
{ {
Pipeline { Pipeline {
service: MapErr::new(self.service, f), service: MapErr::new(self.service, f),
_phantom: PhantomData,
} }
} }
} }
impl<T> Clone for Pipeline<T> impl<T, Req> Clone for Pipeline<T, Req>
where where
T: Clone, T: Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Pipeline { Pipeline {
service: self.service.clone(), service: self.service.clone(),
_phantom: PhantomData,
} }
} }
} }
impl<T: Service> Service for Pipeline<T> { impl<S: Service<Req>, Req> Service<Req> for Pipeline<S, Req> {
type Request = T::Request; type Response = S::Response;
type Response = T::Response; type Error = S::Error;
type Error = T::Error; type Future = S::Future;
type Future = T::Future;
#[inline] #[inline]
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), T::Error>> { fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), S::Error>> {
self.service.poll_ready(ctx) self.service.poll_ready(ctx)
} }
#[inline] #[inline]
fn call(&mut self, req: T::Request) -> Self::Future { fn call(&mut self, req: Req) -> Self::Future {
self.service.call(req) self.service.call(req)
} }
} }
/// Pipeline factory /// Pipeline factory
pub struct PipelineFactory<T> { pub struct PipelineFactory<SF, Req> {
factory: T, factory: SF,
_phantom: PhantomData<Req>,
} }
impl<T: ServiceFactory> PipelineFactory<T> { impl<SF, Req> PipelineFactory<SF, Req>
where
SF: ServiceFactory<Req>,
{
/// Call another service after call to this one has resolved successfully. /// Call another service after call to this one has resolved successfully.
pub fn and_then<F, U>( pub fn and_then<I, SF1>(
self, self,
factory: F, factory: I,
) -> PipelineFactory< ) -> PipelineFactory<
impl ServiceFactory< impl ServiceFactory<
Request = T::Request, Req,
Response = U::Response, Response = SF1::Response,
Error = T::Error, Error = SF::Error,
Config = T::Config, Config = SF::Config,
InitError = T::InitError, InitError = SF::InitError,
Service = impl Service< Service = impl Service<Req, Response = SF1::Response, Error = SF::Error> + Clone,
Request = T::Request,
Response = U::Response,
Error = T::Error,
> + Clone,
> + Clone, > + Clone,
Req,
> >
where where
Self: Sized, Self: Sized,
T::Config: Clone, SF::Config: Clone,
F: IntoServiceFactory<U>, I: IntoServiceFactory<SF1, SF::Response>,
U: ServiceFactory< SF1: ServiceFactory<
Config = T::Config, SF::Response,
Request = T::Response, Config = SF::Config,
Error = T::Error, Error = SF::Error,
InitError = T::InitError, InitError = SF::InitError,
>, >,
{ {
PipelineFactory { PipelineFactory {
factory: AndThenServiceFactory::new(self.factory, factory.into_factory()), factory: AndThenServiceFactory::new(self.factory, factory.into_factory()),
} _phantom: PhantomData,
}
/// Apply function to specified service and use it as a next service in
/// chain.
///
/// Short version of `pipeline_factory(...).and_then(apply_fn_factory(...))`
pub fn and_then_apply_fn<U, I, F, Fut, Res, Err>(
self,
factory: I,
f: F,
) -> PipelineFactory<
impl ServiceFactory<
Request = T::Request,
Response = Res,
Error = Err,
Config = T::Config,
InitError = T::InitError,
Service = impl Service<Request = T::Request, Response = Res, Error = Err> + Clone,
> + Clone,
>
where
Self: Sized,
T::Config: Clone,
I: IntoServiceFactory<U>,
U: ServiceFactory<Config = T::Config, InitError = T::InitError>,
F: FnMut(T::Response, &mut U::Service) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>,
Err: From<T::Error> + From<U::Error>,
{
PipelineFactory {
factory: AndThenApplyFnFactory::new(self.factory, factory.into_factory(), f),
} }
} }
@@ -249,96 +204,103 @@ impl<T: ServiceFactory> PipelineFactory<T> {
/// ///
/// Note that this function consumes the receiving pipeline and returns a /// Note that this function consumes the receiving pipeline and returns a
/// wrapped version of it. /// wrapped version of it.
pub fn then<F, U>( pub fn then<I, SF1>(
self, self,
factory: F, factory: I,
) -> PipelineFactory< ) -> PipelineFactory<
impl ServiceFactory< impl ServiceFactory<
Request = T::Request, Req,
Response = U::Response, Response = SF1::Response,
Error = T::Error, Error = SF::Error,
Config = T::Config, Config = SF::Config,
InitError = T::InitError, InitError = SF::InitError,
Service = impl Service< Service = impl Service<Req, Response = SF1::Response, Error = SF::Error> + Clone,
Request = T::Request,
Response = U::Response,
Error = T::Error,
> + Clone,
> + Clone, > + Clone,
Req,
> >
where where
Self: Sized, Self: Sized,
T::Config: Clone, SF::Config: Clone,
F: IntoServiceFactory<U>, I: IntoServiceFactory<SF1, Result<SF::Response, SF::Error>>,
U: ServiceFactory< SF1: ServiceFactory<
Config = T::Config, Result<SF::Response, SF::Error>,
Request = Result<T::Response, T::Error>, Config = SF::Config,
Error = T::Error, Error = SF::Error,
InitError = T::InitError, InitError = SF::InitError,
>, >,
{ {
PipelineFactory { PipelineFactory {
factory: ThenServiceFactory::new(self.factory, factory.into_factory()), factory: ThenServiceFactory::new(self.factory, factory.into_factory()),
_phantom: PhantomData,
} }
} }
/// Map this service's output to a different type, returning a new service /// Map this service's output to a different type, returning a new service
/// of the resulting type. /// of the resulting type.
pub fn map<F, R>(self, f: F) -> PipelineFactory<MapServiceFactory<T, F, R>> pub fn map<F, R>(self, f: F) -> PipelineFactory<MapServiceFactory<SF, F, Req, R>, Req>
where where
Self: Sized, Self: Sized,
F: FnMut(T::Response) -> R + Clone, F: FnMut(SF::Response) -> R + Clone,
{ {
PipelineFactory { PipelineFactory {
factory: MapServiceFactory::new(self.factory, f), factory: MapServiceFactory::new(self.factory, f),
_phantom: PhantomData,
} }
} }
/// Map this service's error to a different error, returning a new service. /// Map this service's error to a different error, returning a new service.
pub fn map_err<F, E>(self, f: F) -> PipelineFactory<MapErrServiceFactory<T, F, E>> pub fn map_err<F, E>(
self,
f: F,
) -> PipelineFactory<MapErrServiceFactory<SF, Req, F, E>, Req>
where where
Self: Sized, Self: Sized,
F: Fn(T::Error) -> E + Clone, F: Fn(SF::Error) -> E + Clone,
{ {
PipelineFactory { PipelineFactory {
factory: MapErrServiceFactory::new(self.factory, f), factory: MapErrServiceFactory::new(self.factory, f),
_phantom: PhantomData,
} }
} }
/// Map this factory's init error to a different error, returning a new service. /// Map this factory's init error to a different error, returning a new service.
pub fn map_init_err<F, E>(self, f: F) -> PipelineFactory<MapInitErr<T, F, E>> pub fn map_init_err<F, E>(self, f: F) -> PipelineFactory<MapInitErr<SF, F, Req, E>, Req>
where where
Self: Sized, Self: Sized,
F: Fn(T::InitError) -> E + Clone, F: Fn(SF::InitError) -> E + Clone,
{ {
PipelineFactory { PipelineFactory {
factory: MapInitErr::new(self.factory, f), factory: MapInitErr::new(self.factory, f),
_phantom: PhantomData,
} }
} }
} }
impl<T> Clone for PipelineFactory<T> impl<T, Req> Clone for PipelineFactory<T, Req>
where where
T: Clone, T: Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
PipelineFactory { PipelineFactory {
factory: self.factory.clone(), factory: self.factory.clone(),
_phantom: PhantomData,
} }
} }
} }
impl<T: ServiceFactory> ServiceFactory for PipelineFactory<T> { impl<SF, Req> ServiceFactory<Req> for PipelineFactory<SF, Req>
type Config = T::Config; where
type Request = T::Request; SF: ServiceFactory<Req>,
type Response = T::Response; {
type Error = T::Error; type Config = SF::Config;
type Service = T::Service; type Response = SF::Response;
type InitError = T::InitError; type Error = SF::Error;
type Future = T::Future; type Service = SF::Service;
type InitError = SF::InitError;
type Future = SF::Future;
#[inline] #[inline]
fn new_service(&self, cfg: T::Config) -> Self::Future { fn new_service(&self, cfg: SF::Config) -> Self::Future {
self.factory.new_service(cfg) self.factory.new_service(cfg)
} }
} }

View File

@@ -0,0 +1,54 @@
//! When MSRV is 1.48, replace with `core::future::Ready` and `core::future::ready()`.
use core::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
/// Future for the [`ready`](ready()) function.
#[derive(Debug, Clone)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Ready<T> {
val: Option<T>,
}
impl<T> Ready<T> {
/// Unwraps the value from this immediately ready future.
#[inline]
pub fn into_inner(mut self) -> T {
self.val.take().unwrap()
}
}
impl<T> Unpin for Ready<T> {}
impl<T> Future for Ready<T> {
type Output = T;
#[inline]
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<T> {
let val = self.val.take().expect("Ready can not be polled twice.");
Poll::Ready(val)
}
}
/// Creates a future that is immediately ready with a value.
#[allow(dead_code)]
pub(crate) fn ready<T>(val: T) -> Ready<T> {
Ready { val: Some(val) }
}
/// Create a future that is immediately ready with a success value.
#[allow(dead_code)]
pub(crate) fn ok<T, E>(val: T) -> Ready<Result<T, E>> {
Ready { val: Some(Ok(val)) }
}
/// Create a future that is immediately ready with an error value.
#[allow(dead_code)]
pub(crate) fn err<T, E>(err: E) -> Ready<Result<T, E>> {
Ready {
val: Some(Err(err)),
}
}

View File

@@ -1,46 +1,50 @@
use std::future::Future; use alloc::rc::Rc;
use std::pin::Pin; use core::{
use std::rc::Rc; cell::RefCell,
use std::task::{Context, Poll}; future::Future,
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use super::{Service, ServiceFactory}; use super::{Service, ServiceFactory};
use crate::cell::Cell;
/// Service for the `then` combinator, chaining a computation onto the end of /// Service for the `then` combinator, chaining a computation onto the end of
/// another service. /// another service.
/// ///
/// This is created by the `Pipeline::then` method. /// This is created by the `Pipeline::then` method.
pub(crate) struct ThenService<A, B>(Cell<(A, B)>); pub(crate) struct ThenService<A, B, Req>(Rc<RefCell<(A, B)>>, PhantomData<Req>);
impl<A, B> ThenService<A, B> { impl<A, B, Req> ThenService<A, B, Req> {
/// Create new `.then()` combinator /// Create new `.then()` combinator
pub(crate) fn new(a: A, b: B) -> ThenService<A, B> pub(crate) fn new(a: A, b: B) -> ThenService<A, B, Req>
where where
A: Service, A: Service<Req>,
B: Service<Request = Result<A::Response, A::Error>, Error = A::Error>, B: Service<Result<A::Response, A::Error>, Error = A::Error>,
{ {
Self(Cell::new((a, b))) Self(Rc::new(RefCell::new((a, b))), PhantomData)
} }
} }
impl<A, B> Clone for ThenService<A, B> { impl<A, B, Req> Clone for ThenService<A, B, Req> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
ThenService(self.0.clone()) ThenService(self.0.clone(), PhantomData)
} }
} }
impl<A, B> Service for ThenService<A, B> impl<A, B, Req> Service<Req> for ThenService<A, B, Req>
where where
A: Service, A: Service<Req>,
B: Service<Request = Result<A::Response, A::Error>, Error = A::Error>, B: Service<Result<A::Response, A::Error>, Error = A::Error>,
{ {
type Request = A::Request;
type Response = B::Response; type Response = B::Response;
type Error = B::Error; type Error = B::Error;
type Future = ThenServiceResponse<A, B>; type Future = ThenServiceResponse<A, B, Req>;
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 srv = self.0.get_mut(); let mut srv = self.0.borrow_mut();
let not_ready = !srv.0.poll_ready(cx)?.is_ready(); let not_ready = !srv.0.poll_ready(cx)?.is_ready();
if !srv.1.poll_ready(cx)?.is_ready() || not_ready { if !srv.1.poll_ready(cx)?.is_ready() || not_ready {
Poll::Pending Poll::Pending
@@ -49,105 +53,110 @@ where
} }
} }
fn call(&mut self, req: A::Request) -> Self::Future { fn call(&mut self, req: Req) -> Self::Future {
ThenServiceResponse { ThenServiceResponse {
state: State::A(self.0.get_mut().0.call(req), Some(self.0.clone())), state: State::A {
fut: self.0.borrow_mut().0.call(req),
b: Some(self.0.clone()),
},
} }
} }
} }
#[pin_project::pin_project] pin_project! {
pub(crate) struct ThenServiceResponse<A, B> pub(crate) struct ThenServiceResponse<A, B, Req>
where where
A: Service, A: Service<Req>,
B: Service<Request = Result<A::Response, A::Error>>, B: Service<Result<A::Response, A::Error>>,
{ {
#[pin] #[pin]
state: State<A, B>, state: State<A, B, Req>,
}
} }
#[pin_project::pin_project] pin_project! {
enum State<A, B> #[project = StateProj]
where enum State<A, B, Req>
A: Service, where
B: Service<Request = Result<A::Response, A::Error>>, A: Service<Req>,
{ B: Service<Result<A::Response, A::Error>>,
A(#[pin] A::Future, Option<Cell<(A, B)>>), {
B(#[pin] B::Future), A { #[pin] fut: A::Future, b: Option<Rc<RefCell<(A, B)>>> },
Empty, B { #[pin] fut: B::Future },
Empty,
}
} }
impl<A, B> Future for ThenServiceResponse<A, B> impl<A, B, Req> Future for ThenServiceResponse<A, B, Req>
where where
A: Service, A: Service<Req>,
B: Service<Request = Result<A::Response, A::Error>>, B: Service<Result<A::Response, A::Error>>,
{ {
type Output = Result<B::Response, B::Error>; type Output = Result<B::Response, B::Error>;
#[pin_project::project]
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();
#[project]
match this.state.as_mut().project() { match this.state.as_mut().project() {
State::A(fut, b) => match fut.poll(cx) { StateProj::A { fut, b } => match fut.poll(cx) {
Poll::Ready(res) => { Poll::Ready(res) => {
let mut b = b.take().unwrap(); let b = b.take().unwrap();
this.state.set(State::Empty); // drop fut A this.state.set(State::Empty); // drop fut A
let fut = b.get_mut().1.call(res); let fut = b.borrow_mut().1.call(res);
this.state.set(State::B(fut)); this.state.set(State::B { fut });
self.poll(cx) self.poll(cx)
} }
Poll::Pending => Poll::Pending, Poll::Pending => Poll::Pending,
}, },
State::B(fut) => fut.poll(cx).map(|r| { StateProj::B { fut } => fut.poll(cx).map(|r| {
this.state.set(State::Empty); this.state.set(State::Empty);
r r
}), }),
State::Empty => panic!("future must not be polled after it returned `Poll::Ready`"), StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
}
} }
} }
} }
/// `.then()` service factory combinator /// `.then()` service factory combinator
pub(crate) struct ThenServiceFactory<A, B>(Rc<(A, B)>); pub(crate) struct ThenServiceFactory<A, B, Req>(Rc<(A, B)>, PhantomData<Req>);
impl<A, B> ThenServiceFactory<A, B> impl<A, B, Req> ThenServiceFactory<A, B, Req>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
A::Config: Clone, A::Config: Clone,
B: ServiceFactory< B: ServiceFactory<
Result<A::Response, A::Error>,
Config = A::Config, Config = A::Config,
Request = Result<A::Response, A::Error>,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
{ {
/// Create new `AndThen` combinator /// Create new `AndThen` combinator
pub(crate) fn new(a: A, b: B) -> Self { pub(crate) fn new(a: A, b: B) -> Self {
Self(Rc::new((a, b))) Self(Rc::new((a, b)), PhantomData)
} }
} }
impl<A, B> ServiceFactory for ThenServiceFactory<A, B> impl<A, B, Req> ServiceFactory<Req> for ThenServiceFactory<A, B, Req>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
A::Config: Clone, A::Config: Clone,
B: ServiceFactory< B: ServiceFactory<
Result<A::Response, A::Error>,
Config = A::Config, Config = A::Config,
Request = Result<A::Response, A::Error>,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
{ {
type Request = A::Request;
type Response = B::Response; type Response = B::Response;
type Error = A::Error; type Error = A::Error;
type Config = A::Config; type Config = A::Config;
type Service = ThenService<A::Service, B::Service>; type Service = ThenService<A::Service, B::Service, Req>;
type InitError = A::InitError; type InitError = A::InitError;
type Future = ThenServiceFactoryResponse<A, B>; type Future = ThenServiceFactoryResponse<A, B, Req>;
fn new_service(&self, cfg: A::Config) -> Self::Future { fn new_service(&self, cfg: A::Config) -> Self::Future {
let srv = &*self.0; let srv = &*self.0;
@@ -155,37 +164,38 @@ where
} }
} }
impl<A, B> Clone for ThenServiceFactory<A, B> { impl<A, B, Req> Clone for ThenServiceFactory<A, B, Req> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self(self.0.clone()) Self(self.0.clone(), PhantomData)
} }
} }
#[pin_project::pin_project] pin_project! {
pub(crate) struct ThenServiceFactoryResponse<A, B> pub(crate) struct ThenServiceFactoryResponse<A, B, Req>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
B: ServiceFactory< B: ServiceFactory<
Config = A::Config, Result<A::Response, A::Error>,
Request = Result<A::Response, A::Error>, Config = A::Config,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
{ {
#[pin] #[pin]
fut_b: B::Future, fut_b: B::Future,
#[pin] #[pin]
fut_a: A::Future, fut_a: A::Future,
a: Option<A::Service>, a: Option<A::Service>,
b: Option<B::Service>, b: Option<B::Service>,
}
} }
impl<A, B> ThenServiceFactoryResponse<A, B> impl<A, B, Req> ThenServiceFactoryResponse<A, B, Req>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
B: ServiceFactory< B: ServiceFactory<
Result<A::Response, A::Error>,
Config = A::Config, Config = A::Config,
Request = Result<A::Response, A::Error>,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
@@ -200,17 +210,17 @@ where
} }
} }
impl<A, B> Future for ThenServiceFactoryResponse<A, B> impl<A, B, Req> Future for ThenServiceFactoryResponse<A, B, Req>
where where
A: ServiceFactory, A: ServiceFactory<Req>,
B: ServiceFactory< B: ServiceFactory<
Result<A::Response, A::Error>,
Config = A::Config, Config = A::Config,
Request = Result<A::Response, A::Error>,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
{ {
type Output = Result<ThenService<A::Service, B::Service>, A::InitError>; type Output = Result<ThenService<A::Service, B::Service, Req>, A::InitError>;
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.project(); let this = self.project();
@@ -238,19 +248,20 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::cell::Cell; use alloc::rc::Rc;
use std::rc::Rc; use core::{
use std::task::{Context, Poll}; cell::Cell,
task::{Context, Poll},
};
use futures_util::future::{err, lazy, ok, ready, Ready}; use futures_util::future::lazy;
use crate::{pipeline, pipeline_factory, Service, ServiceFactory}; use crate::{err, ok, pipeline, pipeline_factory, ready, Ready, Service, ServiceFactory};
#[derive(Clone)] #[derive(Clone)]
struct Srv1(Rc<Cell<usize>>); struct Srv1(Rc<Cell<usize>>);
impl Service for Srv1 { impl Service<Result<&'static str, &'static str>> for Srv1 {
type Request = Result<&'static str, &'static str>;
type Response = &'static str; type Response = &'static str;
type Error = (); type Error = ();
type Future = Ready<Result<Self::Response, Self::Error>>; type Future = Ready<Result<Self::Response, Self::Error>>;
@@ -270,8 +281,7 @@ mod tests {
struct Srv2(Rc<Cell<usize>>); struct Srv2(Rc<Cell<usize>>);
impl Service for Srv2 { impl Service<Result<&'static str, ()>> for Srv2 {
type Request = Result<&'static str, ()>;
type Response = (&'static str, &'static str); type Response = (&'static str, &'static str);
type Error = (); type Error = ();
type Future = Ready<Result<Self::Response, ()>>; type Future = Ready<Result<Self::Response, ()>>;

View File

@@ -1,18 +1,22 @@
use std::future::Future; use alloc::{rc::Rc, sync::Arc};
use std::pin::Pin; use core::{
use std::rc::Rc; future::Future,
use std::sync::Arc; marker::PhantomData,
use std::task::{Context, Poll}; pin::Pin,
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use crate::transform_err::TransformMapInitErr; use crate::transform_err::TransformMapInitErr;
use crate::{IntoServiceFactory, Service, ServiceFactory}; use crate::{IntoServiceFactory, Service, ServiceFactory};
/// Apply transform to a service. /// Apply transform to a service.
pub fn apply<T, S, U>(t: T, factory: U) -> ApplyTransform<T, S> pub fn apply<T, S, I, Req>(t: T, factory: I) -> ApplyTransform<T, S, Req>
where where
S: ServiceFactory, I: IntoServiceFactory<S, Req>,
T: Transform<S::Service, InitError = S::InitError>, S: ServiceFactory<Req>,
U: IntoServiceFactory<S>, T: Transform<S::Service, Req, InitError = S::InitError>,
{ {
ApplyTransform::new(t, factory.into_factory()) ApplyTransform::new(t, factory.into_factory())
} }
@@ -70,7 +74,7 @@ where
/// timeout: Duration, /// timeout: Duration,
/// } /// }
/// ///
/// impl<S> Transform<S> for TimeoutTransform<E> /// impl<S> Transform<S> for TimeoutTransform
/// where /// where
/// S: Service, /// S: Service,
/// { /// {
@@ -89,10 +93,7 @@ where
/// } /// }
/// } /// }
/// ``` /// ```
pub trait Transform<S> { pub trait Transform<S, Req> {
/// Requests handled by the service.
type Request;
/// Responses given by the service. /// Responses given by the service.
type Response; type Response;
@@ -100,11 +101,7 @@ pub trait Transform<S> {
type Error; type Error;
/// The `TransformService` value created by this factory /// The `TransformService` value created by this factory
type Transform: Service< type Transform: Service<Req, Response = Self::Response, Error = Self::Error>;
Request = Self::Request,
Response = Self::Response,
Error = Self::Error,
>;
/// Errors produced while building a transform service. /// Errors produced while building a transform service.
type InitError; type InitError;
@@ -115,9 +112,9 @@ pub trait Transform<S> {
/// Creates and returns a new Transform component, asynchronously /// Creates and returns a new Transform component, asynchronously
fn new_transform(&self, service: S) -> Self::Future; fn new_transform(&self, service: S) -> Self::Future;
/// Map this transforms's factory error to a different error, /// Map this transform's factory error to a different error,
/// returning a new transform service factory. /// returning a new transform service factory.
fn map_init_err<F, E>(self, f: F) -> TransformMapInitErr<Self, S, F, E> fn map_init_err<F, E>(self, f: F) -> TransformMapInitErr<Self, S, Req, F, E>
where where
Self: Sized, Self: Sized,
F: Fn(Self::InitError) -> E + Clone, F: Fn(Self::InitError) -> E + Clone,
@@ -126,11 +123,10 @@ pub trait Transform<S> {
} }
} }
impl<T, S> Transform<S> for Rc<T> impl<T, S, Req> Transform<S, Req> for Rc<T>
where where
T: Transform<S>, T: Transform<S, Req>,
{ {
type Request = T::Request;
type Response = T::Response; type Response = T::Response;
type Error = T::Error; type Error = T::Error;
type InitError = T::InitError; type InitError = T::InitError;
@@ -142,11 +138,10 @@ where
} }
} }
impl<T, S> Transform<S> for Arc<T> impl<T, S, Req> Transform<S, Req> for Arc<T>
where where
T: Transform<S>, T: Transform<S, Req>,
{ {
type Request = T::Request;
type Response = T::Response; type Response = T::Response;
type Error = T::Error; type Error = T::Error;
type InitError = T::InitError; type InitError = T::InitError;
@@ -159,90 +154,92 @@ where
} }
/// `Apply` transform to new service /// `Apply` transform to new service
pub struct ApplyTransform<T, S>(Rc<(T, S)>); pub struct ApplyTransform<T, S, Req>(Rc<(T, S)>, PhantomData<Req>);
impl<T, S> ApplyTransform<T, S> impl<T, S, Req> ApplyTransform<T, S, Req>
where where
S: ServiceFactory, S: ServiceFactory<Req>,
T: Transform<S::Service, InitError = S::InitError>, T: Transform<S::Service, Req, InitError = S::InitError>,
{ {
/// Create new `ApplyTransform` new service instance /// Create new `ApplyTransform` new service instance
fn new(t: T, service: S) -> Self { fn new(t: T, service: S) -> Self {
Self(Rc::new((t, service))) Self(Rc::new((t, service)), PhantomData)
} }
} }
impl<T, S> Clone for ApplyTransform<T, S> { impl<T, S, Req> Clone for ApplyTransform<T, S, Req> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
ApplyTransform(self.0.clone()) ApplyTransform(self.0.clone(), PhantomData)
} }
} }
impl<T, S> ServiceFactory for ApplyTransform<T, S> impl<T, S, Req> ServiceFactory<Req> for ApplyTransform<T, S, Req>
where where
S: ServiceFactory, S: ServiceFactory<Req>,
T: Transform<S::Service, InitError = S::InitError>, T: Transform<S::Service, Req, InitError = S::InitError>,
{ {
type Request = T::Request;
type Response = T::Response; type Response = T::Response;
type Error = T::Error; type Error = T::Error;
type Config = S::Config; type Config = S::Config;
type Service = T::Transform; type Service = T::Transform;
type InitError = T::InitError; type InitError = T::InitError;
type Future = ApplyTransformFuture<T, S>; type Future = ApplyTransformFuture<T, S, Req>;
fn new_service(&self, cfg: S::Config) -> Self::Future { fn new_service(&self, cfg: S::Config) -> Self::Future {
ApplyTransformFuture { ApplyTransformFuture {
store: self.0.clone(), store: self.0.clone(),
state: ApplyTransformFutureState::A(self.0.as_ref().1.new_service(cfg)), state: ApplyTransformFutureState::A {
fut: self.0.as_ref().1.new_service(cfg),
},
} }
} }
} }
#[pin_project::pin_project] pin_project! {
pub struct ApplyTransformFuture<T, S> pub struct ApplyTransformFuture<T, S, Req>
where where
S: ServiceFactory, S: ServiceFactory<Req>,
T: Transform<S::Service, InitError = S::InitError>, T: Transform<S::Service, Req, InitError = S::InitError>,
{ {
store: Rc<(T, S)>, store: Rc<(T, S)>,
#[pin] #[pin]
state: ApplyTransformFutureState<T, S>, state: ApplyTransformFutureState<T, S, Req>,
}
} }
#[pin_project::pin_project] pin_project! {
pub enum ApplyTransformFutureState<T, S> #[project = ApplyTransformFutureStateProj]
where pub enum ApplyTransformFutureState<T, S, Req>
S: ServiceFactory, where
T: Transform<S::Service, InitError = S::InitError>, S: ServiceFactory<Req>,
{ T: Transform<S::Service, Req, InitError = S::InitError>,
A(#[pin] S::Future), {
B(#[pin] T::Future), A { #[pin] fut: S::Future },
B { #[pin] fut: T::Future },
}
} }
impl<T, S> Future for ApplyTransformFuture<T, S> impl<T, S, Req> Future for ApplyTransformFuture<T, S, Req>
where where
S: ServiceFactory, S: ServiceFactory<Req>,
T: Transform<S::Service, InitError = S::InitError>, T: Transform<S::Service, Req, InitError = S::InitError>,
{ {
type Output = Result<T::Transform, T::InitError>; type Output = Result<T::Transform, T::InitError>;
#[pin_project::project]
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();
#[project]
match this.state.as_mut().project() { match this.state.as_mut().project() {
ApplyTransformFutureState::A(fut) => match fut.poll(cx)? { ApplyTransformFutureStateProj::A { fut } => match fut.poll(cx)? {
Poll::Ready(srv) => { Poll::Ready(srv) => {
let fut = this.store.0.new_transform(srv); let fut = this.store.0.new_transform(srv);
this.state.set(ApplyTransformFutureState::B(fut)); this.state.set(ApplyTransformFutureState::B { fut });
self.poll(cx) self.poll(cx)
} }
Poll::Pending => Poll::Pending, Poll::Pending => Poll::Pending,
}, },
ApplyTransformFutureState::B(fut) => fut.poll(cx), ApplyTransformFutureStateProj::B { fut } => fut.poll(cx),
} }
} }
} }

View File

@@ -1,7 +1,11 @@
use std::future::Future; use core::{
use std::marker::PhantomData; future::Future,
use std::pin::Pin; marker::PhantomData,
use std::task::{Context, Poll}; pin::Pin,
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use super::Transform; use super::Transform;
@@ -9,75 +13,75 @@ use super::Transform;
/// transform's init error. /// transform's init error.
/// ///
/// This is created by the `Transform::map_init_err` method. /// This is created by the `Transform::map_init_err` method.
pub struct TransformMapInitErr<T, S, F, E> { pub struct TransformMapInitErr<T, S, Req, F, E> {
t: T, transform: T,
f: F, mapper: F,
e: PhantomData<(S, E)>, _phantom: PhantomData<(S, Req, E)>,
} }
impl<T, S, F, E> TransformMapInitErr<T, S, F, E> { impl<T, S, F, E, Req> TransformMapInitErr<T, S, Req, F, E> {
pub(crate) fn new(t: T, f: F) -> Self pub(crate) fn new(t: T, f: F) -> Self
where where
T: Transform<S>, T: Transform<S, Req>,
F: Fn(T::InitError) -> E, F: Fn(T::InitError) -> E,
{ {
Self { Self {
t, transform: t,
f, mapper: f,
e: PhantomData, _phantom: PhantomData,
} }
} }
} }
impl<T, S, F, E> Clone for TransformMapInitErr<T, S, F, E> impl<T, S, Req, F, E> Clone for TransformMapInitErr<T, S, Req, F, E>
where where
T: Clone, T: Clone,
F: Clone, F: Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
t: self.t.clone(), transform: self.transform.clone(),
f: self.f.clone(), mapper: self.mapper.clone(),
e: PhantomData, _phantom: PhantomData,
} }
} }
} }
impl<T, S, F, E> Transform<S> for TransformMapInitErr<T, S, F, E> impl<T, S, F, E, Req> Transform<S, Req> for TransformMapInitErr<T, S, Req, F, E>
where where
T: Transform<S>, T: Transform<S, Req>,
F: Fn(T::InitError) -> E + Clone, F: Fn(T::InitError) -> E + Clone,
{ {
type Request = T::Request;
type Response = T::Response; type Response = T::Response;
type Error = T::Error; type Error = T::Error;
type Transform = T::Transform; type Transform = T::Transform;
type InitError = E; type InitError = E;
type Future = TransformMapInitErrFuture<T, S, F, E>; type Future = TransformMapInitErrFuture<T, S, F, E, Req>;
fn new_transform(&self, service: S) -> Self::Future { fn new_transform(&self, service: S) -> Self::Future {
TransformMapInitErrFuture { TransformMapInitErrFuture {
fut: self.t.new_transform(service), fut: self.transform.new_transform(service),
f: self.f.clone(), f: self.mapper.clone(),
} }
} }
} }
#[pin_project::pin_project] pin_project! {
pub struct TransformMapInitErrFuture<T, S, F, E> pub struct TransformMapInitErrFuture<T, S, F, E, Req>
where where
T: Transform<S>, T: Transform<S, Req>,
F: Fn(T::InitError) -> E, F: Fn(T::InitError) -> E,
{ {
#[pin] #[pin]
fut: T::Future, fut: T::Future,
f: F, f: F,
}
} }
impl<T, S, F, E> Future for TransformMapInitErrFuture<T, S, F, E> impl<T, S, F, E, Req> Future for TransformMapInitErrFuture<T, S, F, E, Req>
where where
T: Transform<S>, T: Transform<S, Req>,
F: Fn(T::InitError) -> E + Clone, F: Fn(T::InitError) -> E + Clone,
{ {
type Output = Result<T::Transform, E>; type Output = Result<T::Transform, E>;

View File

@@ -1,5 +1,11 @@
# Changes # Changes
## [1.0.1] - 2020-05-19
* Replace deprecated `net2` crate with `socket2`
* Remove unused `futures` dependency
## [1.0.0] - 2019-12-11 ## [1.0.0] - 2019-12-11
* Update actix-server to 1.0.0 * Update actix-server to 1.0.0

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-testing" name = "actix-testing"
version = "1.0.0" version = "1.0.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix testing utils" description = "Actix testing utils"
keywords = ["network", "framework", "async", "futures"] keywords = ["network", "framework", "async", "futures"]
@@ -8,9 +8,10 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-testing/" documentation = "https://docs.rs/actix-testing/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
workspace = ".." workspace = ".."
readme = "README.md"
[lib] [lib]
name = "actix_testing" name = "actix_testing"
@@ -23,4 +24,4 @@ actix-server = "1.0.0"
actix-service = "1.0.0" actix-service = "1.0.0"
log = "0.4" log = "0.4"
net2 = "0.2" socket2 = "0.3"

View File

@@ -1,13 +1,16 @@
//! Various helpers for Actix applications to use during testing. //! Various helpers for Actix applications to use during testing.
#![deny(rust_2018_idioms, warnings)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![allow(clippy::type_complexity, clippy::needless_doctest_main)] #![allow(clippy::type_complexity, clippy::needless_doctest_main)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
use std::sync::mpsc; use std::sync::mpsc;
use std::{net, thread}; use std::{net, thread};
use actix_rt::{net::TcpStream, System}; use actix_rt::{net::TcpStream, System};
use actix_server::{Server, ServerBuilder, ServiceFactory}; use actix_server::{Server, ServerBuilder, ServiceFactory};
use net2::TcpBuilder; use socket2::{Domain, Protocol, Socket, Type};
#[cfg(not(test))] // Work around for rust-lang/rust#62127 #[cfg(not(test))] // Work around for rust-lang/rust#62127
pub use actix_macros::test; pub use actix_macros::test;
@@ -37,7 +40,7 @@ pub use actix_macros::test;
/// ``` /// ```
pub struct TestServer; pub struct TestServer;
/// Test server runstime /// Test server runtime
pub struct TestServerRuntime { pub struct TestServerRuntime {
addr: net::SocketAddr, addr: net::SocketAddr,
host: String, host: String,
@@ -80,15 +83,18 @@ impl TestServer {
// run server in separate thread // run server in separate thread
thread::spawn(move || { thread::spawn(move || {
let sys = System::new("actix-test-server"); let mut sys = System::new("actix-test-server");
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
let local_addr = tcp.local_addr().unwrap(); let local_addr = tcp.local_addr().unwrap();
Server::build() sys.block_on(async {
.listen("test", tcp, factory)? Server::build()
.workers(1) .listen("test", tcp, factory)
.disable_signals() .unwrap()
.start(); .workers(1)
.disable_signals()
.start();
});
tx.send((System::current(), local_addr)).unwrap(); tx.send((System::current(), local_addr)).unwrap();
sys.run() sys.run()
@@ -107,13 +113,14 @@ impl TestServer {
} }
} }
/// Get firat available unused local address /// Get first available unused local address
pub fn unused_addr() -> net::SocketAddr { pub fn unused_addr() -> net::SocketAddr {
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
let socket = TcpBuilder::new_v4().unwrap(); let socket =
socket.bind(&addr).unwrap(); Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap();
socket.reuse_address(true).unwrap(); socket.bind(&addr.into()).unwrap();
let tcp = socket.to_tcp_listener().unwrap(); socket.set_reuse_address(true).unwrap();
let tcp = socket.into_tcp_listener();
tcp.local_addr().unwrap() tcp.local_addr().unwrap()
} }
} }

View File

@@ -1,10 +1,24 @@
# Changes # Changes
## [0.3.3] - 2020-07-14
### Changed
* Update parking_lot to 0.11
## [0.3.2] - 2020-05-20
## Added
* Implement `std::error::Error` for `BlockingError` [#120]
[#120]: https://github.com/actix/actix-net/pull/120
## [0.3.1] - 2019-12-12 ## [0.3.1] - 2019-12-12
### Changed ### Changed
* Use parking_lot 0.10 * Update parking_lot to 0.10
## [0.3.0] - 2019-12-02 ## [0.3.0] - 2019-12-02

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-threadpool" name = "actix-threadpool"
version = "0.3.1" version = "0.3.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix thread pool for sync code" description = "Actix thread pool for sync code"
keywords = ["actix", "network", "framework", "async", "futures"] keywords = ["actix", "network", "framework", "async", "futures"]
@@ -8,7 +8,7 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-threadpool/" documentation = "https://docs.rs/actix-threadpool/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018" edition = "2018"
workspace = ".." workspace = ".."
@@ -20,7 +20,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
derive_more = "0.99.2" derive_more = "0.99.2"
futures-channel = "0.3.1" futures-channel = "0.3.1"
parking_lot = "0.10" parking_lot = "0.11"
lazy_static = "1.3" lazy_static = "1.3"
log = "0.4" log = "0.4"
num_cpus = "1.10" num_cpus = "1.10"

View File

@@ -1,5 +1,9 @@
//! Thread pool for blocking operations //! Thread pool for blocking operations
#![deny(rust_2018_idioms, nonstandard_style)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
use std::fmt; use std::fmt;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;

View File

@@ -1,27 +1,37 @@
# Changes # Changes
## Unreleased - 2020-xx-xx
## 2.0.0 - 2020-09-03
* `nativetls::NativeTlsAcceptor` is renamed to `nativetls::Acceptor`.
* Where possible, "SSL" terminology is replaced with "TLS".
* `SslError` is renamed to `TlsError`.
* `TlsError::Ssl` enum variant is renamed to `TlsError::Tls`.
* `max_concurrent_ssl_connect` is renamed to `max_concurrent_tls_connect`.
## 2.0.0-alpha.2 - 2020-08-17
* Update `rustls` dependency to 0.18
* Update `tokio-rustls` dependency to 0.14
* Update `webpki-roots` dependency to 0.20
## [2.0.0-alpha.1] - 2020-03-03 ## [2.0.0-alpha.1] - 2020-03-03
### Changed
* Update `rustls` dependency to 0.17 * Update `rustls` dependency to 0.17
* Update `tokio-rustls` dependency to 0.13 * Update `tokio-rustls` dependency to 0.13
* Update `webpki-roots` dependency to 0.19 * Update `webpki-roots` dependency to 0.19
## [1.0.0] - 2019-12-11
## [1.0.0] - 2019-12-11
* 1.0.0 release * 1.0.0 release
## [1.0.0-alpha.3] - 2019-12-07 ## [1.0.0-alpha.3] - 2019-12-07
### Changed
* Migrate to tokio 0.2 * Migrate to tokio 0.2
* Enable rustls acceptor service * Enable rustls acceptor service
* Enable native-tls acceptor service * Enable native-tls acceptor service
## [1.0.0-alpha.1] - 2019-12-02
* Split openssl accetor from actix-server package ## [1.0.0-alpha.1] - 2019-12-02
* Split openssl acceptor from actix-server package

View File

@@ -1,16 +1,15 @@
[package] [package]
name = "actix-tls" name = "actix-tls"
version = "2.0.0-alpha.1" version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix tls services" description = "TLS acceptor services for Actix ecosystem."
keywords = ["network", "framework", "async", "futures"] keywords = ["network", "framework", "async", "tls", "ssl"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-tls/" documentation = "https://docs.rs/actix-tls/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
workspace = ".."
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["openssl", "rustls", "nativetls"] features = ["openssl", "rustls", "nativetls"]
@@ -19,6 +18,10 @@ features = ["openssl", "rustls", "nativetls"]
name = "actix_tls" name = "actix_tls"
path = "src/lib.rs" path = "src/lib.rs"
[[example]]
name = "basic"
required-features = ["rustls"]
[features] [features]
default = [] default = []
@@ -33,28 +36,29 @@ nativetls = ["native-tls", "tokio-tls"]
[dependencies] [dependencies]
actix-service = "1.0.0" actix-service = "1.0.0"
actix-codec = "0.2.0" actix-codec = "0.3.0"
actix-utils = "1.0.0" actix-utils = "2.0.0"
actix-rt = "1.0.0"
derive_more = "0.99.2"
either = "1.5.2"
futures-util = { version = "0.3.4", default-features = false } futures-util = { version = "0.3.4", default-features = false }
log = "0.4"
# openssl # openssl
open-ssl = { version="0.10", package = "openssl", optional = true } open-ssl = { package = "openssl", version = "0.10", optional = true }
tokio-openssl = { version = "0.4.0", optional = true } tokio-openssl = { version = "0.4.0", optional = true }
# rustls # rustls
rust-tls = { version = "0.17.0", package = "rustls", optional = true } rust-tls = { package = "rustls", version = "0.18.0", optional = true }
webpki = { version = "0.21", optional = true } webpki = { version = "0.21", optional = true }
webpki-roots = { version = "0.19", optional = true } webpki-roots = { version = "0.20", optional = true }
tokio-rustls = { version = "0.13.0", optional = true } tokio-rustls = { version = "0.14.0", optional = true }
# native-tls # native-tls
native-tls = { version="0.2", optional = true } native-tls = { version = "0.2", optional = true }
tokio-tls = { version="0.3", optional = true } tokio-tls = { version = "0.3", optional = true }
[dev-dependencies] [dev-dependencies]
bytes = "0.5" bytes = "0.5"
actix-testing = { version="1.0.0" } log = "0.4"
env_logger = "0.7"
actix-testing = "1.0.0"
actix-server = "1"
actix-rt = "1"

View File

@@ -0,0 +1,82 @@
//! TLS Acceptor Server
//!
//! Using either HTTPie (`http`) or cURL:
//!
//! This commands will produce errors in the server log:
//! ```sh
//! curl 127.0.0.1:8443
//! http 127.0.0.1:8443
//! ```
//!
//! These commands will show "empty reply" on the client but will debug print the TLS stream info
//! in the server log, indicating a successful TLS handshake:
//! ```sh
//! curl -k https://127.0.0.1:8443
//! http --verify=false https://127.0.0.1:8443
//! ```
use std::{
env,
fs::File,
io::{self, BufReader},
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
};
use actix_server::Server;
use actix_service::pipeline_factory;
use actix_tls::rustls::Acceptor as RustlsAcceptor;
use futures_util::future::ok;
use log::info;
use rust_tls::{
internal::pemfile::certs, internal::pemfile::rsa_private_keys, NoClientAuth, ServerConfig,
};
#[derive(Debug)]
struct ServiceState {
num: Arc<AtomicUsize>,
}
#[actix_rt::main]
async fn main() -> io::Result<()> {
env::set_var("RUST_LOG", "actix=trace,basic=trace");
env_logger::init();
let mut tls_config = ServerConfig::new(NoClientAuth::new());
// Load TLS key and cert files
let cert_file = &mut BufReader::new(File::open("./examples/cert.pem").unwrap());
let key_file = &mut BufReader::new(File::open("./examples/key.pem").unwrap());
let cert_chain = certs(cert_file).unwrap();
let mut keys = rsa_private_keys(key_file).unwrap();
tls_config
.set_single_cert(cert_chain, keys.remove(0))
.unwrap();
let tls_acceptor = RustlsAcceptor::new(tls_config);
let count = Arc::new(AtomicUsize::new(0));
let addr = ("127.0.0.1", 8443);
info!("starting server on port: {}", &addr.0);
Server::build()
.bind("tls-example", addr, move || {
let count = Arc::clone(&count);
// Set up TLS service factory
pipeline_factory(tls_acceptor.clone())
.map_err(|err| println!("Rustls error: {:?}", err))
.and_then(move |stream| {
let num = count.fetch_add(1, Ordering::Relaxed);
info!("[{}] Got TLS connection: {:?}", num, stream);
ok(())
})
})?
.workers(1)
.run()
.await
}

View File

@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIENjCCAp6gAwIBAgIRANp+D9pBErdacw6KjrwJ+4swDQYJKoZIhvcNAQELBQAw
bTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSEwHwYDVQQLDBhyb2JA
c29tYnJhLng1Mi5kZXYgKFJvYikxKDAmBgNVBAMMH21rY2VydCByb2JAc29tYnJh
Lng1Mi5kZXYgKFJvYikwHhcNMTkwNjAxMDAwMDAwWhcNMzAwOTEzMDIzNDI0WjBM
MScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxITAfBgNV
BAsMGHJvYkBzb21icmEueDUyLmRldiAoUm9iKTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBALYAn8dsQUDTp8SptAtkiAySvQYLpAOct3/OjBn+dSYfbQcp
Ph9w/Zo83Msl7Fb1DBvADHFtyBpESATZ2chS5fwCAwUFTlKrzMk3qauEoJ3cCQa8
ccqhTMLeT38jRlhXrMHWBfz0ipqy+yTLWeM32LX8s0jPbbsZ3gVJ/Ls4qm0CTaqb
zRdcQ7GTVKYet5DR7ZvwvAaLtWk/iiHKwnOveuF27HNlxj0Rwd/lhJ/t9x8xJwyR
MTdm852KQadI8xOSbWNK4j9419yzKjUEMKgn78wT/7DQfeKKCAreHa4MaEw4+koD
2Bqb+V4fI6T84VvXkNG3CjSpmIiYGlIE1LVgBL8CAwEAAaNyMHAwDgYDVR0PAQH/
BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHwYDVR0j
BBgwFoAUto/ox0MqZShmQpViV/gjfJKrMDkwGgYDVR0RBBMwEYIJbG9jYWxob3N0
hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBgQBUCMzqTY0sg+61gh8gKS5TCL6qs0R1
xys/EFFaD5JYUsfM/HyhHd0jq+x5Pd3mB2Jvhoq9xhjMwP11H8Uw5lLBHA8USdF9
EiLW1GvT3/gLfMqb0lPk0RMRBeX8c0QbDtqdiUCE7S6zJbZ5gjFeRuFNjdcGA1Ss
8CPPts2mns5cwah6H7T/BFzj5aR9Qe14vo1Rpr5gD5CpHvk1t16q7YsczQfVMvt3
Ydk6p0rwA8Z5okQK7y3qKPZI+//ygWL6ZBjVjl1/Al8vybG2UYjYgfMBwaVvMiDJ
j/vCdVmlvGb+MZlZID/p2veaNeEKgi1A1EOj3sNuQYXXFfSD9mdamX7JIfGi/U7v
ivvUjJUbzGrUngldt5iCKqcCQum7nlzu9sT1Tm2t/n4tz/btrI+Wimg8riSzM+Nk
dfuvv4NbWe6Th5460HH8mMvfPZSB8dCoxwm98tuqcMXLkR1RJX5Z8LYAaPTsUs/h
HxQCY4EaY7feZ/qFal9FGwvpzVr3/XjgSCU=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAtgCfx2xBQNOnxKm0C2SIDJK9BgukA5y3f86MGf51Jh9tByk+
H3D9mjzcyyXsVvUMG8AMcW3IGkRIBNnZyFLl/AIDBQVOUqvMyTepq4SgndwJBrxx
yqFMwt5PfyNGWFeswdYF/PSKmrL7JMtZ4zfYtfyzSM9tuxneBUn8uziqbQJNqpvN
F1xDsZNUph63kNHtm/C8Bou1aT+KIcrCc6964Xbsc2XGPRHB3+WEn+33HzEnDJEx
N2bznYpBp0jzE5JtY0riP3jX3LMqNQQwqCfvzBP/sNB94ooICt4drgxoTDj6SgPY
Gpv5Xh8jpPzhW9eQ0bcKNKmYiJgaUgTUtWAEvwIDAQABAoIBADC0Zg21+Jhii6jj
SR0rYAUNV6xAfTnCPJDlMzTZlXwIOOMLtGYxlIwr8WIj2eVDWmQqtqm8GSp+T0+N
BOzI0mboGurDCryw4PKQBMWzjk/wTDITR9hT5fjYCSoaxH5rp/2PSrbwsg7ICtFD
4eAeV84Lu+amK9VADNwZepqXhXP6EDOY5yovkwzOQNDM/qVzHSe9EoFP74M/oWnY
ohIuWdZzwAZuTA5SUjPygiVzs/vhsrSE9crMIzr5VgKBi+C+ALkrL7Lc4GlRPI4r
6VsbIxZHa7who+FhjZ0cVfdXHH47QDdf10X5bEXsaFBvGGCLtkQ3XEpov6GOlaH+
aY7fzPECgYEA4LGloaMC9J27uyPxHkQwEehexmJdIu0vNUefv5yiO9PbvrjvYnh7
JxRVgv1fy2bRMOvg19TujCYRZdkrLDqSDsfFfEiThvlFBRZfKKIHmWdyfvIe9Jp9
rqdxhWAco7FoM+W6c8c4iR4xs8/GA60CVcAiTLqgPWWzn12fesiULi0CgYEAz1xD
OulJyfpHVGQ6ZM1wR0SZ9H9GS3BenpL2ue5uBfe3hM+JIAAM61Y48wJuCWT5EvfL
FgnH3oCo7SYGcgGkERS8H7k67DJCLlqDo/3FC7lX/irz+ya/FoZmKBagvjEUWhpe
Bb2dRIbqsG0lsCzU9MVrgtvodD0MBTyt0RM5fhsCgYEAhgYQiLhGBAituLN4mBgO
IDBdj7GOYk3dkcc2J0HTlyIIeduvlinNM4Myel6NrDKY5rhbtgGhhGEUkY6W7NvG
0SAh0L8tmB3JKH6upfr3023b4pKjGj2oZ+wij27DxnQEdqg5reOP+mHTPbDaKMki
kml3TBMpj1XBbXaXsNJBaMUCgYEAnnNzEC4563QrU2pvUJ3HgT4Dotgqv/Sy6NuG
W1e9jSPYgU0RDHndZWtygwdFTDpzNbJR5po8t2J7MxQOcsmcNE0y387sHpbdCYyy
8Po2uxm7CoaJ/02BUVYL8/Aujob0dVGWrS5SYY3zAjO1S+VGKXA+EjW2cDRB3jKa
45ucICcCgYBdMxB5Oj6GpdewWWaBss9dwHtDaD4oVGYIBbIc2qdyCYixWdW9NccV
fRJs0ulGrpg9OtyWbwZASu2jz55+s3hi4rnrcaXKiIh9Rs25v1irF6Dmduvo7CaN
Mf7zBg7LUttmqN6D3npIAxmBULl8KRfjnt6U2tJolF5X0qQ1uqnnTA==
-----END RSA PRIVATE KEY-----

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