mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-03 09:36:36 +02:00
Compare commits
551 Commits
Author | SHA1 | Date | |
---|---|---|---|
9b7ea836d0 | |||
537b420d35 | |||
16906c5951 | |||
45e9aaa462 | |||
564cc15c04 | |||
8fd18d56a5 | |||
a5692d4ecf | |||
2d83f79433 | |||
f3ece74406 | |||
8de1f60347 | |||
b4252f8fd1 | |||
fe2b50a9ef | |||
d8ae8c3821 | |||
c9a026fabb | |||
64eca1546e | |||
b19fe98ff4 | |||
b393ddf879 | |||
7bb7d85c1d | |||
6e976153e7 | |||
03e758cee4 | |||
0d36b8f826 | |||
f82fa08d72 | |||
d6787e6c56 | |||
b9d870645f | |||
ef89430f9b | |||
953a0d4e4a | |||
5ea2d68438 | |||
d65a03f6ac | |||
b588b2bf5c | |||
d455e2cd13 | |||
9306631d6e | |||
f735da504b | |||
487a713ca0 | |||
095ad328ee | |||
a38afa0cec | |||
9619698543 | |||
4b1a471b35 | |||
b6039b0bff | |||
d8fa43034f | |||
92f993e054 | |||
c172deb0f3 | |||
dee6aed010 | |||
76f021a6e3 | |||
2f244ea028 | |||
5f5ddc8f01 | |||
8b473745cb | |||
18575ee1ee | |||
e58b38fd13 | |||
b043c34632 | |||
b748bf3b0d | |||
be12d5e6fc | |||
7c4941f868 | |||
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 | |||
e708f51156 | |||
cbb821148b | |||
d6b021e185 | |||
0adb7e8553 | |||
dbfa1f0ac8 | |||
11347e3c7d | |||
631fe72a46 | |||
f673dba759 | |||
ab978a18ff | |||
327df159c6 | |||
2ccbd5fa18 | |||
058630d041 | |||
f456be0309 | |||
9bd6cb03ac | |||
16afeda79c | |||
83fcdfd91f | |||
8f94ae41cc | |||
4e41347de8 |
@ -4,13 +4,13 @@ environment:
|
||||
matrix:
|
||||
# Stable channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: 1.21.0
|
||||
CHANNEL: 1.24.0
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: 1.21.0
|
||||
CHANNEL: 1.24.0
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: 1.21.0
|
||||
CHANNEL: 1.24.0
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: 1.21.0
|
||||
CHANNEL: 1.24.0
|
||||
# Stable channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: stable
|
||||
@ -31,13 +31,13 @@ environment:
|
||||
CHANNEL: beta
|
||||
# Nightly channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: nightly-2017-12-21
|
||||
CHANNEL: nightly
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: nightly-2017-12-21
|
||||
CHANNEL: nightly
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: nightly-2017-12-21
|
||||
CHANNEL: nightly
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: nightly-2017-12-21
|
||||
CHANNEL: nightly
|
||||
|
||||
# Install Rust and Cargo
|
||||
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
|
||||
@ -59,4 +59,4 @@ build: false
|
||||
|
||||
# Equivalent to Travis' `script` phase
|
||||
test_script:
|
||||
- cargo test --no-default-features
|
||||
- cargo test --no-default-features --features="flate2-rust"
|
||||
|
60
.travis.yml
60
.travis.yml
@ -8,19 +8,12 @@ cache:
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- rust: 1.21.0
|
||||
- rust: 1.24.0
|
||||
- rust: stable
|
||||
- rust: beta
|
||||
- rust: nightly
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
- rust: beta
|
||||
|
||||
#rust:
|
||||
# - 1.21.0
|
||||
# - stable
|
||||
# - beta
|
||||
# - nightly-2018-01-03
|
||||
|
||||
env:
|
||||
global:
|
||||
@ -34,60 +27,29 @@ before_install:
|
||||
|
||||
# Add clippy
|
||||
before_script:
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
||||
( ( cargo install clippy && export CLIPPY=true ) || export CLIPPY=false );
|
||||
fi
|
||||
- export PATH=$PATH:~/.cargo/bin
|
||||
|
||||
script:
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
||||
cargo clean
|
||||
USE_SKEPTIC=1 cargo test --features=alpn
|
||||
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 ../..
|
||||
if [[ "$TRAVIS_RUST_VERSION" != "beta" ]]; then
|
||||
cargo clean
|
||||
cargo test --features="alpn,tls" -- --nocapture
|
||||
fi
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then
|
||||
cargo clippy
|
||||
if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then
|
||||
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
|
||||
USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
echo "Uploaded code coverage"
|
||||
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 &&
|
||||
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 &&
|
||||
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)
|
||||
USE_SKEPTIC=1 cargo tarpaulin --out Xml
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
echo "Uploaded code coverage"
|
||||
fi
|
||||
|
240
CHANGES.md
240
CHANGES.md
@ -1,5 +1,245 @@
|
||||
# Changes
|
||||
|
||||
## 0.6.7 (2018-05-17)
|
||||
|
||||
* Fix compilation with --no-default-features
|
||||
|
||||
|
||||
## 0.6.6 (2018-05-17)
|
||||
|
||||
* Panic during middleware execution #226
|
||||
|
||||
* Add support for listen_tls/listen_ssl #224
|
||||
|
||||
* Implement extractor for `Session`
|
||||
|
||||
* Ranges header support for NamedFile #60
|
||||
|
||||
|
||||
## 0.6.5 (2018-05-15)
|
||||
|
||||
* Fix error handling during request decoding #222
|
||||
|
||||
|
||||
## 0.6.4 (2018-05-11)
|
||||
|
||||
* Fix segfault in ServerSettings::get_response_builder()
|
||||
|
||||
|
||||
## 0.6.3 (2018-05-10)
|
||||
|
||||
* Add `Router::with_async()` method for async handler registration.
|
||||
|
||||
* Added error response functions for 501,502,503,504
|
||||
|
||||
* Fix client request timeout handling
|
||||
|
||||
|
||||
## 0.6.2 (2018-05-09)
|
||||
|
||||
* WsWriter trait is optional.
|
||||
|
||||
|
||||
## 0.6.1 (2018-05-08)
|
||||
|
||||
* Fix http/2 payload streaming #215
|
||||
|
||||
* Fix connector's default `keep-alive` and `lifetime` settings #212
|
||||
|
||||
* Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214
|
||||
|
||||
* Allow to exclude certain endpoints from logging #211
|
||||
|
||||
|
||||
## 0.6.0 (2018-05-08)
|
||||
|
||||
* Add route scopes #202
|
||||
|
||||
* Allow to use ssl and non-ssl connections at the same time #206
|
||||
|
||||
* Websocket CloseCode Empty/Status is ambiguous #193
|
||||
|
||||
* Add Content-Disposition to NamedFile #204
|
||||
|
||||
* Allow to access Error's backtrace object
|
||||
|
||||
* Allow to override files listing renderer for `StaticFiles` #203
|
||||
|
||||
* Various extractor usability improvements #207
|
||||
|
||||
|
||||
## 0.5.6 (2018-04-24)
|
||||
|
||||
* Make flate2 crate optional #200
|
||||
|
||||
|
||||
## 0.5.5 (2018-04-24)
|
||||
|
||||
* Fix panic when Websocket is closed with no error code #191
|
||||
|
||||
* Allow to use rust backend for flate2 crate #199
|
||||
|
||||
## 0.5.4 (2018-04-19)
|
||||
|
||||
* Add identity service middleware
|
||||
|
||||
* Middleware response() is not invoked if there was an error in async handler #187
|
||||
|
||||
* Use Display formatting for InternalError Display implementation #188
|
||||
|
||||
|
||||
## 0.5.3 (2018-04-18)
|
||||
|
||||
* Impossible to quote slashes in path parameters #182
|
||||
|
||||
|
||||
## 0.5.2 (2018-04-16)
|
||||
|
||||
* Allow to configure StaticFiles's CpuPool, via static method or env variable
|
||||
|
||||
* Add support for custom handling of Json extractor errors #181
|
||||
|
||||
* Fix StaticFiles does not support percent encoded paths #177
|
||||
|
||||
* Fix Client Request with custom Body Stream halting on certain size requests #176
|
||||
|
||||
|
||||
## 0.5.1 (2018-04-12)
|
||||
|
||||
* Client connector provides stats, `ClientConnector::stats()`
|
||||
|
||||
* Fix end-of-stream handling in parse_payload #173
|
||||
|
||||
* Fix StaticFiles generate a lot of threads #174
|
||||
|
||||
|
||||
## 0.5.0 (2018-04-10)
|
||||
|
||||
* Type-safe path/query/form parameter handling, using serde #70
|
||||
|
||||
* HttpResponse builder's methods `.body()`, `.finish()`, `.json()`
|
||||
return `HttpResponse` instead of `Result`
|
||||
|
||||
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()`
|
||||
|
||||
* Added `signed` and `private` `CookieSessionBackend`s
|
||||
|
||||
* Added `HttpRequest::resource()`, returns current matched resource
|
||||
|
||||
* Added `ErrorHandlers` middleware
|
||||
|
||||
* Fix router cannot parse Non-ASCII characters in URL #137
|
||||
|
||||
* Fix client connection pooling
|
||||
|
||||
* Fix long client urls #129
|
||||
|
||||
* Fix panic on invalid URL characters #130
|
||||
|
||||
* Fix logger request duration calculation #152
|
||||
|
||||
* Fix prefix and static file serving #168
|
||||
|
||||
|
||||
## 0.4.10 (2018-03-20)
|
||||
|
||||
* Use `Error` instead of `InternalError` for `error::ErrorXXXX` methods
|
||||
|
||||
* Allow to set client request timeout
|
||||
|
||||
* Allow to set client websocket handshake timeout
|
||||
|
||||
* Refactor `TestServer` configuration
|
||||
|
||||
* Fix server websockets big payloads support
|
||||
|
||||
* Fix http/2 date header generation
|
||||
|
||||
|
||||
## 0.4.9 (2018-03-16)
|
||||
|
||||
* Allow to disable http/2 support
|
||||
|
||||
* Wake payload reading task when data is available
|
||||
|
||||
* Fix server keep-alive handling
|
||||
|
||||
* Send Query Parameters in client requests #120
|
||||
|
||||
* Move brotli encoding to a feature
|
||||
|
||||
* Add option of default handler for `StaticFiles` handler #57
|
||||
|
||||
* Add basic client connection pooling
|
||||
|
||||
|
||||
## 0.4.8 (2018-03-12)
|
||||
|
||||
* Allow to set read buffer capacity for server request
|
||||
|
||||
* Handle WouldBlock error for socket accept call
|
||||
|
||||
|
||||
## 0.4.7 (2018-03-11)
|
||||
|
||||
* Fix panic on unknown content encoding
|
||||
|
||||
* Fix connection get closed too early
|
||||
|
||||
* Fix streaming response handling for http/2
|
||||
|
||||
* Better sleep on error support
|
||||
|
||||
|
||||
## 0.4.6 (2018-03-10)
|
||||
|
||||
* Fix client cookie handling
|
||||
|
||||
* Fix json content type detection
|
||||
|
||||
* Fix CORS middleware #117
|
||||
|
||||
* Optimize websockets stream support
|
||||
|
||||
|
||||
## 0.4.5 (2018-03-07)
|
||||
|
||||
* Fix compression #103 and #104
|
||||
|
||||
* Fix client cookie handling #111
|
||||
|
||||
* Non-blocking processing of a `NamedFile`
|
||||
|
||||
* Enable compression support for `NamedFile`
|
||||
|
||||
* Better support for `NamedFile` type
|
||||
|
||||
* Add `ResponseError` impl for `SendRequestError`. This improves ergonomics of the client.
|
||||
|
||||
* Add native-tls support for client
|
||||
|
||||
* Allow client connection timeout to be set #108
|
||||
|
||||
* Allow to use std::net::TcpListener for HttpServer
|
||||
|
||||
* Handle panics in worker threads
|
||||
|
||||
|
||||
## 0.4.4 (2018-03-04)
|
||||
|
||||
* Allow to use Arc<Vec<u8>> as response/request body
|
||||
|
||||
* Fix handling of requests with an encoded body with a length > 8192 #93
|
||||
|
||||
## 0.4.3 (2018-03-03)
|
||||
|
||||
* Fix request body read bug
|
||||
|
||||
* Fix segmentation fault #79
|
||||
|
||||
* Set reuse address before bind #90
|
||||
|
||||
|
||||
## 0.4.2 (2018-03-02)
|
||||
|
||||
* Better naming for websockets implementation
|
||||
|
60
Cargo.toml
60
Cargo.toml
@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "0.4.2"
|
||||
version = "0.6.7"
|
||||
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"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://github.com/actix/actix-web"
|
||||
@ -13,8 +13,7 @@ categories = ["network-programming", "asynchronous",
|
||||
"web-programming::http-client",
|
||||
"web-programming::websocket"]
|
||||
license = "MIT/Apache-2.0"
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config",
|
||||
"appveyor.yml", "/examples/**"]
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||
build = "build.rs"
|
||||
|
||||
[badges]
|
||||
@ -27,20 +26,32 @@ name = "actix_web"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["session", "brotli", "flate2-c"]
|
||||
|
||||
# tls
|
||||
tls = ["native-tls", "tokio-tls"]
|
||||
|
||||
# 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]
|
||||
actix = "^0.5.5"
|
||||
|
||||
base64 = "0.9"
|
||||
bitflags = "1.0"
|
||||
brotli2 = "^0.3.2"
|
||||
failure = "0.1.1"
|
||||
flate2 = "1.0"
|
||||
h2 = "0.1"
|
||||
http = "^0.1.5"
|
||||
httparse = "1.2"
|
||||
@ -48,19 +59,24 @@ http-range = "0.1"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
mime_guess = "1.8"
|
||||
mime_guess = "2.0.0-alpha"
|
||||
num_cpus = "1.0"
|
||||
percent-encoding = "1.0"
|
||||
rand = "0.4"
|
||||
regex = "0.2"
|
||||
regex = "1.0"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.5"
|
||||
sha1 = "0.6"
|
||||
smallvec = "0.6"
|
||||
time = "0.1"
|
||||
encoding = "0.2"
|
||||
language-tags = "0.2"
|
||||
lazy_static = "1.0"
|
||||
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
|
||||
mio = "^0.6.13"
|
||||
@ -68,9 +84,10 @@ net2 = "0.2"
|
||||
bytes = "0.4"
|
||||
byteorder = "1"
|
||||
futures = "0.1"
|
||||
futures-cpupool = "0.1"
|
||||
slab = "0.4"
|
||||
tokio-io = "0.1"
|
||||
tokio-core = "0.1"
|
||||
trust-dns-resolver = "0.8"
|
||||
|
||||
# native-tls
|
||||
native-tls = { version="0.1", optional = true }
|
||||
@ -80,16 +97,11 @@ tokio-tls = { version="0.1", optional = true }
|
||||
openssl = { version="0.10", optional = true }
|
||||
tokio-openssl = { version="0.2", optional = true }
|
||||
|
||||
[dependencies.actix]
|
||||
version = "^0.5.1"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.5"
|
||||
skeptic = "0.13"
|
||||
serde_derive = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
skeptic = "0.13"
|
||||
version_check = "0.1"
|
||||
|
||||
[profile.release]
|
||||
@ -100,19 +112,5 @@ codegen-units = 1
|
||||
[workspace]
|
||||
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/",
|
||||
]
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2017 Nikilay Kim
|
||||
Copyright (c) 2017 Nikolay Kim
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
82
MIGRATION.md
Normal file
82
MIGRATION.md
Normal file
@ -0,0 +1,82 @@
|
||||
## Migration from 0.5 to 0.6
|
||||
|
||||
* `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
|
||||
|
||||
* `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`
|
||||
|
||||
* Use `Query` extractor instead of HttpRequest::query()`.
|
||||
|
||||
```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
|
||||
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
|
||||
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 ..
|
||||
|
71
README.md
71
README.md
@ -1,46 +1,49 @@
|
||||
# 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
|
||||
* 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)
|
||||
* 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
|
||||
* Multipart streams
|
||||
* SSL support with openssl or native-tls
|
||||
* Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging),
|
||||
[Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions),
|
||||
* Static assets
|
||||
* SSL support with OpenSSL or `native-tls`
|
||||
* 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),
|
||||
[DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers),
|
||||
[CORS](https://actix.github.io/actix-web/actix_web/middleware/cors/index.html))
|
||||
* Built on top of [Actix actor framework](https://github.com/actix/actix).
|
||||
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
|
||||
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
|
||||
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
|
||||
* Includes an asynchronous [HTTP client](https://github.com/actix/actix-web/blob/master/src/client/mod.rs)
|
||||
* 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/)
|
||||
* [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/)
|
||||
* [User Guide](https://actix.rs/book/actix-web/)
|
||||
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
|
||||
* [API Documentation (Releases)](https://docs.rs/actix-web/)
|
||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
||||
* Minimum supported Rust version: 1.21 or later
|
||||
* Minimum supported Rust version: 1.24 or later
|
||||
|
||||
## Example
|
||||
|
||||
```rust,ignore
|
||||
```rust
|
||||
extern crate actix_web;
|
||||
use actix_web::*;
|
||||
use actix_web::{http, server, App, Path};
|
||||
|
||||
fn index(req: HttpRequest) -> String {
|
||||
format!("Hello {}!", &req.match_info()["name"])
|
||||
fn index(info: Path<(u32, String)>) -> String {
|
||||
format!("Hello {}! id:{}", info.1, info.0)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
HttpServer::new(
|
||||
|| Application::new()
|
||||
.resource("/{name}", |r| r.f(index)))
|
||||
server::new(
|
||||
|| App::new()
|
||||
.route("/{id}/{name}/index.html", http::Method::GET, index))
|
||||
.bind("127.0.0.1:8080").unwrap()
|
||||
.run();
|
||||
}
|
||||
@ -48,19 +51,21 @@ fn main() {
|
||||
|
||||
### More examples
|
||||
|
||||
* [Basics](https://github.com/actix/actix-web/tree/master/examples/basics/)
|
||||
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/)
|
||||
* [Multipart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/)
|
||||
* [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket/)
|
||||
* [Tera templates](https://github.com/actix/actix-web/tree/master/examples/template_tera/)
|
||||
* [Diesel integration](https://github.com/actix/actix-web/tree/master/examples/diesel/)
|
||||
* [SSL / HTTP/2.0](https://github.com/actix/actix-web/tree/master/examples/tls/)
|
||||
* [Tcp/Websocket chat](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)
|
||||
* [SockJS Server](https://github.com/actix/actix-sockjs)
|
||||
* [Json](https://github.com/actix/actix-web/tree/master/examples/json/)
|
||||
* [Basics](https://github.com/actix/examples/tree/master/basics/)
|
||||
* [Stateful](https://github.com/actix/examples/tree/master/state/)
|
||||
* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/)
|
||||
* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/)
|
||||
* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/)
|
||||
* [Tera](https://github.com/actix/examples/tree/master/template_tera/) /
|
||||
[Askama](https://github.com/actix/examples/tree/master/template_askama/) templates
|
||||
* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/)
|
||||
* [r2d2](https://github.com/actix/examples/tree/master/r2d2/)
|
||||
* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/)
|
||||
* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/)
|
||||
* [Json](https://github.com/actix/examples/tree/master/json/)
|
||||
|
||||
You may consider checking out
|
||||
[this directory](https://github.com/actix/actix-web/tree/master/examples) for more examples.
|
||||
[this directory](https://github.com/actix/examples/tree/master/) for more examples.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
|
48
build.rs
48
build.rs
@ -1,47 +1,15 @@
|
||||
extern crate skeptic;
|
||||
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_min_version("1.26.0") {
|
||||
Some((true, _)) => println!("cargo:rustc-cfg=actix_impl_trait"),
|
||||
_ => (),
|
||||
};
|
||||
match version_check::is_nightly() {
|
||||
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
|
||||
Some(false) => (),
|
||||
None => (),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn main() {
|
||||
match version_check::is_nightly() {
|
||||
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
|
||||
Some(true) => {
|
||||
println!("cargo:rustc-cfg=actix_nightly");
|
||||
println!("cargo:rustc-cfg=actix_impl_trait");
|
||||
}
|
||||
Some(false) => (),
|
||||
None => (),
|
||||
};
|
||||
|
@ -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.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
111
src/body.rs
111
src/body.rs
@ -1,14 +1,17 @@
|
||||
use std::{fmt, mem};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::Stream;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, mem};
|
||||
|
||||
use error::Error;
|
||||
use context::ActorHttpContext;
|
||||
use error::Error;
|
||||
use handler::Responder;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
/// Type represent streaming body
|
||||
pub type BodyStream = Box<Stream<Item=Bytes, Error=Error>>;
|
||||
pub type BodyStream = Box<Stream<Item = Bytes, Error = Error>>;
|
||||
|
||||
/// Represents various types of http message body.
|
||||
pub enum Body {
|
||||
@ -36,6 +39,8 @@ pub enum Binary {
|
||||
/// Shared string body
|
||||
#[doc(hidden)]
|
||||
ArcSharedString(Arc<String>),
|
||||
/// Shared vec body
|
||||
SharedVec(Arc<Vec<u8>>),
|
||||
}
|
||||
|
||||
impl Body {
|
||||
@ -44,7 +49,7 @@ impl Body {
|
||||
pub fn is_streaming(&self) -> bool {
|
||||
match *self {
|
||||
Body::Streaming(_) | Body::Actor(_) => true,
|
||||
_ => false
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +58,16 @@ impl Body {
|
||||
pub fn is_binary(&self) -> bool {
|
||||
match *self {
|
||||
Body::Binary(_) => true,
|
||||
_ => false
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this binary empy.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match *self {
|
||||
Body::Empty => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,6 +75,15 @@ impl Body {
|
||||
pub fn from_slice(s: &[u8]) -> Body {
|
||||
Body::Binary(Binary::Bytes(Bytes::from(s)))
|
||||
}
|
||||
|
||||
/// Is this binary body.
|
||||
#[inline]
|
||||
pub(crate) fn binary(self) -> Binary {
|
||||
match self {
|
||||
Body::Binary(b) => b,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Body {
|
||||
@ -90,7 +113,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 {
|
||||
Body::Binary(b.into())
|
||||
}
|
||||
@ -115,6 +141,7 @@ impl Binary {
|
||||
Binary::Slice(slice) => slice.len(),
|
||||
Binary::SharedString(ref s) => s.len(),
|
||||
Binary::ArcSharedString(ref s) => s.len(),
|
||||
Binary::SharedVec(ref s) => s.len(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,8 +161,9 @@ impl Clone for Binary {
|
||||
match *self {
|
||||
Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()),
|
||||
Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)),
|
||||
Binary::SharedString(ref s) => Binary::Bytes(Bytes::from(s.as_str())),
|
||||
Binary::ArcSharedString(ref s) => Binary::Bytes(Bytes::from(s.as_str())),
|
||||
Binary::SharedString(ref s) => Binary::SharedString(s.clone()),
|
||||
Binary::ArcSharedString(ref s) => Binary::ArcSharedString(s.clone()),
|
||||
Binary::SharedVec(ref s) => Binary::SharedVec(s.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -147,6 +175,7 @@ impl Into<Bytes> for Binary {
|
||||
Binary::Slice(slice) => Bytes::from(slice),
|
||||
Binary::SharedString(s) => Bytes::from(s.as_str()),
|
||||
Binary::ArcSharedString(s) => Bytes::from(s.as_str()),
|
||||
Binary::SharedVec(s) => Bytes::from(AsRef::<[u8]>::as_ref(s.as_ref())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -217,17 +246,42 @@ impl<'a> From<&'a Arc<String>> for Binary {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<Vec<u8>>> for Binary {
|
||||
fn from(body: Arc<Vec<u8>>) -> Binary {
|
||||
Binary::SharedVec(body)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Arc<Vec<u8>>> for Binary {
|
||||
fn from(body: &'a Arc<Vec<u8>>) -> Binary {
|
||||
Binary::SharedVec(Arc::clone(body))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Binary {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
match *self {
|
||||
Binary::Bytes(ref bytes) => bytes.as_ref(),
|
||||
Binary::Slice(slice) => slice,
|
||||
Binary::SharedString(ref s) => s.as_bytes(),
|
||||
Binary::ArcSharedString(ref s) => s.as_bytes(),
|
||||
Binary::SharedVec(ref s) => s.as_ref().as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for Binary {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||
Ok(HttpResponse::build_from(req)
|
||||
.content_type("application/octet-stream")
|
||||
.body(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -247,68 +301,77 @@ mod tests {
|
||||
#[test]
|
||||
fn test_static_str() {
|
||||
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]
|
||||
fn test_static_bytes() {
|
||||
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()).as_ref(), "test".as_bytes());
|
||||
assert_eq!(Binary::from_slice(b"test".as_ref()).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vec() {
|
||||
assert_eq!(Binary::from(Vec::from("test")).len(), 4);
|
||||
assert_eq!(Binary::from(Vec::from("test")).as_ref(), "test".as_bytes());
|
||||
assert_eq!(Binary::from(Vec::from("test")).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes() {
|
||||
assert_eq!(Binary::from(Bytes::from("test")).len(), 4);
|
||||
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), "test".as_bytes());
|
||||
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ref_string() {
|
||||
let b = Rc::new("test".to_owned());
|
||||
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]
|
||||
fn test_rc_string() {
|
||||
let b = Rc::new("test".to_owned());
|
||||
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).as_ref(), "test".as_bytes());
|
||||
assert_eq!(Binary::from(&b).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arc_string() {
|
||||
let b = Arc::new("test".to_owned());
|
||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(b.clone()).as_ref(), "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).as_ref(), "test".as_bytes());
|
||||
assert_eq!(Binary::from(&b).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string() {
|
||||
let b = "test".to_owned();
|
||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(b.clone()).as_ref(), "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).as_ref(), "test".as_bytes());
|
||||
assert_eq!(Binary::from(&b).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shared_vec() {
|
||||
let b = Arc::new(Vec::from(&b"test"[..]));
|
||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(b.clone()).as_ref(), &b"test"[..]);
|
||||
assert_eq!(Binary::from(&b).len(), 4);
|
||||
assert_eq!(Binary::from(&b).as_ref(), &b"test"[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes_mut() {
|
||||
let b = BytesMut::from("test");
|
||||
let b = BytesMut::from("test");
|
||||
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]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user