1
0
mirror of https://github.com/fafhrd91/actix-net synced 2025-08-14 11:10:32 +02:00

Compare commits

..

259 Commits

Author SHA1 Message Date
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
Yuki Okushi
65e2e8052e Release actix-rt 1.1.0 (#126)
* Release actix-rt 1.1.0

* Update actix-rt/CHANGES.md
2020-04-08 16:34:07 +09:00
Jonathas-Conceicao
783880bb0a actix-rt: Add Arbiter::is_running helper and fix System::is_set doc
`Arbiter::is_running` can be used to check if the current even-loop is currently
running; which should also work after the system has stopped. `System::is_set`
was updated to reflect what it actually does, it tells if the event loop has
started, which alone can't tell if it has stopped.

Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-04-05 21:00:54 -03:00
Jonathas-Conceicao
69e8df9d62 actix-rt: Run rustfmt
Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-04-05 21:00:54 -03:00
Yuki Okushi
9addf1a36b Merge pull request #125 from actix/fix/noisy-check
fix noisy check warning
2020-04-05 13:20:25 +09:00
Rob Ede
187a58472d fix noisy check warning 2020-04-04 23:57:52 +01:00
Nikolay Kim
30aa0b7bb6 add serde support to bytestring 2020-03-30 11:54:40 +06:00
Yuki Okushi
e775d08d76 Merge pull request #122 from actix/JohnTitor-patch-1
Upload coverage on PRs
2020-03-18 05:31:59 +09:00
Yuki Okushi
d5f95b54b7 Upload coverage on PRs 2020-03-18 05:03:37 +09:00
Yuki Okushi
904f90abc2 Merge pull request #121 from actix/revert-115-JohnTitor-patch-2
Revert "Disable windows-mingw builder temporarily"
2020-03-16 18:06:42 +09:00
Yuki Okushi
950c73077c Revert "Disable windows-mingw builder temporarily" 2020-03-16 17:31:10 +09:00
Yuki Okushi
732731a9c8 Merge pull request #120 from kornelski/err
std Error for BlockingError
2020-03-14 00:14:42 +09:00
Kornel Lesiński
0dd5a7ce1d std Error for BlockingError
#93
2020-03-13 12:35:20 +00:00
Yuki Okushi
7105091e51 Merge pull request #119 from JohnTitor/futures
Minimize `futures-*` dependencies
2020-03-13 05:12:37 +09:00
Yuki Okushi
08959dfc21 actix-tracing: Minimize futures-util dependencies 2020-03-12 07:13:32 +09:00
Yuki Okushi
2792433ad6 actix-codec: Minimize futures-* dependencies 2020-03-12 07:13:32 +09:00
Yuki Okushi
437a7b05c6 actix-rt: Fix build 2020-03-12 07:13:32 +09:00
Yuki Okushi
3d125c5381 actix-testing: Remove unused deps 2020-03-12 07:13:32 +09:00
Yuki Okushi
fbf7d6ef33 Update examples 2020-03-12 07:13:32 +09:00
Yuki Okushi
e6b6f08369 actix-utils: Minimize futures-* dependencies 2020-03-12 07:13:32 +09:00
Yuki Okushi
4e806b3e3f actix-tls: Minimize futures-* dependencies 2020-03-12 07:13:31 +09:00
Yuki Okushi
f5b07053fc actix-server: Minimize futures-* dependencies 2020-03-12 07:13:31 +09:00
Yuki Okushi
dd3bec83bf actix-ioframe: Minimize futures-* dependencies 2020-03-12 07:13:31 +09:00
Yuki Okushi
f955e49930 actix-connect: Minimize futures-* dependencies 2020-03-12 04:22:38 +09:00
Yuki Okushi
4be11b541b Merge pull request #117 from actix/new-connect
Release actix-http v2.0.0-alpha.2
2020-03-08 15:13:52 +09:00
Yuki Okushi
baba533407 Update actix-http dependency 2020-03-08 14:38:07 +09:00
Yuki Okushi
2bf50826b0 Bump up to 2.0.0-alpha.2 2020-03-08 14:37:33 +09:00
Yuki Okushi
41b2a3b2e2 Merge pull request #116 from Jonathas-Conceicao/topic/upgrade_trust_dns
actix-connect: Upgrade versions of trust-dns
2020-03-08 14:31:07 +09:00
Jonathas-Conceicao
7fdd4a1118 actix-connect: Upgrade versions of trust-dns
- `Address` trait is now required to have static lifetime;
- `start_resolver` and `start_default_resolver` are now `async` and may return
  a `ConnectError`;

Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-03-07 14:52:41 -03:00
Jonathas-Conceicao
cb30f9e86a actix-connect: Run cargo fmt
Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-03-07 14:37:39 -03:00
Yuki Okushi
873f69be51 Merge pull request #115 from actix/JohnTitor-patch-2
Disable windows-mingw builder temporarily
2020-03-06 14:11:50 +09:00
Yuki Okushi
0967061f30 Merge pull request #114 from actix/JohnTitor-patch-1
Unpin quote version
2020-03-06 14:11:28 +09:00
Yuki Okushi
59902cb3a3 Disable windows-mingw builder temporarily 2020-03-06 13:48:55 +09:00
Yuki Okushi
857e50120b Unpin quote version 2020-03-06 13:45:21 +09:00
Yuki Okushi
36a2edf1cd Merge pull request #111 from dunnock/master
Fix build with failing quote
2020-03-05 23:05:19 +09:00
Maksym Vorobiov
346bd072d3 fix build with failing quote 2020-03-05 14:58:44 +02:00
Yuki Okushi
8d3d58b3b7 Merge pull request #110 from Aaron1011/fix/better-pin
Replace calls to `Pin::new_unchecked` with `pin_project`.
2020-03-05 21:52:55 +09:00
Aaron Hill
c41b5d8dd4 Replace calls to Pin::new_unchecked with pin_project.
This is a breaking change, as it changes some public methods to take
`Pin<&mut Self>` rather than `&mut self`.

This brings these methods into line with `Stream::poll_next`, which also
takes a `Pin<&mut Self>`
2020-03-04 12:08:52 -05:00
Yuki Okushi
693d5132a9 Merge pull request #109 from JohnTitor/new-tls
actix-tls: Bump up to 2.0.0-alpha.1
2020-03-03 22:29:08 +09:00
Yuki Okushi
f7dac3feb4 Bump up to 2.0.0-alpha.1 2020-03-03 19:47:40 +09:00
Yuki Okushi
ebc11d03f2 Merge pull request #108 from JohnTitor/new-connect
Release `actix-connect` v2.0.0-alpha.1
2020-03-03 18:33:08 +09:00
Yuki Okushi
e3ad5de270 Update actix-connect dependency 2020-03-03 17:24:41 +09:00
Yuki Okushi
91118bb2ce Bump up to 2.0.0-alpha.1 2020-03-03 17:24:25 +09:00
Yuki Okushi
6628688bcf Merge pull request #107 from JohnTitor/rustls-017
Update `rustls` and `tokio-rustls`
2020-03-01 23:48:13 +09:00
Yuki Okushi
b9567359fd actix-tls: Update rustls and tokio-rustls 2020-03-01 12:08:14 +09:00
Yuki Okushi
7dbc0264b1 actix-connect: Update rustls and tokio-rustls 2020-03-01 12:08:14 +09:00
Erich Gubler
1b7c969f6a actix-rt: minimize futures dependencies to futures-{channel,util} with default features off (#104)
* build(deps): minimize `futures` deps by using `futures-channel` and `futures-util` directly

* style(actix-rt): enforce spaces around equals in `Cargo.toml`
2020-02-27 01:15:21 +09:00
Jonathas-Conceicao
f1685d8253 Add Arbiter::local_join associated function
Arbiter::local_join function can be used to await for futures spawned
on current arbiter.

Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-02-26 12:59:46 -03:00
Jonathas-Conceicao
e3b6a33b97 Add integration tests
These initial tests validade basic usage with timed futures for:
- `System::block_on`;
- `Arbiter::new`;
- `Arbiter::stop`;
- `Arbiter::join`;

Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2020-02-26 12:59:46 -03:00
Yuki Okushi
13b503435f Merge pull request #106 from JohnTitor/server-102
Release actix-server 1.0.2
2020-02-26 20:53:00 +09:00
Yuki Okushi
98f0290f65 actix-server: Bump up to 1.0.2 2020-02-26 19:48:52 +09:00
Yuki Okushi
b8f66f5e7f Update changelog 2020-02-26 19:48:41 +09:00
Yuki Okushi
dd59ee498e Add FIXME comment 2020-02-26 19:48:27 +09:00
Dany Laporte
83320efa31 Avoid error by register() on Windows (#103) 2020-02-26 18:40:31 +09:00
Yuki Okushi
c69bc11e3e Merge pull request #105 from actix/bench
Add action to check benchmark
2020-02-26 17:33:37 +09:00
Yuki Okushi
aad5c42ad7 Add action to check benchmark 2020-02-26 17:11:46 +09:00
Maxim Vorobjov
4d37858fc6 Benchmarks for actix-service: focused around UnsafeCell usage (#98)
* add benchmark comparing unsafecell vs refcell

* fix syntax

* add benches for and_then implementation options

* repeat benches to stabilize
2020-02-26 16:45:23 +09:00
Yuki Okushi
d402f08bb5 Merge pull request #102 from JohnTitor/single-import
Remove single import
2020-02-25 19:11:04 +09:00
Yuki Okushi
fa25e30427 Remove single import 2020-02-25 18:41:15 +09:00
Bo Yao
602db1779e Expose is_set (#99)
* Expose is_set

* Update doc and changes.md
2020-02-25 02:55:02 -03:00
Yuki Okushi
4f2910c6b3 Merge pull request #96 from actix/JohnTitor-patch-1
Disable coverage for PRs
2020-02-15 01:55:20 +09:00
Yuki Okushi
9f7d6bc068 Disable coverage for PRs 2020-02-14 07:30:21 +09:00
Yuki Okushi
6908b58943 Merge pull request #92 from actix/bye-travis
Move script from Travis to Actions
2020-02-02 06:28:42 +09:00
Yuki Okushi
043057ecbd Fix import scopes 2020-02-01 23:32:08 +09:00
Yuki Okushi
e12bf9200b Clean up metadata 2020-01-31 02:21:25 +09:00
Yuki Okushi
03d431e663 Add badges on README 2020-01-31 00:01:47 +09:00
Yuki Okushi
f0d352604e Remove travis config 2020-01-31 00:01:34 +09:00
Yuki Okushi
2f67e4f563 Use markdown format 2020-01-31 00:01:24 +09:00
Yuki Okushi
d1155d60ec Tweak Actions 2020-01-31 00:01:11 +09:00
Yuki Okushi
28d9c6a760 Merge pull request #90 from actix/fix-ci
Tweak GitHub Actions
2020-01-30 00:46:21 +09:00
Yuki Okushi
a970c2c997 Remove AppVeyor config 2020-01-29 12:05:55 +09:00
Yuki Okushi
d5a6c83207 Suppress/fix clippy warnings 2020-01-29 12:05:55 +09:00
Yuki Okushi
ee0db9a617 Tweak GitHub Actions 2020-01-29 12:05:55 +09:00
zero-systems
e5b5df1261 Optimize vector fill in builder. (#89)
* optimize vector fill
2020-01-22 06:35:22 +09:00
Nikolay Kim
dbfa13d6be Fixed unsoundness in .and_then()/.then() service combinators 2020-01-16 16:58:11 -08:00
Nikolay Kim
e7c2439543 prep release 2020-01-15 13:35:07 -08:00
Nikolay Kim
3116db5168 revert 1.0.3 changes 2020-01-15 13:24:38 -08:00
Nikolay Kim
5940731ef0 Fix actix-service 1.0.3 compatibility 2020-01-15 11:58:06 -08:00
Rajasekharan Vengalil
aed5fecc8a Add support for tokio tracing for actix Service. (#86)
* Add support for tokio tracing for actix Service.

* Address comments

* Change trace's return type to ApplyTransform

* Remove redundant type args

* Remove reference to MakeSpan from docs
2020-01-15 11:43:52 -08:00
Nikolay Kim
a751899aad Fixed unsoundness in AndThenService impl #83 2020-01-15 11:40:15 -08:00
Nikolay Kim
fa800aeba3 Fix AsRef<str> impl 2020-01-14 15:06:02 -08:00
Nikolay Kim
2f89483635 Merge branch 'master' of github.com:actix/actix-net 2020-01-14 00:42:29 -08:00
Nikolay Kim
3048073919 Add PartialEq<T: AsRef<str>>, AsRef<[u8]> impls 2020-01-13 11:58:31 +06:00
amosonn
4bbba803c1 Fix Service documentation (#85) 2020-01-12 07:44:01 +09:00
Sven-Hendrik Haase
4dcdeb6795 Merge pull request #84 from currency-engineering/master
Minor grammatical fix to docs.
2020-01-10 15:28:19 +01:00
Eric Findlay
3b4f222242 Minor grammatical fix to docs. 2020-01-10 20:52:49 +09:00
Nikolay Kim
7c5fa25b23 Add into_service helper function 2020-01-08 18:31:50 +06:00
Nikolay Kim
3551d6674d Add Clone impl for condition::Waiter 2020-01-08 11:18:56 +06:00
Nikolay Kim
9f00daea80 add Condition and Pool 2020-01-08 10:59:27 +06:00
Nikolay Kim
7dddeab2a8 Add ResourceDef::resource_path_named() path generation method 2019-12-31 18:02:43 +06:00
Nikolay Kim
dcbcc40da2 Revert "Support named parameters for ResourceDef::resource_path() in form of ((&k, &v), ...)"
This reverts commit b0d44198ba.
2019-12-31 15:14:53 +06:00
Nikolay Kim
b0d44198ba Support named parameters for ResourceDef::resource_path() in form of ((&k, &v), ...) 2019-12-31 14:53:30 +06:00
Nikolay Kim
974bd6b01e leak string instead of rc 2019-12-31 12:04:35 +06:00
Nikolay Kim
5779da0f49 refactor service and state manahement 2019-12-29 13:42:42 +06:00
Nikolay Kim
1918c8d4f8 rename .run to .start() 2019-12-29 10:07:46 +06:00
Nikolay Kim
e21c58930b Add impl IntoPattern for &String 2019-12-25 21:34:14 +04:00
Nikolay Kim
59c5e9be6a Use IntoPattern for RouterBuilder::path() 2019-12-25 21:01:07 +04:00
Nikolay Kim
a2a9d9764d introduce IntoPattern trait 2019-12-25 19:54:20 +04:00
Nikolay Kim
bf0a9d2f6e Add IntoPatterns trait 2019-12-25 15:34:21 +04:00
Nikolay Kim
119027f822 fmt 2019-12-25 15:10:13 +04:00
Nikolay Kim
0fe8038d23 allow specify set of resource patters 2019-12-25 15:10:01 +04:00
Nikolay Kim
b599bc4a0c map_config() and unit_config() accepts IntoServiceFactory type 2019-12-22 16:30:49 +04:00
Nikolay Kim
a80e1f8370 fix new() method and make from_static and from_bytes_unchecked methods const 2019-12-22 16:24:28 +04:00
Nikolay Kim
5fe759cc02 Merge branch 'master' of github.com:actix/actix-net 2019-12-20 09:15:19 +06:00
Nikolay Kim
05549f0b42 Add methods to check LocalWaker registration state 2019-12-20 09:13:11 +06:00
Yuki Okushi
b1430eaded Run tests for all features as possible (#78) 2019-12-19 16:31:32 +09:00
Nikolay Kim
0d3f9e74c5 Use .advance() intead of .split_to() 2019-12-19 09:50:31 +06:00
Nikolay Kim
cab73791ed pin trsut-dns-proto 2019-12-15 13:04:26 +06:00
Nikolay Kim
a7ac1a76ed add license files to actix-macros 2019-12-14 23:01:55 +06:00
Nikolay Kim
37bedff6fb use parking_lot 0.10 2019-12-12 06:57:40 +06:00
Nikolay Kim
33fd6adc11 better InOrder test 2019-12-12 06:56:45 +06:00
Nikolay Kim
4305cdba2c Revert InOrder service changes 2019-12-11 23:10:02 +06:00
Nikolay Kim
52ecb4bcc5 Add oneshot::Sender::is_canceled() method 2019-12-11 20:52:57 +06:00
Nikolay Kim
b28f32e82c Allow to create framed::Dispatcher with custom mpsc::Receiver 2019-12-11 20:23:14 +06:00
Nikolay Kim
081205a02f Disconnect callback accepts owned state 2019-12-11 18:57:43 +06:00
Nikolay Kim
8bb81c0768 optimize InOrder service 2019-12-11 18:55:53 +06:00
Nikolay Kim
c7a8743bf9 remove E param 2019-12-11 16:44:09 +06:00
Nikolay Kim
f26fcc703b prep release 2019-12-11 14:56:05 +06:00
Nikolay Kim
ce4587df82 prepare actix-tls release 2019-12-11 14:53:58 +06:00
Nikolay Kim
9957f28137 prepare actix-testing release 2019-12-11 14:49:26 +06:00
Nikolay Kim
9d84d14ef4 update deps 2019-12-11 14:47:30 +06:00
Nikolay Kim
60bfa1bfb1 prepare actix-server release 2019-12-11 14:43:26 +06:00
Nikolay Kim
2c81c22b3e refactor ioframe dispatcher 2019-12-11 14:36:11 +06:00
Nikolay Kim
dded482514 allow to close mpsc sender 2019-12-11 14:36:00 +06:00
Nikolay Kim
631cb86947 refactor framed and stream dispatchers 2019-12-11 12:42:07 +06:00
Nikolay Kim
2e5e69c9ba Simplify oneshot and mpsc implementations 2019-12-11 11:28:09 +06:00
Nikolay Kim
e315cf2893 prep actix-rt release; update deps 2019-12-11 10:34:50 +06:00
Nikolay Kim
13fd615966 actix-macros release 2019-12-11 10:32:01 +06:00
Nikolay Kim
c094f84b85 prepare actix-service release 2019-12-11 10:29:34 +06:00
Nikolay Kim
25012d290a update actix-codec dependencies 2019-12-11 10:23:01 +06:00
Nikolay Kim
32202188cc prepare actix-codec release 2019-12-11 10:18:11 +06:00
Nikolay Kim
bf734a31dc update docs 2019-12-10 21:34:51 +06:00
Nikolay Kim
d29e7c4ba6 Merge branch 'master' of github.com:actix/actix-net 2019-12-10 21:14:18 +06:00
Nikolay Kim
7163e2c2a2 update doc strings 2019-12-10 21:14:06 +06:00
Nikolay Kim
1d810b4561 re-export AlpnError 2019-12-10 12:15:27 +06:00
daxpedda
0913badd61 Macro improvements. (#74)
* Macro improvements.

* Fix usage in `fn main`.
2019-12-10 08:47:35 +06:00
Nikolay Kim
8b3062cd6e Fix buffer remaining capacity calcualtion 2019-12-09 21:50:36 +06:00
Nikolay Kim
35218a4df1 add Clone impl for Apply service 2019-12-09 14:07:20 +06:00
Nikolay Kim
d47f1fb730 prepare actix-service release 2019-12-08 19:49:35 +06:00
Nikolay Kim
1ad0bbfb7f rename fn service helpers 2019-12-08 19:05:05 +06:00
Nikolay Kim
c38a25f102 fix hash impl 2019-12-07 11:51:47 +06:00
Nikolay Kim
110457477a update changes 2019-12-07 11:04:53 +06:00
Nikolay Kim
a899b1e04d bump actix-ioframe version 2019-12-07 10:55:54 +06:00
Nikolay Kim
393cf1ab25 add unsafe from_bytes_unchecked 2019-12-07 10:48:22 +06:00
Nikolay Kim
40fbbb9c32 fix crate name 2019-12-07 10:39:33 +06:00
Nikolay Kim
99fef4f06b add helper conversions 2019-12-07 10:22:08 +06:00
Nikolay Kim
fc0825fcdd update tokio to 0.2.4 2019-12-07 10:15:26 +06:00
Nikolay Kim
6c00ab8296 add string crate 2019-12-07 09:59:39 +06:00
Nikolay Kim
cbdbc05dbd update tokio verion and prep alpha3 release 2019-12-07 09:57:43 +06:00
Yuki Okushi
5674840c01 Stop running tests for all features (#73) 2019-12-07 08:54:58 +06:00
Nikolay Kim
6f07c9d72a update trust-dns 2019-12-06 14:08:11 +06:00
Nikolay Kim
fa48ddcfa1 fix non unix signals support 2019-12-06 14:06:14 +06:00
Max Gortman
f89a992daf eager drop in then, and_then, and_then_apply_fn (#72) 2019-12-06 10:34:44 +06:00
Nikolay Kim
e670a32ff3 inclide stream feature 2019-12-06 01:34:13 +06:00
Nikolay Kim
021c742d22 use string crate from master 2019-12-06 00:10:27 +06:00
Nikolay Kim
88a60ffa66 reexport ssl types 2019-12-05 23:09:44 +06:00
Nikolay Kim
cb2845cb26 fix dependencies 2019-12-05 20:58:28 +06:00
Nikolay Kim
b18fbc98d5 move rustls and nativetls acceptor services to actix-tls 2019-12-05 20:52:37 +06:00
Nikolay Kim
3a858feaec migrate to tokio 0.2.2 2019-12-05 16:40:24 +06:00
Nikolay Kim
d49aca9595 use bitflags for internal flags; use tokio 0.2 2019-12-05 13:11:56 +06:00
Nikolay Kim
6f41b80cb4 optimize service combinators memory layout 2019-12-05 12:37:26 +06:00
Nikolay Kim
c6eb318536 Fix low/high watermark for write/read buffers; fix oneshot impl 2019-12-05 01:36:31 +06:00
Nikolay Kim
21dcc22e53 refactor server configurations 2019-12-04 21:35:27 +06:00
Nikolay Kim
de84663768 fix initial worker service state 2019-12-04 15:52:49 +06:00
Nikolay Kim
c4e2051327 refactor server worker 2019-12-04 15:12:02 +06:00
Nikolay Kim
0a4fe22003 Restore Service/Factory::apply_fn() in form of Pipeline/Factory::and_then_apply_fn() 2019-12-03 19:59:28 +06:00
Nikolay Kim
eb773c8b8c Merge branch 'master' of github.com:actix/actix-net 2019-12-03 18:34:32 +06:00
Nikolay Kim
db0bc1e156 Restore Transform::map_init_err() combinator 2019-12-03 18:32:02 +06:00
Yuki Okushi
9eb12e0467 Use GitHub Actions (#71) 2019-12-03 20:00:16 +09:00
Nikolay Kim
eb33f0ecbe add Clone for apply combinator 2019-12-03 16:15:06 +06:00
Nikolay Kim
cbc5da8625 update changes 2019-12-03 14:10:36 +06:00
Nikolay Kim
ec8dca8d69 Merge branch 'master' of github.com:actix/actix-net 2019-12-03 14:09:35 +06:00
Nikolay Kim
6a9df026e7 Add missing Clone impl for factory_fn_cfg 2019-12-03 14:05:23 +06:00
Aaron Housh
2756bedc3d Fix for non Unix OS (#69) 2019-12-03 10:07:54 +06:00
Nikolay Kim
bd4c4cda8b update threadpool 2019-12-02 22:49:02 +06:00
Nikolay Kim
c0ede65317 restore 0.1 behavior 2019-12-02 22:47:49 +06:00
Nikolay Kim
9f575418c1 clippy warnings 2019-12-02 22:30:09 +06:00
Nikolay Kim
9ed35cca7a use owned value for service factory config 2019-12-02 21:27:48 +06:00
Nikolay Kim
3385682e09 remove server feature 2019-12-02 17:04:42 +06:00
Nikolay Kim
f55f96bc77 fix dependencies 2019-12-02 11:49:42 +06:00
Nikolay Kim
a08b1eba87 update tests 2019-12-02 11:43:52 +06:00
Nikolay Kim
d81e72cf06 remove deprecaed crate 2019-12-02 11:30:52 +06:00
Nikolay Kim
9fbe6a1f6d refactor server configuration and tls support 2019-12-02 11:30:27 +06:00
Nikolay Kim
16ff283fb2 add metadata 2019-12-01 20:30:24 +06:00
Nikolay Kim
503c2feb08 re-export net primitives 2019-12-01 10:56:25 +06:00
Nikolay Kim
bec4efc699 add extra methods to pipeline 2019-11-29 13:51:00 +06:00
Nikolay Kim
5e5ae2ddec restore stream dispatcher 2019-11-29 10:41:09 +06:00
Nikolay Kim
a02064592b disable rustls 2019-11-27 21:03:26 +06:00
Nikolay Kim
af72005159 move BoxFuture to boxed mod 2019-11-27 20:59:36 +06:00
Nikolay Kim
c254bb978c allow to wait on Server until server stops; restore signal handling 2019-11-26 17:03:52 +06:00
Nikolay Kim
009f8e2e7c allow to wait server exit 2019-11-26 16:33:45 +06:00
Nikolay Kim
f5aecdee8f work around to rust#62127 2019-11-26 10:14:21 +06:00
Nikolay Kim
4546774f4e inclide fn ident to err message 2019-11-26 10:04:46 +06:00
Nikolay Kim
2cf140a869 inclide fn token to err message 2019-11-26 10:01:46 +06:00
Nikolay Kim
e76ea8e80c re-export timeout 2019-11-26 09:04:14 +06:00
Nikolay Kim
52d03fa18c use actix deps instead of tokio 2019-11-26 08:26:22 +06:00
Nikolay Kim
5efac449b1 re-export time utils 2019-11-26 08:12:16 +06:00
Nikolay Kim
4ceac79f2c add test and main macros 2019-11-25 21:49:11 +06:00
Nikolay Kim
1fddd1e75b renamed boxed service 2019-11-25 18:18:00 +06:00
Nikolay Kim
905d058454 upgrade derive_more 2019-11-25 17:54:47 +06:00
158 changed files with 6375 additions and 4955 deletions

View File

@@ -1,41 +0,0 @@
environment:
global:
PROJECT_NAME: actix-net
matrix:
# Stable channel
- TARGET: i686-pc-windows-msvc
CHANNEL: stable
- TARGET: x86_64-pc-windows-gnu
CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
# Nightly channel
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly
- TARGET: x86_64-pc-windows-gnu
CHANNEL: nightly
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
# Install Rust and Cargo
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
install:
- ps: >-
If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') {
$Env:PATH += ';C:\msys64\mingw64\bin'
} ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') {
$Env:PATH += ';C:\MinGW\bin'
}
- curl -sSf -o rustup-init.exe https://win.rustup.rs
- rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -Vv
- cargo -V
# 'cargo test' takes care of building for us, so disable Appveyor's build stage.
build: false
# Equivalent to Travis' `script` phase
test_script:
- cargo clean
- cargo test

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 -->

29
.github/workflows/bench.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Benchmark (Linux)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
check_benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- 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: --all-features --all --tests

82
.github/workflows/linux.yml vendored Normal file
View File

@@ -0,0 +1,82 @@
name: CI (Linux)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- 1.42.0
- stable
- nightly
name: ${{ matrix.version }} - x86_64-unknown-linux-gnu
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- name: Cache cargo dirs
uses: actions/cache@v2
with:
path:
~/.cargo/registry
~/.cargo/git
~/.cargo/bin
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v2
with:
path: target
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture
- name: Generate coverage file
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
run: |
cargo install cargo-tarpaulin
cargo tarpaulin --out Xml --workspace --all-features
- name: Upload to Codecov
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
uses: codecov/codecov-action@v1
with:
file: cobertura.xml
- name: Clear the cargo caches
run: |
rustup update stable
rustup override set stable
cargo install cargo-cache --no-default-features --features ci-autoclean
cargo-cache

43
.github/workflows/macos.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: CI (macOS)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
name: ${{ matrix.version }} - x86_64-apple-darwin
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-apple-darwin
profile: minimal
override: true
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture

45
.github/workflows/windows-mingw.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: CI (Windows-mingw)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
name: ${{ matrix.version }} - x86_64-pc-windows-gnu
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-pc-windows-gnu
profile: minimal
override: true
- name: Install MSYS2
uses: msys2/setup-msys2@v2
- name: Install packages
run: |
msys2 -c 'pacman -Sy --noconfirm pacman'
msys2 -c 'pacman --noconfirm -S base-devel pkg-config'
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests

69
.github/workflows/windows.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: CI (Windows)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
env:
VCPKGRS_DYNAMIC: 1
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
target:
- x86_64-pc-windows-msvc
- i686-pc-windows-msvc
name: ${{ matrix.version }} - ${{ matrix.target }}
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-${{ matrix.target }}
profile: minimal
override: true
- name: Install OpenSSL (x64)
if: matrix.target == 'x86_64-pc-windows-msvc'
run: |
vcpkg integrate install
vcpkg install openssl:x64-windows
Get-ChildItem C:\vcpkg\installed\x64-windows\bin
Get-ChildItem C:\vcpkg\installed\x64-windows\lib
Copy-Item C:\vcpkg\installed\x64-windows\bin\libcrypto-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libcrypto.dll
Copy-Item C:\vcpkg\installed\x64-windows\bin\libssl-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libssl.dll
- name: Install OpenSSL (x86)
if: matrix.target == 'i686-pc-windows-msvc'
run: |
vcpkg integrate install
vcpkg install openssl:x86-windows
Get-ChildItem C:\vcpkg\installed\x86-windows\bin
Get-ChildItem C:\vcpkg\installed\x86-windows\lib
Copy-Item C:\vcpkg\installed\x86-windows\bin\libcrypto-1_1.dll C:\vcpkg\installed\x86-windows\bin\libcrypto.dll
Copy-Item C:\vcpkg\installed\x86-windows\bin\libssl-1_1.dll C:\vcpkg\installed\x86-windows\bin\libssl.dll
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture

View File

@@ -1,49 +0,0 @@
language: rust
sudo: required
dist: trusty
cache:
cargo: true
apt: true
matrix:
include:
- rust: stable
- rust: beta
- rust: nightly-2019-11-07
allow_failures:
- rust: nightly-2019-11-07
env:
global:
- RUSTFLAGS="-C link-dead-code"
- OPENSSL_VERSION=openssl-1.0.2
before_install:
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
- sudo apt-get update -qq
- sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
before_cache: |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-11-07" ]]; then
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install --version 0.6.11 cargo-tarpaulin
fi
# Add clippy
before_script:
- export PATH=$PATH:~/.cargo/bin
script:
- |
if [[ "$TRAVIS_RUST_VERSION" != "nightly-2019-11-07" ]]; then
cargo clean
cargo test --all --all-features -- --nocapture
fi
after_success:
- |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-11-07" ]]; then
taskset -c 0 cargo tarpaulin --all --all-features --out Xml
echo "Uploaded code coverage"
bash <(curl -s https://codecov.io/bash)
fi

View File

@@ -2,26 +2,30 @@
members = [
"actix-codec",
"actix-connect",
"actix-ioframe",
"actix-rt",
"actix-macros",
"actix-service",
"actix-server",
"actix-server-config",
"actix-testing",
"actix-threadpool",
"actix-tls",
"actix-tracing",
"actix-utils",
"router",
"string",
]
[patch.crates-io]
actix-codec = { path = "actix-codec" }
actix-connect = { path = "actix-connect" }
actix-ioframe = { path = "actix-ioframe" }
actix-rt = { path = "actix-rt" }
actix-macros = { path = "actix-macros" }
actix-server = { path = "actix-server" }
actix-server-config = { path = "actix-server-config" }
actix-service = { path = "actix-service" }
actix-testing = { path = "actix-testing" }
actix-threadpool = { path = "actix-threadpool" }
actix-tls = { path = "actix-tls" }
actix-tracing = { path = "actix-tracing" }
actix-utils = { path = "actix-utils" }
actix-router = { path = "router" }
bytestring = { path = "string" }

View File

@@ -1,11 +1,20 @@
# Actix net [![Build Status](https://travis-ci.org/actix/actix-net.svg?branch=master)](https://travis-ci.org/actix/actix-net) [![codecov](https://codecov.io/gh/actix/actix-net/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-net) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
# Actix net [![codecov](https://codecov.io/gh/actix/actix-net/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-net) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Actix net - framework for composable network services
## Build statuses
| Platform | Build Status |
| ---------------- | ------------ |
| Linux | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Linux)") |
| macOS | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28macOS%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(macOS)") |
| Windows | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Windows%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Windows)") |
| Windows (MinGW) | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Windows-mingw%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Windows-mingw)") |
## Documentation & community resources
* [Chat on gitter](https://gitter.im/actix/actix)
* Minimum supported Rust version: 1.39 or later
* [Chat on Gitter](https://gitter.im/actix/actix)
* Minimum supported Rust version: 1.42 or later
## Example
@@ -30,14 +39,16 @@ fn main() -> io::Result<()> {
let num = num.clone();
let acceptor = acceptor.clone();
// service for converting incoming TcpStream to a SslStream<TcpStream>
fn_service(move |stream: Io<tokio_tcp::TcpStream>| {
SslAcceptorExt::accept_async(&acceptor, stream.into_parts().0)
.map_err(|e| println!("Openssl error: {}", e))
})
// .and_then() combinator uses other service to convert incoming `Request` to a
// `Response` and then uses that response as an input for next
// service. in this case, on success we use `logger` service
// construct transformation pipeline
pipeline(
// service for converting incoming TcpStream to a SslStream<TcpStream>
fn_service(move |stream: actix_rt::net::TcpStream| async move {
SslAcceptorExt::accept_async(&acceptor, stream.into_parts().0).await
.map_err(|e| println!("Openssl error: {}", e))
}))
// .and_then() combinator chains result of previos service call to argument
/// for next service calll. in this case, on success we chain
/// ssl stream to the `logger` service.
.and_then(fn_service(logger))
// Next service counts number of connections
.and_then(move |_| {

View File

@@ -1,15 +1,37 @@
# Changes
## Unreleased - 2020-xx-xx
* Use `.advance()` instead of `.split_to()`.
* Upgrade `tokio-util` to `0.3`.
* Improve `BytesCodec` `.encode()` performance
* Simplify `BytesCodec` `.decode()`
## [0.2.0] - 2019-12-10
* Use specific futures dependencies
## [0.2.0-alpha.4]
* Fix buffer remaining capacity calculation
## [0.2.0-alpha.3]
* Use tokio 0.2
* Fix low/high watermark for write/read buffers
## [0.2.0-alpha.2]
* Migrated to `std::future`
## [0.1.2] - 2019-03-27
* Added `Framed::map_io()` method.
## [0.1.1] - 2019-03-06
* Added `FramedParts::with_read_buffer()` method.
## [0.1.0] - 2018-12-09
* Move codec to separate crate

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-codec"
version = "0.2.0-alpha.1"
version = "0.2.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Utilities for encoding and decoding frames"
keywords = ["network", "framework", "async", "futures"]
@@ -8,8 +8,7 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-codec/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
license = "MIT OR Apache-2.0"
edition = "2018"
workspace = ".."
@@ -18,9 +17,11 @@ name = "actix_codec"
path = "src/lib.rs"
[dependencies]
bytes = "0.4.12"
futures = "0.3.1"
pin-project = "0.4.5"
tokio-io = "0.2.0-alpha.6"
tokio-codec = "0.2.0-alpha.6"
log = "0.4"
bitflags = "1.2.1"
bytes = "0.5.2"
futures-core = { version = "0.3.4", default-features = false }
futures-sink = { version = "0.3.4", default-features = false }
tokio = { version = "0.2.5", default-features = false }
tokio-util = { version = "0.3.1", default-features = false, features = ["codec"] }
log = "0.4"
pin-project = "0.4.17"

View File

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

View File

@@ -1,40 +1,42 @@
#![allow(deprecated)]
use std::fmt;
use std::io::{self};
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{fmt, io};
use bytes::BytesMut;
use futures::{ready, Sink, Stream};
use bytes::{Buf, BytesMut};
use futures_core::{ready, Stream};
use futures_sink::Sink;
use pin_project::pin_project;
use tokio_codec::{Decoder, Encoder};
use tokio_io::{AsyncRead, AsyncWrite};
use crate::{AsyncRead, AsyncWrite, Decoder, Encoder};
/// Low-water mark
const LW: usize = 1024;
/// High-water mark
const HW: usize = 8 * 1024;
const INITIAL_CAPACITY: usize = 8 * 1024;
bitflags::bitflags! {
struct Flags: u8 {
const EOF = 0b0001;
const READABLE = 0b0010;
}
}
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using
/// the `Encoder` and `Decoder` traits to encode and decode frames.
///
/// You can create a `Framed` instance by using the `AsyncRead::framed` adapter.
#[pin_project]
pub struct Framed<T, U> {
#[pin]
io: T,
codec: U,
eof: bool,
is_readable: bool,
flags: Flags,
read_buf: BytesMut,
write_buf: BytesMut,
write_lw: usize,
write_hw: usize,
}
impl<T, U> Framed<T, U>
where
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.
@@ -49,35 +51,13 @@ where
/// `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.
///
/// If you want to work more directly with the streams and sink, consider
/// calling `split` on the `Framed` returned by this method, which will
/// break them into separate objects, allowing them to interact more easily.
pub fn new(io: T, codec: U) -> Framed<T, U> {
Framed {
io,
codec,
eof: false,
is_readable: false,
read_buf: BytesMut::with_capacity(INITIAL_CAPACITY),
flags: Flags::empty(),
read_buf: BytesMut::with_capacity(HW),
write_buf: BytesMut::with_capacity(HW),
write_lw: LW,
write_hw: HW,
}
}
/// Same as `Framed::new()` with ability to specify write buffer low/high capacity watermarks.
pub fn new_with_caps(io: T, codec: U, lw: usize, hw: usize) -> Framed<T, U> {
debug_assert!((lw < hw) && hw != 0);
Framed {
io,
codec,
eof: false,
is_readable: false,
read_buf: BytesMut::with_capacity(INITIAL_CAPACITY),
write_buf: BytesMut::with_capacity(hw),
write_lw: lw,
write_hw: hw,
}
}
}
@@ -100,19 +80,12 @@ impl<T, U> Framed<T, U> {
/// 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.
///
/// If you want to work more directly with the streams and sink, consider
/// calling `split` on the `Framed` returned by this method, which will
/// break them into separate objects, allowing them to interact more easily.
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
Framed {
io: parts.io,
codec: parts.codec,
eof: false,
is_readable: false,
flags: parts.flags,
write_buf: parts.write_buf,
write_lw: parts.write_buf_lw,
write_hw: parts.write_buf_hw,
read_buf: parts.read_buf,
}
}
@@ -154,63 +127,45 @@ impl<T, U> Framed<T, U> {
/// Check if write buffer is full.
pub fn is_write_buf_full(&self) -> bool {
self.write_buf.len() >= self.write_hw
}
/// Consumes the `Frame`, returning its underlying I/O 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
/// being worked with.
pub fn into_inner(self) -> T {
self.io
self.write_buf.len() >= HW
}
/// Consume the `Frame`, returning `Frame` with different codec.
pub fn into_framed<U2>(self, codec: U2) -> Framed<T, U2> {
pub fn into_framed<U2, I2>(self, codec: U2) -> Framed<T, U2> {
Framed {
io: self.io,
codec,
eof: self.eof,
is_readable: self.is_readable,
io: self.io,
flags: self.flags,
read_buf: self.read_buf,
write_buf: self.write_buf,
write_lw: self.write_lw,
write_hw: self.write_hw,
}
}
/// Consume the `Frame`, returning `Frame` with different io.
pub fn map_io<F, T2>(self, f: F) -> Framed<T2, U>
pub fn map_io<F, T2, I2>(self, f: F) -> Framed<T2, U>
where
F: Fn(T) -> T2,
{
Framed {
io: f(self.io),
codec: self.codec,
eof: self.eof,
is_readable: self.is_readable,
flags: self.flags,
read_buf: self.read_buf,
write_buf: self.write_buf,
write_lw: self.write_lw,
write_hw: self.write_hw,
}
}
/// Consume the `Frame`, returning `Frame` with different codec.
pub fn map_codec<F, U2>(self, f: F) -> Framed<T, U2>
pub fn map_codec<F, U2, I2>(self, f: F) -> Framed<T, U2>
where
F: Fn(U) -> U2,
{
Framed {
io: self.io,
codec: f(self.codec),
eof: self.eof,
is_readable: self.is_readable,
flags: self.flags,
read_buf: self.read_buf,
write_buf: self.write_buf,
write_lw: self.write_lw,
write_hw: self.write_hw,
}
}
@@ -224,50 +179,57 @@ impl<T, U> Framed<T, U> {
FramedParts {
io: self.io,
codec: self.codec,
flags: self.flags,
read_buf: self.read_buf,
write_buf: self.write_buf,
write_buf_lw: self.write_lw,
write_buf_hw: self.write_hw,
_priv: (),
}
}
}
impl<T, U> Framed<T, U> {
/// Serialize item and Write to the inner buffer
pub fn write(&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
T: AsyncWrite,
U: Encoder,
U: Encoder<I>,
{
let len = self.write_buf.len();
if len < self.write_lw {
self.write_buf.reserve(self.write_hw - len)
let this = self.as_mut().project();
let remaining = this.write_buf.capacity() - this.write_buf.len();
if remaining < LW {
this.write_buf.reserve(HW - remaining);
}
self.codec.encode(item, &mut self.write_buf)?;
this.codec.encode(item, this.write_buf)?;
Ok(())
}
pub fn is_ready(&self) -> bool {
let len = self.write_buf.len();
len < self.write_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
}
pub fn next_item(&mut self, cx: &mut Context) -> Poll<Option<Result<U::Item, U::Error>>>
/// 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 as Decoder>::Item, U::Error>>>
where
T: AsyncRead,
U: Decoder,
{
loop {
let mut this = self.as_mut().project();
// Repeatedly call `decode` or `decode_eof` as long as it is
// "readable". Readable is defined as not having returned `None`. If
// the upstream has returned EOF, and the decoder is no longer
// readable, it can be assumed that the decoder will never become
// readable again, at which point the stream is terminated.
if self.is_readable {
if self.eof {
match self.codec.decode_eof(&mut self.read_buf) {
if this.flags.contains(Flags::READABLE) {
if this.flags.contains(Flags::EOF) {
match this.codec.decode_eof(&mut this.read_buf) {
Ok(Some(frame)) => return Poll::Ready(Some(Ok(frame))),
Ok(None) => return Poll::Ready(None),
Err(e) => return Poll::Ready(Some(Err(e))),
@@ -276,84 +238,86 @@ impl<T, U> Framed<T, U> {
log::trace!("attempting to decode a frame");
match self.codec.decode(&mut self.read_buf) {
match this.codec.decode(&mut this.read_buf) {
Ok(Some(frame)) => {
log::trace!("frame decoded from buffer");
return Poll::Ready(Some(Ok(frame)));
}
Err(e) => return Poll::Ready(Some(Err(e))),
_ => {
// Need more data
}
_ => (), // Need more data
}
self.is_readable = false;
this.flags.remove(Flags::READABLE);
}
assert!(!self.eof);
debug_assert!(!this.flags.contains(Flags::EOF));
// Otherwise, try to read more data and try again. Make sure we've
// got room for at least one byte to read to ensure that we don't
// get a spurious 0 that looks like EOF
self.read_buf.reserve(1);
let cnt = unsafe {
match Pin::new_unchecked(&mut self.io).poll_read_buf(cx, &mut self.read_buf) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))),
Poll::Ready(Ok(cnt)) => cnt,
}
// Otherwise, try to read more data and try again. Make sure we've got room
let remaining = this.read_buf.capacity() - this.read_buf.len();
if remaining < LW {
this.read_buf.reserve(HW - remaining)
}
let cnt = match this.io.poll_read_buf(cx, &mut this.read_buf) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))),
Poll::Ready(Ok(cnt)) => cnt,
};
if cnt == 0 {
self.eof = true;
this.flags.insert(Flags::EOF);
}
self.is_readable = true;
this.flags.insert(Flags::READABLE);
}
}
pub fn flush(&mut self, cx: &mut Context) -> Poll<Result<(), U::Error>>
/// Flush write buffer to underlying I/O stream.
pub fn flush<I>(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), U::Error>>
where
T: AsyncWrite,
U: Encoder,
U: Encoder<I>,
{
let mut this = self.as_mut().project();
log::trace!("flushing framed transport");
while !self.write_buf.is_empty() {
log::trace!("writing; remaining={}", self.write_buf.len());
while !this.write_buf.is_empty() {
log::trace!("writing; remaining={}", this.write_buf.len());
let n = ready!(
unsafe { Pin::new_unchecked(&mut self.io) }.poll_write(cx, &self.write_buf)
)?;
let n = ready!(this.io.as_mut().poll_write(cx, this.write_buf))?;
if n == 0 {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::WriteZero,
"failed to \
write frame to transport",
"failed to write frame to transport",
)
.into()));
}
// TODO: Add a way to `bytes` to do this w/o returning the drained
// data.
let _ = self.write_buf.split_to(n);
// remove written data
this.write_buf.advance(n);
}
// Try flushing the underlying IO
ready!(unsafe { Pin::new_unchecked(&mut self.io) }.poll_flush(cx))?;
ready!(this.io.poll_flush(cx))?;
log::trace!("framed transport flushed");
Poll::Ready(Ok(()))
}
pub fn close(&mut self, cx: &mut Context) -> Poll<Result<(), U::Error>>
/// Flush write buffer and shutdown underlying I/O stream.
pub fn close<I>(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), U::Error>>
where
T: AsyncWrite,
U: Encoder,
U: Encoder<I>,
{
ready!(unsafe { Pin::new_unchecked(&mut self.io) }.poll_flush(cx))?;
ready!(unsafe { Pin::new_unchecked(&mut self.io) }.poll_shutdown(cx))?;
let mut this = self.as_mut().project();
ready!(this.io.as_mut().poll_flush(cx))?;
ready!(this.io.as_mut().poll_shutdown(cx))?;
Poll::Ready(Ok(()))
}
}
@@ -365,39 +329,36 @@ where
{
type Item = Result<U::Item, U::Error>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.next_item(cx)
}
}
impl<T, U> Sink<U::Item> for Framed<T, U>
impl<T, U, I> Sink<I> for Framed<T, U>
where
T: AsyncWrite,
U: Encoder,
U: Encoder<I>,
U::Error: From<io::Error>,
{
type Error = U::Error;
fn poll_ready(self: Pin<&mut Self>, _: &mut Context) -> Poll<Result<(), Self::Error>> {
if self.is_ready() {
fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if self.is_write_ready() {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
fn start_send(
mut self: Pin<&mut Self>,
item: <U as Encoder>::Item,
) -> Result<(), Self::Error> {
fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
self.write(item)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.flush(cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.close(cx)
}
}
@@ -407,7 +368,7 @@ where
T: fmt::Debug,
U: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Framed")
.field("io", &self.io)
.field("codec", &self.codec)
@@ -432,15 +393,7 @@ pub struct FramedParts<T, U> {
/// A buffer with unprocessed data which are not written yet.
pub write_buf: BytesMut,
/// A buffer low watermark capacity
pub write_buf_lw: usize,
/// A buffer high watermark capacity
pub write_buf_hw: usize,
/// This private field allows us to add additional fields in the future in a
/// backwards compatible way.
_priv: (),
flags: Flags,
}
impl<T, U> FramedParts<T, U> {
@@ -449,11 +402,9 @@ impl<T, U> FramedParts<T, U> {
FramedParts {
io,
codec,
flags: Flags::empty(),
read_buf: BytesMut::new(),
write_buf: BytesMut::new(),
write_buf_lw: LW,
write_buf_hw: HW,
_priv: (),
}
}
@@ -463,10 +414,8 @@ impl<T, U> FramedParts<T, U> {
io,
codec,
read_buf,
flags: Flags::empty(),
write_buf: BytesMut::new(),
write_buf_lw: LW,
write_buf_hw: HW,
_priv: (),
}
}
}

View File

@@ -2,13 +2,13 @@
//!
//! Contains adapters to go from streams of bytes, [`AsyncRead`] and
//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`].
//! Framed streams are also known as [transports].
//! Framed streams are also known as `transports`.
//!
//! [`AsyncRead`]: #
//! [`AsyncWrite`]: #
//! [`Sink`]: #
//! [`Stream`]: #
//! [transports]: #
//! [`AsyncRead`]: AsyncRead
//! [`AsyncWrite`]: AsyncWrite
//! [`Sink`]: futures_sink::Sink
//! [`Stream`]: futures_core::Stream
#![deny(rust_2018_idioms)]
mod bcodec;
mod framed;
@@ -16,5 +16,5 @@ mod framed;
pub use self::bcodec::BytesCodec;
pub use self::framed::{Framed, FramedParts};
pub use tokio_codec::{Decoder, Encoder};
pub use tokio_io::{AsyncRead, AsyncWrite};
pub use tokio::io::{AsyncRead, AsyncWrite};
pub use tokio_util::codec::{Decoder, Encoder};

View File

@@ -1,5 +1,66 @@
# Changes
## Unreleased
## 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
### Changed
* Update `trust-dns-proto` dependency to 0.19. [#116]
* Update `trust-dns-resolver` dependency to 0.19. [#116]
* `Address` trait is now required to have static lifetime. [#116]
* `start_resolver` and `start_default_resolver` are now `async` and may return a `ConnectError`. [#116]
[#116]: https://github.com/actix/actix-net/pull/116
## [2.0.0-alpha.1] - 2020-03-03
### Changed
* Update `rustls` dependency to 0.17
* Update `tokio-rustls` dependency to 0.13
## [1.0.2] - 2020-01-15
* Fix actix-service 1.0.3 compatibility
## [1.0.1] - 2019-12-15
* Fix trust-dns-resolver compilation
## [1.0.0] - 2019-12-11
* Release
## [1.0.0-alpha.3] - 2019-12-07
### Changed
* Migrate to tokio 0.2
## [1.0.0-alpha.2] - 2019-12-02
### Changed
* Migrated to `std::future`
## [0.3.0] - 2019-10-03
### Changed

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-connect"
version = "1.0.0-alpha.1"
version = "2.0.0-alpha.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix connect - tcp connector service"
keywords = ["network", "framework", "async", "futures"]
@@ -8,10 +8,8 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-connect/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
license = "MIT OR Apache-2.0"
edition = "2018"
workspace = ".."
[package.metadata.docs.rs]
features = ["openssl", "rustls", "uri"]
@@ -33,31 +31,27 @@ rustls = ["rust-tls", "tokio-rustls", "webpki"]
uri = ["http"]
[dependencies]
actix-service = "1.0.0-alpha.1"
actix-codec = "0.2.0-alpha.1"
actix-utils = "0.5.0-alpha.1"
actix-rt = "1.0.0-alpha.1"
derive_more = "0.15"
either = "1.5.2"
futures = "0.3.1"
http = { version = "0.1.17", optional = true }
actix-service = "1.0.3"
actix-codec = "0.2.0"
actix-utils = "1.0.6"
actix-rt = "1.0.0"
derive_more = "0.99.2"
either = "1.5.3"
futures-util = { version = "0.3.4", default-features = false }
http = { version = "0.2.0", optional = true }
log = "0.4"
tokio-net = "=0.2.0-alpha.6"
tokio-executor = "=0.2.0-alpha.6"
trust-dns-resolver = { version="0.18.0-alpha.1", default-features = false }
trust-dns-proto = { version = "0.19", default-features = false, features = ["tokio-runtime"] }
trust-dns-resolver = { version = "0.19", default-features = false, features = ["tokio-runtime", "system-config"] }
# openssl
open-ssl = { version="0.10", package = "openssl", optional = true }
tokio-openssl = { version = "0.4.0-alpha.6", optional = true }
tokio-openssl = { version = "0.4.0", optional = true }
# rustls
rust-tls = { version = "0.16.0", package = "rustls", optional = true }
# tokio-rustls = { version = "0.10.0", optional = true }
tokio-rustls = { git = "https://github.com/quininer/tokio-rustls.git", branch = "tokio-0.2", optional = true }
rust-tls = { version = "0.18.0", package = "rustls", optional = true }
tokio-rustls = { version = "0.14.0", optional = true }
webpki = { version = "0.21", optional = true }
[dev-dependencies]
bytes = "0.4"
actix-testing = { version="0.3.0-alpha.1" }
actix-server-config = "0.3.0-alpha.1"
tokio = "0.2.0-alpha.6"
bytes = "0.5.3"
actix-testing = { version="1.0.0" }

View File

@@ -6,7 +6,7 @@ use std::net::SocketAddr;
use either::Either;
/// Connect request
pub trait Address: Unpin {
pub trait Address: Unpin + 'static {
/// Host name of the request
fn host(&self) -> &str;
@@ -132,7 +132,7 @@ impl<T: Address> From<T> for Connect<T> {
}
impl<T: Address> fmt::Display for Connect<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.host(), self.port())
}
}
@@ -163,7 +163,7 @@ impl Iterator for ConnectAddrsIter<'_> {
}
impl fmt::Debug for ConnectAddrsIter<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.clone()).finish()
}
}
@@ -275,7 +275,7 @@ impl<T, U> std::ops::DerefMut for Connection<T, U> {
}
impl<T, U: fmt::Debug> fmt::Debug for Connection<T, U> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Stream {{{:?}}}", self.io)
}
}

View File

@@ -6,9 +6,9 @@ use std::net::SocketAddr;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_rt::net::TcpStream;
use actix_service::{Service, ServiceFactory};
use futures::future::{err, ok, BoxFuture, Either, FutureExt, Ready};
use tokio_net::tcp::TcpStream;
use futures_util::future::{err, ok, BoxFuture, Either, FutureExt, Ready};
use super::connect::{Address, Connect, Connection};
use super::error::ConnectError;
@@ -49,7 +49,7 @@ impl<T: Address> ServiceFactory for TcpConnectorFactory<T> {
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &()) -> Self::Future {
fn new_service(&self, _: ()) -> Self::Future {
ok(self.service())
}
}
@@ -76,7 +76,7 @@ impl<T: Address> Service for TcpConnector<T> {
type Error = ConnectError;
type Future = Either<TcpConnectorResponse<T>, Ready<Result<Self::Response, Self::Error>>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
@@ -88,7 +88,7 @@ impl<T: Address> Service for TcpConnector<T> {
Either::Left(TcpConnectorResponse::new(req, port, addr))
} else {
error!("TCP connector: got unresolved address");
Either::Right(err(ConnectError::Unresolverd))
Either::Right(err(ConnectError::Unresolved))
}
}
}
@@ -134,7 +134,7 @@ impl<T: Address> TcpConnectorResponse<T> {
impl<T: Address> Future for TcpConnectorResponse<T> {
type Output = Result<Connection<T, TcpStream>, ConnectError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
// connect

View File

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

View File

@@ -2,9 +2,10 @@
//!
//! ## Package feature
//!
//! * `ssl` - enables ssl support via `openssl` crate
//! * `rust-tls` - enables ssl support via `rustls` crate
//! * `openssl` - enables ssl support via `openssl` crate
//! * `rustls` - enables ssl support via `rustls` crate
#![deny(rust_2018_idioms, warnings)]
#![allow(clippy::type_complexity)]
#![recursion_limit = "128"]
#[macro_use]
@@ -13,38 +14,43 @@ extern crate log;
mod connect;
mod connector;
mod error;
mod resolver;
mod resolve;
mod service;
pub mod ssl;
#[cfg(feature = "uri")]
mod uri;
use actix_rt::Arbiter;
use actix_rt::{net::TcpStream, Arbiter};
use actix_service::{pipeline, pipeline_factory, Service, ServiceFactory};
use tokio_net::tcp::TcpStream;
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
use trust_dns_resolver::system_conf::read_system_conf;
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
pub use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
pub use trust_dns_resolver::system_conf::read_system_conf;
pub use trust_dns_resolver::{error::ResolveError, AsyncResolver};
pub mod resolver {
pub use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
pub use trust_dns_resolver::system_conf::read_system_conf;
pub use trust_dns_resolver::{error::ResolveError, AsyncResolver};
}
pub use self::connect::{Address, Connect, Connection};
pub use self::connector::{TcpConnector, TcpConnectorFactory};
pub use self::error::ConnectError;
pub use self::resolver::{Resolver, ResolverFactory};
pub use self::resolve::{Resolver, ResolverFactory};
pub use self::service::{ConnectService, ConnectServiceFactory, TcpConnectService};
pub fn start_resolver(cfg: ResolverConfig, opts: ResolverOpts) -> AsyncResolver {
let (resolver, bg) = AsyncResolver::new(cfg, opts);
tokio_executor::current_thread::spawn(bg);
resolver
pub async fn start_resolver(
cfg: ResolverConfig,
opts: ResolverOpts,
) -> Result<AsyncResolver, ConnectError> {
Ok(AsyncResolver::tokio(cfg, opts).await?)
}
struct DefaultResolver(AsyncResolver);
pub(crate) fn get_default_resolver() -> AsyncResolver {
pub(crate) async fn get_default_resolver() -> Result<AsyncResolver, ConnectError> {
if Arbiter::contains_item::<DefaultResolver>() {
return Arbiter::get_item(|item: &DefaultResolver| item.0.clone());
Ok(Arbiter::get_item(|item: &DefaultResolver| item.0.clone()))
} else {
let (cfg, opts) = match read_system_conf() {
Ok((cfg, opts)) => (cfg, opts),
@@ -54,20 +60,19 @@ pub(crate) fn get_default_resolver() -> AsyncResolver {
}
};
let (resolver, bg) = AsyncResolver::new(cfg, opts);
tokio_executor::current_thread::spawn(bg);
let resolver = AsyncResolver::tokio(cfg, opts).await?;
Arbiter::set_item(DefaultResolver(resolver.clone()));
resolver
Ok(resolver)
}
}
pub fn start_default_resolver() -> AsyncResolver {
get_default_resolver()
pub async fn start_default_resolver() -> Result<AsyncResolver, ConnectError> {
get_default_resolver().await
}
/// Create tcp connector service
pub fn new_connector<T: Address>(
pub fn new_connector<T: Address + 'static>(
resolver: AsyncResolver,
) -> impl Service<Request = Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError>
+ Clone {
@@ -75,7 +80,7 @@ pub fn new_connector<T: Address>(
}
/// Create tcp connector service
pub fn new_connector_factory<T: Address>(
pub fn new_connector_factory<T: Address + 'static>(
resolver: AsyncResolver,
) -> impl ServiceFactory<
Config = (),
@@ -88,14 +93,14 @@ pub fn new_connector_factory<T: Address>(
}
/// Create connector service with default parameters
pub fn default_connector<T: Address>(
pub fn default_connector<T: Address + 'static>(
) -> impl Service<Request = Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError>
+ Clone {
pipeline(Resolver::default()).and_then(TcpConnector::new())
}
/// Create connector service factory with default parameters
pub fn default_connector_factory<T: Address>() -> impl ServiceFactory<
pub fn default_connector_factory<T: Address + 'static>() -> impl ServiceFactory<
Config = (),
Request = Connect<T>,
Response = Connection<T, TcpStream>,

View File

@@ -5,9 +5,9 @@ use std::pin::Pin;
use std::task::{Context, Poll};
use actix_service::{Service, ServiceFactory};
use futures::future::{ok, Either, Ready};
use trust_dns_resolver::lookup_ip::LookupIpFuture;
use trust_dns_resolver::{AsyncResolver, Background};
use futures_util::future::{ok, Either, Ready};
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
use trust_dns_resolver::{error::ResolveError, lookup_ip::LookupIp};
use crate::connect::{Address, Connect};
use crate::error::ConnectError;
@@ -63,7 +63,7 @@ impl<T: Address> ServiceFactory for ResolverFactory<T> {
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &()) -> Self::Future {
fn new_service(&self, _: ()) -> Self::Future {
ok(self.service())
}
}
@@ -106,9 +106,12 @@ impl<T: Address> Service for Resolver<T> {
type Request = Connect<T>;
type Response = Connect<T>;
type Error = ConnectError;
type Future = Either<ResolverFuture<T>, Ready<Result<Connect<T>, Self::Error>>>;
type Future = Either<
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>,
Ready<Result<Connect<T>, Self::Error>>,
>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
@@ -119,32 +122,48 @@ impl<T: Address> Service for Resolver<T> {
req.addr = Some(either::Either::Left(SocketAddr::new(ip, req.port())));
Either::Right(ok(req))
} else {
trace!("DNS resolver: resolving host {:?}", req.host());
if self.resolver.is_none() {
self.resolver = Some(get_default_resolver());
}
Either::Left(ResolverFuture::new(req, self.resolver.as_ref().unwrap()))
let resolver = self.resolver.as_ref().map(AsyncResolver::clone);
Either::Left(Box::pin(async move {
trace!("DNS resolver: resolving host {:?}", req.host());
let resolver = if let Some(resolver) = resolver {
resolver
} else {
get_default_resolver()
.await
.expect("Failed to get default resolver")
};
ResolverFuture::new(req, &resolver).await
}))
}
}
}
type LookupIpFuture = Pin<Box<dyn Future<Output = Result<LookupIp, ResolveError>>>>;
#[doc(hidden)]
/// Resolver future
pub struct ResolverFuture<T: Address> {
req: Option<Connect<T>>,
lookup: Background<LookupIpFuture>,
lookup: LookupIpFuture,
}
impl<T: Address> ResolverFuture<T> {
pub fn new(req: Connect<T>, resolver: &AsyncResolver) -> Self {
let lookup = if let Some(host) = req.host().splitn(2, ':').next() {
resolver.lookup_ip(host)
let host = if let Some(host) = req.host().splitn(2, ':').next() {
host
} else {
resolver.lookup_ip(req.host())
req.host()
};
// Clone data to be moved to the lookup future
let host_clone = host.to_owned();
let resolver_clone = resolver.clone();
ResolverFuture {
lookup,
lookup: Box::pin(async move {
let resolver = resolver_clone;
resolver.lookup_ip(host_clone).await
}),
req: Some(req),
}
}
@@ -153,7 +172,7 @@ impl<T: Address> ResolverFuture<T> {
impl<T: Address> Future for ResolverFuture<T> {
type Output = Result<Connect<T>, ConnectError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
match Pin::new(&mut this.lookup).poll(cx) {

View File

@@ -2,16 +2,16 @@ use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_rt::net::TcpStream;
use actix_service::{Service, ServiceFactory};
use either::Either;
use futures::future::{ok, Ready};
use tokio_net::tcp::TcpStream;
use trust_dns_resolver::AsyncResolver;
use futures_util::future::{ok, Ready};
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
use crate::connect::{Address, Connect, Connection};
use crate::connector::{TcpConnector, TcpConnectorFactory};
use crate::error::ConnectError;
use crate::resolver::{Resolver, ResolverFactory};
use crate::resolve::{Resolver, ResolverFactory};
pub struct ConnectServiceFactory<T> {
tcp: TcpConnectorFactory<T>,
@@ -79,7 +79,7 @@ impl<T: Address> ServiceFactory for ConnectServiceFactory<T> {
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &()) -> Self::Future {
fn new_service(&self, _: ()) -> Self::Future {
ok(self.service())
}
}
@@ -96,7 +96,7 @@ impl<T: Address> Service for ConnectService<T> {
type Error = ConnectError;
type Future = ConnectServiceResponse<T>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
@@ -116,7 +116,7 @@ enum ConnectState<T: Address> {
impl<T: Address> ConnectState<T> {
fn poll(
&mut self,
cx: &mut Context,
cx: &mut Context<'_>,
) -> Either<Poll<Result<Connection<T, TcpStream>, ConnectError>>, Connect<T>> {
match self {
ConnectState::Resolve(ref mut fut) => match Pin::new(fut).poll(cx) {
@@ -137,7 +137,7 @@ pub struct ConnectServiceResponse<T: Address> {
impl<T: Address> Future for ConnectServiceResponse<T> {
type Output = Result<Connection<T, TcpStream>, ConnectError>;
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 res = match self.state.poll(cx) {
Either::Right(res) => {
self.state = ConnectState::Connect(self.tcp.call(res));
@@ -165,7 +165,7 @@ impl<T: Address + 'static> Service for TcpConnectService<T> {
type Error = ConnectError;
type Future = TcpConnectServiceResponse<T>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
@@ -185,7 +185,7 @@ enum TcpConnectState<T: Address> {
impl<T: Address> TcpConnectState<T> {
fn poll(
&mut self,
cx: &mut Context,
cx: &mut Context<'_>,
) -> Either<Poll<Result<TcpStream, ConnectError>>, Connect<T>> {
match self {
TcpConnectState::Resolve(ref mut fut) => match Pin::new(fut).poll(cx) {
@@ -214,7 +214,7 @@ pub struct TcpConnectServiceResponse<T: Address> {
impl<T: Address> Future for TcpConnectServiceResponse<T> {
type Output = Result<TcpStream, ConnectError>;
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 res = match self.state.poll(cx) {
Either::Right(res) => {
self.state = TcpConnectState::Connect(self.tcp.call(res));

View File

@@ -1,13 +1,7 @@
//! SSL Services
#[cfg(feature = "openssl")]
mod openssl;
#[cfg(feature = "openssl")]
pub use self::openssl::{
OpensslConnectService, OpensslConnectServiceFactory, OpensslConnector,
};
pub mod openssl;
#[cfg(feature = "rustls")]
mod rustls;
#[cfg(feature = "rustls")]
pub use self::rustls::RustlsConnector;
pub mod rustls;

View File

@@ -4,13 +4,14 @@ use std::pin::Pin;
use std::task::{Context, Poll};
use std::{fmt, io};
pub use open_ssl::ssl::{Error as SslError, SslConnector, SslMethod};
pub use tokio_openssl::{HandshakeError, SslStream};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream;
use actix_service::{Service, ServiceFactory};
use futures::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready};
use open_ssl::ssl::SslConnector;
use tokio_net::tcp::TcpStream;
use tokio_openssl::{HandshakeError, SslStream};
use trust_dns_resolver::AsyncResolver;
use futures_util::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready};
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
use crate::{
Address, Connect, ConnectError, ConnectService, ConnectServiceFactory, Connection,
@@ -38,7 +39,7 @@ where
{
pub fn service(connector: SslConnector) -> OpensslConnectorService<T, U> {
OpensslConnectorService {
connector: connector,
connector,
_t: PhantomData,
}
}
@@ -66,7 +67,7 @@ where
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &()) -> Self::Future {
fn new_service(&self, _: ()) -> Self::Future {
ok(OpensslConnectorService {
connector: self.connector.clone(),
_t: PhantomData,
@@ -98,7 +99,7 @@ where
type Error = io::Error;
type Future = Either<ConnectAsyncExt<T, U>, Ready<Result<Self::Response, Self::Error>>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
@@ -131,7 +132,7 @@ where
{
type Output = Result<Connection<T, SslStream<U>>, io::Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
match Pin::new(&mut this.fut).poll(cx) {
@@ -201,7 +202,7 @@ impl<T: Address + 'static> ServiceFactory for OpensslConnectServiceFactory<T> {
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &()) -> Self::Future {
fn new_service(&self, _: ()) -> Self::Future {
ok(self.service())
}
}
@@ -218,7 +219,7 @@ impl<T: Address + 'static> Service for OpensslConnectService<T> {
type Error = ConnectError;
type Future = OpensslConnectServiceResponse<T>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
@@ -240,19 +241,19 @@ pub struct OpensslConnectServiceResponse<T: Address + 'static> {
impl<T: Address> Future for OpensslConnectServiceResponse<T> {
type Output = Result<SslStream<TcpStream>, ConnectError>;
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> {
if let Some(ref mut fut) = self.fut1 {
match futures::ready!(Pin::new(fut).poll(cx)) {
match futures_util::ready!(Pin::new(fut).poll(cx)) {
Ok(res) => {
let _ = self.fut1.take();
self.fut2 = Some(self.openssl.call(res));
}
Err(e) => return Poll::Ready(Err(e.into())),
Err(e) => return Poll::Ready(Err(e)),
}
}
if let Some(ref mut fut) = self.fut2 {
match futures::ready!(Pin::new(fut).poll(cx)) {
match futures_util::ready!(Pin::new(fut).poll(cx)) {
Ok(connect) => Poll::Ready(Ok(connect.into_parts().0)),
Err(e) => Poll::Ready(Err(ConnectError::Io(io::Error::new(
io::ErrorKind::Other,

View File

@@ -5,10 +5,13 @@ use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
pub use rust_tls::Session;
pub use tokio_rustls::{client::TlsStream, rustls::ClientConfig};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_service::{Service, ServiceFactory};
use futures::future::{ok, Ready};
use tokio_rustls::{client::TlsStream, rustls::ClientConfig, Connect, TlsConnector};
use futures_util::future::{ok, Ready};
use tokio_rustls::{Connect, TlsConnector};
use webpki::DNSNameRef;
use crate::{Address, Connection};
@@ -35,7 +38,7 @@ where
{
pub fn service(connector: Arc<ClientConfig>) -> RustlsConnectorService<T, U> {
RustlsConnectorService {
connector: connector,
connector,
_t: PhantomData,
}
}
@@ -62,7 +65,7 @@ where
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &()) -> Self::Future {
fn new_service(&self, _: ()) -> Self::Future {
ok(RustlsConnectorService {
connector: self.connector.clone(),
_t: PhantomData,
@@ -93,7 +96,7 @@ where
type Error = std::io::Error;
type Future = ConnectAsyncExt<T, U>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
@@ -120,10 +123,10 @@ where
{
type Output = Result<Connection<T, TlsStream<U>>, std::io::Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
Poll::Ready(
futures::ready!(Pin::new(&mut this.fut).poll(cx)).map(|stream| {
futures_util::ready!(Pin::new(&mut this.fut).poll(cx)).map(|stream| {
let s = this.stream.take().unwrap();
trace!("SSL Handshake success: {:?}", s.host());
s.replace(stream).1

View File

@@ -1,66 +1,60 @@
use std::io;
use actix_codec::{BytesCodec, Framed};
use actix_server_config::Io;
use actix_service::{service_fn, Service, ServiceFactory};
use actix_testing::{self as test, TestServer};
use actix_rt::net::TcpStream;
use actix_service::{fn_service, Service, ServiceFactory};
use actix_testing::TestServer;
use bytes::Bytes;
use futures::SinkExt;
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
use futures_util::sink::SinkExt;
use actix_connect::resolver::{ResolverConfig, ResolverOpts};
use actix_connect::Connect;
#[cfg(feature = "openssl")]
#[test]
fn test_string() {
#[actix_rt::test]
async fn test_string() {
let srv = TestServer::with(|| {
service_fn(|io: Io<tokio_net::tcp::TcpStream>| {
async {
let mut framed = Framed::new(io.into_parts().0, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
}
fn_service(|io: TcpStream| async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
})
});
let mut conn = actix_connect::default_connector();
let addr = format!("localhost:{}", srv.port());
let con = test::call_service(&mut conn, addr.into());
let con = conn.call(addr.into()).await.unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr());
}
#[cfg(feature = "rustls")]
#[test]
fn test_rustls_string() {
#[actix_rt::test]
async fn test_rustls_string() {
let srv = TestServer::with(|| {
service_fn(|io: Io<tokio_net::tcp::TcpStream>| {
async {
let mut framed = Framed::new(io.into_parts().0, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
}
fn_service(|io: TcpStream| async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
})
});
let mut conn = actix_connect::default_connector();
let addr = format!("localhost:{}", srv.port());
let con = test::call_service(&mut conn, addr.into());
let con = conn.call(addr.into()).await.unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr());
}
#[tokio::test]
#[actix_rt::test]
async fn test_static_str() {
let srv = TestServer::with(|| {
service_fn(|io: Io<tokio_net::tcp::TcpStream>| {
async {
let mut framed = Framed::new(io.into_parts().0, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
}
fn_service(|io: TcpStream| async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
})
});
let resolver = actix_connect::start_default_resolver();
let resolver = actix_connect::start_default_resolver().await.unwrap();
let mut conn = actix_connect::new_connector(resolver.clone());
let con = conn.call(Connect::with("10", srv.addr())).await.unwrap();
@@ -72,66 +66,62 @@ async fn test_static_str() {
assert!(con.is_err());
}
#[test]
fn test_new_service() {
#[actix_rt::test]
async fn test_new_service() {
let srv = TestServer::with(|| {
service_fn(|io: Io<tokio_net::tcp::TcpStream>| {
async {
let mut framed = Framed::new(io.into_parts().0, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
}
fn_service(|io: TcpStream| async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
})
});
let resolver = test::block_on(async {
let resolver =
actix_connect::start_resolver(ResolverConfig::default(), ResolverOpts::default())
});
let factory = test::block_on(async { actix_connect::new_connector_factory(resolver) });
.await
.unwrap();
let mut conn = test::block_on(factory.new_service(&())).unwrap();
let con = test::block_on(conn.call(Connect::with("10", srv.addr()))).unwrap();
let factory = actix_connect::new_connector_factory(resolver);
let mut conn = factory.new_service(()).await.unwrap();
let con = conn.call(Connect::with("10", srv.addr())).await.unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr());
}
#[cfg(feature = "openssl")]
#[test]
fn test_uri() {
use http::HttpTryFrom;
#[actix_rt::test]
async fn test_uri() {
use std::convert::TryFrom;
let srv = TestServer::with(|| {
service_fn(|io: Io<tokio_net::tcp::TcpStream>| {
async {
let mut framed = Framed::new(io.into_parts().0, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
}
fn_service(|io: TcpStream| async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
})
});
let mut conn = actix_connect::default_connector();
let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
let con = test::call_service(&mut conn, addr.into());
let con = conn.call(addr.into()).await.unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr());
}
#[cfg(feature = "rustls")]
#[test]
fn test_rustls_uri() {
use http::HttpTryFrom;
#[actix_rt::test]
async fn test_rustls_uri() {
use std::convert::TryFrom;
let srv = TestServer::with(|| {
service_fn(|io: Io<tokio_net::tcp::TcpStream>| {
async {
let mut framed = Framed::new(io.into_parts().0, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
}
fn_service(|io: TcpStream| async {
let mut framed = Framed::new(io, BytesCodec);
framed.send(Bytes::from_static(b"test")).await?;
Ok::<_, io::Error>(())
})
});
let mut conn = actix_connect::default_connector();
let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
let con = test::call_service(&mut conn, addr.into());
let con = conn.call(addr.into()).await.unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr());
}

View File

@@ -1,11 +0,0 @@
# Changes
## [0.1.1] - 2019-10-14
* Re-register task on every dispatcher poll.
## [0.1.0] - 2019-09-25
* Initial release

View File

@@ -1,37 +0,0 @@
[package]
name = "actix-ioframe"
version = "0.3.0-alpha.1"
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"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018"
workspace = ".."
[lib]
name = "actix_ioframe"
path = "src/lib.rs"
[dependencies]
actix-service = "1.0.0-alpha.1"
actix-codec = "0.2.0-alpha.1"
actix-utils = "0.5.0-alpha.1"
bytes = "0.4"
either = "1.5.2"
futures = "0.3.1"
pin-project = "0.4.5"
tokio-executor = "=0.2.0-alpha.6"
log = "0.4"
[dev-dependencies]
actix-rt = "1.0.0-alpha.1"
actix-connect = "1.0.0-alpha.1"
actix-testing = "0.3.0-alpha.1"
actix-server-config = "0.3.0-alpha.1"
tokio-net = "=0.2.0-alpha.6"
tokio-timer = "=0.3.0-alpha.6"

3
actix-ioframe/README.md Normal file
View File

@@ -0,0 +1,3 @@
# actix-ioframe
**This crate has been deprecated and removed.**

View File

@@ -1,39 +0,0 @@
//! Custom cell impl
use std::cell::UnsafeCell;
use std::fmt;
use std::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 fn new(inner: T) -> Self {
Self {
inner: Rc::new(UnsafeCell::new(inner)),
}
}
pub(crate) unsafe fn get_ref(&mut self) -> &T {
&*self.inner.as_ref().get()
}
pub(crate) unsafe fn get_mut(&mut self) -> &mut T {
&mut *self.inner.as_ref().get()
}
}

View File

@@ -1,120 +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;
use futures::Stream;
use crate::dispatcher::FramedMessage;
use crate::sink::Sink;
pub struct Connect<Io, St = (), Codec = ()> {
io: Io,
_t: PhantomData<(St, Codec)>,
}
impl<Io> Connect<Io>
where
Io: AsyncRead + AsyncWrite,
{
pub(crate) fn new(io: Io) -> Self {
Self {
io,
_t: PhantomData,
}
}
pub fn codec<Codec>(self, codec: Codec) -> ConnectResult<Io, (), Codec>
where
Codec: Encoder + Decoder,
{
let (tx, rx) = mpsc::channel();
let sink = Sink::new(tx);
ConnectResult {
state: (),
framed: Framed::new(self.io, codec),
rx,
sink,
}
}
}
#[pin_project::pin_project]
pub struct ConnectResult<Io, St, Codec: Encoder + Decoder> {
pub(crate) state: St,
pub(crate) framed: Framed<Io, Codec>,
pub(crate) rx: mpsc::Receiver<FramedMessage<<Codec as Encoder>::Item>>,
pub(crate) sink: Sink<<Codec as Encoder>::Item>,
}
impl<Io, St, Codec: Encoder + Decoder> ConnectResult<Io, St, Codec> {
#[inline]
pub fn sink(&self) -> &Sink<<Codec as Encoder>::Item> {
&self.sink
}
#[inline]
pub fn get_ref(&self) -> &Io {
self.framed.get_ref()
}
#[inline]
pub fn get_mut(&mut self) -> &mut Io {
self.framed.get_mut()
}
#[inline]
pub fn state<S>(self, state: S) -> ConnectResult<Io, S, Codec> {
ConnectResult {
state,
framed: self.framed,
rx: self.rx,
sink: self.sink,
}
}
}
impl<Io, St, Codec> Stream for ConnectResult<Io, St, Codec>
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> futures::Sink<<Codec as Encoder>::Item> for ConnectResult<Io, St, Codec>
where
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
{
type Error = <Codec as Encoder>::Error;
fn poll_ready(self: Pin<&mut Self>, _: &mut Context) -> Poll<Result<(), Self::Error>> {
if self.framed.is_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(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.get_mut().framed.flush(cx)
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.get_mut().framed.close(cx)
}
}

View File

@@ -1,393 +0,0 @@
//! Framed dispatcher service and related utilities
use std::collections::VecDeque;
use std::mem;
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, Service};
use actix_utils::task::LocalWaker;
use actix_utils::{mpsc, oneshot};
use futures::future::ready;
use futures::{FutureExt, Sink as FutureSink, Stream};
use log::debug;
use crate::cell::Cell;
use crate::error::ServiceError;
use crate::item::Item;
use crate::sink::Sink;
use crate::state::State;
type Request<S, U> = Item<S, U>;
type Response<U> = <U as Encoder>::Item;
pub(crate) enum FramedMessage<T> {
Message(T),
Close,
WaitClose(oneshot::Sender<()>),
}
/// FramedTransport - is a future that reads frames from Framed object
/// and pass then to the service.
#[pin_project::pin_project]
pub(crate) struct FramedDispatcher<St, S, T, U>
where
S: Service<Request = Request<St, 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,
{
service: S,
sink: Sink<<U as Encoder>::Item>,
state: State<St>,
dispatch_state: FramedState<S, U>,
framed: Framed<T, U>,
rx: Option<mpsc::Receiver<FramedMessage<<U as Encoder>::Item>>>,
inner: Cell<FramedDispatcherInner<<U as Encoder>::Item, S::Error>>,
disconnect: Option<Rc<dyn Fn(&mut St, bool)>>,
}
impl<St, S, T, U> FramedDispatcher<St, S, T, U>
where
S: Service<Request = Request<St, 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,
{
pub(crate) fn new<F: IntoService<S>>(
framed: Framed<T, U>,
state: State<St>,
service: F,
rx: mpsc::Receiver<FramedMessage<<U as Encoder>::Item>>,
sink: Sink<<U as Encoder>::Item>,
disconnect: Option<Rc<dyn Fn(&mut St, bool)>>,
) -> Self {
FramedDispatcher {
framed,
state,
sink,
disconnect,
rx: Some(rx),
service: service.into_service(),
dispatch_state: FramedState::Processing,
inner: Cell::new(FramedDispatcherInner {
buf: VecDeque::new(),
task: LocalWaker::new(),
}),
}
}
}
enum FramedState<S: Service, U: Encoder + Decoder> {
Processing,
Error(ServiceError<S::Error, U>),
FramedError(ServiceError<S::Error, U>),
FlushAndStop(Vec<oneshot::Sender<()>>),
Stopping,
}
impl<S: Service, U: Encoder + Decoder> FramedState<S, U> {
fn stop(&mut self, tx: Option<oneshot::Sender<()>>) {
match self {
FramedState::FlushAndStop(ref mut vec) => {
if let Some(tx) = tx {
vec.push(tx)
}
}
FramedState::Processing => {
*self = FramedState::FlushAndStop(if let Some(tx) = tx {
vec![tx]
} else {
Vec::new()
})
}
FramedState::Error(_) | FramedState::FramedError(_) | FramedState::Stopping => {
if let Some(tx) = tx {
let _ = tx.send(());
}
}
}
}
}
struct FramedDispatcherInner<I, E> {
buf: VecDeque<Result<I, E>>,
task: LocalWaker,
}
impl<St, S, T, U> FramedDispatcher<St, S, T, U>
where
S: Service<Request = Request<St, 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,
{
pub(crate) fn poll(
&mut self,
cx: &mut Context,
) -> Poll<Result<(), ServiceError<S::Error, U>>> {
let this = self;
unsafe { this.inner.get_ref().task.register(cx.waker()) };
poll(
cx,
&mut this.service,
&mut this.state,
&mut this.sink,
&mut this.framed,
&mut this.dispatch_state,
&mut this.rx,
&mut this.inner,
&mut this.disconnect,
)
}
}
fn poll<St, S, T, U>(
cx: &mut Context,
srv: &mut S,
state: &mut State<St>,
sink: &mut Sink<<U as Encoder>::Item>,
framed: &mut Framed<T, U>,
dispatch_state: &mut FramedState<S, U>,
rx: &mut Option<mpsc::Receiver<FramedMessage<<U as Encoder>::Item>>>,
inner: &mut Cell<FramedDispatcherInner<<U as Encoder>::Item, S::Error>>,
disconnect: &mut Option<Rc<dyn Fn(&mut St, bool)>>,
) -> Poll<Result<(), ServiceError<S::Error, U>>>
where
S: Service<Request = Request<St, 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,
{
match mem::replace(dispatch_state, FramedState::Processing) {
FramedState::Processing => {
if poll_read(cx, srv, state, sink, framed, dispatch_state, inner)
|| poll_write(cx, framed, dispatch_state, rx, inner)
{
poll(
cx,
srv,
state,
sink,
framed,
dispatch_state,
rx,
inner,
disconnect,
)
} else {
Poll::Pending
}
}
FramedState::Error(err) => {
if framed.is_write_buf_empty()
|| (poll_write(cx, framed, dispatch_state, rx, inner)
|| framed.is_write_buf_empty())
{
if let Some(ref disconnect) = disconnect {
(&*disconnect)(&mut *state.get_mut(), true);
}
Poll::Ready(Err(err))
} else {
*dispatch_state = FramedState::Error(err);
Poll::Pending
}
}
FramedState::FlushAndStop(mut vec) => {
if !framed.is_write_buf_empty() {
match Pin::new(framed).poll_flush(cx) {
Poll::Ready(Err(err)) => {
debug!("Error sending data: {:?}", err);
}
Poll::Pending => {
*dispatch_state = FramedState::FlushAndStop(vec);
return Poll::Pending;
}
Poll::Ready(_) => (),
}
};
for tx in vec.drain(..) {
let _ = tx.send(());
}
if let Some(ref disconnect) = disconnect {
(&*disconnect)(&mut *state.get_mut(), false);
}
Poll::Ready(Ok(()))
}
FramedState::FramedError(err) => {
if let Some(ref disconnect) = disconnect {
(&*disconnect)(&mut *state.get_mut(), true);
}
Poll::Ready(Err(err))
}
FramedState::Stopping => {
if let Some(ref disconnect) = disconnect {
(&*disconnect)(&mut *state.get_mut(), false);
}
Poll::Ready(Ok(()))
}
}
}
fn poll_read<St, S, T, U>(
cx: &mut Context,
srv: &mut S,
state: &mut State<St>,
sink: &mut Sink<<U as Encoder>::Item>,
framed: &mut Framed<T, U>,
dispatch_state: &mut FramedState<S, U>,
inner: &mut Cell<FramedDispatcherInner<<U as Encoder>::Item, S::Error>>,
) -> bool
where
S: Service<Request = Request<St, 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,
{
loop {
match srv.poll_ready(cx) {
Poll::Ready(Ok(_)) => {
let item = match framed.next_item(cx) {
Poll::Ready(Some(Ok(el))) => el,
Poll::Ready(Some(Err(err))) => {
*dispatch_state = FramedState::FramedError(ServiceError::Decoder(err));
return true;
}
Poll::Pending => return false,
Poll::Ready(None) => {
log::trace!("Client disconnected");
*dispatch_state = FramedState::Stopping;
return true;
}
};
let mut cell = inner.clone();
tokio_executor::current_thread::spawn(
srv.call(Item::new(state.clone(), sink.clone(), item))
.then(move |item| {
let item = match item {
Ok(Some(item)) => Ok(item),
Ok(None) => return ready(()),
Err(err) => Err(err),
};
unsafe {
let inner = cell.get_mut();
inner.buf.push_back(item);
inner.task.wake();
}
ready(())
}),
);
}
Poll::Pending => return false,
Poll::Ready(Err(err)) => {
*dispatch_state = FramedState::Error(ServiceError::Service(err));
return true;
}
}
}
}
/// write to framed object
fn poll_write<St, S, T, U>(
cx: &mut Context,
framed: &mut Framed<T, U>,
dispatch_state: &mut FramedState<S, U>,
rx: &mut Option<mpsc::Receiver<FramedMessage<<U as Encoder>::Item>>>,
inner: &mut Cell<FramedDispatcherInner<<U as Encoder>::Item, S::Error>>,
) -> bool
where
S: Service<Request = Request<St, 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,
{
let inner = unsafe { inner.get_mut() };
let mut rx_done = rx.is_none();
let mut buf_empty = inner.buf.is_empty();
loop {
while !framed.is_write_buf_full() {
if !buf_empty {
match inner.buf.pop_front().unwrap() {
Ok(msg) => {
if let Err(err) = framed.write(msg) {
*dispatch_state =
FramedState::FramedError(ServiceError::Encoder(err));
return true;
}
buf_empty = inner.buf.is_empty();
}
Err(err) => {
*dispatch_state = FramedState::Error(ServiceError::Service(err));
return true;
}
}
}
if !rx_done && rx.is_some() {
match Pin::new(rx.as_mut().unwrap()).poll_next(cx) {
Poll::Ready(Some(FramedMessage::Message(msg))) => {
if let Err(err) = framed.write(msg) {
*dispatch_state =
FramedState::FramedError(ServiceError::Encoder(err));
return true;
}
}
Poll::Ready(Some(FramedMessage::Close)) => {
dispatch_state.stop(None);
return true;
}
Poll::Ready(Some(FramedMessage::WaitClose(tx))) => {
dispatch_state.stop(Some(tx));
return true;
}
Poll::Ready(None) => {
rx_done = true;
let _ = rx.take();
}
Poll::Pending => rx_done = true,
}
}
if rx_done && buf_empty {
break;
}
}
if !framed.is_write_buf_empty() {
match framed.flush(cx) {
Poll::Pending => break,
Poll::Ready(Err(err)) => {
debug!("Error sending data: {:?}", err);
*dispatch_state = FramedState::FramedError(ServiceError::Encoder(err));
return true;
}
Poll::Ready(_) => (),
}
} else {
break;
}
}
false
}

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,90 +0,0 @@
use std::cell::{Ref, RefMut};
use std::fmt;
use std::ops::{Deref, DerefMut};
use actix_codec::{Decoder, Encoder};
use crate::sink::Sink;
use crate::state::State;
pub struct Item<St, Codec: Encoder + Decoder> {
state: State<St>,
sink: Sink<<Codec as Encoder>::Item>,
item: <Codec as Decoder>::Item,
}
impl<St, Codec> Item<St, Codec>
where
Codec: Encoder + Decoder,
{
pub(crate) fn new(
state: State<St>,
sink: Sink<<Codec as Encoder>::Item>,
item: <Codec as Decoder>::Item,
) -> Self {
Item { state, sink, item }
}
#[inline]
pub fn state(&self) -> Ref<St> {
self.state.get_ref()
}
#[inline]
pub fn state_mut(&mut self) -> RefMut<St> {
self.state.get_mut()
}
#[inline]
pub fn sink(&self) -> &Sink<<Codec as Encoder>::Item> {
&self.sink
}
#[inline]
pub fn into_inner(self) -> <Codec as Decoder>::Item {
self.item
}
#[inline]
pub fn into_parts(
self,
) -> (
State<St>,
Sink<<Codec as Encoder>::Item>,
<Codec as Decoder>::Item,
) {
(self.state, self.sink, self.item)
}
}
impl<St, Codec> Deref for Item<St, Codec>
where
Codec: Encoder + Decoder,
{
type Target = <Codec as Decoder>::Item;
#[inline]
fn deref(&self) -> &<Codec as Decoder>::Item {
&self.item
}
}
impl<St, Codec> DerefMut for Item<St, Codec>
where
Codec: Encoder + Decoder,
{
#[inline]
fn deref_mut(&mut self) -> &mut <Codec as Decoder>::Item {
&mut self.item
}
}
impl<St, Codec> fmt::Debug for Item<St, Codec>
where
Codec: Encoder + Decoder,
<Codec as Decoder>::Item: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("FramedItem").field(&self.item).finish()
}
}

View File

@@ -1,15 +0,0 @@
mod cell;
mod connect;
mod dispatcher;
mod error;
mod item;
mod service;
mod sink;
mod state;
pub use self::connect::{Connect, ConnectResult};
pub use self::error::ServiceError;
pub use self::item::Item;
pub use self::service::{Builder, NewServiceBuilder, ServiceBuilder};
pub use self::sink::Sink;
pub use self::state::State;

View File

@@ -1,415 +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};
use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory};
use either::Either;
use futures::future::{FutureExt, LocalBoxFuture};
use pin_project::project;
use crate::connect::{Connect, ConnectResult};
use crate::dispatcher::FramedDispatcher;
use crate::error::ServiceError;
use crate::item::Item;
use crate::state::State;
type RequestItem<S, U> = Item<S, U>;
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, Codec>(PhantomData<(St, Codec)>);
impl<St, Codec> Builder<St, Codec> {
pub fn new() -> Builder<St, Codec> {
Builder(PhantomData)
}
/// Construct framed handler service with specified connect service
pub fn service<Io, C, F>(self, connect: F) -> ServiceBuilder<St, C, Io, Codec>
where
F: IntoService<C>,
Io: AsyncRead + AsyncWrite,
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
Codec: Decoder + Encoder,
{
ServiceBuilder {
connect: connect.into_service(),
disconnect: None,
_t: PhantomData,
}
}
/// Construct framed handler new service with specified connect service
pub fn factory<Io, C, F>(self, connect: F) -> NewServiceBuilder<St, C, Io, Codec>
where
F: IntoServiceFactory<C>,
Io: AsyncRead + AsyncWrite,
C: ServiceFactory<
Config = (),
Request = Connect<Io>,
Response = ConnectResult<Io, St, Codec>,
>,
C::Error: 'static,
C::Future: 'static,
Codec: Decoder + Encoder,
{
NewServiceBuilder {
connect: connect.into_factory(),
disconnect: None,
_t: PhantomData,
}
}
}
pub struct ServiceBuilder<St, C, Io, Codec> {
connect: C,
disconnect: Option<Rc<dyn Fn(&mut St, bool)>>,
_t: PhantomData<(St, Io, Codec)>,
}
impl<St, C, Io, Codec> ServiceBuilder<St, C, Io, Codec>
where
St: 'static,
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
C::Error: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
/// Callback to execute on disconnect
///
/// Second parameter indicates error occured during disconnect.
pub fn disconnect<F, Out>(mut self, disconnect: F) -> Self
where
F: Fn(&mut St, bool) + 'static,
{
self.disconnect = Some(Rc::new(disconnect));
self
}
/// Provide stream items handler service and construct service factory.
pub fn finish<F, T>(self, service: F) -> FramedServiceImpl<St, C, T, Io, Codec>
where
F: IntoServiceFactory<T>,
T: ServiceFactory<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
> + 'static,
{
FramedServiceImpl {
connect: self.connect,
handler: Rc::new(service.into_factory()),
disconnect: self.disconnect.clone(),
_t: PhantomData,
}
}
}
pub struct NewServiceBuilder<St, C, Io, Codec> {
connect: C,
disconnect: Option<Rc<dyn Fn(&mut St, bool)>>,
_t: PhantomData<(St, Io, Codec)>,
}
impl<St, C, Io, Codec> NewServiceBuilder<St, C, Io, Codec>
where
St: 'static,
Io: AsyncRead + AsyncWrite,
C: ServiceFactory<
Config = (),
Request = Connect<Io>,
Response = ConnectResult<Io, St, Codec>,
>,
C::Error: 'static,
C::Future: 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
/// Callback to execute on disconnect
///
/// Second parameter indicates error occured during disconnect.
pub fn disconnect<F>(mut self, disconnect: F) -> Self
where
F: Fn(&mut St, bool) + 'static,
{
self.disconnect = Some(Rc::new(disconnect));
self
}
pub fn finish<F, T, Cfg>(self, service: F) -> FramedService<St, C, T, Io, Codec, Cfg>
where
F: IntoServiceFactory<T>,
T: ServiceFactory<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
> + 'static,
{
FramedService {
connect: self.connect,
handler: Rc::new(service.into_factory()),
disconnect: self.disconnect,
_t: PhantomData,
}
}
}
pub struct FramedService<St, C, T, Io, Codec, Cfg> {
connect: C,
handler: Rc<T>,
disconnect: Option<Rc<dyn Fn(&mut St, bool)>>,
_t: PhantomData<(St, Io, Codec, Cfg)>,
}
impl<St, C, T, Io, Codec, Cfg> ServiceFactory for FramedService<St, C, T, Io, Codec, Cfg>
where
St: 'static,
Io: AsyncRead + AsyncWrite,
C: ServiceFactory<
Config = (),
Request = Connect<Io>,
Response = ConnectResult<Io, St, Codec>,
>,
C::Error: 'static,
C::Future: 'static,
T: ServiceFactory<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
> + 'static,
<T::Service as Service>::Future: 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
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>;
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &Cfg) -> Self::Future {
let handler = self.handler.clone();
let disconnect = self.disconnect.clone();
// create connect service and then create service impl
self.connect
.new_service(&())
.map(move |result| {
result.map(move |connect| FramedServiceImpl {
connect,
handler,
disconnect,
_t: PhantomData,
})
})
.boxed_local()
}
}
pub struct FramedServiceImpl<St, C, T, Io, Codec> {
connect: C,
handler: Rc<T>,
disconnect: Option<Rc<dyn Fn(&mut St, bool)>>,
_t: PhantomData<(St, Io, Codec)>,
}
impl<St, C, T, Io, Codec> Service for FramedServiceImpl<St, C, T, Io, Codec>
where
Io: AsyncRead + AsyncWrite,
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
C::Error: 'static,
T: ServiceFactory<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Future: 'static,
Codec: Decoder + Encoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
type Request = Io;
type Response = ();
type Error = ServiceError<C::Error, Codec>;
type Future = FramedServiceImplResponse<St, Io, Codec, 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(),
self.disconnect.clone(),
),
}
}
}
#[pin_project::pin_project]
pub struct FramedServiceImplResponse<St, Io, Codec, C, T>
where
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
C::Error: 'static,
T: ServiceFactory<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
#[pin]
inner: FramedServiceImplResponseInner<St, Io, Codec, C, T>,
}
impl<St, Io, Codec, C, T> Future for FramedServiceImplResponse<St, Io, Codec, C, T>
where
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
C::Error: 'static,
T: ServiceFactory<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
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, C, T>
where
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
C::Error: 'static,
T: ServiceFactory<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
Connect(#[pin] C::Future, Rc<T>, Option<Rc<dyn Fn(&mut St, bool)>>),
Handler(
#[pin] T::Future,
Option<ConnectResult<Io, St, Codec>>,
Option<Rc<dyn Fn(&mut St, bool)>>,
),
Dispatcher(#[pin] FramedDispatcher<St, T::Service, Io, Codec>),
}
impl<St, Io, Codec, C, T> FramedServiceImplResponseInner<St, Io, Codec, C, T>
where
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
C::Error: 'static,
T: ServiceFactory<
Config = St,
Request = RequestItem<St, Codec>,
Response = ResponseItem<Codec>,
Error = C::Error,
InitError = C::Error,
>,
<T::Service as Service>::Future: 'static,
Io: AsyncRead + AsyncWrite,
Codec: Encoder + Decoder,
<Codec as Encoder>::Item: 'static,
<Codec as Encoder>::Error: std::fmt::Debug,
{
#[project]
fn poll(
self: Pin<&mut Self>,
cx: &mut Context,
) -> Either<
FramedServiceImplResponseInner<St, Io, Codec, C, T>,
Poll<Result<(), ServiceError<C::Error, Codec>>>,
> {
#[project]
match self.project() {
FramedServiceImplResponseInner::Connect(fut, handler, disconnect) => {
match fut.poll(cx) {
Poll::Ready(Ok(res)) => {
Either::Left(FramedServiceImplResponseInner::Handler(
handler.new_service(&res.state),
Some(res),
disconnect.take(),
))
}
Poll::Pending => Either::Right(Poll::Pending),
Poll::Ready(Err(e)) => Either::Right(Poll::Ready(Err(e.into()))),
}
}
FramedServiceImplResponseInner::Handler(fut, res, disconnect) => match fut.poll(cx)
{
Poll::Ready(Ok(handler)) => {
let res = res.take().unwrap();
Either::Left(FramedServiceImplResponseInner::Dispatcher(
FramedDispatcher::new(
res.framed,
State::new(res.state),
handler,
res.rx,
res.sink,
disconnect.take(),
),
))
}
Poll::Pending => Either::Right(Poll::Pending),
Poll::Ready(Err(e)) => Either::Right(Poll::Ready(Err(e.into()))),
},
FramedServiceImplResponseInner::Dispatcher(ref mut fut) => {
Either::Right(fut.poll(cx))
}
}
}
}

View File

@@ -1,44 +0,0 @@
use std::fmt;
use actix_utils::{mpsc, oneshot};
use futures::future::{Future, FutureExt};
use crate::dispatcher::FramedMessage;
pub struct Sink<T>(mpsc::Sender<FramedMessage<T>>);
impl<T> Clone for Sink<T> {
fn clone(&self) -> Self {
Sink(self.0.clone())
}
}
impl<T> Sink<T> {
pub(crate) fn new(tx: mpsc::Sender<FramedMessage<T>>) -> Self {
Sink(tx)
}
/// Close connection
pub fn close(&self) {
let _ = self.0.send(FramedMessage::Close);
}
/// Close connection
pub fn wait_close(&self) -> impl Future<Output = ()> {
let (tx, rx) = oneshot::channel();
let _ = self.0.send(FramedMessage::WaitClose(tx));
rx.map(|_| ())
}
/// Send item
pub fn send(&self, item: T) {
let _ = self.0.send(FramedMessage::Message(item));
}
}
impl<T> fmt::Debug for Sink<T> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Sink").finish()
}
}

View File

@@ -1,30 +0,0 @@
use std::cell::{Ref, RefCell, RefMut};
use std::rc::Rc;
/// Connection state
///
/// Connection state is an arbitrary data attached to the each incoming message.
#[derive(Debug)]
pub struct State<T>(Rc<RefCell<T>>);
impl<T> State<T> {
pub(crate) fn new(st: T) -> Self {
State(Rc::new(RefCell::new(st)))
}
#[inline]
pub fn get_ref(&self) -> Ref<T> {
self.0.borrow()
}
#[inline]
pub fn get_mut(&mut self) -> RefMut<T> {
self.0.borrow_mut()
}
}
impl<T> Clone for State<T> {
fn clone(&self) -> Self {
State(self.0.clone())
}
}

View File

@@ -1,57 +0,0 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use actix_codec::BytesCodec;
use actix_server_config::Io;
use actix_service::{apply_fn_factory, service_fn, Service};
use actix_testing::{self as test, TestServer};
use futures::future::ok;
use tokio_net::tcp::TcpStream;
use tokio_timer::delay_for;
use actix_ioframe::{Builder, Connect};
struct State;
#[test]
fn test_disconnect() -> std::io::Result<()> {
let disconnect = Arc::new(AtomicBool::new(false));
let disconnect1 = disconnect.clone();
let srv = TestServer::with(move || {
let disconnect1 = disconnect1.clone();
apply_fn_factory(
Builder::new()
.factory(service_fn(|conn: Connect<_>| {
ok(conn.codec(BytesCodec).state(State))
}))
.disconnect(move |_, _| {
disconnect1.store(true, Ordering::Relaxed);
})
.finish(service_fn(|_t| ok(None))),
|io: Io<TcpStream>, srv| srv.call(io.into_parts().0),
)
});
let mut client = Builder::new()
.service(|conn: Connect<_>| {
let conn = conn.codec(BytesCodec).state(State);
conn.sink().close();
ok(conn)
})
.finish(service_fn(|_t| ok(None)));
let conn = test::block_on(
actix_connect::default_connector()
.call(actix_connect::Connect::with(String::new(), srv.addr())),
)
.unwrap();
test::block_on(client.call(conn.into_parts().0)).unwrap();
let _ = test::block_on(delay_for(Duration::from_millis(100)));
assert!(disconnect.load(Ordering::Relaxed));
Ok(())
}

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

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

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

@@ -0,0 +1,9 @@
# CHANGES
## 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

23
actix-macros/Cargo.toml Normal file
View File

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

102
actix-macros/src/lib.rs Normal file
View File

@@ -0,0 +1,102 @@
//! Macros for use with Tokio
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
/// Marks async function to be executed by actix system.
///
/// ## Usage
///
/// ```rust
/// #[actix_rt::main]
/// async fn main() {
/// println!("Hello world");
/// }
/// ```
#[allow(clippy::needless_doctest_main)]
#[proc_macro_attribute]
#[cfg(not(test))] // Work around for rust-lang/rust#62127
pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
let attrs = &input.attrs;
let vis = &input.vis;
let sig = &mut input.sig;
let body = &input.block;
let name = &sig.ident;
if sig.asyncness.is_none() {
return syn::Error::new_spanned(sig.fn_token, "only async fn is supported")
.to_compile_error()
.into();
}
sig.asyncness = None;
(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.
///
/// ## Usage
///
/// ```no_run
/// #[actix_rt::test]
/// async fn my_test() {
/// assert!(true);
/// }
/// ```
#[proc_macro_attribute]
pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
let attrs = &input.attrs;
let vis = &input.vis;
let sig = &mut input.sig;
let body = &input.block;
let mut has_test_attr = false;
for attr in attrs {
if attr.path.is_ident("test") {
has_test_attr = true;
}
}
if sig.asyncness.is_none() {
return syn::Error::new_spanned(
input.sig.fn_token,
format!("only async fn is supported, {}", input.sig.ident),
)
.to_compile_error()
.into();
}
sig.asyncness = None;
let result = if has_test_attr {
quote! {
#(#attrs)*
#vis #sig {
actix_rt::System::new("test")
.block_on(async { #body })
}
}
} else {
quote! {
#[test]
#(#attrs)*
#vis #sig {
actix_rt::System::new("test")
.block_on(async { #body })
}
}
};
result.into()
}

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,5 +1,61 @@
# Changes
## [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
**This version has been yanked.**
### Added
* Expose `System::is_set` to check if current system has ben started [#99]
* Add `Arbiter::is_running` to check if event loop is running [#124]
* Add `Arbiter::local_join` associated function
to get be able to `await` for spawned futures [#94]
[#94]: https://github.com/actix/actix-net/pull/94
[#99]: https://github.com/actix/actix-net/pull/99
[#124]: https://github.com/actix/actix-net/pull/124
## [1.0.0] - 2019-12-11
* Update dependencies
## [1.0.0-alpha.3] - 2019-12-07
### Fixed
* Fix compilation on non-unix platforms
### Changed
* Migrate to tokio 0.2
## [1.0.0-alpha.2] - 2019-12-02
Added
* Export `main` and `test` attribute macros
* Export `time` module (re-export of tokio-timer)
* Export `net` module (re-export of tokio-net)
## [1.0.0-alpha.1] - 2019-11-22
### Changed
* Migrate to std::future and tokio 0.2
## [0.2.6] - 2019-11-14
### Fixed

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-rt"
version = "1.0.0-alpha.1"
version = "1.1.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix runtime"
keywords = ["network", "framework", "async", "futures"]
@@ -8,23 +8,18 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-rt/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
license = "MIT OR Apache-2.0"
edition = "2018"
workspace = ".."
[lib]
name = "actix_rt"
path = "src/lib.rs"
[dependencies]
actix-threadpool = "0.2"
futures = "0.3.1"
# TODO: Replace this with dependency on tokio-runtime once it is ready
tokio = { version = "0.2.0-alpha.6" }
tokio-timer = "=0.3.0-alpha.6"
tokio-executor = "=0.2.0-alpha.6"
tokio-net = "=0.2.0-alpha.6"
actix-macros = "0.1.0"
actix-threadpool = "0.3"
futures-channel = { version = "0.3.4", default-features = false }
futures-util = { version = "0.3.4", default-features = false, features = ["alloc"] }
copyless = "0.1.4"
smallvec = "1"
tokio = { version = "0.2.6", default-features = false, features = ["rt-core", "rt-util", "io-driver", "tcp", "uds", "udp", "time", "signal", "stream"] }

View File

@@ -6,20 +6,26 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::task::{Context, Poll};
use std::{fmt, thread};
use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures::channel::oneshot::{channel, Canceled, Sender};
use futures::{future, Future, FutureExt, Stream};
use tokio::runtime::current_thread::spawn;
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures_channel::oneshot::{channel, Canceled, Sender};
use futures_util::{
future::{self, Future, FutureExt},
stream::Stream,
};
use crate::builder::Builder;
use crate::runtime::Runtime;
use crate::system::System;
use copyless::BoxHelper;
use smallvec::SmallVec;
pub use tokio::task::JoinHandle;
thread_local!(
static ADDR: RefCell<Option<Arbiter>> = RefCell::new(None);
static RUNNING: Cell<bool> = Cell::new(false);
static Q: RefCell<Vec<Box<dyn Future<Output = ()>>>> = RefCell::new(Vec::new());
static Q: RefCell<Vec<Pin<Box<dyn Future<Output = ()>>>>> = RefCell::new(Vec::new());
static PENDING: RefCell<SmallVec<[JoinHandle<()>; 8]>> = RefCell::new(SmallVec::new());
static STORAGE: RefCell<HashMap<TypeId, Box<dyn Any>>> = RefCell::new(HashMap::new());
);
@@ -32,7 +38,7 @@ pub(crate) enum ArbiterCommand {
}
impl fmt::Debug for ArbiterCommand {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ArbiterCommand::Stop => write!(f, "ArbiterCommand::Stop"),
ArbiterCommand::Execute(_) => write!(f, "ArbiterCommand::Execute"),
@@ -43,8 +49,8 @@ impl fmt::Debug for ArbiterCommand {
#[derive(Debug)]
/// Arbiters provide an asynchronous execution environment for actors, functions
/// and futures. When an Arbiter is created, they spawn a new OS thread, and
/// host an event loop. Some Arbiter functions execute on the current thread.
/// and futures. When an Arbiter is created, it spawns a new OS thread, and
/// hosts an event loop. Some Arbiter functions execute on the current thread.
pub struct Arbiter {
sender: UnboundedSender<ArbiterCommand>,
thread_handle: Option<thread::JoinHandle<()>>,
@@ -84,6 +90,11 @@ impl Arbiter {
})
}
/// Check if current arbiter is running.
pub fn is_running() -> bool {
RUNNING.with(|cell| cell.get())
}
/// Stop arbiter from continuing it's event loop.
pub fn stop(&self) {
let _ = self.sender.unbounded_send(ArbiterCommand::Stop);
@@ -101,7 +112,7 @@ impl Arbiter {
let handle = thread::Builder::new()
.name(name.clone())
.spawn(move || {
let mut rt = Builder::new().build_rt().expect("Can not create Runtime");
let mut rt = Runtime::new().expect("Can not create Runtime");
let arb = Arbiter::with_sender(arb_tx);
let (stop, stop_rx) = channel();
@@ -143,14 +154,16 @@ impl Arbiter {
}
}
pub(crate) fn run_system() {
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(..) {
// We pin the boxed future, so it can never again be moved.
let fut = unsafe { Pin::new_unchecked(fut) };
tokio_executor::current_thread::spawn(fut);
if let Some(rt) = rt {
rt.spawn(fut);
} else {
tokio::task::spawn_local(fut);
}
}
});
}
@@ -169,11 +182,21 @@ impl Arbiter {
RUNNING.with(move |cell| {
if cell.get() {
// Spawn the future on running executor
spawn(future);
let len = PENDING.with(move |cell| {
let mut p = cell.borrow_mut();
p.push(tokio::task::spawn_local(future));
p.len()
});
if len > 7 {
// Before reaching the inline size
tokio::task::spawn_local(CleanupPending);
}
} 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(Box::alloc().init(future)));
Q.with(move |cell| {
cell.borrow_mut().push(Pin::from(Box::alloc().init(future)))
});
}
});
}
@@ -290,6 +313,39 @@ impl Arbiter {
Ok(())
}
}
/// Returns a future that will be completed once all currently spawned futures
/// have completed.
pub fn local_join() -> impl Future<Output = ()> {
PENDING.with(move |cell| {
let current = cell.replace(SmallVec::new());
future::join_all(current).map(|_| ())
})
}
}
/// 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> {
PENDING.with(move |cell| {
let mut pending = cell.borrow_mut();
let mut i = 0;
while i != pending.len() {
if let Poll::Ready(_) = Pin::new(&mut pending[i]).poll(cx) {
pending.remove(i);
} else {
i += 1;
}
}
});
Poll::Ready(())
}
}
struct ArbiterController {
@@ -325,7 +381,15 @@ impl Future for ArbiterController {
return Poll::Ready(());
}
ArbiterCommand::Execute(fut) => {
spawn(fut);
let len = PENDING.with(move |cell| {
let mut p = cell.borrow_mut();
p.push(tokio::task::spawn_local(fut));
p.len()
});
if len > 7 {
// Before reaching the inline size
tokio::task::spawn_local(CleanupPending);
}
}
ArbiterCommand::ExecuteFn(f) => {
f.call_box();

View File

@@ -1,19 +1,14 @@
use std::borrow::Cow;
use std::io;
use futures::channel::mpsc::unbounded;
use futures::channel::oneshot::{channel, Receiver};
use futures::future::{lazy, Future};
use futures::{future, FutureExt};
use tokio::runtime::current_thread::Handle;
use tokio_net::driver::Reactor;
use tokio_timer::{clock::Clock, timer::Timer};
use futures_channel::mpsc::unbounded;
use futures_channel::oneshot::{channel, Receiver};
use futures_util::future::{lazy, Future, FutureExt};
use tokio::task::LocalSet;
use crate::arbiter::{Arbiter, SystemArbiter};
use crate::runtime::Runtime;
use crate::system::System;
use tokio_executor::current_thread::CurrentThread;
/// Builder struct for a actix runtime.
///
@@ -24,9 +19,6 @@ pub struct Builder {
/// Name of the System. Defaults to "actix" if unset.
name: Cow<'static, str>,
/// The clock to use
clock: Clock,
/// Whether the Arbiter will stop the whole System on uncaught panic. Defaults to false.
stop_on_panic: bool,
}
@@ -35,7 +27,6 @@ impl Builder {
pub(crate) fn new() -> Self {
Builder {
name: Cow::Borrowed("actix"),
clock: Clock::new(),
stop_on_panic: false,
}
}
@@ -46,14 +37,6 @@ impl Builder {
self
}
/// Set the Clock instance that will be used by this System.
///
/// Defaults to the system clock.
pub fn clock(mut self, clock: Clock) -> Self {
self.clock = clock;
self
}
/// Sets the option 'stop_on_panic' which controls whether the System is stopped when an
/// uncaught panic is thrown from a worker thread.
///
@@ -73,8 +56,8 @@ impl Builder {
/// Create new System that can run asynchronously.
///
/// This method panics if it cannot start the system arbiter
pub(crate) fn build_async(self, executor: Handle) -> AsyncSystemRunner {
self.create_async_runtime(executor)
pub(crate) fn build_async(self, local: &LocalSet) -> AsyncSystemRunner {
self.create_async_runtime(local)
}
/// This function will start tokio runtime and will finish once the
@@ -87,7 +70,7 @@ impl Builder {
self.create_runtime(f).run()
}
fn create_async_runtime(self, executor: Handle) -> AsyncSystemRunner {
fn create_async_runtime(self, local: &LocalSet) -> AsyncSystemRunner {
let (stop_tx, stop) = channel();
let (sys_sender, sys_receiver) = unbounded();
@@ -97,7 +80,7 @@ impl Builder {
let arb = SystemArbiter::new(stop_tx, sys_receiver);
// start the system arbiter
executor.spawn(arb).expect("could not start system arbiter");
let _ = local.spawn_local(arb);
AsyncSystemRunner { stop, system }
}
@@ -114,40 +97,14 @@ impl Builder {
// system arbiter
let arb = SystemArbiter::new(stop_tx, sys_receiver);
let mut rt = self.build_rt().unwrap();
let mut rt = Runtime::new().unwrap();
rt.spawn(arb);
// init system arbiter and run configuration method
let _ = rt.block_on(lazy(move |_| {
f();
Ok::<_, ()>(())
}));
rt.block_on(lazy(move |_| f()));
SystemRunner { rt, stop, system }
}
pub(crate) fn build_rt(&self) -> io::Result<Runtime> {
// We need a reactor to receive events about IO objects from kernel
let reactor = Reactor::new()?;
let reactor_handle = reactor.handle();
// Place a timer wheel on top of the reactor. If there are no timeouts to fire, it'll let the
// reactor pick up some new external events.
let timer = Timer::new_with_now(reactor, self.clock.clone());
let timer_handle = timer.handle();
// And now put a single-threaded executor on top of the timer. When there are no futures ready
// to do something, it'll let the timer or the reactor to generate some new stimuli for the
// futures to continue in their life.
let executor = CurrentThread::new_with_park(timer);
Ok(Runtime::new2(
reactor_handle,
timer_handle,
self.clock.clone(),
executor,
))
}
}
#[derive(Debug)]
@@ -163,8 +120,8 @@ impl AsyncSystemRunner {
let AsyncSystemRunner { stop, .. } = self;
// run loop
future::lazy(|_| {
Arbiter::run_system();
lazy(|_| {
Arbiter::run_system(None);
async {
let res = match stop.await {
Ok(code) => {
@@ -203,10 +160,7 @@ impl SystemRunner {
let SystemRunner { mut rt, stop, .. } = self;
// run loop
let _ = rt.block_on(async {
Arbiter::run_system();
Ok::<_, ()>(())
});
Arbiter::run_system(Some(&rt));
let result = match rt.block_on(stop) {
Ok(code) => {
if code != 0 {
@@ -227,17 +181,11 @@ impl SystemRunner {
/// Execute a future and wait for result.
pub fn block_on<F, O>(&mut self, fut: F) -> O
where
F: Future<Output = O>,
F: Future<Output = O> + 'static,
{
self.rt.block_on(async {
Arbiter::run_system();
});
Arbiter::run_system(Some(&self.rt));
let res = self.rt.block_on(fut);
self.rt.block_on(async {
Arbiter::stop_system();
});
Arbiter::stop_system();
res
}
}

View File

@@ -1,4 +1,9 @@
//! A runtime implementation that runs everything on the current thread.
#![deny(rust_2018_idioms, warnings)]
#![allow(clippy::type_complexity)]
#[cfg(not(test))] // Work around for rust-lang/rust#62127
pub use actix_macros::{main, test};
mod arbiter;
mod builder;
@@ -20,7 +25,7 @@ pub use actix_threadpool as blocking;
/// This function panics if actix system is not running.
pub fn spawn<F>(f: F)
where
F: futures::Future<Output = ()> + 'static,
F: futures_util::future::Future<Output = ()> + 'static,
{
if !System::is_set() {
panic!("System is not running");
@@ -28,3 +33,34 @@ where
Arbiter::spawn(f);
}
/// Asynchronous signal handling
pub mod signal {
#[cfg(unix)]
pub mod unix {
pub use tokio::signal::unix::*;
}
pub use tokio::signal::ctrl_c;
}
/// TCP/UDP/Unix bindings
pub mod net {
pub use tokio::net::UdpSocket;
pub use tokio::net::{TcpListener, TcpStream};
#[cfg(unix)]
mod unix {
pub use tokio::net::{UnixDatagram, UnixListener, UnixStream};
}
#[cfg(unix)]
pub use self::unix::*;
}
/// Utilities for tracking time.
pub mod time {
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::{timeout, Timeout};
}

View File

@@ -1,92 +0,0 @@
//! A runtime implementation that runs everything on the current thread.
//!
//! [`current_thread::Runtime`][rt] is similar to the primary
//! [`Runtime`][concurrent-rt] except that it runs all components on the current
//! thread instead of using a thread pool. This means that it is able to spawn
//! futures that do not implement `Send`.
//!
//! Same as the default [`Runtime`][concurrent-rt], the
//! [`current_thread::Runtime`][rt] includes:
//!
//! * A [reactor] to drive I/O resources.
//! * An [executor] to execute tasks that use these I/O resources.
//! * A [timer] for scheduling work to run after a set period of time.
//!
//! Note that [`current_thread::Runtime`][rt] does not implement `Send` itself
//! and cannot be safely moved to other threads.
//!
//! # Spawning from other threads
//!
//! While [`current_thread::Runtime`][rt] does not implement `Send` and cannot
//! safely be moved to other threads, it provides a `Handle` that can be sent
//! to other threads and allows to spawn new tasks from there.
//!
//! For example:
//!
//! ```
//! # extern crate tokio;
//! # extern crate futures;
//! use tokio::runtime::current_thread::Runtime;
//! use tokio::prelude::*;
//! use std::thread;
//!
//! # fn main() {
//! let mut runtime = Runtime::new().unwrap();
//! let handle = runtime.handle();
//!
//! thread::spawn(move || {
//! handle.spawn(future::ok(()));
//! }).join().unwrap();
//!
//! # /*
//! runtime.run().unwrap();
//! # */
//! # }
//! ```
//!
//! # Examples
//!
//! Creating a new `Runtime` and running a future `f` until its completion and
//! returning its result.
//!
//! ```
//! use tokio::runtime::current_thread::Runtime;
//! use tokio::prelude::*;
//!
//! let mut runtime = Runtime::new().unwrap();
//!
//! // Use the runtime...
//! // runtime.block_on(f); // where f is a future
//! ```
//!
//! [rt]: struct.Runtime.html
//! [concurrent-rt]: ../struct.Runtime.html
//! [chan]: https://docs.rs/futures/0.1/futures/sync/mpsc/fn.channel.html
//! [reactor]: ../../reactor/struct.Reactor.html
//! [executor]: https://tokio.rs/docs/getting-started/runtime-model/#executors
//! [timer]: ../../timer/index.html
mod builder;
mod runtime;
pub use self::builder::Builder;
pub use self::runtime::{Runtime, Handle};
pub use tokio_current_thread::spawn;
pub use tokio_current_thread::TaskExecutor;
use futures::Future;
/// Run the provided future to completion using a runtime running on the current thread.
///
/// This first creates a new [`Runtime`], and calls [`Runtime::block_on`] with the provided future,
/// which blocks the current thread until the provided future completes. It then calls
/// [`Runtime::run`] to wait for any other spawned futures to resolve.
pub fn block_on_all<F>(future: F) -> Result<F::Item, F::Error>
where
F: Future,
{
let mut r = Runtime::new().expect("failed to start runtime on current thread");
let v = r.block_on(future)?;
r.run().expect("failed to resolve remaining futures");
Ok(v)
}

View File

@@ -1,73 +1,36 @@
use std::error::Error;
use std::{fmt, io};
use futures::Future;
use tokio_executor::current_thread::{self, CurrentThread};
use tokio_net::driver::{Handle as ReactorHandle, Reactor};
use tokio_timer::{
clock::Clock,
timer::{self, Timer},
};
use crate::builder::Builder;
use std::future::Future;
use std::io;
use tokio::{runtime, task::LocalSet};
/// Single-threaded runtime provides a way to start reactor
/// and executor on the current thread.
/// and runtime on the current thread.
///
/// See [module level][mod] documentation for more details.
///
/// [mod]: index.html
#[derive(Debug)]
pub struct Runtime {
reactor_handle: ReactorHandle,
timer_handle: timer::Handle,
clock: Clock,
executor: CurrentThread<Timer<Reactor>>,
}
/// Error returned by the `run` function.
#[derive(Debug)]
pub struct RunError {
inner: current_thread::RunError,
}
impl fmt::Display for RunError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.inner)
}
}
impl Error for RunError {
fn description(&self) -> &str {
self.inner.description()
}
fn cause(&self) -> Option<&dyn Error> {
self.inner.source()
}
local: LocalSet,
rt: runtime::Runtime,
}
impl Runtime {
#[allow(clippy::new_ret_no_self)]
/// Returns a new runtime initialized with default configuration values.
pub fn new() -> io::Result<Runtime> {
Builder::new().build_rt()
let rt = runtime::Builder::new()
.enable_io()
.enable_time()
.basic_scheduler()
.build()?;
Ok(Runtime {
rt,
local: LocalSet::new(),
})
}
pub(super) fn new2(
reactor_handle: ReactorHandle,
timer_handle: timer::Handle,
clock: Clock,
executor: CurrentThread<Timer<Reactor>>,
) -> Runtime {
Runtime {
reactor_handle,
timer_handle,
clock,
executor,
}
}
/// Spawn a future onto the single-threaded Tokio runtime.
/// Spawn a future onto the single-threaded runtime.
///
/// See [module level][mod] documentation for more details.
///
@@ -75,7 +38,7 @@ impl Runtime {
///
/// # Examples
///
/// ```rust
/// ```rust,ignore
/// # use futures::{future, Future, Stream};
/// use actix_rt::Runtime;
///
@@ -95,11 +58,11 @@ impl Runtime {
///
/// This function panics if the spawn fails. Failure occurs if the executor
/// is currently at capacity and is unable to spawn a new future.
pub fn spawn<F>(&mut self, future: F) -> &mut Self
pub fn spawn<F>(&self, future: F) -> &Self
where
F: Future<Output = ()> + 'static,
{
self.executor.spawn(future);
self.local.spawn_local(future);
self
}
@@ -121,41 +84,8 @@ impl Runtime {
/// complete execution by calling `block_on` or `run`.
pub fn block_on<F>(&mut self, f: F) -> F::Output
where
F: Future,
F: Future + 'static,
{
self.enter(|executor| {
// Run the provided future
executor.block_on(f)
})
}
/// Run the executor to completion, blocking the thread until **all**
/// spawned futures have completed.
pub fn run(&mut self) -> Result<(), RunError> {
self.enter(|executor| executor.run())
.map_err(|e| RunError { inner: e })
}
fn enter<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut CurrentThread<Timer<Reactor>>) -> R,
{
let Runtime {
ref reactor_handle,
ref timer_handle,
ref clock,
ref mut executor,
..
} = *self;
// WARN: We do not enter the executor here, since in tokio 0.2 the executor is entered
// automatically inside its `block_on` and `run` methods
tokio_executor::with_default(&mut current_thread::TaskExecutor::current(), || {
tokio_timer::clock::with_default(clock, || {
let _reactor_guard = tokio_net::driver::set_default(reactor_handle);
let _timer_guard = tokio_timer::set_default(timer_handle);
f(executor)
})
})
self.local.block_on(&mut self.rt, f)
}
}

View File

@@ -1,10 +1,10 @@
use std::cell::RefCell;
use std::future::Future;
use std::io;
use std::sync::atomic::{AtomicUsize, Ordering};
use futures::channel::mpsc::UnboundedSender;
use futures::Future;
use tokio::runtime::current_thread::Handle;
use futures_channel::mpsc::UnboundedSender;
use tokio::task::LocalSet;
use crate::arbiter::{Arbiter, SystemCommand};
use crate::builder::{Builder, SystemRunner};
@@ -58,16 +58,16 @@ impl System {
}
#[allow(clippy::new_ret_no_self)]
/// Create new system using provided CurrentThread Handle.
/// Create new system using provided tokio Handle.
///
/// This method panics if it can not spawn system arbiter
pub fn run_in_executor<T: Into<String>>(
pub fn run_in_tokio<T: Into<String>>(
name: T,
executor: Handle,
) -> impl Future<Output = Result<(), io::Error>> + Send {
local: &LocalSet,
) -> impl Future<Output = io::Result<()>> {
Self::builder()
.name(name)
.build_async(executor)
.build_async(local)
.run_nonblocking()
}
@@ -79,8 +79,8 @@ impl System {
})
}
/// Set current running system.
pub(crate) fn is_set() -> bool {
/// Check if current system is set, i.e., as already been started.
pub fn is_set() -> bool {
CURRENT.with(|cell| cell.borrow().is_some())
}

View File

@@ -0,0 +1,114 @@
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]
fn await_for_timer() {
let time = Duration::from_secs(2);
let instant = Instant::now();
actix_rt::System::new("test_wait_timer").block_on(async move {
tokio::time::delay_for(time).await;
});
assert!(
instant.elapsed() >= time,
"Block on should poll awaited future to completion"
);
}
#[test]
fn join_another_arbiter() {
let time = Duration::from_secs(2);
let instant = Instant::now();
actix_rt::System::new("test_join_another_arbiter").block_on(async move {
let mut arbiter = actix_rt::Arbiter::new();
arbiter.send(Box::pin(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
}));
arbiter.join().unwrap();
});
assert!(
instant.elapsed() >= time,
"Join on another arbiter should complete only when it calls stop"
);
let instant = Instant::now();
actix_rt::System::new("test_join_another_arbiter").block_on(async move {
let mut arbiter = actix_rt::Arbiter::new();
arbiter.exec_fn(move || {
actix_rt::spawn(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
});
});
arbiter.join().unwrap();
});
assert!(
instant.elapsed() >= time,
"Join on a arbiter that has used actix_rt::spawn should wait for said future"
);
let instant = Instant::now();
actix_rt::System::new("test_join_another_arbiter").block_on(async move {
let mut arbiter = actix_rt::Arbiter::new();
arbiter.send(Box::pin(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
}));
arbiter.stop();
arbiter.join().unwrap();
});
assert!(
instant.elapsed() < time,
"Premature stop of arbiter should conclude regardless of it's current state"
);
}
#[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"
);
}

View File

@@ -1,29 +0,0 @@
[package]
name = "actix-server-config"
version = "0.3.0-alpha.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix server config utils"
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
license = "MIT/Apache-2.0"
edition = "2018"
workspace = ".."
[lib]
name = "actix_server_config"
path = "src/lib.rs"
[package.metadata.docs.rs]
features = ["openssl", "rustls"]
[features]
default = []
openssl = ["tokio-openssl"]
rustls = ["tokio-rustls"]
[dependencies]
tokio-io = "=0.2.0-alpha.6"
tokio-net = { version = "=0.2.0-alpha.6", features = ["tcp", "uds"] }
tokio-openssl = { version = "0.4.0-alpha.6", optional = true }
#tokio-rustls = { version = "0.12.0-alpha.8", optional = true }
tokio-rustls = { git = "https://github.com/quininer/tokio-rustls.git", branch = "tokio-0.2", optional = true }

View File

@@ -1,21 +0,0 @@
# Changes
## [0.2.0] - 2019-10-03
### Changed
* Update `rustls` to 0.16
* Minimum required Rust version upped to 1.37.0
## [0.1.2] - 2019-07-18
### Added
* Add unix domnain sockets support
## [0.1.1] - 2019-04-16
### Added
* `IoStream` trait and impls for TcpStream, SslStream and TlsStream

View File

@@ -1,249 +0,0 @@
//! Actix server config utils.
use std::cell::Cell;
use std::net::SocketAddr;
use std::rc::Rc;
use std::{fmt, io, net, ops, time};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_net::tcp::TcpStream;
#[derive(Debug, Clone)]
pub struct ServerConfig {
addr: SocketAddr,
secure: Rc<Cell<bool>>,
}
impl ServerConfig {
#[inline]
pub fn new(addr: SocketAddr) -> Self {
ServerConfig {
addr,
secure: Rc::new(Cell::new(false)),
}
}
/// Returns the address of the local half of this TCP server socket
#[inline]
pub fn local_addr(&self) -> SocketAddr {
self.addr
}
/// Returns true if connection is secure (tls enabled)
#[inline]
pub fn secure(&self) -> bool {
self.secure.as_ref().get()
}
/// Set secure flag
#[inline]
pub fn set_secure(&self) {
self.secure.as_ref().set(true)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Protocol {
Unknown,
Http10,
Http11,
Http2,
Proto1,
Proto2,
Proto3,
Proto4,
Proto5,
Proto6,
}
pub struct Io<T, P = ()> {
io: T,
proto: Protocol,
params: P,
}
impl<T: Unpin> Unpin for Io<T> {}
impl<T> Io<T, ()> {
pub fn new(io: T) -> Self {
Self {
io,
proto: Protocol::Unknown,
params: (),
}
}
}
impl<T, P> Io<T, P> {
/// Reconstruct from a parts.
pub fn from_parts(io: T, params: P, proto: Protocol) -> Self {
Self { io, params, proto }
}
/// Deconstruct into a parts.
pub fn into_parts(self) -> (T, P, Protocol) {
(self.io, self.params, self.proto)
}
/// Returns a shared reference to the underlying stream.
pub fn get_ref(&self) -> &T {
&self.io
}
/// Returns a mutable reference to the underlying stream.
pub fn get_mut(&mut self) -> &mut T {
&mut self.io
}
/// Get selected protocol
pub fn protocol(&self) -> Protocol {
self.proto
}
/// Return new Io object with new parameter.
pub fn set<U>(self, params: U) -> Io<T, U> {
Io {
params,
io: self.io,
proto: self.proto,
}
}
/// Maps an Io<_, P> to Io<_, U> by applying a function to a contained value.
pub fn map<U, F>(self, op: F) -> Io<T, U>
where
F: FnOnce(P) -> U,
{
Io {
io: self.io,
proto: self.proto,
params: op(self.params),
}
}
}
impl<T, P> ops::Deref for Io<T, P> {
type Target = T;
fn deref(&self) -> &T {
&self.io
}
}
impl<T, P> ops::DerefMut for Io<T, P> {
fn deref_mut(&mut self) -> &mut T {
&mut self.io
}
}
impl<T: fmt::Debug, P> fmt::Debug for Io<T, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Io {{{:?}}}", self.io)
}
}
/// Low-level io stream operations
pub trait IoStream: AsyncRead + AsyncWrite + Unpin {
/// Returns the socket address of the remote peer of this TCP connection.
fn peer_addr(&self) -> Option<SocketAddr> {
None
}
/// Sets the value of the TCP_NODELAY option on this socket.
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>;
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
}
impl IoStream for TcpStream {
#[inline]
fn peer_addr(&self) -> Option<net::SocketAddr> {
TcpStream::peer_addr(self).ok()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
TcpStream::set_nodelay(self, nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
TcpStream::set_linger(self, dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
TcpStream::set_keepalive(self, dur)
}
}
#[cfg(feature = "openssl")]
impl<T: IoStream + Unpin> IoStream for tokio_openssl::SslStream<T> {
#[inline]
fn peer_addr(&self) -> Option<net::SocketAddr> {
self.get_ref().peer_addr()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().set_keepalive(dur)
}
}
#[cfg(feature = "rustls")]
impl<T: IoStream + Unpin> IoStream for tokio_rustls::server::TlsStream<T> {
#[inline]
fn peer_addr(&self) -> Option<net::SocketAddr> {
self.get_ref().0.peer_addr()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().0.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_keepalive(dur)
}
}
#[cfg(unix)]
impl IoStream for tokio_net::uds::UnixStream {
#[inline]
fn peer_addr(&self) -> Option<net::SocketAddr> {
None
}
#[inline]
fn set_nodelay(&mut self, _: bool) -> io::Result<()> {
Ok(())
}
#[inline]
fn set_linger(&mut self, _: Option<time::Duration>) -> io::Result<()> {
Ok(())
}
#[inline]
fn set_keepalive(&mut self, _: Option<time::Duration>) -> io::Result<()> {
Ok(())
}
}

View File

@@ -1,5 +1,69 @@
# Changes
## [1.0.3] - 2020-05-19
### Changed
* Replace deprecated `net2` crate with `socket2` [#140]
[#140]: https://github.com/actix/actix-net/pull/140
## [1.0.2] - 2020-02-26
### Fixed
* Avoid error by calling `reregister()` on Windows [#103]
[#103]: https://github.com/actix/actix-net/pull/103
## [1.0.1] - 2019-12-29
### Changed
* Rename `.start()` method to `.run()`
## [1.0.0] - 2019-12-11
### Changed
* Use actix-net releases
## [1.0.0-alpha.4] - 2019-12-08
### Changed
* Use actix-service 1.0.0-alpha.4
## [1.0.0-alpha.3] - 2019-12-07
### Changed
* Migrate to tokio 0.2
### Fixed
* Fix compilation on non-unix platforms
* Better handling server configuration
## [1.0.0-alpha.2] - 2019-12-02
### Changed
* Simplify server service (remove actix-server-config)
* Allow to wait on `Server` until server stops
## [0.8.0-alpha.1] - 2019-11-22
### Changed
* Migrate to `std::future`
## [0.7.0] - 2019-10-04
### Changed

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-server"
version = "0.8.0-alpha.1"
version = "1.0.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix server - General purpose tcp server"
keywords = ["network", "framework", "async", "futures"]
@@ -8,60 +8,37 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-server/"
categories = ["network-programming", "asynchronous"]
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
license = "MIT OR Apache-2.0"
exclude = [".gitignore", ".cargo/config"]
edition = "2018"
workspace = ".."
[package.metadata.docs.rs]
features = ["nativetls", "openssl", "rustls", "uds"]
[lib]
name = "actix_server"
path = "src/lib.rs"
[features]
default = []
nativetls = ["native-tls", "tokio-tls"]
openssl = ["open-ssl", "tokio-openssl", "actix-server-config/openssl"]
#rustls = ["rust-tls", "tokio-rustls", "webpki", "webpki-roots", "actix-server-config/rustls"]
[dependencies]
actix-rt = "1.0.0-alpha.1"
actix-service = "1.0.0-alpha.1"
actix-server-config = "0.3.0-alpha.1"
actix-service = "1.0.1"
actix-rt = "1.0.0"
actix-codec = "0.2.0"
actix-utils = "1.0.4"
log = "0.4"
num_cpus = "1.0"
num_cpus = "1.11"
mio = "0.6.19"
net2 = "0.2"
futures = "0.3.1"
socket2 = "0.3"
futures-channel = { version = "0.3.4", default-features = false }
futures-util = { version = "0.3.4", default-features = false, features = ["sink"] }
slab = "0.4"
tokio = "0.2.0-alpha.6"
tokio-io = "0.2.0-alpha.6"
tokio-net = { version = "0.2.0-alpha.6", features = ["signal", "tcp", "uds"] }
tokio-timer = "0.3.0-alpha.6"
# unix domain sockets
# FIXME: Remove it and use mio own uds feature once mio 0.7 is released
mio-uds = { version = "0.6.7" }
# nativetls
native-tls = { version = "0.2", optional = true }
tokio-tls = { version = "0.3.0-alpha.6", optional = true }
# openssl
open-ssl = { version = "0.10", package = "openssl", optional = true }
tokio-openssl = { version = "0.4.0-alpha.6", optional = true }
# rustls
rust-tls = { version = "0.16.0", package = "rustls", optional = true }
# tokio-rustls = { version = "0.12.0-alpha.2", optional = true }
tokio-rustls = { git = "https://github.com/quininer/tokio-rustls.git", branch = "tokio-0.2", optional = true }
webpki = { version = "0.21", optional = true }
webpki-roots = { version = "0.17", optional = true }
[dev-dependencies]
bytes = "0.4"
actix-codec = "0.2.0-alpha.1"
env_logger = "0.6"
bytes = "0.5"
env_logger = "0.7"
actix-testing = "1.0.0"

View File

@@ -1,12 +1,11 @@
use std::sync::mpsc as sync_mpsc;
use std::time::{Duration, Instant};
use std::time::Duration;
use std::{io, thread};
use actix_rt::time::{delay_until, Instant};
use actix_rt::System;
use futures::FutureExt;
use log::{error, info};
use slab::Slab;
use tokio_timer::delay;
use crate::server::Server;
use crate::socket::{SocketAddr, SocketListener, StdListener};
@@ -299,12 +298,7 @@ impl Accept {
}
Command::Resume => {
for (token, info) in self.sockets.iter() {
if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
if let Err(err) = self.register(token, info) {
error!("Can not resume socket accept process: {}", err);
} else {
info!(
@@ -339,17 +333,44 @@ impl Accept {
true
}
#[cfg(not(target_os = "windows"))]
fn register(&self, token: usize, info: &ServerSocketInfo) -> io::Result<()> {
self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
)
}
#[cfg(target_os = "windows")]
fn register(&self, token: usize, info: &ServerSocketInfo) -> io::Result<()> {
// On windows, calling register without deregister cause an error.
// See https://github.com/actix/actix-web/issues/905
// Calling reregister seems to fix the issue.
self.poll
.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
)
.or_else(|_| {
self.poll.reregister(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
)
})
}
fn backpressure(&mut self, on: bool) {
if self.backpressure {
if !on {
self.backpressure = false;
for (token, info) in self.sockets.iter() {
if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
if let Err(err) = self.register(token, info) {
error!("Can not resume socket accept process: {}", err);
} else {
info!("Accepting connections on {} has been resumed", info.addr);
@@ -440,13 +461,10 @@ impl Accept {
info.timeout = Some(Instant::now() + Duration::from_millis(500));
let r = self.timer.1.clone();
System::current().arbiter().send(
async move {
delay(Instant::now() + Duration::from_millis(510)).await;
let _ = r.set_readiness(mio::Ready::readable());
}
.boxed(),
);
System::current().arbiter().send(Box::pin(async move {
delay_until(Instant::now() + Duration::from_millis(510)).await;
let _ = r.set_readiness(mio::Ready::readable());
}));
return;
}
}

View File

@@ -1,27 +1,27 @@
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
use std::time::Duration;
use std::{io, mem, net};
use actix_rt::{spawn, Arbiter, System};
use futures::channel::mpsc::{unbounded, UnboundedReceiver};
use futures::future::ready;
use futures::stream::FuturesUnordered;
use futures::{ready, Future, FutureExt, Stream, StreamExt};
use actix_rt::net::TcpStream;
use actix_rt::time::{delay_until, Instant};
use actix_rt::{spawn, System};
use futures_channel::mpsc::{unbounded, UnboundedReceiver};
use futures_channel::oneshot;
use futures_util::future::ready;
use futures_util::stream::FuturesUnordered;
use futures_util::{future::Future, ready, stream::Stream, FutureExt, StreamExt};
use log::{error, info};
use net2::TcpBuilder;
use num_cpus;
use tokio_net::tcp::TcpStream;
use tokio_timer::delay;
use socket2::{Domain, Protocol, Socket, Type};
use crate::accept::{AcceptLoop, AcceptNotify, Command};
use crate::config::{ConfiguredService, ServiceConfig};
use crate::server::{Server, ServerCommand};
use crate::service::{InternalServiceFactory, ServiceFactory, StreamNewService};
// use crate::signals::{Signal, Signals};
use crate::signals::{Signal, Signals};
use crate::socket::StdListener;
use crate::worker::{self, Worker, WorkerAvailability, WorkerClient};
use crate::{ssl, Token};
use crate::Token;
/// Server builder
pub struct ServerBuilder {
@@ -30,13 +30,14 @@ pub struct ServerBuilder {
backlog: i32,
workers: Vec<(usize, WorkerClient)>,
services: Vec<Box<dyn InternalServiceFactory>>,
sockets: Vec<(Token, StdListener)>,
sockets: Vec<(Token, String, StdListener)>,
accept: AcceptLoop,
exit: bool,
shutdown_timeout: Duration,
no_signals: bool,
cmd: UnboundedReceiver<ServerCommand>,
server: Server,
notify: Vec<oneshot::Sender<()>>,
}
impl Default for ServerBuilder {
@@ -63,6 +64,7 @@ impl ServerBuilder {
shutdown_timeout: Duration::from_secs(30),
no_signals: false,
cmd: rx,
notify: Vec::new(),
server,
}
}
@@ -102,17 +104,6 @@ impl ServerBuilder {
self
}
/// Sets the maximum per-worker concurrent connection establish process.
///
/// All listeners will stop accepting connections when this limit is reached. It
/// can be used to limit the global SSL CPU usage.
///
/// By default max connections is set to a 256.
pub fn maxconnrate(self, num: usize) -> Self {
ssl::max_concurrent_ssl_connect(num);
self
}
/// Stop actix system.
pub fn system_exit(mut self) -> Self {
self.exit = true;
@@ -154,8 +145,8 @@ impl ServerBuilder {
let mut srv = ConfiguredService::new(apply);
for (name, lst) in cfg.services {
let token = self.token.next();
srv.stream(token, name, lst.local_addr()?);
self.sockets.push((token, StdListener::Tcp(lst)));
srv.stream(token, name.clone(), lst.local_addr()?);
self.sockets.push((token, name, StdListener::Tcp(lst)));
}
self.services.push(Box::new(srv));
}
@@ -180,7 +171,8 @@ impl ServerBuilder {
factory.clone(),
lst.local_addr()?,
));
self.sockets.push((token, StdListener::Tcp(lst)));
self.sockets
.push((token, name.as_ref().to_string(), StdListener::Tcp(lst)));
}
Ok(self)
}
@@ -189,7 +181,7 @@ impl ServerBuilder {
/// Add new unix domain service to the server.
pub fn bind_uds<F, U, N>(self, name: N, addr: U, factory: F) -> io::Result<Self>
where
F: ServiceFactory<tokio_net::uds::UnixStream>,
F: ServiceFactory<actix_rt::net::UnixStream>,
N: AsRef<str>,
U: AsRef<std::path::Path>,
{
@@ -219,7 +211,7 @@ impl ServerBuilder {
factory: F,
) -> io::Result<Self>
where
F: ServiceFactory<tokio_net::uds::UnixStream>,
F: ServiceFactory<actix_rt::net::UnixStream>,
{
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
let token = self.token.next();
@@ -227,10 +219,11 @@ impl ServerBuilder {
self.services.push(StreamNewService::create(
name.as_ref().to_string(),
token,
factory.clone(),
factory,
addr,
));
self.sockets.push((token, StdListener::Uds(lst)));
self.sockets
.push((token, name.as_ref().to_string(), StdListener::Uds(lst)));
Ok(self)
}
@@ -251,59 +244,48 @@ impl ServerBuilder {
factory,
lst.local_addr()?,
));
self.sockets.push((token, StdListener::Tcp(lst)));
self.sockets
.push((token, name.as_ref().to_string(), StdListener::Tcp(lst)));
Ok(self)
}
/// Spawn new thread and start listening for incoming connections.
///
/// This method spawns new thread and starts new actix system. Other than
/// that it is similar to `start()` method. This method blocks.
///
/// This methods panics if no socket addresses get bound.
///
/// ```rust,ignore
/// use actix_web::*;
///
/// fn main() -> std::io::Result<()> {
/// Server::new().
/// .service(
/// HttpServer::new(|| App::new().service(web::service("/").to(|| HttpResponse::Ok())))
/// .bind("127.0.0.1:0")
/// .run()
/// }
/// ```
pub fn run(self) -> io::Result<()> {
let sys = System::new("http-server");
self.start();
sys.run()
#[doc(hidden)]
pub fn start(self) -> Server {
self.run()
}
/// Starts processing incoming connections and return server controller.
pub fn start(mut self) -> Server {
pub fn run(mut self) -> Server {
if self.sockets.is_empty() {
panic!("Server should have at least one bound socket");
} else {
info!("Starting {} workers", self.threads);
// start workers
let mut workers = Vec::new();
for idx in 0..self.threads {
let worker = self.start_worker(idx, self.accept.get_notify());
workers.push(worker.clone());
self.workers.push((idx, worker));
}
let workers = (0..self.threads)
.map(|idx| {
let worker = self.start_worker(idx, self.accept.get_notify());
self.workers.push((idx, worker.clone()));
worker
})
.collect();
// start accept thread
for sock in &self.sockets {
info!("Starting server on {}", sock.1);
info!("Starting \"{}\" service on {}", sock.1, sock.2);
}
self.accept
.start(mem::replace(&mut self.sockets, Vec::new()), workers);
self.accept.start(
mem::take(&mut self.sockets)
.into_iter()
.map(|t| (t.0, t.2))
.collect(),
workers,
);
// handle signals
if !self.no_signals {
// Signals::start(self.server.clone());
Signals::start(self.server.clone()).unwrap();
}
// start http server actor
@@ -314,22 +296,11 @@ impl ServerBuilder {
}
fn start_worker(&self, idx: usize, notify: AcceptNotify) -> WorkerClient {
let (tx1, rx1) = unbounded();
let (tx2, rx2) = unbounded();
let timeout = self.shutdown_timeout;
let avail = WorkerAvailability::new(notify);
let worker = WorkerClient::new(idx, tx1, tx2, avail.clone());
let services: Vec<Box<dyn InternalServiceFactory>> =
self.services.iter().map(|v| v.clone_factory()).collect();
Arbiter::new().send(
async move {
Worker::start(rx1, rx2, services, avail, timeout);
}
.boxed(),
);
worker
Worker::start(idx, services, avail, self.shutdown_timeout)
}
fn handle_cmd(&mut self, item: ServerCommand) {
@@ -342,37 +313,40 @@ impl ServerBuilder {
self.accept.send(Command::Resume);
let _ = tx.send(());
}
// ServerCommand::Signal(sig) => {
// Signals support
// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and stop actix system
// match sig {
// Signal::Int => {
// info!("SIGINT received, exiting");
// self.exit = true;
// self.handle_cmd(ServerCommand::Stop {
// graceful: false,
// completion: None,
// })
// }
// Signal::Term => {
// info!("SIGTERM received, stopping");
// self.exit = true;
// self.handle_cmd(ServerCommand::Stop {
// graceful: true,
// completion: None,
// })
// }
// Signal::Quit => {
// info!("SIGQUIT received, exiting");
// self.exit = true;
// self.handle_cmd(ServerCommand::Stop {
// graceful: false,
// completion: None,
// })
// }
// _ => (),
// }
// }
ServerCommand::Signal(sig) => {
// Signals support
// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and stop actix system
match sig {
Signal::Int => {
info!("SIGINT received, exiting");
self.exit = true;
self.handle_cmd(ServerCommand::Stop {
graceful: false,
completion: None,
})
}
Signal::Term => {
info!("SIGTERM received, stopping");
self.exit = true;
self.handle_cmd(ServerCommand::Stop {
graceful: true,
completion: None,
})
}
Signal::Quit => {
info!("SIGQUIT received, exiting");
self.exit = true;
self.handle_cmd(ServerCommand::Stop {
graceful: false,
completion: None,
})
}
_ => (),
}
}
ServerCommand::Notify(tx) => {
self.notify.push(tx);
}
ServerCommand::Stop {
graceful,
completion,
@@ -381,6 +355,7 @@ impl ServerBuilder {
// stop accept thread
self.accept.send(Command::Stop);
let notify = std::mem::take(&mut self.notify);
// stop workers
if !self.workers.is_empty() && graceful {
@@ -394,14 +369,19 @@ impl ServerBuilder {
if let Some(tx) = completion {
let _ = tx.send(());
}
for tx in notify {
let _ = tx.send(());
}
if exit {
spawn(
async {
delay(Instant::now() + Duration::from_millis(300))
.await;
delay_until(
Instant::now() + Duration::from_millis(300),
)
.await;
System::current().stop();
}
.boxed(),
.boxed(),
);
}
ready(())
@@ -411,15 +391,20 @@ impl ServerBuilder {
// we need to stop system if server was spawned
if self.exit {
spawn(
delay(Instant::now() + Duration::from_millis(300)).then(|_| {
System::current().stop();
ready(())
}),
delay_until(Instant::now() + Duration::from_millis(300)).then(
|_| {
System::current().stop();
ready(())
},
),
);
}
if let Some(tx) = completion {
let _ = tx.send(());
}
for tx in notify {
let _ = tx.send(());
}
}
}
ServerCommand::WorkerFaulted(idx) => {
@@ -502,11 +487,13 @@ pub(super) fn bind_addr<S: net::ToSocketAddrs>(
}
fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result<net::TcpListener> {
let builder = match addr {
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,
net::SocketAddr::V6(_) => TcpBuilder::new_v6()?,
let domain = match addr {
net::SocketAddr::V4(_) => Domain::ipv4(),
net::SocketAddr::V6(_) => Domain::ipv6(),
};
builder.reuse_address(true)?;
builder.bind(addr)?;
Ok(builder.listen(backlog)?)
let socket = Socket::new(domain, Type::stream(), Some(Protocol::tcp()))?;
socket.set_reuse_address(true)?;
socket.bind(&addr.into())?;
socket.listen(backlog)?;
Ok(socket.into_tcp_listener())
}

View File

@@ -1,18 +1,17 @@
use std::collections::HashMap;
use std::{fmt, io, net};
use actix_server_config::{Io, ServerConfig};
use actix_rt::net::TcpStream;
use actix_service as actix;
use futures::future::{Future, FutureExt, LocalBoxFuture};
use actix_utils::counter::CounterGuard;
use futures_util::future::{ok, Future, FutureExt, LocalBoxFuture};
use log::error;
use tokio_net::tcp::TcpStream;
use super::builder::bind_addr;
use super::service::{
BoxedServerService, InternalServiceFactory, ServerMessage, StreamService,
};
use super::Token;
use crate::counter::CounterGuard;
pub struct ServiceConfig {
pub(crate) services: Vec<(String, net::TcpListener)>,
@@ -76,7 +75,8 @@ impl ServiceConfig {
pub(super) struct ConfiguredService {
rt: Box<dyn ServiceRuntimeConfiguration>,
names: HashMap<Token, (String, net::SocketAddr)>,
services: HashMap<String, Token>,
topics: HashMap<String, Token>,
services: Vec<Token>,
}
impl ConfiguredService {
@@ -84,13 +84,15 @@ impl ConfiguredService {
ConfiguredService {
rt,
names: HashMap::new(),
services: HashMap::new(),
topics: HashMap::new(),
services: Vec::new(),
}
}
pub(super) fn stream(&mut self, token: Token, name: String, addr: net::SocketAddr) {
self.names.insert(token, (name.clone(), addr));
self.services.insert(name, token);
self.topics.insert(name, token);
self.services.push(token);
}
}
@@ -103,43 +105,55 @@ impl InternalServiceFactory for ConfiguredService {
Box::new(Self {
rt: self.rt.clone(),
names: self.names.clone(),
topics: self.topics.clone(),
services: self.services.clone(),
})
}
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> {
// configure services
let mut rt = ServiceRuntime::new(self.services.clone());
let mut rt = ServiceRuntime::new(self.topics.clone());
self.rt.configure(&mut rt);
rt.validate();
let names = self.names.clone();
let mut names = self.names.clone();
let tokens = self.services.clone();
// construct services
async move {
let services = rt.services;
let mut services = rt.services;
// TODO: Proper error handling here
for f in rt.onstart.into_iter() {
f.await;
}
let mut res = vec![];
for (token, ns) in services.into_iter() {
let config = ServerConfig::new(names[&token].1);
let newserv = ns.new_service(&config);
match newserv.await {
Ok(serv) => {
res.push((token, serv));
}
Err(e) => {
error!("Can not construct service {:?}", e);
return Err(e);
for token in tokens {
if let Some(srv) = services.remove(&token) {
let newserv = srv.new_service(());
match newserv.await {
Ok(serv) => {
res.push((token, serv));
}
Err(_) => {
error!("Can not construct service");
return Err(());
}
}
} else {
let name = names.remove(&token).unwrap().0;
res.push((
token,
Box::new(StreamService::new(actix::fn_service(
move |_: TcpStream| {
error!("Service {:?} is not configured", name);
ok::<_, ()>(())
},
))),
));
};
}
return Ok(res);
}
.boxed_local()
.boxed_local()
}
}
@@ -196,7 +210,7 @@ impl ServiceRuntime {
pub fn service<T, F>(&mut self, name: &str, service: F)
where
F: actix::IntoServiceFactory<T>,
T: actix::ServiceFactory<Config = ServerConfig, Request = Io<TcpStream>> + 'static,
T: actix::ServiceFactory<Config = (), Request = TcpStream> + 'static,
T::Future: 'static,
T::Service: 'static,
T::InitError: fmt::Debug,
@@ -204,7 +218,7 @@ impl ServiceRuntime {
// let name = name.to_owned();
if let Some(token) = self.names.get(name) {
self.services.insert(
token.clone(),
*token,
Box::new(ServiceFactory {
inner: service.into_factory(),
}),
@@ -229,7 +243,7 @@ type BoxedNewService = Box<
Response = (),
Error = (),
InitError = (),
Config = ServerConfig,
Config = (),
Service = BoxedServerService,
Future = LocalBoxFuture<'static, Result<BoxedServerService, ()>>,
>,
@@ -241,7 +255,7 @@ struct ServiceFactory<T> {
impl<T> actix::ServiceFactory for ServiceFactory<T>
where
T: actix::ServiceFactory<Config = ServerConfig, Request = Io<TcpStream>>,
T: actix::ServiceFactory<Config = (), Request = TcpStream>,
T::Future: 'static,
T::Service: 'static,
T::Error: 'static,
@@ -251,12 +265,12 @@ where
type Response = ();
type Error = ();
type InitError = ();
type Config = ServerConfig;
type Config = ();
type Service = BoxedServerService;
type Future = LocalBoxFuture<'static, Result<BoxedServerService, ()>>;
fn new_service(&self, cfg: &ServerConfig) -> Self::Future {
let fut = self.inner.new_service(cfg);
fn new_service(&self, _: ()) -> Self::Future {
let fut = self.inner.new_service(());
async move {
return match fut.await {
Ok(s) => Ok(Box::new(StreamService::new(s)) as BoxedServerService),
@@ -266,6 +280,6 @@ where
}
};
}
.boxed_local()
.boxed_local()
}
}

View File

@@ -1,81 +0,0 @@
use std::cell::Cell;
use std::rc::Rc;
use futures::task::AtomicWaker;
use std::task;
#[derive(Clone)]
/// Simple counter with ability to notify task on reaching specific number
///
/// Counter could be cloned, total ncount is shared across all clones.
pub struct Counter(Rc<CounterInner>);
#[derive(Debug)]
struct CounterInner {
count: Cell<usize>,
capacity: usize,
task: AtomicWaker,
}
impl Counter {
/// Create `Counter` instance and set max value.
pub fn new(capacity: usize) -> Self {
Counter(Rc::new(CounterInner {
capacity,
count: Cell::new(0),
task: AtomicWaker::new(),
}))
}
pub fn get(&self) -> CounterGuard {
CounterGuard::new(self.0.clone())
}
/// Check if counter is not at capacity
pub fn available(&self, cx: &mut task::Context) -> bool {
self.0.available(cx)
}
/// Get total number of acquired counts
pub fn total(&self) -> usize {
self.0.count.get()
}
}
#[derive(Debug)]
pub struct CounterGuard(Rc<CounterInner>);
impl CounterGuard {
fn new(inner: Rc<CounterInner>) -> Self {
inner.inc();
CounterGuard(inner)
}
}
impl Drop for CounterGuard {
fn drop(&mut self) {
self.0.dec();
}
}
impl CounterInner {
fn inc(&self) {
self.count.set(self.count.get() + 1);
}
fn dec(&self) {
let num = self.count.get();
self.count.set(num - 1);
if num == self.capacity {
self.task.wake();
}
}
fn available(&self, cx: &mut task::Context) -> bool {
let avail = self.count.get() < self.capacity;
if !avail {
self.task.register(cx.waker());
}
avail
}
}

View File

@@ -1,18 +1,16 @@
//! General purpose tcp server
#![deny(rust_2018_idioms, warnings)]
#![allow(clippy::type_complexity)]
mod accept;
mod builder;
mod config;
mod counter;
mod server;
mod service;
// mod signals;
mod signals;
mod socket;
pub mod ssl;
mod worker;
pub use actix_server_config::{Io, IoStream, Protocol, ServerConfig};
pub use self::builder::ServerBuilder;
pub use self::config::{ServiceConfig, ServiceRuntime};
pub use self::server::Server;
@@ -27,7 +25,7 @@ pub(crate) struct Token(usize);
impl Token {
pub(crate) fn next(&mut self) -> Token {
let token = Token(self.0 + 1);
let token = Token(self.0);
self.0 += 1;
token
}

View File

@@ -1,29 +1,39 @@
use futures::channel::mpsc::UnboundedSender;
use futures::channel::oneshot;
use futures::{Future, TryFutureExt};
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures_channel::mpsc::UnboundedSender;
use futures_channel::oneshot;
use futures_util::FutureExt;
use crate::builder::ServerBuilder;
// use crate::signals::Signal;
use crate::signals::Signal;
#[derive(Debug)]
pub(crate) enum ServerCommand {
WorkerFaulted(usize),
Pause(oneshot::Sender<()>),
Resume(oneshot::Sender<()>),
// Signal(Signal),
Signal(Signal),
/// Whether to try and shut down gracefully
Stop {
graceful: bool,
completion: Option<oneshot::Sender<()>>,
},
/// Notify of server stop
Notify(oneshot::Sender<()>),
}
#[derive(Debug, Clone)]
pub struct Server(UnboundedSender<ServerCommand>);
#[derive(Debug)]
pub struct Server(
UnboundedSender<ServerCommand>,
Option<oneshot::Receiver<()>>,
);
impl Server {
pub(crate) fn new(tx: UnboundedSender<ServerCommand>) -> Self {
Server(tx)
Server(tx, None)
}
/// Start server building process
@@ -31,9 +41,9 @@ impl Server {
ServerBuilder::default()
}
// pub(crate) fn signal(&self, sig: Signal) {
// let _ = self.0.unbounded_send(ServerCommand::Signal(sig));
// }
pub(crate) fn signal(&self, sig: Signal) {
let _ = self.0.unbounded_send(ServerCommand::Signal(sig));
}
pub(crate) fn worker_faulted(&self, idx: usize) {
let _ = self.0.unbounded_send(ServerCommand::WorkerFaulted(idx));
@@ -43,28 +53,56 @@ impl Server {
///
/// If socket contains some pending connection, they might be dropped.
/// All opened connection remains active.
pub fn pause(&self) -> impl Future<Output = Result<(), ()>> {
pub fn pause(&self) -> impl Future<Output = ()> {
let (tx, rx) = oneshot::channel();
let _ = self.0.unbounded_send(ServerCommand::Pause(tx));
rx.map_err(|_| ())
rx.map(|_| ())
}
/// Resume accepting incoming connections
pub fn resume(&self) -> impl Future<Output = Result<(), ()>> {
pub fn resume(&self) -> impl Future<Output = ()> {
let (tx, rx) = oneshot::channel();
let _ = self.0.unbounded_send(ServerCommand::Resume(tx));
rx.map_err(|_| ())
rx.map(|_| ())
}
/// Stop incoming connection processing, stop all workers and exit.
///
/// If server starts with `spawn()` method, then spawned thread get terminated.
pub fn stop(&self, graceful: bool) -> impl Future<Output = Result<(), ()>> {
pub fn stop(&self, graceful: bool) -> impl Future<Output = ()> {
let (tx, rx) = oneshot::channel();
let _ = self.0.unbounded_send(ServerCommand::Stop {
graceful,
completion: Some(tx),
});
rx.map_err(|_| ())
rx.map(|_| ())
}
}
impl Clone for Server {
fn clone(&self) -> Self {
Self(self.0.clone(), None)
}
}
impl Future for Server {
type Output = io::Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
if this.1.is_none() {
let (tx, rx) = oneshot::channel();
if this.0.unbounded_send(ServerCommand::Notify(tx)).is_err() {
return Poll::Ready(Ok(()));
}
this.1 = Some(rx);
}
match Pin::new(this.1.as_mut().unwrap()).poll(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(Ok(_)) => Poll::Ready(Ok(())),
Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
}
}
}

View File

@@ -4,14 +4,13 @@ use std::task::{Context, Poll};
use std::time::Duration;
use actix_rt::spawn;
use actix_server_config::{Io, ServerConfig};
use actix_service::{self as actix, Service, ServiceFactory as ActixServiceFactory};
use futures::future::{err, ok, LocalBoxFuture, Ready};
use futures::{FutureExt, TryFutureExt};
use actix_utils::counter::CounterGuard;
use futures_util::future::{err, ok, LocalBoxFuture, Ready};
use futures_util::{FutureExt, TryFutureExt};
use log::error;
use super::Token;
use crate::counter::CounterGuard;
use crate::socket::{FromStream, StdStream};
/// Server message
@@ -25,7 +24,7 @@ pub(crate) enum ServerMessage {
}
pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static {
type Factory: actix::ServiceFactory<Config = ServerConfig, Request = Io<Stream>>;
type Factory: actix::ServiceFactory<Config = (), Request = Stream>;
fn create(&self) -> Self::Factory;
}
@@ -59,7 +58,7 @@ impl<T> StreamService<T> {
impl<T, I> Service for StreamService<T>
where
T: Service<Request = Io<I>>,
T: Service<Request = I>,
T::Future: 'static,
T::Error: 'static,
I: FromStream,
@@ -81,14 +80,11 @@ where
});
if let Ok(stream) = stream {
let f = self.service.call(Io::new(stream));
spawn(
async move {
let _ = f.await;
drop(guard);
}
.boxed_local(),
);
let f = self.service.call(stream);
spawn(async move {
let _ = f.await;
drop(guard);
});
ok(())
} else {
err(())
@@ -149,11 +145,9 @@ where
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> {
let token = self.token;
let config = ServerConfig::new(self.addr);
self.inner
.create()
.new_service(&config)
.new_service(())
.map_err(|_| ())
.map_ok(move |inner| {
let service: BoxedServerService = Box::new(StreamService::new(inner));
@@ -180,7 +174,7 @@ impl InternalServiceFactory for Box<dyn InternalServiceFactory> {
impl<F, T, I> ServiceFactory<I> for F
where
F: Fn() -> T + Send + Clone + 'static,
T: actix::ServiceFactory<Config = ServerConfig, Request = Io<I>>,
T: actix::ServiceFactory<Config = (), Request = I>,
I: FromStream,
{
type Factory = T;

View File

@@ -3,15 +3,12 @@ use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_rt::spawn;
use futures::future::LocalBoxFuture;
use futures::stream::{futures_unordered, FuturesUnordered, LocalBoxStream};
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStream, TryStreamExt};
use tokio_net::signal::unix::signal;
use futures_util::future::lazy;
use crate::server::Server;
/// Different types of process signals
#[allow(dead_code)]
#[derive(PartialEq, Clone, Copy, Debug)]
pub(crate) enum Signal {
/// SIGHUP
@@ -27,126 +24,80 @@ pub(crate) enum Signal {
pub(crate) struct Signals {
srv: Server,
#[cfg(not(unix))]
stream: SigStream,
stream: Pin<Box<dyn Future<Output = io::Result<()>>>>,
#[cfg(unix)]
streams: Vec<SigStream>,
streams: Vec<(Signal, actix_rt::signal::unix::Signal)>,
}
type SigStream = LocalBoxStream<'static, Result<Signal, io::Error>>;
impl Signals {
pub(crate) fn start(srv: Server) {
let fut = {
pub(crate) fn start(srv: Server) -> io::Result<()> {
actix_rt::spawn(lazy(|_| {
#[cfg(not(unix))]
{
tokio_net::signal::ctrl_c()
.map_err(|_| ())
.and_then(move |stream| Signals {
srv,
stream: Box::new(stream.map(|_| Signal::Int)),
})
actix_rt::spawn(Signals {
srv,
stream: Box::pin(actix_rt::signal::ctrl_c()),
});
}
#[cfg(unix)]
{
use tokio_net::signal::unix;
use actix_rt::signal::unix;
let mut sigs: Vec<_> = Vec::new();
let mut streams = Vec::new();
let mut SIG_MAP = [
(
tokio_net::signal::unix::SignalKind::interrupt(),
Signal::Int,
),
(tokio_net::signal::unix::SignalKind::hangup(), Signal::Hup),
(
tokio_net::signal::unix::SignalKind::terminate(),
Signal::Term,
),
(tokio_net::signal::unix::SignalKind::quit(), Signal::Quit),
let sig_map = [
(unix::SignalKind::interrupt(), Signal::Int),
(unix::SignalKind::hangup(), Signal::Hup),
(unix::SignalKind::terminate(), Signal::Term),
(unix::SignalKind::quit(), Signal::Quit),
];
for (kind, sig) in SIG_MAP.into_iter() {
let sig = sig.clone();
let fut = signal(*kind).unwrap();
sigs.push(fut.map(move |_| Ok(sig)).boxed_local());
for (kind, sig) in sig_map.iter() {
match unix::signal(*kind) {
Ok(stream) => streams.push((*sig, stream)),
Err(e) => log::error!(
"Can not initialize stream handler for {:?} err: {}",
sig,
e
),
}
}
/* TODO: Finish rewriting this
sigs.push(
tokio_net::signal::unix::signal(tokio_net::signal::si).unwrap()
.map(|stream| {
let s: SigStream = Box::new(stream.map(|_| Signal::Int));
s
}).boxed()
);
sigs.push(
tokio_net::signal::unix::signal(tokio_net::signal::unix::SignalKind::hangup()).unwrap()
.map(|stream: unix::Signal| {
let s: SigStream = Box::new(stream.map(|_| Signal::Hup));
s
}).boxed()
);
sigs.push(
tokio_net::signal::unix::signal(
tokio_net::signal::unix::SignalKind::terminate()
).unwrap()
.map(|stream| {
let s: SigStream = Box::new(stream.map(|_| Signal::Term));
s
}).boxed(),
);
sigs.push(
tokio_net::signal::unix::signal(
tokio_net::signal::unix::SignalKind::quit()
).unwrap()
.map(|stream| {
let s: SigStream = Box::new(stream.map(|_| Signal::Quit));
s
}).boxed()
);
*/
Signals { srv, streams: sigs }
actix_rt::spawn(Signals { srv, streams })
}
};
spawn(async {});
}));
Ok(())
}
}
impl Future for Signals {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
unimplemented!()
}
/*
type Item = ();
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
#[cfg(not(unix))]
loop {
match self.stream.poll() {
Ok(Async::Ready(None)) | Err(_) => return Ok(Async::Ready(())),
Ok(Async::Ready(Some(sig))) => self.srv.signal(sig),
Ok(Async::NotReady) => return Ok(Async::NotReady),
match Pin::new(&mut self.stream).poll(cx) {
Poll::Ready(_) => {
self.srv.signal(Signal::Int);
Poll::Ready(())
}
Poll::Pending => return Poll::Pending,
}
#[cfg(unix)]
{
for s in &mut self.streams {
for idx in 0..self.streams.len() {
loop {
match s.poll() {
Ok(Async::Ready(None)) | Err(_) => return Ok(Async::Ready(())),
Ok(Async::NotReady) => break,
Ok(Async::Ready(Some(sig))) => self.srv.signal(sig),
match self.streams[idx].1.poll_recv(cx) {
Poll::Ready(None) => return Poll::Ready(()),
Poll::Pending => break,
Poll::Ready(Some(_)) => {
let sig = self.streams[idx].0;
self.srv.signal(sig);
}
}
}
}
Ok(Async::NotReady)
Poll::Pending
}
}
*/
}

View File

@@ -1,8 +1,7 @@
use std::{fmt, io, net};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_net::driver::Handle;
use tokio_net::tcp::TcpStream;
use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream;
pub(crate) enum StdListener {
Tcp(net::TcpListener),
@@ -151,7 +150,7 @@ pub trait FromStream: AsyncRead + AsyncWrite + Sized {
impl FromStream for TcpStream {
fn from_stdstream(sock: StdStream) -> io::Result<Self> {
match sock {
StdStream::Tcp(stream) => TcpStream::from_std(stream, &Handle::default()),
StdStream::Tcp(stream) => TcpStream::from_std(stream),
#[cfg(all(unix))]
StdStream::Uds(_) => {
panic!("Should not happen, bug in server impl");
@@ -161,13 +160,11 @@ impl FromStream for TcpStream {
}
#[cfg(all(unix))]
impl FromStream for tokio_net::uds::UnixStream {
impl FromStream for actix_rt::net::UnixStream {
fn from_stdstream(sock: StdStream) -> io::Result<Self> {
match sock {
StdStream::Tcp(_) => panic!("Should not happen, bug in server impl"),
StdStream::Uds(stream) => {
tokio_net::uds::UnixStream::from_std(stream, &Handle::default())
}
StdStream::Uds(stream) => actix_rt::net::UnixStream::from_std(stream),
}
}
}

View File

@@ -1,138 +0,0 @@
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_service::{Service, ServiceFactory};
use futures::future::{ok, FutureExt, LocalBoxFuture, Ready};
use open_ssl::ssl::SslAcceptor;
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_openssl::{HandshakeError, SslStream};
use crate::counter::{Counter, CounterGuard};
use crate::ssl::MAX_CONN_COUNTER;
use crate::{Io, Protocol, ServerConfig};
/// Support `SSL` connections via openssl package
///
/// `ssl` feature enables `OpensslAcceptor` type
pub struct OpensslAcceptor<T: AsyncRead + AsyncWrite, P = ()> {
acceptor: SslAcceptor,
io: PhantomData<(T, P)>,
}
impl<T: AsyncRead + AsyncWrite, P> OpensslAcceptor<T, P> {
/// Create default `OpensslAcceptor`
pub fn new(acceptor: SslAcceptor) -> Self {
OpensslAcceptor {
acceptor,
io: PhantomData,
}
}
}
impl<T: AsyncRead + AsyncWrite, P> Clone for OpensslAcceptor<T, P> {
fn clone(&self) -> Self {
Self {
acceptor: self.acceptor.clone(),
io: PhantomData,
}
}
}
impl<T: AsyncRead + AsyncWrite + Unpin + 'static, P> ServiceFactory for OpensslAcceptor<T, P> {
type Request = Io<T, P>;
type Response = Io<SslStream<T>, P>;
type Error = HandshakeError<T>;
type Config = ServerConfig;
type Service = OpensslAcceptorService<T, P>;
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, cfg: &ServerConfig) -> Self::Future {
cfg.set_secure();
MAX_CONN_COUNTER.with(|conns| {
ok(OpensslAcceptorService {
acceptor: self.acceptor.clone(),
conns: conns.clone(),
io: PhantomData,
})
})
}
}
pub struct OpensslAcceptorService<T, P> {
acceptor: SslAcceptor,
conns: Counter,
io: PhantomData<(T, P)>,
}
impl<T: AsyncRead + AsyncWrite + Unpin + 'static, P> Service for OpensslAcceptorService<T, P> {
type Request = Io<T, P>;
type Response = Io<SslStream<T>, P>;
type Error = HandshakeError<T>;
type Future = OpensslAcceptorServiceFut<T, P>;
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if self.conns.available(ctx) {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
fn call(&mut self, req: Self::Request) -> Self::Future {
let (io, params, _) = req.into_parts();
let acc = self.acceptor.clone();
OpensslAcceptorServiceFut {
_guard: self.conns.get(),
fut: async move {
let acc = acc;
tokio_openssl::accept(&acc, io).await
}
.boxed_local(),
params: Some(params),
}
}
}
pub struct OpensslAcceptorServiceFut<T, P>
where
T: AsyncRead + AsyncWrite,
{
fut: LocalBoxFuture<'static, Result<SslStream<T>, HandshakeError<T>>>,
params: Option<P>,
_guard: CounterGuard,
}
impl<T: AsyncRead + AsyncWrite + Unpin, P> Unpin for OpensslAcceptorServiceFut<T, P> {}
impl<T: AsyncRead + AsyncWrite + Unpin, P> Future for OpensslAcceptorServiceFut<T, P> {
type Output = Result<Io<SslStream<T>, P>, HandshakeError<T>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
let io = futures::ready!(Pin::new(&mut this.fut).poll(cx))?;
let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() {
const H2: &[u8] = b"\x02h2";
const HTTP10: &[u8] = b"\x08http/1.0";
const HTTP11: &[u8] = b"\x08http/1.1";
if protos.windows(3).any(|window| window == H2) {
Protocol::Http2
} else if protos.windows(9).any(|window| window == HTTP11) {
Protocol::Http11
} else if protos.windows(9).any(|window| window == HTTP10) {
Protocol::Http10
} else {
Protocol::Unknown
}
} else {
Protocol::Unknown
};
Poll::Ready(Ok(Io::from_parts(io, this.params.take().unwrap(), proto)))
}
}

View File

@@ -1,124 +0,0 @@
use std::future::Future;
use std::io;
use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use actix_service::{Service, ServiceFactory};
use futures::future::{ok, Ready};
use rust_tls::ServerConfig;
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_rustls::{server::TlsStream, Accept, TlsAcceptor};
use crate::counter::{Counter, CounterGuard};
use crate::ssl::MAX_CONN_COUNTER;
use crate::{Io, Protocol, ServerConfig as SrvConfig};
/// Support `SSL` connections via rustls package
///
/// `rust-tls` feature enables `RustlsAcceptor` type
pub struct RustlsAcceptor<T, P = ()> {
config: Arc<ServerConfig>,
io: PhantomData<(T, P)>,
}
impl<T: AsyncRead + AsyncWrite, P> RustlsAcceptor<T, P> {
/// Create `RustlsAcceptor` new service
pub fn new(config: ServerConfig) -> Self {
RustlsAcceptor {
config: Arc::new(config),
io: PhantomData,
}
}
}
impl<T, P> Clone for RustlsAcceptor<T, P> {
fn clone(&self) -> Self {
Self {
config: self.config.clone(),
io: PhantomData,
}
}
}
impl<T: AsyncRead + AsyncWrite + Unpin, P> ServiceFactory for RustlsAcceptor<T, P> {
type Request = Io<T, P>;
type Response = Io<TlsStream<T>, P>;
type Error = io::Error;
type Config = SrvConfig;
type Service = RustlsAcceptorService<T, P>;
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, cfg: &SrvConfig) -> Self::Future {
cfg.set_secure();
MAX_CONN_COUNTER.with(|conns| {
ok(RustlsAcceptorService {
acceptor: self.config.clone().into(),
conns: conns.clone(),
io: PhantomData,
})
})
}
}
pub struct RustlsAcceptorService<T, P> {
acceptor: TlsAcceptor,
io: PhantomData<(T, P)>,
conns: Counter,
}
impl<T: AsyncRead + AsyncWrite + Unpin, P> Service for RustlsAcceptorService<T, P> {
type Request = Io<T, P>;
type Response = Io<TlsStream<T>, P>;
type Error = io::Error;
type Future = RustlsAcceptorServiceFut<T, P>;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
if self.conns.available(cx) {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
fn call(&mut self, req: Self::Request) -> Self::Future {
let (io, params, _) = req.into_parts();
RustlsAcceptorServiceFut {
_guard: self.conns.get(),
fut: self.acceptor.accept(io),
params: Some(params),
}
}
}
pub struct RustlsAcceptorServiceFut<T, P>
where
T: AsyncRead + AsyncWrite + Unpin,
{
fut: Accept<T>,
params: Option<P>,
_guard: CounterGuard,
}
impl<T: AsyncRead + AsyncWrite + Unpin, P> Unpin for RustlsAcceptorServiceFut<T, P> {}
impl<T: AsyncRead + AsyncWrite + Unpin, P> Future for RustlsAcceptorServiceFut<T, P> {
type Output = Result<Io<TlsStream<T>, P>, io::Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let this = self.get_mut();
let res = futures::ready!(Pin::new(&mut this.fut).poll(cx));
match res {
Ok(io) => {
let params = this.params.take().unwrap();
Poll::Ready(Ok(Io::from_parts(io, params, Protocol::Unknown)))
}
Err(e) => Poll::Ready(Err(e)),
}
}
}

View File

@@ -2,18 +2,18 @@ use std::pin::Pin;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::task::{Context, Poll};
use std::{mem, time};
use std::time;
use actix_rt::time::{delay_until, Delay, Instant};
use actix_rt::{spawn, Arbiter};
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures::channel::oneshot;
use futures::future::{join_all, LocalBoxFuture, MapOk};
use futures::{Future, FutureExt, Stream, TryFutureExt};
use actix_utils::counter::Counter;
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures_channel::oneshot;
use futures_util::future::{join_all, LocalBoxFuture, MapOk};
use futures_util::{future::Future, stream::Stream, FutureExt, TryFutureExt};
use log::{error, info, trace};
use tokio_timer::{delay, Delay};
use crate::accept::AcceptNotify;
use crate::counter::Counter;
use crate::service::{BoxedServerService, InternalServiceFactory, ServerMessage};
use crate::socket::{SocketAddr, StdStream};
use crate::Token;
@@ -128,7 +128,7 @@ impl WorkerAvailability {
pub(crate) struct Worker {
rx: UnboundedReceiver<WorkerCommand>,
rx2: UnboundedReceiver<StopCommand>,
services: Vec<Option<(usize, BoxedServerService)>>,
services: Vec<WorkerService>,
availability: WorkerAvailability,
conns: Counter,
factories: Vec<Box<dyn InternalServiceFactory>>,
@@ -136,103 +136,155 @@ pub(crate) struct Worker {
shutdown_timeout: time::Duration,
}
struct WorkerService {
factory: usize,
status: WorkerServiceStatus,
service: BoxedServerService,
}
impl WorkerService {
fn created(&mut self, service: BoxedServerService) {
self.service = service;
self.status = WorkerServiceStatus::Unavailable;
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum WorkerServiceStatus {
Available,
Unavailable,
Failed,
Restarting,
Stopping,
Stopped,
}
impl Worker {
pub(crate) fn start(
rx: UnboundedReceiver<WorkerCommand>,
rx2: UnboundedReceiver<StopCommand>,
idx: usize,
factories: Vec<Box<dyn InternalServiceFactory>>,
availability: WorkerAvailability,
shutdown_timeout: time::Duration,
) {
availability.set(false);
let mut wrk = MAX_CONNS_COUNTER.with(|conns| Worker {
rx,
rx2,
availability,
factories,
shutdown_timeout,
services: Vec::new(),
conns: conns.clone(),
state: WorkerState::Unavailable(Vec::new()),
});
) -> WorkerClient {
let (tx1, rx) = unbounded();
let (tx2, rx2) = unbounded();
let avail = availability.clone();
let mut fut: Vec<MapOk<LocalBoxFuture<'static, _>, _>> = Vec::new();
for (idx, factory) in wrk.factories.iter().enumerate() {
fut.push(factory.create().map_ok(move |r| {
r.into_iter()
.map(|(t, s): (Token, _)| (idx, t, s))
.collect::<Vec<_>>()
}));
}
spawn(
Arbiter::new().send(
async move {
let res = join_all(fut).await;
let res: Result<Vec<_>, _> = res.into_iter().collect();
match res {
Ok(services) => {
for item in services {
for (idx, token, service) in item {
while token.0 >= wrk.services.len() {
wrk.services.push(None);
availability.set(false);
let mut wrk = MAX_CONNS_COUNTER.with(move |conns| Worker {
rx,
rx2,
availability,
factories,
shutdown_timeout,
services: Vec::new(),
conns: conns.clone(),
state: WorkerState::Unavailable(Vec::new()),
});
let mut fut: Vec<MapOk<LocalBoxFuture<'static, _>, _>> = Vec::new();
for (idx, factory) in wrk.factories.iter().enumerate() {
fut.push(factory.create().map_ok(move |r| {
r.into_iter()
.map(|(t, s): (Token, _)| (idx, t, s))
.collect::<Vec<_>>()
}));
}
spawn(async move {
let res = join_all(fut).await;
let res: Result<Vec<_>, _> = res.into_iter().collect();
match res {
Ok(services) => {
for item in services {
for (factory, token, service) in item {
assert_eq!(token.0, wrk.services.len());
wrk.services.push(WorkerService {
factory,
service,
status: WorkerServiceStatus::Unavailable,
});
}
wrk.services[token.0] = Some((idx, service));
}
}
Err(e) => {
error!("Can not start worker: {:?}", e);
Arbiter::current().stop();
}
}
Err(e) => {
error!("Can not start worker: {:?}", e);
Arbiter::current().stop();
}
}
wrk.await
wrk.await
});
}
.boxed_local(),
.boxed(),
);
WorkerClient::new(idx, tx1, tx2, avail)
}
fn shutdown(&mut self, force: bool) {
if force {
self.services.iter_mut().for_each(|h| {
if let Some(h) = h {
let _ = h.1.call((None, ServerMessage::ForceShutdown));
self.services.iter_mut().for_each(|srv| {
if srv.status == WorkerServiceStatus::Available {
srv.status = WorkerServiceStatus::Stopped;
actix_rt::spawn(
srv.service
.call((None, ServerMessage::ForceShutdown))
.map(|_| ()),
);
}
});
} else {
let timeout = self.shutdown_timeout;
self.services.iter_mut().for_each(move |h| {
if let Some(h) = h {
let _ = h.1.call((None, ServerMessage::Shutdown(timeout)));
self.services.iter_mut().for_each(move |srv| {
if srv.status == WorkerServiceStatus::Available {
srv.status = WorkerServiceStatus::Stopping;
actix_rt::spawn(
srv.service
.call((None, ServerMessage::Shutdown(timeout)))
.map(|_| ()),
);
}
});
}
}
fn check_readiness(
&mut self,
trace: bool,
cx: &mut Context<'_>,
) -> Result<bool, (Token, usize)> {
fn check_readiness(&mut self, cx: &mut Context<'_>) -> Result<bool, (Token, usize)> {
let mut ready = self.conns.available(cx);
let mut failed = None;
for (token, service) in &mut self.services.iter_mut().enumerate() {
if let Some(service) = service {
match service.1.poll_ready(cx) {
for (idx, srv) in &mut self.services.iter_mut().enumerate() {
if srv.status == WorkerServiceStatus::Available
|| srv.status == WorkerServiceStatus::Unavailable
{
match srv.service.poll_ready(cx) {
Poll::Ready(Ok(_)) => {
if trace {
if srv.status == WorkerServiceStatus::Unavailable {
trace!(
"Service {:?} is available",
self.factories[service.0].name(Token(token))
self.factories[srv.factory].name(Token(idx))
);
srv.status = WorkerServiceStatus::Available;
}
}
Poll::Pending => {
ready = false;
if srv.status == WorkerServiceStatus::Available {
trace!(
"Service {:?} is unavailable",
self.factories[srv.factory].name(Token(idx))
);
srv.status = WorkerServiceStatus::Unavailable;
}
}
Poll::Pending => ready = false,
Poll::Ready(Err(_)) => {
error!(
"Service {:?} readiness check returned error, restarting",
self.factories[service.0].name(Token(token))
self.factories[srv.factory].name(Token(idx))
);
failed = Some((Token(token), service.0));
failed = Some((Token(idx), srv.factory));
srv.status = WorkerServiceStatus::Failed;
}
}
}
@@ -246,7 +298,6 @@ impl Worker {
}
enum WorkerState {
None,
Available,
Unavailable(Vec<Conn>),
Restarting(
@@ -254,12 +305,18 @@ enum WorkerState {
Token,
Pin<Box<dyn Future<Output = Result<Vec<(Token, BoxedServerService)>, ()>>>>,
),
Shutdown(Delay, Delay, oneshot::Sender<bool>),
Shutdown(
Pin<Box<Delay>>,
Pin<Box<Delay>>,
Option<oneshot::Sender<bool>>,
),
}
impl Future for Worker {
type Output = ();
// FIXME: remove this attribute
#[allow(clippy::never_loop)]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// `StopWorker` message handler
if let Poll::Ready(Some(StopCommand { graceful, result })) =
@@ -277,9 +334,9 @@ impl Future for Worker {
if num != 0 {
info!("Graceful worker shutdown, {} connections", num);
self.state = WorkerState::Shutdown(
delay(time::Instant::now() + time::Duration::from_secs(1)),
delay(time::Instant::now() + self.shutdown_timeout),
result,
Box::pin(delay_until(Instant::now() + time::Duration::from_secs(1))),
Box::pin(delay_until(Instant::now() + self.shutdown_timeout)),
Some(result),
);
} else {
let _ = result.send(true);
@@ -293,72 +350,55 @@ impl Future for Worker {
}
}
let state = mem::replace(&mut self.state, WorkerState::None);
match state {
WorkerState::Unavailable(mut conns) => {
match self.check_readiness(true, cx) {
match self.state {
WorkerState::Unavailable(ref mut conns) => {
let conn = conns.pop();
match self.check_readiness(cx) {
Ok(true) => {
self.state = WorkerState::Available;
// process requests from wait queue
while let Some(msg) = conns.pop() {
match self.check_readiness(false, cx) {
Ok(true) => {
let guard = self.conns.get();
let _ = self.services[msg.token.0]
.as_mut()
.expect("actix net bug")
.1
.call((Some(guard), ServerMessage::Connect(msg.io)));
}
Ok(false) => {
trace!("Worker is unavailable");
self.state = WorkerState::Unavailable(conns);
return self.poll(cx);
}
Err((token, idx)) => {
trace!(
"Service {:?} failed, restarting",
self.factories[idx].name(token)
);
self.state = WorkerState::Restarting(
idx,
token,
self.factories[idx].create(),
);
return self.poll(cx);
}
}
if let Some(conn) = conn {
let guard = self.conns.get();
let _ = self.services[conn.token.0]
.service
.call((Some(guard), ServerMessage::Connect(conn.io)));
} else {
self.state = WorkerState::Available;
self.availability.set(true);
}
self.availability.set(true);
return self.poll(cx);
self.poll(cx)
}
Ok(false) => {
self.state = WorkerState::Unavailable(conns);
return Poll::Pending;
// push connection back to queue
if let Some(conn) = conn {
if let WorkerState::Unavailable(ref mut conns) = self.state {
conns.push(conn);
}
}
Poll::Pending
}
Err((token, idx)) => {
trace!(
"Service {:?} failed, restarting",
self.factories[idx].name(token)
);
self.services[token.0].status = WorkerServiceStatus::Restarting;
self.state =
WorkerState::Restarting(idx, token, self.factories[idx].create());
return self.poll(cx);
self.poll(cx)
}
}
}
WorkerState::Restarting(idx, token, mut fut) => {
match Pin::new(&mut fut).poll(cx) {
WorkerState::Restarting(idx, token, ref mut fut) => {
match Pin::new(fut).poll(cx) {
Poll::Ready(Ok(item)) => {
for (token, service) in item {
trace!(
"Service {:?} has been restarted",
self.factories[idx].name(token)
);
self.services[token.0] = Some((idx, service));
self.services[token.0].created(service);
self.state = WorkerState::Unavailable(Vec::new());
return self.poll(cx);
}
}
Poll::Ready(Err(_)) => {
@@ -368,54 +408,52 @@ impl Future for Worker {
);
}
Poll::Pending => {
self.state = WorkerState::Restarting(idx, token, fut);
return Poll::Pending;
}
}
return self.poll(cx);
self.poll(cx)
}
WorkerState::Shutdown(mut t1, mut t2, tx) => {
WorkerState::Shutdown(ref mut t1, ref mut t2, ref mut tx) => {
let num = num_connections();
if num == 0 {
let _ = tx.send(true);
let _ = tx.take().unwrap().send(true);
Arbiter::current().stop();
return Poll::Ready(());
}
// check graceful timeout
match Pin::new(&mut t2).poll(cx) {
match t2.as_mut().poll(cx) {
Poll::Pending => (),
Poll::Ready(_) => {
let _ = tx.take().unwrap().send(false);
self.shutdown(true);
let _ = tx.send(false);
Arbiter::current().stop();
return Poll::Ready(());
}
}
// sleep for 1 second and then check again
match Pin::new(&mut t1).poll(cx) {
match t1.as_mut().poll(cx) {
Poll::Pending => (),
Poll::Ready(_) => {
t1 = delay(time::Instant::now() + time::Duration::from_secs(1));
let _ = Pin::new(&mut t1).poll(cx);
*t1 = Box::pin(delay_until(
Instant::now() + time::Duration::from_secs(1),
));
let _ = t1.as_mut().poll(cx);
}
}
self.state = WorkerState::Shutdown(t1, t2, tx);
return Poll::Pending;
Poll::Pending
}
WorkerState::Available => {
loop {
match Pin::new(&mut self.rx).poll_next(cx) {
// handle incoming tcp stream
// handle incoming io stream
Poll::Ready(Some(WorkerCommand(msg))) => {
match self.check_readiness(false, cx) {
match self.check_readiness(cx) {
Ok(true) => {
let guard = self.conns.get();
let _ = self.services[msg.token.0]
.as_mut()
.expect("actix-server bug")
.1
.service
.call((Some(guard), ServerMessage::Connect(msg.io)));
continue;
}
@@ -430,6 +468,8 @@ impl Future for Worker {
self.factories[idx].name(token)
);
self.availability.set(false);
self.services[token.0].status =
WorkerServiceStatus::Restarting;
self.state = WorkerState::Restarting(
idx,
token,
@@ -447,7 +487,6 @@ impl Future for Worker {
}
}
}
WorkerState::None => panic!(),
};
}
}
}

View File

@@ -1,21 +1,18 @@
use std::io::Read;
use std::sync::mpsc;
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use std::sync::{mpsc, Arc};
use std::{net, thread, time};
use actix_codec::{BytesCodec, Framed};
use actix_server::{Io, Server, ServerConfig};
use actix_service::{factory_fn_cfg, service_fn, service_fn2};
use bytes::Bytes;
use futures::{future::ok, SinkExt};
use net2::TcpBuilder;
use tokio_net::tcp::TcpStream;
use actix_server::Server;
use actix_service::fn_service;
use futures_util::future::{lazy, ok};
use socket2::{Domain, Protocol, Socket, Type};
fn unused_addr() -> net::SocketAddr {
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
let socket = TcpBuilder::new_v4().unwrap();
socket.bind(&addr).unwrap();
socket.reuse_address(true).unwrap();
let tcp = socket.to_tcp_listener().unwrap();
let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap();
socket.bind(&addr.into()).unwrap();
socket.set_reuse_address(true).unwrap();
let tcp = socket.into_tcp_listener();
tcp.local_addr().unwrap()
}
@@ -27,12 +24,9 @@ fn test_bind() {
let h = thread::spawn(move || {
let sys = actix_rt::System::new("test");
let srv = Server::build()
.bind("test", addr, move || {
factory_fn_cfg(move |cfg: &ServerConfig| {
assert_eq!(cfg.local_addr(), addr);
ok::<_, ()>(service_fn2(|_| ok::<_, ()>(())))
})
})
.workers(1)
.disable_signals()
.bind("test", addr, move || fn_service(|_| ok::<_, ()>(())))
.unwrap()
.start();
let _ = tx.send((srv, actix_rt::System::current()));
@@ -42,27 +36,7 @@ fn test_bind() {
thread::sleep(time::Duration::from_millis(500));
assert!(net::TcpStream::connect(addr).is_ok());
let _ = sys.stop();
let _ = h.join();
}
#[test]
fn test_bind_no_config() {
let addr = unused_addr();
let (tx, rx) = mpsc::channel();
let h = thread::spawn(move || {
let sys = actix_rt::System::new("test");
let srv = Server::build()
.bind("test", addr, move || service_fn(|_| ok::<_, ()>(())))
.unwrap()
.start();
let _ = tx.send((srv, actix_rt::System::current()));
let _ = sys.run();
});
let (_, sys) = rx.recv().unwrap();
assert!(net::TcpStream::connect(addr).is_ok());
let _ = sys.stop();
sys.stop();
let _ = h.join();
}
@@ -74,29 +48,32 @@ fn test_listen() {
let h = thread::spawn(move || {
let sys = actix_rt::System::new("test");
let lst = net::TcpListener::bind(addr).unwrap();
let srv = Server::build()
.listen("test", lst, move || {
factory_fn_cfg(move |cfg: &ServerConfig| {
assert_eq!(cfg.local_addr(), addr);
ok::<_, ()>(service_fn2(|_| ok::<_, ()>(())))
})
})
Server::build()
.disable_signals()
.workers(1)
.listen("test", lst, move || fn_service(|_| ok::<_, ()>(())))
.unwrap()
.start();
let _ = tx.send((srv, actix_rt::System::current()));
let _ = tx.send(actix_rt::System::current());
let _ = sys.run();
});
let (_, sys) = rx.recv().unwrap();
let sys = rx.recv().unwrap();
thread::sleep(time::Duration::from_millis(500));
assert!(net::TcpStream::connect(addr).is_ok());
let _ = sys.stop();
sys.stop();
let _ = h.join();
}
#[test]
#[cfg(unix)]
fn test_start() {
use actix_codec::{BytesCodec, Framed};
use actix_rt::net::TcpStream;
use bytes::Bytes;
use futures_util::sink::SinkExt;
use std::io::Read;
let addr = unused_addr();
let (tx, rx) = mpsc::channel();
@@ -104,19 +81,12 @@ fn test_start() {
let sys = actix_rt::System::new("test");
let srv: Server = Server::build()
.backlog(100)
.disable_signals()
.bind("test", addr, move || {
factory_fn_cfg(move |cfg: &ServerConfig| {
assert_eq!(cfg.local_addr(), addr);
let srv = service_fn2(|io: Io<TcpStream>| {
async {
let mut f = Framed::new(io.into_parts().0, BytesCodec);
f.send(Bytes::from_static(b"test")).await.unwrap();
Ok::<_, ()>(())
}
});
ok::<_, ()>(srv)
fn_service(|io: TcpStream| async move {
let mut f = Framed::new(io, BytesCodec);
f.send(Bytes::from_static(b"test")).await.unwrap();
Ok::<_, ()>(())
})
})
.unwrap()
@@ -159,6 +129,54 @@ fn test_start() {
assert!(net::TcpStream::connect(addr).is_err());
thread::sleep(time::Duration::from_millis(100));
let _ = sys.stop();
sys.stop();
let _ = h.join();
}
#[test]
fn test_configure() {
let addr1 = unused_addr();
let addr2 = unused_addr();
let addr3 = unused_addr();
let (tx, rx) = mpsc::channel();
let num = Arc::new(AtomicUsize::new(0));
let num2 = num.clone();
let h = thread::spawn(move || {
let num = num2.clone();
let sys = actix_rt::System::new("test");
let srv = Server::build()
.disable_signals()
.configure(move |cfg| {
let num = num.clone();
let lst = net::TcpListener::bind(addr3).unwrap();
cfg.bind("addr1", addr1)
.unwrap()
.bind("addr2", addr2)
.unwrap()
.listen("addr3", lst)
.apply(move |rt| {
let num = num.clone();
rt.service("addr1", fn_service(|_| ok::<_, ()>(())));
rt.service("addr3", fn_service(|_| ok::<_, ()>(())));
rt.on_start(lazy(move |_| {
let _ = num.fetch_add(1, Relaxed);
}))
})
})
.unwrap()
.workers(1)
.start();
let _ = tx.send((srv, actix_rt::System::current()));
let _ = sys.run();
});
let (_, sys) = rx.recv().unwrap();
thread::sleep(time::Duration::from_millis(500));
assert!(net::TcpStream::connect(addr1).is_ok());
assert!(net::TcpStream::connect(addr2).is_ok());
assert!(net::TcpStream::connect(addr3).is_ok());
assert_eq!(num.load(Relaxed), 1);
sys.stop();
let _ = h.join();
}

View File

@@ -1,5 +1,97 @@
# Changes
## Unreleased - 2020-xx-xx
## 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
### Fixed
* Fixed unsoundness in .and_then()/.then() service combinators
## [1.0.4] - 2020-01-15
### Fixed
* Revert 1.0.3 change
## [1.0.3] - 2020-01-15
### Fixed
* Fixed unsoundness in `AndThenService` impl
## [1.0.2] - 2020-01-08
### Added
* Add `into_service` helper function
## [1.0.1] - 2019-12-22
### Changed
* `map_config()` and `unit_config()` accepts `IntoServiceFactory` type
## [1.0.0] - 2019-12-11
### Added
* Add Clone impl for Apply service
## [1.0.0-alpha.4] - 2019-12-08
### Changed
* Renamed `service_fn` to `fn_service`
* Renamed `factory_fn` to `fn_factory`
* Renamed `factory_fn_cfg` to `fn_factory_with_config`
## [1.0.0-alpha.3] - 2019-12-06
### Changed
* Add missing Clone impls
* Restore `Transform::map_init_err()` combinator
* Restore `Service/Factory::apply_fn()` in form of `Pipeline/Factory::and_then_apply_fn()`
* Optimize service combinators and futures memory layout
## [1.0.0-alpha.2] - 2019-12-02
### Changed
* Use owned config value for service factory
* Renamed BoxedNewService/BoxedService to BoxServiceFactory/BoxService
## [1.0.0-alpha.1] - 2019-11-25
### Changed
* Migraded to `std::future`
* `NewService` renamed to `ServiceFactory`
* Added `pipeline` and `pipeline_factory` function
## [0.4.2] - 2019-08-27
### Fixed

View File

@@ -1,31 +1,33 @@
[package]
name = "actix-service"
version = "1.0.0-alpha.1"
version = "1.0.6"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix Service"
keywords = ["network", "framework", "async", "futures"]
description = "Service trait and combinators for representing asynchronous request/response operations."
keywords = ["network", "framework", "async", "futures", "service"]
homepage = "https://actix.rs"
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"]
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
license = "MIT OR Apache-2.0"
edition = "2018"
workspace = ".."
[badges]
travis-ci = { repository = "actix/actix-service", branch = "master" }
appveyor = { repository = "actix/actix-net" }
codecov = { repository = "actix/actix-service", branch = "master", service = "github" }
[lib]
name = "actix_service"
path = "src/lib.rs"
[dependencies]
futures = "0.3.1"
pin-project = "0.4.5"
futures-util = "0.3.1"
pin-project = "0.4.17"
[dev-dependencies]
tokio = "0.2.0-alpha.6"
# actix-rt = "1.0.0-alpha.1"
actix-rt = "1.0.0"
criterion = "0.3"
[[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

@@ -0,0 +1,332 @@
use actix_service::boxed::BoxFuture;
use actix_service::IntoService;
use actix_service::Service;
/// Benchmark various implementations of and_then
use criterion::{criterion_main, Criterion};
use futures_util::future::join_all;
use futures_util::future::TryFutureExt;
use std::cell::{RefCell, UnsafeCell};
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
/*
* 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(project = StateProj)]
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>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
match this.state.as_mut().project() {
StateProj::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,
},
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
StateProj::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(project = StateRCProj)]
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>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
match this.state.as_mut().project() {
StateRCProj::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,
},
StateRCProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(StateRC::Empty);
r
}),
StateRCProj::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 });
// check that at least first request succeeded
start.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

@@ -0,0 +1,112 @@
use actix_service::Service;
use criterion::{criterion_main, Criterion};
use futures_util::future::join_all;
use futures_util::future::{ok, Ready};
use std::cell::{RefCell, UnsafeCell};
use std::rc::Rc;
use std::task::{Context, Poll};
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 });
// check that at least first request succeeded
start.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,18 +1,16 @@
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use super::{Service, ServiceFactory};
use crate::cell::Cell;
/// Service for the `and_then` combinator, chaining a computation onto the end
/// of another service which completes successfully.
///
/// This is created by the `ServiceExt::and_then` method.
pub struct AndThenService<A, B> {
a: A,
b: Cell<B>,
}
pub(crate) struct AndThenService<A, B>(Rc<RefCell<(A, B)>>);
impl<A, B> AndThenService<A, B> {
/// Create new `AndThen` combinator
@@ -21,19 +19,13 @@ impl<A, B> AndThenService<A, B> {
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
Self { a, b: Cell::new(b) }
Self(Rc::new(RefCell::new((a, b))))
}
}
impl<A, B> Clone for AndThenService<A, B>
where
A: Clone,
{
impl<A, B> Clone for AndThenService<A, B> {
fn clone(&self) -> Self {
AndThenService {
a: self.a.clone(),
b: self.b.clone(),
}
AndThenService(self.0.clone())
}
}
@@ -48,8 +40,9 @@ where
type Future = AndThenServiceResponse<A, B>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let not_ready = !self.a.poll_ready(cx)?.is_ready();
if !self.b.get_mut().poll_ready(cx)?.is_ready() || not_ready {
let mut srv = self.0.borrow_mut();
let not_ready = !srv.0.poll_ready(cx)?.is_ready();
if !srv.1.poll_ready(cx)?.is_ready() || not_ready {
Poll::Pending
} else {
Poll::Ready(Ok(()))
@@ -57,35 +50,31 @@ where
}
fn call(&mut self, req: A::Request) -> Self::Future {
AndThenServiceResponse::new(self.a.call(req), self.b.clone())
AndThenServiceResponse {
state: State::A(self.0.borrow_mut().0.call(req), Some(self.0.clone())),
}
}
}
#[pin_project::pin_project]
pub struct AndThenServiceResponse<A, B>
pub(crate) struct AndThenServiceResponse<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
b: Cell<B>,
#[pin]
fut_b: Option<B::Future>,
#[pin]
fut_a: Option<A::Future>,
state: State<A, B>,
}
impl<A, B> AndThenServiceResponse<A, B>
#[pin_project::pin_project(project = StateProj)]
enum State<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
fn new(a: A::Future, b: Cell<B>) -> Self {
AndThenServiceResponse {
b,
fut_a: Some(a),
fut_b: None,
}
}
A(#[pin] A::Future, Option<Rc<RefCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
impl<A, B> Future for AndThenServiceResponse<A, B>
@@ -98,42 +87,47 @@ where
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
loop {
if let Some(fut) = this.fut_b.as_pin_mut() {
return fut.poll(cx);
}
match this
.fut_a
.as_pin_mut()
.expect("Bug in actix-service")
.poll(cx)
{
Poll::Ready(Ok(resp)) => {
this = self.as_mut().project();
this.fut_a.set(None);
this.fut_b.set(Some(this.b.get_mut().call(resp)));
match this.state.as_mut().project() {
StateProj::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 = b.borrow_mut().1.call(res);
this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => return Poll::Pending,
Poll::Pending => Poll::Pending,
},
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
}
}
}
}
/// `.and_then()` service factory combinator
pub struct AndThenServiceFactory<A, B>
pub(crate) struct AndThenServiceFactory<A, B>
where
A: ServiceFactory,
B: ServiceFactory,
A::Config: Clone,
B: ServiceFactory<
Config = A::Config,
Request = A::Response,
Error = A::Error,
InitError = A::InitError,
>,
{
a: A,
b: B,
inner: Rc<(A, B)>,
}
impl<A, B> AndThenServiceFactory<A, B>
where
A: ServiceFactory,
A::Config: Clone,
B: ServiceFactory<
Config = A::Config,
Request = A::Response,
@@ -142,14 +136,17 @@ where
>,
{
/// Create new `AndThenFactory` combinator
pub fn new(a: A, b: B) -> Self {
Self { a, b }
pub(crate) fn new(a: A, b: B) -> Self {
Self {
inner: Rc::new((a, b)),
}
}
}
impl<A, B> ServiceFactory for AndThenServiceFactory<A, B>
where
A: ServiceFactory,
A::Config: Clone,
B: ServiceFactory<
Config = A::Config,
Request = A::Response,
@@ -166,34 +163,43 @@ where
type InitError = A::InitError;
type Future = AndThenServiceFactoryResponse<A, B>;
fn new_service(&self, cfg: &A::Config) -> Self::Future {
AndThenServiceFactoryResponse::new(self.a.new_service(cfg), self.b.new_service(cfg))
fn new_service(&self, cfg: A::Config) -> Self::Future {
let inner = &*self.inner;
AndThenServiceFactoryResponse::new(
inner.0.new_service(cfg.clone()),
inner.1.new_service(cfg),
)
}
}
impl<A, B> Clone for AndThenServiceFactory<A, B>
where
A: ServiceFactory + Clone,
B: ServiceFactory + Clone,
A: ServiceFactory,
A::Config: Clone,
B: ServiceFactory<
Config = A::Config,
Request = A::Response,
Error = A::Error,
InitError = A::InitError,
>,
{
fn clone(&self) -> Self {
Self {
a: self.a.clone(),
b: self.b.clone(),
inner: self.inner.clone(),
}
}
}
#[pin_project::pin_project]
pub struct AndThenServiceFactoryResponse<A, B>
pub(crate) struct AndThenServiceFactoryResponse<A, B>
where
A: ServiceFactory,
B: ServiceFactory<Request = A::Response>,
{
#[pin]
fut_b: B::Future,
#[pin]
fut_a: A::Future,
#[pin]
fut_b: B::Future,
a: Option<A::Service>,
b: Option<B::Service>,
@@ -251,9 +257,9 @@ mod tests {
use std::rc::Rc;
use std::task::{Context, Poll};
use futures::future::{lazy, ok, ready, Ready};
use futures_util::future::{lazy, ok, ready, Ready};
use crate::{factory_fn, pipeline, pipeline_factory, Service, ServiceFactory};
use crate::{fn_factory, pipeline, pipeline_factory, Service, ServiceFactory};
struct Srv1(Rc<Cell<usize>>);
@@ -282,7 +288,7 @@ mod tests {
type Error = ();
type Future = Ready<Result<Self::Response, ()>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.set(self.0.get() + 1);
Poll::Ready(Ok(()))
}
@@ -292,7 +298,7 @@ mod tests {
}
}
#[tokio::test]
#[actix_rt::test]
async fn test_poll_ready() {
let cnt = Rc::new(Cell::new(0));
let mut srv = pipeline(Srv1(cnt.clone())).and_then(Srv2(cnt.clone()));
@@ -301,24 +307,24 @@ mod tests {
assert_eq!(cnt.get(), 2);
}
#[tokio::test]
#[actix_rt::test]
async fn test_call() {
let cnt = Rc::new(Cell::new(0));
let mut srv = pipeline(Srv1(cnt.clone())).and_then(Srv2(cnt));
let res = srv.call("srv1").await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), (("srv1", "srv2")));
assert_eq!(res.unwrap(), ("srv1", "srv2"));
}
#[tokio::test]
#[actix_rt::test]
async fn test_new_service() {
let cnt = Rc::new(Cell::new(0));
let cnt2 = cnt.clone();
let new_srv =
pipeline_factory(factory_fn(move || ready(Ok::<_, ()>(Srv1(cnt2.clone())))))
pipeline_factory(fn_factory(move || ready(Ok::<_, ()>(Srv1(cnt2.clone())))))
.and_then(move || ready(Ok(Srv2(cnt.clone()))));
let mut srv = new_srv.new_service(&()).await.unwrap();
let mut srv = new_srv.new_service(()).await.unwrap();
let res = srv.call("srv1").await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), ("srv1", "srv2"));

View File

@@ -0,0 +1,326 @@
use std::cell::RefCell;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
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: Rc<RefCell<(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: Rc::new(RefCell::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 mut inner = self.srv.borrow_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.borrow_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(project = StateProj)]
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<Rc<RefCell<(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>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
match this.state.as_mut().project() {
StateProj::A(fut, b) => match fut.poll(cx)? {
Poll::Ready(res) => {
let b = b.take().unwrap();
this.state.set(State::Empty);
let (_, b, f) = &mut *b.borrow_mut();
let fut = f(res, b);
this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
StateProj::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: Rc::new(RefCell::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

@@ -5,7 +5,7 @@ use std::task::{Context, Poll};
use super::{IntoService, IntoServiceFactory, Service, ServiceFactory};
/// Apply tranform function to a service
/// Apply tranform 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>
where
T: Service<Error = Err>,
@@ -16,7 +16,7 @@ where
Apply::new(service.into_service(), f)
}
/// Create factory for `apply` service.
/// Service factory that prodices `apply_fn` service.
pub fn apply_fn_factory<T, F, R, In, Out, Err, U>(
service: U,
f: F,
@@ -56,6 +56,21 @@ where
}
}
impl<T, F, R, In, Out, Err> Clone for Apply<T, F, R, In, Out, Err>
where
T: Service<Error = Err> + Clone,
F: FnMut(In, &mut T) -> R + Clone,
R: Future<Output = Result<Out, Err>>,
{
fn clone(&self) -> Self {
Apply {
service: self.service.clone(),
f: self.f.clone(),
r: PhantomData,
}
}
}
impl<T, F, R, In, Out, Err> Service for Apply<T, F, R, In, Out, Err>
where
T: Service<Error = Err>,
@@ -67,8 +82,8 @@ where
type Error = Err;
type Future = R;
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(futures::ready!(self.service.poll_ready(ctx)))
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(futures_util::ready!(self.service.poll_ready(cx)))
}
fn call(&mut self, req: In) -> Self::Future {
@@ -80,6 +95,8 @@ where
pub struct ApplyServiceFactory<T, F, R, In, Out, Err>
where
T: ServiceFactory<Error = Err>,
F: FnMut(In, &mut T::Service) -> R + Clone,
R: Future<Output = Result<Out, Err>>,
{
service: T,
f: F,
@@ -102,6 +119,21 @@ where
}
}
impl<T, F, R, In, Out, Err> Clone for ApplyServiceFactory<T, F, R, In, Out, Err>
where
T: ServiceFactory<Error = Err> + Clone,
F: FnMut(In, &mut T::Service) -> R + Clone,
R: Future<Output = Result<Out, Err>>,
{
fn clone(&self) -> Self {
Self {
service: self.service.clone(),
f: self.f.clone(),
r: PhantomData,
}
}
}
impl<T, F, R, In, Out, Err> ServiceFactory for ApplyServiceFactory<T, F, R, In, Out, Err>
where
T: ServiceFactory<Error = Err>,
@@ -117,7 +149,7 @@ where
type InitError = T::InitError;
type Future = ApplyServiceFactoryResponse<T, F, R, In, Out, Err>;
fn new_service(&self, cfg: &T::Config) -> Self::Future {
fn new_service(&self, cfg: T::Config) -> Self::Future {
ApplyServiceFactoryResponse::new(self.service.new_service(cfg), self.f.clone())
}
}
@@ -126,7 +158,7 @@ where
pub struct ApplyServiceFactoryResponse<T, F, R, In, Out, Err>
where
T: ServiceFactory<Error = Err>,
F: FnMut(In, &mut T::Service) -> R + Clone,
F: FnMut(In, &mut T::Service) -> R,
R: Future<Output = Result<Out, Err>>,
{
#[pin]
@@ -138,7 +170,7 @@ where
impl<T, F, R, In, Out, Err> ApplyServiceFactoryResponse<T, F, R, In, Out, Err>
where
T: ServiceFactory<Error = Err>,
F: FnMut(In, &mut T::Service) -> R + Clone,
F: FnMut(In, &mut T::Service) -> R,
R: Future<Output = Result<Out, Err>>,
{
fn new(fut: T::Future, f: F) -> Self {
@@ -153,7 +185,7 @@ where
impl<T, F, R, In, Out, Err> Future for ApplyServiceFactoryResponse<T, F, R, In, Out, Err>
where
T: ServiceFactory<Error = Err>,
F: FnMut(In, &mut T::Service) -> R + Clone,
F: FnMut(In, &mut T::Service) -> R,
R: Future<Output = Result<Out, Err>>,
{
type Output = Result<Apply<T::Service, F, R, In, Out, Err>, T::InitError>;
@@ -173,7 +205,7 @@ where
mod tests {
use std::task::{Context, Poll};
use futures::future::{lazy, ok, Ready};
use futures_util::future::{lazy, ok, Ready};
use super::*;
use crate::{pipeline, pipeline_factory, Service, ServiceFactory};
@@ -187,7 +219,7 @@ mod tests {
type Error = ();
type Future = Ready<Result<(), ()>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
@@ -196,13 +228,13 @@ mod tests {
}
}
#[tokio::test]
#[actix_rt::test]
async fn test_call() {
let mut srv = pipeline(apply_fn(Srv, |req: &'static str, srv| {
let fut = srv.call(());
async move {
let res = fut.await.unwrap();
Ok((req, res))
fut.await.unwrap();
Ok((req, ()))
}
}));
@@ -210,28 +242,28 @@ mod tests {
let res = srv.call("srv").await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), (("srv", ())));
assert_eq!(res.unwrap(), ("srv", ()));
}
#[tokio::test]
#[actix_rt::test]
async fn test_new_service() {
let new_srv = pipeline_factory(apply_fn_factory(
|| ok::<_, ()>(Srv),
|req: &'static str, srv| {
let fut = srv.call(());
async move {
let res = fut.await.unwrap();
Ok((req, res))
fut.await.unwrap();
Ok((req, ()))
}
},
));
let mut srv = new_srv.new_service(&()).await.unwrap();
let mut srv = new_srv.new_service(()).await.unwrap();
assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
let res = srv.call("srv").await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), (("srv", ())));
assert_eq!(res.unwrap(), ("srv", ()));
}
}

View File

@@ -1,70 +1,85 @@
use std::cell::RefCell;
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};
/// Convert `Fn(&Config, &mut Service) -> Future<Service>` fn to a NewService
pub fn apply_cfg<F, C, T, R, S, E>(srv: T, f: F) -> ApplyConfigService<F, C, T, R, S, E>
/// Convert `Fn(Config, &mut Service1) -> Future<Service2>` fn to a service factory
pub fn apply_cfg<F, C, T, R, S, E>(
srv: T,
f: F,
) -> impl ServiceFactory<
Config = C,
Request = S::Request,
Response = S::Response,
Error = S::Error,
Service = S,
InitError = E,
Future = R,
> + Clone
where
F: FnMut(&C, &mut T) -> R,
F: FnMut(C, &mut T) -> R,
T: Service,
R: Future<Output = Result<S, E>>,
S: Service,
{
ApplyConfigService {
f: Cell::new(f),
srv: Cell::new(srv),
srv: Rc::new(RefCell::new((srv, f))),
_t: PhantomData,
}
}
/// Convert `Fn(&Config, &mut Service) -> Future<Service>` fn to a NewService
/// Service get constructor from NewService.
/// Convert `Fn(Config, &mut Service1) -> Future<Service2>` fn to a service factory
///
/// Service1 get constructed from `T` factory.
pub fn apply_cfg_factory<F, C, T, R, S>(
srv: T,
factory: T,
f: F,
) -> ApplyConfigServiceFactory<F, C, T, R, S>
) -> impl ServiceFactory<
Config = C,
Request = S::Request,
Response = S::Response,
Error = S::Error,
Service = S,
InitError = T::InitError,
> + Clone
where
C: Clone,
F: FnMut(&C, &mut T::Service) -> R,
F: FnMut(C, &mut T::Service) -> R,
T: ServiceFactory<Config = ()>,
T::InitError: From<T::Error>,
R: Future<Output = Result<S, T::InitError>>,
S: Service,
{
ApplyConfigServiceFactory {
f: Cell::new(f),
srv: Cell::new(srv),
srv: Rc::new(RefCell::new((factory, f))),
_t: PhantomData,
}
}
/// Convert `Fn(&Config) -> Future<Service>` fn to NewService\
pub struct ApplyConfigService<F, C, T, R, S, E>
/// Convert `Fn(Config, &mut Server) -> Future<Service>` fn to NewService\
struct ApplyConfigService<F, C, T, R, S, E>
where
F: FnMut(&C, &mut T) -> R,
F: FnMut(C, &mut T) -> R,
T: Service,
R: Future<Output = Result<S, E>>,
S: Service,
{
f: Cell<F>,
srv: Cell<T>,
srv: Rc<RefCell<(T, F)>>,
_t: PhantomData<(C, R, S)>,
}
impl<F, C, T, R, S, E> Clone for ApplyConfigService<F, C, T, R, S, E>
where
F: FnMut(&C, &mut T) -> R,
F: FnMut(C, &mut T) -> R,
T: Service,
R: Future<Output = Result<S, E>>,
S: Service,
{
fn clone(&self) -> Self {
ApplyConfigService {
f: self.f.clone(),
srv: self.srv.clone(),
_t: PhantomData,
}
@@ -73,7 +88,7 @@ where
impl<F, C, T, R, S, E> ServiceFactory for ApplyConfigService<F, C, T, R, S, E>
where
F: FnMut(&C, &mut T) -> R,
F: FnMut(C, &mut T) -> R,
T: Service,
R: Future<Output = Result<S, E>>,
S: Service,
@@ -87,36 +102,33 @@ where
type InitError = E;
type Future = R;
fn new_service(&self, cfg: &C) -> Self::Future {
unsafe { (self.f.get_mut_unsafe())(cfg, self.srv.get_mut_unsafe()) }
fn new_service(&self, cfg: C) -> Self::Future {
let (t, f) = &mut *self.srv.borrow_mut();
f(cfg, t)
}
}
/// Convert `Fn(&Config) -> Future<Service>` fn to NewService
pub struct ApplyConfigServiceFactory<F, C, T, R, S>
struct ApplyConfigServiceFactory<F, C, T, R, S>
where
C: Clone,
F: FnMut(&C, &mut T::Service) -> R,
F: FnMut(C, &mut T::Service) -> R,
T: ServiceFactory<Config = ()>,
R: Future<Output = Result<S, T::InitError>>,
S: Service,
{
f: Cell<F>,
srv: Cell<T>,
srv: Rc<RefCell<(T, F)>>,
_t: PhantomData<(C, R, S)>,
}
impl<F, C, T, R, S> Clone for ApplyConfigServiceFactory<F, C, T, R, S>
where
C: Clone,
F: FnMut(&C, &mut T::Service) -> R,
F: FnMut(C, &mut T::Service) -> R,
T: ServiceFactory<Config = ()>,
R: Future<Output = Result<S, T::InitError>>,
S: Service,
{
fn clone(&self) -> Self {
Self {
f: self.f.clone(),
srv: self.srv.clone(),
_t: PhantomData,
}
@@ -125,8 +137,7 @@ where
impl<F, C, T, R, S> ServiceFactory for ApplyConfigServiceFactory<F, C, T, R, S>
where
C: Clone,
F: FnMut(&C, &mut T::Service) -> R,
F: FnMut(C, &mut T::Service) -> R,
T: ServiceFactory<Config = ()>,
T::InitError: From<T::Error>,
R: Future<Output = Result<S, T::InitError>>,
@@ -141,42 +152,46 @@ where
type InitError = T::InitError;
type Future = ApplyConfigServiceFactoryResponse<F, C, T, R, S>;
fn new_service(&self, cfg: &C) -> Self::Future {
fn new_service(&self, cfg: C) -> Self::Future {
ApplyConfigServiceFactoryResponse {
f: self.f.clone(),
cfg: cfg.clone(),
fut: None,
srv: None,
srv_fut: Some(self.srv.get_ref().new_service(&())),
_t: PhantomData,
cfg: Some(cfg),
store: self.srv.clone(),
state: State::A(self.srv.borrow().0.new_service(())),
}
}
}
#[pin_project::pin_project]
pub struct ApplyConfigServiceFactoryResponse<F, C, T, R, S>
struct ApplyConfigServiceFactoryResponse<F, C, T, R, S>
where
C: Clone,
F: FnMut(&C, &mut T::Service) -> R,
F: FnMut(C, &mut T::Service) -> R,
T: ServiceFactory<Config = ()>,
T::InitError: From<T::Error>,
R: Future<Output = Result<S, T::InitError>>,
S: Service,
{
cfg: C,
f: Cell<F>,
srv: Option<T::Service>,
cfg: Option<C>,
store: Rc<RefCell<(T, F)>>,
#[pin]
srv_fut: Option<T::Future>,
#[pin]
fut: Option<R>,
_t: PhantomData<(S,)>,
state: State<T, R, S>,
}
#[pin_project::pin_project(project = StateProj)]
enum State<T, R, S>
where
T: ServiceFactory<Config = ()>,
T::InitError: From<T::Error>,
R: Future<Output = Result<S, T::InitError>>,
S: Service,
{
A(#[pin] T::Future),
B(T::Service),
C(#[pin] R),
}
impl<F, C, T, R, S> Future for ApplyConfigServiceFactoryResponse<F, C, T, R, S>
where
C: Clone,
F: FnMut(&C, &mut T::Service) -> R,
F: FnMut(C, &mut T::Service) -> R,
T: ServiceFactory<Config = ()>,
T::InitError: From<T::Error>,
R: Future<Output = Result<S, T::InitError>>,
@@ -187,34 +202,26 @@ where
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
loop {
if let Some(fut) = this.srv_fut.as_pin_mut() {
match fut.poll(cx)? {
Poll::Pending => return Poll::Pending,
Poll::Ready(srv) => {
this = self.as_mut().project();
this.srv_fut.set(None);
*this.srv = Some(srv);
continue;
}
match this.state.as_mut().project() {
StateProj::A(fut) => match fut.poll(cx)? {
Poll::Pending => Poll::Pending,
Poll::Ready(srv) => {
this.state.set(State::B(srv));
self.poll(cx)
}
}
if let Some(fut) = this.fut.as_pin_mut() {
return fut.poll(cx);
} else if let Some(srv) = this.srv {
match srv.poll_ready(cx)? {
Poll::Ready(_) => {
let fut = this.f.get_mut()(&this.cfg, srv);
this = self.as_mut().project();
this.fut.set(Some(fut));
continue;
},
StateProj::B(srv) => match srv.poll_ready(cx)? {
Poll::Ready(_) => {
{
let (_, f) = &mut *this.store.borrow_mut();
let fut = f(this.cfg.take().unwrap(), srv);
this.state.set(State::C(fut));
}
Poll::Pending => return Poll::Pending,
self.poll(cx)
}
} else {
return Poll::Pending;
}
Poll::Pending => Poll::Pending,
},
StateProj::C(fut) => fut.poll(cx),
}
}
}

View File

@@ -1,26 +1,22 @@
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures::future::{FutureExt, LocalBoxFuture};
use futures_util::future::FutureExt;
use crate::{Service, ServiceFactory};
pub type BoxedService<Req, Res, Err> = Box<
dyn Service<
Request = Req,
Response = Res,
Error = Err,
Future = BoxedServiceResponse<Res, Err>,
>,
>;
pub type BoxFuture<I, E> = Pin<Box<dyn Future<Output = Result<I, E>>>>;
pub type BoxedServiceResponse<Res, Err> = LocalBoxFuture<'static, Result<Res, Err>>;
pub type BoxService<Req, Res, Err> =
Box<dyn Service<Request = Req, Response = Res, Error = Err, Future = BoxFuture<Res, Err>>>;
pub struct BoxedNewService<C, Req, Res, Err, InitErr>(Inner<C, Req, Res, Err, InitErr>);
pub struct BoxServiceFactory<C, Req, Res, Err, InitErr>(Inner<C, Req, Res, Err, InitErr>);
/// Create boxed new service
/// Create boxed service factory
pub fn factory<T>(
factory: T,
) -> BoxedNewService<T::Config, T::Request, T::Response, T::Error, T::InitError>
) -> BoxServiceFactory<T::Config, T::Request, T::Response, T::Error, T::InitError>
where
T: ServiceFactory + 'static,
T::Request: 'static,
@@ -30,14 +26,14 @@ where
T::Error: 'static,
T::InitError: 'static,
{
BoxedNewService(Box::new(FactoryWrapper {
BoxServiceFactory(Box::new(FactoryWrapper {
factory,
_t: std::marker::PhantomData,
}))
}
/// Create boxed service
pub fn service<T>(service: T) -> BoxedService<T::Request, T::Response, T::Error>
pub fn service<T>(service: T) -> BoxService<T::Request, T::Response, T::Error>
where
T: Service + 'static,
T::Future: 'static,
@@ -52,12 +48,12 @@ type Inner<C, Req, Res, Err, InitErr> = Box<
Response = Res,
Error = Err,
InitError = InitErr,
Service = BoxedService<Req, Res, Err>,
Future = LocalBoxFuture<'static, Result<BoxedService<Req, Res, Err>, InitErr>>,
Service = BoxService<Req, Res, Err>,
Future = BoxFuture<BoxService<Req, Res, Err>, InitErr>,
>,
>;
impl<C, Req, Res, Err, InitErr> ServiceFactory for BoxedNewService<C, Req, Res, Err, InitErr>
impl<C, Req, Res, Err, InitErr> ServiceFactory for BoxServiceFactory<C, Req, Res, Err, InitErr>
where
Req: 'static,
Res: 'static,
@@ -69,11 +65,11 @@ where
type Error = Err;
type InitError = InitErr;
type Config = C;
type Service = BoxedService<Req, Res, Err>;
type Service = BoxService<Req, Res, Err>;
type Future = LocalBoxFuture<'static, Result<Self::Service, InitErr>>;
type Future = BoxFuture<Self::Service, InitErr>;
fn new_service(&self, cfg: &C) -> Self::Future {
fn new_service(&self, cfg: C) -> Self::Future {
self.0.new_service(cfg)
}
}
@@ -105,14 +101,15 @@ where
type Error = Err;
type InitError = InitErr;
type Config = C;
type Service = BoxedService<Req, Res, Err>;
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
type Service = BoxService<Req, Res, Err>;
type Future = BoxFuture<Self::Service, Self::InitError>;
fn new_service(&self, cfg: &C) -> Self::Future {
self.factory
.new_service(cfg)
.map(|res| res.map(ServiceWrapper::boxed))
.boxed_local()
fn new_service(&self, cfg: C) -> Self::Future {
Box::pin(
self.factory
.new_service(cfg)
.map(|res| res.map(ServiceWrapper::boxed)),
)
}
}
@@ -123,7 +120,7 @@ where
T: Service + 'static,
T::Future: 'static,
{
fn boxed(service: T) -> BoxedService<T::Request, T::Response, T::Error> {
fn boxed(service: T) -> BoxService<T::Request, T::Response, T::Error> {
Box::new(ServiceWrapper(service))
}
}
@@ -136,13 +133,13 @@ where
type Request = Req;
type Response = Res;
type Error = Err;
type Future = LocalBoxFuture<'static, Result<Res, Err>>;
type Future = BoxFuture<Res, Err>;
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(ctx)
}
fn call(&mut self, req: Self::Request) -> Self::Future {
self.0.call(req).boxed_local()
Box::pin(self.0.call(req))
}
}

View File

@@ -1,41 +0,0 @@
//! Custom cell impl, internal use only
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()
}
}

View File

@@ -2,12 +2,12 @@ use std::future::Future;
use std::marker::PhantomData;
use std::task::{Context, Poll};
use futures::future::{ok, Ready};
use futures_util::future::{ok, Ready};
use crate::{IntoService, IntoServiceFactory, Service, ServiceFactory};
/// Create `ServiceFactory` for function that can act as a `Service`
pub fn service_fn<F, Fut, Req, Res, Err, Cfg>(
pub fn fn_service<F, Fut, Req, Res, Err, Cfg>(
f: F,
) -> FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
where
@@ -17,16 +17,43 @@ where
FnServiceFactory::new(f)
}
pub fn service_fn2<F, Fut, Req, Res, Err>(f: F) -> FnService<F, Fut, Req, Res, Err>
where
F: FnMut(Req) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
{
FnService::new(f)
}
/// Create `ServiceFactory` for function that can produce services
pub fn factory_fn<F, Cfg, Srv, Fut, Err>(f: F) -> FnServiceNoConfig<F, Cfg, Srv, Fut, Err>
///
/// # Example
///
/// ```rust
/// use std::io;
/// use actix_service::{fn_factory, fn_service, Service, ServiceFactory};
/// use futures_util::future::ok;
///
/// /// Service that divides two usize values.
/// async fn div((x, y): (usize, usize)) -> Result<usize, io::Error> {
/// if y == 0 {
/// Err(io::Error::new(io::ErrorKind::Other, "divide by zero"))
/// } else {
/// Ok(x / y)
/// }
/// }
///
/// #[actix_rt::main]
/// async fn main() -> io::Result<()> {
/// // Create service factory that produces `div` services
/// let factory = fn_factory(|| {
/// ok::<_, io::Error>(fn_service(div))
/// });
///
/// // construct new service
/// let mut srv = factory.new_service(()).await?;
///
/// // now we can use `div` service
/// let result = srv.call((10, 20)).await?;
///
/// println!("10 / 20 = {}", result);
///
/// Ok(())
/// }
/// ```
pub fn fn_factory<F, Cfg, Srv, Fut, Err>(f: F) -> FnServiceNoConfig<F, Cfg, Srv, Fut, Err>
where
Srv: Service,
F: Fn() -> Fut,
@@ -35,10 +62,41 @@ where
FnServiceNoConfig::new(f)
}
/// Create `ServiceFactory` for function that can produce services with configuration
pub fn factory_fn_cfg<F, Fut, Cfg, Srv, Err>(f: F) -> FnServiceConfig<F, Fut, Cfg, Srv, Err>
/// Create `ServiceFactory` for function that accepts config argument and can produce services
///
/// Any function that has following form `Fn(Config) -> Future<Output = Service>` could
/// act as a `ServiceFactory`.
///
/// # Example
///
/// ```rust
/// use std::io;
/// use actix_service::{fn_factory_with_config, fn_service, Service, ServiceFactory};
/// use futures_util::future::ok;
///
/// #[actix_rt::main]
/// async fn main() -> io::Result<()> {
/// // Create service factory. factory uses config argument for
/// // services it generates.
/// let factory = fn_factory_with_config(|y: usize| {
/// ok::<_, io::Error>(fn_service(move |x: usize| ok::<_, io::Error>(x * y)))
/// });
///
/// // construct new service with config argument
/// let mut srv = factory.new_service(10).await?;
///
/// let result = srv.call(10).await?;
/// assert_eq!(result, 100);
///
/// println!("10 * 10 = {}", result);
/// Ok(())
/// }
/// ```
pub fn fn_factory_with_config<F, Fut, Cfg, Srv, Err>(
f: F,
) -> FnServiceConfig<F, Fut, Cfg, Srv, Err>
where
F: Fn(&Cfg) -> Fut,
F: Fn(Cfg) -> Fut,
Fut: Future<Output = Result<Srv, Err>>,
Srv: Service,
{
@@ -132,6 +190,25 @@ where
}
}
impl<F, Fut, Req, Res, Err> Service for FnServiceFactory<F, Fut, Req, Res, Err, ()>
where
F: FnMut(Req) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>,
{
type Request = Req;
type Response = Res;
type Error = Err;
type Future = Fut;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Self::Request) -> Self::Future {
(self.f)(req)
}
}
impl<F, Fut, Req, Res, Err, Cfg> ServiceFactory for FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
where
F: FnMut(Req) -> Fut + Clone,
@@ -146,7 +223,7 @@ where
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &Cfg) -> Self::Future {
fn new_service(&self, _: Cfg) -> Self::Future {
ok(FnService::new(self.f.clone()))
}
}
@@ -165,7 +242,7 @@ where
/// Convert `Fn(&Config) -> Future<Service>` fn to NewService
pub struct FnServiceConfig<F, Fut, Cfg, Srv, Err>
where
F: Fn(&Cfg) -> Fut,
F: Fn(Cfg) -> Fut,
Fut: Future<Output = Result<Srv, Err>>,
Srv: Service,
{
@@ -175,7 +252,7 @@ where
impl<F, Fut, Cfg, Srv, Err> FnServiceConfig<F, Fut, Cfg, Srv, Err>
where
F: Fn(&Cfg) -> Fut,
F: Fn(Cfg) -> Fut,
Fut: Future<Output = Result<Srv, Err>>,
Srv: Service,
{
@@ -184,9 +261,23 @@ where
}
}
impl<F, Fut, Cfg, Srv, Err> Clone for FnServiceConfig<F, Fut, Cfg, Srv, Err>
where
F: Fn(Cfg) -> Fut + Clone,
Fut: Future<Output = Result<Srv, Err>>,
Srv: Service,
{
fn clone(&self) -> Self {
FnServiceConfig {
f: self.f.clone(),
_t: PhantomData,
}
}
}
impl<F, Fut, Cfg, Srv, Err> ServiceFactory for FnServiceConfig<F, Fut, Cfg, Srv, Err>
where
F: Fn(&Cfg) -> Fut,
F: Fn(Cfg) -> Fut,
Fut: Future<Output = Result<Srv, Err>>,
Srv: Service,
{
@@ -199,7 +290,7 @@ where
type InitError = Err;
type Future = Fut;
fn new_service(&self, cfg: &Cfg) -> Self::Future {
fn new_service(&self, cfg: Cfg) -> Self::Future {
(self.f)(cfg)
}
}
@@ -240,7 +331,7 @@ where
type InitError = E;
type Future = R;
fn new_service(&self, _: &C) -> Self::Future {
fn new_service(&self, _: C) -> Self::Future {
(self.f)()
}
}
@@ -266,3 +357,47 @@ where
FnServiceNoConfig::new(self)
}
}
#[cfg(test)]
mod tests {
use std::task::Poll;
use futures_util::future::{lazy, ok};
use super::*;
use crate::{Service, ServiceFactory};
#[actix_rt::test]
async fn test_fn_service() {
let new_srv = fn_service(|()| ok::<_, ()>("srv"));
let mut srv = new_srv.new_service(()).await.unwrap();
let res = srv.call(()).await;
assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
assert!(res.is_ok());
assert_eq!(res.unwrap(), "srv");
}
#[actix_rt::test]
async fn test_fn_service_service() {
let mut srv = fn_service(|()| ok::<_, ()>("srv"));
let res = srv.call(()).await;
assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
assert!(res.is_ok());
assert_eq!(res.unwrap(), "srv");
}
#[actix_rt::test]
async fn test_fn_service_with_config() {
let new_srv = fn_factory_with_config(|cfg: usize| {
ok::<_, ()>(fn_service(move |()| ok::<_, ()>(("srv", cfg))))
});
let mut srv = new_srv.new_service(1).await.unwrap();
let res = srv.call(()).await;
assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
assert!(res.is_ok());
assert_eq!(res.unwrap(), ("srv", 1));
}
}

View File

@@ -1,3 +1,8 @@
//! See [`Service`](trait.Service.html) docs for information on this crate's foundational trait.
#![deny(rust_2018_idioms, warnings)]
#![allow(clippy::type_complexity)]
use std::cell::RefCell;
use std::future::Future;
use std::rc::Rc;
@@ -5,10 +10,10 @@ use std::sync::Arc;
use std::task::{self, Context, Poll};
mod and_then;
mod and_then_apply_fn;
mod apply;
mod apply_cfg;
pub mod boxed;
mod cell;
mod fn_service;
mod map;
mod map_config;
@@ -17,15 +22,54 @@ mod map_init_err;
mod pipeline;
mod then;
mod transform;
mod transform_err;
pub use self::apply::{apply_fn, apply_fn_factory};
pub use self::apply_cfg::{apply_cfg, apply_cfg_factory};
pub use self::fn_service::{factory_fn, factory_fn_cfg, service_fn, service_fn2};
pub use self::map_config::{map_config, unit_config, MappedConfig};
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::pipeline::{pipeline, pipeline_factory, Pipeline, PipelineFactory};
pub use self::transform::{apply, Transform};
/// An asynchronous function from `Request` to a `Response`.
/// An asynchronous operation from `Request` to a `Response`.
///
/// The `Service` trait models a request/response interaction, receiving requests and returning
/// replies. You can think about a service as a function with one argument that returns some result
/// asynchronously. Conceptually, the operation looks like this:
///
/// ```rust,ignore
/// async fn(Request) -> Result<Response, Err>
/// ```
///
/// The `Service` trait just generalizes this form where each parameter is described as an
/// 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
/// struct MyService;
///
/// impl Service for MyService {
/// type Request = u8;
/// type Response = u64;
/// type Error = MyError;
/// type Future = Pin<Box<Future<Output=Result<Self::Response, Self::Error>>>>;
///
/// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { ... }
///
/// fn call(&mut self, req: Self::Request) -> Self::Future { ... }
/// }
/// ```
///
/// Sometimes it is not necessary to implement the Service trait. For example, the above service
/// could be rewritten as a simple function and passed to [fn_service](fn.fn_service.html).
///
/// ```rust,ignore
/// async fn my_service(req: u8) -> Result<u64, MyError>;
/// ```
pub trait Service {
/// Requests handled by the service.
type Request;
@@ -41,13 +85,17 @@ pub trait Service {
/// Returns `Ready` when the service is able to process requests.
///
/// If the service is at capacity, then `NotReady` is returned and the task
/// If the service is at capacity, then `Pending` is returned and the task
/// is notified when the service becomes ready again. This function is
/// expected to be called while on a task.
///
/// This is a **best effort** implementation. False positives are permitted.
/// It is permitted for the service to return `Ready` from a `poll_ready`
/// call and the next invocation of `call` results in an error.
///
/// # Notes
/// 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.
fn poll_ready(&mut self, ctx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>;
/// Process the request and return the response asynchronously.
@@ -81,7 +129,7 @@ pub trait Service {
/// 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
/// 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
@@ -95,43 +143,43 @@ pub trait Service {
}
}
/// Creates new `Service` values.
/// Factory for creating `Service`s.
///
/// Acts as a service factory. This is useful for cases where new `Service`
/// values must be produced. One case is a TCP server listener. The listener
/// accepts new TCP streams, obtains a new `Service` value using the
/// `ServiceFactory` trait, and uses that new `Service` value to process inbound
/// Acts as a service factory. This is useful for cases where new `Service`s
/// must be produced. One case is a TCP server listener. The listener
/// accepts new TCP streams, obtains a new `Service` using the
/// `ServiceFactory` trait, and uses the new `Service` to process inbound
/// requests on that new TCP stream.
///
/// `Config` is a service factory configuration type.
pub trait ServiceFactory {
/// Requests handled by the service.
/// Requests handled by the created services.
type Request;
/// Responses given by the service
/// Responses given by the created services.
type Response;
/// Errors produced by the service
/// Errors produced by the created services.
type Error;
/// Service factory configuration
/// Service factory configuration.
type Config;
/// The `Service` value created by this factory
/// The kind of `Service` created by this factory.
type Service: Service<
Request = Self::Request,
Response = Self::Response,
Error = Self::Error,
>;
/// Errors produced while building a service.
/// Errors potentially raised while building a service.
type InitError;
/// The future of the `Service` instance.
type Future: Future<Output = Result<Self::Service, Self::InitError>>;
/// Create and return a new service value asynchronously.
fn new_service(&self, cfg: &Self::Config) -> Self::Future;
/// Create and return a new service asynchronously.
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.
@@ -246,7 +294,7 @@ where
type InitError = S::InitError;
type Future = S::Future;
fn new_service(&self, cfg: &S::Config) -> S::Future {
fn new_service(&self, cfg: S::Config) -> S::Future {
self.as_ref().new_service(cfg)
}
}
@@ -263,7 +311,7 @@ where
type InitError = S::InitError;
type Future = S::Future;
fn new_service(&self, cfg: &S::Config) -> S::Future {
fn new_service(&self, cfg: S::Config) -> S::Future {
self.as_ref().new_service(cfg)
}
}
@@ -282,7 +330,7 @@ pub trait IntoServiceFactory<T>
where
T: ServiceFactory,
{
/// Convert `Self` an `ServiceFactory`
/// Convert `Self` to a `ServiceFactory`
fn into_factory(self) -> T;
}
@@ -304,10 +352,17 @@ where
}
}
/// Convert object of type `T` to a service `S`
pub fn into_service<T, S>(tp: T) -> S
where
S: Service,
T: IntoService<S>,
{
tp.into_service()
}
pub mod dev {
pub use crate::and_then::{AndThenService, AndThenServiceFactory};
pub use crate::apply::{Apply, ApplyServiceFactory};
pub use crate::apply_cfg::{ApplyConfigService, ApplyConfigServiceFactory};
pub use crate::fn_service::{
FnService, FnServiceConfig, FnServiceFactory, FnServiceNoConfig,
};
@@ -315,6 +370,6 @@ pub mod dev {
pub use crate::map_config::{MapConfig, UnitConfig};
pub use crate::map_err::{MapErr, MapErrServiceFactory};
pub use crate::map_init_err::MapInitErr;
pub use crate::then::{ThenService, ThenServiceFactory};
pub use crate::transform::ApplyTransform;
pub use crate::transform_err::TransformMapInitErr;
}

View File

@@ -151,7 +151,7 @@ where
type InitError = A::InitError;
type Future = MapServiceFuture<A, F, 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())
}
}
@@ -197,7 +197,7 @@ where
#[cfg(test)]
mod tests {
use futures::future::{lazy, ok, Ready};
use futures_util::future::{lazy, ok, Ready};
use super::*;
use crate::{IntoServiceFactory, Service, ServiceFactory};
@@ -210,7 +210,7 @@ mod tests {
type Error = ();
type Future = Ready<Result<(), ()>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
@@ -219,14 +219,14 @@ mod tests {
}
}
#[tokio::test]
#[actix_rt::test]
async fn test_poll_ready() {
let mut srv = Srv.map(|_| "ok");
let res = lazy(|cx| srv.poll_ready(cx)).await;
assert_eq!(res, Poll::Ready(Ok(())));
}
#[tokio::test]
#[actix_rt::test]
async fn test_call() {
let mut srv = Srv.map(|_| "ok");
let res = srv.call(()).await;
@@ -234,7 +234,7 @@ mod tests {
assert_eq!(res.unwrap(), "ok");
}
#[tokio::test]
#[actix_rt::test]
async fn test_new_service() {
let new_srv = (|| ok::<_, ()>(Srv)).into_factory().map(|_| "ok");
let mut srv = new_srv.new_service(&()).await.unwrap();

View File

@@ -1,30 +1,30 @@
use std::marker::PhantomData;
use super::ServiceFactory;
use super::{IntoServiceFactory, ServiceFactory};
pub enum MappedConfig<'a, T> {
Ref(&'a T),
Owned(T),
}
/// Adapt external config to a config for provided new service
pub fn map_config<T, F, C>(factory: T, f: F) -> MapConfig<T, F, C>
/// Adapt external config argument to a config for provided service factory
///
/// Note that this function consumes the receiving service factory and returns
/// a wrapped version of it.
pub fn map_config<T, U, F, C>(factory: U, f: F) -> MapConfig<T, F, C>
where
T: ServiceFactory,
F: Fn(&C) -> MappedConfig<T::Config>,
U: IntoServiceFactory<T>,
F: Fn(C) -> T::Config,
{
MapConfig::new(factory, f)
MapConfig::new(factory.into_factory(), f)
}
/// Replace config with unit
pub fn unit_config<T, C>(new_service: T) -> UnitConfig<T, C>
pub fn unit_config<T, U, C>(factory: U) -> UnitConfig<T, C>
where
T: ServiceFactory<Config = ()>,
U: IntoServiceFactory<T>,
{
UnitConfig::new(new_service)
UnitConfig::new(factory.into_factory())
}
/// `.map_config()` service combinator
/// `map_config()` adapter service factory
pub struct MapConfig<A, F, C> {
a: A,
f: F,
@@ -36,7 +36,7 @@ impl<A, F, C> MapConfig<A, F, C> {
pub(crate) fn new(a: A, f: F) -> Self
where
A: ServiceFactory,
F: Fn(&C) -> MappedConfig<A::Config>,
F: Fn(C) -> A::Config,
{
Self {
a,
@@ -63,7 +63,7 @@ where
impl<A, F, C> ServiceFactory for MapConfig<A, F, C>
where
A: ServiceFactory,
F: Fn(&C) -> MappedConfig<A::Config>,
F: Fn(C) -> A::Config,
{
type Request = A::Request;
type Response = A::Response;
@@ -74,11 +74,8 @@ where
type InitError = A::InitError;
type Future = A::Future;
fn new_service(&self, cfg: &C) -> Self::Future {
match (self.f)(cfg) {
MappedConfig::Ref(cfg) => self.a.new_service(cfg),
MappedConfig::Owned(cfg) => self.a.new_service(&cfg),
}
fn new_service(&self, cfg: C) -> Self::Future {
self.a.new_service((self.f)(cfg))
}
}
@@ -123,7 +120,7 @@ where
type InitError = A::InitError;
type Future = A::Future;
fn new_service(&self, _: &C) -> Self::Future {
self.a.new_service(&())
fn new_service(&self, _: C) -> Self::Future {
self.a.new_service(())
}
}

View File

@@ -154,7 +154,7 @@ where
type InitError = A::InitError;
type Future = MapErrServiceFuture<A, 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())
}
}
@@ -199,7 +199,7 @@ where
#[cfg(test)]
mod tests {
use futures::future::{err, lazy, ok, Ready};
use futures_util::future::{err, lazy, ok, Ready};
use super::*;
use crate::{IntoServiceFactory, Service, ServiceFactory};
@@ -212,7 +212,7 @@ mod tests {
type Error = ();
type Future = Ready<Result<(), ()>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Err(()))
}
@@ -221,14 +221,14 @@ mod tests {
}
}
#[tokio::test]
#[actix_rt::test]
async fn test_poll_ready() {
let mut srv = Srv.map_err(|_| "error");
let res = lazy(|cx| srv.poll_ready(cx)).await;
assert_eq!(res, Poll::Ready(Err("error")));
}
#[tokio::test]
#[actix_rt::test]
async fn test_call() {
let mut srv = Srv.map_err(|_| "error");
let res = srv.call(()).await;
@@ -236,7 +236,7 @@ mod tests {
assert_eq!(res.err().unwrap(), "error");
}
#[tokio::test]
#[actix_rt::test]
async fn test_new_service() {
let new_srv = (|| ok::<_, ()>(Srv)).into_factory().map_err(|_| "error");
let mut srv = new_srv.new_service(&()).await.unwrap();

View File

@@ -55,7 +55,7 @@ where
type InitError = E;
type Future = MapInitErrFuture<A, F, 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())
}
}

View File

@@ -1,9 +1,15 @@
use std::future::Future;
use std::task::{Context, Poll};
use crate::and_then::{AndThenService, AndThenServiceFactory};
use crate::and_then_apply_fn::{AndThenApplyFn, AndThenApplyFnFactory};
use crate::map::{Map, MapServiceFactory};
use crate::map_err::{MapErr, MapErrServiceFactory};
use crate::map_init_err::MapInitErr;
use crate::then::{ThenService, ThenServiceFactory};
use crate::{IntoService, IntoServiceFactory, Service, ServiceFactory};
/// Contruct new pipeline with one service in pipeline chain.
pub fn pipeline<F, T>(service: F) -> Pipeline<T>
where
F: IntoService<T>,
@@ -14,6 +20,7 @@ where
}
}
/// Contruct new pipeline factory with one service factory.
pub fn pipeline_factory<T, F>(factory: F) -> PipelineFactory<T>
where
T: ServiceFactory,
@@ -24,7 +31,7 @@ where
}
}
/// Pipeline service
/// Pipeline service - pipeline allows to compose multiple service into one service.
pub struct Pipeline<T> {
service: T,
}
@@ -39,7 +46,12 @@ impl<T: Service> Pipeline<T> {
///
/// Note that this function consumes the receiving service and returns a
/// wrapped version of it.
pub fn and_then<F, U>(self, service: F) -> Pipeline<AndThenService<T, U>>
pub fn and_then<F, U>(
self,
service: F,
) -> Pipeline<
impl Service<Request = T::Request, Response = U::Response, Error = T::Error> + Clone,
>
where
Self: Sized,
F: IntoService<U>,
@@ -50,12 +62,39 @@ impl<T: Service> Pipeline<T> {
}
}
/// 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),
}
}
/// Chain on a computation for when a call to the service finished,
/// passing the result of the call to the next service `U`.
///
/// Note that this function consumes the receiving pipeline and returns a
/// wrapped version of it.
pub fn then<F, U>(self, service: F) -> Pipeline<ThenService<T, U>>
pub fn then<F, U>(
self,
service: F,
) -> Pipeline<
impl Service<Request = T::Request, Response = U::Response, Error = T::Error> + Clone,
>
where
Self: Sized,
F: IntoService<U>,
@@ -65,6 +104,43 @@ impl<T: Service> Pipeline<T> {
service: ThenService::new(self.service, service.into_service()),
}
}
/// 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.
pub fn map<F, R>(self, f: F) -> Pipeline<Map<T, F, R>>
where
Self: Sized,
F: FnMut(T::Response) -> R,
{
Pipeline {
service: Map::new(self.service, 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.
pub fn map_err<F, E>(self, f: F) -> Pipeline<MapErr<T, F, E>>
where
Self: Sized,
F: Fn(T::Error) -> E,
{
Pipeline {
service: MapErr::new(self.service, f),
}
}
}
impl<T> Clone for Pipeline<T>
@@ -95,16 +171,33 @@ impl<T: Service> Service for Pipeline<T> {
}
}
/// Pipeline constructor
/// Pipeline factory
pub struct PipelineFactory<T> {
factory: T,
}
impl<T: ServiceFactory> PipelineFactory<T> {
/// Call another service after call to this one has resolved successfully.
pub fn and_then<F, U>(self, factory: F) -> PipelineFactory<AndThenServiceFactory<T, U>>
pub fn and_then<F, U>(
self,
factory: F,
) -> PipelineFactory<
impl ServiceFactory<
Request = T::Request,
Response = U::Response,
Error = T::Error,
Config = T::Config,
InitError = T::InitError,
Service = impl Service<
Request = T::Request,
Response = U::Response,
Error = T::Error,
> + Clone,
> + Clone,
>
where
Self: Sized,
T::Config: Clone,
F: IntoServiceFactory<U>,
U: ServiceFactory<
Config = T::Config,
@@ -118,15 +211,64 @@ impl<T: ServiceFactory> PipelineFactory<T> {
}
}
/// 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),
}
}
/// Create `NewService` to chain on a computation for when a call to the
/// service finished, passing the result of the call to the next
/// service `U`.
///
/// Note that this function consumes the receiving pipeline and returns a
/// wrapped version of it.
pub fn then<F, U>(self, factory: F) -> PipelineFactory<ThenServiceFactory<T, U>>
pub fn then<F, U>(
self,
factory: F,
) -> PipelineFactory<
impl ServiceFactory<
Request = T::Request,
Response = U::Response,
Error = T::Error,
Config = T::Config,
InitError = T::InitError,
Service = impl Service<
Request = T::Request,
Response = U::Response,
Error = T::Error,
> + Clone,
> + Clone,
>
where
Self: Sized,
T::Config: Clone,
F: IntoServiceFactory<U>,
U: ServiceFactory<
Config = T::Config,
@@ -139,6 +281,40 @@ impl<T: ServiceFactory> PipelineFactory<T> {
factory: ThenServiceFactory::new(self.factory, factory.into_factory()),
}
}
/// Map this service's output to a different type, returning a new service
/// of the resulting type.
pub fn map<F, R>(self, f: F) -> PipelineFactory<MapServiceFactory<T, F, R>>
where
Self: Sized,
F: FnMut(T::Response) -> R + Clone,
{
PipelineFactory {
factory: MapServiceFactory::new(self.factory, f),
}
}
/// 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>>
where
Self: Sized,
F: Fn(T::Error) -> E + Clone,
{
PipelineFactory {
factory: MapErrServiceFactory::new(self.factory, f),
}
}
/// 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>>
where
Self: Sized,
F: Fn(T::InitError) -> E + Clone,
{
PipelineFactory {
factory: MapInitErr::new(self.factory, f),
}
}
}
impl<T> Clone for PipelineFactory<T>
@@ -162,7 +338,7 @@ impl<T: ServiceFactory> ServiceFactory for PipelineFactory<T> {
type Future = T::Future;
#[inline]
fn new_service(&self, cfg: &T::Config) -> Self::Future {
fn new_service(&self, cfg: T::Config) -> Self::Future {
self.factory.new_service(cfg)
}
}

View File

@@ -1,18 +1,16 @@
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use super::{Service, ServiceFactory};
use crate::cell::Cell;
/// Service for the `then` combinator, chaining a computation onto the end of
/// another service.
///
/// This is created by the `ServiceExt::then` method.
pub struct ThenService<A, B> {
a: A,
b: Cell<B>,
}
/// This is created by the `Pipeline::then` method.
pub(crate) struct ThenService<A, B>(Rc<RefCell<(A, B)>>);
impl<A, B> ThenService<A, B> {
/// Create new `.then()` combinator
@@ -21,19 +19,13 @@ impl<A, B> ThenService<A, B> {
A: Service,
B: Service<Request = Result<A::Response, A::Error>, Error = A::Error>,
{
Self { a, b: Cell::new(b) }
Self(Rc::new(RefCell::new((a, b))))
}
}
impl<A, B> Clone for ThenService<A, B>
where
A: Clone,
{
impl<A, B> Clone for ThenService<A, B> {
fn clone(&self) -> Self {
ThenService {
a: self.a.clone(),
b: self.b.clone(),
}
ThenService(self.0.clone())
}
}
@@ -47,9 +39,10 @@ where
type Error = B::Error;
type Future = ThenServiceResponse<A, B>;
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let not_ready = !self.a.poll_ready(ctx)?.is_ready();
if !self.b.get_mut().poll_ready(ctx)?.is_ready() || not_ready {
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let mut srv = self.0.borrow_mut();
let not_ready = !srv.0.poll_ready(cx)?.is_ready();
if !srv.1.poll_ready(cx)?.is_ready() || not_ready {
Poll::Pending
} else {
Poll::Ready(Ok(()))
@@ -57,35 +50,31 @@ where
}
fn call(&mut self, req: A::Request) -> Self::Future {
ThenServiceResponse::new(self.a.call(req), self.b.clone())
ThenServiceResponse {
state: State::A(self.0.borrow_mut().0.call(req), Some(self.0.clone())),
}
}
}
#[pin_project::pin_project]
pub struct ThenServiceResponse<A, B>
pub(crate) struct ThenServiceResponse<A, B>
where
A: Service,
B: Service<Request = Result<A::Response, A::Error>>,
{
b: Cell<B>,
#[pin]
fut_b: Option<B::Future>,
#[pin]
fut_a: Option<A::Future>,
state: State<A, B>,
}
impl<A, B> ThenServiceResponse<A, B>
#[pin_project::pin_project(project = StateProj)]
enum State<A, B>
where
A: Service,
B: Service<Request = Result<A::Response, A::Error>>,
{
fn new(a: A::Future, b: Cell<B>) -> Self {
ThenServiceResponse {
b,
fut_a: Some(a),
fut_b: None,
}
}
A(#[pin] A::Future, Option<Rc<RefCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
impl<A, B> Future for ThenServiceResponse<A, B>
@@ -98,37 +87,35 @@ where
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
loop {
if let Some(fut) = this.fut_b.as_pin_mut() {
return fut.poll(cx);
}
match this
.fut_a
.as_pin_mut()
.expect("Bug in actix-service")
.poll(cx)
{
Poll::Ready(r) => {
this = self.as_mut().project();
this.fut_b.set(Some(this.b.get_mut().call(r)));
match this.state.as_mut().project() {
StateProj::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 = b.borrow_mut().1.call(res);
this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => return Poll::Pending,
Poll::Pending => Poll::Pending,
},
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
}
}
}
}
/// `.then()` service factory combinator
pub struct ThenServiceFactory<A, B> {
a: A,
b: B,
}
pub(crate) struct ThenServiceFactory<A, B>(Rc<(A, B)>);
impl<A, B> ThenServiceFactory<A, B>
where
A: ServiceFactory,
A::Config: Clone,
B: ServiceFactory<
Config = A::Config,
Request = Result<A::Response, A::Error>,
@@ -138,13 +125,14 @@ where
{
/// Create new `AndThen` combinator
pub(crate) fn new(a: A, b: B) -> Self {
Self { a, b }
Self(Rc::new((a, b)))
}
}
impl<A, B> ServiceFactory for ThenServiceFactory<A, B>
where
A: ServiceFactory,
A::Config: Clone,
B: ServiceFactory<
Config = A::Config,
Request = Result<A::Response, A::Error>,
@@ -161,26 +149,20 @@ where
type InitError = A::InitError;
type Future = ThenServiceFactoryResponse<A, B>;
fn new_service(&self, cfg: &A::Config) -> Self::Future {
ThenServiceFactoryResponse::new(self.a.new_service(cfg), self.b.new_service(cfg))
fn new_service(&self, cfg: A::Config) -> Self::Future {
let srv = &*self.0;
ThenServiceFactoryResponse::new(srv.0.new_service(cfg.clone()), srv.1.new_service(cfg))
}
}
impl<A, B> Clone for ThenServiceFactory<A, B>
where
A: Clone,
B: Clone,
{
impl<A, B> Clone for ThenServiceFactory<A, B> {
fn clone(&self) -> Self {
Self {
a: self.a.clone(),
b: self.b.clone(),
}
Self(self.0.clone())
}
}
#[pin_project::pin_project]
pub struct ThenServiceFactoryResponse<A, B>
pub(crate) struct ThenServiceFactoryResponse<A, B>
where
A: ServiceFactory,
B: ServiceFactory<
@@ -260,7 +242,7 @@ mod tests {
use std::rc::Rc;
use std::task::{Context, Poll};
use futures::future::{err, lazy, ok, ready, Ready};
use futures_util::future::{err, lazy, ok, ready, Ready};
use crate::{pipeline, pipeline_factory, Service, ServiceFactory};
@@ -307,7 +289,7 @@ mod tests {
}
}
#[tokio::test]
#[actix_rt::test]
async fn test_poll_ready() {
let cnt = Rc::new(Cell::new(0));
let mut srv = pipeline(Srv1(cnt.clone())).then(Srv2(cnt.clone()));
@@ -316,21 +298,21 @@ mod tests {
assert_eq!(cnt.get(), 2);
}
#[tokio::test]
#[actix_rt::test]
async fn test_call() {
let cnt = Rc::new(Cell::new(0));
let mut srv = pipeline(Srv1(cnt.clone())).then(Srv2(cnt));
let res = srv.call(Ok("srv1")).await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), (("srv1", "ok")));
assert_eq!(res.unwrap(), ("srv1", "ok"));
let res = srv.call(Err("srv")).await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), (("srv2", "err")));
assert_eq!(res.unwrap(), ("srv2", "err"));
}
#[tokio::test]
#[actix_rt::test]
async fn test_factory() {
let cnt = Rc::new(Cell::new(0));
let cnt2 = cnt.clone();
@@ -339,10 +321,10 @@ mod tests {
let mut srv = factory.new_service(&()).await.unwrap();
let res = srv.call(Ok("srv1")).await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), (("srv1", "ok")));
assert_eq!(res.unwrap(), ("srv1", "ok"));
let res = srv.call(Err("srv")).await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), (("srv2", "err")));
assert_eq!(res.unwrap(), ("srv2", "err"));
}
}

View File

@@ -4,25 +4,91 @@ use std::rc::Rc;
use std::sync::Arc;
use std::task::{Context, Poll};
use crate::transform_err::TransformMapInitErr;
use crate::{IntoServiceFactory, Service, ServiceFactory};
/// Apply transform to a service. Function returns
/// services factory that in initialization creates
/// service and applies transform to this service.
pub fn apply<T, S, U>(t: T, service: U) -> ApplyTransform<T, S>
/// Apply transform to a service.
pub fn apply<T, S, U>(t: T, factory: U) -> ApplyTransform<T, S>
where
S: ServiceFactory,
T: Transform<S::Service, InitError = S::InitError>,
U: IntoServiceFactory<S>,
{
ApplyTransform::new(t, service.into_factory())
ApplyTransform::new(t, factory.into_factory())
}
/// The `Transform` trait defines the interface of a service factory that wraps inner service
/// during construction.
///
/// Transform(middleware) wraps inner service and runs during
/// inbound and/or outbound processing in the request/response lifecycle.
/// It may modify request and/or response.
///
/// For example, timeout transform:
///
/// ```rust,ignore
/// pub struct Timeout<S> {
/// service: S,
/// timeout: Duration,
/// }
///
/// impl<S> Service for Timeout<S>
/// where
/// S: Service,
/// {
/// type Request = S::Request;
/// type Response = S::Response;
/// type Error = TimeoutError<S::Error>;
/// type Future = TimeoutServiceResponse<S>;
///
/// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
/// ready!(self.service.poll_ready(cx)).map_err(TimeoutError::Service)
/// }
///
/// fn call(&mut self, req: S::Request) -> Self::Future {
/// TimeoutServiceResponse {
/// fut: self.service.call(req),
/// sleep: Delay::new(clock::now() + self.timeout),
/// }
/// }
/// }
/// ```
///
/// Timeout service in above example is decoupled from underlying service implementation
/// and could be applied to any service.
///
/// The `Transform` trait defines the interface of a Service factory. `Transform`
/// is often implemented for middleware, defining how to construct a
/// middleware Service. A Service that is constructed by the factory takes
/// the Service that follows it during execution as a parameter, assuming
/// ownership of the next Service.
///
/// Factory for `Timeout` middleware from the above example could look like this:
///
/// ```rust,,ignore
/// pub struct TimeoutTransform {
/// timeout: Duration,
/// }
///
/// impl<S> Transform<S> for TimeoutTransform<E>
/// where
/// S: Service,
/// {
/// type Request = S::Request;
/// type Response = S::Response;
/// type Error = TimeoutError<S::Error>;
/// type InitError = S::Error;
/// type Transform = Timeout<S>;
/// type Future = Ready<Result<Self::Transform, Self::InitError>>;
///
/// fn new_transform(&self, service: S) -> Self::Future {
/// ok(TimeoutService {
/// service,
/// timeout: self.timeout,
/// })
/// }
/// }
/// ```
pub trait Transform<S> {
/// Requests handled by the service.
type Request;
@@ -40,14 +106,24 @@ pub trait Transform<S> {
Error = Self::Error,
>;
/// Errors produced while building a service.
/// Errors produced while building a transform service.
type InitError;
/// The future response value.
type Future: Future<Output = Result<Self::Transform, Self::InitError>>;
/// Creates and returns a new Service component, asynchronously
/// Creates and returns a new Transform component, asynchronously
fn new_transform(&self, service: S) -> Self::Future;
/// Map this transforms's factory error to a different error,
/// returning a new transform service factory.
fn map_init_err<F, E>(self, f: F) -> TransformMapInitErr<Self, S, F, E>
where
Self: Sized,
F: Fn(Self::InitError) -> E + Clone,
{
TransformMapInitErr::new(self, f)
}
}
impl<T, S> Transform<S> for Rc<T>
@@ -83,10 +159,7 @@ where
}
/// `Apply` transform to new service
pub struct ApplyTransform<T, S> {
s: Rc<S>,
t: Rc<T>,
}
pub struct ApplyTransform<T, S>(Rc<(T, S)>);
impl<T, S> ApplyTransform<T, S>
where
@@ -95,19 +168,13 @@ where
{
/// Create new `ApplyTransform` new service instance
fn new(t: T, service: S) -> Self {
Self {
s: Rc::new(service),
t: Rc::new(t),
}
Self(Rc::new((t, service)))
}
}
impl<T, S> Clone for ApplyTransform<T, S> {
fn clone(&self) -> Self {
ApplyTransform {
s: self.s.clone(),
t: self.t.clone(),
}
ApplyTransform(self.0.clone())
}
}
@@ -125,11 +192,10 @@ where
type InitError = T::InitError;
type Future = ApplyTransformFuture<T, S>;
fn new_service(&self, cfg: &S::Config) -> Self::Future {
fn new_service(&self, cfg: S::Config) -> Self::Future {
ApplyTransformFuture {
t_cell: self.t.clone(),
fut_a: self.s.new_service(cfg),
fut_t: None,
store: self.0.clone(),
state: ApplyTransformFutureState::A(self.0.as_ref().1.new_service(cfg)),
}
}
}
@@ -140,11 +206,19 @@ where
S: ServiceFactory,
T: Transform<S::Service, InitError = S::InitError>,
{
store: Rc<(T, S)>,
#[pin]
fut_a: S::Future,
#[pin]
fut_t: Option<T::Future>,
t_cell: Rc<T>,
state: ApplyTransformFutureState<T, S>,
}
#[pin_project::pin_project(project = ApplyTransformFutureStateProj)]
pub enum ApplyTransformFutureState<T, S>
where
S: ServiceFactory,
T: Transform<S::Service, InitError = S::InitError>,
{
A(#[pin] S::Future),
B(#[pin] T::Future),
}
impl<T, S> Future for ApplyTransformFuture<T, S>
@@ -157,17 +231,16 @@ where
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
if let Some(fut) = this.fut_t.as_pin_mut() {
return fut.poll(cx);
}
if let Poll::Ready(service) = this.fut_a.poll(cx)? {
let fut = this.t_cell.new_transform(service);
this = self.as_mut().project();
this.fut_t.set(Some(fut));
this.fut_t.as_pin_mut().unwrap().poll(cx)
} else {
return Poll::Pending;
match this.state.as_mut().project() {
ApplyTransformFutureStateProj::A(fut) => match fut.poll(cx)? {
Poll::Ready(srv) => {
let fut = this.store.0.new_transform(srv);
this.state.set(ApplyTransformFutureState::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
ApplyTransformFutureStateProj::B(fut) => fut.poll(cx),
}
}
}

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