mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-06 19:00:18 +02:00
Compare commits
678 Commits
Author | SHA1 | Date | |
---|---|---|---|
764421fe44 | |||
b339ea0a3a | |||
8994732227 | |||
7591592279 | |||
4a48b43927 | |||
b1ad4763a2 | |||
2f3a2115c0 | |||
1283c00583 | |||
9f81eae215 | |||
ccb6ebb259 | |||
da76de76f0 | |||
c316a99746 | |||
1e2aa4fc90 | |||
e2c8f17c2c | |||
9b06eac720 | |||
4f99cd1580 | |||
f56fa49a9b | |||
1f063e4136 | |||
33c935dccc | |||
a7bf635158 | |||
aac9b5a97c | |||
6c480fae90 | |||
5dcb558f50 | |||
a344c3a02e | |||
0ab8bc11f3 | |||
abae65a49e | |||
d6fd4a3524 | |||
72aa2d9eae | |||
644f1a9518 | |||
56ae565688 | |||
0a3b776aa7 | |||
6ef9c60361 | |||
a2b98b31e8 | |||
ab5ed27bf1 | |||
141b992450 | |||
4e41e13baf | |||
ea8e8e75a2 | |||
a855c8b2c9 | |||
fd31eb74c5 | |||
3b22b1b168 | |||
7a7df7f8fb | |||
25aabfb3e2 | |||
3a3657cfaf | |||
aff43cc8b8 | |||
4a9c1ae894 | |||
4a07430e8e | |||
9a076c69d1 | |||
8f2d3a0a76 | |||
d4611f8bb9 | |||
fd56e5dc82 | |||
7c74259453 | |||
6a01af32bc | |||
5634e5794f | |||
187644e178 | |||
7198dde465 | |||
2374aa42ed | |||
03912d2089 | |||
3f95cce9e8 | |||
979cea03ac | |||
6424defee6 | |||
6ee14efbe2 | |||
4d81186059 | |||
ddc82395e8 | |||
360ffbba68 | |||
f2f1798215 | |||
548f4e4d62 | |||
cb70d5ec3d | |||
edd114f6e4 | |||
816c6fb0e0 | |||
0da382a7a4 | |||
3e3d3279b8 | |||
3c95823e53 | |||
8607c51bcf | |||
080bb3e5ae | |||
d31e71a169 | |||
7b0e1642b6 | |||
096dee519c | |||
8bce3b9d10 | |||
b28ecbcf0c | |||
8f9ec5c23c | |||
96b87761d1 | |||
b1eec3131f | |||
a544034c06 | |||
4b8181476c | |||
eb041de36d | |||
80285f2a32 | |||
de869ed879 | |||
7ccacb92ce | |||
57655d8153 | |||
335ca8ff33 | |||
720d8c36c1 | |||
8c1b5fa945 | |||
232aba2080 | |||
30bdf9cb5e | |||
285c66e7d8 | |||
856055c6ca | |||
e3081306da | |||
94c4053cb5 | |||
762961b0f4 | |||
3109f9be62 | |||
2d049e4a9f | |||
0c98775b51 | |||
b4b5c78b51 | |||
78da98a16d | |||
74377ef73d | |||
728377a447 | |||
73ed1342eb | |||
bc6300be34 | |||
2faf3a5eb6 | |||
6181a84d7b | |||
f8f99ec0c7 | |||
bd03ba1192 | |||
d0cbf7cd25 | |||
93aa220e8d | |||
81e4fb9353 | |||
884ea02f5c | |||
b6d5516e3a | |||
46841cc87e | |||
7ad66956b2 | |||
d568161852 | |||
671ab35cf6 | |||
c63ad4b6f1 | |||
eb713bd60e | |||
2b74fbf586 | |||
58f85658bd | |||
7e9fbfca72 | |||
5115384501 | |||
a1b96b1cf4 | |||
a565e71018 | |||
e41b175e3d | |||
db39f122be | |||
afd2dc4666 | |||
cba7e426a5 | |||
01e7cc9620 | |||
5a5497b745 | |||
b698e3546b | |||
e99a5e8144 | |||
577f91206c | |||
76f9542df7 | |||
9739168d48 | |||
5cbaf3a1b8 | |||
a02e0dfab6 | |||
5cc3bba5cc | |||
6e51573975 | |||
b686f39d0b | |||
6416a796c3 | |||
b6a394a113 | |||
456fd1364a | |||
f3cce6a04c | |||
9835a4537a | |||
715ec4ae2f | |||
55b2fb7f77 | |||
7c7743c145 | |||
826fc62299 | |||
5dd2e7523d | |||
4821d51167 | |||
c446be48e3 | |||
042f8391bb | |||
d4bc3294a3 | |||
04d53d6f57 | |||
881e0e0346 | |||
b9f8a00ba3 | |||
99bed67bec | |||
52a454800f | |||
c09c8e4980 | |||
b931dda1fe | |||
74166b4834 | |||
4abb769ee5 | |||
e8e2ca1526 | |||
78967dea13 | |||
58a5d493b7 | |||
c5341017cd | |||
f4873fcdee | |||
35efd017bb | |||
fb76c490c6 | |||
3653c78e92 | |||
1053c44326 | |||
e6ea177181 | |||
1957469061 | |||
2227120ae0 | |||
21c8c0371d | |||
1914a6a0d8 | |||
1cff4619e7 | |||
7bb7adf89c | |||
f55ff24925 | |||
f5f78d79e6 | |||
9180625dfd | |||
552320bae2 | |||
7cf221f767 | |||
98931a8623 | |||
ae10a89014 | |||
71d534dadb | |||
867bb1d409 | |||
91c44a1cf1 | |||
3bc60a8d5d | |||
58df8fa4b9 | |||
81f92b43e5 | |||
e1d9c3803b | |||
a7c24aace1 | |||
89a89e7b18 | |||
3425f7be40 | |||
09a6f8a34f | |||
7060f298b4 | |||
33dbe15760 | |||
e95c7dfc29 | |||
927a92fcac | |||
2b0f3d2a9a | |||
93fdb596d4 | |||
305666067e | |||
b805d87ee7 | |||
bc6bb9984f | |||
c043fd7912 | |||
781282897a | |||
a9c71b2894 | |||
edd26837dd | |||
3105bca13b | |||
c470e7a02b | |||
8a96e8fdd0 | |||
e919ec485e | |||
e482b88741 | |||
eb8052b936 | |||
dab918261c | |||
11342e4566 | |||
f7b895b53a | |||
ac89880c0a | |||
8a058efb4e | |||
fa93701bee | |||
0707dfe5bb | |||
0a41ecd01d | |||
0648ad6f33 | |||
728d4f1f57 | |||
d152860fa7 | |||
43f14224b1 | |||
f7d9b45e64 | |||
448b73a4b5 | |||
1a31554ee6 | |||
49cdddf479 | |||
aed90ed458 | |||
e0faf3f69c | |||
f7807e43d8 | |||
fee54d1de0 | |||
1445cc7a2c | |||
16e9512457 | |||
615db0d9d8 | |||
3f3dcf413b | |||
d85081b64e | |||
4b72a1b325 | |||
8aae2daafa | |||
d7f59ce481 | |||
ce78f17a79 | |||
16310a5ebd | |||
e8412672a2 | |||
6c7dda495b | |||
584d0c9e99 | |||
a159a9cd6e | |||
9d4e926302 | |||
41c94a1220 | |||
c7798ef45d | |||
d696c1692e | |||
f90bc0caae | |||
f802fe09e6 | |||
513fcd4a47 | |||
c28d052d22 | |||
550f68ca05 | |||
3074071d03 | |||
3ffefb8b29 | |||
3cf2fbbb23 | |||
896981cdf8 | |||
71da72efdb | |||
5e9b94a6dd | |||
665f4edf6b | |||
247c23c1ea | |||
3ed9e872ad | |||
473ec38439 | |||
524493e0b0 | |||
5ae646332e | |||
5ff35f5b99 | |||
dea354d6d8 | |||
20d5c61c11 | |||
91230afc44 | |||
afeffe4b19 | |||
fdf7726831 | |||
9559f6a175 | |||
1f7aee23df | |||
bf11bfed8e | |||
e439d0546b | |||
8348c830e2 | |||
ae084d1146 | |||
88031b7fde | |||
70ea43b3c0 | |||
9e6d090fd0 | |||
7af3b3f956 | |||
3a59344ffb | |||
3768a2885d | |||
f0fdcc9936 | |||
77ba1de305 | |||
fb2c78d9fc | |||
b49eadf7e5 | |||
9040f588af | |||
284b59722a | |||
e798af26a2 | |||
fc88bb294a | |||
f3a90a2829 | |||
d2f54b7d19 | |||
8e89ff1d1e | |||
cc38b30f7b | |||
967d3244d7 | |||
d8548ad83b | |||
5741b8b372 | |||
7962d28a6d | |||
e18f9f3f3a | |||
73e2773a10 | |||
c998c75515 | |||
f1f5b23e77 | |||
76b03851e6 | |||
87188e1505 | |||
df393df547 | |||
a1dc5a6bd1 | |||
8e580ef7b9 | |||
12345004dd | |||
a1a77600c6 | |||
d7d9e8c0e9 | |||
e93af57fa7 | |||
a166fc82f4 | |||
2d769f805a | |||
6ea894547d | |||
491d43aa8c | |||
1baead993a | |||
3d3e4dae9a | |||
1d195a2cf2 | |||
d87fafb563 | |||
308df19865 | |||
538fea8027 | |||
3f4898a6d1 | |||
6a2bb9a473 | |||
d8b0ce88a5 | |||
783e19c1bf | |||
d80a0c9f94 | |||
02b37570f4 | |||
820404cdd2 | |||
27b0dfd761 | |||
b714e1f4ce | |||
093d0bae40 | |||
8941557da6 | |||
6bb893deab | |||
19e1c1b75b | |||
556de72932 | |||
4d741b4de5 | |||
0589f2ee49 | |||
da8aa8b988 | |||
be1cd2936d | |||
e1fb32c6e5 | |||
5df5cc7374 | |||
0d21c2da22 | |||
183bcd38f8 | |||
3abd0db6b1 | |||
29adc20581 | |||
743235b8fd | |||
7f77ba557d | |||
d3b7d2d6b3 | |||
f6510161b5 | |||
e3b0f02794 | |||
9521de5746 | |||
dd3a2aa68a | |||
cce9c68a10 | |||
5e17a846af | |||
030a70841a | |||
e4bfef9d26 | |||
cf8c2ca95e | |||
b61a07a320 | |||
ffb5742b71 | |||
465a87a7cf | |||
5b65987f6a | |||
89c9dfb5bc | |||
a578262f73 | |||
b0c8fa03f0 | |||
98b0e023f3 | |||
012d55e424 | |||
f1e82ebc1e | |||
ddd9c24bb2 | |||
9f9c75d832 | |||
eaab28cd3b | |||
c35d294611 | |||
18f3841783 | |||
0567e6fb0a | |||
55534bff8c | |||
bca1dd4f9e | |||
0a68811dce | |||
406d2c41e9 | |||
63ddc07ccb | |||
33b2be3281 | |||
bf23aa5d4b | |||
3c5fd18e02 | |||
4dd3382ac7 | |||
50891986bc | |||
df2aa42dad | |||
c36ad06332 | |||
821c96c37c | |||
cbb81bc747 | |||
79f047f5be | |||
813b56ebe5 | |||
c3a39e026d | |||
e05596b65d | |||
65767558fb | |||
7fc7d6e17a | |||
c47e2ccfee | |||
d0c01c2cdd | |||
50b2f62c80 | |||
0a96b8c579 | |||
d41aade0b7 | |||
626999bcc9 | |||
64d867d9a1 | |||
1596f4db73 | |||
fa2a3bc55e | |||
52c9865716 | |||
db7bd962cb | |||
f858fa7a32 | |||
2bad99b645 | |||
566066e855 | |||
009874125e | |||
2e790dfcc6 | |||
e3f9345420 | |||
790793f8a1 | |||
13cbfc877d | |||
4f6145e5c7 | |||
f3b853f224 | |||
0cab873066 | |||
64dc6c5771 | |||
669975df75 | |||
56fd088163 | |||
2124730e0a | |||
e9a3845e26 | |||
3f0e7456c0 | |||
1e1da5832f | |||
625c4ad0db | |||
fde94bfe95 | |||
3e8a6c3988 | |||
26af6040ff | |||
9ed4159c0c | |||
27d92f3a23 | |||
4b421b44a2 | |||
1a51f75ecc | |||
9821c6ea90 | |||
167717d20e | |||
91ffab8f6e | |||
b1f33e29ec | |||
ed8bd3d6a3 | |||
1daf50095a | |||
a8b2f1b821 | |||
1ddcce7b76 | |||
c3d5e4301a | |||
71c37bde5a | |||
4913e7d3c2 | |||
d77156c16c | |||
106f43e874 | |||
2b0994e448 | |||
a2dff8a0b9 | |||
c37565cc4a | |||
b61c2a0cf0 | |||
c98d320f8c | |||
355f54efe2 | |||
8c1487f7f2 | |||
4529efa948 | |||
9d0a64ac98 | |||
b7cde3f4a9 | |||
408ddf0be1 | |||
406ef20262 | |||
c2751efa87 | |||
653b431895 | |||
96f598f2c4 | |||
81f8da03ae | |||
6b61041aec | |||
d4187f682b | |||
55204c829c | |||
2e83c5924d | |||
ab6efd2421 | |||
55818028cb | |||
e9aa67b75d | |||
b9da09ddf0 | |||
d7efbb516d | |||
6e3f598c50 | |||
007b7ce62f | |||
b1ae7f95cc | |||
96381f5d6a | |||
0f75d066f2 | |||
caca907c23 | |||
c5490a851c | |||
0388a464ba | |||
71bbe2a5dd | |||
7addd2800d | |||
273de2260d | |||
b98ab2eebe | |||
4a40b026a4 | |||
a44f71d8c2 | |||
9043e7286d | |||
3e91b06241 | |||
774bfc0a86 | |||
1293619096 | |||
2192d14eff | |||
b71ddf7b4c | |||
3f06439d3e | |||
d595dd850e | |||
0abb3863dc | |||
dff7618f35 | |||
968f5d39d6 | |||
9e3aa59155 | |||
2a0d5db41a | |||
4b03d03404 | |||
9ea0781aba | |||
63502fa833 | |||
a18bd5dac0 | |||
0dd27bd224 | |||
8d52e2bbd9 | |||
c63f058647 | |||
87c7441f7d | |||
04ded5ba68 | |||
903b391e0a | |||
c2bfc091bd | |||
20af8822fd | |||
d7e65b6212 | |||
c3de32c3b3 | |||
d8b880e167 | |||
3de43c2a46 | |||
bd1e9abdd8 | |||
86d7290f9e | |||
a83d9b24ae | |||
3c9b6ea619 | |||
fd6b243cd6 | |||
2950c90c77 | |||
f4e9fc7b6a | |||
e98972e93b | |||
e332c1242f | |||
f5d6179a34 | |||
03f7d95d88 | |||
a163e75318 | |||
3bf3738e65 | |||
57fd35ffc1 | |||
d35be02587 | |||
57c53bd2a0 | |||
319e9bbd05 | |||
5decff9154 | |||
69f0c098e3 | |||
5abc46034a | |||
7c6faaa8e0 | |||
6bc7d60f52 | |||
fb3185de94 | |||
61744b68a1 | |||
187948ddd1 | |||
29a26b3236 | |||
d0b9d9c1d6 | |||
0fc01c48d1 | |||
ebfd3ac275 | |||
d8f27e95a6 | |||
d03d1207a8 | |||
8f33dec026 | |||
0dae109172 | |||
1a5df7192e | |||
3ffd36eee2 | |||
c3a0a4457a | |||
e6feec62a8 | |||
f0c346f18c | |||
186726fbad | |||
97bed17fd2 | |||
47645626c4 | |||
9a1ba527c0 | |||
3fcd5f6935 | |||
7135c0163b | |||
f53f35f364 | |||
07cc017320 | |||
271a292ea5 | |||
6e138bf373 | |||
a0bca2d4cf | |||
6c4fdf604b | |||
e4f8551cba | |||
559b1c50a3 | |||
d2eae3d5b3 | |||
27035c9454 | |||
991dd107b1 | |||
ffb2e3c0ab | |||
acc2fff655 | |||
427566b90d | |||
16ceb741b8 | |||
6f833798c7 | |||
6177d86d97 | |||
e9bfab8012 | |||
afeecea05f | |||
6f5b58b691 | |||
987b275c3f | |||
932e751240 | |||
706e2a07de | |||
a3022e6d88 | |||
b55d69b4c2 | |||
0bd8725426 | |||
ac3fe30d19 | |||
88eb6bc6a2 | |||
91cefa8e2f | |||
26413d1d61 | |||
06f9b7b52f | |||
599f3c26e0 | |||
b5a4f6f855 | |||
42716d3252 | |||
170d3163f3 | |||
0519056199 | |||
45433f71e5 | |||
b62b303fdb | |||
8e80fed2af | |||
fdafb0c848 | |||
5a3b6638a7 | |||
8e0a7f44d4 | |||
eb7f48a1c6 | |||
32483735ba | |||
53ce186294 | |||
37c1e78c7a | |||
45ecb87eab | |||
54bbc98343 | |||
f4972150cc | |||
1fc64bc83d | |||
64ade803f9 | |||
940bc08aba | |||
7569036dd4 | |||
59b8214685 | |||
f33c489154 | |||
5529ea0428 | |||
39a20fb95d | |||
155a636487 | |||
e571587a8c | |||
5945035fc3 | |||
766e243c63 | |||
83862dfbb4 | |||
c44e4ad100 | |||
1a0e87ac3c | |||
72edd75eab | |||
78d8d21196 | |||
fd3dcdf0f6 | |||
a87784ba15 | |||
c800bf55f5 | |||
0143e18fe9 | |||
de71ad7de4 | |||
c565965865 | |||
f2520d2d79 | |||
be3a1ab770 | |||
f369d9af0e | |||
265628750c | |||
657efb8cce | |||
40c1d3b711 | |||
51cd08ef57 | |||
2a319d733f | |||
519a9e64f8 | |||
4d575c6269 | |||
e9fe2ba740 | |||
7565ed8e06 | |||
02fb424659 | |||
1392b2b171 | |||
e558414867 | |||
76b8104f52 | |||
2eb3ad0de3 | |||
6974213036 | |||
72c8ad9fe1 | |||
a65fd695e1 | |||
994d0afd80 | |||
2379bcbf39 | |||
bddd8e9c2e | |||
c2978a6eea | |||
b467ddf970 | |||
3f649b8e07 | |||
e9e247217a | |||
f23974cfb5 | |||
53868a88fa | |||
28652a3ba8 | |||
41be1db8bc | |||
67f3ad31ab | |||
d7d3d663e9 | |||
32cefb8455 | |||
4add742aba | |||
f010672885 | |||
c14e6c9008 | |||
ec3b139273 | |||
a12e5e9cf5 |
@ -3,6 +3,15 @@ environment:
|
||||
PROJECT_NAME: actix
|
||||
matrix:
|
||||
# Stable channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: 1.21.0
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: 1.21.0
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: 1.21.0
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: 1.21.0
|
||||
# Stable channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: stable
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
@ -22,17 +31,23 @@ environment:
|
||||
CHANNEL: beta
|
||||
# Nightly channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: nightly
|
||||
CHANNEL: nightly-2017-12-21
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
CHANNEL: nightly-2017-12-21
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: nightly
|
||||
CHANNEL: nightly-2017-12-21
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
CHANNEL: nightly-2017-12-21
|
||||
|
||||
# Install Rust and Cargo
|
||||
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
|
||||
install:
|
||||
- ps: >-
|
||||
If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') {
|
||||
$Env:PATH += ';C:\msys64\mingw64\bin'
|
||||
} ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') {
|
||||
$Env:PATH += ';C:\MinGW\bin'
|
||||
}
|
||||
- curl -sSf -o rustup-init.exe https://win.rustup.rs
|
||||
- rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
|
||||
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||
@ -44,6 +59,4 @@ build: false
|
||||
|
||||
# Equivalent to Travis' `script` phase
|
||||
test_script:
|
||||
- cargo build --no-default-features
|
||||
- cargo clean
|
||||
- cargo test --no-default-features
|
||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,7 +1,7 @@
|
||||
target/
|
||||
Cargo.lock
|
||||
target/
|
||||
guide/build/
|
||||
/gh-pages
|
||||
__pycache__
|
||||
|
||||
*.so
|
||||
*.out
|
||||
@ -9,7 +9,6 @@ __pycache__
|
||||
*.pid
|
||||
*.sock
|
||||
*~
|
||||
*.egg-info/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
94
.travis.yml
94
.travis.yml
@ -1,28 +1,36 @@
|
||||
language: rust
|
||||
|
||||
rust:
|
||||
- 1.20.0
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
|
||||
sudo: required
|
||||
sudo: false
|
||||
dist: trusty
|
||||
|
||||
cache:
|
||||
cargo: true
|
||||
apt: true
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- rust: 1.21.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:
|
||||
- RUSTFLAGS="-C link-dead-code"
|
||||
# - RUSTFLAGS="-C link-dead-code"
|
||||
- OPENSSL_VERSION=openssl-1.0.2
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libcurl4-openssl-dev
|
||||
- libelf-dev
|
||||
- libdw-dev
|
||||
- cmake
|
||||
- gcc
|
||||
- binutils-dev
|
||||
- libiberty-dev
|
||||
before_install:
|
||||
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
|
||||
|
||||
# Add clippy
|
||||
before_script:
|
||||
@ -33,7 +41,31 @@ before_script:
|
||||
- export PATH=$PATH:~/.cargo/bin
|
||||
|
||||
script:
|
||||
- cargo test --no-default-features
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
||||
cargo clean
|
||||
USE_SKEPTIC=1 cargo test --features=alpn
|
||||
else
|
||||
cargo clean
|
||||
cargo test
|
||||
# --features=alpn
|
||||
fi
|
||||
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
||||
cd examples/basics && cargo check && cd ../..
|
||||
cd examples/hello-world && cargo check && cd ../..
|
||||
cd examples/multipart && cargo check && cd ../..
|
||||
cd examples/json && cargo check && cd ../..
|
||||
cd examples/juniper && cargo check && cd ../..
|
||||
cd examples/state && cargo check && cd ../..
|
||||
cd examples/template_tera && cargo check && cd ../..
|
||||
cd examples/diesel && cargo check && cd ../..
|
||||
cd examples/r2d2 && cargo check && cd ../..
|
||||
cd examples/tls && cargo check && cd ../..
|
||||
cd examples/websocket-chat && cargo check && cd ../..
|
||||
cd examples/websocket && cargo check && cd ../..
|
||||
fi
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then
|
||||
cargo clippy
|
||||
@ -43,27 +75,19 @@ script:
|
||||
after_success:
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
||||
cargo doc --no-deps &&
|
||||
cargo doc --features "alpn, tls" --no-deps &&
|
||||
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
||||
cargo install mdbook &&
|
||||
cd guide && mdbook build -d ../target/doc/guide && cd .. &&
|
||||
git clone https://github.com/davisp/ghp-import.git &&
|
||||
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
|
||||
echo "Uploaded documentation"
|
||||
fi
|
||||
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
||||
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
|
||||
tar xzf master.tar.gz &&
|
||||
cd kcov-master &&
|
||||
mkdir build &&
|
||||
cd build &&
|
||||
cmake .. &&
|
||||
make &&
|
||||
make install DESTDIR=../../kcov-build &&
|
||||
cd ../.. &&
|
||||
rm -rf kcov-master &&
|
||||
for file in target/debug/actix_web-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done &&
|
||||
for file in target/debug/test_*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done &&
|
||||
bash <(curl -s https://codecov.io/bash) &&
|
||||
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
|
||||
|
89
CHANGES.md
89
CHANGES.md
@ -1,4 +1,91 @@
|
||||
# CHANGES
|
||||
# Changes
|
||||
|
||||
## 0.4.0 (2018-02-28)
|
||||
|
||||
* Actix 0.5 compatibility
|
||||
|
||||
* Fix request json/urlencoded loaders
|
||||
|
||||
* Simplify HttpServer type definition
|
||||
|
||||
* Added HttpRequest::encoding() method
|
||||
|
||||
* Added HttpRequest::mime_type() method
|
||||
|
||||
* Added HttpRequest::uri_mut(), allows to modify request uri
|
||||
|
||||
* Added StaticFiles::index_file()
|
||||
|
||||
* Added http client
|
||||
|
||||
* Added websocket client
|
||||
|
||||
* Added TestServer::ws(), test websockets client
|
||||
|
||||
* Added TestServer http client support
|
||||
|
||||
* Allow to override content encoding on application level
|
||||
|
||||
|
||||
## 0.3.3 (2018-01-25)
|
||||
|
||||
* Stop processing any events after context stop
|
||||
|
||||
* Re-enable write back-pressure for h1 connections
|
||||
|
||||
* Refactor HttpServer::start_ssl() method
|
||||
|
||||
* Upgrade openssl to 0.10
|
||||
|
||||
|
||||
## 0.3.2 (2018-01-21)
|
||||
|
||||
* Fix HEAD requests handling
|
||||
|
||||
* Log request processing errors
|
||||
|
||||
* Always enable content encoding if encoding explicitly selected
|
||||
|
||||
* Allow multiple Applications on a single server with different state #49
|
||||
|
||||
* CORS middleware: allowed_headers is defaulting to None #50
|
||||
|
||||
|
||||
## 0.3.1 (2018-01-13)
|
||||
|
||||
* Fix directory entry path #47
|
||||
|
||||
* Do not enable chunked encoding for HTTP/1.0
|
||||
|
||||
* Allow explicitly disable chunked encoding
|
||||
|
||||
|
||||
## 0.3.0 (2018-01-12)
|
||||
|
||||
* HTTP/2 Support
|
||||
|
||||
* Refactor streaming responses
|
||||
|
||||
* Refactor error handling
|
||||
|
||||
* Asynchronous middlewares
|
||||
|
||||
* Refactor logger middleware
|
||||
|
||||
* Content compression/decompression (br, gzip, deflate)
|
||||
|
||||
* Server multi-threading
|
||||
|
||||
* Gracefull shutdown support
|
||||
|
||||
|
||||
## 0.2.1 (2017-11-03)
|
||||
|
||||
* Allow to start tls server with `HttpServer::serve_tls`
|
||||
|
||||
* Export `Frame` enum
|
||||
|
||||
* Add conversion impl from `HttpResponse` and `BinaryBody` to a `Frame`
|
||||
|
||||
|
||||
## 0.2.0 (2017-10-30)
|
||||
|
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
97
Cargo.toml
97
Cargo.toml
@ -1,16 +1,20 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "0.2.0"
|
||||
version = "0.4.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix web framework"
|
||||
readme = "README.md"
|
||||
keywords = ["actor", "http", "web"]
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://github.com/actix/actix-web"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
documentation = "https://docs.rs/actix-web/"
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
license = "Apache-2.0"
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||
categories = ["network-programming", "asynchronous",
|
||||
"web-programming::http-server",
|
||||
"web-programming::http-client",
|
||||
"web-programming::websocket"]
|
||||
license = "MIT/Apache-2.0"
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config",
|
||||
"appveyor.yml", "/examples/**"]
|
||||
build = "build.rs"
|
||||
|
||||
[badges]
|
||||
@ -25,46 +29,89 @@ path = "src/lib.rs"
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# http/2
|
||||
# http2 = ["h2"]
|
||||
# tls
|
||||
tls = ["native-tls", "tokio-tls"]
|
||||
|
||||
# openssl
|
||||
alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.3"
|
||||
time = "0.1"
|
||||
http = "0.1"
|
||||
httparse = "0.1"
|
||||
base64 = "0.9"
|
||||
bitflags = "1.0"
|
||||
brotli2 = "^0.3.2"
|
||||
failure = "0.1.1"
|
||||
flate2 = "1.0"
|
||||
h2 = "0.1"
|
||||
http = "^0.1.2"
|
||||
httparse = "1.2"
|
||||
http-range = "0.1"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
mime_guess = "1.8"
|
||||
cookie = { version="0.10", features=["percent-encode"] }
|
||||
regex = "0.2"
|
||||
sha1 = "0.2"
|
||||
url = "1.5"
|
||||
num_cpus = "1.0"
|
||||
percent-encoding = "1.0"
|
||||
rand = "0.4"
|
||||
regex = "0.2"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
sha1 = "0.6"
|
||||
smallvec = "0.6"
|
||||
time = "0.1"
|
||||
encoding = "0.2"
|
||||
url = { version="1.7", features=["query_encoding"] }
|
||||
cookie = { version="0.10", features=["percent-encode", "secure"] }
|
||||
|
||||
# tokio
|
||||
# io
|
||||
mio = "^0.6.13"
|
||||
net2 = "0.2"
|
||||
bytes = "0.4"
|
||||
byteorder = "1"
|
||||
futures = "0.1"
|
||||
tokio-io = "0.1"
|
||||
tokio-core = "0.1"
|
||||
# h2 = { git = 'https://github.com/carllerche/h2', optional = true }
|
||||
trust-dns-resolver = "0.8"
|
||||
|
||||
# native-tls
|
||||
native-tls = { version="0.1", optional = true }
|
||||
tokio-tls = { version="0.1", optional = true }
|
||||
|
||||
# openssl
|
||||
openssl = { version="0.10", optional = true }
|
||||
tokio-openssl = { version="0.2", optional = true }
|
||||
|
||||
[dependencies.actix]
|
||||
version = ">=0.3.1"
|
||||
#path = "../actix"
|
||||
#git = "https://github.com/actix/actix.git"
|
||||
default-features = false
|
||||
features = []
|
||||
version = "0.5"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.4"
|
||||
reqwest = "0.8"
|
||||
env_logger = "0.5"
|
||||
skeptic = "0.13"
|
||||
serde_derive = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
skeptic = "0.13"
|
||||
version_check = "0.1"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 3
|
||||
debug = true
|
||||
codegen-units = 1
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"./",
|
||||
"examples/basics",
|
||||
"examples/juniper",
|
||||
"examples/diesel",
|
||||
"examples/r2d2",
|
||||
"examples/json",
|
||||
"examples/hello-world",
|
||||
"examples/multipart",
|
||||
"examples/state",
|
||||
"examples/template_tera",
|
||||
"examples/tls",
|
||||
"examples/websocket",
|
||||
"examples/websocket-chat",
|
||||
"examples/web-cors/backend",
|
||||
"tools/wsload/",
|
||||
]
|
||||
|
25
LICENSE-MIT
Normal file
25
LICENSE-MIT
Normal file
@ -0,0 +1,25 @@
|
||||
Copyright (c) 2017 Nikilay Kim
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
8
Makefile
8
Makefile
@ -1,6 +1,6 @@
|
||||
.PHONY: default build test doc clean
|
||||
.PHONY: default build test doc book clean
|
||||
|
||||
CARGO_FLAGS := --features "$(FEATURES)"
|
||||
CARGO_FLAGS := --features "$(FEATURES) alpn"
|
||||
|
||||
default: test
|
||||
|
||||
@ -20,3 +20,7 @@ clippy:
|
||||
|
||||
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 ..
|
||||
|
168
README.md
168
README.md
@ -1,123 +1,79 @@
|
||||
# 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)
|
||||
# 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)
|
||||
|
||||
Asynchronous web framework for [Actix](https://github.com/actix/actix).
|
||||
Actix web is a small, pragmatic, 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
|
||||
* Streaming and pipelining
|
||||
* Keep-alive and slow requests handling
|
||||
* Client/server [WebSockets](https://actix.github.io/actix-web/guide/qs_9.html) support
|
||||
* Transparent content compression/decompression (br, gzip, deflate)
|
||||
* Configurable [request routing](https://actix.github.io/actix-web/guide/qs_5.html)
|
||||
* Graceful server shutdown
|
||||
* Multipart streams
|
||||
* 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),
|
||||
[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](https://github.com/actix/actix).
|
||||
|
||||
## Documentation
|
||||
|
||||
* [User Guide](http://actix.github.io/actix-web/guide/)
|
||||
* [API Documentation (Development)](http://actix.github.io/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.20 or later
|
||||
|
||||
---
|
||||
|
||||
Actix web is licensed under the [Apache-2.0 license](http://opensource.org/licenses/APACHE-2.0).
|
||||
|
||||
## Features
|
||||
|
||||
* HTTP 1.1 and 1.0 support
|
||||
* Streaming and pipelining support
|
||||
* Keep-alive and slow requests support
|
||||
* [WebSockets support](https://actix.github.io/actix-web/actix_web/ws/index.html)
|
||||
* Configurable request routing
|
||||
* Multipart streams
|
||||
* Middlewares
|
||||
|
||||
## Usage
|
||||
|
||||
To use `actix-web`, add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
actix-web = "0.2"
|
||||
```
|
||||
* Minimum supported Rust version: 1.21 or later
|
||||
|
||||
## Example
|
||||
|
||||
* [Basic](https://github.com/actix/actix-web/tree/master/examples/basic.rs)
|
||||
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state.rs)
|
||||
* [Mulitpart streams](https://github.com/actix/actix-web/tree/master/examples/multipart)
|
||||
* [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket.rs)
|
||||
* [Tcp/Websocket chat](https://github.com/actix/actix-web/tree/master/examples/websocket-chat)
|
||||
* [SockJS Server](https://github.com/fafhrd91/actix-sockjs)
|
||||
|
||||
|
||||
```rust
|
||||
extern crate actix;
|
||||
```rust,ignore
|
||||
extern crate actix_web;
|
||||
extern crate env_logger;
|
||||
|
||||
use actix::*;
|
||||
use actix_web::*;
|
||||
|
||||
|
||||
struct MyWebSocket;
|
||||
|
||||
/// Actor with http context
|
||||
impl Actor for MyWebSocket {
|
||||
type Context = HttpContext<Self>;
|
||||
}
|
||||
|
||||
/// Http route handler
|
||||
impl Route for MyWebSocket {
|
||||
type State = ();
|
||||
|
||||
fn request(req: &mut HttpRequest,
|
||||
payload: Payload, ctx: &mut HttpContext<Self>) -> RouteResult<Self>
|
||||
{
|
||||
// websocket handshake
|
||||
let resp = ws::handshake(req)?;
|
||||
// send HttpResponse back to peer
|
||||
ctx.start(resp);
|
||||
// convert bytes stream to a stream of `ws::Message` and handle stream
|
||||
ctx.add_stream(ws::WsStream::new(payload));
|
||||
Reply::async(MyWebSocket)
|
||||
}
|
||||
}
|
||||
|
||||
/// Standard actix's stream handler for a stream of `ws::Message`
|
||||
impl StreamHandler<ws::Message> for MyWebSocket {
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
println!("WebSocket session openned");
|
||||
}
|
||||
|
||||
fn finished(&mut self, ctx: &mut Self::Context) {
|
||||
println!("WebSocket session closed");
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<ws::Message> for MyWebSocket {
|
||||
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>)
|
||||
-> Response<Self, ws::Message>
|
||||
{
|
||||
// process websocket messages
|
||||
println!("WS: {:?}", msg);
|
||||
match msg {
|
||||
ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg),
|
||||
ws::Message::Text(text) => ws::WsWriter::text(ctx, &text),
|
||||
ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin),
|
||||
ws::Message::Closed | ws::Message::Error => {
|
||||
ctx.stop();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Self::empty()
|
||||
}
|
||||
fn index(req: HttpRequest) -> String {
|
||||
format!("Hello {}!", &req.match_info()["name"])
|
||||
}
|
||||
|
||||
fn main() {
|
||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||
let _ = env_logger::init();
|
||||
let sys = actix::System::new("ws-example");
|
||||
|
||||
HttpServer::new(
|
||||
Application::default("/")
|
||||
// enable logger
|
||||
.middleware(Logger::new(None))
|
||||
// websocket route
|
||||
.resource("/ws/", |r| r.get::<MyWebSocket>())
|
||||
.route_handler("/", StaticFiles::new("examples/static/", true)))
|
||||
.serve::<_, ()>("127.0.0.1:8080").unwrap();
|
||||
|
||||
Arbiter::system().send(msgs::SystemExit(0));
|
||||
let _ = sys.run();
|
||||
|| Application::new()
|
||||
.resource("/{name}", |r| r.f(index)))
|
||||
.bind("127.0.0.1:8080").unwrap()
|
||||
.run();
|
||||
}
|
||||
```
|
||||
|
||||
### 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/)
|
||||
|
||||
## Benchmarks
|
||||
|
||||
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext)
|
||||
|
||||
* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks).
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
|
||||
|
||||
at your option.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Contribution to the actix-web crate is organized under the terms of the
|
||||
Contributor Covenant, the maintainer of actix-web, @fafhrd91, promises to
|
||||
intervene to uphold that code of conduct.
|
||||
|
34
build.rs
34
build.rs
@ -1,18 +1,48 @@
|
||||
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"]);
|
||||
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 f = env::var("OUT_DIR").unwrap() + "/skeptic-tests.rs";
|
||||
let _ = fs::File::create(f);
|
||||
}
|
||||
|
||||
match version_check::is_nightly() {
|
||||
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
|
||||
Some(false) => (),
|
||||
None => (),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn main() {
|
||||
match version_check::is_nightly() {
|
||||
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
|
||||
Some(false) => (),
|
||||
None => (),
|
||||
};
|
||||
}
|
||||
|
4
cov.sh
4
cov.sh
@ -1,4 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
for file in target/debug/actix_web-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; /usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done &&
|
||||
for file in target/debug/test_*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; /usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done
|
@ -1,53 +0,0 @@
|
||||
#![allow(unused_variables)]
|
||||
extern crate actix;
|
||||
extern crate actix_web;
|
||||
extern crate env_logger;
|
||||
|
||||
use actix_web::*;
|
||||
|
||||
/// somple handle
|
||||
fn index(req: &mut HttpRequest, _payload: Payload, state: &()) -> HttpResponse {
|
||||
println!("{:?}", req);
|
||||
httpcodes::HTTPOk.into()
|
||||
}
|
||||
|
||||
/// handle with path parameters like `/name/{name}/`
|
||||
fn with_param(req: &mut HttpRequest, _payload: Payload, state: &())
|
||||
-> HandlerResult<HttpResponse>
|
||||
{
|
||||
println!("{:?}", req);
|
||||
|
||||
Ok(HttpResponse::builder(StatusCode::OK)
|
||||
.content_type("test/plain")
|
||||
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))?)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||
let _ = env_logger::init();
|
||||
let sys = actix::System::new("ws-example");
|
||||
|
||||
HttpServer::new(
|
||||
Application::default("/")
|
||||
// enable logger
|
||||
.middleware(Logger::new(None))
|
||||
// register simple handler, handle all methods
|
||||
.handler("/index.html", index)
|
||||
// with path parameters
|
||||
.resource("/user/{name}/", |r| r.handler(Method::GET, with_param))
|
||||
// redirect
|
||||
.resource("/", |r| r.handler(Method::GET, |req, _, _| {
|
||||
println!("{:?}", req);
|
||||
|
||||
Ok(httpcodes::HTTPFound
|
||||
.builder()
|
||||
.header("LOCATION", "/index.html")
|
||||
.body(Body::Empty)?)
|
||||
}))
|
||||
// static files
|
||||
.route_handler("/static", StaticFiles::new("examples/static/", true)))
|
||||
.serve::<_, ()>("127.0.0.1:8080").unwrap();
|
||||
|
||||
println!("Started http server: 127.0.0.1:8080");
|
||||
let _ = sys.run();
|
||||
}
|
11
examples/basics/Cargo.toml
Normal file
11
examples/basics/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[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="../.." }
|
20
examples/basics/README.md
Normal file
20
examples/basics/README.md
Normal file
@ -0,0 +1,20 @@
|
||||
# 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
|
151
examples/basics/src/main.rs
Normal file
151
examples/basics/src/main.rs
Normal file
@ -0,0 +1,151 @@
|
||||
#![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().p(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
examples/diesel/.env
Normal file
1
examples/diesel/.env
Normal file
@ -0,0 +1 @@
|
||||
DATABASE_URL=file:test.db
|
19
examples/diesel/Cargo.toml
Normal file
19
examples/diesel/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[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"
|
43
examples/diesel/README.md
Normal file
43
examples/diesel/README.md
Normal file
@ -0,0 +1,43 @@
|
||||
# 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)
|
@ -0,0 +1 @@
|
||||
DROP TABLE users
|
@ -0,0 +1,4 @@
|
||||
CREATE TABLE users (
|
||||
id VARCHAR NOT NULL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL
|
||||
)
|
52
examples/diesel/src/db.rs
Normal file
52
examples/diesel/src/db.rs
Normal file
@ -0,0 +1,52 @@
|
||||
//! 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())
|
||||
}
|
||||
}
|
73
examples/diesel/src/main.rs
Normal file
73
examples/diesel/src/main.rs
Normal file
@ -0,0 +1,73 @@
|
||||
//! 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();
|
||||
}
|
14
examples/diesel/src/models.rs
Normal file
14
examples/diesel/src/models.rs
Normal file
@ -0,0 +1,14 @@
|
||||
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,
|
||||
}
|
6
examples/diesel/src/schema.rs
Normal file
6
examples/diesel/src/schema.rs
Normal file
@ -0,0 +1,6 @@
|
||||
table! {
|
||||
users (id) {
|
||||
id -> Text,
|
||||
name -> Text,
|
||||
}
|
||||
}
|
BIN
examples/diesel/test.db
Normal file
BIN
examples/diesel/test.db
Normal file
Binary file not shown.
10
examples/hello-world/Cargo.toml
Normal file
10
examples/hello-world/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[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 = "../../" }
|
28
examples/hello-world/src/main.rs
Normal file
28
examples/hello-world/src/main.rs
Normal file
@ -0,0 +1,28 @@
|
||||
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();
|
||||
}
|
18
examples/json/Cargo.toml
Normal file
18
examples/json/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[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="../../" }
|
48
examples/json/README.md
Normal file
48
examples/json/README.md
Normal file
@ -0,0 +1,48 @@
|
||||
# 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``
|
18
examples/json/client.py
Normal file
18
examples/json/client.py
Normal file
@ -0,0 +1,18 @@
|
||||
# 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())
|
99
examples/json/src/main.rs
Normal file
99
examples/json/src/main.rs
Normal file
@ -0,0 +1,99 @@
|
||||
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();
|
||||
}
|
17
examples/juniper/Cargo.toml
Normal file
17
examples/juniper/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[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"
|
15
examples/juniper/README.md
Normal file
15
examples/juniper/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# 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)
|
110
examples/juniper/src/main.rs
Normal file
110
examples/juniper/src/main.rs
Normal file
@ -0,0 +1,110 @@
|
||||
//! 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://localhost: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();
|
||||
}
|
58
examples/juniper/src/schema.rs
Normal file
58
examples/juniper/src/schema.rs
Normal file
@ -0,0 +1,58 @@
|
||||
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 {})
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
name = "multipart-example"
|
||||
version = "0.1.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
workspace = "../.."
|
||||
|
||||
[[bin]]
|
||||
name = "multipart"
|
||||
@ -9,6 +10,6 @@ path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
env_logger = "*"
|
||||
#actix = "0.3"
|
||||
actix = { git = "https://github.com/actix/actix.git" }
|
||||
actix-web = { path = "../../" }
|
||||
futures = "0.1"
|
||||
actix = "0.5"
|
||||
actix-web = { path="../../" }
|
||||
|
24
examples/multipart/README.md
Normal file
24
examples/multipart/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# 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,26 +1,28 @@
|
||||
# This script could be used for actix-web multipart example test
|
||||
# just start server and run client.py
|
||||
|
||||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
|
||||
def req1():
|
||||
async def req1():
|
||||
with aiohttp.MultipartWriter() as writer:
|
||||
writer.append('test')
|
||||
writer.append_json({'passed': True})
|
||||
|
||||
resp = yield from aiohttp.request(
|
||||
resp = await aiohttp.ClientSession().request(
|
||||
"post", 'http://localhost:8080/multipart',
|
||||
data=writer, headers=writer.headers)
|
||||
print(resp)
|
||||
assert 200 == resp.status
|
||||
|
||||
|
||||
def req2():
|
||||
async def req2():
|
||||
with aiohttp.MultipartWriter() as writer:
|
||||
writer.append('test')
|
||||
writer.append_json({'passed': True})
|
||||
writer.append(open('src/main.rs'))
|
||||
|
||||
resp = yield from aiohttp.request(
|
||||
resp = await aiohttp.ClientSession().request(
|
||||
"post", 'http://localhost:8080/multipart',
|
||||
data=writer, headers=writer.headers)
|
||||
print(resp)
|
||||
|
@ -2,77 +2,58 @@
|
||||
extern crate actix;
|
||||
extern crate actix_web;
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
|
||||
use actix::*;
|
||||
use actix_web::*;
|
||||
|
||||
struct MyRoute;
|
||||
use futures::{Future, Stream};
|
||||
use futures::future::{result, Either};
|
||||
|
||||
impl Actor for MyRoute {
|
||||
type Context = HttpContext<Self>;
|
||||
}
|
||||
|
||||
impl Route for MyRoute {
|
||||
type State = ();
|
||||
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>>
|
||||
{
|
||||
println!("{:?}", req);
|
||||
|
||||
fn request(req: &mut HttpRequest, payload: Payload,
|
||||
ctx: &mut HttpContext<Self>) -> RouteResult<Self> {
|
||||
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);
|
||||
|
||||
let multipart = req.multipart(payload)?;
|
||||
|
||||
// get Multipart stream
|
||||
WrapStream::<MyRoute>::actstream(multipart)
|
||||
.and_then(|item, act, ctx| {
|
||||
// Multipart stream is a stream of Fields and nested Multiparts
|
||||
match item {
|
||||
multipart::MultipartItem::Field(field) => {
|
||||
println!("==== FIELD ==== {:?}", field);
|
||||
|
||||
// Read field's stream
|
||||
fut::Either::A(
|
||||
field.actstream()
|
||||
.map(|chunk, act, ctx| {
|
||||
println!(
|
||||
"-- CHUNK: \n{}",
|
||||
std::str::from_utf8(&chunk.0).unwrap());
|
||||
})
|
||||
.finish())
|
||||
},
|
||||
multipart::MultipartItem::Nested(mp) => {
|
||||
// Do nothing for nested multipart stream
|
||||
fut::Either::B(fut::ok(()))
|
||||
}
|
||||
// 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(())))
|
||||
}
|
||||
})
|
||||
// wait until stream finish
|
||||
.finish()
|
||||
.map_err(|e, act, ctx| {
|
||||
ctx.start(httpcodes::HTTPBadRequest);
|
||||
ctx.write_eof();
|
||||
})
|
||||
.map(|_, act, ctx| {
|
||||
ctx.start(httpcodes::HTTPOk);
|
||||
ctx.write_eof();
|
||||
})
|
||||
.spawn(ctx);
|
||||
|
||||
Reply::async(MyRoute)
|
||||
}
|
||||
}
|
||||
})
|
||||
.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");
|
||||
|
||||
HttpServer::new(
|
||||
vec![
|
||||
Application::default("/")
|
||||
.resource("/multipart", |r| {
|
||||
r.post::<MyRoute>();
|
||||
}).finish()
|
||||
])
|
||||
.serve::<_, ()>("127.0.0.1:8080").unwrap();
|
||||
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();
|
||||
}
|
||||
|
20
examples/r2d2/Cargo.toml
Normal file
20
examples/r2d2/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
||||
[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 = "*"
|
41
examples/r2d2/src/db.rs
Normal file
41
examples/r2d2/src/db.rs
Normal file
@ -0,0 +1,41 @@
|
||||
//! 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"))?)
|
||||
}
|
||||
}
|
64
examples/r2d2/src/main.rs
Normal file
64
examples/r2d2/src/main.rs
Normal file
@ -0,0 +1,64 @@
|
||||
//! 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();
|
||||
}
|
BIN
examples/r2d2/test.db
Normal file
BIN
examples/r2d2/test.db
Normal file
Binary file not shown.
@ -1,86 +0,0 @@
|
||||
//! There are two level of statfulness 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 actix::*;
|
||||
use actix_web::*;
|
||||
use std::cell::Cell;
|
||||
|
||||
struct AppState {
|
||||
counter: Cell<usize>,
|
||||
}
|
||||
|
||||
/// somple handle
|
||||
fn index(req: &mut HttpRequest, _: Payload, state: &AppState) -> HttpResponse {
|
||||
println!("{:?}", req);
|
||||
state.counter.set(state.counter.get() + 1);
|
||||
httpcodes::HTTPOk.with_body(
|
||||
format!("Num of requests: {}", 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 = HttpContext<Self>;
|
||||
}
|
||||
|
||||
impl Route for MyWebSocket {
|
||||
/// Shared application state
|
||||
type State = AppState;
|
||||
|
||||
fn request(req: &mut HttpRequest,
|
||||
payload: Payload, ctx: &mut HttpContext<Self>) -> RouteResult<Self>
|
||||
{
|
||||
let resp = ws::handshake(req)?;
|
||||
ctx.start(resp);
|
||||
ctx.add_stream(ws::WsStream::new(payload));
|
||||
Reply::async(MyWebSocket{counter: 0})
|
||||
}
|
||||
}
|
||||
impl StreamHandler<ws::Message> for MyWebSocket {}
|
||||
impl Handler<ws::Message> for MyWebSocket {
|
||||
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>)
|
||||
-> Response<Self, ws::Message>
|
||||
{
|
||||
self.counter += 1;
|
||||
println!("WS({}): {:?}", self.counter, msg);
|
||||
match msg {
|
||||
ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg),
|
||||
ws::Message::Text(text) => ws::WsWriter::text(ctx, &text),
|
||||
ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin),
|
||||
ws::Message::Closed | ws::Message::Error => {
|
||||
ctx.stop();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||
let _ = env_logger::init();
|
||||
let sys = actix::System::new("ws-example");
|
||||
|
||||
HttpServer::new(
|
||||
Application::builder("/", AppState{counter: Cell::new(0)})
|
||||
// enable logger
|
||||
.middleware(Logger::new(None))
|
||||
// websocket route
|
||||
.resource("/ws/", |r| r.get::<MyWebSocket>())
|
||||
// register simple handler, handle all methods
|
||||
.handler("/", index))
|
||||
.serve::<_, ()>("127.0.0.1:8080").unwrap();
|
||||
|
||||
println!("Started http server: 127.0.0.1:8080");
|
||||
let _ = sys.run();
|
||||
}
|
11
examples/state/Cargo.toml
Normal file
11
examples/state/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[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 = "../../" }
|
15
examples/state/README.md
Normal file
15
examples/state/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# 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/)
|
76
examples/state/src/main.rs
Normal file
76
examples/state/src/main.rs
Normal file
@ -0,0 +1,76 @@
|
||||
#![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::WsError> 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();
|
||||
}
|
BIN
examples/static/actixLogo.png
Normal file
BIN
examples/static/actixLogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
BIN
examples/static/favicon.ico
Normal file
BIN
examples/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
11
examples/template_tera/Cargo.toml
Normal file
11
examples/template_tera/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[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 = "*"
|
17
examples/template_tera/README.md
Normal file
17
examples/template_tera/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
# 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)
|
47
examples/template_tera/src/main.rs
Normal file
47
examples/template_tera/src/main.rs
Normal file
@ -0,0 +1,47 @@
|
||||
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();
|
||||
}
|
17
examples/template_tera/templates/index.html
Normal file
17
examples/template_tera/templates/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!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>
|
13
examples/template_tera/templates/user.html
Normal file
13
examples/template_tera/templates/user.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Actix web</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi, {{ name }}!</h1>
|
||||
<p>
|
||||
{{ text }}
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
15
examples/tls/Cargo.toml
Normal file
15
examples/tls/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[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" }
|
16
examples/tls/README.md
Normal file
16
examples/tls/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# 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)
|
31
examples/tls/cert.pem
Normal file
31
examples/tls/cert.pem
Normal file
@ -0,0 +1,31 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFPjCCAyYCCQDvLYiYD+jqeTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV
|
||||
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww
|
||||
CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xODAxMjUx
|
||||
NzQ2MDFaFw0xOTAxMjUxNzQ2MDFaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD
|
||||
QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY
|
||||
MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
|
||||
MIICCgKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEPn8k1
|
||||
sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+MIK5U
|
||||
NLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM54jXy
|
||||
voLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZWLWr
|
||||
odGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAkoqND
|
||||
xdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNliJDmA
|
||||
CRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6/stI
|
||||
yFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuDYX2U
|
||||
UuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nPwPTO
|
||||
vRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA69un
|
||||
CEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEAATAN
|
||||
BgkqhkiG9w0BAQsFAAOCAgEApavsgsn7SpPHfhDSN5iZs1ILZQRewJg0Bty0xPfk
|
||||
3tynSW6bNH3nSaKbpsdmxxomthNSQgD2heOq1By9YzeOoNR+7Pk3s4FkASnf3ToI
|
||||
JNTUasBFFfaCG96s4Yvs8KiWS/k84yaWuU8c3Wb1jXs5Rv1qE1Uvuwat1DSGXSoD
|
||||
JNluuIkCsC4kWkyq5pWCGQrabWPRTWsHwC3PTcwSRBaFgYLJaR72SloHB1ot02zL
|
||||
d2age9dmFRFLLCBzP+D7RojBvL37qS/HR+rQ4SoQwiVc/JzaeqSe7ZbvEH9sZYEu
|
||||
ALowJzgbwro7oZflwTWunSeSGDSltkqKjvWvZI61pwfHKDahUTmZ5h2y67FuGEaC
|
||||
CIOUI8dSVSPKITxaq3JL4ze2e9/0Lt7hj19YK2uUmtMAW5Tirz4Yx5lyGH9U8Wur
|
||||
y/X8VPxTc4A9TMlJgkyz0hqvhbPOT/zSWB10zXh0glKAsSBryAOEDxV1UygmSir7
|
||||
YV8Qaq+oyKUTMc1MFq5vZ07M51EPaietn85t8V2Y+k/8XYltRp32NxsypxAJuyxh
|
||||
g/ko6RVTrWa1sMvz/F9LFqAdKiK5eM96lh9IU4xiLg4ob8aS/GRAA8oIFkZFhLrt
|
||||
tOwjIUPmEPyHWFi8dLpNuQKYalLYhuwZftG/9xV+wqhKGZO9iPrpHSYBRTap8w2y
|
||||
1QU=
|
||||
-----END CERTIFICATE-----
|
51
examples/tls/key.pem
Normal file
51
examples/tls/key.pem
Normal file
@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIJKAIBAAKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEP
|
||||
n8k1sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+M
|
||||
IK5UNLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM5
|
||||
4jXyvoLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZ
|
||||
WLWrodGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAk
|
||||
oqNDxdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNli
|
||||
JDmACRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6
|
||||
/stIyFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuD
|
||||
YX2UUuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nP
|
||||
wPTOvRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA
|
||||
69unCEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEA
|
||||
AQKCAgAME3aoeXNCPxMrSri7u4Xnnk71YXl0Tm9vwvjRQlMusXZggP8VKN/KjP0/
|
||||
9AE/GhmoxqPLrLCZ9ZE1EIjgmZ9Xgde9+C8rTtfCG2RFUL7/5J2p6NonlocmxoJm
|
||||
YkxYwjP6ce86RTjQWL3RF3s09u0inz9/efJk5O7M6bOWMQ9VZXDlBiRY5BYvbqUR
|
||||
6FeSzD4MnMbdyMRoVBeXE88gTvZk8xhB6DJnLzYgc0tKiRoeKT0iYv5JZw25VyRM
|
||||
ycLzfTrFmXCPfB1ylb483d9Ly4fBlM8nkx37PzEnAuukIawDxsPOb9yZC+hfvNJI
|
||||
7NFiMN+3maEqG2iC00w4Lep4skHY7eHUEUMl+Wjr+koAy2YGLWAwHZQTm7iXn9Ab
|
||||
L6adL53zyCKelRuEQOzbeosJAqS+5fpMK0ekXyoFIuskj7bWuIoCX7K/kg6q5IW+
|
||||
vC2FrlsrbQ79GztWLVmHFO1I4J9M5r666YS0qdh8c+2yyRl4FmSiHfGxb3eOKpxQ
|
||||
b6uI97iZlkxPF9LYUCSc7wq0V2gGz+6LnGvTHlHrOfVXqw/5pLAKhXqxvnroDTwz
|
||||
0Ay/xFF6ei/NSxBY5t8ztGCBm45wCU3l8pW0X6dXqwUipw5b4MRy1VFRu6rqlmbL
|
||||
OPSCuLxqyqsigiEYsBgS/icvXz9DWmCQMPd2XM9YhsHvUq+R4QKCAQEA98EuMMXI
|
||||
6UKIt1kK2t/3OeJRyDd4iv/fCMUAnuPjLBvFE4cXD/SbqCxcQYqb+pue3PYkiTIC
|
||||
71rN8OQAc5yKhzmmnCE5N26br/0pG4pwEjIr6mt8kZHmemOCNEzvhhT83nfKmV0g
|
||||
9lNtuGEQMiwmZrpUOF51JOMC39bzcVjYX2Cmvb7cFbIq3lR0zwM+aZpQ4P8LHCIu
|
||||
bgHmwbdlkLyIULJcQmHIbo6nPFB3ZZE4mqmjwY+rA6Fh9rgBa8OFCfTtrgeYXrNb
|
||||
IgZQ5U8GoYRPNC2ot0vpTinraboa/cgm6oG4M7FW1POCJTl+/ktHEnKuO5oroSga
|
||||
/BSg7hCNFVaOhwKCAQEA4Kkys0HtwEbV5mY/NnvUD5KwfXX7BxoXc9lZ6seVoLEc
|
||||
KjgPYxqYRVrC7dB2YDwwp3qcRTi/uBAgFNm3iYlDzI4xS5SeaudUWjglj7BSgXE2
|
||||
iOEa7EwcvVPluLaTgiWjlzUKeUCNNHWSeQOt+paBOT+IgwRVemGVpAgkqQzNh/nP
|
||||
tl3p9aNtgzEm1qVlPclY/XUCtf3bcOR+z1f1b4jBdn0leu5OhnxkC+Htik+2fTXD
|
||||
jt6JGrMkanN25YzsjnD3Sn+v6SO26H99wnYx5oMSdmb8SlWRrKtfJHnihphjG/YY
|
||||
l1cyorV6M/asSgXNQfGJm4OuJi0I4/FL2wLUHnU+JwKCAQEAzh4WipcRthYXXcoj
|
||||
gMKRkMOb3GFh1OpYqJgVExtudNTJmZxq8GhFU51MR27Eo7LycMwKy2UjEfTOnplh
|
||||
Us2qZiPtW7k8O8S2m6yXlYUQBeNdq9IuuYDTaYD94vsazscJNSAeGodjE+uGvb1q
|
||||
1wLqE87yoE7dUInYa1cOA3+xy2/CaNuviBFJHtzOrSb6tqqenQEyQf6h9/12+DTW
|
||||
t5pSIiixHrzxHiFqOoCLRKGToQB+71rSINwTf0nITNpGBWmSj5VcC3VV3TG5/XxI
|
||||
fPlxV2yhD5WFDPVNGBGvwPDSh4jSMZdZMSNBZCy4XWFNSKjGEWoK4DFYed3DoSt9
|
||||
5IG1YwKCAQA63ntHl64KJUWlkwNbboU583FF3uWBjee5VqoGKHhf3CkKMxhtGqnt
|
||||
+oN7t5VdUEhbinhqdx1dyPPvIsHCS3K1pkjqii4cyzNCVNYa2dQ00Qq+QWZBpwwc
|
||||
3GAkz8rFXsGIPMDa1vxpU6mnBjzPniKMcsZ9tmQDppCEpBGfLpio2eAA5IkK8eEf
|
||||
cIDB3CM0Vo94EvI76CJZabaE9IJ+0HIJb2+jz9BJ00yQBIqvJIYoNy9gP5Xjpi+T
|
||||
qV/tdMkD5jwWjHD3AYHLWKUGkNwwkAYFeqT/gX6jpWBP+ZRPOp011X3KInJFSpKU
|
||||
DT5GQ1Dux7EMTCwVGtXqjO8Ym5wjwwsfAoIBAEcxlhIW1G6BiNfnWbNPWBdh3v/K
|
||||
5Ln98Rcrz8UIbWyl7qNPjYb13C1KmifVG1Rym9vWMO3KuG5atK3Mz2yLVRtmWAVc
|
||||
fxzR57zz9MZFDun66xo+Z1wN3fVxQB4CYpOEI4Lb9ioX4v85hm3D6RpFukNtRQEc
|
||||
Gfr4scTjJX4jFWDp0h6ffMb8mY+quvZoJ0TJqV9L9Yj6Ksdvqez/bdSraev97bHQ
|
||||
4gbQxaTZ6WjaD4HjpPQefMdWp97Metg0ZQSS8b8EzmNFgyJ3XcjirzwliKTAQtn6
|
||||
I2sd0NCIooelrKRD8EJoDUwxoOctY7R97wpZ7/wEHU45cBCbRV3H4JILS5c=
|
||||
-----END RSA PRIVATE KEY-----
|
50
examples/tls/src/main.rs
Normal file
50
examples/tls/src/main.rs
Normal file
@ -0,0 +1,50 @@
|
||||
#![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();
|
||||
}
|
15
examples/web-cors/README.md
Normal file
15
examples/web-cors/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# 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
Normal file
4
examples/web-cors/backend/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
/target/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
17
examples/web-cors/backend/Cargo.toml
Normal file
17
examples/web-cors/backend/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[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"
|
45
examples/web-cors/backend/src/main.rs
Normal file
45
examples/web-cors/backend/src/main.rs
Normal file
@ -0,0 +1,45 @@
|
||||
#[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();
|
||||
}
|
19
examples/web-cors/backend/src/user.rs
Normal file
19
examples/web-cors/backend/src/user.rs
Normal file
@ -0,0 +1,19 @@
|
||||
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()
|
||||
}
|
3
examples/web-cors/frontend/.babelrc
Normal file
3
examples/web-cors/frontend/.babelrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["env"]
|
||||
}
|
14
examples/web-cors/frontend/.gitignore
vendored
Normal file
14
examples/web-cors/frontend/.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
/dist/
|
||||
.cache
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
13
examples/web-cors/frontend/index.html
Normal file
13
examples/web-cors/frontend/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!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>
|
22
examples/web-cors/frontend/package.json
Normal file
22
examples/web-cors/frontend/package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
145
examples/web-cors/frontend/src/app.vue
Normal file
145
examples/web-cors/frontend/src/app.vue
Normal file
@ -0,0 +1,145 @@
|
||||
<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>
|
11
examples/web-cors/frontend/src/main.js
Normal file
11
examples/web-cors/frontend/src/main.js
Normal file
@ -0,0 +1,11 @@
|
||||
import Vue from 'vue'
|
||||
import App from './app'
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
render: h => h(App)
|
||||
})
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept();
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
name = "websocket-example"
|
||||
version = "0.1.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
workspace = "../.."
|
||||
|
||||
[[bin]]
|
||||
name = "server"
|
||||
@ -24,6 +25,5 @@ serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_derive = "1.0"
|
||||
|
||||
#actix = "0.3"
|
||||
actix = { git = "https://github.com/actix/actix.git" }
|
||||
actix-web = { path = "../../" }
|
||||
actix = "0.5"
|
||||
actix-web = { path="../../" }
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Websocket chat example
|
||||
|
||||
This is extension of the
|
||||
This is extension of the
|
||||
[actix chat example](https://github.com/actix/actix/tree/master/examples/chat)
|
||||
|
||||
Added features:
|
||||
@ -9,18 +9,16 @@ Added features:
|
||||
* 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 messsage 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 droppped
|
||||
|
||||
* `\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
|
||||
@ -29,7 +27,6 @@ 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/
|
||||
Open url: [http://localhost:8080/](http://localhost:8080/)
|
||||
|
@ -1,4 +1,4 @@
|
||||
extern crate actix;
|
||||
#[macro_use] extern crate actix;
|
||||
extern crate bytes;
|
||||
extern crate byteorder;
|
||||
extern crate futures;
|
||||
@ -12,6 +12,9 @@ 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::*;
|
||||
|
||||
@ -26,7 +29,12 @@ fn main() {
|
||||
Arbiter::handle().spawn(
|
||||
TcpStream::connect(&addr, Arbiter::handle())
|
||||
.and_then(|stream| {
|
||||
let addr: SyncAddress<_> = ChatClient.framed(stream, codec::ClientChatCodec);
|
||||
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|| {
|
||||
@ -37,7 +45,7 @@ fn main() {
|
||||
return
|
||||
}
|
||||
|
||||
addr.send(ClientCommand(cmd));
|
||||
addr.do_send(ClientCommand(cmd));
|
||||
}
|
||||
});
|
||||
|
||||
@ -54,43 +62,48 @@ fn main() {
|
||||
}
|
||||
|
||||
|
||||
struct ChatClient;
|
||||
|
||||
struct ClientCommand(String);
|
||||
|
||||
impl ResponseType for ClientCommand {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
struct ChatClient {
|
||||
framed: actix::io::FramedWrite<WriteHalf<TcpStream>, codec::ClientChatCodec>,
|
||||
}
|
||||
|
||||
impl Actor for ChatClient {
|
||||
type Context = FramedContext<Self>;
|
||||
#[derive(Message)]
|
||||
struct ClientCommand(String);
|
||||
|
||||
fn started(&mut self, ctx: &mut FramedContext<Self>) {
|
||||
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 FramedContext<Self>) {
|
||||
fn hb(&self, ctx: &mut Context<Self>) {
|
||||
ctx.run_later(Duration::new(1, 0), |act, ctx| {
|
||||
if ctx.send(codec::ChatRequest::Ping).is_ok() {
|
||||
act.hb(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
|
||||
{
|
||||
fn handle(&mut self, msg: ClientCommand, ctx: &mut FramedContext<Self>)
|
||||
-> Response<Self, ClientCommand>
|
||||
{
|
||||
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 Self::empty()
|
||||
return
|
||||
}
|
||||
|
||||
// we check for /sss type of messages
|
||||
@ -98,11 +111,11 @@ impl Handler<ClientCommand> for ChatClient
|
||||
let v: Vec<&str> = m.splitn(2, ' ').collect();
|
||||
match v[0] {
|
||||
"/list" => {
|
||||
let _ = ctx.send(codec::ChatRequest::List);
|
||||
self.framed.write(codec::ChatRequest::List);
|
||||
},
|
||||
"/join" => {
|
||||
if v.len() == 2 {
|
||||
let _ = ctx.send(codec::ChatRequest::Join(v[1].to_owned()));
|
||||
self.framed.write(codec::ChatRequest::Join(v[1].to_owned()));
|
||||
} else {
|
||||
println!("!!! room name is required");
|
||||
}
|
||||
@ -110,35 +123,16 @@ impl Handler<ClientCommand> for ChatClient
|
||||
_ => println!("!!! unknown command"),
|
||||
}
|
||||
} else {
|
||||
let _ = ctx.send(codec::ChatRequest::Message(m.to_owned()));
|
||||
self.framed.write(codec::ChatRequest::Message(m.to_owned()));
|
||||
}
|
||||
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Server communication
|
||||
|
||||
impl FramedActor for ChatClient {
|
||||
type Io = TcpStream;
|
||||
type Codec = codec::ClientChatCodec;
|
||||
}
|
||||
|
||||
impl StreamHandler<codec::ChatResponse, io::Error> for ChatClient {
|
||||
|
||||
fn finished(&mut self, _: &mut FramedContext<Self>) {
|
||||
println!("Disconnected");
|
||||
|
||||
// Stop application on disconnect
|
||||
Arbiter::system().send(msgs::SystemExit(0));
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<codec::ChatResponse, io::Error> for ChatClient {
|
||||
|
||||
fn handle(&mut self, msg: codec::ChatResponse, _: &mut FramedContext<Self>)
|
||||
-> Response<Self, codec::ChatResponse>
|
||||
{
|
||||
fn handle(&mut self, msg: codec::ChatResponse, _: &mut Context<Self>) {
|
||||
match msg {
|
||||
codec::ChatResponse::Message(ref msg) => {
|
||||
println!("message: {}", msg);
|
||||
@ -155,7 +149,5 @@ impl Handler<codec::ChatResponse, io::Error> for ChatClient {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,9 @@ use serde_json as json;
|
||||
use byteorder::{BigEndian , ByteOrder};
|
||||
use bytes::{BytesMut, BufMut};
|
||||
use tokio_io::codec::{Encoder, Decoder};
|
||||
use actix::ResponseType;
|
||||
|
||||
/// Client request
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Message)]
|
||||
#[serde(tag="cmd", content="data")]
|
||||
pub enum ChatRequest {
|
||||
/// List rooms
|
||||
@ -20,13 +19,8 @@ pub enum ChatRequest {
|
||||
Ping
|
||||
}
|
||||
|
||||
impl ResponseType for ChatRequest {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
}
|
||||
|
||||
/// Server response
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Message)]
|
||||
#[serde(tag="cmd", content="data")]
|
||||
pub enum ChatResponse {
|
||||
Ping,
|
||||
@ -41,11 +35,6 @@ pub enum ChatResponse {
|
||||
Message(String),
|
||||
}
|
||||
|
||||
impl ResponseType for ChatResponse {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
}
|
||||
|
||||
/// Codec for Client -> Server transport
|
||||
pub struct ChatCodec;
|
||||
|
||||
|
@ -10,6 +10,7 @@ extern crate serde;
|
||||
extern crate serde_json;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
|
||||
#[macro_use]
|
||||
extern crate actix;
|
||||
extern crate actix_web;
|
||||
|
||||
@ -22,11 +23,21 @@ 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: SyncAddress<server::ChatServer>,
|
||||
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 {
|
||||
@ -41,50 +52,53 @@ struct WsChatSession {
|
||||
}
|
||||
|
||||
impl Actor for WsChatSession {
|
||||
type Context = HttpContext<Self>;
|
||||
}
|
||||
type Context = ws::WebsocketContext<Self, WsChatSessionState>;
|
||||
|
||||
/// Entry point for our route
|
||||
impl Route for WsChatSession {
|
||||
type State = 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 request(req: &mut HttpRequest,
|
||||
payload: Payload, ctx: &mut HttpContext<Self>) -> RouteResult<Self>
|
||||
{
|
||||
// websocket handshakre, it may fail if request is not websocket request
|
||||
let resp = ws::handshake(&req)?;
|
||||
ctx.start(resp);
|
||||
ctx.add_stream(ws::WsStream::new(payload));
|
||||
Reply::async(
|
||||
WsChatSession {
|
||||
id: 0,
|
||||
hb: Instant::now(),
|
||||
room: "Main".to_owned(),
|
||||
name: None})
|
||||
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 {
|
||||
fn handle(&mut self, msg: session::Message, ctx: &mut HttpContext<Self>)
|
||||
-> Response<Self, session::Message>
|
||||
{
|
||||
ws::WsWriter::text(ctx, &msg.0);
|
||||
Self::empty()
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) {
|
||||
ctx.text(msg.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// WebSocket message handler
|
||||
impl Handler<ws::Message> for WsChatSession {
|
||||
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>)
|
||||
-> Response<Self, ws::Message>
|
||||
{
|
||||
impl StreamHandler<ws::Message, ws::WsError> for WsChatSession {
|
||||
|
||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||
println!("WEBSOCKET MESSAGE: {:?}", msg);
|
||||
match msg {
|
||||
ws::Message::Ping(msg) =>
|
||||
ws::WsWriter::pong(ctx, &msg),
|
||||
ws::Message::Pong(msg) =>
|
||||
self.hb = Instant::now(),
|
||||
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
|
||||
@ -94,17 +108,19 @@ impl Handler<ws::Message> for WsChatSession {
|
||||
"/list" => {
|
||||
// Send ListRooms message to chat server and wait for response
|
||||
println!("List rooms");
|
||||
ctx.state().addr.call(self, server::ListRooms).then(|res, _, ctx| {
|
||||
match res {
|
||||
Ok(Ok(rooms)) => {
|
||||
for room in rooms {
|
||||
ws::WsWriter::text(ctx, &room);
|
||||
}
|
||||
},
|
||||
_ => println!("Something is wrong"),
|
||||
}
|
||||
fut::ok(())
|
||||
}).wait(ctx)
|
||||
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
|
||||
@ -112,23 +128,22 @@ impl Handler<ws::Message> for WsChatSession {
|
||||
"/join" => {
|
||||
if v.len() == 2 {
|
||||
self.room = v[1].to_owned();
|
||||
ctx.state().addr.send(
|
||||
ctx.state().addr.do_send(
|
||||
server::Join{id: self.id, name: self.room.clone()});
|
||||
|
||||
ws::WsWriter::text(ctx, "joined");
|
||||
ctx.text("joined");
|
||||
} else {
|
||||
ws::WsWriter::text(ctx, "!!! room name is required");
|
||||
ctx.text("!!! room name is required");
|
||||
}
|
||||
},
|
||||
"/name" => {
|
||||
if v.len() == 2 {
|
||||
self.name = Some(v[1].to_owned());
|
||||
} else {
|
||||
ws::WsWriter::text(ctx, "!!! name is required");
|
||||
ctx.text("!!! name is required");
|
||||
}
|
||||
},
|
||||
_ => ws::WsWriter::text(
|
||||
ctx, &format!("!!! unknown command: {:?}", m)),
|
||||
_ => ctx.text(format!("!!! unknown command: {:?}", m)),
|
||||
}
|
||||
} else {
|
||||
let msg = if let Some(ref name) = self.name {
|
||||
@ -137,7 +152,7 @@ impl Handler<ws::Message> for WsChatSession {
|
||||
m.to_owned()
|
||||
};
|
||||
// send message to chat server
|
||||
ctx.state().addr.send(
|
||||
ctx.state().addr.do_send(
|
||||
server::Message{id: self.id,
|
||||
msg: msg,
|
||||
room: self.room.clone()})
|
||||
@ -145,82 +160,50 @@ impl Handler<ws::Message> for WsChatSession {
|
||||
},
|
||||
ws::Message::Binary(bin) =>
|
||||
println!("Unexpected binary"),
|
||||
ws::Message::Closed | ws::Message::Error => {
|
||||
ws::Message::Close(_) => {
|
||||
ctx.stop();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamHandler<ws::Message> for WsChatSession
|
||||
{
|
||||
/// Method is called when stream get polled first time.
|
||||
/// 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 subs = ctx.sync_subscriber();
|
||||
ctx.state().addr.call(
|
||||
self, server::Connect{addr: subs}).then(
|
||||
|res, act, ctx| {
|
||||
match res {
|
||||
Ok(Ok(res)) => act.id = res,
|
||||
// something is wrong with chat server
|
||||
_ => ctx.stop(),
|
||||
}
|
||||
fut::ok(())
|
||||
}).wait(ctx);
|
||||
}
|
||||
|
||||
/// Method is called when stream finishes, even if stream finishes with error.
|
||||
fn finished(&mut self, ctx: &mut Self::Context) {
|
||||
// notify chat server
|
||||
ctx.state().addr.send(server::Disconnect{id: self.id});
|
||||
ctx.stop()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
let _ = env_logger::init();
|
||||
let sys = actix::System::new("websocket-example");
|
||||
|
||||
// Start chat server actor in separate thread
|
||||
let server: SyncAddress<_> =
|
||||
Arbiter::start(|_| server::ChatServer::default());
|
||||
let server: Addr<Syn, _> = Arbiter::start(|_| server::ChatServer::default());
|
||||
|
||||
// Start tcp server in separate thread
|
||||
let srv = server.clone();
|
||||
Arbiter::new("tcp-server").send::<msgs::Execute>(
|
||||
Arbiter::new("tcp-server").do_send::<msgs::Execute>(
|
||||
msgs::Execute::new(move || {
|
||||
session::TcpServer::new("127.0.0.1:12345", srv);
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
// Websocket sessions state
|
||||
let state = WsChatSessionState { addr: server };
|
||||
|
||||
// Create Http server with websocket support
|
||||
HttpServer::new(
|
||||
Application::builder("/", state)
|
||||
// redirect to websocket.html
|
||||
.resource("/", |r|
|
||||
r.handler(Method::GET, |req, payload, state| {
|
||||
Ok(httpcodes::HTTPFound
|
||||
.builder()
|
||||
.header("LOCATION", "/static/websocket.html")
|
||||
.body(Body::Empty)?)
|
||||
}))
|
||||
// websocket
|
||||
.resource("/ws/", |r| r.get::<WsChatSession>())
|
||||
// static resources
|
||||
.route_handler("/static", StaticFiles::new("static/", true)))
|
||||
.serve::<_, ()>("127.0.0.1:8080").unwrap();
|
||||
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();
|
||||
}
|
||||
|
@ -12,29 +12,20 @@ use session;
|
||||
/// Message for chat server communications
|
||||
|
||||
/// New chat session is created
|
||||
#[derive(Message)]
|
||||
#[rtype(usize)]
|
||||
pub struct Connect {
|
||||
pub addr: Box<Subscriber<session::Message> + Send>,
|
||||
}
|
||||
|
||||
/// Response type for Connect message
|
||||
///
|
||||
/// Chat server returns unique session id
|
||||
impl ResponseType for Connect {
|
||||
type Item = usize;
|
||||
type Error = ();
|
||||
pub addr: Recipient<Syn, session::Message>,
|
||||
}
|
||||
|
||||
/// Session is disconnected
|
||||
#[derive(Message)]
|
||||
pub struct Disconnect {
|
||||
pub id: usize,
|
||||
}
|
||||
|
||||
impl ResponseType for Disconnect {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
}
|
||||
|
||||
/// Send message to specific room
|
||||
#[derive(Message)]
|
||||
pub struct Message {
|
||||
/// Id of the client session
|
||||
pub id: usize,
|
||||
@ -44,20 +35,15 @@ pub struct Message {
|
||||
pub room: String,
|
||||
}
|
||||
|
||||
impl ResponseType for Message {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
}
|
||||
|
||||
/// List of available rooms
|
||||
pub struct ListRooms;
|
||||
|
||||
impl ResponseType for ListRooms {
|
||||
type Item = Vec<String>;
|
||||
type Error = ();
|
||||
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,
|
||||
@ -65,15 +51,10 @@ pub struct Join {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl ResponseType for Join {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
}
|
||||
|
||||
/// `ChatServer` manages chat rooms and responsible for coordinating chat session.
|
||||
/// implementation is super primitive
|
||||
pub struct ChatServer {
|
||||
sessions: HashMap<usize, Box<Subscriber<session::Message> + Send>>,
|
||||
sessions: HashMap<usize, Recipient<Syn, session::Message>>,
|
||||
rooms: HashMap<String, HashSet<usize>>,
|
||||
rng: RefCell<ThreadRng>,
|
||||
}
|
||||
@ -99,7 +80,7 @@ impl ChatServer {
|
||||
for id in sessions {
|
||||
if *id != skip_id {
|
||||
if let Some(addr) = self.sessions.get(id) {
|
||||
let _ = addr.send(session::Message(message.to_owned()));
|
||||
let _ = addr.do_send(session::Message(message.to_owned()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -118,8 +99,9 @@ impl Actor for ChatServer {
|
||||
///
|
||||
/// 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>) -> Response<Self, Connect> {
|
||||
fn handle(&mut self, msg: Connect, _: &mut Context<Self>) -> Self::Result {
|
||||
println!("Someone joined");
|
||||
|
||||
// notify all users in same room
|
||||
@ -133,14 +115,15 @@ impl Handler<Connect> for ChatServer {
|
||||
self.rooms.get_mut(&"Main".to_owned()).unwrap().insert(id);
|
||||
|
||||
// send id back
|
||||
Self::reply(id)
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler for Disconnect message.
|
||||
impl Handler<Disconnect> for ChatServer {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) -> Response<Self, Disconnect> {
|
||||
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) {
|
||||
println!("Someone disconnected");
|
||||
|
||||
let mut rooms: Vec<String> = Vec::new();
|
||||
@ -158,40 +141,39 @@ impl Handler<Disconnect> for ChatServer {
|
||||
for room in rooms {
|
||||
self.send_message(&room, "Someone disconnected", 0);
|
||||
}
|
||||
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler for Message message.
|
||||
impl Handler<Message> for ChatServer {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: Message, _: &mut Context<Self>) -> Response<Self, Message> {
|
||||
fn handle(&mut self, msg: Message, _: &mut Context<Self>) {
|
||||
self.send_message(&msg.room, msg.msg.as_str(), msg.id);
|
||||
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler for `ListRooms` message.
|
||||
impl Handler<ListRooms> for ChatServer {
|
||||
type Result = MessageResult<ListRooms>;
|
||||
|
||||
fn handle(&mut self, _: ListRooms, _: &mut Context<Self>) -> Response<Self, 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())
|
||||
}
|
||||
|
||||
Self::reply(rooms)
|
||||
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>) -> Response<Self, Join> {
|
||||
fn handle(&mut self, msg: Join, _: &mut Context<Self>) {
|
||||
let Join {id, name} = msg;
|
||||
let mut rooms = Vec::new();
|
||||
|
||||
@ -211,7 +193,5 @@ impl Handler<Join> for ChatServer {
|
||||
}
|
||||
self.send_message(&name, "Someone connected", id);
|
||||
self.rooms.get_mut(&name).unwrap().insert(id);
|
||||
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
@ -4,113 +4,102 @@ 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::*;
|
||||
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);
|
||||
|
||||
impl ResponseType for Message {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
}
|
||||
|
||||
/// `ChatSession` actor is responsible for tcp peer communitions.
|
||||
/// `ChatSession` actor is responsible for tcp peer communications.
|
||||
pub struct ChatSession {
|
||||
/// unique session id
|
||||
id: usize,
|
||||
/// this is address of chat server
|
||||
addr: SyncAddress<ChatServer>,
|
||||
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 convinient wrapper around `Framed` object from `tokio_io`
|
||||
type Context = FramedContext<Self>;
|
||||
}
|
||||
/// It is convenient wrapper around `Framed` object from `tokio_io`
|
||||
type Context = Context<Self>;
|
||||
|
||||
/// To use `FramedContext` we have to define Io type and Codec
|
||||
impl FramedActor for ChatSession {
|
||||
type Io = TcpStream;
|
||||
type Codec= ChatCodec;
|
||||
}
|
||||
|
||||
/// Also `FramedContext` requires Actor which is able to handle stream
|
||||
/// of `<Codec as Decoder>::Item` items.
|
||||
impl StreamHandler<ChatRequest, io::Error> for ChatSession {
|
||||
|
||||
fn started(&mut self, ctx: &mut FramedContext<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.
|
||||
self.addr.call(self, server::Connect{addr: ctx.sync_subscriber()}).then(|res, act, ctx| {
|
||||
match res {
|
||||
Ok(Ok(res)) => act.id = res,
|
||||
// something is wrong with chat server
|
||||
_ => ctx.stop(),
|
||||
}
|
||||
fut::ok(())
|
||||
}).wait(ctx);
|
||||
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 finished(&mut self, ctx: &mut FramedContext<Self>) {
|
||||
fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
|
||||
// notify chat server
|
||||
self.addr.send(server::Disconnect{id: self.id});
|
||||
|
||||
ctx.stop()
|
||||
self.addr.do_send(server::Disconnect{id: self.id});
|
||||
Running::Stop
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<ChatRequest, io::Error> for ChatSession {
|
||||
impl actix::io::WriteHandler<io::Error> for ChatSession {}
|
||||
|
||||
/// We'll stop chat session actor on any error, high likely it is just
|
||||
/// termination of the tcp stream.
|
||||
fn error(&mut self, _: io::Error, ctx: &mut FramedContext<Self>) {
|
||||
ctx.stop()
|
||||
}
|
||||
/// 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 FramedContext<Self>)
|
||||
-> Response<Self, ChatRequest>
|
||||
{
|
||||
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.call(self, server::ListRooms).then(|res, _, ctx| {
|
||||
match res {
|
||||
Ok(Ok(rooms)) => {
|
||||
let _ = ctx.send(ChatResponse::Rooms(rooms));
|
||||
},
|
||||
_ => println!("Something is wrong"),
|
||||
}
|
||||
fut::ok(())
|
||||
}).wait(ctx)
|
||||
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.send(server::Join{id: self.id, name: name.clone()});
|
||||
let _ = ctx.send(ChatResponse::Joined(name));
|
||||
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.send(
|
||||
self.addr.do_send(
|
||||
server::Message{id: self.id,
|
||||
msg: message, room:
|
||||
self.room.clone()})
|
||||
@ -119,35 +108,32 @@ impl Handler<ChatRequest, io::Error> for ChatSession {
|
||||
ChatRequest::Ping =>
|
||||
self.hb = Instant::now(),
|
||||
}
|
||||
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 FramedContext<Self>)
|
||||
-> Response<Self, Message>
|
||||
{
|
||||
fn handle(&mut self, msg: Message, ctx: &mut Context<Self>) {
|
||||
// send message to peer
|
||||
let _ = ctx.send(ChatResponse::Message(msg.0));
|
||||
|
||||
Self::empty()
|
||||
self.framed.write(ChatResponse::Message(msg.0));
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper methods
|
||||
impl ChatSession {
|
||||
|
||||
pub fn new(addr: SyncAddress<ChatServer>) -> ChatSession {
|
||||
ChatSession {id: 0, addr: addr, hb: Instant::now(), room: "Main".to_owned()}
|
||||
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 FramedContext<Self>) {
|
||||
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) {
|
||||
@ -155,29 +141,28 @@ impl ChatSession {
|
||||
println!("Client heartbeat failed, disconnecting!");
|
||||
|
||||
// notify chat server
|
||||
act.addr.send(server::Disconnect{id: act.id});
|
||||
act.addr.do_send(server::Disconnect{id: act.id});
|
||||
|
||||
// stop actor
|
||||
ctx.stop();
|
||||
}
|
||||
|
||||
if ctx.send(ChatResponse::Ping).is_ok() {
|
||||
// if we can not send message to sink, sink is closed (disconnected)
|
||||
act.hb(ctx);
|
||||
}
|
||||
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 incomint tcp connection and create
|
||||
/// Define tcp server that will accept incoming tcp connection and create
|
||||
/// chat actors.
|
||||
pub struct TcpServer {
|
||||
chat: SyncAddress<ChatServer>,
|
||||
chat: Addr<Syn, ChatServer>,
|
||||
}
|
||||
|
||||
impl TcpServer {
|
||||
pub fn new(s: &str, chat: SyncAddress<ChatServer>) {
|
||||
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();
|
||||
@ -188,7 +173,9 @@ impl TcpServer {
|
||||
// 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_stream(listener.incoming().map(|(t, a)| TcpConnect(t, a)));
|
||||
ctx.add_message_stream(listener.incoming()
|
||||
.map_err(|_| ())
|
||||
.map(|(t, a)| TcpConnect(t, a)));
|
||||
TcpServer{chat: chat}
|
||||
});
|
||||
}
|
||||
@ -200,27 +187,21 @@ impl Actor for TcpServer {
|
||||
type Context = Context<Self>;
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
struct TcpConnect(TcpStream, net::SocketAddr);
|
||||
|
||||
impl ResponseType for TcpConnect {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
}
|
||||
|
||||
/// Handle stream of TcpStream's
|
||||
impl StreamHandler<TcpConnect, io::Error> for TcpServer {}
|
||||
impl Handler<TcpConnect> for TcpServer {
|
||||
type Result = ();
|
||||
|
||||
impl Handler<TcpConnect, io::Error> for TcpServer {
|
||||
|
||||
fn handle(&mut self, msg: TcpConnect, _: &mut Context<Self>) -> Response<Self, TcpConnect>
|
||||
{
|
||||
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::new(server).framed(msg.0, ChatCodec);
|
||||
|
||||
// this is response for message, which is defined by `ResponseType` trait
|
||||
// in this case we just return unit.
|
||||
Self::empty()
|
||||
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,85 +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::*;
|
||||
|
||||
|
||||
struct MyWebSocket;
|
||||
|
||||
impl Actor for MyWebSocket {
|
||||
type Context = HttpContext<Self>;
|
||||
}
|
||||
|
||||
/// Http route handler
|
||||
impl Route for MyWebSocket {
|
||||
type State = ();
|
||||
|
||||
fn request(req: &mut HttpRequest,
|
||||
payload: Payload, ctx: &mut HttpContext<Self>) -> RouteResult<Self>
|
||||
{
|
||||
// websocket handshake
|
||||
let resp = ws::handshake(req)?;
|
||||
// send HttpResponse back to peer
|
||||
ctx.start(resp);
|
||||
// convert bytes stream to a stream of `ws::Message` and register it
|
||||
ctx.add_stream(ws::WsStream::new(payload));
|
||||
Reply::async(MyWebSocket)
|
||||
}
|
||||
}
|
||||
|
||||
/// Standard actix's stream handler for a stream of `ws::Message`
|
||||
impl StreamHandler<ws::Message> for MyWebSocket {
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
println!("WebSocket session openned");
|
||||
}
|
||||
|
||||
fn finished(&mut self, ctx: &mut Self::Context) {
|
||||
println!("WebSocket session closed");
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<ws::Message> for MyWebSocket {
|
||||
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>)
|
||||
-> Response<Self, ws::Message>
|
||||
{
|
||||
// process websocket messages
|
||||
println!("WS: {:?}", msg);
|
||||
match msg {
|
||||
ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg),
|
||||
ws::Message::Text(text) => ws::WsWriter::text(ctx, &text),
|
||||
ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin),
|
||||
ws::Message::Closed | ws::Message::Error => {
|
||||
ctx.stop();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||
let _ = env_logger::init();
|
||||
let sys = actix::System::new("ws-example");
|
||||
|
||||
HttpServer::new(
|
||||
Application::default("/")
|
||||
// enable logger
|
||||
.middleware(Logger::new(None))
|
||||
// websocket route
|
||||
.resource("/ws/", |r| r.get::<MyWebSocket>())
|
||||
.route_handler("/", StaticFiles::new("examples/static/", true)))
|
||||
// start http server on 127.0.0.1:8080
|
||||
.serve::<_, ()>("127.0.0.1:8080").unwrap();
|
||||
|
||||
println!("Started http server: 127.0.0.1:8080");
|
||||
let _ = sys.run();
|
||||
}
|
20
examples/websocket/Cargo.toml
Normal file
20
examples/websocket/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
||||
[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="../../" }
|
27
examples/websocket/README.md
Normal file
27
examples/websocket/README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# 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``
|
113
examples/websocket/src/client.rs
Normal file
113
examples/websocket/src/client.rs
Normal file
@ -0,0 +1,113 @@
|
||||
//! 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, WsError, WsClient, WsClientWriter};
|
||||
|
||||
|
||||
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(
|
||||
WsClient::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(WsClientWriter);
|
||||
|
||||
#[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, WsError> 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()
|
||||
}
|
||||
}
|
65
examples/websocket/src/main.rs
Normal file
65
examples/websocket/src/main.rs
Normal file
@ -0,0 +1,65 @@
|
||||
//! 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::WsError> 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();
|
||||
}
|
7
guide/book.toml
Normal file
7
guide/book.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[book]
|
||||
title = "Actix web"
|
||||
description = "Actix web framework guide"
|
||||
author = "Actix Project and Contributors"
|
||||
|
||||
[output.html]
|
||||
google-analytics = "UA-110322332-1"
|
16
guide/src/SUMMARY.md
Normal file
16
guide/src/SUMMARY.md
Normal file
@ -0,0 +1,16 @@
|
||||
# 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)
|
34
guide/src/qs_1.md
Normal file
34
guide/src/qs_1.md
Normal file
@ -0,0 +1,34 @@
|
||||
# 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.
|
212
guide/src/qs_10.md
Normal file
212
guide/src/qs_10.md
Normal file
@ -0,0 +1,212 @@
|
||||
# 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();
|
||||
}
|
||||
```
|
49
guide/src/qs_12.md
Normal file
49
guide/src/qs_12.md
Normal file
@ -0,0 +1,49 @@
|
||||
# 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.
|
44
guide/src/qs_13.md
Normal file
44
guide/src/qs_13.md
Normal file
@ -0,0 +1,44 @@
|
||||
# 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.
|
125
guide/src/qs_14.md
Normal file
125
guide/src/qs_14.md
Normal file
@ -0,0 +1,125 @@
|
||||
# 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).
|
98
guide/src/qs_2.md
Normal file
98
guide/src/qs_2.md
Normal file
@ -0,0 +1,98 @@
|
||||
# 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/)
|
109
guide/src/qs_3.md
Normal file
109
guide/src/qs_3.md
Normal file
@ -0,0 +1,109 @@
|
||||
# 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`.
|
193
guide/src/qs_3_5.md
Normal file
193
guide/src/qs_3_5.md
Normal file
@ -0,0 +1,193 @@
|
||||
# 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.
|
245
guide/src/qs_4.md
Normal file
245
guide/src/qs_4.md
Normal file
@ -0,0 +1,245 @@
|
||||
# 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.
|
151
guide/src/qs_4_5.md
Normal file
151
guide/src/qs_4_5.md
Normal file
@ -0,0 +1,151 @@
|
||||
# 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
|
||||
```
|
575
guide/src/qs_5.md
Normal file
575
guide/src/qs_5.md
Normal file
@ -0,0 +1,575 @@
|
||||
# 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()
|
||||
.p(pred::Get())
|
||||
.p(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::p()*](../actix_web/struct.Route.html#method.p) 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()
|
||||
.p(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()
|
||||
.p(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().p(pred::Not(pred::Get())).f(|req| HTTPMethodNotAllowed);
|
||||
})
|
||||
# .finish();
|
||||
}
|
||||
```
|
287
guide/src/qs_7.md
Normal file
287
guide/src/qs_7.md
Normal file
@ -0,0 +1,287 @@
|
||||
# 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() {}
|
||||
```
|
154
guide/src/qs_8.md
Normal file
154
guide/src/qs_8.md
Normal file
@ -0,0 +1,154 @@
|
||||
# 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())));
|
||||
}
|
||||
```
|
48
guide/src/qs_9.md
Normal file
48
guide/src/qs_9.md
Normal file
@ -0,0 +1,48 @@
|
||||
# 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::WsError> 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/)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user