mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-04 18:06:23 +02:00
Compare commits
481 Commits
Author | SHA1 | Date | |
---|---|---|---|
d1f5c457c4 | |||
c26c5fd9a4 | |||
4a73d1c8c1 | |||
7c395fcc83 | |||
54c33a7aff | |||
47d80382b2 | |||
ba816a8562 | |||
6f75b0e95e | |||
b3cc43bb9b | |||
ecda97aadd | |||
8cda362866 | |||
3c6c1268c9 | |||
72908d974c | |||
c755d71a8b | |||
a817ddb57b | |||
44c36e93d1 | |||
c92ebc22d7 | |||
599fd6af93 | |||
fa81d97004 | |||
c54f045b39 | |||
cd11293c1f | |||
45325a5f75 | |||
a7c40024ce | |||
0af4d01fe4 | |||
bd6e18b7fe | |||
f66cf16823 | |||
03d6b04eef | |||
f37880d89c | |||
8b43574bd5 | |||
b07d0e712f | |||
acd7380865 | |||
0208dfb6b2 | |||
bb61dd41af | |||
58079b5bbe | |||
3623383e83 | |||
7036656ae4 | |||
32a2866449 | |||
35a4078434 | |||
4ca5d8bcfc | |||
a38acb41e5 | |||
31e23d4ab1 | |||
1aadfee6f7 | |||
76b644365f | |||
80f385e703 | |||
a1958deaae | |||
8d65468c58 | |||
195246573e | |||
e01102bda2 | |||
9b6343d54b | |||
d9a4fadaae | |||
48e05a2d87 | |||
70d0c5c700 | |||
d43ca96c5c | |||
bfd46e6a71 | |||
25b245ac72 | |||
eefbe19651 | |||
ab4e889f96 | |||
91235ac816 | |||
9c1bda3eca | |||
4a29f12876 | |||
368730f5f1 | |||
aa757a5be8 | |||
03ded62337 | |||
c72d1381a6 | |||
d98d723f97 | |||
eb6e618812 | |||
de222fe33b | |||
de49796fd1 | |||
a38c3985f6 | |||
492c072564 | |||
fd876efa68 | |||
c5b9bed478 | |||
3eba383cdc | |||
927f2e594e | |||
fa9edf2180 | |||
5ca904d1db | |||
2e7d323e1a | |||
b66566f610 | |||
2477afcf30 | |||
bcd03a9c62 | |||
f8af3ef7f4 | |||
f8b75c157f | |||
b7b61afacc | |||
507361c1df | |||
f6fd9e70f9 | |||
de8a09254d | |||
f89b7a9bb8 | |||
59244b203c | |||
2adf8a3a48 | |||
805dbea8e7 | |||
dc9a24a189 | |||
5528cf62f0 | |||
9880a95603 | |||
2579c49865 | |||
01a0f3f5a0 | |||
2c8d987241 | |||
813d1d6e66 | |||
48b02abee7 | |||
ce1081432b | |||
e9bdba57a0 | |||
f907be585e | |||
022f9800ed | |||
a9a54ac4c6 | |||
50b9fee3a7 | |||
bf9a90293f | |||
17ec3a3a26 | |||
5b4b885fd6 | |||
65b8197876 | |||
a826d113ee | |||
3a79505a44 | |||
5f3a7a6a52 | |||
6a7b097bcf | |||
30a36bed9d | |||
79818560b2 | |||
58cc0dfbc5 | |||
a9ea649348 | |||
634c5723a0 | |||
a5b5ff0894 | |||
5140fea8d1 | |||
333b4f57d3 | |||
827ca5eada | |||
ebc1f6eff9 | |||
a8567da3e2 | |||
113f5ad1a8 | |||
95f6277007 | |||
22c776f46e | |||
c0976bfa17 | |||
5e9ec4299c | |||
e05aba65de | |||
c5b18c6d30 | |||
94c5bb5cdd | |||
2ca0ea70c4 | |||
0b01884fca | |||
83168731fc | |||
7295846426 | |||
72bc1546c4 | |||
d39b531537 | |||
35e68723df | |||
0624f9b9d9 | |||
0e3820afdf | |||
839d67ac6a | |||
b517957761 | |||
d18f9c5905 | |||
76fcdc13a3 | |||
62a9b4c53c | |||
c570229351 | |||
d041df6c4b | |||
bc28e54976 | |||
26ab5cbd01 | |||
50c2a5ceb0 | |||
8dbbb0ee07 | |||
ca76dff5a7 | |||
88f66d49d0 | |||
be288fa00a | |||
5e6a0aa3df | |||
fd87eb59f8 | |||
81ac905c7b | |||
bb11fb3d24 | |||
23eea54776 | |||
2881859400 | |||
1686682c19 | |||
d04ff13955 | |||
e757dc5a71 | |||
be358db422 | |||
7df2d6b12a | |||
458e6bdcc2 | |||
0b0bbd6bd9 | |||
5617896780 | |||
2b803f30c9 | |||
9b152acc32 | |||
eb66685d1a | |||
b505e682d4 | |||
48e7013997 | |||
ff14633b3d | |||
37db7d8168 | |||
89bf12605d | |||
9fb0498437 | |||
b2a43a3c8d | |||
38063b9873 | |||
1045a6c6f0 | |||
7243c58fce | |||
fffaf2bb2d | |||
7becb95a97 | |||
a4b837a1c1 | |||
542315ce7f | |||
602d78b76c | |||
18b706d4fb | |||
94b41fd484 | |||
3a80cb7bf3 | |||
e4a85a53f4 | |||
1a45dbd768 | |||
7cff5d9ade | |||
c04e0fdec4 | |||
0f0fe5f148 | |||
e7f9f5b46d | |||
c3fbba2678 | |||
a88e97edba | |||
1f08100f6f | |||
ab60ec6e1d | |||
0fbd05009d | |||
fdb7419e24 | |||
191b53bd7c | |||
2d4ee0ee01 | |||
5bd5f67d79 | |||
5d8cbccfe9 | |||
8d5fa6ee71 | |||
084104d058 | |||
2c411a04a9 | |||
af0c8d893d | |||
691457fbfe | |||
2dafd9c681 | |||
12586db15c | |||
b847bda8ca | |||
2a543001e0 | |||
0f86c596fa | |||
6c55501252 | |||
961edfd21a | |||
7f0de705a3 | |||
c2ad65a61d | |||
3c93e0c654 | |||
a0f1ff7eb3 | |||
9f45cfe492 | |||
46e6641528 | |||
a3f124685a | |||
800f711cc1 | |||
7be4b1f399 | |||
eeae0ddab4 | |||
c1af59c618 | |||
d8a9606162 | |||
8038a52287 | |||
c273b7ac3f | |||
df21892b5b | |||
a255a6fb69 | |||
b693d5491b | |||
56a31ea0ee | |||
2a269f1111 | |||
fee30d6f47 | |||
476b1fb36a | |||
3b93bff602 | |||
d292c5023f | |||
ef6f310060 | |||
a6cbdde43f | |||
cbf4c61eb5 | |||
280c8d87f8 | |||
83bf852192 | |||
9d39f441e9 | |||
03d851680b | |||
0ddd018214 | |||
8219a7aebe | |||
6c906b08e1 | |||
220cbe40e5 | |||
74d0656d27 | |||
17c27ef42d | |||
b2e771df2c | |||
a5a36ff194 | |||
97e2bcd055 | |||
23cfa649f4 | |||
8791c0f880 | |||
16c212f853 | |||
3ee228005d | |||
7a743fa6b5 | |||
44e3df82f6 | |||
8d8f6bedad | |||
9e751de707 | |||
b16419348e | |||
3ccaa04575 | |||
d80b84c915 | |||
145010a2b0 | |||
3e98177fad | |||
d24752d9bc | |||
92fe2e96de | |||
3cf54bc0fd | |||
86dd732704 | |||
dfd8f1058e | |||
f5636f321b | |||
ae6c9cb7fa | |||
32052c2750 | |||
7d6deab9fb | |||
9e61c67128 | |||
13bb5f20d2 | |||
d14991ec96 | |||
45dec8d0c0 | |||
90e3aaaf8a | |||
4f7d45ee9c | |||
e1d2536d85 | |||
65700281e8 | |||
80f6b93714 | |||
5585465859 | |||
368103dd09 | |||
df7ffe14f2 | |||
36161aba99 | |||
9f5a91ae3c | |||
4e61e0db34 | |||
2dfccdd924 | |||
4358da9926 | |||
62fb75ff95 | |||
29a0feb415 | |||
dcc5eb7ace | |||
81f4e12a27 | |||
2f60a4b89d | |||
b03c7051ff | |||
8fff2c7595 | |||
052d5f0bc5 | |||
68cf32e848 | |||
a56e5113ee | |||
5127b85672 | |||
2d80c5053d | |||
d46854b315 | |||
47f836cd1b | |||
449709dd7e | |||
5a25fd95f5 | |||
b942bcc4a6 | |||
1107fdec9d | |||
04515e4697 | |||
93d99b5a49 | |||
e49910cdab | |||
e8a1850c79 | |||
afb81b6b8f | |||
4866a26578 | |||
2d75ced4ed | |||
7bcc258b09 | |||
d5fa0a9418 | |||
ce6d237cc1 | |||
70caa2552b | |||
ee7d58dd7f | |||
c4f4cadb43 | |||
978091cedb | |||
8198f5e10a | |||
6cd40df387 | |||
35ee5d36d8 | |||
e7ec0f9fd7 | |||
f4a47ef71e | |||
6b1a79fab8 | |||
ab73da4a1a | |||
e0c8da567c | |||
c10dedf7e4 | |||
ec192e0ab1 | |||
6d792d9948 | |||
1fe4315c94 | |||
381b90e9a1 | |||
2d18dba40a | |||
d2693d58a8 | |||
84bf282c17 | |||
b15b5e5246 | |||
52b3b0c362 | |||
64c4cefa8f | |||
7e8b231f57 | |||
8a344d0c94 | |||
4096089a3f | |||
b16f2d5f05 | |||
5baf15822a | |||
5368ce823e | |||
4effdf065b | |||
61970ab190 | |||
484b00a0f9 | |||
73bf2068aa | |||
1cda949204 | |||
ad6b823255 | |||
0f064db31d | |||
fd0bb54469 | |||
e27bbaa55c | |||
8a50eae1e2 | |||
38080f67b3 | |||
08504e0892 | |||
401c0ad809 | |||
b4b0deb7fa | |||
05ff35d383 | |||
29c3e8f7ea | |||
6657446433 | |||
46b9a9c887 | |||
b3cdb472d0 | |||
31e1aab9a4 | |||
67f383f346 | |||
49f5c335f6 | |||
692e11a584 | |||
208117ca6f | |||
3e276ac921 | |||
4af115a19c | |||
051703eb2c | |||
31fbbd3168 | |||
fee1e255ac | |||
a4c933e56e | |||
9ddf5a3550 | |||
9ab0fa604d | |||
6c709b33cc | |||
71b4c07ea4 | |||
ac9eba8261 | |||
cad55f9c80 | |||
4263574a58 | |||
84ef5ee410 | |||
598fb9190d | |||
9a404a0c03 | |||
3dd8fdf450 | |||
05f5ba0084 | |||
8169149554 | |||
8d1de6c497 | |||
caaace82e3 | |||
02dd5375a9 | |||
717602472a | |||
b56be8e571 | |||
2853086463 | |||
e2107ec6f4 | |||
c33caddf57 | |||
db1e04e418 | |||
f8b8fe3865 | |||
1c6ddfd34c | |||
49e007ff2a | |||
2068eee669 | |||
f3c63e631a | |||
3f0803a7d3 | |||
f12b613211 | |||
695c052c58 | |||
63634be542 | |||
f88f1c65b6 | |||
a0b589eb96 | |||
ebdc983dfe | |||
395243a539 | |||
1ab676d7eb | |||
47f01e5b7e | |||
ffb89935b6 | |||
77a111b95c | |||
6c0fb3a7d2 | |||
824244622f | |||
42d2a29b1d | |||
af8875f6ab | |||
1db1ce1ca3 | |||
f55ef3a059 | |||
67bf0ae79f | |||
b06cf32329 | |||
7cce29b633 | |||
c26d9545a5 | |||
b950d6997d | |||
0bf29a522b | |||
24342fb745 | |||
0278e364ec | |||
b9d6bbd357 | |||
5816ecd1bc | |||
2ff55ee1c5 | |||
b42de6c41f | |||
9e0e081c90 | |||
178f5a104e | |||
1e42f9575a | |||
24dfcf1303 | |||
6cc3aaef1b | |||
436a16a2c8 | |||
9afad5885b | |||
04d0abb3c7 | |||
1e5daa1de8 | |||
d3c859f9f3 | |||
c1419413aa | |||
acd33cccbb | |||
57a1d68f89 | |||
5c88441cd7 | |||
6a3c5c4ce0 | |||
14a511bdad | |||
e4ed53d691 | |||
5bf4f3be8b | |||
6b9e51740b | |||
be7e8d159b | |||
ceb97cd6b9 | |||
85b650048d | |||
a0e6313d56 | |||
9cc6f6b1e4 | |||
526753ee88 | |||
779e773185 | |||
7eb310f8ce | |||
d573cf2d97 | |||
32b5544ad9 | |||
e182ed33b1 | |||
6f1836f80e | |||
5b530f11b5 | |||
0c30057c8c | |||
6078344ecc | |||
67f5a949a4 | |||
05e49e893e | |||
c8844425ad | |||
b282ec106e | |||
ea2a8f6908 | |||
2b942ec5f2 | |||
bf77be0337 | |||
c2741054bb |
@ -4,13 +4,13 @@ environment:
|
|||||||
matrix:
|
matrix:
|
||||||
# Stable channel
|
# Stable channel
|
||||||
- TARGET: i686-pc-windows-gnu
|
- TARGET: i686-pc-windows-gnu
|
||||||
CHANNEL: 1.21.0
|
CHANNEL: 1.24.0
|
||||||
- TARGET: i686-pc-windows-msvc
|
- TARGET: i686-pc-windows-msvc
|
||||||
CHANNEL: 1.21.0
|
CHANNEL: 1.24.0
|
||||||
- TARGET: x86_64-pc-windows-gnu
|
- TARGET: x86_64-pc-windows-gnu
|
||||||
CHANNEL: 1.21.0
|
CHANNEL: 1.24.0
|
||||||
- TARGET: x86_64-pc-windows-msvc
|
- TARGET: x86_64-pc-windows-msvc
|
||||||
CHANNEL: 1.21.0
|
CHANNEL: 1.24.0
|
||||||
# Stable channel
|
# Stable channel
|
||||||
- TARGET: i686-pc-windows-gnu
|
- TARGET: i686-pc-windows-gnu
|
||||||
CHANNEL: stable
|
CHANNEL: stable
|
||||||
@ -59,4 +59,4 @@ build: false
|
|||||||
|
|
||||||
# Equivalent to Travis' `script` phase
|
# Equivalent to Travis' `script` phase
|
||||||
test_script:
|
test_script:
|
||||||
- cargo test --no-default-features
|
- cargo test --no-default-features --features="flate2-rust"
|
||||||
|
68
.travis.yml
68
.travis.yml
@ -8,19 +8,12 @@ cache:
|
|||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- rust: 1.21.0
|
- rust: 1.24.0
|
||||||
- rust: stable
|
- rust: stable
|
||||||
- rust: beta
|
- rust: beta
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
- rust: beta
|
|
||||||
|
|
||||||
#rust:
|
|
||||||
# - 1.21.0
|
|
||||||
# - stable
|
|
||||||
# - beta
|
|
||||||
# - nightly-2018-01-03
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
@ -34,60 +27,29 @@ before_install:
|
|||||||
|
|
||||||
# Add clippy
|
# Add clippy
|
||||||
before_script:
|
before_script:
|
||||||
- |
|
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
|
||||||
( ( cargo install clippy && export CLIPPY=true ) || export CLIPPY=false );
|
|
||||||
fi
|
|
||||||
- export PATH=$PATH:~/.cargo/bin
|
- export PATH=$PATH:~/.cargo/bin
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
if [[ "$TRAVIS_RUST_VERSION" != "beta" ]]; then
|
||||||
cargo clean
|
cargo clean
|
||||||
USE_SKEPTIC=1 cargo test --features=alpn
|
cargo test --features="alpn,tls" -- --nocapture
|
||||||
else
|
|
||||||
cargo clean
|
|
||||||
cargo test -- --nocapture
|
|
||||||
# --features=alpn
|
|
||||||
fi
|
|
||||||
|
|
||||||
- |
|
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
|
||||||
cd examples/basics && cargo check && cd ../..
|
|
||||||
cd examples/hello-world && cargo check && cd ../..
|
|
||||||
cd examples/multipart && cargo check && cd ../..
|
|
||||||
cd examples/json && cargo check && cd ../..
|
|
||||||
cd examples/juniper && cargo check && cd ../..
|
|
||||||
cd examples/state && cargo check && cd ../..
|
|
||||||
cd examples/template_tera && cargo check && cd ../..
|
|
||||||
cd examples/diesel && cargo check && cd ../..
|
|
||||||
cd examples/r2d2 && cargo check && cd ../..
|
|
||||||
cd examples/tls && cargo check && cd ../..
|
|
||||||
cd examples/websocket-chat && cargo check && cd ../..
|
|
||||||
cd examples/websocket && cargo check && cd ../..
|
|
||||||
fi
|
fi
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then
|
if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then
|
||||||
cargo clippy
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Upload docs
|
|
||||||
after_success:
|
|
||||||
- |
|
|
||||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
|
||||||
cargo doc --features "alpn, tls" --no-deps &&
|
|
||||||
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
|
||||||
cargo install mdbook &&
|
|
||||||
cd guide && mdbook build -d ../target/doc/guide && cd .. &&
|
|
||||||
git clone https://github.com/davisp/ghp-import.git &&
|
|
||||||
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
|
|
||||||
echo "Uploaded documentation"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- |
|
|
||||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "1.21.0" ]]; then
|
|
||||||
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
|
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
|
||||||
USE_SKEPTIC=1 cargo tarpaulin --out Xml
|
USE_SKEPTIC=1 cargo tarpaulin --out Xml
|
||||||
bash <(curl -s https://codecov.io/bash)
|
bash <(curl -s https://codecov.io/bash)
|
||||||
echo "Uploaded code coverage"
|
echo "Uploaded code coverage"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Upload docs
|
||||||
|
after_success:
|
||||||
|
- |
|
||||||
|
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
||||||
|
cargo doc --features "alpn, tls, session" --no-deps &&
|
||||||
|
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
||||||
|
git clone https://github.com/davisp/ghp-import.git &&
|
||||||
|
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
|
||||||
|
echo "Uploaded documentation"
|
||||||
|
fi
|
||||||
|
185
CHANGES.md
185
CHANGES.md
@ -1,5 +1,190 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
## 0.6.1 (2018-05-08)
|
||||||
|
|
||||||
|
* Fix http/2 payload streaming #215
|
||||||
|
|
||||||
|
* Fix connector's default `keep-alive` and `lifetime` settings #212
|
||||||
|
|
||||||
|
* Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214
|
||||||
|
|
||||||
|
* Allow to exclude certain endpoints from logging #211
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0 (2018-05-08)
|
||||||
|
|
||||||
|
* Add route scopes #202
|
||||||
|
|
||||||
|
* Allow to use ssl and non-ssl connections at the same time #206
|
||||||
|
|
||||||
|
* Websocket CloseCode Empty/Status is ambiguous #193
|
||||||
|
|
||||||
|
* Add Content-Disposition to NamedFile #204
|
||||||
|
|
||||||
|
* Allow to access Error's backtrace object
|
||||||
|
|
||||||
|
* Allow to override files listing renderer for `StaticFiles` #203
|
||||||
|
|
||||||
|
* Various extractor usability improvements #207
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.6 (2018-04-24)
|
||||||
|
|
||||||
|
* Make flate2 crate optional #200
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.5 (2018-04-24)
|
||||||
|
|
||||||
|
* Fix panic when Websocket is closed with no error code #191
|
||||||
|
|
||||||
|
* Allow to use rust backend for flate2 crate #199
|
||||||
|
|
||||||
|
## 0.5.4 (2018-04-19)
|
||||||
|
|
||||||
|
* Add identity service middleware
|
||||||
|
|
||||||
|
* Middleware response() is not invoked if there was an error in async handler #187
|
||||||
|
|
||||||
|
* Use Display formatting for InternalError Display implementation #188
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.3 (2018-04-18)
|
||||||
|
|
||||||
|
* Impossible to quote slashes in path parameters #182
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.2 (2018-04-16)
|
||||||
|
|
||||||
|
* Allow to configure StaticFiles's CpuPool, via static method or env variable
|
||||||
|
|
||||||
|
* Add support for custom handling of Json extractor errors #181
|
||||||
|
|
||||||
|
* Fix StaticFiles does not support percent encoded paths #177
|
||||||
|
|
||||||
|
* Fix Client Request with custom Body Stream halting on certain size requests #176
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.1 (2018-04-12)
|
||||||
|
|
||||||
|
* Client connector provides stats, `ClientConnector::stats()`
|
||||||
|
|
||||||
|
* Fix end-of-stream handling in parse_payload #173
|
||||||
|
|
||||||
|
* Fix StaticFiles generate a lot of threads #174
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0 (2018-04-10)
|
||||||
|
|
||||||
|
* Type-safe path/query/form parameter handling, using serde #70
|
||||||
|
|
||||||
|
* HttpResponse builder's methods `.body()`, `.finish()`, `.json()`
|
||||||
|
return `HttpResponse` instead of `Result`
|
||||||
|
|
||||||
|
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()`
|
||||||
|
|
||||||
|
* Added `signed` and `private` `CookieSessionBackend`s
|
||||||
|
|
||||||
|
* Added `HttpRequest::resource()`, returns current matched resource
|
||||||
|
|
||||||
|
* Added `ErrorHandlers` middleware
|
||||||
|
|
||||||
|
* Fix router cannot parse Non-ASCII characters in URL #137
|
||||||
|
|
||||||
|
* Fix client connection pooling
|
||||||
|
|
||||||
|
* Fix long client urls #129
|
||||||
|
|
||||||
|
* Fix panic on invalid URL characters #130
|
||||||
|
|
||||||
|
* Fix logger request duration calculation #152
|
||||||
|
|
||||||
|
* Fix prefix and static file serving #168
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.10 (2018-03-20)
|
||||||
|
|
||||||
|
* Use `Error` instead of `InternalError` for `error::ErrorXXXX` methods
|
||||||
|
|
||||||
|
* Allow to set client request timeout
|
||||||
|
|
||||||
|
* Allow to set client websocket handshake timeout
|
||||||
|
|
||||||
|
* Refactor `TestServer` configuration
|
||||||
|
|
||||||
|
* Fix server websockets big payloads support
|
||||||
|
|
||||||
|
* Fix http/2 date header generation
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.9 (2018-03-16)
|
||||||
|
|
||||||
|
* Allow to disable http/2 support
|
||||||
|
|
||||||
|
* Wake payload reading task when data is available
|
||||||
|
|
||||||
|
* Fix server keep-alive handling
|
||||||
|
|
||||||
|
* Send Query Parameters in client requests #120
|
||||||
|
|
||||||
|
* Move brotli encoding to a feature
|
||||||
|
|
||||||
|
* Add option of default handler for `StaticFiles` handler #57
|
||||||
|
|
||||||
|
* Add basic client connection pooling
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.8 (2018-03-12)
|
||||||
|
|
||||||
|
* Allow to set read buffer capacity for server request
|
||||||
|
|
||||||
|
* Handle WouldBlock error for socket accept call
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.7 (2018-03-11)
|
||||||
|
|
||||||
|
* Fix panic on unknown content encoding
|
||||||
|
|
||||||
|
* Fix connection get closed too early
|
||||||
|
|
||||||
|
* Fix streaming response handling for http/2
|
||||||
|
|
||||||
|
* Better sleep on error support
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.6 (2018-03-10)
|
||||||
|
|
||||||
|
* Fix client cookie handling
|
||||||
|
|
||||||
|
* Fix json content type detection
|
||||||
|
|
||||||
|
* Fix CORS middleware #117
|
||||||
|
|
||||||
|
* Optimize websockets stream support
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.5 (2018-03-07)
|
||||||
|
|
||||||
|
* Fix compression #103 and #104
|
||||||
|
|
||||||
|
* Fix client cookie handling #111
|
||||||
|
|
||||||
|
* Non-blocking processing of a `NamedFile`
|
||||||
|
|
||||||
|
* Enable compression support for `NamedFile`
|
||||||
|
|
||||||
|
* Better support for `NamedFile` type
|
||||||
|
|
||||||
|
* Add `ResponseError` impl for `SendRequestError`. This improves ergonomics of the client.
|
||||||
|
|
||||||
|
* Add native-tls support for client
|
||||||
|
|
||||||
|
* Allow client connection timeout to be set #108
|
||||||
|
|
||||||
|
* Allow to use std::net::TcpListener for HttpServer
|
||||||
|
|
||||||
|
* Handle panics in worker threads
|
||||||
|
|
||||||
|
|
||||||
## 0.4.4 (2018-03-04)
|
## 0.4.4 (2018-03-04)
|
||||||
|
|
||||||
* Allow to use Arc<Vec<u8>> as response/request body
|
* Allow to use Arc<Vec<u8>> as response/request body
|
||||||
|
60
Cargo.toml
60
Cargo.toml
@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "0.4.4"
|
version = "0.6.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix web is a small, pragmatic, extremely fast, web framework for Rust."
|
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://github.com/actix/actix-web"
|
homepage = "https://github.com/actix/actix-web"
|
||||||
@ -13,8 +13,7 @@ categories = ["network-programming", "asynchronous",
|
|||||||
"web-programming::http-client",
|
"web-programming::http-client",
|
||||||
"web-programming::websocket"]
|
"web-programming::websocket"]
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
exclude = [".gitignore", ".travis.yml", ".cargo/config",
|
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||||
"appveyor.yml", "/examples/**"]
|
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
@ -27,20 +26,32 @@ name = "actix_web"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["session", "brotli", "flate2-c"]
|
||||||
|
|
||||||
# tls
|
# tls
|
||||||
tls = ["native-tls", "tokio-tls"]
|
tls = ["native-tls", "tokio-tls"]
|
||||||
|
|
||||||
# openssl
|
# openssl
|
||||||
alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"]
|
alpn = ["openssl", "tokio-openssl"]
|
||||||
|
|
||||||
|
# sessions feature, session require "ring" crate and c compiler
|
||||||
|
session = ["cookie/secure"]
|
||||||
|
|
||||||
|
# brotli encoding, requires c compiler
|
||||||
|
brotli = ["brotli2"]
|
||||||
|
|
||||||
|
# miniz-sys backend for flate2 crate
|
||||||
|
flate2-c = ["flate2/miniz-sys"]
|
||||||
|
|
||||||
|
# rust backend for flate2 crate
|
||||||
|
flate2-rust = ["flate2/rust_backend"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
actix = "^0.5.5"
|
||||||
|
|
||||||
base64 = "0.9"
|
base64 = "0.9"
|
||||||
bitflags = "1.0"
|
bitflags = "1.0"
|
||||||
brotli2 = "^0.3.2"
|
|
||||||
failure = "0.1.1"
|
failure = "0.1.1"
|
||||||
flate2 = "1.0"
|
|
||||||
h2 = "0.1"
|
h2 = "0.1"
|
||||||
http = "^0.1.5"
|
http = "^0.1.5"
|
||||||
httparse = "1.2"
|
httparse = "1.2"
|
||||||
@ -48,19 +59,24 @@ http-range = "0.1"
|
|||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
mime_guess = "1.8"
|
mime_guess = "2.0.0-alpha"
|
||||||
num_cpus = "1.0"
|
num_cpus = "1.0"
|
||||||
percent-encoding = "1.0"
|
percent-encoding = "1.0"
|
||||||
rand = "0.4"
|
rand = "0.4"
|
||||||
regex = "0.2"
|
regex = "1.0"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
serde_urlencoded = "0.5"
|
||||||
sha1 = "0.6"
|
sha1 = "0.6"
|
||||||
smallvec = "0.6"
|
smallvec = "0.6"
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
encoding = "0.2"
|
encoding = "0.2"
|
||||||
|
language-tags = "0.2"
|
||||||
|
lazy_static = "1.0"
|
||||||
url = { version="1.7", features=["query_encoding"] }
|
url = { version="1.7", features=["query_encoding"] }
|
||||||
cookie = { version="0.10", features=["percent-encode", "secure"] }
|
cookie = { version="0.10", features=["percent-encode"] }
|
||||||
|
brotli2 = { version="^0.3.2", optional = true }
|
||||||
|
flate2 = { version="1.0", optional = true, default-features = false }
|
||||||
|
|
||||||
# io
|
# io
|
||||||
mio = "^0.6.13"
|
mio = "^0.6.13"
|
||||||
@ -68,9 +84,10 @@ net2 = "0.2"
|
|||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
|
futures-cpupool = "0.1"
|
||||||
|
slab = "0.4"
|
||||||
tokio-io = "0.1"
|
tokio-io = "0.1"
|
||||||
tokio-core = "0.1"
|
tokio-core = "0.1"
|
||||||
trust-dns-resolver = "0.8"
|
|
||||||
|
|
||||||
# native-tls
|
# native-tls
|
||||||
native-tls = { version="0.1", optional = true }
|
native-tls = { version="0.1", optional = true }
|
||||||
@ -80,16 +97,11 @@ tokio-tls = { version="0.1", optional = true }
|
|||||||
openssl = { version="0.10", optional = true }
|
openssl = { version="0.10", optional = true }
|
||||||
tokio-openssl = { version="0.2", optional = true }
|
tokio-openssl = { version="0.2", optional = true }
|
||||||
|
|
||||||
[dependencies.actix]
|
|
||||||
version = "^0.5.1"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.5"
|
env_logger = "0.5"
|
||||||
skeptic = "0.13"
|
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
skeptic = "0.13"
|
|
||||||
version_check = "0.1"
|
version_check = "0.1"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
@ -100,19 +112,5 @@ codegen-units = 1
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"./",
|
"./",
|
||||||
"examples/basics",
|
|
||||||
"examples/juniper",
|
|
||||||
"examples/diesel",
|
|
||||||
"examples/r2d2",
|
|
||||||
"examples/json",
|
|
||||||
"examples/hello-world",
|
|
||||||
"examples/multipart",
|
|
||||||
"examples/state",
|
|
||||||
"examples/redis-session",
|
|
||||||
"examples/template_tera",
|
|
||||||
"examples/tls",
|
|
||||||
"examples/websocket",
|
|
||||||
"examples/websocket-chat",
|
|
||||||
"examples/web-cors/backend",
|
|
||||||
"tools/wsload/",
|
"tools/wsload/",
|
||||||
]
|
]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2017 Nikilay Kim
|
Copyright (c) 2017 Nikolay Kim
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any
|
Permission is hereby granted, free of charge, to any
|
||||||
person obtaining a copy of this software and associated
|
person obtaining a copy of this software and associated
|
||||||
|
80
MIGRATION.md
Normal file
80
MIGRATION.md
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
## Migration from 0.5 to 0.6
|
||||||
|
|
||||||
|
* `ws::Message::Close` now includes optional close reason.
|
||||||
|
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
|
||||||
|
|
||||||
|
* `HttpServer::threads()` renamed to `HttpServer::workers()`.
|
||||||
|
|
||||||
|
* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated.
|
||||||
|
Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead.
|
||||||
|
|
||||||
|
* `HttpRequest::extensions()` returns read only reference to the request's Extension
|
||||||
|
`HttpRequest::extensions_mut()` returns mutable reference.
|
||||||
|
|
||||||
|
* Instead of
|
||||||
|
|
||||||
|
`use actix_web::middleware::{
|
||||||
|
CookieSessionBackend, CookieSessionError, RequestSession,
|
||||||
|
Session, SessionBackend, SessionImpl, SessionStorage};`
|
||||||
|
|
||||||
|
use `actix_web::middleware::session`
|
||||||
|
|
||||||
|
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError,
|
||||||
|
RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
|
||||||
|
|
||||||
|
* `FromRequest::from_request()` accepts mutable reference to a request
|
||||||
|
|
||||||
|
* `FromRequest::Result` has to implement `Into<Reply<Self>>`
|
||||||
|
|
||||||
|
* [`Responder::respond_to()`](
|
||||||
|
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
|
||||||
|
is generic over `S`
|
||||||
|
|
||||||
|
* `HttpRequest::query()` is deprecated. Use `Query` extractor.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let q = Query::<HashMap<String, String>>::extract(req);
|
||||||
|
```
|
||||||
|
|
||||||
|
* Websocket operations are implemented as `WsWriter` trait.
|
||||||
|
you need to use `use actix_web::ws::WsWriter`
|
||||||
|
|
||||||
|
|
||||||
|
## Migration from 0.4 to 0.5
|
||||||
|
|
||||||
|
* `HttpResponseBuilder::body()`, `.finish()`, `.json()`
|
||||||
|
methods return `HttpResponse` instead of `Result<HttpResponse>`
|
||||||
|
|
||||||
|
* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version`
|
||||||
|
moved to `actix_web::http` module
|
||||||
|
|
||||||
|
* `actix_web::header` moved to `actix_web::http::header`
|
||||||
|
|
||||||
|
* `NormalizePath` moved to `actix_web::http` module
|
||||||
|
|
||||||
|
* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function,
|
||||||
|
shortcut for `actix_web::server::HttpServer::new()`
|
||||||
|
|
||||||
|
* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself
|
||||||
|
|
||||||
|
* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead.
|
||||||
|
|
||||||
|
* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type
|
||||||
|
|
||||||
|
* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()`
|
||||||
|
functions should be used instead
|
||||||
|
|
||||||
|
* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>`
|
||||||
|
instead of `Result<_, http::Error>`
|
||||||
|
|
||||||
|
* `Application` renamed to a `App`
|
||||||
|
|
||||||
|
* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev`
|
12
Makefile
12
Makefile
@ -10,17 +10,5 @@ build:
|
|||||||
test: build clippy
|
test: build clippy
|
||||||
cargo test $(CARGO_FLAGS)
|
cargo test $(CARGO_FLAGS)
|
||||||
|
|
||||||
skeptic:
|
|
||||||
USE_SKEPTIC=1 cargo test $(CARGO_FLAGS)
|
|
||||||
|
|
||||||
# cd examples/word-count && python setup.py install && pytest -v tests
|
|
||||||
|
|
||||||
clippy:
|
|
||||||
if $$CLIPPY; then cargo clippy $(CARGO_FLAGS); fi
|
|
||||||
|
|
||||||
doc: build
|
doc: build
|
||||||
cargo doc --no-deps $(CARGO_FLAGS)
|
cargo doc --no-deps $(CARGO_FLAGS)
|
||||||
cd guide; mdbook build -d ../target/doc/guide/; cd ..
|
|
||||||
|
|
||||||
book:
|
|
||||||
cd guide; mdbook build -d ../target/doc/guide/; cd ..
|
|
||||||
|
70
README.md
70
README.md
@ -1,46 +1,48 @@
|
|||||||
# Actix web [](https://travis-ci.org/actix/actix-web) [](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-web) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
# Actix web [](https://travis-ci.org/actix/actix-web) [](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-web) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
Actix web is a small, pragmatic, extremely fast, web framework for Rust.
|
Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
||||||
|
|
||||||
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.github.io/actix-web/guide/qs_13.html) protocols
|
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/book/actix-web/sec-12-http2.html) protocols
|
||||||
* Streaming and pipelining
|
* Streaming and pipelining
|
||||||
* Keep-alive and slow requests handling
|
* Keep-alive and slow requests handling
|
||||||
* Client/server [WebSockets](https://actix.github.io/actix-web/guide/qs_9.html) support
|
* Client/server [WebSockets](https://actix.rs/book/actix-web/sec-11-websockets.html) support
|
||||||
* Transparent content compression/decompression (br, gzip, deflate)
|
* Transparent content compression/decompression (br, gzip, deflate)
|
||||||
* Configurable [request routing](https://actix.github.io/actix-web/guide/qs_5.html)
|
* Configurable [request routing](https://actix.rs/book/actix-web/sec-6-url-dispatch.html)
|
||||||
* Graceful server shutdown
|
* Graceful server shutdown
|
||||||
* Multipart streams
|
* Multipart streams
|
||||||
* SSL support with openssl or native-tls
|
* Static assets
|
||||||
* Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging),
|
* SSL support with OpenSSL or `native-tls`
|
||||||
[Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions),
|
* Middlewares ([Logger](https://actix.rs/book/actix-web/sec-9-middlewares.html#logging),
|
||||||
|
[Session](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions),
|
||||||
[Redis sessions](https://github.com/actix/actix-redis),
|
[Redis sessions](https://github.com/actix/actix-redis),
|
||||||
[DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers),
|
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
|
||||||
[CORS](https://actix.github.io/actix-web/actix_web/middleware/cors/index.html))
|
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
|
||||||
* Built on top of [Actix actor framework](https://github.com/actix/actix).
|
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
|
||||||
|
* Built on top of [Actix actor framework](https://github.com/actix/actix)
|
||||||
|
|
||||||
## Documentation
|
## Documentation & community resources
|
||||||
|
|
||||||
* [User Guide](http://actix.github.io/actix-web/guide/)
|
* [User Guide](https://actix.rs/book/actix-web/)
|
||||||
* [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/)
|
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
|
||||||
* [API Documentation (Releases)](https://docs.rs/actix-web/)
|
* [API Documentation (Releases)](https://docs.rs/actix-web/)
|
||||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||||
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
||||||
* Minimum supported Rust version: 1.21 or later
|
* Minimum supported Rust version: 1.24 or later
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
```rust,ignore
|
```rust
|
||||||
extern crate actix_web;
|
extern crate actix_web;
|
||||||
use actix_web::*;
|
use actix_web::{http, server, App, Path};
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> String {
|
fn index(info: Path<(u32, String)>) -> String {
|
||||||
format!("Hello {}!", &req.match_info()["name"])
|
format!("Hello {}! id:{}", info.0, info.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
HttpServer::new(
|
server::new(
|
||||||
|| Application::new()
|
|| App::new()
|
||||||
.resource("/{name}", |r| r.f(index)))
|
.route("/{id}/{name}/index.html", http::Method::GET, index))
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
@ -48,19 +50,21 @@ fn main() {
|
|||||||
|
|
||||||
### More examples
|
### More examples
|
||||||
|
|
||||||
* [Basics](https://github.com/actix/actix-web/tree/master/examples/basics/)
|
* [Basics](https://github.com/actix/examples/tree/master/basics/)
|
||||||
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/)
|
* [Stateful](https://github.com/actix/examples/tree/master/state/)
|
||||||
* [Multipart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/)
|
* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/)
|
||||||
* [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket/)
|
* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/)
|
||||||
* [Tera templates](https://github.com/actix/actix-web/tree/master/examples/template_tera/)
|
* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/)
|
||||||
* [Diesel integration](https://github.com/actix/actix-web/tree/master/examples/diesel/)
|
* [Tera](https://github.com/actix/examples/tree/master/template_tera/) /
|
||||||
* [SSL / HTTP/2.0](https://github.com/actix/actix-web/tree/master/examples/tls/)
|
[Askama](https://github.com/actix/examples/tree/master/template_askama/) templates
|
||||||
* [Tcp/Websocket chat](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)
|
* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/)
|
||||||
* [SockJS Server](https://github.com/actix/actix-sockjs)
|
* [r2d2](https://github.com/actix/examples/tree/master/r2d2/)
|
||||||
* [Json](https://github.com/actix/actix-web/tree/master/examples/json/)
|
* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/)
|
||||||
|
* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/)
|
||||||
|
* [Json](https://github.com/actix/examples/tree/master/json/)
|
||||||
|
|
||||||
You may consider checking out
|
You may consider checking out
|
||||||
[this directory](https://github.com/actix/actix-web/tree/master/examples) for more examples.
|
[this directory](https://github.com/actix/examples/tree/master/) for more examples.
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
|
39
build.rs
39
build.rs
@ -1,44 +1,5 @@
|
|||||||
extern crate skeptic;
|
|
||||||
extern crate version_check;
|
extern crate version_check;
|
||||||
|
|
||||||
use std::{env, fs};
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn main() {
|
|
||||||
let f = env::var("OUT_DIR").unwrap() + "/skeptic-tests.rs";
|
|
||||||
if env::var("USE_SKEPTIC").is_ok() {
|
|
||||||
let _ = fs::remove_file(f);
|
|
||||||
// generates doc tests for `README.md`.
|
|
||||||
skeptic::generate_doc_tests(
|
|
||||||
&["README.md",
|
|
||||||
"guide/src/qs_1.md",
|
|
||||||
"guide/src/qs_2.md",
|
|
||||||
"guide/src/qs_3.md",
|
|
||||||
"guide/src/qs_3_5.md",
|
|
||||||
"guide/src/qs_4.md",
|
|
||||||
"guide/src/qs_4_5.md",
|
|
||||||
"guide/src/qs_5.md",
|
|
||||||
"guide/src/qs_7.md",
|
|
||||||
"guide/src/qs_8.md",
|
|
||||||
"guide/src/qs_9.md",
|
|
||||||
"guide/src/qs_10.md",
|
|
||||||
"guide/src/qs_12.md",
|
|
||||||
"guide/src/qs_13.md",
|
|
||||||
"guide/src/qs_14.md",
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
let _ = fs::File::create(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
match version_check::is_nightly() {
|
|
||||||
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
|
|
||||||
Some(false) => (),
|
|
||||||
None => (),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
fn main() {
|
fn main() {
|
||||||
match version_check::is_nightly() {
|
match version_check::is_nightly() {
|
||||||
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
|
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "basics"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
futures = "*"
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path="../.." }
|
|
@ -1,20 +0,0 @@
|
|||||||
# basics
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/basics
|
|
||||||
cargo run
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
- [http://localhost:8080/index.html](http://localhost:8080/index.html)
|
|
||||||
- [http://localhost:8080/async/bob](http://localhost:8080/async/bob)
|
|
||||||
- [http://localhost:8080/user/bob/](http://localhost:8080/user/bob/) plain/text download
|
|
||||||
- [http://localhost:8080/test](http://localhost:8080/test) (return status switch GET or POST or other)
|
|
||||||
- [http://localhost:8080/static/index.html](http://localhost:8080/static/index.html)
|
|
||||||
- [http://localhost:8080/static/notexit](http://localhost:8080/static/notexit) display 404 page
|
|
@ -1,151 +0,0 @@
|
|||||||
#![allow(unused_variables)]
|
|
||||||
#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))]
|
|
||||||
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate futures;
|
|
||||||
use futures::Stream;
|
|
||||||
|
|
||||||
use std::{io, env};
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::middleware::RequestSession;
|
|
||||||
use futures::future::{FutureResult, result};
|
|
||||||
|
|
||||||
/// favicon handler
|
|
||||||
fn favicon(req: HttpRequest) -> Result<fs::NamedFile> {
|
|
||||||
Ok(fs::NamedFile::open("../static/favicon.ico")?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// simple index handler
|
|
||||||
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
// example of ...
|
|
||||||
if let Ok(ch) = req.poll() {
|
|
||||||
if let futures::Async::Ready(Some(d)) = ch {
|
|
||||||
println!("{}", String::from_utf8_lossy(d.as_ref()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// session
|
|
||||||
let mut counter = 1;
|
|
||||||
if let Some(count) = req.session().get::<i32>("counter")? {
|
|
||||||
println!("SESSION value: {}", count);
|
|
||||||
counter = count + 1;
|
|
||||||
req.session().set("counter", counter)?;
|
|
||||||
} else {
|
|
||||||
req.session().set("counter", counter)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// html
|
|
||||||
let html = format!(r#"<!DOCTYPE html><html><head><title>actix - basics</title><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /></head>
|
|
||||||
<body>
|
|
||||||
<h1>Welcome <img width="30px" height="30px" src="/static/actixLogo.png" /></h1>
|
|
||||||
session counter = {}
|
|
||||||
</body>
|
|
||||||
</html>"#, counter);
|
|
||||||
|
|
||||||
// response
|
|
||||||
Ok(HttpResponse::build(StatusCode::OK)
|
|
||||||
.content_type("text/html; charset=utf-8")
|
|
||||||
.body(&html).unwrap())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 404 handler
|
|
||||||
fn p404(req: HttpRequest) -> Result<HttpResponse> {
|
|
||||||
|
|
||||||
// html
|
|
||||||
let html = r#"<!DOCTYPE html><html><head><title>actix - basics</title><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /></head>
|
|
||||||
<body>
|
|
||||||
<a href="index.html">back to home</a>
|
|
||||||
<h1>404</h1>
|
|
||||||
</body>
|
|
||||||
</html>"#;
|
|
||||||
|
|
||||||
// response
|
|
||||||
Ok(HttpResponse::build(StatusCode::NOT_FOUND)
|
|
||||||
.content_type("text/html; charset=utf-8")
|
|
||||||
.body(html).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// async handler
|
|
||||||
fn index_async(req: HttpRequest) -> FutureResult<HttpResponse, Error>
|
|
||||||
{
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
result(HttpResponse::Ok()
|
|
||||||
.content_type("text/html")
|
|
||||||
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))
|
|
||||||
.map_err(|e| e.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// handler with path parameters like `/user/{name}/`
|
|
||||||
fn with_param(req: HttpRequest) -> Result<HttpResponse>
|
|
||||||
{
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
|
||||||
.content_type("test/plain")
|
|
||||||
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
env::set_var("RUST_LOG", "actix_web=debug");
|
|
||||||
env::set_var("RUST_BACKTRACE", "1");
|
|
||||||
env_logger::init();
|
|
||||||
let sys = actix::System::new("basic-example");
|
|
||||||
|
|
||||||
let addr = HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
// cookie session middleware
|
|
||||||
.middleware(middleware::SessionStorage::new(
|
|
||||||
middleware::CookieSessionBackend::build(&[0; 32])
|
|
||||||
.secure(false)
|
|
||||||
.finish()
|
|
||||||
))
|
|
||||||
// register favicon
|
|
||||||
.resource("/favicon.ico", |r| r.f(favicon))
|
|
||||||
// register simple route, handle all methods
|
|
||||||
.resource("/index.html", |r| r.f(index))
|
|
||||||
// with path parameters
|
|
||||||
.resource("/user/{name}/", |r| r.method(Method::GET).f(with_param))
|
|
||||||
// async handler
|
|
||||||
.resource("/async/{name}", |r| r.method(Method::GET).a(index_async))
|
|
||||||
.resource("/test", |r| r.f(|req| {
|
|
||||||
match *req.method() {
|
|
||||||
Method::GET => httpcodes::HTTPOk,
|
|
||||||
Method::POST => httpcodes::HTTPMethodNotAllowed,
|
|
||||||
_ => httpcodes::HTTPNotFound,
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.resource("/error.html", |r| r.f(|req| {
|
|
||||||
error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "test"))
|
|
||||||
}))
|
|
||||||
// static files
|
|
||||||
.handler("/static/", fs::StaticFiles::new("../static/", true))
|
|
||||||
// redirect
|
|
||||||
.resource("/", |r| r.method(Method::GET).f(|req| {
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
HttpResponse::Found()
|
|
||||||
.header("LOCATION", "/index.html")
|
|
||||||
.finish()
|
|
||||||
}))
|
|
||||||
// default
|
|
||||||
.default_resource(|r| {
|
|
||||||
r.method(Method::GET).f(p404);
|
|
||||||
r.route().filter(pred::Not(pred::Get())).f(|req| httpcodes::HTTPMethodNotAllowed);
|
|
||||||
}))
|
|
||||||
|
|
||||||
.bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080")
|
|
||||||
.shutdown_timeout(0) // <- Set shutdown timeout to 0 seconds (default 60s)
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Starting http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
DATABASE_URL=file:test.db
|
|
@ -1,19 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "diesel-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../" }
|
|
||||||
|
|
||||||
futures = "0.1"
|
|
||||||
uuid = { version = "0.5", features = ["serde", "v4"] }
|
|
||||||
serde = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
|
|
||||||
diesel = { version = "1.0.0-beta1", features = ["sqlite"] }
|
|
||||||
dotenv = "0.10"
|
|
@ -1,43 +0,0 @@
|
|||||||
# diesel
|
|
||||||
|
|
||||||
Diesel's `Getting Started` guide using SQLite for Actix web
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### init database sqlite
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install diesel_cli --no-default-features --features sqlite
|
|
||||||
cd actix-web/examples/diesel
|
|
||||||
echo "DATABASE_URL=file:test.db" > .env
|
|
||||||
diesel migration run
|
|
||||||
```
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# if ubuntu : sudo apt-get install libsqlite3-dev
|
|
||||||
# if fedora : sudo dnf install libsqlite3x-devel
|
|
||||||
cd actix-web/examples/diesel
|
|
||||||
cargo run (or ``cargo watch -x run``)
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
[http://127.0.0.1:8080/NAME](http://127.0.0.1:8080/NAME)
|
|
||||||
|
|
||||||
### sqlite client
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# if ubuntu : sudo apt-get install sqlite3
|
|
||||||
# if fedora : sudo dnf install sqlite3x
|
|
||||||
sqlite3 test.db
|
|
||||||
sqlite> .tables
|
|
||||||
sqlite> select * from users;
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Postgresql
|
|
||||||
|
|
||||||
You will also find another complete example of diesel+postgresql on [https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/actix](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/actix)
|
|
@ -1 +0,0 @@
|
|||||||
DROP TABLE users
|
|
@ -1,4 +0,0 @@
|
|||||||
CREATE TABLE users (
|
|
||||||
id VARCHAR NOT NULL PRIMARY KEY,
|
|
||||||
name VARCHAR NOT NULL
|
|
||||||
)
|
|
@ -1,52 +0,0 @@
|
|||||||
//! Db executor actor
|
|
||||||
use uuid;
|
|
||||||
use diesel;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix::prelude::*;
|
|
||||||
use diesel::prelude::*;
|
|
||||||
|
|
||||||
use models;
|
|
||||||
use schema;
|
|
||||||
|
|
||||||
/// This is db executor actor. We are going to run 3 of them in parallel.
|
|
||||||
pub struct DbExecutor(pub SqliteConnection);
|
|
||||||
|
|
||||||
/// This is only message that this actor can handle, but it is easy to extend number of
|
|
||||||
/// messages.
|
|
||||||
pub struct CreateUser {
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message for CreateUser {
|
|
||||||
type Result = Result<models::User, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for DbExecutor {
|
|
||||||
type Context = SyncContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler<CreateUser> for DbExecutor {
|
|
||||||
type Result = Result<models::User, Error>;
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result {
|
|
||||||
use self::schema::users::dsl::*;
|
|
||||||
|
|
||||||
let uuid = format!("{}", uuid::Uuid::new_v4());
|
|
||||||
let new_user = models::NewUser {
|
|
||||||
id: &uuid,
|
|
||||||
name: &msg.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
diesel::insert_into(users)
|
|
||||||
.values(&new_user)
|
|
||||||
.execute(&self.0)
|
|
||||||
.expect("Error inserting person");
|
|
||||||
|
|
||||||
let mut items = users
|
|
||||||
.filter(id.eq(&uuid))
|
|
||||||
.load::<models::User>(&self.0)
|
|
||||||
.expect("Error loading person");
|
|
||||||
|
|
||||||
Ok(items.pop().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
//! Actix web diesel example
|
|
||||||
//!
|
|
||||||
//! Diesel does not support tokio, so we have to run it in separate threads.
|
|
||||||
//! Actix supports sync actors by default, so we going to create sync actor that will
|
|
||||||
//! use diesel. Technically sync actors are worker style actors, multiple of them
|
|
||||||
//! can run in parallel and process messages from same queue.
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate diesel;
|
|
||||||
extern crate uuid;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
use diesel::prelude::*;
|
|
||||||
use futures::future::Future;
|
|
||||||
|
|
||||||
mod db;
|
|
||||||
mod models;
|
|
||||||
mod schema;
|
|
||||||
|
|
||||||
use db::{CreateUser, DbExecutor};
|
|
||||||
|
|
||||||
|
|
||||||
/// State with DbExecutor address
|
|
||||||
struct State {
|
|
||||||
db: Addr<Syn, DbExecutor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Async request handler
|
|
||||||
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
let name = &req.match_info()["name"];
|
|
||||||
|
|
||||||
req.state().db.send(CreateUser{name: name.to_owned()})
|
|
||||||
.from_err()
|
|
||||||
.and_then(|res| {
|
|
||||||
match res {
|
|
||||||
Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?),
|
|
||||||
Err(_) => Ok(httpcodes::HTTPInternalServerError.into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("diesel-example");
|
|
||||||
|
|
||||||
// Start db executor actors
|
|
||||||
let addr = SyncArbiter::start(3, || {
|
|
||||||
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start http server
|
|
||||||
let _addr = HttpServer::new(move || {
|
|
||||||
Application::with_state(State{db: addr.clone()})
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/{name}", |r| r.method(Method::GET).a(index))})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
use super::schema::users;
|
|
||||||
|
|
||||||
#[derive(Serialize, Queryable)]
|
|
||||||
pub struct User {
|
|
||||||
pub id: String,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Insertable)]
|
|
||||||
#[table_name = "users"]
|
|
||||||
pub struct NewUser<'a> {
|
|
||||||
pub id: &'a str,
|
|
||||||
pub name: &'a str,
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
table! {
|
|
||||||
users (id) {
|
|
||||||
id -> Text,
|
|
||||||
name -> Text,
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
@ -1,10 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "hello-world"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../" }
|
|
@ -1,28 +0,0 @@
|
|||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
|
|
||||||
fn index(_req: HttpRequest) -> &'static str {
|
|
||||||
"Hello world!"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("ws-example");
|
|
||||||
|
|
||||||
let _addr = HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/index.html", |r| r.f(|_| "Hello world!"))
|
|
||||||
.resource("/", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "json-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bytes = "0.4"
|
|
||||||
futures = "0.1"
|
|
||||||
env_logger = "*"
|
|
||||||
|
|
||||||
serde = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
json = "*"
|
|
||||||
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path="../../" }
|
|
@ -1,48 +0,0 @@
|
|||||||
# json
|
|
||||||
|
|
||||||
Json's `Getting Started` guide using json (serde-json or json-rust) for Actix web
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/json
|
|
||||||
cargo run
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
With [Postman](https://www.getpostman.com/) or [Rested](moz-extension://60daeb1c-5b1b-4afd-9842-0579ed34dfcb/dist/index.html)
|
|
||||||
|
|
||||||
- POST / (embed serde-json):
|
|
||||||
|
|
||||||
- method : ``POST``
|
|
||||||
- url : ``http://127.0.0.1:8080/``
|
|
||||||
- header : ``Content-Type`` = ``application/json``
|
|
||||||
- body (raw) : ``{"name": "Test user", "number": 100}``
|
|
||||||
|
|
||||||
- POST /manual (manual serde-json):
|
|
||||||
|
|
||||||
- method : ``POST``
|
|
||||||
- url : ``http://127.0.0.1:8080/manual``
|
|
||||||
- header : ``Content-Type`` = ``application/json``
|
|
||||||
- body (raw) : ``{"name": "Test user", "number": 100}``
|
|
||||||
|
|
||||||
- POST /mjsonrust (manual json-rust):
|
|
||||||
|
|
||||||
- method : ``POST``
|
|
||||||
- url : ``http://127.0.0.1:8080/mjsonrust``
|
|
||||||
- header : ``Content-Type`` = ``application/json``
|
|
||||||
- body (raw) : ``{"name": "Test user", "number": 100}`` (you can also test ``{notjson}``)
|
|
||||||
|
|
||||||
### python client
|
|
||||||
|
|
||||||
- ``pip install aiohttp``
|
|
||||||
- ``python client.py``
|
|
||||||
|
|
||||||
if ubuntu :
|
|
||||||
|
|
||||||
- ``pip3 install aiohttp``
|
|
||||||
- ``python3 client.py``
|
|
@ -1,18 +0,0 @@
|
|||||||
# This script could be used for actix-web multipart example test
|
|
||||||
# just start server and run client.py
|
|
||||||
|
|
||||||
import json
|
|
||||||
import asyncio
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
async def req():
|
|
||||||
resp = await aiohttp.ClientSession().request(
|
|
||||||
"post", 'http://localhost:8080/',
|
|
||||||
data=json.dumps({"name": "Test user", "number": 100}),
|
|
||||||
headers={"content-type": "application/json"})
|
|
||||||
print(str(resp))
|
|
||||||
print(await resp.text())
|
|
||||||
assert 200 == resp.status
|
|
||||||
|
|
||||||
|
|
||||||
asyncio.get_event_loop().run_until_complete(req())
|
|
@ -1,99 +0,0 @@
|
|||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate bytes;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate serde_json;
|
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
#[macro_use] extern crate json;
|
|
||||||
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
use bytes::BytesMut;
|
|
||||||
use futures::{Future, Stream};
|
|
||||||
use json::JsonValue;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct MyObj {
|
|
||||||
name: String,
|
|
||||||
number: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This handler uses `HttpRequest::json()` for loading serde json object.
|
|
||||||
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
req.json()
|
|
||||||
.from_err() // convert all errors into `Error`
|
|
||||||
.and_then(|val: MyObj| {
|
|
||||||
println!("model: {:?}", val);
|
|
||||||
Ok(httpcodes::HTTPOk.build().json(val)?) // <- send response
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const MAX_SIZE: usize = 262_144; // max payload size is 256k
|
|
||||||
|
|
||||||
/// This handler manually load request payload and parse serde json
|
|
||||||
fn index_manual(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
// HttpRequest is stream of Bytes objects
|
|
||||||
req
|
|
||||||
// `Future::from_err` acts like `?` in that it coerces the error type from
|
|
||||||
// the future into the final error type
|
|
||||||
.from_err()
|
|
||||||
|
|
||||||
// `fold` will asynchronously read each chunk of the request body and
|
|
||||||
// call supplied closure, then it resolves to result of closure
|
|
||||||
.fold(BytesMut::new(), move |mut body, chunk| {
|
|
||||||
// limit max size of in-memory payload
|
|
||||||
if (body.len() + chunk.len()) > MAX_SIZE {
|
|
||||||
Err(error::ErrorBadRequest("overflow"))
|
|
||||||
} else {
|
|
||||||
body.extend_from_slice(&chunk);
|
|
||||||
Ok(body)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// `Future::and_then` can be used to merge an asynchronous workflow with a
|
|
||||||
// synchronous workflow
|
|
||||||
.and_then(|body| {
|
|
||||||
// body is loaded, now we can deserialize serde-json
|
|
||||||
let obj = serde_json::from_slice::<MyObj>(&body)?;
|
|
||||||
Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This handler manually load request payload and parse json-rust
|
|
||||||
fn index_mjsonrust(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
req.concat2()
|
|
||||||
.from_err()
|
|
||||||
.and_then(|body| {
|
|
||||||
// body is loaded, now we can deserialize json-rust
|
|
||||||
let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result
|
|
||||||
let injson: JsonValue = match result { Ok(v) => v, Err(e) => object!{"err" => e.to_string() } };
|
|
||||||
Ok(HttpResponse::build(StatusCode::OK)
|
|
||||||
.content_type("application/json")
|
|
||||||
.body(injson.dump()).unwrap())
|
|
||||||
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("json-example");
|
|
||||||
|
|
||||||
let addr = HttpServer::new(|| {
|
|
||||||
Application::new()
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/manual", |r| r.method(Method::POST).f(index_manual))
|
|
||||||
.resource("/mjsonrust", |r| r.method(Method::POST).f(index_mjsonrust))
|
|
||||||
.resource("/", |r| r.method(Method::POST).f(index))})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.shutdown_timeout(1)
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "juniper-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["pyros2097 <pyros2097@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../" }
|
|
||||||
|
|
||||||
futures = "0.1"
|
|
||||||
serde = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
|
|
||||||
juniper = "0.9.2"
|
|
@ -1,15 +0,0 @@
|
|||||||
# juniper
|
|
||||||
|
|
||||||
Juniper integration for Actix web
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/juniper
|
|
||||||
cargo run (or ``cargo watch -x run``)
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
[http://127.0.0.1:8080/graphiql](http://127.0.0.1:8080/graphiql)
|
|
@ -1,110 +0,0 @@
|
|||||||
//! Actix web juniper example
|
|
||||||
//!
|
|
||||||
//! A simple example integrating juniper in actix-web
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate juniper;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
use juniper::http::graphiql::graphiql_source;
|
|
||||||
use juniper::http::GraphQLRequest;
|
|
||||||
|
|
||||||
use futures::future::Future;
|
|
||||||
|
|
||||||
mod schema;
|
|
||||||
|
|
||||||
use schema::Schema;
|
|
||||||
use schema::create_schema;
|
|
||||||
|
|
||||||
struct State {
|
|
||||||
executor: Addr<Syn, GraphQLExecutor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct GraphQLData(GraphQLRequest);
|
|
||||||
|
|
||||||
impl Message for GraphQLData {
|
|
||||||
type Result = Result<String, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GraphQLExecutor {
|
|
||||||
schema: std::sync::Arc<Schema>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GraphQLExecutor {
|
|
||||||
fn new(schema: std::sync::Arc<Schema>) -> GraphQLExecutor {
|
|
||||||
GraphQLExecutor {
|
|
||||||
schema: schema,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for GraphQLExecutor {
|
|
||||||
type Context = SyncContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler<GraphQLData> for GraphQLExecutor {
|
|
||||||
type Result = Result<String, Error>;
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: GraphQLData, _: &mut Self::Context) -> Self::Result {
|
|
||||||
let res = msg.0.execute(&self.schema, &());
|
|
||||||
let res_text = serde_json::to_string(&res)?;
|
|
||||||
Ok(res_text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn graphiql(_req: HttpRequest<State>) -> Result<HttpResponse> {
|
|
||||||
let html = graphiql_source("http://127.0.0.1:8080/graphql");
|
|
||||||
Ok(HttpResponse::build(StatusCode::OK)
|
|
||||||
.content_type("text/html; charset=utf-8")
|
|
||||||
.body(html).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn graphql(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
let executor = req.state().executor.clone();
|
|
||||||
req.json()
|
|
||||||
.from_err()
|
|
||||||
.and_then(move |val: GraphQLData| {
|
|
||||||
executor.send(val)
|
|
||||||
.from_err()
|
|
||||||
.and_then(|res| {
|
|
||||||
match res {
|
|
||||||
Ok(user) => Ok(httpcodes::HTTPOk.build().body(user)?),
|
|
||||||
Err(_) => Ok(httpcodes::HTTPInternalServerError.into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("juniper-example");
|
|
||||||
|
|
||||||
let schema = std::sync::Arc::new(create_schema());
|
|
||||||
let addr = SyncArbiter::start(3, move || {
|
|
||||||
GraphQLExecutor::new(schema.clone())
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start http server
|
|
||||||
let _addr = HttpServer::new(move || {
|
|
||||||
Application::with_state(State{executor: addr.clone()})
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/graphql", |r| r.method(Method::POST).a(graphql))
|
|
||||||
.resource("/graphiql", |r| r.method(Method::GET).f(graphiql))})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
use juniper::FieldResult;
|
|
||||||
use juniper::RootNode;
|
|
||||||
|
|
||||||
#[derive(GraphQLEnum)]
|
|
||||||
enum Episode {
|
|
||||||
NewHope,
|
|
||||||
Empire,
|
|
||||||
Jedi,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
|
||||||
#[graphql(description = "A humanoid creature in the Star Wars universe")]
|
|
||||||
struct Human {
|
|
||||||
id: String,
|
|
||||||
name: String,
|
|
||||||
appears_in: Vec<Episode>,
|
|
||||||
home_planet: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(GraphQLInputObject)]
|
|
||||||
#[graphql(description = "A humanoid creature in the Star Wars universe")]
|
|
||||||
struct NewHuman {
|
|
||||||
name: String,
|
|
||||||
appears_in: Vec<Episode>,
|
|
||||||
home_planet: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct QueryRoot;
|
|
||||||
|
|
||||||
graphql_object!(QueryRoot: () |&self| {
|
|
||||||
field human(&executor, id: String) -> FieldResult<Human> {
|
|
||||||
Ok(Human{
|
|
||||||
id: "1234".to_owned(),
|
|
||||||
name: "Luke".to_owned(),
|
|
||||||
appears_in: vec![Episode::NewHope],
|
|
||||||
home_planet: "Mars".to_owned(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pub struct MutationRoot;
|
|
||||||
|
|
||||||
graphql_object!(MutationRoot: () |&self| {
|
|
||||||
field createHuman(&executor, new_human: NewHuman) -> FieldResult<Human> {
|
|
||||||
Ok(Human{
|
|
||||||
id: "1234".to_owned(),
|
|
||||||
name: new_human.name,
|
|
||||||
appears_in: new_human.appears_in,
|
|
||||||
home_planet: new_human.home_planet,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pub type Schema = RootNode<'static, QueryRoot, MutationRoot>;
|
|
||||||
|
|
||||||
pub fn create_schema() -> Schema {
|
|
||||||
Schema::new(QueryRoot {}, MutationRoot {})
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "multipart-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "multipart"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "*"
|
|
||||||
futures = "0.1"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path="../../" }
|
|
@ -1,24 +0,0 @@
|
|||||||
# multipart
|
|
||||||
|
|
||||||
Multipart's `Getting Started` guide for Actix web
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/multipart
|
|
||||||
cargo run (or ``cargo watch -x run``)
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### client
|
|
||||||
|
|
||||||
- ``pip install aiohttp``
|
|
||||||
- ``python client.py``
|
|
||||||
- you must see in server console multipart fields
|
|
||||||
|
|
||||||
if ubuntu :
|
|
||||||
|
|
||||||
- ``pip3 install aiohttp``
|
|
||||||
- ``python3 client.py``
|
|
@ -1,34 +0,0 @@
|
|||||||
# This script could be used for actix-web multipart example test
|
|
||||||
# just start server and run client.py
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
async def req1():
|
|
||||||
with aiohttp.MultipartWriter() as writer:
|
|
||||||
writer.append('test')
|
|
||||||
writer.append_json({'passed': True})
|
|
||||||
|
|
||||||
resp = await aiohttp.ClientSession().request(
|
|
||||||
"post", 'http://localhost:8080/multipart',
|
|
||||||
data=writer, headers=writer.headers)
|
|
||||||
print(resp)
|
|
||||||
assert 200 == resp.status
|
|
||||||
|
|
||||||
|
|
||||||
async def req2():
|
|
||||||
with aiohttp.MultipartWriter() as writer:
|
|
||||||
writer.append('test')
|
|
||||||
writer.append_json({'passed': True})
|
|
||||||
writer.append(open('src/main.rs'))
|
|
||||||
|
|
||||||
resp = await aiohttp.ClientSession().request(
|
|
||||||
"post", 'http://localhost:8080/multipart',
|
|
||||||
data=writer, headers=writer.headers)
|
|
||||||
print(resp)
|
|
||||||
assert 200 == resp.status
|
|
||||||
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.run_until_complete(req1())
|
|
||||||
loop.run_until_complete(req2())
|
|
@ -1,59 +0,0 @@
|
|||||||
#![allow(unused_variables)]
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate futures;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
use futures::{Future, Stream};
|
|
||||||
use futures::future::{result, Either};
|
|
||||||
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>>
|
|
||||||
{
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
req.multipart() // <- get multipart stream for current request
|
|
||||||
.from_err() // <- convert multipart errors
|
|
||||||
.and_then(|item| { // <- iterate over multipart items
|
|
||||||
match item {
|
|
||||||
// Handle multipart Field
|
|
||||||
multipart::MultipartItem::Field(field) => {
|
|
||||||
println!("==== FIELD ==== {:?}", field);
|
|
||||||
|
|
||||||
// Field in turn is stream of *Bytes* object
|
|
||||||
Either::A(
|
|
||||||
field.map_err(Error::from)
|
|
||||||
.map(|chunk| {
|
|
||||||
println!("-- CHUNK: \n{}",
|
|
||||||
std::str::from_utf8(&chunk).unwrap());})
|
|
||||||
.finish())
|
|
||||||
},
|
|
||||||
multipart::MultipartItem::Nested(mp) => {
|
|
||||||
// Or item could be nested Multipart stream
|
|
||||||
Either::B(result(Ok(())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finish() // <- Stream::finish() combinator from actix
|
|
||||||
.map(|_| httpcodes::HTTPOk.into())
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("multipart-example");
|
|
||||||
|
|
||||||
let addr = HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.middleware(middleware::Logger::default()) // <- logger
|
|
||||||
.resource("/multipart", |r| r.method(Method::POST).a(index)))
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Starting http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "r2d2-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../" }
|
|
||||||
|
|
||||||
futures = "0.1"
|
|
||||||
uuid = { version = "0.5", features = ["serde", "v4"] }
|
|
||||||
serde = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
|
|
||||||
r2d2 = "*"
|
|
||||||
r2d2_sqlite = "*"
|
|
||||||
rusqlite = "*"
|
|
@ -1,41 +0,0 @@
|
|||||||
//! Db executor actor
|
|
||||||
use std::io;
|
|
||||||
use uuid;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix::prelude::*;
|
|
||||||
use r2d2::Pool;
|
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
|
||||||
|
|
||||||
|
|
||||||
/// This is db executor actor. We are going to run 3 of them in parallel.
|
|
||||||
pub struct DbExecutor(pub Pool<SqliteConnectionManager>);
|
|
||||||
|
|
||||||
/// This is only message that this actor can handle, but it is easy to extend number of
|
|
||||||
/// messages.
|
|
||||||
pub struct CreateUser {
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message for CreateUser {
|
|
||||||
type Result = Result<String, io::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for DbExecutor {
|
|
||||||
type Context = SyncContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler<CreateUser> for DbExecutor {
|
|
||||||
type Result = Result<String, io::Error>;
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result {
|
|
||||||
let conn = self.0.get().unwrap();
|
|
||||||
|
|
||||||
let uuid = format!("{}", uuid::Uuid::new_v4());
|
|
||||||
conn.execute("INSERT INTO users (id, name) VALUES ($1, $2)",
|
|
||||||
&[&uuid, &msg.name]).unwrap();
|
|
||||||
|
|
||||||
Ok(conn.query_row("SELECT name FROM users WHERE id=$1", &[&uuid], |row| {
|
|
||||||
row.get(0)
|
|
||||||
}).map_err(|_| io::Error::new(io::ErrorKind::Other, "db error"))?)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
//! Actix web r2d2 example
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
extern crate uuid;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate r2d2;
|
|
||||||
extern crate r2d2_sqlite;
|
|
||||||
extern crate rusqlite;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
use futures::future::Future;
|
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
|
||||||
|
|
||||||
mod db;
|
|
||||||
use db::{CreateUser, DbExecutor};
|
|
||||||
|
|
||||||
|
|
||||||
/// State with DbExecutor address
|
|
||||||
struct State {
|
|
||||||
db: Addr<Syn, DbExecutor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Async request handler
|
|
||||||
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
let name = &req.match_info()["name"];
|
|
||||||
|
|
||||||
req.state().db.send(CreateUser{name: name.to_owned()})
|
|
||||||
.from_err()
|
|
||||||
.and_then(|res| {
|
|
||||||
match res {
|
|
||||||
Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?),
|
|
||||||
Err(_) => Ok(httpcodes::HTTPInternalServerError.into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=debug");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("r2d2-example");
|
|
||||||
|
|
||||||
// r2d2 pool
|
|
||||||
let manager = SqliteConnectionManager::file("test.db");
|
|
||||||
let pool = r2d2::Pool::new(manager).unwrap();
|
|
||||||
|
|
||||||
// Start db executor actors
|
|
||||||
let addr = SyncArbiter::start(3, move || DbExecutor(pool.clone()));
|
|
||||||
|
|
||||||
// Start http server
|
|
||||||
let _addr = HttpServer::new(move || {
|
|
||||||
Application::with_state(State{db: addr.clone()})
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/{name}", |r| r.method(Method::GET).a(index))})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
Binary file not shown.
@ -1,11 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "redis-session"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = "0.4"
|
|
||||||
actix-redis = { version = "0.2", features = ["web"] }
|
|
@ -1,48 +0,0 @@
|
|||||||
#![allow(unused_variables)]
|
|
||||||
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate actix_redis;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::middleware::RequestSession;
|
|
||||||
use actix_redis::RedisSessionBackend;
|
|
||||||
|
|
||||||
|
|
||||||
/// simple handler
|
|
||||||
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
// session
|
|
||||||
if let Some(count) = req.session().get::<i32>("counter")? {
|
|
||||||
println!("SESSION value: {}", count);
|
|
||||||
req.session().set("counter", count+1)?;
|
|
||||||
} else {
|
|
||||||
req.session().set("counter", 1)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok("Welcome!".into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info");
|
|
||||||
env_logger::init();
|
|
||||||
let sys = actix::System::new("basic-example");
|
|
||||||
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
// cookie session middleware
|
|
||||||
.middleware(middleware::SessionStorage::new(
|
|
||||||
RedisSessionBackend::new("127.0.0.1:6379", &[0; 32])
|
|
||||||
))
|
|
||||||
// register simple route, handle all methods
|
|
||||||
.resource("/", |r| r.f(index)))
|
|
||||||
.bind("0.0.0.0:8080").unwrap()
|
|
||||||
.threads(1)
|
|
||||||
.start();
|
|
||||||
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "state"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
futures = "*"
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../" }
|
|
@ -1,15 +0,0 @@
|
|||||||
# state
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/state
|
|
||||||
cargo run
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
- [http://localhost:8080/](http://localhost:8080/)
|
|
@ -1,76 +0,0 @@
|
|||||||
#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))]
|
|
||||||
//! There are two level of statefulness in actix-web. Application has state
|
|
||||||
//! that is shared across all handlers within same Application.
|
|
||||||
//! And individual handler can have state.
|
|
||||||
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use std::cell::Cell;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
/// Application state
|
|
||||||
struct AppState {
|
|
||||||
counter: Cell<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// somple handle
|
|
||||||
fn index(req: HttpRequest<AppState>) -> HttpResponse {
|
|
||||||
println!("{:?}", req);
|
|
||||||
req.state().counter.set(req.state().counter.get() + 1);
|
|
||||||
|
|
||||||
httpcodes::HTTPOk.with_body(
|
|
||||||
format!("Num of requests: {}", req.state().counter.get()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `MyWebSocket` counts how many messages it receives from peer,
|
|
||||||
/// websocket-client.py could be used for tests
|
|
||||||
struct MyWebSocket {
|
|
||||||
counter: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for MyWebSocket {
|
|
||||||
type Context = ws::WebsocketContext<Self, AppState>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
|
||||||
self.counter += 1;
|
|
||||||
println!("WS({}): {:?}", self.counter, msg);
|
|
||||||
match msg {
|
|
||||||
ws::Message::Ping(msg) => ctx.pong(&msg),
|
|
||||||
ws::Message::Text(text) => ctx.text(text),
|
|
||||||
ws::Message::Binary(bin) => ctx.binary(bin),
|
|
||||||
ws::Message::Close(_) => {
|
|
||||||
ctx.stop();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("ws-example");
|
|
||||||
|
|
||||||
let addr = HttpServer::new(
|
|
||||||
|| Application::with_state(AppState{counter: Cell::new(0)})
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
// websocket route
|
|
||||||
.resource(
|
|
||||||
"/ws/", |r|
|
|
||||||
r.method(Method::GET).f(|req| ws::start(req, MyWebSocket{counter: 0})))
|
|
||||||
// register simple handler, handle all methods
|
|
||||||
.resource("/", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
@ -1,90 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
|
|
||||||
</script>
|
|
||||||
<script language="javascript" type="text/javascript">
|
|
||||||
$(function() {
|
|
||||||
var conn = null;
|
|
||||||
function log(msg) {
|
|
||||||
var control = $('#log');
|
|
||||||
control.html(control.html() + msg + '<br/>');
|
|
||||||
control.scrollTop(control.scrollTop() + 1000);
|
|
||||||
}
|
|
||||||
function connect() {
|
|
||||||
disconnect();
|
|
||||||
var wsUri = (window.location.protocol=='https:'&&'wss://'||'ws://')+window.location.host + '/ws/';
|
|
||||||
conn = new WebSocket(wsUri);
|
|
||||||
log('Connecting...');
|
|
||||||
conn.onopen = function() {
|
|
||||||
log('Connected.');
|
|
||||||
update_ui();
|
|
||||||
};
|
|
||||||
conn.onmessage = function(e) {
|
|
||||||
log('Received: ' + e.data);
|
|
||||||
};
|
|
||||||
conn.onclose = function() {
|
|
||||||
log('Disconnected.');
|
|
||||||
conn = null;
|
|
||||||
update_ui();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function disconnect() {
|
|
||||||
if (conn != null) {
|
|
||||||
log('Disconnecting...');
|
|
||||||
conn.close();
|
|
||||||
conn = null;
|
|
||||||
update_ui();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function update_ui() {
|
|
||||||
var msg = '';
|
|
||||||
if (conn == null) {
|
|
||||||
$('#status').text('disconnected');
|
|
||||||
$('#connect').html('Connect');
|
|
||||||
} else {
|
|
||||||
$('#status').text('connected (' + conn.protocol + ')');
|
|
||||||
$('#connect').html('Disconnect');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$('#connect').click(function() {
|
|
||||||
if (conn == null) {
|
|
||||||
connect();
|
|
||||||
} else {
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
update_ui();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
$('#send').click(function() {
|
|
||||||
var text = $('#text').val();
|
|
||||||
log('Sending: ' + text);
|
|
||||||
conn.send(text);
|
|
||||||
$('#text').val('').focus();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
$('#text').keyup(function(e) {
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
$('#send').click();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h3>Chat!</h3>
|
|
||||||
<div>
|
|
||||||
<button id="connect">Connect</button> | Status:
|
|
||||||
<span id="status">disconnected</span>
|
|
||||||
</div>
|
|
||||||
<div id="log"
|
|
||||||
style="width:20em;height:15em;overflow:auto;border:1px solid black">
|
|
||||||
</div>
|
|
||||||
<form id="chatform" onsubmit="return false;">
|
|
||||||
<input id="text" type="text" />
|
|
||||||
<input id="send" type="button" value="Send" />
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,11 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "template-tera"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../" }
|
|
||||||
tera = "*"
|
|
@ -1,17 +0,0 @@
|
|||||||
# template_tera
|
|
||||||
|
|
||||||
Minimal example of using the template [tera](https://github.com/Keats/tera) that displays a form.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/template_tera
|
|
||||||
cargo run (or ``cargo watch -x run``)
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
- [http://localhost:8080](http://localhost:8080)
|
|
@ -1,47 +0,0 @@
|
|||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate tera;
|
|
||||||
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
|
|
||||||
struct State {
|
|
||||||
template: tera::Tera, // <- store tera template in application state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(req: HttpRequest<State>) -> Result<HttpResponse> {
|
|
||||||
let s = if let Some(name) = req.query().get("name") { // <- submitted form
|
|
||||||
let mut ctx = tera::Context::new();
|
|
||||||
ctx.add("name", &name.to_owned());
|
|
||||||
ctx.add("text", &"Welcome!".to_owned());
|
|
||||||
req.state().template.render("user.html", &ctx)
|
|
||||||
.map_err(|_| error::ErrorInternalServerError("Template error"))?
|
|
||||||
} else {
|
|
||||||
req.state().template.render("index.html", &tera::Context::new())
|
|
||||||
.map_err(|_| error::ErrorInternalServerError("Template error"))?
|
|
||||||
};
|
|
||||||
Ok(httpcodes::HTTPOk.build()
|
|
||||||
.content_type("text/html")
|
|
||||||
.body(s)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("tera-example");
|
|
||||||
|
|
||||||
let addr = HttpServer::new(|| {
|
|
||||||
let tera = compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*"));
|
|
||||||
|
|
||||||
Application::with_state(State{template: tera})
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/", |r| r.method(Method::GET).f(index))})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Actix web</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Welcome!</h1>
|
|
||||||
<p>
|
|
||||||
<h3>What is your name?</h3>
|
|
||||||
<form>
|
|
||||||
<input type="text" name="name" /><br/>
|
|
||||||
<p><input type="submit"></p>
|
|
||||||
</form>
|
|
||||||
</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Actix web</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Hi, {{ name }}!</h1>
|
|
||||||
<p>
|
|
||||||
{{ text }}
|
|
||||||
</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,15 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "tls-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "server"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../", features=["alpn"] }
|
|
||||||
openssl = { version="0.10" }
|
|
@ -1,16 +0,0 @@
|
|||||||
# tls example
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/tls
|
|
||||||
cargo run (or ``cargo watch -x run``)
|
|
||||||
# Started http server: 127.0.0.1:8443
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
- curl: ``curl -v https://127.0.0.1:8443/index.html --compress -k``
|
|
||||||
- browser: [https://127.0.0.1:8443/index.html](https://127.0.0.1:8080/index.html)
|
|
@ -1,50 +0,0 @@
|
|||||||
#![allow(unused_variables)]
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate openssl;
|
|
||||||
|
|
||||||
use actix_web::*;
|
|
||||||
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
|
|
||||||
|
|
||||||
|
|
||||||
/// simple handle
|
|
||||||
fn index(req: HttpRequest) -> Result<HttpResponse> {
|
|
||||||
println!("{:?}", req);
|
|
||||||
Ok(httpcodes::HTTPOk
|
|
||||||
.build()
|
|
||||||
.content_type("text/plain")
|
|
||||||
.body("Welcome!")?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
if ::std::env::var("RUST_LOG").is_err() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
}
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("ws-example");
|
|
||||||
|
|
||||||
// load ssl keys
|
|
||||||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
|
||||||
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
|
|
||||||
builder.set_certificate_chain_file("cert.pem").unwrap();
|
|
||||||
|
|
||||||
let addr = HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
// register simple handler, handle all methods
|
|
||||||
.resource("/index.html", |r| r.f(index))
|
|
||||||
// with path parameters
|
|
||||||
.resource("/", |r| r.method(Method::GET).f(|req| {
|
|
||||||
httpcodes::HTTPFound
|
|
||||||
.build()
|
|
||||||
.header("LOCATION", "/index.html")
|
|
||||||
.body(Body::Empty)
|
|
||||||
})))
|
|
||||||
.bind("127.0.0.1:8443").unwrap()
|
|
||||||
.start_ssl(builder).unwrap();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8443");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
# Actix Web CORS example
|
|
||||||
|
|
||||||
## start
|
|
||||||
1 - backend server
|
|
||||||
```bash
|
|
||||||
$ cd web-cors/backend
|
|
||||||
$ cargo run
|
|
||||||
```
|
|
||||||
2 - frontend server
|
|
||||||
```bash
|
|
||||||
$ cd web-cors/frontend
|
|
||||||
$ npm install
|
|
||||||
$ npm run dev
|
|
||||||
```
|
|
||||||
then open browser 'http://localhost:1234/'
|
|
4
examples/web-cors/backend/.gitignore
vendored
4
examples/web-cors/backend/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
/target/
|
|
||||||
**/*.rs.bk
|
|
||||||
Cargo.lock
|
|
@ -1,17 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "actix-web-cors"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["krircc <krircc@aliyun.com>"]
|
|
||||||
workspace = "../../../"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
serde = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
http = "0.1"
|
|
||||||
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../../" }
|
|
||||||
dotenv = "0.10"
|
|
||||||
env_logger = "0.5"
|
|
||||||
futures = "0.1"
|
|
@ -1,45 +0,0 @@
|
|||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate http;
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use http::header;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::middleware::cors;
|
|
||||||
|
|
||||||
mod user;
|
|
||||||
use user::info;
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
let sys = actix::System::new("Actix-web-CORS");
|
|
||||||
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/user/info", |r| {
|
|
||||||
cors::Cors::build()
|
|
||||||
.allowed_origin("http://localhost:1234")
|
|
||||||
.allowed_methods(vec!["GET", "POST"])
|
|
||||||
.allowed_headers(
|
|
||||||
vec![header::AUTHORIZATION,
|
|
||||||
header::ACCEPT, header::CONTENT_TYPE])
|
|
||||||
.max_age(3600)
|
|
||||||
.finish().expect("Can not create CORS middleware")
|
|
||||||
.register(r);
|
|
||||||
r.method(Method::POST).a(info);
|
|
||||||
}))
|
|
||||||
.bind("127.0.0.1:8000").unwrap()
|
|
||||||
.shutdown_timeout(200)
|
|
||||||
.start();
|
|
||||||
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
use actix_web::*;
|
|
||||||
use futures::Future;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Deserialize,Serialize, Debug)]
|
|
||||||
struct Info {
|
|
||||||
username: String,
|
|
||||||
email: String,
|
|
||||||
password: String,
|
|
||||||
confirm_password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn info(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
req.json()
|
|
||||||
.from_err()
|
|
||||||
.and_then(|res: Info| {
|
|
||||||
Ok(httpcodes::HTTPOk.build().json(res)?)
|
|
||||||
}).responder()
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": ["env"]
|
|
||||||
}
|
|
14
examples/web-cors/frontend/.gitignore
vendored
14
examples/web-cors/frontend/.gitignore
vendored
@ -1,14 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
node_modules/
|
|
||||||
/dist/
|
|
||||||
.cache
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.idea
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
||||||
<title>webapp</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script src="./src/main.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "actix-web-cors",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "webapp",
|
|
||||||
"main": "main.js",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "rm -rf dist/ && NODE_ENV=development parcel index.html",
|
|
||||||
"build": "NODE_ENV=production parcel build index.html",
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"vue": "^2.5.13",
|
|
||||||
"vue-router": "^3.0.1",
|
|
||||||
"axios": "^0.17.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"babel-preset-env": "^1.6.1",
|
|
||||||
"parcel-bundler": "^1.4.1",
|
|
||||||
"parcel-plugin-vue": "^1.5.0"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,145 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="app">
|
|
||||||
<div id="content">
|
|
||||||
<div id="title">
|
|
||||||
<a to="#">SignUp</a>
|
|
||||||
</div>
|
|
||||||
<input type="text" name="username" placeholder="Username" v-model="Username" required />
|
|
||||||
<input type="text" name="email" placeholder="E-mail" v-model="Email" required />
|
|
||||||
<input type="password" name="password" placeholder="Password" v-model="Password" required/>
|
|
||||||
<input type="password" name="confirm_password" placeholder="Confirm password" v-model="ConfirmPassword" required/><br/>
|
|
||||||
|
|
||||||
<button id="submit" @click="signup">Sign up</button>
|
|
||||||
|
|
||||||
<div id="user-info">
|
|
||||||
<p>Click Above 'Sign up' Button <br> Then Get Your Signup Info!</p>
|
|
||||||
<p>email : {{ email }}</p>
|
|
||||||
<p>username :{{ username }}</p>
|
|
||||||
<p>password : {{ password }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
export default {
|
|
||||||
name: 'app',
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
Username: '',
|
|
||||||
Email: '',
|
|
||||||
Password: '',
|
|
||||||
ConfirmPassword: '',
|
|
||||||
|
|
||||||
email: '',
|
|
||||||
username: '',
|
|
||||||
password: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
signup () {
|
|
||||||
var username = this.Username
|
|
||||||
var email = this.Email
|
|
||||||
var password = this.Password
|
|
||||||
var confirm_password = this.ConfirmPassword
|
|
||||||
console.log(email)
|
|
||||||
axios.post('http://localhost:8000/user/info', {
|
|
||||||
username: username,
|
|
||||||
email: email,
|
|
||||||
password: password,
|
|
||||||
confirm_password: confirm_password
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
console.log(response.data)
|
|
||||||
this.email = response.data.email
|
|
||||||
this.username = response.data.username
|
|
||||||
this.password = response.data.password
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.log(e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
#content {
|
|
||||||
width: 250px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding-top: 33px;
|
|
||||||
}
|
|
||||||
#title {
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color:bisque;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
input[type="text"],
|
|
||||||
input[type="password"] {
|
|
||||||
margin: 6px auto auto;
|
|
||||||
width: 250px;
|
|
||||||
height: 36px;
|
|
||||||
border: none;
|
|
||||||
border-bottom: 1px solid #AAA;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
#submit {
|
|
||||||
margin: 10px 0 20px 0;
|
|
||||||
width: 250px;
|
|
||||||
height: 33px;
|
|
||||||
background-color:bisque;
|
|
||||||
border: none;
|
|
||||||
border-radius: 2px;
|
|
||||||
font-family: 'Roboto', sans-serif;
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
transition: 0.1s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
input[type="checkbox"] {
|
|
||||||
margin-top: 11px;
|
|
||||||
}
|
|
||||||
dialog {
|
|
||||||
top: 50%;
|
|
||||||
width: 80%;
|
|
||||||
border: 5px solid rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
dialog::backdrop{
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.7);
|
|
||||||
}
|
|
||||||
#closeDialog {
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: none;
|
|
||||||
font-size: 1rem;
|
|
||||||
padding: 0.4rem 0.8em;
|
|
||||||
background: #eb9816;
|
|
||||||
border-bottom: 1px solid #f1b75c;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#closeDialog:hover, #closeDialog:focus {
|
|
||||||
opacity: 0.92;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
#user-info {
|
|
||||||
width: 250px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding-top: 44px;
|
|
||||||
}
|
|
||||||
@media only screen and (min-width: 600px) {
|
|
||||||
#content {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding-top: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,11 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
import App from './app'
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el: '#app',
|
|
||||||
render: h => h(App)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (module.hot) {
|
|
||||||
module.hot.accept();
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "websocket-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "server"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "client"
|
|
||||||
path = "src/client.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
rand = "*"
|
|
||||||
bytes = "0.4"
|
|
||||||
byteorder = "1.1"
|
|
||||||
futures = "0.1"
|
|
||||||
tokio-io = "0.1"
|
|
||||||
tokio-core = "0.1"
|
|
||||||
env_logger = "*"
|
|
||||||
|
|
||||||
serde = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path="../../" }
|
|
@ -1,32 +0,0 @@
|
|||||||
# Websocket chat example
|
|
||||||
|
|
||||||
This is extension of the
|
|
||||||
[actix chat example](https://github.com/actix/actix/tree/master/examples/chat)
|
|
||||||
|
|
||||||
Added features:
|
|
||||||
|
|
||||||
* Browser WebSocket client
|
|
||||||
* Chat server runs in separate thread
|
|
||||||
* Tcp listener runs in separate thread
|
|
||||||
|
|
||||||
## Server
|
|
||||||
|
|
||||||
Chat server listens for incoming tcp connections. Server can access several types of message:
|
|
||||||
|
|
||||||
* `\list` - list all available rooms
|
|
||||||
* `\join name` - join room, if room does not exist, create new one
|
|
||||||
* `\name name` - set session name
|
|
||||||
* `some message` - just string, send message to all peers in same room
|
|
||||||
* client has to send heartbeat `Ping` messages, if server does not receive a heartbeat message for 10 seconds connection gets dropped
|
|
||||||
|
|
||||||
To start server use command: `cargo run --bin server`
|
|
||||||
|
|
||||||
## Client
|
|
||||||
|
|
||||||
Client connects to server. Reads input from stdin and sends to server.
|
|
||||||
|
|
||||||
To run client use command: `cargo run --bin client`
|
|
||||||
|
|
||||||
## WebSocket Browser Client
|
|
||||||
|
|
||||||
Open url: [http://localhost:8080/](http://localhost:8080/)
|
|
@ -1,72 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""websocket cmd client for wssrv.py example."""
|
|
||||||
import argparse
|
|
||||||
import asyncio
|
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
|
|
||||||
def start_client(loop, url):
|
|
||||||
name = input('Please enter your name: ')
|
|
||||||
|
|
||||||
# send request
|
|
||||||
ws = yield from aiohttp.ClientSession().ws_connect(url, autoclose=False, autoping=False)
|
|
||||||
|
|
||||||
# input reader
|
|
||||||
def stdin_callback():
|
|
||||||
line = sys.stdin.buffer.readline().decode('utf-8')
|
|
||||||
if not line:
|
|
||||||
loop.stop()
|
|
||||||
else:
|
|
||||||
ws.send_str(name + ': ' + line)
|
|
||||||
loop.add_reader(sys.stdin.fileno(), stdin_callback)
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
def dispatch():
|
|
||||||
while True:
|
|
||||||
msg = yield from ws.receive()
|
|
||||||
|
|
||||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
|
||||||
print('Text: ', msg.data.strip())
|
|
||||||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
|
||||||
print('Binary: ', msg.data)
|
|
||||||
elif msg.type == aiohttp.WSMsgType.PING:
|
|
||||||
ws.pong()
|
|
||||||
elif msg.type == aiohttp.WSMsgType.PONG:
|
|
||||||
print('Pong received')
|
|
||||||
else:
|
|
||||||
if msg.type == aiohttp.WSMsgType.CLOSE:
|
|
||||||
yield from ws.close()
|
|
||||||
elif msg.type == aiohttp.WSMsgType.ERROR:
|
|
||||||
print('Error during receive %s' % ws.exception())
|
|
||||||
elif msg.type == aiohttp.WSMsgType.CLOSED:
|
|
||||||
pass
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
yield from dispatch()
|
|
||||||
|
|
||||||
|
|
||||||
ARGS = argparse.ArgumentParser(
|
|
||||||
description="websocket console client for wssrv.py example.")
|
|
||||||
ARGS.add_argument(
|
|
||||||
'--host', action="store", dest='host',
|
|
||||||
default='127.0.0.1', help='Host name')
|
|
||||||
ARGS.add_argument(
|
|
||||||
'--port', action="store", dest='port',
|
|
||||||
default=8080, type=int, help='Port number')
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
args = ARGS.parse_args()
|
|
||||||
if ':' in args.host:
|
|
||||||
args.host, port = args.host.split(':', 1)
|
|
||||||
args.port = int(port)
|
|
||||||
|
|
||||||
url = 'http://{}:{}/ws/'.format(args.host, args.port)
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.add_signal_handler(signal.SIGINT, loop.stop)
|
|
||||||
asyncio.Task(start_client(loop, url))
|
|
||||||
loop.run_forever()
|
|
@ -1,153 +0,0 @@
|
|||||||
#[macro_use] extern crate actix;
|
|
||||||
extern crate bytes;
|
|
||||||
extern crate byteorder;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate tokio_io;
|
|
||||||
extern crate tokio_core;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
|
|
||||||
use std::{io, net, process, thread};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::time::Duration;
|
|
||||||
use futures::Future;
|
|
||||||
use tokio_io::AsyncRead;
|
|
||||||
use tokio_io::io::WriteHalf;
|
|
||||||
use tokio_io::codec::FramedRead;
|
|
||||||
use tokio_core::net::TcpStream;
|
|
||||||
use actix::prelude::*;
|
|
||||||
|
|
||||||
mod codec;
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let sys = actix::System::new("chat-client");
|
|
||||||
|
|
||||||
// Connect to server
|
|
||||||
let addr = net::SocketAddr::from_str("127.0.0.1:12345").unwrap();
|
|
||||||
Arbiter::handle().spawn(
|
|
||||||
TcpStream::connect(&addr, Arbiter::handle())
|
|
||||||
.and_then(|stream| {
|
|
||||||
let addr: Addr<Syn, _> = ChatClient::create(|ctx| {
|
|
||||||
let (r, w) = stream.split();
|
|
||||||
ChatClient::add_stream(FramedRead::new(r, codec::ClientChatCodec), ctx);
|
|
||||||
ChatClient{
|
|
||||||
framed: actix::io::FramedWrite::new(
|
|
||||||
w, codec::ClientChatCodec, ctx)}});
|
|
||||||
|
|
||||||
// start console loop
|
|
||||||
thread::spawn(move|| {
|
|
||||||
loop {
|
|
||||||
let mut cmd = String::new();
|
|
||||||
if io::stdin().read_line(&mut cmd).is_err() {
|
|
||||||
println!("error");
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
addr.do_send(ClientCommand(cmd));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
futures::future::ok(())
|
|
||||||
})
|
|
||||||
.map_err(|e| {
|
|
||||||
println!("Can not connect to server: {}", e);
|
|
||||||
process::exit(1)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
println!("Running chat client");
|
|
||||||
sys.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct ChatClient {
|
|
||||||
framed: actix::io::FramedWrite<WriteHalf<TcpStream>, codec::ClientChatCodec>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Message)]
|
|
||||||
struct ClientCommand(String);
|
|
||||||
|
|
||||||
impl Actor for ChatClient {
|
|
||||||
type Context = Context<Self>;
|
|
||||||
|
|
||||||
fn started(&mut self, ctx: &mut Context<Self>) {
|
|
||||||
// start heartbeats otherwise server will disconnect after 10 seconds
|
|
||||||
self.hb(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stopped(&mut self, _: &mut Context<Self>) {
|
|
||||||
println!("Disconnected");
|
|
||||||
|
|
||||||
// Stop application on disconnect
|
|
||||||
Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChatClient {
|
|
||||||
fn hb(&self, ctx: &mut Context<Self>) {
|
|
||||||
ctx.run_later(Duration::new(1, 0), |act, ctx| {
|
|
||||||
act.framed.write(codec::ChatRequest::Ping);
|
|
||||||
act.hb(ctx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl actix::io::WriteHandler<io::Error> for ChatClient {}
|
|
||||||
|
|
||||||
/// Handle stdin commands
|
|
||||||
impl Handler<ClientCommand> for ChatClient {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ClientCommand, _: &mut Context<Self>) {
|
|
||||||
let m = msg.0.trim();
|
|
||||||
if m.is_empty() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// we check for /sss type of messages
|
|
||||||
if m.starts_with('/') {
|
|
||||||
let v: Vec<&str> = m.splitn(2, ' ').collect();
|
|
||||||
match v[0] {
|
|
||||||
"/list" => {
|
|
||||||
self.framed.write(codec::ChatRequest::List);
|
|
||||||
},
|
|
||||||
"/join" => {
|
|
||||||
if v.len() == 2 {
|
|
||||||
self.framed.write(codec::ChatRequest::Join(v[1].to_owned()));
|
|
||||||
} else {
|
|
||||||
println!("!!! room name is required");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => println!("!!! unknown command"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.framed.write(codec::ChatRequest::Message(m.to_owned()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Server communication
|
|
||||||
|
|
||||||
impl StreamHandler<codec::ChatResponse, io::Error> for ChatClient {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: codec::ChatResponse, _: &mut Context<Self>) {
|
|
||||||
match msg {
|
|
||||||
codec::ChatResponse::Message(ref msg) => {
|
|
||||||
println!("message: {}", msg);
|
|
||||||
}
|
|
||||||
codec::ChatResponse::Joined(ref msg) => {
|
|
||||||
println!("!!! joined: {}", msg);
|
|
||||||
}
|
|
||||||
codec::ChatResponse::Rooms(rooms) => {
|
|
||||||
println!("\n!!! Available rooms:");
|
|
||||||
for room in rooms {
|
|
||||||
println!("{}", room);
|
|
||||||
}
|
|
||||||
println!("");
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
use std::io;
|
|
||||||
use serde_json as json;
|
|
||||||
use byteorder::{BigEndian , ByteOrder};
|
|
||||||
use bytes::{BytesMut, BufMut};
|
|
||||||
use tokio_io::codec::{Encoder, Decoder};
|
|
||||||
|
|
||||||
/// Client request
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Message)]
|
|
||||||
#[serde(tag="cmd", content="data")]
|
|
||||||
pub enum ChatRequest {
|
|
||||||
/// List rooms
|
|
||||||
List,
|
|
||||||
/// Join rooms
|
|
||||||
Join(String),
|
|
||||||
/// Send message
|
|
||||||
Message(String),
|
|
||||||
/// Ping
|
|
||||||
Ping
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Server response
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Message)]
|
|
||||||
#[serde(tag="cmd", content="data")]
|
|
||||||
pub enum ChatResponse {
|
|
||||||
Ping,
|
|
||||||
|
|
||||||
/// List of rooms
|
|
||||||
Rooms(Vec<String>),
|
|
||||||
|
|
||||||
/// Joined
|
|
||||||
Joined(String),
|
|
||||||
|
|
||||||
/// Message
|
|
||||||
Message(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Codec for Client -> Server transport
|
|
||||||
pub struct ChatCodec;
|
|
||||||
|
|
||||||
impl Decoder for ChatCodec
|
|
||||||
{
|
|
||||||
type Item = ChatRequest;
|
|
||||||
type Error = io::Error;
|
|
||||||
|
|
||||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
|
||||||
let size = {
|
|
||||||
if src.len() < 2 {
|
|
||||||
return Ok(None)
|
|
||||||
}
|
|
||||||
BigEndian::read_u16(src.as_ref()) as usize
|
|
||||||
};
|
|
||||||
|
|
||||||
if src.len() >= size + 2 {
|
|
||||||
src.split_to(2);
|
|
||||||
let buf = src.split_to(size);
|
|
||||||
Ok(Some(json::from_slice::<ChatRequest>(&buf)?))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encoder for ChatCodec
|
|
||||||
{
|
|
||||||
type Item = ChatResponse;
|
|
||||||
type Error = io::Error;
|
|
||||||
|
|
||||||
fn encode(&mut self, msg: ChatResponse, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
|
||||||
let msg = json::to_string(&msg).unwrap();
|
|
||||||
let msg_ref: &[u8] = msg.as_ref();
|
|
||||||
|
|
||||||
dst.reserve(msg_ref.len() + 2);
|
|
||||||
dst.put_u16::<BigEndian>(msg_ref.len() as u16);
|
|
||||||
dst.put(msg_ref);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Codec for Server -> Client transport
|
|
||||||
pub struct ClientChatCodec;
|
|
||||||
|
|
||||||
impl Decoder for ClientChatCodec
|
|
||||||
{
|
|
||||||
type Item = ChatResponse;
|
|
||||||
type Error = io::Error;
|
|
||||||
|
|
||||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
|
||||||
let size = {
|
|
||||||
if src.len() < 2 {
|
|
||||||
return Ok(None)
|
|
||||||
}
|
|
||||||
BigEndian::read_u16(src.as_ref()) as usize
|
|
||||||
};
|
|
||||||
|
|
||||||
if src.len() >= size + 2 {
|
|
||||||
src.split_to(2);
|
|
||||||
let buf = src.split_to(size);
|
|
||||||
Ok(Some(json::from_slice::<ChatResponse>(&buf)?))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encoder for ClientChatCodec
|
|
||||||
{
|
|
||||||
type Item = ChatRequest;
|
|
||||||
type Error = io::Error;
|
|
||||||
|
|
||||||
fn encode(&mut self, msg: ChatRequest, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
|
||||||
let msg = json::to_string(&msg).unwrap();
|
|
||||||
let msg_ref: &[u8] = msg.as_ref();
|
|
||||||
|
|
||||||
dst.reserve(msg_ref.len() + 2);
|
|
||||||
dst.put_u16::<BigEndian>(msg_ref.len() as u16);
|
|
||||||
dst.put(msg_ref);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,209 +0,0 @@
|
|||||||
#![allow(unused_variables)]
|
|
||||||
extern crate rand;
|
|
||||||
extern crate bytes;
|
|
||||||
extern crate byteorder;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate tokio_io;
|
|
||||||
extern crate tokio_core;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
mod codec;
|
|
||||||
mod server;
|
|
||||||
mod session;
|
|
||||||
|
|
||||||
/// This is our websocket route state, this state is shared with all route instances
|
|
||||||
/// via `HttpContext::state()`
|
|
||||||
struct WsChatSessionState {
|
|
||||||
addr: Addr<Syn, server::ChatServer>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Entry point for our route
|
|
||||||
fn chat_route(req: HttpRequest<WsChatSessionState>) -> Result<HttpResponse> {
|
|
||||||
ws::start(
|
|
||||||
req,
|
|
||||||
WsChatSession {
|
|
||||||
id: 0,
|
|
||||||
hb: Instant::now(),
|
|
||||||
room: "Main".to_owned(),
|
|
||||||
name: None})
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WsChatSession {
|
|
||||||
/// unique session id
|
|
||||||
id: usize,
|
|
||||||
/// Client must send ping at least once per 10 seconds, otherwise we drop connection.
|
|
||||||
hb: Instant,
|
|
||||||
/// joined room
|
|
||||||
room: String,
|
|
||||||
/// peer name
|
|
||||||
name: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for WsChatSession {
|
|
||||||
type Context = ws::WebsocketContext<Self, WsChatSessionState>;
|
|
||||||
|
|
||||||
/// Method is called on actor start.
|
|
||||||
/// We register ws session with ChatServer
|
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
|
||||||
// register self in chat server. `AsyncContext::wait` register
|
|
||||||
// future within context, but context waits until this future resolves
|
|
||||||
// before processing any other events.
|
|
||||||
// HttpContext::state() is instance of WsChatSessionState, state is shared across all
|
|
||||||
// routes within application
|
|
||||||
let addr: Addr<Syn, _> = ctx.address();
|
|
||||||
ctx.state().addr.send(server::Connect{addr: addr.recipient()})
|
|
||||||
.into_actor(self)
|
|
||||||
.then(|res, act, ctx| {
|
|
||||||
match res {
|
|
||||||
Ok(res) => act.id = res,
|
|
||||||
// something is wrong with chat server
|
|
||||||
_ => ctx.stop(),
|
|
||||||
}
|
|
||||||
fut::ok(())
|
|
||||||
}).wait(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
|
|
||||||
// notify chat server
|
|
||||||
ctx.state().addr.do_send(server::Disconnect{id: self.id});
|
|
||||||
Running::Stop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle messages from chat server, we simply send it to peer websocket
|
|
||||||
impl Handler<session::Message> for WsChatSession {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) {
|
|
||||||
ctx.text(msg.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// WebSocket message handler
|
|
||||||
impl StreamHandler<ws::Message, ws::ProtocolError> for WsChatSession {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
|
||||||
println!("WEBSOCKET MESSAGE: {:?}", msg);
|
|
||||||
match msg {
|
|
||||||
ws::Message::Ping(msg) => ctx.pong(&msg),
|
|
||||||
ws::Message::Pong(msg) => self.hb = Instant::now(),
|
|
||||||
ws::Message::Text(text) => {
|
|
||||||
let m = text.trim();
|
|
||||||
// we check for /sss type of messages
|
|
||||||
if m.starts_with('/') {
|
|
||||||
let v: Vec<&str> = m.splitn(2, ' ').collect();
|
|
||||||
match v[0] {
|
|
||||||
"/list" => {
|
|
||||||
// Send ListRooms message to chat server and wait for response
|
|
||||||
println!("List rooms");
|
|
||||||
ctx.state().addr.send(server::ListRooms)
|
|
||||||
.into_actor(self)
|
|
||||||
.then(|res, _, ctx| {
|
|
||||||
match res {
|
|
||||||
Ok(rooms) => {
|
|
||||||
for room in rooms {
|
|
||||||
ctx.text(room);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => println!("Something is wrong"),
|
|
||||||
}
|
|
||||||
fut::ok(())
|
|
||||||
}).wait(ctx)
|
|
||||||
// .wait(ctx) pauses all events in context,
|
|
||||||
// so actor wont receive any new messages until it get list
|
|
||||||
// of rooms back
|
|
||||||
},
|
|
||||||
"/join" => {
|
|
||||||
if v.len() == 2 {
|
|
||||||
self.room = v[1].to_owned();
|
|
||||||
ctx.state().addr.do_send(
|
|
||||||
server::Join{id: self.id, name: self.room.clone()});
|
|
||||||
|
|
||||||
ctx.text("joined");
|
|
||||||
} else {
|
|
||||||
ctx.text("!!! room name is required");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/name" => {
|
|
||||||
if v.len() == 2 {
|
|
||||||
self.name = Some(v[1].to_owned());
|
|
||||||
} else {
|
|
||||||
ctx.text("!!! name is required");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => ctx.text(format!("!!! unknown command: {:?}", m)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let msg = if let Some(ref name) = self.name {
|
|
||||||
format!("{}: {}", name, m)
|
|
||||||
} else {
|
|
||||||
m.to_owned()
|
|
||||||
};
|
|
||||||
// send message to chat server
|
|
||||||
ctx.state().addr.do_send(
|
|
||||||
server::Message{id: self.id,
|
|
||||||
msg: msg,
|
|
||||||
room: self.room.clone()})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ws::Message::Binary(bin) =>
|
|
||||||
println!("Unexpected binary"),
|
|
||||||
ws::Message::Close(_) => {
|
|
||||||
ctx.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("websocket-example");
|
|
||||||
|
|
||||||
// Start chat server actor in separate thread
|
|
||||||
let server: Addr<Syn, _> = Arbiter::start(|_| server::ChatServer::default());
|
|
||||||
|
|
||||||
// Start tcp server in separate thread
|
|
||||||
let srv = server.clone();
|
|
||||||
Arbiter::new("tcp-server").do_send::<msgs::Execute>(
|
|
||||||
msgs::Execute::new(move || {
|
|
||||||
session::TcpServer::new("127.0.0.1:12345", srv);
|
|
||||||
Ok(())
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Create Http server with websocket support
|
|
||||||
let addr = HttpServer::new(
|
|
||||||
move || {
|
|
||||||
// Websocket sessions state
|
|
||||||
let state = WsChatSessionState { addr: server.clone() };
|
|
||||||
|
|
||||||
Application::with_state(state)
|
|
||||||
// redirect to websocket.html
|
|
||||||
.resource("/", |r| r.method(Method::GET).f(|_| {
|
|
||||||
httpcodes::HTTPFound
|
|
||||||
.build()
|
|
||||||
.header("LOCATION", "/static/websocket.html")
|
|
||||||
.finish()
|
|
||||||
}))
|
|
||||||
// websocket
|
|
||||||
.resource("/ws/", |r| r.route().f(chat_route))
|
|
||||||
// static resources
|
|
||||||
.handler("/static/", fs::StaticFiles::new("static/", true))
|
|
||||||
})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,197 +0,0 @@
|
|||||||
//! `ChatServer` is an actor. It maintains list of connection client session.
|
|
||||||
//! And manages available rooms. Peers send messages to other peers in same
|
|
||||||
//! room through `ChatServer`.
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use rand::{self, Rng, ThreadRng};
|
|
||||||
use actix::prelude::*;
|
|
||||||
|
|
||||||
use session;
|
|
||||||
|
|
||||||
/// Message for chat server communications
|
|
||||||
|
|
||||||
/// New chat session is created
|
|
||||||
#[derive(Message)]
|
|
||||||
#[rtype(usize)]
|
|
||||||
pub struct Connect {
|
|
||||||
pub addr: Recipient<Syn, session::Message>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Session is disconnected
|
|
||||||
#[derive(Message)]
|
|
||||||
pub struct Disconnect {
|
|
||||||
pub id: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send message to specific room
|
|
||||||
#[derive(Message)]
|
|
||||||
pub struct Message {
|
|
||||||
/// Id of the client session
|
|
||||||
pub id: usize,
|
|
||||||
/// Peer message
|
|
||||||
pub msg: String,
|
|
||||||
/// Room name
|
|
||||||
pub room: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List of available rooms
|
|
||||||
pub struct ListRooms;
|
|
||||||
|
|
||||||
impl actix::Message for ListRooms {
|
|
||||||
type Result = Vec<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Join room, if room does not exists create new one.
|
|
||||||
#[derive(Message)]
|
|
||||||
pub struct Join {
|
|
||||||
/// Client id
|
|
||||||
pub id: usize,
|
|
||||||
/// Room name
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `ChatServer` manages chat rooms and responsible for coordinating chat session.
|
|
||||||
/// implementation is super primitive
|
|
||||||
pub struct ChatServer {
|
|
||||||
sessions: HashMap<usize, Recipient<Syn, session::Message>>,
|
|
||||||
rooms: HashMap<String, HashSet<usize>>,
|
|
||||||
rng: RefCell<ThreadRng>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ChatServer {
|
|
||||||
fn default() -> ChatServer {
|
|
||||||
// default room
|
|
||||||
let mut rooms = HashMap::new();
|
|
||||||
rooms.insert("Main".to_owned(), HashSet::new());
|
|
||||||
|
|
||||||
ChatServer {
|
|
||||||
sessions: HashMap::new(),
|
|
||||||
rooms: rooms,
|
|
||||||
rng: RefCell::new(rand::thread_rng()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChatServer {
|
|
||||||
/// Send message to all users in the room
|
|
||||||
fn send_message(&self, room: &str, message: &str, skip_id: usize) {
|
|
||||||
if let Some(sessions) = self.rooms.get(room) {
|
|
||||||
for id in sessions {
|
|
||||||
if *id != skip_id {
|
|
||||||
if let Some(addr) = self.sessions.get(id) {
|
|
||||||
let _ = addr.do_send(session::Message(message.to_owned()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make actor from `ChatServer`
|
|
||||||
impl Actor for ChatServer {
|
|
||||||
/// We are going to use simple Context, we just need ability to communicate
|
|
||||||
/// with other actors.
|
|
||||||
type Context = Context<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for Connect message.
|
|
||||||
///
|
|
||||||
/// Register new session and assign unique id to this session
|
|
||||||
impl Handler<Connect> for ChatServer {
|
|
||||||
type Result = usize;
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: Connect, _: &mut Context<Self>) -> Self::Result {
|
|
||||||
println!("Someone joined");
|
|
||||||
|
|
||||||
// notify all users in same room
|
|
||||||
self.send_message(&"Main".to_owned(), "Someone joined", 0);
|
|
||||||
|
|
||||||
// register session with random id
|
|
||||||
let id = self.rng.borrow_mut().gen::<usize>();
|
|
||||||
self.sessions.insert(id, msg.addr);
|
|
||||||
|
|
||||||
// auto join session to Main room
|
|
||||||
self.rooms.get_mut(&"Main".to_owned()).unwrap().insert(id);
|
|
||||||
|
|
||||||
// send id back
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for Disconnect message.
|
|
||||||
impl Handler<Disconnect> for ChatServer {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) {
|
|
||||||
println!("Someone disconnected");
|
|
||||||
|
|
||||||
let mut rooms: Vec<String> = Vec::new();
|
|
||||||
|
|
||||||
// remove address
|
|
||||||
if self.sessions.remove(&msg.id).is_some() {
|
|
||||||
// remove session from all rooms
|
|
||||||
for (name, sessions) in &mut self.rooms {
|
|
||||||
if sessions.remove(&msg.id) {
|
|
||||||
rooms.push(name.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// send message to other users
|
|
||||||
for room in rooms {
|
|
||||||
self.send_message(&room, "Someone disconnected", 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for Message message.
|
|
||||||
impl Handler<Message> for ChatServer {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: Message, _: &mut Context<Self>) {
|
|
||||||
self.send_message(&msg.room, msg.msg.as_str(), msg.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for `ListRooms` message.
|
|
||||||
impl Handler<ListRooms> for ChatServer {
|
|
||||||
type Result = MessageResult<ListRooms>;
|
|
||||||
|
|
||||||
fn handle(&mut self, _: ListRooms, _: &mut Context<Self>) -> Self::Result {
|
|
||||||
let mut rooms = Vec::new();
|
|
||||||
|
|
||||||
for key in self.rooms.keys() {
|
|
||||||
rooms.push(key.to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageResult(rooms)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Join room, send disconnect message to old room
|
|
||||||
/// send join message to new room
|
|
||||||
impl Handler<Join> for ChatServer {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: Join, _: &mut Context<Self>) {
|
|
||||||
let Join {id, name} = msg;
|
|
||||||
let mut rooms = Vec::new();
|
|
||||||
|
|
||||||
// remove session from all rooms
|
|
||||||
for (n, sessions) in &mut self.rooms {
|
|
||||||
if sessions.remove(&id) {
|
|
||||||
rooms.push(n.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// send message to other users
|
|
||||||
for room in rooms {
|
|
||||||
self.send_message(&room, "Someone disconnected", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.rooms.get_mut(&name).is_none() {
|
|
||||||
self.rooms.insert(name.clone(), HashSet::new());
|
|
||||||
}
|
|
||||||
self.send_message(&name, "Someone connected", id);
|
|
||||||
self.rooms.get_mut(&name).unwrap().insert(id);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,207 +0,0 @@
|
|||||||
//! `ClientSession` is an actor, it manages peer tcp connection and
|
|
||||||
//! proxies commands from peer to `ChatServer`.
|
|
||||||
use std::{io, net};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::time::{Instant, Duration};
|
|
||||||
use futures::Stream;
|
|
||||||
use tokio_io::AsyncRead;
|
|
||||||
use tokio_io::io::WriteHalf;
|
|
||||||
use tokio_io::codec::FramedRead;
|
|
||||||
use tokio_core::net::{TcpStream, TcpListener};
|
|
||||||
|
|
||||||
use actix::prelude::*;
|
|
||||||
|
|
||||||
use server::{self, ChatServer};
|
|
||||||
use codec::{ChatRequest, ChatResponse, ChatCodec};
|
|
||||||
|
|
||||||
|
|
||||||
/// Chat server sends this messages to session
|
|
||||||
#[derive(Message)]
|
|
||||||
pub struct Message(pub String);
|
|
||||||
|
|
||||||
/// `ChatSession` actor is responsible for tcp peer communications.
|
|
||||||
pub struct ChatSession {
|
|
||||||
/// unique session id
|
|
||||||
id: usize,
|
|
||||||
/// this is address of chat server
|
|
||||||
addr: Addr<Syn, ChatServer>,
|
|
||||||
/// Client must send ping at least once per 10 seconds, otherwise we drop connection.
|
|
||||||
hb: Instant,
|
|
||||||
/// joined room
|
|
||||||
room: String,
|
|
||||||
/// Framed wrapper
|
|
||||||
framed: actix::io::FramedWrite<WriteHalf<TcpStream>, ChatCodec>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for ChatSession {
|
|
||||||
/// For tcp communication we are going to use `FramedContext`.
|
|
||||||
/// It is convenient wrapper around `Framed` object from `tokio_io`
|
|
||||||
type Context = Context<Self>;
|
|
||||||
|
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
|
||||||
// we'll start heartbeat process on session start.
|
|
||||||
self.hb(ctx);
|
|
||||||
|
|
||||||
// register self in chat server. `AsyncContext::wait` register
|
|
||||||
// future within context, but context waits until this future resolves
|
|
||||||
// before processing any other events.
|
|
||||||
let addr: Addr<Syn, _> = ctx.address();
|
|
||||||
self.addr.send(server::Connect{addr: addr.recipient()})
|
|
||||||
.into_actor(self)
|
|
||||||
.then(|res, act, ctx| {
|
|
||||||
match res {
|
|
||||||
Ok(res) => act.id = res,
|
|
||||||
// something is wrong with chat server
|
|
||||||
_ => ctx.stop(),
|
|
||||||
}
|
|
||||||
actix::fut::ok(())
|
|
||||||
}).wait(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
|
|
||||||
// notify chat server
|
|
||||||
self.addr.do_send(server::Disconnect{id: self.id});
|
|
||||||
Running::Stop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl actix::io::WriteHandler<io::Error> for ChatSession {}
|
|
||||||
|
|
||||||
/// To use `Framed` we have to define Io type and Codec
|
|
||||||
impl StreamHandler<ChatRequest, io::Error> for ChatSession {
|
|
||||||
|
|
||||||
/// This is main event loop for client requests
|
|
||||||
fn handle(&mut self, msg: ChatRequest, ctx: &mut Context<Self>) {
|
|
||||||
match msg {
|
|
||||||
ChatRequest::List => {
|
|
||||||
// Send ListRooms message to chat server and wait for response
|
|
||||||
println!("List rooms");
|
|
||||||
self.addr.send(server::ListRooms)
|
|
||||||
.into_actor(self)
|
|
||||||
.then(|res, act, ctx| {
|
|
||||||
match res {
|
|
||||||
Ok(rooms) => {
|
|
||||||
act.framed.write(ChatResponse::Rooms(rooms));
|
|
||||||
},
|
|
||||||
_ => println!("Something is wrong"),
|
|
||||||
}
|
|
||||||
actix::fut::ok(())
|
|
||||||
}).wait(ctx)
|
|
||||||
// .wait(ctx) pauses all events in context,
|
|
||||||
// so actor wont receive any new messages until it get list of rooms back
|
|
||||||
},
|
|
||||||
ChatRequest::Join(name) => {
|
|
||||||
println!("Join to room: {}", name);
|
|
||||||
self.room = name.clone();
|
|
||||||
self.addr.do_send(server::Join{id: self.id, name: name.clone()});
|
|
||||||
self.framed.write(ChatResponse::Joined(name));
|
|
||||||
},
|
|
||||||
ChatRequest::Message(message) => {
|
|
||||||
// send message to chat server
|
|
||||||
println!("Peer message: {}", message);
|
|
||||||
self.addr.do_send(
|
|
||||||
server::Message{id: self.id,
|
|
||||||
msg: message, room:
|
|
||||||
self.room.clone()})
|
|
||||||
}
|
|
||||||
// we update heartbeat time on ping from peer
|
|
||||||
ChatRequest::Ping =>
|
|
||||||
self.hb = Instant::now(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for Message, chat server sends this message, we just send string to peer
|
|
||||||
impl Handler<Message> for ChatSession {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: Message, ctx: &mut Context<Self>) {
|
|
||||||
// send message to peer
|
|
||||||
self.framed.write(ChatResponse::Message(msg.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper methods
|
|
||||||
impl ChatSession {
|
|
||||||
|
|
||||||
pub fn new(addr: Addr<Syn,ChatServer>,
|
|
||||||
framed: actix::io::FramedWrite<WriteHalf<TcpStream>, ChatCodec>) -> ChatSession {
|
|
||||||
ChatSession {id: 0, addr: addr, hb: Instant::now(),
|
|
||||||
room: "Main".to_owned(), framed: framed}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// helper method that sends ping to client every second.
|
|
||||||
///
|
|
||||||
/// also this method check heartbeats from client
|
|
||||||
fn hb(&self, ctx: &mut Context<Self>) {
|
|
||||||
ctx.run_later(Duration::new(1, 0), |act, ctx| {
|
|
||||||
// check client heartbeats
|
|
||||||
if Instant::now().duration_since(act.hb) > Duration::new(10, 0) {
|
|
||||||
// heartbeat timed out
|
|
||||||
println!("Client heartbeat failed, disconnecting!");
|
|
||||||
|
|
||||||
// notify chat server
|
|
||||||
act.addr.do_send(server::Disconnect{id: act.id});
|
|
||||||
|
|
||||||
// stop actor
|
|
||||||
ctx.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
act.framed.write(ChatResponse::Ping);
|
|
||||||
// if we can not send message to sink, sink is closed (disconnected)
|
|
||||||
act.hb(ctx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Define tcp server that will accept incoming tcp connection and create
|
|
||||||
/// chat actors.
|
|
||||||
pub struct TcpServer {
|
|
||||||
chat: Addr<Syn, ChatServer>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TcpServer {
|
|
||||||
pub fn new(s: &str, chat: Addr<Syn, ChatServer>) {
|
|
||||||
// Create server listener
|
|
||||||
let addr = net::SocketAddr::from_str("127.0.0.1:12345").unwrap();
|
|
||||||
let listener = TcpListener::bind(&addr, Arbiter::handle()).unwrap();
|
|
||||||
|
|
||||||
// Our chat server `Server` is an actor, first we need to start it
|
|
||||||
// and then add stream on incoming tcp connections to it.
|
|
||||||
// TcpListener::incoming() returns stream of the (TcpStream, net::SocketAddr) items
|
|
||||||
// So to be able to handle this events `Server` actor has to implement
|
|
||||||
// stream handler `StreamHandler<(TcpStream, net::SocketAddr), io::Error>`
|
|
||||||
let _: () = TcpServer::create(|ctx| {
|
|
||||||
ctx.add_message_stream(listener.incoming()
|
|
||||||
.map_err(|_| ())
|
|
||||||
.map(|(t, a)| TcpConnect(t, a)));
|
|
||||||
TcpServer{chat: chat}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make actor from `Server`
|
|
||||||
impl Actor for TcpServer {
|
|
||||||
/// Every actor has to provide execution `Context` in which it can run.
|
|
||||||
type Context = Context<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Message)]
|
|
||||||
struct TcpConnect(TcpStream, net::SocketAddr);
|
|
||||||
|
|
||||||
/// Handle stream of TcpStream's
|
|
||||||
impl Handler<TcpConnect> for TcpServer {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: TcpConnect, _: &mut Context<Self>) {
|
|
||||||
// For each incoming connection we create `ChatSession` actor
|
|
||||||
// with out chat server address.
|
|
||||||
let server = self.chat.clone();
|
|
||||||
let _: () = ChatSession::create(|ctx| {
|
|
||||||
let (r, w) = msg.0.split();
|
|
||||||
ChatSession::add_stream(FramedRead::new(r, ChatCodec), ctx);
|
|
||||||
ChatSession::new(server, actix::io::FramedWrite::new(w, ChatCodec, ctx))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
|
|
||||||
</script>
|
|
||||||
<script language="javascript" type="text/javascript">
|
|
||||||
$(function() {
|
|
||||||
var conn = null;
|
|
||||||
function log(msg) {
|
|
||||||
var control = $('#log');
|
|
||||||
control.html(control.html() + msg + '<br/>');
|
|
||||||
control.scrollTop(control.scrollTop() + 1000);
|
|
||||||
}
|
|
||||||
function connect() {
|
|
||||||
disconnect();
|
|
||||||
var wsUri = (window.location.protocol=='https:'&&'wss://'||'ws://')+window.location.host + '/ws/';
|
|
||||||
conn = new WebSocket(wsUri);
|
|
||||||
log('Connecting...');
|
|
||||||
conn.onopen = function() {
|
|
||||||
log('Connected.');
|
|
||||||
update_ui();
|
|
||||||
};
|
|
||||||
conn.onmessage = function(e) {
|
|
||||||
log('Received: ' + e.data);
|
|
||||||
};
|
|
||||||
conn.onclose = function() {
|
|
||||||
log('Disconnected.');
|
|
||||||
conn = null;
|
|
||||||
update_ui();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function disconnect() {
|
|
||||||
if (conn != null) {
|
|
||||||
log('Disconnecting...');
|
|
||||||
conn.close();
|
|
||||||
conn = null;
|
|
||||||
update_ui();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function update_ui() {
|
|
||||||
var msg = '';
|
|
||||||
if (conn == null) {
|
|
||||||
$('#status').text('disconnected');
|
|
||||||
$('#connect').html('Connect');
|
|
||||||
} else {
|
|
||||||
$('#status').text('connected (' + conn.protocol + ')');
|
|
||||||
$('#connect').html('Disconnect');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$('#connect').click(function() {
|
|
||||||
if (conn == null) {
|
|
||||||
connect();
|
|
||||||
} else {
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
update_ui();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
$('#send').click(function() {
|
|
||||||
var text = $('#text').val();
|
|
||||||
log('Sending: ' + text);
|
|
||||||
conn.send(text);
|
|
||||||
$('#text').val('').focus();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
$('#text').keyup(function(e) {
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
$('#send').click();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h3>Chat!</h3>
|
|
||||||
<div>
|
|
||||||
<button id="connect">Connect</button> | Status:
|
|
||||||
<span id="status">disconnected</span>
|
|
||||||
</div>
|
|
||||||
<div id="log"
|
|
||||||
style="width:20em;height:15em;overflow:auto;border:1px solid black">
|
|
||||||
</div>
|
|
||||||
<form id="chatform" onsubmit="return false;">
|
|
||||||
<input id="text" type="text" />
|
|
||||||
<input id="send" type="button" value="Send" />
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,20 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "websocket"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "server"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "client"
|
|
||||||
path = "src/client.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "*"
|
|
||||||
futures = "0.1"
|
|
||||||
tokio-core = "0.1"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path="../../" }
|
|
@ -1,27 +0,0 @@
|
|||||||
# websockect
|
|
||||||
|
|
||||||
Simple echo websocket server.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/websocket
|
|
||||||
cargo run
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
- [http://localhost:8080/ws/index.html](http://localhost:8080/ws/index.html)
|
|
||||||
|
|
||||||
### python client
|
|
||||||
|
|
||||||
- ``pip install aiohttp``
|
|
||||||
- ``python websocket-client.py``
|
|
||||||
|
|
||||||
if ubuntu :
|
|
||||||
|
|
||||||
- ``pip3 install aiohttp``
|
|
||||||
- ``python3 websocket-client.py``
|
|
@ -1,113 +0,0 @@
|
|||||||
//! Simple websocket client.
|
|
||||||
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate tokio_core;
|
|
||||||
|
|
||||||
use std::{io, thread};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use futures::Future;
|
|
||||||
use actix_web::ws::{Message, ProtocolError, Client, ClientWriter};
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("ws-example");
|
|
||||||
|
|
||||||
Arbiter::handle().spawn(
|
|
||||||
Client::new("http://127.0.0.1:8080/ws/")
|
|
||||||
.connect()
|
|
||||||
.map_err(|e| {
|
|
||||||
println!("Error: {}", e);
|
|
||||||
()
|
|
||||||
})
|
|
||||||
.map(|(reader, writer)| {
|
|
||||||
let addr: Addr<Syn, _> = ChatClient::create(|ctx| {
|
|
||||||
ChatClient::add_stream(reader, ctx);
|
|
||||||
ChatClient(writer)
|
|
||||||
});
|
|
||||||
|
|
||||||
// start console loop
|
|
||||||
thread::spawn(move|| {
|
|
||||||
loop {
|
|
||||||
let mut cmd = String::new();
|
|
||||||
if io::stdin().read_line(&mut cmd).is_err() {
|
|
||||||
println!("error");
|
|
||||||
return
|
|
||||||
}
|
|
||||||
addr.do_send(ClientCommand(cmd));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct ChatClient(ClientWriter);
|
|
||||||
|
|
||||||
#[derive(Message)]
|
|
||||||
struct ClientCommand(String);
|
|
||||||
|
|
||||||
impl Actor for ChatClient {
|
|
||||||
type Context = Context<Self>;
|
|
||||||
|
|
||||||
fn started(&mut self, ctx: &mut Context<Self>) {
|
|
||||||
// start heartbeats otherwise server will disconnect after 10 seconds
|
|
||||||
self.hb(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stopped(&mut self, _: &mut Context<Self>) {
|
|
||||||
println!("Disconnected");
|
|
||||||
|
|
||||||
// Stop application on disconnect
|
|
||||||
Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChatClient {
|
|
||||||
fn hb(&self, ctx: &mut Context<Self>) {
|
|
||||||
ctx.run_later(Duration::new(1, 0), |act, ctx| {
|
|
||||||
act.0.ping("");
|
|
||||||
act.hb(ctx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle stdin commands
|
|
||||||
impl Handler<ClientCommand> for ChatClient {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ClientCommand, ctx: &mut Context<Self>) {
|
|
||||||
self.0.text(msg.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle server websocket messages
|
|
||||||
impl StreamHandler<Message, ProtocolError> for ChatClient {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: Message, ctx: &mut Context<Self>) {
|
|
||||||
match msg {
|
|
||||||
Message::Text(txt) => println!("Server: {:?}", txt),
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn started(&mut self, ctx: &mut Context<Self>) {
|
|
||||||
println!("Connected");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finished(&mut self, ctx: &mut Context<Self>) {
|
|
||||||
println!("Server disconnected");
|
|
||||||
ctx.stop()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
//! Simple echo websocket server.
|
|
||||||
//! Open `http://localhost:8080/ws/index.html` in browser
|
|
||||||
//! or [python console client](https://github.com/actix/actix-web/blob/master/examples/websocket-client.py)
|
|
||||||
//! could be used for testing.
|
|
||||||
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
/// do websocket handshake and start `MyWebSocket` actor
|
|
||||||
fn ws_index(r: HttpRequest) -> Result<HttpResponse> {
|
|
||||||
ws::start(r, MyWebSocket)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// websocket connection is long running connection, it easier
|
|
||||||
/// to handle with an actor
|
|
||||||
struct MyWebSocket;
|
|
||||||
|
|
||||||
impl Actor for MyWebSocket {
|
|
||||||
type Context = ws::WebsocketContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for `ws::Message`
|
|
||||||
impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
|
||||||
// process websocket messages
|
|
||||||
println!("WS: {:?}", msg);
|
|
||||||
match msg {
|
|
||||||
ws::Message::Ping(msg) => ctx.pong(&msg),
|
|
||||||
ws::Message::Text(text) => ctx.text(text),
|
|
||||||
ws::Message::Binary(bin) => ctx.binary(bin),
|
|
||||||
ws::Message::Close(_) => {
|
|
||||||
ctx.stop();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("ws-example");
|
|
||||||
|
|
||||||
let _addr = HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
// websocket route
|
|
||||||
.resource("/ws/", |r| r.method(Method::GET).f(ws_index))
|
|
||||||
// static files
|
|
||||||
.handler("/", fs::StaticFiles::new("../static/", true)
|
|
||||||
.index_file("index.html")))
|
|
||||||
// start http server on 127.0.0.1:8080
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""websocket cmd client for wssrv.py example."""
|
|
||||||
import argparse
|
|
||||||
import asyncio
|
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
|
|
||||||
def start_client(loop, url):
|
|
||||||
name = input('Please enter your name: ')
|
|
||||||
|
|
||||||
# send request
|
|
||||||
ws = yield from aiohttp.ClientSession().ws_connect(url, autoclose=False, autoping=False)
|
|
||||||
|
|
||||||
# input reader
|
|
||||||
def stdin_callback():
|
|
||||||
line = sys.stdin.buffer.readline().decode('utf-8')
|
|
||||||
if not line:
|
|
||||||
loop.stop()
|
|
||||||
else:
|
|
||||||
ws.send_str(name + ': ' + line)
|
|
||||||
loop.add_reader(sys.stdin.fileno(), stdin_callback)
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
def dispatch():
|
|
||||||
while True:
|
|
||||||
msg = yield from ws.receive()
|
|
||||||
|
|
||||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
|
||||||
print('Text: ', msg.data.strip())
|
|
||||||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
|
||||||
print('Binary: ', msg.data)
|
|
||||||
elif msg.type == aiohttp.WSMsgType.PING:
|
|
||||||
ws.pong()
|
|
||||||
elif msg.type == aiohttp.WSMsgType.PONG:
|
|
||||||
print('Pong received')
|
|
||||||
else:
|
|
||||||
if msg.type == aiohttp.WSMsgType.CLOSE:
|
|
||||||
yield from ws.close()
|
|
||||||
elif msg.type == aiohttp.WSMsgType.ERROR:
|
|
||||||
print('Error during receive %s' % ws.exception())
|
|
||||||
elif msg.type == aiohttp.WSMsgType.CLOSED:
|
|
||||||
pass
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
yield from dispatch()
|
|
||||||
|
|
||||||
|
|
||||||
ARGS = argparse.ArgumentParser(
|
|
||||||
description="websocket console client for wssrv.py example.")
|
|
||||||
ARGS.add_argument(
|
|
||||||
'--host', action="store", dest='host',
|
|
||||||
default='127.0.0.1', help='Host name')
|
|
||||||
ARGS.add_argument(
|
|
||||||
'--port', action="store", dest='port',
|
|
||||||
default=8080, type=int, help='Port number')
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
args = ARGS.parse_args()
|
|
||||||
if ':' in args.host:
|
|
||||||
args.host, port = args.host.split(':', 1)
|
|
||||||
args.port = int(port)
|
|
||||||
|
|
||||||
url = 'http://{}:{}/ws/'.format(args.host, args.port)
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.add_signal_handler(signal.SIGINT, loop.stop)
|
|
||||||
asyncio.Task(start_client(loop, url))
|
|
||||||
loop.run_forever()
|
|
@ -1,7 +0,0 @@
|
|||||||
[book]
|
|
||||||
title = "Actix web"
|
|
||||||
description = "Actix web framework guide"
|
|
||||||
author = "Actix Project and Contributors"
|
|
||||||
|
|
||||||
[output.html]
|
|
||||||
google-analytics = "UA-110322332-1"
|
|
@ -1,16 +0,0 @@
|
|||||||
# Summary
|
|
||||||
|
|
||||||
[Quickstart](./qs_1.md)
|
|
||||||
- [Getting Started](./qs_2.md)
|
|
||||||
- [Application](./qs_3.md)
|
|
||||||
- [Server](./qs_3_5.md)
|
|
||||||
- [Handler](./qs_4.md)
|
|
||||||
- [Errors](./qs_4_5.md)
|
|
||||||
- [URL Dispatch](./qs_5.md)
|
|
||||||
- [Request & Response](./qs_7.md)
|
|
||||||
- [Testing](./qs_8.md)
|
|
||||||
- [Middlewares](./qs_10.md)
|
|
||||||
- [Static file handling](./qs_12.md)
|
|
||||||
- [WebSockets](./qs_9.md)
|
|
||||||
- [HTTP/2](./qs_13.md)
|
|
||||||
- [Database integration](./qs_14.md)
|
|
@ -1,34 +0,0 @@
|
|||||||
# Quick start
|
|
||||||
|
|
||||||
Before you can start writing a actix web application, you’ll need a version of Rust installed.
|
|
||||||
We recommend you use rustup to install or configure such a version.
|
|
||||||
|
|
||||||
## Install Rust
|
|
||||||
|
|
||||||
Before we begin, we need to install Rust using the [rustup](https://www.rustup.rs/) installer:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl https://sh.rustup.rs -sSf | sh
|
|
||||||
```
|
|
||||||
|
|
||||||
If you already have rustup installed, run this command to ensure you have the latest version of Rust:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rustup update
|
|
||||||
```
|
|
||||||
|
|
||||||
Actix web framework requires rust version 1.21 and up.
|
|
||||||
|
|
||||||
## Running Examples
|
|
||||||
|
|
||||||
The fastest way to start experimenting with actix web is to clone the actix web repository
|
|
||||||
and run the included examples in the examples/ directory. The following set of
|
|
||||||
commands runs the `basics` example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/actix/actix-web
|
|
||||||
cd actix-web/examples/basics
|
|
||||||
cargo run
|
|
||||||
```
|
|
||||||
|
|
||||||
Check [examples/](https://github.com/actix/actix-web/tree/master/examples) directory for more examples.
|
|
@ -1,212 +0,0 @@
|
|||||||
# Middlewares
|
|
||||||
|
|
||||||
Actix middlewares system allows to add additional behavior to request/response processing.
|
|
||||||
Middleware can hook into incoming request process and modify request or halt request
|
|
||||||
processing and return response early. Also it can hook into response processing.
|
|
||||||
|
|
||||||
Typically middlewares involves in following actions:
|
|
||||||
|
|
||||||
* Pre-process the Request
|
|
||||||
* Post-process a Response
|
|
||||||
* Modify application state
|
|
||||||
* Access external services (redis, logging, sessions)
|
|
||||||
|
|
||||||
Middlewares are registered for each application and get executed in same order as
|
|
||||||
registration order. In general, *middleware* is a type that implements
|
|
||||||
[*Middleware trait*](../actix_web/middlewares/trait.Middleware.html). Each method
|
|
||||||
in this trait has default implementation. Each method can return result immediately
|
|
||||||
or *future* object.
|
|
||||||
|
|
||||||
Here is example of simple middleware that adds request and response headers:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate http;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use http::{header, HttpTryFrom};
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::middleware::{Middleware, Started, Response};
|
|
||||||
|
|
||||||
struct Headers; // <- Our middleware
|
|
||||||
|
|
||||||
/// Middleware implementation, middlewares are generic over application state,
|
|
||||||
/// so you can access state with `HttpRequest::state()` method.
|
|
||||||
impl<S> Middleware<S> for Headers {
|
|
||||||
|
|
||||||
/// Method is called when request is ready. It may return
|
|
||||||
/// future, which should resolve before next middleware get called.
|
|
||||||
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
|
||||||
req.headers_mut().insert(
|
|
||||||
header::CONTENT_TYPE, header::HeaderValue::from_static("text/plain"));
|
|
||||||
Ok(Started::Done)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Method is called when handler returns response,
|
|
||||||
/// but before sending http message to peer.
|
|
||||||
fn response(&self, req: &mut HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> {
|
|
||||||
resp.headers_mut().insert(
|
|
||||||
header::HeaderName::try_from("X-VERSION").unwrap(),
|
|
||||||
header::HeaderValue::from_static("0.2"));
|
|
||||||
Ok(Response::Done(resp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.middleware(Headers) // <- Register middleware, this method could be called multiple times
|
|
||||||
.resource("/", |r| r.h(httpcodes::HttpOk));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Active provides several useful middlewares, like *logging*, *user sessions*, etc.
|
|
||||||
|
|
||||||
|
|
||||||
## Logging
|
|
||||||
|
|
||||||
Logging is implemented as middleware.
|
|
||||||
It is common to register logging middleware as first middleware for application.
|
|
||||||
Logging middleware has to be registered for each application. *Logger* middleware
|
|
||||||
uses standard log crate to log information. You should enable logger for *actix_web*
|
|
||||||
package to see access log. ([env_logger](https://docs.rs/env_logger/*/env_logger/) or similar)
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
Create `Logger` middleware with the specified `format`.
|
|
||||||
Default `Logger` could be created with `default` method, it uses the default format:
|
|
||||||
|
|
||||||
```ignore
|
|
||||||
%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
|
||||||
```
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
use actix_web::Application;
|
|
||||||
use actix_web::middleware::Logger;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
Application::new()
|
|
||||||
.middleware(Logger::default())
|
|
||||||
.middleware(Logger::new("%a %{User-Agent}i"))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Here is example of default logging format:
|
|
||||||
|
|
||||||
```
|
|
||||||
INFO:actix_web::middleware::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397
|
|
||||||
INFO:actix_web::middleware::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] "GET /index.html HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0" 0.000646
|
|
||||||
```
|
|
||||||
|
|
||||||
### Format
|
|
||||||
|
|
||||||
`%%` The percent sign
|
|
||||||
|
|
||||||
`%a` Remote IP-address (IP-address of proxy if using reverse proxy)
|
|
||||||
|
|
||||||
`%t` Time when the request was started to process
|
|
||||||
|
|
||||||
`%P` The process ID of the child that serviced the request
|
|
||||||
|
|
||||||
`%r` First line of request
|
|
||||||
|
|
||||||
`%s` Response status code
|
|
||||||
|
|
||||||
`%b` Size of response in bytes, including HTTP headers
|
|
||||||
|
|
||||||
`%T` Time taken to serve the request, in seconds with floating fraction in .06f format
|
|
||||||
|
|
||||||
`%D` Time taken to serve the request, in milliseconds
|
|
||||||
|
|
||||||
`%{FOO}i` request.headers['FOO']
|
|
||||||
|
|
||||||
`%{FOO}o` response.headers['FOO']
|
|
||||||
|
|
||||||
`%{FOO}e` os.environ['FOO']
|
|
||||||
|
|
||||||
|
|
||||||
## Default headers
|
|
||||||
|
|
||||||
To set default response headers `DefaultHeaders` middleware could be used.
|
|
||||||
*DefaultHeaders* middleware does not set header if response headers already contains
|
|
||||||
specified header.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let app = Application::new()
|
|
||||||
.middleware(
|
|
||||||
middleware::DefaultHeaders::build()
|
|
||||||
.header("X-Version", "0.2")
|
|
||||||
.finish())
|
|
||||||
.resource("/test", |r| {
|
|
||||||
r.method(Method::GET).f(|req| httpcodes::HttpOk);
|
|
||||||
r.method(Method::HEAD).f(|req| httpcodes::HttpMethodNotAllowed);
|
|
||||||
})
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## User sessions
|
|
||||||
|
|
||||||
Actix provides general solution for session management.
|
|
||||||
[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware can be
|
|
||||||
use with different backend types to store session data in different backends.
|
|
||||||
By default only cookie session backend is implemented. Other backend implementations
|
|
||||||
could be added later.
|
|
||||||
|
|
||||||
[*Cookie session backend*](../actix_web/middleware/struct.CookieSessionBackend.html)
|
|
||||||
uses signed cookies as session storage. *Cookie session backend* creates sessions which
|
|
||||||
are limited to storing fewer than 4000 bytes of data (as the payload must fit into a
|
|
||||||
single cookie). Internal server error get generated if session contains more than 4000 bytes.
|
|
||||||
|
|
||||||
You need to pass a random value to the constructor of *CookieSessionBackend*.
|
|
||||||
This is private key for cookie session. When this value is changed, all session data is lost.
|
|
||||||
Note that whatever you write into your session is visible by the user (but not modifiable).
|
|
||||||
|
|
||||||
In general case, you create
|
|
||||||
[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware
|
|
||||||
and initializes it with specific backend implementation, like *CookieSessionBackend*.
|
|
||||||
To access session data
|
|
||||||
[*HttpRequest::session()*](../actix_web/middleware/trait.RequestSession.html#tymethod.session)
|
|
||||||
method has to be used. This method returns
|
|
||||||
[*Session*](../actix_web/middleware/struct.Session.html) object, which allows to get or set
|
|
||||||
session data.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend};
|
|
||||||
|
|
||||||
fn index(mut req: HttpRequest) -> Result<&'static str> {
|
|
||||||
// access session data
|
|
||||||
if let Some(count) = req.session().get::<i32>("counter")? {
|
|
||||||
println!("SESSION value: {}", count);
|
|
||||||
req.session().set("counter", count+1)?;
|
|
||||||
} else {
|
|
||||||
req.session().set("counter", 1)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok("Welcome!")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
# let sys = actix::System::new("basic-example");
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.middleware(SessionStorage::new( // <- create session middleware
|
|
||||||
CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend
|
|
||||||
.secure(false)
|
|
||||||
.finish()
|
|
||||||
)))
|
|
||||||
.bind("127.0.0.1:59880").unwrap()
|
|
||||||
.start();
|
|
||||||
# actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
|
||||||
# let _ = sys.run();
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,49 +0,0 @@
|
|||||||
# Static file handling
|
|
||||||
|
|
||||||
## Individual file
|
|
||||||
|
|
||||||
It is possible to serve static files with custom path pattern and `NamedFile`. To
|
|
||||||
match path tail we can use `[.*]` regex.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Result<fs::NamedFile> {
|
|
||||||
let path: PathBuf = req.match_info().query("tail")?;
|
|
||||||
Ok(fs::NamedFile::open(path)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Directory
|
|
||||||
|
|
||||||
To serve files from specific directory and sub-directories `StaticFiles` could be used.
|
|
||||||
`StaticFiles` must be registered with `Application::handler()` method otherwise
|
|
||||||
it won't be able to server sub-paths.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.handler("/static", fs::StaticFiles::new(".", true))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
First parameter is a base directory. Second parameter is *show_index*, if it is set to *true*
|
|
||||||
directory listing would be returned for directories, if it is set to *false*
|
|
||||||
then *404 Not Found* would be returned instead of directory listing.
|
|
||||||
|
|
||||||
Instead of showing files listing for directory, it is possible to redirect to specific
|
|
||||||
index file. Use
|
|
||||||
[*StaticFiles::index_file()*](../actix_web/s/struct.StaticFiles.html#method.index_file)
|
|
||||||
method to configure this redirect.
|
|
@ -1,44 +0,0 @@
|
|||||||
# HTTP/2.0
|
|
||||||
|
|
||||||
Actix web automatically upgrades connection to *HTTP/2.0* if possible.
|
|
||||||
|
|
||||||
## Negotiation
|
|
||||||
|
|
||||||
*HTTP/2.0* protocol over tls without prior knowledge requires
|
|
||||||
[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only
|
|
||||||
`rust-openssl` has support. Turn on `alpn` feature to enable `alpn` negotiation.
|
|
||||||
With enable `alpn` feature `HttpServer` provides
|
|
||||||
[serve_tls](../actix_web/struct.HttpServer.html#method.serve_tls) method.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
actix-web = { version = "0.3.3", features=["alpn"] }
|
|
||||||
openssl = { version="0.10", features = ["v110"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
use std::fs::File;
|
|
||||||
use actix_web::*;
|
|
||||||
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// load ssl keys
|
|
||||||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
|
||||||
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
|
|
||||||
builder.set_certificate_chain_file("cert.pem").unwrap();
|
|
||||||
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.resource("/index.html", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8080").unwrap();
|
|
||||||
.serve_ssl(builder).unwrap();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Upgrade to *HTTP/2.0* schema described in
|
|
||||||
[rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) is not supported.
|
|
||||||
Starting *HTTP/2* with prior knowledge is supported for both clear text connection
|
|
||||||
and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.section.3.4)
|
|
||||||
|
|
||||||
Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls)
|
|
||||||
for concrete example.
|
|
@ -1,125 +0,0 @@
|
|||||||
# Database integration
|
|
||||||
|
|
||||||
## Diesel
|
|
||||||
|
|
||||||
At the moment of 1.0 release Diesel does not support asynchronous operations.
|
|
||||||
But it possible to use `actix` synchronous actor system as a db interface api.
|
|
||||||
Technically sync actors are worker style actors, multiple of them
|
|
||||||
can be run in parallel and process messages from same queue (sync actors work in mpmc mode).
|
|
||||||
|
|
||||||
Let's create simple db api that can insert new user row into sqlite table.
|
|
||||||
We have to define sync actor and connection that this actor will use. Same approach
|
|
||||||
could be used for other databases.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
use actix::prelude::*;*
|
|
||||||
|
|
||||||
struct DbExecutor(SqliteConnection);
|
|
||||||
|
|
||||||
impl Actor for DbExecutor {
|
|
||||||
type Context = SyncContext<Self>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is definition of our actor. Now we need to define *create user* message and response.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
#[derive(Message)]
|
|
||||||
#[rtype(User, Error)]
|
|
||||||
struct CreateUser {
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
We can send `CreateUser` message to `DbExecutor` actor, and as result we get
|
|
||||||
`User` model. Now we need to define actual handler implementation for this message.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
impl Handler<CreateUser> for DbExecutor {
|
|
||||||
type Result = Result<User, Error>
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result
|
|
||||||
{
|
|
||||||
use self::schema::users::dsl::*;
|
|
||||||
|
|
||||||
// Create insertion model
|
|
||||||
let uuid = format!("{}", uuid::Uuid::new_v4());
|
|
||||||
let new_user = models::NewUser {
|
|
||||||
id: &uuid,
|
|
||||||
name: &msg.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
// normal diesl operations
|
|
||||||
diesel::insert_into(users)
|
|
||||||
.values(&new_user)
|
|
||||||
.execute(&self.0)
|
|
||||||
.expect("Error inserting person");
|
|
||||||
|
|
||||||
let mut items = users
|
|
||||||
.filter(id.eq(&uuid))
|
|
||||||
.load::<models::User>(&self.0)
|
|
||||||
.expect("Error loading person");
|
|
||||||
|
|
||||||
Ok(items.pop().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
That is it. Now we can use *DbExecutor* actor from any http handler or middleware.
|
|
||||||
All we need is to start *DbExecutor* actors and store address in a state where http handler
|
|
||||||
can access it.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
/// This is state where we will store *DbExecutor* address.
|
|
||||||
struct State {
|
|
||||||
db: Addr<Syn, DbExecutor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let sys = actix::System::new("diesel-example");
|
|
||||||
|
|
||||||
// Start 3 parallel db executors
|
|
||||||
let addr = SyncArbiter::start(3, || {
|
|
||||||
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start http server
|
|
||||||
HttpServer::new(move || {
|
|
||||||
Application::with_state(State{db: addr.clone()})
|
|
||||||
.resource("/{name}", |r| r.method(Method::GET).a(index))})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start().unwrap();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And finally we can use address in a request handler. We get message response
|
|
||||||
asynchronously, so handler needs to return future object, also `Route::a()` needs to be
|
|
||||||
used for async handler registration.
|
|
||||||
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
/// Async handler
|
|
||||||
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
let name = &req.match_info()["name"];
|
|
||||||
|
|
||||||
// Send message to `DbExecutor` actor
|
|
||||||
req.state().db.send(CreateUser{name: name.to_owned()})
|
|
||||||
.from_err()
|
|
||||||
.and_then(|res| {
|
|
||||||
match res {
|
|
||||||
Ok(user) => Ok(httpcodes::HttpOk.build().json(user)?),
|
|
||||||
Err(_) => Ok(httpcodes::HttpInternalServerError.into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Full example is available in
|
|
||||||
[examples directory](https://github.com/actix/actix-web/tree/master/examples/diesel/).
|
|
||||||
|
|
||||||
More information on sync actors could be found in
|
|
||||||
[actix documentation](https://docs.rs/actix/0.5.0/actix/sync/index.html).
|
|
@ -1,98 +0,0 @@
|
|||||||
# Getting Started
|
|
||||||
|
|
||||||
Let’s create and run our first actix web application. We’ll create a new Cargo project
|
|
||||||
that depends on actix web and then run the application.
|
|
||||||
|
|
||||||
In previous section we already installed required rust version. Now let's create new cargo projects.
|
|
||||||
|
|
||||||
## Hello, world!
|
|
||||||
|
|
||||||
Let’s write our first actix web application! Start by creating a new binary-based
|
|
||||||
Cargo project and changing into the new directory:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo new hello-world --bin
|
|
||||||
cd hello-world
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, add actix and actix web as dependencies of your project by ensuring your Cargo.toml
|
|
||||||
contains the following:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = "0.4"
|
|
||||||
```
|
|
||||||
|
|
||||||
In order to implement a web server, first we need to create a request handler.
|
|
||||||
|
|
||||||
A request handler is a function that accepts a `HttpRequest` instance as its only parameter
|
|
||||||
and returns a type that can be converted into `HttpResponse`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::*;
|
|
||||||
fn index(req: HttpRequest) -> &'static str {
|
|
||||||
"Hello world!"
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, create an `Application` instance and register the
|
|
||||||
request handler with the application's `resource` on a particular *HTTP method* and *path*::
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::*;
|
|
||||||
# fn index(req: HttpRequest) -> &'static str {
|
|
||||||
# "Hello world!"
|
|
||||||
# }
|
|
||||||
# fn main() {
|
|
||||||
Application::new()
|
|
||||||
.resource("/", |r| r.f(index));
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
After that, application instance can be used with `HttpServer` to listen for incoming
|
|
||||||
connections. Server accepts function that should return `HttpHandler` instance:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.resource("/", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8088")?
|
|
||||||
.run();
|
|
||||||
```
|
|
||||||
|
|
||||||
That's it. Now, compile and run the program with cargo run.
|
|
||||||
Head over to ``http://localhost:8088/`` to see the results.
|
|
||||||
|
|
||||||
Here is full source of main.rs file:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# use std::thread;
|
|
||||||
extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> &'static str {
|
|
||||||
"Hello world!"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
# // In the doctest suite we can't run blocking code - deliberately leak a thread
|
|
||||||
# // If copying this example in show-all mode make sure you skip the thread spawn
|
|
||||||
# // call.
|
|
||||||
# thread::spawn(|| {
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.resource("/", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088")
|
|
||||||
.run();
|
|
||||||
# });
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note on `actix` crate. Actix web framework is built on top of actix actor library.
|
|
||||||
`actix::System` initializes actor system, `HttpServer` is an actor and must run within
|
|
||||||
properly configured actix system. For more information please check
|
|
||||||
[actix documentation](https://actix.github.io/actix/actix/)
|
|
@ -1,109 +0,0 @@
|
|||||||
# Application
|
|
||||||
|
|
||||||
Actix web provides some primitives to build web servers and applications with Rust.
|
|
||||||
It provides routing, middlewares, pre-processing of requests, and post-processing of responses,
|
|
||||||
websocket protocol handling, multipart streams, etc.
|
|
||||||
|
|
||||||
All actix web server is built around `Application` instance.
|
|
||||||
It is used for registering routes for resources, middlewares.
|
|
||||||
Also it stores application specific state that is shared across all handlers
|
|
||||||
within same application.
|
|
||||||
|
|
||||||
Application acts as namespace for all routes, i.e all routes for specific application
|
|
||||||
has same url path prefix. Application prefix always contains leading "/" slash.
|
|
||||||
If supplied prefix does not contain leading slash, it get inserted.
|
|
||||||
Prefix should consists of value path segments. i.e for application with prefix `/app`
|
|
||||||
any request with following paths `/app`, `/app/` or `/app/test` would match,
|
|
||||||
but path `/application` would not match.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate tokio_core;
|
|
||||||
# use actix_web::*;
|
|
||||||
# fn index(req: HttpRequest) -> &'static str {
|
|
||||||
# "Hello world!"
|
|
||||||
# }
|
|
||||||
# fn main() {
|
|
||||||
let app = Application::new()
|
|
||||||
.prefix("/app")
|
|
||||||
.resource("/index.html", |r| r.method(Method::GET).f(index))
|
|
||||||
.finish()
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example application with `/app` prefix and `index.html` resource
|
|
||||||
get created. This resource is available as on `/app/index.html` url.
|
|
||||||
For more information check
|
|
||||||
[*URL Matching*](./qs_5.html#using-a-application-prefix-to-compose-applications) section.
|
|
||||||
|
|
||||||
Multiple applications could be served with one server:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate tokio_core;
|
|
||||||
# use tokio_core::net::TcpStream;
|
|
||||||
# use std::net::SocketAddr;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
HttpServer::new(|| vec![
|
|
||||||
Application::new()
|
|
||||||
.prefix("/app1")
|
|
||||||
.resource("/", |r| r.f(|r| httpcodes::HttpOk)),
|
|
||||||
Application::new()
|
|
||||||
.prefix("/app2")
|
|
||||||
.resource("/", |r| r.f(|r| httpcodes::HttpOk)),
|
|
||||||
Application::new()
|
|
||||||
.resource("/", |r| r.f(|r| httpcodes::HttpOk)),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
All `/app1` requests route to first application, `/app2` to second and then all other to third.
|
|
||||||
Applications get matched based on registration order, if application with more general
|
|
||||||
prefix is registered before less generic, that would effectively block less generic
|
|
||||||
application to get matched. For example if *application* with prefix "/" get registered
|
|
||||||
as first application, it would match all incoming requests.
|
|
||||||
|
|
||||||
## State
|
|
||||||
|
|
||||||
Application state is shared with all routes and resources within same application.
|
|
||||||
State could be accessed with `HttpRequest::state()` method as a read-only item
|
|
||||||
but interior mutability pattern with `RefCell` could be used to archive state mutability.
|
|
||||||
State could be accessed with `HttpContext::state()` in case of http actor.
|
|
||||||
State also available to route matching predicates and middlewares.
|
|
||||||
|
|
||||||
Let's write simple application that uses shared state. We are going to store requests count
|
|
||||||
in the state:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
#
|
|
||||||
use actix_web::*;
|
|
||||||
use std::cell::Cell;
|
|
||||||
|
|
||||||
// This struct represents state
|
|
||||||
struct AppState {
|
|
||||||
counter: Cell<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(req: HttpRequest<AppState>) -> String {
|
|
||||||
let count = req.state().counter.get() + 1; // <- get count
|
|
||||||
req.state().counter.set(count); // <- store new count in state
|
|
||||||
|
|
||||||
format!("Request number: {}", count) // <- response with count
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::with_state(AppState{counter: Cell::new(0)})
|
|
||||||
.resource("/", |r| r.method(Method::GET).f(index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note on application state, http server accepts application factory rather than application
|
|
||||||
instance. Http server construct application instance for each thread, so application state
|
|
||||||
must be constructed multiple times. If you want to share state between different thread
|
|
||||||
shared object should be used, like `Arc`. Application state does not need to be `Send` and `Sync`
|
|
||||||
but application factory must be `Send` + `Sync`.
|
|
@ -1,193 +0,0 @@
|
|||||||
# Server
|
|
||||||
|
|
||||||
[*HttpServer*](../actix_web/struct.HttpServer.html) type is responsible for
|
|
||||||
serving http requests. *HttpServer* accept application factory as a parameter,
|
|
||||||
Application factory must have `Send` + `Sync` boundaries. More about that in
|
|
||||||
*multi-threading* section. To bind to specific socket address `bind()` must be used.
|
|
||||||
This method could be called multiple times. To start http server one of the *start*
|
|
||||||
methods could be used. `start()` method start simple server, `start_tls()` or `start_ssl()`
|
|
||||||
starts ssl server. *HttpServer* is an actix actor, it has to be initialized
|
|
||||||
within properly configured actix system:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let sys = actix::System::new("guide");
|
|
||||||
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.resource("/", |r| r.h(httpcodes::HttpOk)))
|
|
||||||
.bind("127.0.0.1:59080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
# actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
It is possible to start server in separate thread with *spawn()* method. In that
|
|
||||||
case server spawns new thread and create new actix system in it. To stop
|
|
||||||
this server send `StopServer` message.
|
|
||||||
|
|
||||||
Http server is implemented as an actix actor. It is possible to communicate with server
|
|
||||||
via messaging system. All start methods like `start()`, `start_ssl()`, etc returns
|
|
||||||
address of the started http server. Actix http server accept several messages:
|
|
||||||
|
|
||||||
* `PauseServer` - Pause accepting incoming connections
|
|
||||||
* `ResumeServer` - Resume accepting incoming connections
|
|
||||||
* `StopServer` - Stop incoming connection processing, stop all workers and exit
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate futures;
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use futures::Future;
|
|
||||||
use actix_web::*;
|
|
||||||
use std::thread;
|
|
||||||
use std::sync::mpsc;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
|
||||||
let sys = actix::System::new("http-server");
|
|
||||||
let addr = HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.resource("/", |r| r.h(httpcodes::HttpOk)))
|
|
||||||
.bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0")
|
|
||||||
.shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds
|
|
||||||
.start();
|
|
||||||
let _ = tx.send(addr);
|
|
||||||
let _ = sys.run();
|
|
||||||
});
|
|
||||||
|
|
||||||
let addr = rx.recv().unwrap();
|
|
||||||
let _ = addr.send(
|
|
||||||
server::StopServer{graceful:true}).wait(); // <- Send `StopServer` message to server.
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Multi-threading
|
|
||||||
|
|
||||||
Http server automatically starts number of http workers, by default
|
|
||||||
this number is equal to number of logical cpu in the system. This number
|
|
||||||
could be overridden with `HttpServer::threads()` method.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate tokio_core;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.resource("/", |r| r.h(httpcodes::HttpOk)))
|
|
||||||
.threads(4); // <- Start 4 workers
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Server create separate application instance for each created worker. Application state
|
|
||||||
is not shared between threads, to share state `Arc` could be used. Application state
|
|
||||||
does not need to be `Send` and `Sync` but application factory must be `Send` + `Sync`.
|
|
||||||
|
|
||||||
## SSL
|
|
||||||
|
|
||||||
There are two `tls` and `alpn` features for ssl server. `tls` feature is for `native-tls`
|
|
||||||
integration and `alpn` is for `openssl`.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
actix-web = { git = "https://github.com/actix/actix-web", features=["alpn"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
use std::fs::File;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut file = File::open("identity.pfx").unwrap();
|
|
||||||
let mut pkcs12 = vec![];
|
|
||||||
file.read_to_end(&mut pkcs12).unwrap();
|
|
||||||
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap();
|
|
||||||
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.resource("/index.html", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.serve_ssl(pkcs12).unwrap();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note on *HTTP/2.0* protocol over tls without prior knowledge, it requires
|
|
||||||
[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only
|
|
||||||
`openssl` has `alpn ` support.
|
|
||||||
|
|
||||||
Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls)
|
|
||||||
for full example.
|
|
||||||
|
|
||||||
## Keep-Alive
|
|
||||||
|
|
||||||
Actix can wait for requests on a keep-alive connection. *Keep alive*
|
|
||||||
connection behavior is defined by server settings.
|
|
||||||
|
|
||||||
* `Some(75)` - enable 75 sec *keep alive* timer according request and response settings.
|
|
||||||
* `Some(0)` - disable *keep alive*.
|
|
||||||
* `None` - Use `SO_KEEPALIVE` socket option.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate tokio_core;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
HttpServer::new(||
|
|
||||||
Application::new()
|
|
||||||
.resource("/", |r| r.h(httpcodes::HttpOk)))
|
|
||||||
.keep_alive(None); // <- Use `SO_KEEPALIVE` socket option.
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If first option is selected then *keep alive* state
|
|
||||||
calculated based on response's *connection-type*. By default
|
|
||||||
`HttpResponse::connection_type` is not defined in that case *keep alive*
|
|
||||||
defined by request's http version. Keep alive is off for *HTTP/1.0*
|
|
||||||
and is on for *HTTP/1.1* and *HTTP/2.0*.
|
|
||||||
|
|
||||||
*Connection type* could be change with `HttpResponseBuilder::connection_type()` method.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::httpcodes::*;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
HttpOk.build()
|
|
||||||
.connection_type(headers::ConnectionType::Close) // <- Close connection
|
|
||||||
.force_close() // <- Alternative method
|
|
||||||
.finish().unwrap()
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Graceful shutdown
|
|
||||||
|
|
||||||
Actix http server support graceful shutdown. After receiving a stop signal, workers
|
|
||||||
have specific amount of time to finish serving requests. Workers still alive after the
|
|
||||||
timeout are force dropped. By default shutdown timeout sets to 30 seconds.
|
|
||||||
You can change this parameter with `HttpServer::shutdown_timeout()` method.
|
|
||||||
|
|
||||||
You can send stop message to server with server address and specify if you what
|
|
||||||
graceful shutdown or not. `start()` methods return address of the server.
|
|
||||||
|
|
||||||
Http server handles several OS signals. *CTRL-C* is available on all OSs,
|
|
||||||
other signals are available on unix systems.
|
|
||||||
|
|
||||||
* *SIGINT* - Force shutdown workers
|
|
||||||
* *SIGTERM* - Graceful shutdown workers
|
|
||||||
* *SIGQUIT* - Force shutdown workers
|
|
||||||
|
|
||||||
It is possible to disable signals handling with `HttpServer::disable_signals()` method.
|
|
@ -1,245 +0,0 @@
|
|||||||
# Handler
|
|
||||||
|
|
||||||
A request handler can by any object that implements
|
|
||||||
[*Handler trait*](../actix_web/dev/trait.Handler.html).
|
|
||||||
Request handling happen in two stages. First handler object get called.
|
|
||||||
Handle can return any object that implements
|
|
||||||
[*Responder trait*](../actix_web/trait.Responder.html#foreign-impls).
|
|
||||||
Then `respond_to()` get called on returned object. And finally
|
|
||||||
result of the `respond_to()` call get converted to `Reply` object.
|
|
||||||
|
|
||||||
By default actix provides `Responder` implementations for some standard types,
|
|
||||||
like `&'static str`, `String`, etc.
|
|
||||||
For complete list of implementations check
|
|
||||||
[*Responder documentation*](../actix_web/trait.Responder.html#foreign-impls).
|
|
||||||
|
|
||||||
Examples of valid handlers:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
fn index(req: HttpRequest) -> &'static str {
|
|
||||||
"Hello world!"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
fn index(req: HttpRequest) -> String {
|
|
||||||
"Hello world!".to_owned()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
fn index(req: HttpRequest) -> Bytes {
|
|
||||||
Bytes::from_static("Hello world!")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Some notes on shared application state and handler state. If you noticed
|
|
||||||
*Handler* trait is generic over *S*, which defines application state type. So
|
|
||||||
application state is accessible from handler with `HttpRequest::state()` method.
|
|
||||||
But state is accessible as a read-only reference, if you need mutable access to state
|
|
||||||
you have to implement it yourself. On other hand handler can mutable access it's own state
|
|
||||||
as `handle` method takes mutable reference to *self*. Beware, actix creates multiple copies
|
|
||||||
of application state and handlers, unique for each thread, so if you run your
|
|
||||||
application in several threads actix will create same amount as number of threads
|
|
||||||
of application state objects and handler objects.
|
|
||||||
|
|
||||||
Here is example of handler that stores number of processed requests:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::dev::Handler;
|
|
||||||
|
|
||||||
struct MyHandler(usize);
|
|
||||||
|
|
||||||
impl<S> Handler<S> for MyHandler {
|
|
||||||
type Result = HttpResponse;
|
|
||||||
|
|
||||||
/// Handle request
|
|
||||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
|
||||||
self.0 += 1;
|
|
||||||
httpcodes::HttpOk.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
This handler will work, but `self.0` value will be different depends on number of threads and
|
|
||||||
number of requests processed per thread. Proper implementation would use `Arc` and `AtomicUsize`
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::dev::Handler;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
||||||
|
|
||||||
struct MyHandler(Arc<AtomicUsize>);
|
|
||||||
|
|
||||||
impl<S> Handler<S> for MyHandler {
|
|
||||||
type Result = HttpResponse;
|
|
||||||
|
|
||||||
/// Handle request
|
|
||||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
|
||||||
self.0.fetch_add(1, Ordering::Relaxed);
|
|
||||||
httpcodes::HttpOk.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let sys = actix::System::new("example");
|
|
||||||
|
|
||||||
let inc = Arc::new(AtomicUsize::new(0));
|
|
||||||
|
|
||||||
HttpServer::new(
|
|
||||||
move || {
|
|
||||||
let cloned = inc.clone();
|
|
||||||
Application::new()
|
|
||||||
.resource("/", move |r| r.h(MyHandler(cloned)))
|
|
||||||
})
|
|
||||||
.bind("127.0.0.1:8088").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8088");
|
|
||||||
# actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Be careful with synchronization primitives like *Mutex* or *RwLock*. Actix web framework
|
|
||||||
handles request asynchronously, by blocking thread execution all concurrent
|
|
||||||
request handling processes would block. If you need to share or update some state
|
|
||||||
from multiple threads consider using [actix](https://actix.github.io/actix/actix/) actor system.
|
|
||||||
|
|
||||||
## Response with custom type
|
|
||||||
|
|
||||||
To return custom type directly from handler function, type needs to implement `Responder` trait.
|
|
||||||
Let's create response for custom type that serializes to `application/json` response:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct MyObj {
|
|
||||||
name: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Responder
|
|
||||||
impl Responder for MyObj {
|
|
||||||
type Item = HttpResponse;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse> {
|
|
||||||
let body = serde_json::to_string(&self)?;
|
|
||||||
|
|
||||||
// Create response and set content type
|
|
||||||
Ok(HttpResponse::Ok()
|
|
||||||
.content_type("application/json")
|
|
||||||
.body(body)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Because `MyObj` implements `Responder`, it is possible to return it directly
|
|
||||||
fn index(req: HttpRequest) -> MyObj {
|
|
||||||
MyObj{name: "user"}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let sys = actix::System::new("example");
|
|
||||||
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.resource("/", |r| r.method(Method::GET).f(index)))
|
|
||||||
.bind("127.0.0.1:8088").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8088");
|
|
||||||
# actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Async handlers
|
|
||||||
|
|
||||||
There are two different types of async handlers.
|
|
||||||
|
|
||||||
Response object could be generated asynchronously or more precisely, any type
|
|
||||||
that implements [*Responder*](../actix_web/trait.Responder.html) trait. In this case handle must
|
|
||||||
return `Future` object that resolves to *Responder* type, i.e:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# extern crate bytes;
|
|
||||||
# use actix_web::*;
|
|
||||||
# use bytes::Bytes;
|
|
||||||
# use futures::stream::once;
|
|
||||||
# use futures::future::{FutureResult, result};
|
|
||||||
fn index(req: HttpRequest) -> FutureResult<HttpResponse, Error> {
|
|
||||||
|
|
||||||
result(HttpResponse::Ok()
|
|
||||||
.content_type("text/html")
|
|
||||||
.body(format!("Hello!"))
|
|
||||||
.map_err(|e| e.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index2(req: HttpRequest) -> FutureResult<&'static str, Error> {
|
|
||||||
result(Ok("Welcome!"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.resource("/async", |r| r.route().a(index))
|
|
||||||
.resource("/", |r| r.route().a(index2))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Or response body can be generated asynchronously. In this case body
|
|
||||||
must implement stream trait `Stream<Item=Bytes, Error=Error>`, i.e:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# extern crate bytes;
|
|
||||||
# use actix_web::*;
|
|
||||||
# use bytes::Bytes;
|
|
||||||
# use futures::stream::once;
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
let body = once(Ok(Bytes::from_static(b"test")));
|
|
||||||
|
|
||||||
HttpResponse::Ok()
|
|
||||||
.content_type("application/json")
|
|
||||||
.body(Body::Streaming(Box::new(body))).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.resource("/async", |r| r.f(index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Both methods could be combined. (i.e Async response with streaming body)
|
|
||||||
|
|
||||||
## Tokio core handle
|
|
||||||
|
|
||||||
Any actix web handler runs within properly configured
|
|
||||||
[actix system](https://actix.github.io/actix/actix/struct.System.html)
|
|
||||||
and [arbiter](https://actix.github.io/actix/actix/struct.Arbiter.html).
|
|
||||||
You can always get access to tokio handle via
|
|
||||||
[Arbiter::handle()](https://actix.github.io/actix/actix/struct.Arbiter.html#method.handle)
|
|
||||||
method.
|
|
@ -1,151 +0,0 @@
|
|||||||
# Errors
|
|
||||||
|
|
||||||
Actix uses [`Error` type](../actix_web/error/struct.Error.html)
|
|
||||||
and [`ResponseError` trait](../actix_web/error/trait.ResponseError.html)
|
|
||||||
for handling handler's errors.
|
|
||||||
Any error that implements `ResponseError` trait can be returned as error value.
|
|
||||||
*Handler* can return *Result* object, actix by default provides
|
|
||||||
`Responder` implementation for compatible result object. Here is implementation
|
|
||||||
definition:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
impl<T: Responder, E: Into<Error>> Responder for Result<T, E>
|
|
||||||
```
|
|
||||||
|
|
||||||
And any error that implements `ResponseError` can be converted into `Error` object.
|
|
||||||
For example if *handler* function returns `io::Error`, it would be converted
|
|
||||||
into `HttpInternalServerError` response. Implementation for `io::Error` is provided
|
|
||||||
by default.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::*;
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> io::Result<fs::NamedFile> {
|
|
||||||
Ok(fs::NamedFile::open("static/index.html")?)
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# fn main() {
|
|
||||||
# Application::new()
|
|
||||||
# .resource(r"/a/index.html", |r| r.f(index))
|
|
||||||
# .finish();
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Custom error response
|
|
||||||
|
|
||||||
To add support for custom errors, all we need to do is just implement `ResponseError` trait
|
|
||||||
for custom error. `ResponseError` trait has default implementation
|
|
||||||
for `error_response()` method, it generates *500* response.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
#[macro_use] extern crate failure;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
#[derive(Fail, Debug)]
|
|
||||||
#[fail(display="my error")]
|
|
||||||
struct MyError {
|
|
||||||
name: &'static str
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Use default implementation for `error_response()` method
|
|
||||||
impl error::ResponseError for MyError {}
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Result<&'static str, MyError> {
|
|
||||||
Err(MyError{name: "test"})
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# fn main() {
|
|
||||||
# Application::new()
|
|
||||||
# .resource(r"/a/index.html", |r| r.f(index))
|
|
||||||
# .finish();
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example *index* handler will always return *500* response. But it is easy
|
|
||||||
to return different responses for different type of errors.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
#[macro_use] extern crate failure;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
#[derive(Fail, Debug)]
|
|
||||||
enum MyError {
|
|
||||||
#[fail(display="internal error")]
|
|
||||||
InternalError,
|
|
||||||
#[fail(display="bad request")]
|
|
||||||
BadClientData,
|
|
||||||
#[fail(display="timeout")]
|
|
||||||
Timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::ResponseError for MyError {
|
|
||||||
fn error_response(&self) -> HttpResponse {
|
|
||||||
match *self {
|
|
||||||
MyError::InternalError => HttpResponse::new(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR, Body::Empty),
|
|
||||||
MyError::BadClientData => HttpResponse::new(
|
|
||||||
StatusCode::BAD_REQUEST, Body::Empty),
|
|
||||||
MyError::Timeout => HttpResponse::new(
|
|
||||||
StatusCode::GATEWAY_TIMEOUT, Body::Empty),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Result<&'static str, MyError> {
|
|
||||||
Err(MyError::BadClientData)
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# fn main() {
|
|
||||||
# Application::new()
|
|
||||||
# .resource(r"/a/index.html", |r| r.f(index))
|
|
||||||
# .finish();
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error helpers
|
|
||||||
|
|
||||||
Actix provides set of error helper types. It is possible to use them to generate
|
|
||||||
specific error response. We can use helper types for first example with custom error.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
#[macro_use] extern crate failure;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct MyError {
|
|
||||||
name: &'static str
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Result<&'static str> {
|
|
||||||
let result: Result<&'static str, MyError> = Err(MyError{name: "test"});
|
|
||||||
|
|
||||||
Ok(result.map_err(error::ErrorBadRequest)?)
|
|
||||||
}
|
|
||||||
# fn main() {
|
|
||||||
# Application::new()
|
|
||||||
# .resource(r"/a/index.html", |r| r.f(index))
|
|
||||||
# .finish();
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example *BAD REQUEST* response get generated for `MyError` error.
|
|
||||||
|
|
||||||
## Error logging
|
|
||||||
|
|
||||||
Actix logs all errors with `WARN` log level. If log level set to `DEBUG`
|
|
||||||
and `RUST_BACKTRACE` is enabled, backtrace get logged. The Error type uses
|
|
||||||
cause's error backtrace if available, if the underlying failure does not provide
|
|
||||||
a backtrace, a new backtrace is constructed pointing to that conversion point
|
|
||||||
(rather than the origin of the error). This construction only happens if there
|
|
||||||
is no underlying backtrace; if it does have a backtrace no new backtrace is constructed.
|
|
||||||
|
|
||||||
You can enable backtrace and debug logging with following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
>> RUST_BACKTRACE=1 RUST_LOG=actix_web=debug cargo run
|
|
||||||
```
|
|
@ -1,575 +0,0 @@
|
|||||||
# URL Dispatch
|
|
||||||
|
|
||||||
URL dispatch provides a simple way to map URLs to `Handler` code using a simple pattern matching
|
|
||||||
language. If one of the patterns matches the path information associated with a request,
|
|
||||||
a particular handler object is invoked. A handler is a specific object that implements
|
|
||||||
`Handler` trait, defined in your application, that receives the request and returns
|
|
||||||
a response object. More information is available in [handler section](../qs_4.html).
|
|
||||||
|
|
||||||
## Resource configuration
|
|
||||||
|
|
||||||
Resource configuration is the act of adding a new resource to an application.
|
|
||||||
A resource has a name, which acts as an identifier to be used for URL generation.
|
|
||||||
The name also allows developers to add routes to existing resources.
|
|
||||||
A resource also has a pattern, meant to match against the *PATH* portion of a *URL*,
|
|
||||||
it does not match against *QUERY* portion (the portion following the scheme and
|
|
||||||
port, e.g., */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*).
|
|
||||||
|
|
||||||
The [Application::resource](../actix_web/struct.Application.html#method.resource) methods
|
|
||||||
add a single resource to application routing table. This method accepts *path pattern*
|
|
||||||
and resource configuration function.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::*;
|
|
||||||
# use actix_web::httpcodes::*;
|
|
||||||
#
|
|
||||||
# fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
# unimplemented!()
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.resource("/prefix", |r| r.f(index))
|
|
||||||
.resource("/user/{name}",
|
|
||||||
|r| r.method(Method::GET).f(|req| HttpOk))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
*Configuration function* has following type:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
FnOnce(&mut Resource<_>) -> ()
|
|
||||||
```
|
|
||||||
|
|
||||||
*Configuration function* can set name and register specific routes.
|
|
||||||
If resource does not contain any route or does not have any matching routes it
|
|
||||||
returns *NOT FOUND* http resources.
|
|
||||||
|
|
||||||
## Configuring a Route
|
|
||||||
|
|
||||||
Resource contains set of routes. Each route in turn has set of predicates and handler.
|
|
||||||
New route could be created with `Resource::route()` method which returns reference
|
|
||||||
to new *Route* instance. By default *route* does not contain any predicates, so matches
|
|
||||||
all requests and default handler is `HttpNotFound`.
|
|
||||||
|
|
||||||
Application routes incoming requests based on route criteria which is defined during
|
|
||||||
resource registration and route registration. Resource matches all routes it contains in
|
|
||||||
the order that the routes were registered via `Resource::route()`. *Route* can contain
|
|
||||||
any number of *predicates* but only one handler.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::*;
|
|
||||||
# use actix_web::httpcodes::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.resource("/path", |resource|
|
|
||||||
resource.route()
|
|
||||||
.filter(pred::Get())
|
|
||||||
.filter(pred::Header("content-type", "text/plain"))
|
|
||||||
.f(|req| HttpOk)
|
|
||||||
)
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example `index` get called for *GET* request,
|
|
||||||
if request contains `Content-Type` header and value of this header is *text/plain*
|
|
||||||
and path equals to `/test`. Resource calls handle of the first matches route.
|
|
||||||
If resource can not match any route "NOT FOUND" response get returned.
|
|
||||||
|
|
||||||
[*Resource::route()*](../actix_web/struct.Resource.html#method.route) method returns
|
|
||||||
[*Route*](../actix_web/struct.Route.html) object. Route can be configured with
|
|
||||||
builder-like pattern. Following configuration methods are available:
|
|
||||||
|
|
||||||
* [*Route::filter()*](../actix_web/struct.Route.html#method.filter) method registers new predicate,
|
|
||||||
any number of predicates could be registered for each route.
|
|
||||||
|
|
||||||
* [*Route::f()*](../actix_web/struct.Route.html#method.f) method registers handler function
|
|
||||||
for this route. Only one handler could be registered. Usually handler registration
|
|
||||||
is the last config operation. Handler function could be function or closure and has type
|
|
||||||
`Fn(HttpRequest<S>) -> R + 'static`
|
|
||||||
|
|
||||||
* [*Route::h()*](../actix_web/struct.Route.html#method.h) method registers handler object
|
|
||||||
that implements `Handler` trait. This is similar to `f()` method, only one handler could
|
|
||||||
be registered. Handler registration is the last config operation.
|
|
||||||
|
|
||||||
* [*Route::a()*](../actix_web/struct.Route.html#method.a) method registers async handler
|
|
||||||
function for this route. Only one handler could be registered. Handler registration
|
|
||||||
is the last config operation. Handler function could be function or closure and has type
|
|
||||||
`Fn(HttpRequest<S>) -> Future<Item = HttpResponse, Error = Error> + 'static`
|
|
||||||
|
|
||||||
## Route matching
|
|
||||||
|
|
||||||
The main purpose of route configuration is to match (or not match) the request's `path`
|
|
||||||
against a URL path pattern. `path` represents the path portion of the URL that was requested.
|
|
||||||
|
|
||||||
The way that *actix* does this is very simple. When a request enters the system,
|
|
||||||
for each resource configuration declaration present in the system, actix checks
|
|
||||||
the request's path against the pattern declared. This checking happens in the order that
|
|
||||||
the routes were declared via `Application::resource()` method. If resource could not be found,
|
|
||||||
*default resource* get used as matched resource.
|
|
||||||
|
|
||||||
When a route configuration is declared, it may contain route predicate arguments. All route
|
|
||||||
predicates associated with a route declaration must be `true` for the route configuration to
|
|
||||||
be used for a given request during a check. If any predicate in the set of route predicate
|
|
||||||
arguments provided to a route configuration returns `false` during a check, that route is
|
|
||||||
skipped and route matching continues through the ordered set of routes.
|
|
||||||
|
|
||||||
If any route matches, the route matching process stops and the handler associated with
|
|
||||||
route get invoked.
|
|
||||||
|
|
||||||
If no route matches after all route patterns are exhausted, *NOT FOUND* response get returned.
|
|
||||||
|
|
||||||
## Resource pattern syntax
|
|
||||||
|
|
||||||
The syntax of the pattern matching language used by the actix in the pattern
|
|
||||||
argument is straightforward.
|
|
||||||
|
|
||||||
The pattern used in route configuration may start with a slash character. If the pattern
|
|
||||||
does not start with a slash character, an implicit slash will be prepended
|
|
||||||
to it at matching time. For example, the following patterns are equivalent:
|
|
||||||
|
|
||||||
```
|
|
||||||
{foo}/bar/baz
|
|
||||||
```
|
|
||||||
|
|
||||||
and:
|
|
||||||
|
|
||||||
```
|
|
||||||
/{foo}/bar/baz
|
|
||||||
```
|
|
||||||
|
|
||||||
A *variable part* (replacement marker) is specified in the form *{identifier}*,
|
|
||||||
where this means "accept any characters up to the next slash character and use this
|
|
||||||
as the name in the `HttpRequest.match_info()` object".
|
|
||||||
|
|
||||||
A replacement marker in a pattern matches the regular expression `[^{}/]+`.
|
|
||||||
|
|
||||||
A match_info is the `Params` object representing the dynamic parts extracted from a
|
|
||||||
*URL* based on the routing pattern. It is available as *request.match_info*. For example, the
|
|
||||||
following pattern defines one literal segment (foo) and two replacement markers (baz, and bar):
|
|
||||||
|
|
||||||
```
|
|
||||||
foo/{baz}/{bar}
|
|
||||||
```
|
|
||||||
|
|
||||||
The above pattern will match these URLs, generating the following match information:
|
|
||||||
|
|
||||||
```
|
|
||||||
foo/1/2 -> Params {'baz':'1', 'bar':'2'}
|
|
||||||
foo/abc/def -> Params {'baz':'abc', 'bar':'def'}
|
|
||||||
```
|
|
||||||
|
|
||||||
It will not match the following patterns however:
|
|
||||||
|
|
||||||
```
|
|
||||||
foo/1/2/ -> No match (trailing slash)
|
|
||||||
bar/abc/def -> First segment literal mismatch
|
|
||||||
```
|
|
||||||
|
|
||||||
The match for a segment replacement marker in a segment will be done only up to
|
|
||||||
the first non-alphanumeric character in the segment in the pattern. So, for instance,
|
|
||||||
if this route pattern was used:
|
|
||||||
|
|
||||||
```
|
|
||||||
foo/{name}.html
|
|
||||||
```
|
|
||||||
|
|
||||||
The literal path */foo/biz.html* will match the above route pattern, and the match result
|
|
||||||
will be `Params{'name': 'biz'}`. However, the literal path */foo/biz* will not match,
|
|
||||||
because it does not contain a literal *.html* at the end of the segment represented
|
|
||||||
by *{name}.html* (it only contains biz, not biz.html).
|
|
||||||
|
|
||||||
To capture both segments, two replacement markers can be used:
|
|
||||||
|
|
||||||
```
|
|
||||||
foo/{name}.{ext}
|
|
||||||
```
|
|
||||||
|
|
||||||
The literal path */foo/biz.html* will match the above route pattern, and the match
|
|
||||||
result will be *Params{'name': 'biz', 'ext': 'html'}*. This occurs because there is a
|
|
||||||
literal part of *.* (period) between the two replacement markers *{name}* and *{ext}*.
|
|
||||||
|
|
||||||
Replacement markers can optionally specify a regular expression which will be used to decide
|
|
||||||
whether a path segment should match the marker. To specify that a replacement marker should
|
|
||||||
match only a specific set of characters as defined by a regular expression, you must use a
|
|
||||||
slightly extended form of replacement marker syntax. Within braces, the replacement marker
|
|
||||||
name must be followed by a colon, then directly thereafter, the regular expression. The default
|
|
||||||
regular expression associated with a replacement marker *[^/]+* matches one or more characters
|
|
||||||
which are not a slash. For example, under the hood, the replacement marker *{foo}* can more
|
|
||||||
verbosely be spelled as *{foo:[^/]+}*. You can change this to be an arbitrary regular expression
|
|
||||||
to match an arbitrary sequence of characters, such as *{foo:\d+}* to match only digits.
|
|
||||||
|
|
||||||
Segments must contain at least one character in order to match a segment replacement marker.
|
|
||||||
For example, for the URL */abc/*:
|
|
||||||
|
|
||||||
* */abc/{foo}* will not match.
|
|
||||||
* */{foo}/* will match.
|
|
||||||
|
|
||||||
Note that path will be URL-unquoted and decoded into valid unicode string before
|
|
||||||
matching pattern and values representing matched path segments will be URL-unquoted too.
|
|
||||||
So for instance, the following pattern:
|
|
||||||
|
|
||||||
```
|
|
||||||
foo/{bar}
|
|
||||||
```
|
|
||||||
|
|
||||||
When matching the following URL:
|
|
||||||
|
|
||||||
```
|
|
||||||
http://example.com/foo/La%20Pe%C3%B1a
|
|
||||||
```
|
|
||||||
|
|
||||||
The matchdict will look like so (the value is URL-decoded):
|
|
||||||
|
|
||||||
```
|
|
||||||
Params{'bar': 'La Pe\xf1a'}
|
|
||||||
```
|
|
||||||
|
|
||||||
Literal strings in the path segment should represent the decoded value of the
|
|
||||||
path provided to actix. You don't want to use a URL-encoded value in the pattern.
|
|
||||||
For example, rather than this:
|
|
||||||
|
|
||||||
```
|
|
||||||
/Foo%20Bar/{baz}
|
|
||||||
```
|
|
||||||
|
|
||||||
You'll want to use something like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
/Foo Bar/{baz}
|
|
||||||
```
|
|
||||||
|
|
||||||
It is possible to get "tail match". For this purpose custom regex has to be used.
|
|
||||||
|
|
||||||
```
|
|
||||||
foo/{bar}/{tail:.*}
|
|
||||||
```
|
|
||||||
|
|
||||||
The above pattern will match these URLs, generating the following match information:
|
|
||||||
|
|
||||||
```
|
|
||||||
foo/1/2/ -> Params{'bar':'1', 'tail': '2/'}
|
|
||||||
foo/abc/def/a/b/c -> Params{'bar':u'abc', 'tail': 'def/a/b/c'}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Match information
|
|
||||||
|
|
||||||
All values representing matched path segments are available in
|
|
||||||
[`HttpRequest::match_info`](../actix_web/struct.HttpRequest.html#method.match_info).
|
|
||||||
Specific value can be received with
|
|
||||||
[`Params::get()`](../actix_web/dev/struct.Params.html#method.get) method.
|
|
||||||
|
|
||||||
Any matched parameter can be deserialized into specific type if this type
|
|
||||||
implements `FromParam` trait. For example most of standard integer types
|
|
||||||
implements `FromParam` trait. i.e.:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Result<String> {
|
|
||||||
let v1: u8 = req.match_info().query("v1")?;
|
|
||||||
let v2: u8 = req.match_info().query("v2")?;
|
|
||||||
Ok(format!("Values {} {}", v1, v2))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.resource(r"/a/{v1}/{v2}/", |r| r.f(index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For this example for path '/a/1/2/', values v1 and v2 will resolve to "1" and "2".
|
|
||||||
|
|
||||||
It is possible to create a `PathBuf` from a tail path parameter. The returned `PathBuf` is
|
|
||||||
percent-decoded. If a segment is equal to "..", the previous segment (if
|
|
||||||
any) is skipped.
|
|
||||||
|
|
||||||
For security purposes, if a segment meets any of the following conditions,
|
|
||||||
an `Err` is returned indicating the condition met:
|
|
||||||
|
|
||||||
* Decoded segment starts with any of: `.` (except `..`), `*`
|
|
||||||
* Decoded segment ends with any of: `:`, `>`, `<`
|
|
||||||
* Decoded segment contains any of: `/`
|
|
||||||
* On Windows, decoded segment contains any of: '\'
|
|
||||||
* Percent-encoding results in invalid UTF8.
|
|
||||||
|
|
||||||
As a result of these conditions, a `PathBuf` parsed from request path parameter is
|
|
||||||
safe to interpolate within, or use as a suffix of, a path without additional checks.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Result<String> {
|
|
||||||
let path: PathBuf = req.match_info().query("tail")?;
|
|
||||||
Ok(format!("Path {:?}", path))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
List of `FromParam` implementation could be found in
|
|
||||||
[api docs](../actix_web/dev/trait.FromParam.html#foreign-impls)
|
|
||||||
|
|
||||||
## Generating resource URLs
|
|
||||||
|
|
||||||
Use the [HttpRequest.url_for()](../actix_web/struct.HttpRequest.html#method.url_for)
|
|
||||||
method to generate URLs based on resource patterns. For example, if you've configured a
|
|
||||||
resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::*;
|
|
||||||
# use actix_web::httpcodes::*;
|
|
||||||
#
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
|
|
||||||
HttpOk.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let app = Application::new()
|
|
||||||
.resource("/test/{a}/{b}/{c}", |r| {
|
|
||||||
r.name("foo"); // <- set resource name, then it could be used in `url_for`
|
|
||||||
r.method(Method::GET).f(|_| httpcodes::HttpOk);
|
|
||||||
})
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This would return something like the string *http://example.com/test/1/2/3* (at least if
|
|
||||||
the current protocol and hostname implied http://example.com).
|
|
||||||
`url_for()` method return [*Url object*](https://docs.rs/url/1.6.0/url/struct.Url.html) so you
|
|
||||||
can modify this url (add query parameters, anchor, etc).
|
|
||||||
`url_for()` could be called only for *named* resources otherwise error get returned.
|
|
||||||
|
|
||||||
## External resources
|
|
||||||
|
|
||||||
Resources that are valid URLs, could be registered as external resources. They are useful
|
|
||||||
for URL generation purposes only and are never considered for matching at request time.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
|
|
||||||
let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
|
|
||||||
assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
|
|
||||||
Ok(httpcodes::HttpOk.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let app = Application::new()
|
|
||||||
.resource("/index.html", |r| r.f(index))
|
|
||||||
.external_resource("youtube", "https://youtube.com/watch/{video_id}")
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Path normalization and redirecting to slash-appended routes
|
|
||||||
|
|
||||||
By normalizing it means:
|
|
||||||
|
|
||||||
- Add a trailing slash to the path.
|
|
||||||
- Double slashes are replaced by one.
|
|
||||||
|
|
||||||
The handler returns as soon as it finds a path that resolves
|
|
||||||
correctly. The order if all enable is 1) merge, 3) both merge and append
|
|
||||||
and 3) append. If the path resolves with
|
|
||||||
at least one of those conditions, it will redirect to the new path.
|
|
||||||
|
|
||||||
If *append* is *true* append slash when needed. If a resource is
|
|
||||||
defined with trailing slash and the request comes without it, it will
|
|
||||||
append it automatically.
|
|
||||||
|
|
||||||
If *merge* is *true*, merge multiple consecutive slashes in the path into one.
|
|
||||||
|
|
||||||
This handler designed to be use as a handler for application's *default resource*.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# #[macro_use] extern crate serde_derive;
|
|
||||||
# use actix_web::*;
|
|
||||||
#
|
|
||||||
# fn index(req: HttpRequest) -> httpcodes::StaticResponse {
|
|
||||||
# httpcodes::HttpOk
|
|
||||||
# }
|
|
||||||
fn main() {
|
|
||||||
let app = Application::new()
|
|
||||||
.resource("/resource/", |r| r.f(index))
|
|
||||||
.default_resource(|r| r.h(NormalizePath::default()))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example `/resource`, `//resource///` will be redirected to `/resource/` url.
|
|
||||||
|
|
||||||
In this example path normalization handler get registered for all method,
|
|
||||||
but you should not rely on this mechanism to redirect *POST* requests. The redirect of the
|
|
||||||
slash-appending *Not Found* will turn a *POST* request into a GET, losing any
|
|
||||||
*POST* data in the original request.
|
|
||||||
|
|
||||||
It is possible to register path normalization only for *GET* requests only
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# #[macro_use] extern crate serde_derive;
|
|
||||||
# use actix_web::*;
|
|
||||||
#
|
|
||||||
# fn index(req: HttpRequest) -> httpcodes::StaticResponse {
|
|
||||||
# httpcodes::HttpOk
|
|
||||||
# }
|
|
||||||
fn main() {
|
|
||||||
let app = Application::new()
|
|
||||||
.resource("/resource/", |r| r.f(index))
|
|
||||||
.default_resource(|r| r.method(Method::GET).h(NormalizePath::default()))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using a Application Prefix to Compose Applications
|
|
||||||
|
|
||||||
The `Application::prefix()`" method allows to set specific application prefix.
|
|
||||||
This prefix represents a resource prefix that will be prepended to all resource patterns added
|
|
||||||
by the resource configuration. This can be used to help mount a set of routes at a different
|
|
||||||
location than the included callable's author intended while still maintaining the same
|
|
||||||
resource names.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::*;
|
|
||||||
#
|
|
||||||
fn show_users(req: HttpRequest) -> HttpResponse {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.prefix("/users")
|
|
||||||
.resource("/show", |r| r.f(show_users))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In the above example, the *show_users* route will have an effective route pattern of
|
|
||||||
*/users/show* instead of */show* because the application's prefix argument will be prepended
|
|
||||||
to the pattern. The route will then only match if the URL path is */users/show*,
|
|
||||||
and when the `HttpRequest.url_for()` function is called with the route name show_users,
|
|
||||||
it will generate a URL with that same path.
|
|
||||||
|
|
||||||
## Custom route predicates
|
|
||||||
|
|
||||||
You can think of predicate as simple function that accept *request* object reference
|
|
||||||
and returns *true* or *false*. Formally predicate is any object that implements
|
|
||||||
[`Predicate`](../actix_web/pred/trait.Predicate.html) trait. Actix provides
|
|
||||||
several predicates, you can check [functions section](../actix_web/pred/index.html#functions)
|
|
||||||
of api docs.
|
|
||||||
|
|
||||||
Here is simple predicates that check that request contains specific *header*:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate http;
|
|
||||||
# use actix_web::*;
|
|
||||||
# use actix_web::httpcodes::*;
|
|
||||||
use http::header::CONTENT_TYPE;
|
|
||||||
use actix_web::pred::Predicate;
|
|
||||||
|
|
||||||
struct ContentTypeHeader;
|
|
||||||
|
|
||||||
impl<S: 'static> Predicate<S> for ContentTypeHeader {
|
|
||||||
|
|
||||||
fn check(&self, req: &mut HttpRequest<S>) -> bool {
|
|
||||||
req.headers().contains_key(CONTENT_TYPE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.resource("/index.html", |r|
|
|
||||||
r.route()
|
|
||||||
.filter(ContentTypeHeader)
|
|
||||||
.h(HttpOk));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example *index* handler will be called only if request contains *CONTENT-TYPE* header.
|
|
||||||
|
|
||||||
Predicates can have access to application's state via `HttpRequest::state()` method.
|
|
||||||
Also predicates can store extra information in
|
|
||||||
[requests`s extensions](../actix_web/struct.HttpRequest.html#method.extensions).
|
|
||||||
|
|
||||||
### Modifying predicate values
|
|
||||||
|
|
||||||
You can invert the meaning of any predicate value by wrapping it in a `Not` predicate.
|
|
||||||
For example if you want to return "METHOD NOT ALLOWED" response for all methods
|
|
||||||
except "GET":
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate http;
|
|
||||||
# use actix_web::*;
|
|
||||||
# use actix_web::httpcodes::*;
|
|
||||||
use actix_web::pred;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.resource("/index.html", |r|
|
|
||||||
r.route()
|
|
||||||
.filter(pred::Not(pred::Get()))
|
|
||||||
.f(|req| HttpMethodNotAllowed))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`Any` predicate accept list of predicates and matches if any of the supplied
|
|
||||||
predicates match. i.e:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
pred::Any(pred::Get()).or(pred::Post())
|
|
||||||
```
|
|
||||||
|
|
||||||
`All` predicate accept list of predicates and matches if all of the supplied
|
|
||||||
predicates match. i.e:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
pred::All(pred::Get()).and(pred::Header("content-type", "plain/text"))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Changing the default Not Found response
|
|
||||||
|
|
||||||
If path pattern can not be found in routing table or resource can not find matching
|
|
||||||
route, default resource is used. Default response is *NOT FOUND* response.
|
|
||||||
It is possible to override *NOT FOUND* response with `Application::default_resource()` method.
|
|
||||||
This method accepts *configuration function* same as normal resource configuration
|
|
||||||
with `Application::resource()` method.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate http;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::httpcodes::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.default_resource(|r| {
|
|
||||||
r.method(Method::GET).f(|req| HttpNotFound);
|
|
||||||
r.route().filter(pred::Not(pred::Get())).f(|req| HttpMethodNotAllowed);
|
|
||||||
})
|
|
||||||
# .finish();
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,287 +0,0 @@
|
|||||||
# Request & Response
|
|
||||||
|
|
||||||
## Response
|
|
||||||
|
|
||||||
Builder-like patter is used to construct an instance of `HttpResponse`.
|
|
||||||
`HttpResponse` provides several method that returns `HttpResponseBuilder` instance,
|
|
||||||
which is implements various convenience methods that helps build response.
|
|
||||||
Check [documentation](../actix_web/dev/struct.HttpResponseBuilder.html)
|
|
||||||
for type description. Methods `.body`, `.finish`, `.json` finalizes response creation and
|
|
||||||
returns constructed *HttpResponse* instance. if this methods get called for the same
|
|
||||||
builder instance multiple times, builder will panic.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::headers::ContentEncoding;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
HttpResponse::Ok()
|
|
||||||
.content_encoding(ContentEncoding::Br)
|
|
||||||
.content_type("plain/text")
|
|
||||||
.header("X-Hdr", "sample")
|
|
||||||
.body("data").unwrap()
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Content encoding
|
|
||||||
|
|
||||||
Actix automatically *compress*/*decompress* payload. Following codecs are supported:
|
|
||||||
|
|
||||||
* Brotli
|
|
||||||
* Gzip
|
|
||||||
* Deflate
|
|
||||||
* Identity
|
|
||||||
|
|
||||||
If request headers contains `Content-Encoding` header, request payload get decompressed
|
|
||||||
according to header value. Multiple codecs are not supported, i.e: `Content-Encoding: br, gzip`.
|
|
||||||
|
|
||||||
Response payload get compressed based on *content_encoding* parameter.
|
|
||||||
By default `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected
|
|
||||||
then compression depends on request's `Accept-Encoding` header.
|
|
||||||
`ContentEncoding::Identity` could be used to disable compression.
|
|
||||||
If other content encoding is selected the compression is enforced for this codec. For example,
|
|
||||||
to enable `brotli` response's body compression use `ContentEncoding::Br`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::headers::ContentEncoding;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
HttpResponse::Ok()
|
|
||||||
.content_encoding(ContentEncoding::Br)
|
|
||||||
.body("data").unwrap()
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## JSON Request
|
|
||||||
|
|
||||||
There are two options of json body deserialization.
|
|
||||||
|
|
||||||
First option is to use *HttpResponse::json()* method. This method returns
|
|
||||||
[*JsonBody*](../actix_web/dev/struct.JsonBody.html) object which resolves into
|
|
||||||
deserialized value.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# extern crate serde_json;
|
|
||||||
# #[macro_use] extern crate serde_derive;
|
|
||||||
# use actix_web::*;
|
|
||||||
# use futures::Future;
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct MyObj {
|
|
||||||
name: String,
|
|
||||||
number: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
req.json().from_err()
|
|
||||||
.and_then(|val: MyObj| {
|
|
||||||
println!("model: {:?}", val);
|
|
||||||
Ok(httpcodes::HttpOk.build().json(val)?) // <- send response
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
Or you can manually load payload into memory and then deserialize it.
|
|
||||||
Here is simple example. We will deserialize *MyObj* struct. We need to load request
|
|
||||||
body first and then deserialize json into object.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# use actix_web::*;
|
|
||||||
# #[macro_use] extern crate serde_derive;
|
|
||||||
extern crate serde_json;
|
|
||||||
use futures::{Future, Stream};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct MyObj {name: String, number: i32}
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
// `concat2` will asynchronously read each chunk of the request body and
|
|
||||||
// return a single, concatenated, chunk
|
|
||||||
req.concat2()
|
|
||||||
// `Future::from_err` acts like `?` in that it coerces the error type from
|
|
||||||
// the future into the final error type
|
|
||||||
.from_err()
|
|
||||||
// `Future::and_then` can be used to merge an asynchronous workflow with a
|
|
||||||
// synchronous workflow
|
|
||||||
.and_then(|body| { // <- body is loaded, now we can deserialize json
|
|
||||||
let obj = serde_json::from_slice::<MyObj>(&body)?;
|
|
||||||
Ok(httpcodes::HttpOk.build().json(obj)?) // <- send response
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
Complete example for both options is available in
|
|
||||||
[examples directory](https://github.com/actix/actix-web/tree/master/examples/json/).
|
|
||||||
|
|
||||||
|
|
||||||
## JSON Response
|
|
||||||
|
|
||||||
The `Json` type allows you to respond with well-formed JSON data: simply return a value of
|
|
||||||
type Json<T> where T is the type of a structure to serialize into *JSON*. The
|
|
||||||
type `T` must implement the `Serialize` trait from *serde*.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct MyObj {
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Result<Json<MyObj>> {
|
|
||||||
Ok(Json(MyObj{name: req.match_info().query("name")?}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.resource(r"/a/{name}", |r| r.method(Method::GET).f(index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Chunked transfer encoding
|
|
||||||
|
|
||||||
Actix automatically decode *chunked* encoding. `HttpRequest::payload()` already contains
|
|
||||||
decoded bytes stream. If request payload compressed with one of supported
|
|
||||||
compression codecs (br, gzip, deflate) bytes stream get decompressed.
|
|
||||||
|
|
||||||
Chunked encoding on response could be enabled with `HttpResponseBuilder::chunked()` method.
|
|
||||||
But this takes effect only for `Body::Streaming(BodyStream)` or `Body::StreamingContext` bodies.
|
|
||||||
Also if response payload compression is enabled and streaming body is used, chunked encoding
|
|
||||||
get enabled automatically.
|
|
||||||
|
|
||||||
Enabling chunked encoding for *HTTP/2.0* responses is forbidden.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate bytes;
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# use futures::Stream;
|
|
||||||
use actix_web::*;
|
|
||||||
use bytes::Bytes;
|
|
||||||
use futures::stream::once;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
HttpResponse::Ok()
|
|
||||||
.chunked()
|
|
||||||
.body(Body::Streaming(Box::new(once(Ok(Bytes::from_static(b"data")))))).unwrap()
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Multipart body
|
|
||||||
|
|
||||||
Actix provides multipart stream support.
|
|
||||||
[*Multipart*](../actix_web/multipart/struct.Multipart.html) is implemented as
|
|
||||||
a stream of multipart items, each item could be
|
|
||||||
[*Field*](../actix_web/multipart/struct.Field.html) or nested *Multipart* stream.
|
|
||||||
`HttpResponse::multipart()` method returns *Multipart* stream for current request.
|
|
||||||
|
|
||||||
In simple form multipart stream handling could be implemented similar to this example
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Box<Future<...>> {
|
|
||||||
req.multipart() // <- get multipart stream for current request
|
|
||||||
.and_then(|item| { // <- iterate over multipart items
|
|
||||||
match item {
|
|
||||||
// Handle multipart Field
|
|
||||||
multipart::MultipartItem::Field(field) => {
|
|
||||||
println!("==== FIELD ==== {:?} {:?}", field.headers(), field.content_type());
|
|
||||||
|
|
||||||
Either::A(
|
|
||||||
// Field in turn is a stream of *Bytes* objects
|
|
||||||
field.map(|chunk| {
|
|
||||||
println!("-- CHUNK: \n{}",
|
|
||||||
std::str::from_utf8(&chunk).unwrap());})
|
|
||||||
.fold((), |_, _| result(Ok(()))))
|
|
||||||
},
|
|
||||||
multipart::MultipartItem::Nested(mp) => {
|
|
||||||
// Or item could be nested Multipart stream
|
|
||||||
Either::B(result(Ok(())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Full example is available in
|
|
||||||
[examples directory](https://github.com/actix/actix-web/tree/master/examples/multipart/).
|
|
||||||
|
|
||||||
## Urlencoded body
|
|
||||||
|
|
||||||
Actix provides support for *application/x-www-form-urlencoded* encoded body.
|
|
||||||
`HttpResponse::urlencoded()` method returns
|
|
||||||
[*UrlEncoded*](../actix_web/dev/struct.UrlEncoded.html) future, it resolves
|
|
||||||
into `HashMap<String, String>` which contains decoded parameters.
|
|
||||||
*UrlEncoded* future can resolve into a error in several cases:
|
|
||||||
|
|
||||||
* content type is not `application/x-www-form-urlencoded`
|
|
||||||
* transfer encoding is `chunked`.
|
|
||||||
* content-length is greater than 256k
|
|
||||||
* payload terminates with error.
|
|
||||||
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
use actix_web::*;
|
|
||||||
use futures::future::{Future, ok};
|
|
||||||
|
|
||||||
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
req.urlencoded() // <- get UrlEncoded future
|
|
||||||
.from_err()
|
|
||||||
.and_then(|params| { // <- url encoded parameters
|
|
||||||
println!("==== BODY ==== {:?}", params);
|
|
||||||
ok(httpcodes::HttpOk.into())
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Streaming request
|
|
||||||
|
|
||||||
*HttpRequest* is a stream of `Bytes` objects. It could be used to read request
|
|
||||||
body payload.
|
|
||||||
|
|
||||||
In this example handle reads request payload chunk by chunk and prints every chunk.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# use futures::future::result;
|
|
||||||
use actix_web::*;
|
|
||||||
use futures::{Future, Stream};
|
|
||||||
|
|
||||||
|
|
||||||
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
req.from_err()
|
|
||||||
.fold((), |_, chunk| {
|
|
||||||
println!("Chunk: {:?}", chunk);
|
|
||||||
result::<_, error::PayloadError>(Ok(()))
|
|
||||||
})
|
|
||||||
.map(|_| HttpResponse::Ok().finish().unwrap())
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
@ -1,154 +0,0 @@
|
|||||||
# Testing
|
|
||||||
|
|
||||||
Every application should be well tested and. Actix provides the tools to perform unit and
|
|
||||||
integration tests.
|
|
||||||
|
|
||||||
## Unit tests
|
|
||||||
|
|
||||||
For unit testing actix provides request builder type and simple handler runner.
|
|
||||||
[*TestRequest*](../actix_web/test/struct.TestRequest.html) implements builder-like pattern.
|
|
||||||
You can generate `HttpRequest` instance with `finish()` method or you can
|
|
||||||
run your handler with `run()` or `run_async()` methods.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate http;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use http::{header, StatusCode};
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::test::TestRequest;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
|
|
||||||
if let Ok(s) = hdr.to_str() {
|
|
||||||
return httpcodes::HttpOk.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
httpcodes::HttpBadRequest.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let resp = TestRequest::with_header("content-type", "text/plain")
|
|
||||||
.run(index)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
|
||||||
|
|
||||||
let resp = TestRequest::default()
|
|
||||||
.run(index)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Integration tests
|
|
||||||
|
|
||||||
There are several methods how you can test your application. Actix provides
|
|
||||||
[*TestServer*](../actix_web/test/struct.TestServer.html)
|
|
||||||
server that could be used to run whole application of just specific handlers
|
|
||||||
in real http server. *TrstServer::get()*, *TrstServer::post()* or *TrstServer::client()*
|
|
||||||
methods could be used to send request to test server.
|
|
||||||
|
|
||||||
In simple form *TestServer* could be configured to use handler. *TestServer::new* method
|
|
||||||
accepts configuration function, only argument for this function is *test application*
|
|
||||||
instance. You can check [api documentation](../actix_web/test/struct.TestApp.html)
|
|
||||||
for more information.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::test::TestServer;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
httpcodes::HttpOk.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut srv = TestServer::new(|app| app.handler(index)); // <- Start new test server
|
|
||||||
|
|
||||||
let request = srv.get().finish().unwrap(); // <- create client request
|
|
||||||
let response = srv.execute(request.send()).unwrap(); // <- send request to the server
|
|
||||||
assert!(response.status().is_success()); // <- check response
|
|
||||||
|
|
||||||
let bytes = srv.execute(response.body()).unwrap(); // <- read response body
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Other option is to use application factory. In this case you need to pass factory function
|
|
||||||
same as you use for real http server configuration.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate http;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use http::Method;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::test::TestServer;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
httpcodes::HttpOk.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function get called by http server.
|
|
||||||
fn create_app() -> Application {
|
|
||||||
Application::new()
|
|
||||||
.resource("/test", |r| r.h(index))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut srv = TestServer::with_factory(create_app); // <- Start new test server
|
|
||||||
|
|
||||||
let request = srv.client(Method::GET, "/test").finish().unwrap(); // <- create client request
|
|
||||||
let response = srv.execute(request.send()).unwrap(); // <- send request to the server
|
|
||||||
|
|
||||||
assert!(response.status().is_success()); // <- check response
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## WebSocket server tests
|
|
||||||
|
|
||||||
It is possible to register *handler* with `TestApp::handler()` method that
|
|
||||||
initiate web socket connection. *TestServer* provides `ws()` which connects to
|
|
||||||
websocket server and returns ws reader and writer objects. *TestServer* also
|
|
||||||
provides `execute()` method which runs future object to completion and returns
|
|
||||||
result of the future computation.
|
|
||||||
|
|
||||||
Here is simple example, that shows how to test server websocket handler.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# extern crate http;
|
|
||||||
# extern crate bytes;
|
|
||||||
|
|
||||||
use actix_web::*;
|
|
||||||
use futures::Stream;
|
|
||||||
# use actix::prelude::*;
|
|
||||||
|
|
||||||
struct Ws; // <- WebSocket actor
|
|
||||||
|
|
||||||
impl Actor for Ws {
|
|
||||||
type Context = ws::WebsocketContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StreamHandler<ws::Message, ws::WsError> for Ws {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
|
||||||
match msg {
|
|
||||||
ws::Message::Text(text) => ctx.text(text),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut srv = test::TestServer::new( // <- start our server with ws handler
|
|
||||||
|app| app.handler(|req| ws::start(req, Ws)));
|
|
||||||
|
|
||||||
let (reader, mut writer) = srv.ws().unwrap(); // <- connect to ws server
|
|
||||||
|
|
||||||
writer.text("text"); // <- send message to server
|
|
||||||
|
|
||||||
let (item, reader) = srv.execute(reader.into_future()).unwrap(); // <- wait for one message
|
|
||||||
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,48 +0,0 @@
|
|||||||
# WebSockets
|
|
||||||
|
|
||||||
Actix supports WebSockets out-of-the-box. It is possible to convert request's `Payload`
|
|
||||||
to a stream of [*ws::Message*](../actix_web/ws/enum.Message.html) with
|
|
||||||
a [*ws::WsStream*](../actix_web/ws/struct.WsStream.html) and then use stream
|
|
||||||
combinators to handle actual messages. But it is simpler to handle websocket communications
|
|
||||||
with http actor.
|
|
||||||
|
|
||||||
This is example of simple websocket echo server:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
/// Define http actor
|
|
||||||
struct Ws;
|
|
||||||
|
|
||||||
impl Actor for Ws {
|
|
||||||
type Context = ws::WebsocketContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for ws::Message message
|
|
||||||
impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
|
||||||
match msg {
|
|
||||||
ws::Message::Ping(msg) => ctx.pong(&msg),
|
|
||||||
ws::Message::Text(text) => ctx.text(text),
|
|
||||||
ws::Message::Binary(bin) => ctx.binary(bin),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.resource("/ws/", |r| r.f(|req| ws::start(req, Ws))) // <- register websocket route
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Simple websocket echo server example is available in
|
|
||||||
[examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket).
|
|
||||||
|
|
||||||
Example chat server with ability to chat over websocket connection or tcp connection
|
|
||||||
is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)
|
|
5
rustfmt.toml
Normal file
5
rustfmt.toml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
max_width = 89
|
||||||
|
reorder_imports = true
|
||||||
|
wrap_comments = true
|
||||||
|
fn_args_density = "Compressed"
|
||||||
|
#use_small_heuristics = false
|
File diff suppressed because it is too large
Load Diff
60
src/body.rs
60
src/body.rs
@ -1,14 +1,17 @@
|
|||||||
use std::{fmt, mem};
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::{fmt, mem};
|
||||||
|
|
||||||
use error::Error;
|
|
||||||
use context::ActorHttpContext;
|
use context::ActorHttpContext;
|
||||||
|
use error::Error;
|
||||||
|
use handler::Responder;
|
||||||
|
use httprequest::HttpRequest;
|
||||||
|
use httpresponse::HttpResponse;
|
||||||
|
|
||||||
/// Type represent streaming body
|
/// Type represent streaming body
|
||||||
pub type BodyStream = Box<Stream<Item=Bytes, Error=Error>>;
|
pub type BodyStream = Box<Stream<Item = Bytes, Error = Error>>;
|
||||||
|
|
||||||
/// Represents various types of http message body.
|
/// Represents various types of http message body.
|
||||||
pub enum Body {
|
pub enum Body {
|
||||||
@ -46,7 +49,7 @@ impl Body {
|
|||||||
pub fn is_streaming(&self) -> bool {
|
pub fn is_streaming(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
Body::Streaming(_) | Body::Actor(_) => true,
|
Body::Streaming(_) | Body::Actor(_) => true,
|
||||||
_ => false
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +58,7 @@ impl Body {
|
|||||||
pub fn is_binary(&self) -> bool {
|
pub fn is_binary(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
Body::Binary(_) => true,
|
Body::Binary(_) => true,
|
||||||
_ => false
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +95,10 @@ impl fmt::Debug for Body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<T> for Body where T: Into<Binary>{
|
impl<T> From<T> for Body
|
||||||
|
where
|
||||||
|
T: Into<Binary>,
|
||||||
|
{
|
||||||
fn from(b: T) -> Body {
|
fn from(b: T) -> Body {
|
||||||
Body::Binary(b.into())
|
Body::Binary(b.into())
|
||||||
}
|
}
|
||||||
@ -235,6 +241,7 @@ impl<'a> From<&'a Arc<Vec<u8>>> for Binary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<[u8]> for Binary {
|
impl AsRef<[u8]> for Binary {
|
||||||
|
#[inline]
|
||||||
fn as_ref(&self) -> &[u8] {
|
fn as_ref(&self) -> &[u8] {
|
||||||
match *self {
|
match *self {
|
||||||
Binary::Bytes(ref bytes) => bytes.as_ref(),
|
Binary::Bytes(ref bytes) => bytes.as_ref(),
|
||||||
@ -246,6 +253,17 @@ impl AsRef<[u8]> for Binary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Responder for Binary {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||||
|
Ok(HttpResponse::build_from(req)
|
||||||
|
.content_type("application/octet-stream")
|
||||||
|
.body(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -265,61 +283,61 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_static_str() {
|
fn test_static_str() {
|
||||||
assert_eq!(Binary::from("test").len(), 4);
|
assert_eq!(Binary::from("test").len(), 4);
|
||||||
assert_eq!(Binary::from("test").as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from("test").as_ref(), b"test");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_static_bytes() {
|
fn test_static_bytes() {
|
||||||
assert_eq!(Binary::from(b"test".as_ref()).len(), 4);
|
assert_eq!(Binary::from(b"test".as_ref()).len(), 4);
|
||||||
assert_eq!(Binary::from(b"test".as_ref()).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from(b"test".as_ref()).as_ref(), b"test");
|
||||||
assert_eq!(Binary::from_slice(b"test".as_ref()).len(), 4);
|
assert_eq!(Binary::from_slice(b"test".as_ref()).len(), 4);
|
||||||
assert_eq!(Binary::from_slice(b"test".as_ref()).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from_slice(b"test".as_ref()).as_ref(), b"test");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vec() {
|
fn test_vec() {
|
||||||
assert_eq!(Binary::from(Vec::from("test")).len(), 4);
|
assert_eq!(Binary::from(Vec::from("test")).len(), 4);
|
||||||
assert_eq!(Binary::from(Vec::from("test")).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from(Vec::from("test")).as_ref(), b"test");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bytes() {
|
fn test_bytes() {
|
||||||
assert_eq!(Binary::from(Bytes::from("test")).len(), 4);
|
assert_eq!(Binary::from(Bytes::from("test")).len(), 4);
|
||||||
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ref_string() {
|
fn test_ref_string() {
|
||||||
let b = Rc::new("test".to_owned());
|
let b = Rc::new("test".to_owned());
|
||||||
assert_eq!(Binary::from(&b).len(), 4);
|
assert_eq!(Binary::from(&b).len(), 4);
|
||||||
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from(&b).as_ref(), b"test");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rc_string() {
|
fn test_rc_string() {
|
||||||
let b = Rc::new("test".to_owned());
|
let b = Rc::new("test".to_owned());
|
||||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
assert_eq!(Binary::from(b.clone()).len(), 4);
|
||||||
assert_eq!(Binary::from(b.clone()).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
|
||||||
assert_eq!(Binary::from(&b).len(), 4);
|
assert_eq!(Binary::from(&b).len(), 4);
|
||||||
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from(&b).as_ref(), b"test");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_arc_string() {
|
fn test_arc_string() {
|
||||||
let b = Arc::new("test".to_owned());
|
let b = Arc::new("test".to_owned());
|
||||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
assert_eq!(Binary::from(b.clone()).len(), 4);
|
||||||
assert_eq!(Binary::from(b.clone()).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
|
||||||
assert_eq!(Binary::from(&b).len(), 4);
|
assert_eq!(Binary::from(&b).len(), 4);
|
||||||
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from(&b).as_ref(), b"test");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_string() {
|
fn test_string() {
|
||||||
let b = "test".to_owned();
|
let b = "test".to_owned();
|
||||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
assert_eq!(Binary::from(b.clone()).len(), 4);
|
||||||
assert_eq!(Binary::from(b.clone()).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
|
||||||
assert_eq!(Binary::from(&b).len(), 4);
|
assert_eq!(Binary::from(&b).len(), 4);
|
||||||
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from(&b).as_ref(), b"test");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -335,7 +353,7 @@ mod tests {
|
|||||||
fn test_bytes_mut() {
|
fn test_bytes_mut() {
|
||||||
let b = BytesMut::from("test");
|
let b = BytesMut::from("test");
|
||||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
assert_eq!(Binary::from(b.clone()).len(), 4);
|
||||||
assert_eq!(Binary::from(b).as_ref(), "test".as_bytes());
|
assert_eq!(Binary::from(b).as_ref(), b"test");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user