mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-04 01:51:30 +02:00
Compare commits
1077 Commits
Author | SHA1 | Date | |
---|---|---|---|
c5b18c6d30 | |||
94c5bb5cdd | |||
2ca0ea70c4 | |||
0b01884fca | |||
83168731fc | |||
7295846426 | |||
72bc1546c4 | |||
d39b531537 | |||
35e68723df | |||
0624f9b9d9 | |||
0e3820afdf | |||
839d67ac6a | |||
b517957761 | |||
d18f9c5905 | |||
76fcdc13a3 | |||
62a9b4c53c | |||
c570229351 | |||
d041df6c4b | |||
bc28e54976 | |||
26ab5cbd01 | |||
50c2a5ceb0 | |||
8dbbb0ee07 | |||
ca76dff5a7 | |||
88f66d49d0 | |||
be288fa00a | |||
5e6a0aa3df | |||
fd87eb59f8 | |||
81ac905c7b | |||
bb11fb3d24 | |||
23eea54776 | |||
2881859400 | |||
1686682c19 | |||
d04ff13955 | |||
e757dc5a71 | |||
be358db422 | |||
7df2d6b12a | |||
458e6bdcc2 | |||
0b0bbd6bd9 | |||
5617896780 | |||
2b803f30c9 | |||
9b152acc32 | |||
eb66685d1a | |||
b505e682d4 | |||
48e7013997 | |||
ff14633b3d | |||
37db7d8168 | |||
89bf12605d | |||
9fb0498437 | |||
b2a43a3c8d | |||
38063b9873 | |||
1045a6c6f0 | |||
7243c58fce | |||
fffaf2bb2d | |||
7becb95a97 | |||
a4b837a1c1 | |||
542315ce7f | |||
602d78b76c | |||
18b706d4fb | |||
94b41fd484 | |||
3a80cb7bf3 | |||
e4a85a53f4 | |||
1a45dbd768 | |||
7cff5d9ade | |||
c04e0fdec4 | |||
0f0fe5f148 | |||
e7f9f5b46d | |||
c3fbba2678 | |||
a88e97edba | |||
1f08100f6f | |||
ab60ec6e1d | |||
0fbd05009d | |||
fdb7419e24 | |||
191b53bd7c | |||
2d4ee0ee01 | |||
5bd5f67d79 | |||
5d8cbccfe9 | |||
8d5fa6ee71 | |||
084104d058 | |||
2c411a04a9 | |||
af0c8d893d | |||
691457fbfe | |||
2dafd9c681 | |||
12586db15c | |||
b847bda8ca | |||
2a543001e0 | |||
0f86c596fa | |||
6c55501252 | |||
961edfd21a | |||
7f0de705a3 | |||
c2ad65a61d | |||
3c93e0c654 | |||
a0f1ff7eb3 | |||
9f45cfe492 | |||
46e6641528 | |||
a3f124685a | |||
800f711cc1 | |||
7be4b1f399 | |||
eeae0ddab4 | |||
c1af59c618 | |||
d8a9606162 | |||
8038a52287 | |||
c273b7ac3f | |||
df21892b5b | |||
a255a6fb69 | |||
b693d5491b | |||
56a31ea0ee | |||
2a269f1111 | |||
fee30d6f47 | |||
476b1fb36a | |||
3b93bff602 | |||
d292c5023f | |||
ef6f310060 | |||
a6cbdde43f | |||
cbf4c61eb5 | |||
280c8d87f8 | |||
83bf852192 | |||
9d39f441e9 | |||
03d851680b | |||
0ddd018214 | |||
8219a7aebe | |||
6c906b08e1 | |||
220cbe40e5 | |||
74d0656d27 | |||
17c27ef42d | |||
b2e771df2c | |||
a5a36ff194 | |||
97e2bcd055 | |||
23cfa649f4 | |||
8791c0f880 | |||
16c212f853 | |||
3ee228005d | |||
7a743fa6b5 | |||
44e3df82f6 | |||
8d8f6bedad | |||
9e751de707 | |||
b16419348e | |||
3ccaa04575 | |||
d80b84c915 | |||
145010a2b0 | |||
3e98177fad | |||
d24752d9bc | |||
92fe2e96de | |||
3cf54bc0fd | |||
86dd732704 | |||
dfd8f1058e | |||
f5636f321b | |||
ae6c9cb7fa | |||
32052c2750 | |||
7d6deab9fb | |||
9e61c67128 | |||
13bb5f20d2 | |||
d14991ec96 | |||
45dec8d0c0 | |||
90e3aaaf8a | |||
4f7d45ee9c | |||
e1d2536d85 | |||
65700281e8 | |||
80f6b93714 | |||
5585465859 | |||
368103dd09 | |||
df7ffe14f2 | |||
36161aba99 | |||
9f5a91ae3c | |||
4e61e0db34 | |||
2dfccdd924 | |||
4358da9926 | |||
62fb75ff95 | |||
29a0feb415 | |||
dcc5eb7ace | |||
81f4e12a27 | |||
2f60a4b89d | |||
b03c7051ff | |||
8fff2c7595 | |||
052d5f0bc5 | |||
68cf32e848 | |||
a56e5113ee | |||
5127b85672 | |||
2d80c5053d | |||
d46854b315 | |||
47f836cd1b | |||
449709dd7e | |||
5a25fd95f5 | |||
b942bcc4a6 | |||
1107fdec9d | |||
04515e4697 | |||
93d99b5a49 | |||
e49910cdab | |||
e8a1850c79 | |||
afb81b6b8f | |||
4866a26578 | |||
2d75ced4ed | |||
7bcc258b09 | |||
d5fa0a9418 | |||
ce6d237cc1 | |||
70caa2552b | |||
ee7d58dd7f | |||
c4f4cadb43 | |||
978091cedb | |||
8198f5e10a | |||
6cd40df387 | |||
35ee5d36d8 | |||
e7ec0f9fd7 | |||
f4a47ef71e | |||
6b1a79fab8 | |||
ab73da4a1a | |||
e0c8da567c | |||
c10dedf7e4 | |||
ec192e0ab1 | |||
6d792d9948 | |||
1fe4315c94 | |||
381b90e9a1 | |||
2d18dba40a | |||
d2693d58a8 | |||
84bf282c17 | |||
b15b5e5246 | |||
52b3b0c362 | |||
64c4cefa8f | |||
7e8b231f57 | |||
8a344d0c94 | |||
4096089a3f | |||
b16f2d5f05 | |||
5baf15822a | |||
5368ce823e | |||
4effdf065b | |||
61970ab190 | |||
484b00a0f9 | |||
73bf2068aa | |||
1cda949204 | |||
ad6b823255 | |||
0f064db31d | |||
fd0bb54469 | |||
e27bbaa55c | |||
8a50eae1e2 | |||
38080f67b3 | |||
08504e0892 | |||
401c0ad809 | |||
b4b0deb7fa | |||
05ff35d383 | |||
29c3e8f7ea | |||
6657446433 | |||
46b9a9c887 | |||
b3cdb472d0 | |||
31e1aab9a4 | |||
67f383f346 | |||
49f5c335f6 | |||
692e11a584 | |||
208117ca6f | |||
3e276ac921 | |||
4af115a19c | |||
051703eb2c | |||
31fbbd3168 | |||
fee1e255ac | |||
a4c933e56e | |||
9ddf5a3550 | |||
9ab0fa604d | |||
6c709b33cc | |||
71b4c07ea4 | |||
ac9eba8261 | |||
cad55f9c80 | |||
4263574a58 | |||
84ef5ee410 | |||
598fb9190d | |||
9a404a0c03 | |||
3dd8fdf450 | |||
05f5ba0084 | |||
8169149554 | |||
8d1de6c497 | |||
caaace82e3 | |||
02dd5375a9 | |||
717602472a | |||
b56be8e571 | |||
2853086463 | |||
e2107ec6f4 | |||
c33caddf57 | |||
db1e04e418 | |||
f8b8fe3865 | |||
1c6ddfd34c | |||
49e007ff2a | |||
2068eee669 | |||
f3c63e631a | |||
3f0803a7d3 | |||
f12b613211 | |||
695c052c58 | |||
63634be542 | |||
f88f1c65b6 | |||
a0b589eb96 | |||
ebdc983dfe | |||
395243a539 | |||
1ab676d7eb | |||
47f01e5b7e | |||
ffb89935b6 | |||
77a111b95c | |||
6c0fb3a7d2 | |||
824244622f | |||
42d2a29b1d | |||
af8875f6ab | |||
1db1ce1ca3 | |||
f55ef3a059 | |||
67bf0ae79f | |||
b06cf32329 | |||
7cce29b633 | |||
c26d9545a5 | |||
b950d6997d | |||
0bf29a522b | |||
24342fb745 | |||
0278e364ec | |||
b9d6bbd357 | |||
5816ecd1bc | |||
2ff55ee1c5 | |||
b42de6c41f | |||
9e0e081c90 | |||
178f5a104e | |||
1e42f9575a | |||
24dfcf1303 | |||
6cc3aaef1b | |||
436a16a2c8 | |||
9afad5885b | |||
04d0abb3c7 | |||
1e5daa1de8 | |||
d3c859f9f3 | |||
c1419413aa | |||
acd33cccbb | |||
57a1d68f89 | |||
5c88441cd7 | |||
6a3c5c4ce0 | |||
14a511bdad | |||
e4ed53d691 | |||
5bf4f3be8b | |||
6b9e51740b | |||
be7e8d159b | |||
ceb97cd6b9 | |||
85b650048d | |||
a0e6313d56 | |||
9cc6f6b1e4 | |||
526753ee88 | |||
779e773185 | |||
7eb310f8ce | |||
d573cf2d97 | |||
32b5544ad9 | |||
e182ed33b1 | |||
6f1836f80e | |||
5b530f11b5 | |||
0c30057c8c | |||
6078344ecc | |||
67f5a949a4 | |||
05e49e893e | |||
c8844425ad | |||
b282ec106e | |||
ea2a8f6908 | |||
2b942ec5f2 | |||
bf77be0337 | |||
c2741054bb | |||
e708f51156 | |||
cbb821148b | |||
d6b021e185 | |||
0adb7e8553 | |||
dbfa1f0ac8 | |||
11347e3c7d | |||
631fe72a46 | |||
f673dba759 | |||
ab978a18ff | |||
327df159c6 | |||
2ccbd5fa18 | |||
058630d041 | |||
f456be0309 | |||
9bd6cb03ac | |||
16afeda79c | |||
83fcdfd91f | |||
8f94ae41cc | |||
4e41347de8 | |||
6acb6dd4e7 | |||
791a980e2d | |||
c2d8abcee7 | |||
16c05f07ba | |||
2158ad29ee | |||
feba5aeffd | |||
343888017e | |||
3a5d445b2f | |||
e60acb7607 | |||
bebfc6c9b5 | |||
3b2928a391 | |||
10f57dac31 | |||
b640b49b05 | |||
1fea4bd9a6 | |||
206c4e581a | |||
4e13505b92 | |||
5b6d7cddbf | |||
4aaf9f08f8 | |||
b0ba23ff55 | |||
42b19b1819 | |||
0335fde3f9 | |||
f27edbff89 | |||
d62d6e68e0 | |||
1284264511 | |||
d977fe563b | |||
bb68f9dd90 | |||
313396d9b5 | |||
171a23561e | |||
67f33a4760 | |||
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
|
PROJECT_NAME: actix
|
||||||
matrix:
|
matrix:
|
||||||
# Stable channel
|
# 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
|
- TARGET: i686-pc-windows-gnu
|
||||||
CHANNEL: stable
|
CHANNEL: stable
|
||||||
- TARGET: i686-pc-windows-msvc
|
- TARGET: i686-pc-windows-msvc
|
||||||
@ -22,17 +31,23 @@ environment:
|
|||||||
CHANNEL: beta
|
CHANNEL: beta
|
||||||
# Nightly channel
|
# Nightly channel
|
||||||
- TARGET: i686-pc-windows-gnu
|
- TARGET: i686-pc-windows-gnu
|
||||||
CHANNEL: nightly
|
CHANNEL: nightly-2017-12-21
|
||||||
- TARGET: i686-pc-windows-msvc
|
- TARGET: i686-pc-windows-msvc
|
||||||
CHANNEL: nightly
|
CHANNEL: nightly-2017-12-21
|
||||||
- TARGET: x86_64-pc-windows-gnu
|
- TARGET: x86_64-pc-windows-gnu
|
||||||
CHANNEL: nightly
|
CHANNEL: nightly-2017-12-21
|
||||||
- TARGET: x86_64-pc-windows-msvc
|
- TARGET: x86_64-pc-windows-msvc
|
||||||
CHANNEL: nightly
|
CHANNEL: nightly-2017-12-21
|
||||||
|
|
||||||
# Install Rust and Cargo
|
# Install Rust and Cargo
|
||||||
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
|
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
|
||||||
install:
|
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
|
- curl -sSf -o rustup-init.exe https://win.rustup.rs
|
||||||
- rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
|
- rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
|
||||||
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||||
@ -44,6 +59,4 @@ build: false
|
|||||||
|
|
||||||
# Equivalent to Travis' `script` phase
|
# Equivalent to Travis' `script` phase
|
||||||
test_script:
|
test_script:
|
||||||
- cargo build --no-default-features
|
|
||||||
- cargo clean
|
|
||||||
- cargo test --no-default-features
|
- cargo test --no-default-features
|
||||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,7 +1,7 @@
|
|||||||
target/
|
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
target/
|
||||||
|
guide/build/
|
||||||
/gh-pages
|
/gh-pages
|
||||||
__pycache__
|
|
||||||
|
|
||||||
*.so
|
*.so
|
||||||
*.out
|
*.out
|
||||||
@ -9,7 +9,6 @@ __pycache__
|
|||||||
*.pid
|
*.pid
|
||||||
*.sock
|
*.sock
|
||||||
*~
|
*~
|
||||||
*.egg-info/
|
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
98
.travis.yml
98
.travis.yml
@ -1,28 +1,35 @@
|
|||||||
language: rust
|
language: rust
|
||||||
|
sudo: false
|
||||||
rust:
|
|
||||||
- 1.20.0
|
|
||||||
- stable
|
|
||||||
- beta
|
|
||||||
- nightly
|
|
||||||
|
|
||||||
sudo: required
|
|
||||||
dist: trusty
|
dist: trusty
|
||||||
|
|
||||||
|
cache:
|
||||||
|
cargo: true
|
||||||
|
apt: true
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- rust: 1.21.0
|
||||||
|
- rust: stable
|
||||||
|
- rust: beta
|
||||||
|
- rust: nightly
|
||||||
|
allow_failures:
|
||||||
|
- rust: nightly
|
||||||
|
|
||||||
|
#rust:
|
||||||
|
# - 1.21.0
|
||||||
|
# - stable
|
||||||
|
# - beta
|
||||||
|
# - nightly-2018-01-03
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- RUSTFLAGS="-C link-dead-code"
|
# - RUSTFLAGS="-C link-dead-code"
|
||||||
|
- OPENSSL_VERSION=openssl-1.0.2
|
||||||
|
|
||||||
addons:
|
before_install:
|
||||||
apt:
|
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
|
||||||
packages:
|
- sudo apt-get update -qq
|
||||||
- libcurl4-openssl-dev
|
- sudo apt-get install -qq libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
|
||||||
- libelf-dev
|
|
||||||
- libdw-dev
|
|
||||||
- cmake
|
|
||||||
- gcc
|
|
||||||
- binutils-dev
|
|
||||||
- libiberty-dev
|
|
||||||
|
|
||||||
# Add clippy
|
# Add clippy
|
||||||
before_script:
|
before_script:
|
||||||
@ -33,7 +40,34 @@ before_script:
|
|||||||
- export PATH=$PATH:~/.cargo/bin
|
- export PATH=$PATH:~/.cargo/bin
|
||||||
|
|
||||||
script:
|
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 -- --nocapture
|
||||||
|
# --features=alpn
|
||||||
|
fi
|
||||||
|
|
||||||
|
- |
|
||||||
|
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
||||||
|
cd examples/basics && cargo check && cd ../..
|
||||||
|
cd examples/hello-world && cargo check && cd ../..
|
||||||
|
cd examples/http-proxy && cargo check && cd ../..
|
||||||
|
cd examples/multipart && cargo check && cd ../..
|
||||||
|
cd examples/json && cargo check && cd ../..
|
||||||
|
cd examples/juniper && cargo check && cd ../..
|
||||||
|
cd examples/protobuf && 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 ../..
|
||||||
|
cd examples/unix-socket && cargo check && cd ../..
|
||||||
|
fi
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then
|
if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then
|
||||||
cargo clippy
|
cargo clippy
|
||||||
@ -42,28 +76,20 @@ script:
|
|||||||
# Upload docs
|
# Upload docs
|
||||||
after_success:
|
after_success:
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then
|
||||||
cargo doc --no-deps &&
|
cargo doc --features "alpn, tls, session" --no-deps &&
|
||||||
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
||||||
|
curl -sL https://github.com/rust-lang-nursery/mdBook/releases/download/v0.1.2/mdbook-v0.1.2-x86_64-unknown-linux-gnu.tar.gz | tar xvz -C $HOME/.cargo/bin &&
|
||||||
|
cd guide && mdbook build -d ../target/doc/guide && cd .. &&
|
||||||
git clone https://github.com/davisp/ghp-import.git &&
|
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 &&
|
./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"
|
echo "Uploaded documentation"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
||||||
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
|
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
|
||||||
tar xzf master.tar.gz &&
|
USE_SKEPTIC=1 cargo tarpaulin --out Xml
|
||||||
cd kcov-master &&
|
bash <(curl -s https://codecov.io/bash)
|
||||||
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) &&
|
|
||||||
echo "Uploaded code coverage"
|
echo "Uploaded code coverage"
|
||||||
fi
|
fi
|
||||||
|
248
CHANGES.md
248
CHANGES.md
@ -1,4 +1,250 @@
|
|||||||
# CHANGES
|
# Changes
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.1 (2018-04-12)
|
||||||
|
|
||||||
|
* Client connector provides stats, `ClientConnector::stats()`
|
||||||
|
|
||||||
|
* Fix end-of-stream handling in parse_payload #173
|
||||||
|
|
||||||
|
* Fix StaticFiles generate a lot of threads #174
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0 (2018-04-10)
|
||||||
|
|
||||||
|
* Type-safe path/query/form parameter handling, using serde #70
|
||||||
|
|
||||||
|
* HttpResponse builder's methods `.body()`, `.finish()`, `.json()`
|
||||||
|
return `HttpResponse` instead of `Result`
|
||||||
|
|
||||||
|
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()`
|
||||||
|
|
||||||
|
* Added `signed` and `private` `CookieSessionBackend`s
|
||||||
|
|
||||||
|
* Added `HttpRequest::resource()`, returns current matched resource
|
||||||
|
|
||||||
|
* Added `ErrorHandlers` middleware
|
||||||
|
|
||||||
|
* Fix router cannot parse Non-ASCII characters in URL #137
|
||||||
|
|
||||||
|
* Fix client connection pooling
|
||||||
|
|
||||||
|
* Fix long client urls #129
|
||||||
|
|
||||||
|
* Fix panic on invalid URL characters #130
|
||||||
|
|
||||||
|
* Fix logger request duration calculation #152
|
||||||
|
|
||||||
|
* Fix prefix and static file serving #168
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.10 (2018-03-20)
|
||||||
|
|
||||||
|
* Use `Error` instead of `InternalError` for `error::ErrorXXXX` methods
|
||||||
|
|
||||||
|
* Allow to set client request timeout
|
||||||
|
|
||||||
|
* Allow to set client websocket handshake timeout
|
||||||
|
|
||||||
|
* Refactor `TestServer` configuration
|
||||||
|
|
||||||
|
* Fix server websockets big payloads support
|
||||||
|
|
||||||
|
* Fix http/2 date header generation
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.9 (2018-03-16)
|
||||||
|
|
||||||
|
* Allow to disable http/2 support
|
||||||
|
|
||||||
|
* Wake payload reading task when data is available
|
||||||
|
|
||||||
|
* Fix server keep-alive handling
|
||||||
|
|
||||||
|
* Send Query Parameters in client requests #120
|
||||||
|
|
||||||
|
* Move brotli encoding to a feature
|
||||||
|
|
||||||
|
* Add option of default handler for `StaticFiles` handler #57
|
||||||
|
|
||||||
|
* Add basic client connection pooling
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.8 (2018-03-12)
|
||||||
|
|
||||||
|
* Allow to set read buffer capacity for server request
|
||||||
|
|
||||||
|
* Handle WouldBlock error for socket accept call
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.7 (2018-03-11)
|
||||||
|
|
||||||
|
* Fix panic on unknown content encoding
|
||||||
|
|
||||||
|
* Fix connection get closed too early
|
||||||
|
|
||||||
|
* Fix streaming response handling for http/2
|
||||||
|
|
||||||
|
* Better sleep on error support
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.6 (2018-03-10)
|
||||||
|
|
||||||
|
* Fix client cookie handling
|
||||||
|
|
||||||
|
* Fix json content type detection
|
||||||
|
|
||||||
|
* Fix CORS middleware #117
|
||||||
|
|
||||||
|
* Optimize websockets stream support
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.5 (2018-03-07)
|
||||||
|
|
||||||
|
* Fix compression #103 and #104
|
||||||
|
|
||||||
|
* Fix client cookie handling #111
|
||||||
|
|
||||||
|
* Non-blocking processing of a `NamedFile`
|
||||||
|
|
||||||
|
* Enable compression support for `NamedFile`
|
||||||
|
|
||||||
|
* Better support for `NamedFile` type
|
||||||
|
|
||||||
|
* Add `ResponseError` impl for `SendRequestError`. This improves ergonomics of the client.
|
||||||
|
|
||||||
|
* Add native-tls support for client
|
||||||
|
|
||||||
|
* Allow client connection timeout to be set #108
|
||||||
|
|
||||||
|
* Allow to use std::net::TcpListener for HttpServer
|
||||||
|
|
||||||
|
* Handle panics in worker threads
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.4 (2018-03-04)
|
||||||
|
|
||||||
|
* Allow to use Arc<Vec<u8>> as response/request body
|
||||||
|
|
||||||
|
* Fix handling of requests with an encoded body with a length > 8192 #93
|
||||||
|
|
||||||
|
## 0.4.3 (2018-03-03)
|
||||||
|
|
||||||
|
* Fix request body read bug
|
||||||
|
|
||||||
|
* Fix segmentation fault #79
|
||||||
|
|
||||||
|
* Set reuse address before bind #90
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.2 (2018-03-02)
|
||||||
|
|
||||||
|
* Better naming for websockets implementation
|
||||||
|
|
||||||
|
* Add `Pattern::with_prefix()`, make it more usable outside of actix
|
||||||
|
|
||||||
|
* Add csrf middleware for filter for cross-site request forgery #89
|
||||||
|
|
||||||
|
* Fix disconnect on idle connections
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.1 (2018-03-01)
|
||||||
|
|
||||||
|
* Rename `Route::p()` to `Route::filter()`
|
||||||
|
|
||||||
|
* Better naming for http codes
|
||||||
|
|
||||||
|
* Fix payload parse in situation when socket data is not ready.
|
||||||
|
|
||||||
|
* Fix Session mutable borrow lifetime #87
|
||||||
|
|
||||||
|
|
||||||
|
## 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)
|
## 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/
|
124
Cargo.toml
124
Cargo.toml
@ -1,16 +1,20 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "0.2.0"
|
version = "0.5.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix web framework"
|
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["actor", "http", "web"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://github.com/actix/actix-web"
|
homepage = "https://github.com/actix/actix-web"
|
||||||
repository = "https://github.com/actix/actix-web.git"
|
repository = "https://github.com/actix/actix-web.git"
|
||||||
documentation = "https://docs.rs/actix-web/"
|
documentation = "https://docs.rs/actix-web/"
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous",
|
||||||
license = "Apache-2.0"
|
"web-programming::http-server",
|
||||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
"web-programming::http-client",
|
||||||
|
"web-programming::websocket"]
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
exclude = [".gitignore", ".travis.yml", ".cargo/config",
|
||||||
|
"appveyor.yml", "/examples/**"]
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
@ -23,48 +27,104 @@ name = "actix_web"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["session", "brotli"]
|
||||||
|
|
||||||
# http/2
|
# tls
|
||||||
# http2 = ["h2"]
|
tls = ["native-tls", "tokio-tls"]
|
||||||
|
|
||||||
|
# openssl
|
||||||
|
alpn = ["openssl", "tokio-openssl"]
|
||||||
|
|
||||||
|
# sessions
|
||||||
|
session = ["cookie/secure"]
|
||||||
|
|
||||||
|
# brotli encoding
|
||||||
|
brotli = ["brotli2"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.3"
|
actix = "^0.5.5"
|
||||||
time = "0.1"
|
|
||||||
http = "0.1"
|
|
||||||
httparse = "0.1"
|
|
||||||
http-range = "0.1"
|
|
||||||
mime = "0.3"
|
|
||||||
mime_guess = "1.8"
|
|
||||||
cookie = { version="0.10", features=["percent-encode"] }
|
|
||||||
regex = "0.2"
|
|
||||||
sha1 = "0.2"
|
|
||||||
url = "1.5"
|
|
||||||
percent-encoding = "1.0"
|
|
||||||
|
|
||||||
# tokio
|
base64 = "0.9"
|
||||||
|
bitflags = "1.0"
|
||||||
|
failure = "0.1.1"
|
||||||
|
flate2 = "1.0"
|
||||||
|
h2 = "0.1"
|
||||||
|
http = "^0.1.5"
|
||||||
|
httparse = "1.2"
|
||||||
|
http-range = "0.1"
|
||||||
|
libc = "0.2"
|
||||||
|
log = "0.4"
|
||||||
|
mime = "0.3"
|
||||||
|
mime_guess = "2.0.0-alpha"
|
||||||
|
num_cpus = "1.0"
|
||||||
|
percent-encoding = "1.0"
|
||||||
|
rand = "0.4"
|
||||||
|
regex = "0.2"
|
||||||
|
serde = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde_urlencoded = "0.5"
|
||||||
|
sha1 = "0.6"
|
||||||
|
smallvec = "0.6"
|
||||||
|
time = "0.1"
|
||||||
|
encoding = "0.2"
|
||||||
|
language-tags = "0.2"
|
||||||
|
lazy_static = "1.0"
|
||||||
|
url = { version="1.7", features=["query_encoding"] }
|
||||||
|
cookie = { version="0.10", features=["percent-encode"] }
|
||||||
|
brotli2 = { version="^0.3.2", optional = true }
|
||||||
|
|
||||||
|
# io
|
||||||
|
mio = "^0.6.13"
|
||||||
|
net2 = "0.2"
|
||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
|
byteorder = "1"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
|
futures-cpupool = "0.1"
|
||||||
tokio-io = "0.1"
|
tokio-io = "0.1"
|
||||||
tokio-core = "0.1"
|
tokio-core = "0.1"
|
||||||
# h2 = { git = 'https://github.com/carllerche/h2', optional = true }
|
trust-dns-resolver = "0.8"
|
||||||
|
|
||||||
[dependencies.actix]
|
# native-tls
|
||||||
version = ">=0.3.1"
|
native-tls = { version="0.1", optional = true }
|
||||||
#path = "../actix"
|
tokio-tls = { version="0.1", optional = true }
|
||||||
#git = "https://github.com/actix/actix.git"
|
|
||||||
default-features = false
|
# openssl
|
||||||
features = []
|
openssl = { version="0.10", optional = true }
|
||||||
|
tokio-openssl = { version="0.2", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.4"
|
env_logger = "0.5"
|
||||||
reqwest = "0.8"
|
|
||||||
skeptic = "0.13"
|
skeptic = "0.13"
|
||||||
|
serde_derive = "1.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
skeptic = "0.13"
|
skeptic = "0.13"
|
||||||
|
version_check = "0.1"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
debug = true
|
codegen-units = 1
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"./",
|
||||||
|
"examples/basics",
|
||||||
|
"examples/juniper",
|
||||||
|
"examples/diesel",
|
||||||
|
"examples/r2d2",
|
||||||
|
"examples/json",
|
||||||
|
"examples/protobuf",
|
||||||
|
"examples/hello-world",
|
||||||
|
"examples/http-proxy",
|
||||||
|
"examples/multipart",
|
||||||
|
"examples/state",
|
||||||
|
"examples/redis-session",
|
||||||
|
"examples/template_tera",
|
||||||
|
"examples/tls",
|
||||||
|
"examples/websocket",
|
||||||
|
"examples/websocket-chat",
|
||||||
|
"examples/web-cors/backend",
|
||||||
|
"examples/unix-socket",
|
||||||
|
"tools/wsload/",
|
||||||
|
]
|
||||||
|
25
LICENSE-MIT
Normal file
25
LICENSE-MIT
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2017 Nikolay 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.
|
30
MIGRATION-0.4-0.5.md
Normal file
30
MIGRATION-0.4-0.5.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Migration from 0.4 to 0.5
|
||||||
|
|
||||||
|
* `HttpResponseBuilder::body()`, `.finish()`, `.json()`
|
||||||
|
methods return `HttpResponse` instead of `Result<HttpResponse>`
|
||||||
|
|
||||||
|
* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version`
|
||||||
|
moved to `actix_web::http` module
|
||||||
|
|
||||||
|
* `actix_web::header` moved to `actix_web::http::header`
|
||||||
|
|
||||||
|
* `NormalizePath` moved to `actix_web::http` module
|
||||||
|
|
||||||
|
* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function,
|
||||||
|
shortcut for `actix_web::server::HttpServer::new()`
|
||||||
|
|
||||||
|
* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself
|
||||||
|
|
||||||
|
* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead.
|
||||||
|
|
||||||
|
* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type
|
||||||
|
|
||||||
|
* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()`
|
||||||
|
functions should be used instead
|
||||||
|
|
||||||
|
* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>`
|
||||||
|
instead of `Result<_, http::Error>`
|
||||||
|
|
||||||
|
* `Application` renamed to a `App`
|
||||||
|
|
||||||
|
* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev`
|
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
|
default: test
|
||||||
|
|
||||||
@ -20,3 +20,7 @@ clippy:
|
|||||||
|
|
||||||
doc: build
|
doc: build
|
||||||
cargo doc --no-deps $(CARGO_FLAGS)
|
cargo doc --no-deps $(CARGO_FLAGS)
|
||||||
|
cd guide; mdbook build -d ../target/doc/guide/; cd ..
|
||||||
|
|
||||||
|
book:
|
||||||
|
cd guide; mdbook build -d ../target/doc/guide/; cd ..
|
||||||
|
179
README.md
179
README.md
@ -1,123 +1,86 @@
|
|||||||
# 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 simple, pragmatic and extremely fast web framework for Rust.
|
||||||
|
|
||||||
* [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/)
|
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/actix-web/guide/qs_13.html) protocols
|
||||||
|
* Streaming and pipelining
|
||||||
|
* Keep-alive and slow requests handling
|
||||||
|
* Client/server [WebSockets](https://actix.rs/actix-web/guide/qs_9.html) support
|
||||||
|
* Transparent content compression/decompression (br, gzip, deflate)
|
||||||
|
* Configurable [request routing](https://actix.rs/actix-web/guide/qs_5.html)
|
||||||
|
* Graceful server shutdown
|
||||||
|
* Multipart streams
|
||||||
|
* Static assets
|
||||||
|
* SSL support with OpenSSL or `native-tls`
|
||||||
|
* Middlewares ([Logger](https://actix.rs/actix-web/guide/qs_10.html#logging),
|
||||||
|
[Session](https://actix.rs/actix-web/guide/qs_10.html#user-sessions),
|
||||||
|
[Redis sessions](https://github.com/actix/actix-redis),
|
||||||
|
[DefaultHeaders](https://actix.rs/actix-web/guide/qs_10.html#default-headers),
|
||||||
|
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
|
||||||
|
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
|
||||||
|
* Built on top of [Actix actor framework](https://github.com/actix/actix)
|
||||||
|
|
||||||
|
## Documentation & community resources
|
||||||
|
|
||||||
|
* [User Guide](https://actix.github.io/actix-web/guide/)
|
||||||
|
* [API Documentation (Development)](https://actix.github.io/actix-web/actix_web/)
|
||||||
* [API Documentation (Releases)](https://docs.rs/actix-web/)
|
* [API Documentation (Releases)](https://docs.rs/actix-web/)
|
||||||
|
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||||
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
||||||
* Minimum supported Rust version: 1.20 or later
|
* Minimum supported Rust version: 1.21 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"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example
|
## 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
|
```rust
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
extern crate actix_web;
|
||||||
extern crate env_logger;
|
use actix_web::{http, server, App, Path};
|
||||||
|
|
||||||
use actix::*;
|
fn index(info: Path<(u32, String)>) -> String {
|
||||||
use actix_web::*;
|
format!("Hello {}! id:{}", info.0, info.1)
|
||||||
|
|
||||||
|
|
||||||
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 main() {
|
fn main() {
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
server::new(
|
||||||
let _ = env_logger::init();
|
|| App::new()
|
||||||
let sys = actix::System::new("ws-example");
|
.route("/{id}/{name}/index.html", http::Method::GET, index))
|
||||||
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
HttpServer::new(
|
.run();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### More examples
|
||||||
|
|
||||||
|
* [Basics](https://github.com/actix/actix-web/tree/master/examples/basics/)
|
||||||
|
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/)
|
||||||
|
* [Protobuf support](https://github.com/actix/actix-web/tree/master/examples/protobuf/)
|
||||||
|
* [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/)
|
||||||
|
* [Json](https://github.com/actix/actix-web/tree/master/examples/json/)
|
||||||
|
|
||||||
|
You may consider checking out
|
||||||
|
[this directory](https://github.com/actix/actix-web/tree/master/examples) for more examples.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
35
build.rs
35
build.rs
@ -1,18 +1,49 @@
|
|||||||
extern crate skeptic;
|
extern crate skeptic;
|
||||||
|
extern crate version_check;
|
||||||
|
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn main() {
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-env-changed=USE_SKEPTIC");
|
||||||
|
let f = env::var("OUT_DIR").unwrap() + "/skeptic-tests.rs";
|
||||||
if env::var("USE_SKEPTIC").is_ok() {
|
if env::var("USE_SKEPTIC").is_ok() {
|
||||||
|
let _ = fs::remove_file(f);
|
||||||
// generates doc tests for `README.md`.
|
// 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 {
|
} else {
|
||||||
let f = env::var("OUT_DIR").unwrap() + "/skeptic-tests.rs";
|
|
||||||
let _ = fs::File::create(f);
|
let _ = fs::File::create(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match version_check::is_nightly() {
|
||||||
|
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
|
||||||
|
Some(false) => (),
|
||||||
|
None => (),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
fn main() {
|
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 = "0.1"
|
||||||
|
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
|
136
examples/basics/src/main.rs
Normal file
136
examples/basics/src/main.rs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
#![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::{error, fs, pred, server,
|
||||||
|
App, HttpRequest, HttpResponse, Result, Error};
|
||||||
|
use actix_web::http::{header, Method, StatusCode};
|
||||||
|
use actix_web::middleware::{self, 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)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// response
|
||||||
|
Ok(HttpResponse::build(StatusCode::OK)
|
||||||
|
.content_type("text/html; charset=utf-8")
|
||||||
|
.body(include_str!("../static/welcome.html")))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 404 handler
|
||||||
|
fn p404(req: HttpRequest) -> Result<fs::NamedFile> {
|
||||||
|
Ok(fs::NamedFile::open("./static/404.html")?
|
||||||
|
.set_status_code(StatusCode::NOT_FOUND))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// async handler
|
||||||
|
fn index_async(req: HttpRequest) -> FutureResult<HttpResponse, Error>
|
||||||
|
{
|
||||||
|
println!("{:?}", req);
|
||||||
|
|
||||||
|
result(Ok(HttpResponse::Ok()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// handler with path parameters like `/user/{name}/`
|
||||||
|
fn with_param(req: HttpRequest) -> HttpResponse
|
||||||
|
{
|
||||||
|
println!("{:?}", req);
|
||||||
|
|
||||||
|
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 = server::new(
|
||||||
|
|| App::new()
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
// cookie session middleware
|
||||||
|
.middleware(middleware::SessionStorage::new(
|
||||||
|
middleware::CookieSessionBackend::signed(&[0; 32]).secure(false)
|
||||||
|
))
|
||||||
|
// 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 => HttpResponse::Ok(),
|
||||||
|
Method::POST => HttpResponse::MethodNotAllowed(),
|
||||||
|
_ => HttpResponse::NotFound(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.resource("/error.html", |r| r.f(|req| {
|
||||||
|
error::InternalError::new(
|
||||||
|
io::Error::new(io::ErrorKind::Other, "test"), StatusCode::OK)
|
||||||
|
}))
|
||||||
|
// static files
|
||||||
|
.handler("/static/", fs::StaticFiles::new("../static/"))
|
||||||
|
// redirect
|
||||||
|
.resource("/", |r| r.method(Method::GET).f(|req| {
|
||||||
|
println!("{:?}", req);
|
||||||
|
HttpResponse::Found()
|
||||||
|
.header(header::LOCATION, "/index.html")
|
||||||
|
.finish()
|
||||||
|
}))
|
||||||
|
// default
|
||||||
|
.default_resource(|r| {
|
||||||
|
// 404 for GET request
|
||||||
|
r.method(Method::GET).f(p404);
|
||||||
|
|
||||||
|
// all requests that are not `GET`
|
||||||
|
r.route().filter(pred::Not(pred::Get())).f(
|
||||||
|
|req| HttpResponse::MethodNotAllowed());
|
||||||
|
}))
|
||||||
|
|
||||||
|
.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();
|
||||||
|
}
|
7
examples/basics/static/404.html
Normal file
7
examples/basics/static/404.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<!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>
|
6
examples/basics/static/welcome.html
Normal file
6
examples/basics/static/welcome.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<!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>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
examples/diesel/.env
Normal file
1
examples/diesel/.env
Normal file
@ -0,0 +1 @@
|
|||||||
|
DATABASE_URL=file:test.db
|
20
examples/diesel/Cargo.toml
Normal file
20
examples/diesel/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[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.1.0", features = ["sqlite", "r2d2"] }
|
||||||
|
r2d2 = "0.8"
|
||||||
|
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
|
||||||
|
)
|
55
examples/diesel/src/db.rs
Normal file
55
examples/diesel/src/db.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
//! Db executor actor
|
||||||
|
use uuid;
|
||||||
|
use diesel;
|
||||||
|
use actix_web::*;
|
||||||
|
use actix::prelude::*;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel::r2d2::{Pool, ConnectionManager};
|
||||||
|
|
||||||
|
use models;
|
||||||
|
use schema;
|
||||||
|
|
||||||
|
/// This is db executor actor. We are going to run 3 of them in parallel.
|
||||||
|
pub struct DbExecutor(pub Pool<ConnectionManager<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,
|
||||||
|
};
|
||||||
|
|
||||||
|
let conn: &SqliteConnection = &self.0.get().unwrap();
|
||||||
|
|
||||||
|
diesel::insert_into(users)
|
||||||
|
.values(&new_user)
|
||||||
|
.execute(conn)
|
||||||
|
.expect("Error inserting person");
|
||||||
|
|
||||||
|
let mut items = users
|
||||||
|
.filter(id.eq(&uuid))
|
||||||
|
.load::<models::User>(conn)
|
||||||
|
.expect("Error loading person");
|
||||||
|
|
||||||
|
Ok(items.pop().unwrap())
|
||||||
|
}
|
||||||
|
}
|
78
examples/diesel/src/main.rs
Normal file
78
examples/diesel/src/main.rs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
//! 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 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 r2d2;
|
||||||
|
extern crate uuid;
|
||||||
|
extern crate futures;
|
||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
|
extern crate env_logger;
|
||||||
|
|
||||||
|
use actix::prelude::*;
|
||||||
|
use actix_web::{http, server, middleware,
|
||||||
|
App, Path, State, HttpResponse, AsyncResponder, FutureResponse};
|
||||||
|
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel::r2d2::{ Pool, ConnectionManager };
|
||||||
|
use futures::future::Future;
|
||||||
|
|
||||||
|
mod db;
|
||||||
|
mod models;
|
||||||
|
mod schema;
|
||||||
|
|
||||||
|
use db::{CreateUser, DbExecutor};
|
||||||
|
|
||||||
|
|
||||||
|
/// State with DbExecutor address
|
||||||
|
struct AppState {
|
||||||
|
db: Addr<Syn, DbExecutor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Async request handler
|
||||||
|
fn index(name: Path<String>, state: State<AppState>) -> FutureResponse<HttpResponse> {
|
||||||
|
// send async `CreateUser` message to a `DbExecutor`
|
||||||
|
state.db.send(CreateUser{name: name.into_inner()})
|
||||||
|
.from_err()
|
||||||
|
.and_then(|res| {
|
||||||
|
match res {
|
||||||
|
Ok(user) => Ok(HttpResponse::Ok().json(user)),
|
||||||
|
Err(_) => Ok(HttpResponse::InternalServerError().into())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
env_logger::init();
|
||||||
|
let sys = actix::System::new("diesel-example");
|
||||||
|
|
||||||
|
// Start 3 db executor actors
|
||||||
|
let manager = ConnectionManager::<SqliteConnection>::new("test.db");
|
||||||
|
let pool = r2d2::Pool::builder().build(manager).expect("Failed to create pool.");
|
||||||
|
|
||||||
|
let addr = SyncArbiter::start(3, move || {
|
||||||
|
DbExecutor(pool.clone())
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start http server
|
||||||
|
server::new(move || {
|
||||||
|
App::with_state(AppState{db: addr.clone()})
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
.resource("/{name}", |r| r.method(http::Method::GET).with2(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::{App, HttpRequest, server, middleware};
|
||||||
|
|
||||||
|
|
||||||
|
fn index(_req: HttpRequest) -> &'static str {
|
||||||
|
"Hello world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
env_logger::init();
|
||||||
|
let sys = actix::System::new("hello-world");
|
||||||
|
|
||||||
|
server::new(
|
||||||
|
|| App::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();
|
||||||
|
}
|
11
examples/http-proxy/Cargo.toml
Normal file
11
examples/http-proxy/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "http-proxy"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
workspace = "../.."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
env_logger = "0.5"
|
||||||
|
futures = "0.1"
|
||||||
|
actix = "0.5"
|
||||||
|
actix-web = { path = "../../", features=["alpn"] }
|
58
examples/http-proxy/src/main.rs
Normal file
58
examples/http-proxy/src/main.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
|
extern crate futures;
|
||||||
|
extern crate env_logger;
|
||||||
|
|
||||||
|
use futures::{Future, Stream};
|
||||||
|
use actix_web::{
|
||||||
|
client, server, middleware,
|
||||||
|
App, AsyncResponder, Body, HttpRequest, HttpResponse, HttpMessage, Error};
|
||||||
|
|
||||||
|
|
||||||
|
/// Stream client request response and then send body to a server response
|
||||||
|
fn index(_req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
client::ClientRequest::get("https://www.rust-lang.org/en-US/")
|
||||||
|
.finish().unwrap()
|
||||||
|
.send()
|
||||||
|
.map_err(Error::from) // <- convert SendRequestError to an Error
|
||||||
|
.and_then(
|
||||||
|
|resp| resp.body() // <- this is MessageBody type, resolves to complete body
|
||||||
|
.from_err() // <- convert PayloadError to a Error
|
||||||
|
.and_then(|body| { // <- we got complete body, now send as server response
|
||||||
|
Ok(HttpResponse::Ok().body(body))
|
||||||
|
}))
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// streaming client request to a streaming server response
|
||||||
|
fn streaming(_req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
// send client request
|
||||||
|
client::ClientRequest::get("https://www.rust-lang.org/en-US/")
|
||||||
|
.finish().unwrap()
|
||||||
|
.send() // <- connect to host and send request
|
||||||
|
.map_err(Error::from) // <- convert SendRequestError to an Error
|
||||||
|
.and_then(|resp| { // <- we received client response
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
// read one chunk from client response and send this chunk to a server response
|
||||||
|
// .from_err() converts PayloadError to a Error
|
||||||
|
.body(Body::Streaming(Box::new(resp.from_err()))))
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
env_logger::init();
|
||||||
|
let sys = actix::System::new("http-proxy");
|
||||||
|
|
||||||
|
server::new(
|
||||||
|
|| App::new()
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
.resource("/streaming", |r| r.f(streaming))
|
||||||
|
.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())
|
110
examples/json/src/main.rs
Normal file
110
examples/json/src/main.rs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
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::{
|
||||||
|
middleware, http, error, server,
|
||||||
|
App, AsyncResponder, HttpRequest, HttpResponse, HttpMessage, Error, Json};
|
||||||
|
|
||||||
|
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 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(HttpResponse::Ok().json(val)) // <- send response
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This handler uses json extractor
|
||||||
|
fn extract_item(item: Json<MyObj>) -> HttpResponse {
|
||||||
|
println!("model: {:?}", &item);
|
||||||
|
HttpResponse::Ok().json(item.0) // <- send response
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_SIZE: usize = 262_144; // max payload size is 256k
|
||||||
|
|
||||||
|
/// This handler manually load request payload and parse json object
|
||||||
|
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(HttpResponse::Ok().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::Ok()
|
||||||
|
.content_type("application/json")
|
||||||
|
.body(injson.dump()))
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
env_logger::init();
|
||||||
|
let sys = actix::System::new("json-example");
|
||||||
|
|
||||||
|
server::new(|| {
|
||||||
|
App::new()
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
.resource("/extractor", |r| {
|
||||||
|
r.method(http::Method::POST)
|
||||||
|
.with(extract_item)
|
||||||
|
.limit(4096); // <- limit size of the payload
|
||||||
|
})
|
||||||
|
.resource("/manual", |r| r.method(http::Method::POST).f(index_manual))
|
||||||
|
.resource("/mjsonrust", |r| r.method(http::Method::POST).f(index_mjsonrust))
|
||||||
|
.resource("/", |r| r.method(http::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)
|
108
examples/juniper/src/main.rs
Normal file
108
examples/juniper/src/main.rs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
//! 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::prelude::*;
|
||||||
|
use actix_web::{
|
||||||
|
middleware, http, server,
|
||||||
|
App, AsyncResponder, HttpRequest, HttpResponse, FutureResponse, Error, State, Json};
|
||||||
|
use juniper::http::graphiql::graphiql_source;
|
||||||
|
use juniper::http::GraphQLRequest;
|
||||||
|
use futures::future::Future;
|
||||||
|
|
||||||
|
mod schema;
|
||||||
|
|
||||||
|
use schema::Schema;
|
||||||
|
use schema::create_schema;
|
||||||
|
|
||||||
|
struct AppState {
|
||||||
|
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<AppState>) -> Result<HttpResponse, Error> {
|
||||||
|
let html = graphiql_source("http://127.0.0.1:8080/graphql");
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type("text/html; charset=utf-8")
|
||||||
|
.body(html))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn graphql(st: State<AppState>, data: Json<GraphQLData>) -> FutureResponse<HttpResponse> {
|
||||||
|
st.executor.send(data.0)
|
||||||
|
.from_err()
|
||||||
|
.and_then(|res| {
|
||||||
|
match res {
|
||||||
|
Ok(user) => Ok(HttpResponse::Ok()
|
||||||
|
.content_type("application/json")
|
||||||
|
.body(user)),
|
||||||
|
Err(_) => Ok(HttpResponse::InternalServerError().into())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
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
|
||||||
|
server::new(move || {
|
||||||
|
App::with_state(AppState{executor: addr.clone()})
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
.resource("/graphql", |r| r.method(http::Method::POST).with2(graphql))
|
||||||
|
.resource("/graphiql", |r| r.method(http::Method::GET).h(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"
|
name = "multipart-example"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
workspace = "../.."
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "multipart"
|
name = "multipart"
|
||||||
@ -9,6 +10,6 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "*"
|
env_logger = "*"
|
||||||
#actix = "0.3"
|
futures = "0.1"
|
||||||
actix = { git = "https://github.com/actix/actix.git" }
|
actix = "0.5"
|
||||||
actix-web = { path = "../../" }
|
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 asyncio
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
|
async def req1():
|
||||||
def req1():
|
|
||||||
with aiohttp.MultipartWriter() as writer:
|
with aiohttp.MultipartWriter() as writer:
|
||||||
writer.append('test')
|
writer.append('test')
|
||||||
writer.append_json({'passed': True})
|
writer.append_json({'passed': True})
|
||||||
|
|
||||||
resp = yield from aiohttp.request(
|
resp = await aiohttp.ClientSession().request(
|
||||||
"post", 'http://localhost:8080/multipart',
|
"post", 'http://localhost:8080/multipart',
|
||||||
data=writer, headers=writer.headers)
|
data=writer, headers=writer.headers)
|
||||||
print(resp)
|
print(resp)
|
||||||
assert 200 == resp.status
|
assert 200 == resp.status
|
||||||
|
|
||||||
|
|
||||||
def req2():
|
async def req2():
|
||||||
with aiohttp.MultipartWriter() as writer:
|
with aiohttp.MultipartWriter() as writer:
|
||||||
writer.append('test')
|
writer.append('test')
|
||||||
writer.append_json({'passed': True})
|
writer.append_json({'passed': True})
|
||||||
writer.append(open('src/main.rs'))
|
writer.append(open('src/main.rs'))
|
||||||
|
|
||||||
resp = yield from aiohttp.request(
|
resp = await aiohttp.ClientSession().request(
|
||||||
"post", 'http://localhost:8080/multipart',
|
"post", 'http://localhost:8080/multipart',
|
||||||
data=writer, headers=writer.headers)
|
data=writer, headers=writer.headers)
|
||||||
print(resp)
|
print(resp)
|
||||||
|
@ -2,77 +2,60 @@
|
|||||||
extern crate actix;
|
extern crate actix;
|
||||||
extern crate actix_web;
|
extern crate actix_web;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
|
extern crate futures;
|
||||||
|
|
||||||
use actix::*;
|
use actix::*;
|
||||||
use actix_web::*;
|
use actix_web::{
|
||||||
|
http, middleware, multipart, server,
|
||||||
|
App, AsyncResponder, HttpRequest, HttpResponse, HttpMessage, Error};
|
||||||
|
|
||||||
struct MyRoute;
|
use futures::{Future, Stream};
|
||||||
|
use futures::future::{result, Either};
|
||||||
|
|
||||||
impl Actor for MyRoute {
|
|
||||||
type Context = HttpContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Route for MyRoute {
|
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>>
|
||||||
type State = ();
|
{
|
||||||
|
println!("{:?}", req);
|
||||||
|
|
||||||
fn request(req: &mut HttpRequest, payload: Payload,
|
req.multipart() // <- get multipart stream for current request
|
||||||
ctx: &mut HttpContext<Self>) -> RouteResult<Self> {
|
.from_err() // <- convert multipart errors
|
||||||
println!("{:?}", req);
|
.and_then(|item| { // <- iterate over multipart items
|
||||||
|
match item {
|
||||||
|
// Handle multipart Field
|
||||||
|
multipart::MultipartItem::Field(field) => {
|
||||||
|
println!("==== FIELD ==== {:?}", field);
|
||||||
|
|
||||||
let multipart = req.multipart(payload)?;
|
// Field in turn is stream of *Bytes* object
|
||||||
|
Either::A(
|
||||||
// get Multipart stream
|
field.map_err(Error::from)
|
||||||
WrapStream::<MyRoute>::actstream(multipart)
|
.map(|chunk| {
|
||||||
.and_then(|item, act, ctx| {
|
println!("-- CHUNK: \n{}",
|
||||||
// Multipart stream is a stream of Fields and nested Multiparts
|
std::str::from_utf8(&chunk).unwrap());})
|
||||||
match item {
|
.finish())
|
||||||
multipart::MultipartItem::Field(field) => {
|
},
|
||||||
println!("==== FIELD ==== {:?}", field);
|
multipart::MultipartItem::Nested(mp) => {
|
||||||
|
// Or item could be nested Multipart stream
|
||||||
// Read field's stream
|
Either::B(result(Ok(())))
|
||||||
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(()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
// wait until stream finish
|
})
|
||||||
.finish()
|
.finish() // <- Stream::finish() combinator from actix
|
||||||
.map_err(|e, act, ctx| {
|
.map(|_| HttpResponse::Ok().into())
|
||||||
ctx.start(httpcodes::HTTPBadRequest);
|
.responder()
|
||||||
ctx.write_eof();
|
|
||||||
})
|
|
||||||
.map(|_, act, ctx| {
|
|
||||||
ctx.start(httpcodes::HTTPOk);
|
|
||||||
ctx.write_eof();
|
|
||||||
})
|
|
||||||
.spawn(ctx);
|
|
||||||
|
|
||||||
Reply::async(MyRoute)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
let _ = env_logger::init();
|
let _ = env_logger::init();
|
||||||
let sys = actix::System::new("multipart-example");
|
let sys = actix::System::new("multipart-example");
|
||||||
|
|
||||||
HttpServer::new(
|
server::new(
|
||||||
vec![
|
|| App::new()
|
||||||
Application::default("/")
|
.middleware(middleware::Logger::default()) // <- logger
|
||||||
.resource("/multipart", |r| {
|
.resource("/multipart", |r| r.method(http::Method::POST).a(index)))
|
||||||
r.post::<MyRoute>();
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
}).finish()
|
.start();
|
||||||
])
|
|
||||||
.serve::<_, ()>("127.0.0.1:8080").unwrap();
|
|
||||||
|
|
||||||
|
println!("Starting http server: 127.0.0.1:8080");
|
||||||
let _ = sys.run();
|
let _ = sys.run();
|
||||||
}
|
}
|
||||||
|
16
examples/protobuf/Cargo.toml
Normal file
16
examples/protobuf/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "protobuf-example"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["kingxsp <jin_hb_zh@126.com>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bytes = "0.4"
|
||||||
|
futures = "0.1"
|
||||||
|
failure = "0.1"
|
||||||
|
env_logger = "*"
|
||||||
|
|
||||||
|
prost = "0.2.0"
|
||||||
|
prost-derive = "0.2.0"
|
||||||
|
|
||||||
|
actix = "0.5"
|
||||||
|
actix-web = { path="../../" }
|
66
examples/protobuf/client.py
Normal file
66
examples/protobuf/client.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# just start server and run client.py
|
||||||
|
|
||||||
|
# wget https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-python-3.5.1.zip
|
||||||
|
# unzip protobuf-python-3.5.1.zip.1
|
||||||
|
# cd protobuf-3.5.1/python/
|
||||||
|
# python3.6 setup.py install
|
||||||
|
|
||||||
|
# pip3.6 install --upgrade pip
|
||||||
|
# pip3.6 install aiohttp
|
||||||
|
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import test_pb2
|
||||||
|
import traceback
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
def op():
|
||||||
|
try:
|
||||||
|
obj = test_pb2.MyObj()
|
||||||
|
obj.number = 9
|
||||||
|
obj.name = 'USB'
|
||||||
|
|
||||||
|
#Serialize
|
||||||
|
sendDataStr = obj.SerializeToString()
|
||||||
|
#print serialized string value
|
||||||
|
print('serialized string:', sendDataStr)
|
||||||
|
#------------------------#
|
||||||
|
# message transmission #
|
||||||
|
#------------------------#
|
||||||
|
receiveDataStr = sendDataStr
|
||||||
|
receiveData = test_pb2.MyObj()
|
||||||
|
|
||||||
|
#Deserialize
|
||||||
|
receiveData.ParseFromString(receiveDataStr)
|
||||||
|
print('pares serialize string, return: devId = ', receiveData.number, ', name = ', receiveData.name)
|
||||||
|
except(Exception, e):
|
||||||
|
print(Exception, ':', e)
|
||||||
|
print(traceback.print_exc())
|
||||||
|
errInfo = sys.exc_info()
|
||||||
|
print(errInfo[0], ':', errInfo[1])
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch(session):
|
||||||
|
obj = test_pb2.MyObj()
|
||||||
|
obj.number = 9
|
||||||
|
obj.name = 'USB'
|
||||||
|
async with session.post('http://localhost:8080/', data=obj.SerializeToString(),
|
||||||
|
headers={"content-type": "application/protobuf"}) as resp:
|
||||||
|
print(resp.status)
|
||||||
|
data = await resp.read()
|
||||||
|
receiveObj = test_pb2.MyObj()
|
||||||
|
receiveObj.ParseFromString(data)
|
||||||
|
print(receiveObj)
|
||||||
|
|
||||||
|
async def go(loop):
|
||||||
|
obj = test_pb2.MyObj()
|
||||||
|
obj.number = 9
|
||||||
|
obj.name = 'USB'
|
||||||
|
async with aiohttp.ClientSession(loop=loop) as session:
|
||||||
|
await fetch(session)
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(go(loop))
|
||||||
|
loop.close()
|
57
examples/protobuf/src/main.rs
Normal file
57
examples/protobuf/src/main.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
|
extern crate bytes;
|
||||||
|
extern crate futures;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate failure;
|
||||||
|
extern crate env_logger;
|
||||||
|
extern crate prost;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate prost_derive;
|
||||||
|
|
||||||
|
use futures::Future;
|
||||||
|
use actix_web::{
|
||||||
|
http, middleware, server,
|
||||||
|
App, AsyncResponder, HttpRequest, HttpResponse, Error};
|
||||||
|
|
||||||
|
mod protobuf;
|
||||||
|
use protobuf::ProtoBufResponseBuilder;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Message)]
|
||||||
|
pub struct MyObj {
|
||||||
|
#[prost(int32, tag="1")]
|
||||||
|
pub number: i32,
|
||||||
|
#[prost(string, tag="2")]
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// This handler uses `ProtoBufMessage` for loading protobuf object.
|
||||||
|
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
protobuf::ProtoBufMessage::new(req)
|
||||||
|
.from_err() // convert all errors into `Error`
|
||||||
|
.and_then(|val: MyObj| {
|
||||||
|
println!("model: {:?}", val);
|
||||||
|
Ok(HttpResponse::Ok().protobuf(val)?) // <- send response
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
env_logger::init();
|
||||||
|
let sys = actix::System::new("protobuf-example");
|
||||||
|
|
||||||
|
server::new(|| {
|
||||||
|
App::new()
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
.resource("/", |r| r.method(http::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();
|
||||||
|
}
|
168
examples/protobuf/src/protobuf.rs
Normal file
168
examples/protobuf/src/protobuf.rs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
use bytes::{Bytes, BytesMut};
|
||||||
|
use futures::{Poll, Future, Stream};
|
||||||
|
|
||||||
|
use bytes::IntoBuf;
|
||||||
|
use prost::Message;
|
||||||
|
use prost::DecodeError as ProtoBufDecodeError;
|
||||||
|
use prost::EncodeError as ProtoBufEncodeError;
|
||||||
|
|
||||||
|
use actix_web::http::header::{CONTENT_TYPE, CONTENT_LENGTH};
|
||||||
|
use actix_web::{Responder, HttpMessage, HttpRequest, HttpResponse};
|
||||||
|
use actix_web::dev::HttpResponseBuilder;
|
||||||
|
use actix_web::error::{Error, PayloadError, ResponseError};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
pub enum ProtoBufPayloadError {
|
||||||
|
/// Payload size is bigger than 256k
|
||||||
|
#[fail(display="Payload size is bigger than 256k")]
|
||||||
|
Overflow,
|
||||||
|
/// Content type error
|
||||||
|
#[fail(display="Content type error")]
|
||||||
|
ContentType,
|
||||||
|
/// Serialize error
|
||||||
|
#[fail(display="ProtoBud serialize error: {}", _0)]
|
||||||
|
Serialize(#[cause] ProtoBufEncodeError),
|
||||||
|
/// Deserialize error
|
||||||
|
#[fail(display="ProtoBud deserialize error: {}", _0)]
|
||||||
|
Deserialize(#[cause] ProtoBufDecodeError),
|
||||||
|
/// Payload error
|
||||||
|
#[fail(display="Error that occur during reading payload: {}", _0)]
|
||||||
|
Payload(#[cause] PayloadError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseError for ProtoBufPayloadError {
|
||||||
|
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
match *self {
|
||||||
|
ProtoBufPayloadError::Overflow => HttpResponse::PayloadTooLarge().into(),
|
||||||
|
_ => HttpResponse::BadRequest().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PayloadError> for ProtoBufPayloadError {
|
||||||
|
fn from(err: PayloadError) -> ProtoBufPayloadError {
|
||||||
|
ProtoBufPayloadError::Payload(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ProtoBufDecodeError> for ProtoBufPayloadError {
|
||||||
|
fn from(err: ProtoBufDecodeError) -> ProtoBufPayloadError {
|
||||||
|
ProtoBufPayloadError::Deserialize(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ProtoBuf<T: Message>(pub T);
|
||||||
|
|
||||||
|
impl<T: Message> Responder for ProtoBuf<T> {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
self.0.encode(&mut buf)
|
||||||
|
.map_err(|e| Error::from(ProtoBufPayloadError::Serialize(e)))
|
||||||
|
.and_then(|()| {
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type("application/protobuf")
|
||||||
|
.body(buf)
|
||||||
|
.into())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ProtoBufMessage<T, U: Message + Default>{
|
||||||
|
limit: usize,
|
||||||
|
ct: &'static str,
|
||||||
|
req: Option<T>,
|
||||||
|
fut: Option<Box<Future<Item=U, Error=ProtoBufPayloadError>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U: Message + Default> ProtoBufMessage<T, U> {
|
||||||
|
|
||||||
|
/// Create `ProtoBufMessage` for request.
|
||||||
|
pub fn new(req: T) -> Self {
|
||||||
|
ProtoBufMessage{
|
||||||
|
limit: 262_144,
|
||||||
|
req: Some(req),
|
||||||
|
fut: None,
|
||||||
|
ct: "application/protobuf",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change max size of payload. By default max size is 256Kb
|
||||||
|
pub fn limit(mut self, limit: usize) -> Self {
|
||||||
|
self.limit = limit;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set allowed content type.
|
||||||
|
///
|
||||||
|
/// By default *application/protobuf* content type is used. Set content type
|
||||||
|
/// to empty string if you want to disable content type check.
|
||||||
|
pub fn content_type(mut self, ct: &'static str) -> Self {
|
||||||
|
self.ct = ct;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U: Message + Default + 'static> Future for ProtoBufMessage<T, U>
|
||||||
|
where T: HttpMessage + Stream<Item=Bytes, Error=PayloadError> + 'static
|
||||||
|
{
|
||||||
|
type Item = U;
|
||||||
|
type Error = ProtoBufPayloadError;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<U, ProtoBufPayloadError> {
|
||||||
|
if let Some(req) = self.req.take() {
|
||||||
|
if let Some(len) = req.headers().get(CONTENT_LENGTH) {
|
||||||
|
if let Ok(s) = len.to_str() {
|
||||||
|
if let Ok(len) = s.parse::<usize>() {
|
||||||
|
if len > self.limit {
|
||||||
|
return Err(ProtoBufPayloadError::Overflow);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(ProtoBufPayloadError::Overflow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check content-type
|
||||||
|
if !self.ct.is_empty() && req.content_type() != self.ct {
|
||||||
|
return Err(ProtoBufPayloadError::ContentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
let limit = self.limit;
|
||||||
|
let fut = req.from_err()
|
||||||
|
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||||
|
if (body.len() + chunk.len()) > limit {
|
||||||
|
Err(ProtoBufPayloadError::Overflow)
|
||||||
|
} else {
|
||||||
|
body.extend_from_slice(&chunk);
|
||||||
|
Ok(body)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.and_then(|body| Ok(<U>::decode(&mut body.into_buf())?));
|
||||||
|
self.fut = Some(Box::new(fut));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fut.as_mut().expect("ProtoBufBody could not be used second time").poll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub trait ProtoBufResponseBuilder {
|
||||||
|
|
||||||
|
fn protobuf<T: Message>(&mut self, value: T) -> Result<HttpResponse, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProtoBufResponseBuilder for HttpResponseBuilder {
|
||||||
|
|
||||||
|
fn protobuf<T: Message>(&mut self, value: T) -> Result<HttpResponse, Error> {
|
||||||
|
self.header(CONTENT_TYPE, "application/protobuf");
|
||||||
|
|
||||||
|
let mut body = Vec::new();
|
||||||
|
value.encode(&mut body).map_err(|e| ProtoBufPayloadError::Serialize(e))?;
|
||||||
|
Ok(self.body(body))
|
||||||
|
}
|
||||||
|
}
|
6
examples/protobuf/test.proto
Normal file
6
examples/protobuf/test.proto
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
message MyObj {
|
||||||
|
int32 number = 1;
|
||||||
|
string name = 2;
|
||||||
|
}
|
76
examples/protobuf/test_pb2.py
Normal file
76
examples/protobuf/test_pb2.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||||
|
# source: test.proto
|
||||||
|
|
||||||
|
import sys
|
||||||
|
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
|
||||||
|
from google.protobuf import descriptor as _descriptor
|
||||||
|
from google.protobuf import message as _message
|
||||||
|
from google.protobuf import reflection as _reflection
|
||||||
|
from google.protobuf import symbol_database as _symbol_database
|
||||||
|
from google.protobuf import descriptor_pb2
|
||||||
|
# @@protoc_insertion_point(imports)
|
||||||
|
|
||||||
|
_sym_db = _symbol_database.Default()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DESCRIPTOR = _descriptor.FileDescriptor(
|
||||||
|
name='test.proto',
|
||||||
|
package='',
|
||||||
|
syntax='proto3',
|
||||||
|
serialized_pb=_b('\n\ntest.proto\"%\n\x05MyObj\x12\x0e\n\x06number\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\tb\x06proto3')
|
||||||
|
)
|
||||||
|
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_MYOBJ = _descriptor.Descriptor(
|
||||||
|
name='MyObj',
|
||||||
|
full_name='MyObj',
|
||||||
|
filename=None,
|
||||||
|
file=DESCRIPTOR,
|
||||||
|
containing_type=None,
|
||||||
|
fields=[
|
||||||
|
_descriptor.FieldDescriptor(
|
||||||
|
name='number', full_name='MyObj.number', index=0,
|
||||||
|
number=1, type=5, cpp_type=1, label=1,
|
||||||
|
has_default_value=False, default_value=0,
|
||||||
|
message_type=None, enum_type=None, containing_type=None,
|
||||||
|
is_extension=False, extension_scope=None,
|
||||||
|
options=None),
|
||||||
|
_descriptor.FieldDescriptor(
|
||||||
|
name='name', full_name='MyObj.name', index=1,
|
||||||
|
number=2, type=9, cpp_type=9, label=1,
|
||||||
|
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||||
|
message_type=None, enum_type=None, containing_type=None,
|
||||||
|
is_extension=False, extension_scope=None,
|
||||||
|
options=None),
|
||||||
|
],
|
||||||
|
extensions=[
|
||||||
|
],
|
||||||
|
nested_types=[],
|
||||||
|
enum_types=[
|
||||||
|
],
|
||||||
|
options=None,
|
||||||
|
is_extendable=False,
|
||||||
|
syntax='proto3',
|
||||||
|
extension_ranges=[],
|
||||||
|
oneofs=[
|
||||||
|
],
|
||||||
|
serialized_start=14,
|
||||||
|
serialized_end=51,
|
||||||
|
)
|
||||||
|
|
||||||
|
DESCRIPTOR.message_types_by_name['MyObj'] = _MYOBJ
|
||||||
|
|
||||||
|
MyObj = _reflection.GeneratedProtocolMessageType('MyObj', (_message.Message,), dict(
|
||||||
|
DESCRIPTOR = _MYOBJ,
|
||||||
|
__module__ = 'test_pb2'
|
||||||
|
# @@protoc_insertion_point(class_scope:MyObj)
|
||||||
|
))
|
||||||
|
_sym_db.RegisterMessage(MyObj)
|
||||||
|
|
||||||
|
|
||||||
|
# @@protoc_insertion_point(module_scope)
|
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"))?)
|
||||||
|
}
|
||||||
|
}
|
65
examples/r2d2/src/main.rs
Normal file
65
examples/r2d2/src/main.rs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
//! 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::prelude::*;
|
||||||
|
use actix_web::{
|
||||||
|
middleware, http, server, App, AsyncResponder, HttpRequest, HttpResponse, Error};
|
||||||
|
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(HttpResponse::Ok().json(user)),
|
||||||
|
Err(_) => Ok(HttpResponse::InternalServerError().into())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=debug");
|
||||||
|
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
|
||||||
|
server::new(move || {
|
||||||
|
App::with_state(State{db: addr.clone()})
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
.resource("/{name}", |r| r.method(http::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.
11
examples/redis-session/Cargo.toml
Normal file
11
examples/redis-session/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "redis-session"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
workspace = "../.."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
env_logger = "0.5"
|
||||||
|
actix = "0.5"
|
||||||
|
actix-web = "0.5"
|
||||||
|
actix-redis = { version = "0.3", features = ["web"] }
|
48
examples/redis-session/src/main.rs
Normal file
48
examples/redis-session/src/main.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#![allow(unused_variables)]
|
||||||
|
|
||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
|
extern crate actix_redis;
|
||||||
|
extern crate env_logger;
|
||||||
|
|
||||||
|
use actix_web::{server, App, HttpRequest, HttpResponse, Result};
|
||||||
|
use actix_web::middleware::{Logger, SessionStorage, RequestSession};
|
||||||
|
use actix_redis::RedisSessionBackend;
|
||||||
|
|
||||||
|
|
||||||
|
/// simple handler
|
||||||
|
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
|
||||||
|
println!("{:?}", req);
|
||||||
|
|
||||||
|
// session
|
||||||
|
if let Some(count) = req.session().get::<i32>("counter")? {
|
||||||
|
println!("SESSION value: {}", count);
|
||||||
|
req.session().set("counter", count+1)?;
|
||||||
|
} else {
|
||||||
|
req.session().set("counter", 1)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok("Welcome!".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info");
|
||||||
|
env_logger::init();
|
||||||
|
let sys = actix::System::new("basic-example");
|
||||||
|
|
||||||
|
server::new(
|
||||||
|
|| App::new()
|
||||||
|
// enable logger
|
||||||
|
.middleware(Logger::default())
|
||||||
|
// cookie session middleware
|
||||||
|
.middleware(SessionStorage::new(
|
||||||
|
RedisSessionBackend::new("127.0.0.1:6379", &[0; 32])
|
||||||
|
))
|
||||||
|
// register simple route, handle all methods
|
||||||
|
.resource("/", |r| r.f(index)))
|
||||||
|
.bind("0.0.0.0:8080").unwrap()
|
||||||
|
.threads(1)
|
||||||
|
.start();
|
||||||
|
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
@ -1,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 = "0.1"
|
||||||
|
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/)
|
77
examples/state/src/main.rs
Normal file
77
examples/state/src/main.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#![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::prelude::*;
|
||||||
|
use actix_web::{
|
||||||
|
http, server, ws, middleware, App, HttpRequest, HttpResponse};
|
||||||
|
|
||||||
|
/// Application state
|
||||||
|
struct AppState {
|
||||||
|
counter: Cell<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// simple handle
|
||||||
|
fn index(req: HttpRequest<AppState>) -> HttpResponse {
|
||||||
|
println!("{:?}", req);
|
||||||
|
req.state().counter.set(req.state().counter.get() + 1);
|
||||||
|
|
||||||
|
HttpResponse::Ok().body(format!("Num of requests: {}", req.state().counter.get()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `MyWebSocket` counts how many messages it receives from peer,
|
||||||
|
/// websocket-client.py could be used for tests
|
||||||
|
struct MyWebSocket {
|
||||||
|
counter: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actor for MyWebSocket {
|
||||||
|
type Context = ws::WebsocketContext<Self, AppState>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||||
|
self.counter += 1;
|
||||||
|
println!("WS({}): {:?}", self.counter, msg);
|
||||||
|
match msg {
|
||||||
|
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||||
|
ws::Message::Text(text) => ctx.text(text),
|
||||||
|
ws::Message::Binary(bin) => ctx.binary(bin),
|
||||||
|
ws::Message::Close(_) => {
|
||||||
|
ctx.stop();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
env_logger::init();
|
||||||
|
let sys = actix::System::new("ws-example");
|
||||||
|
|
||||||
|
server::new(
|
||||||
|
|| App::with_state(AppState{counter: Cell::new(0)})
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
// websocket route
|
||||||
|
.resource(
|
||||||
|
"/ws/", |r|
|
||||||
|
r.method(http::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: 13 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)
|
48
examples/template_tera/src/main.rs
Normal file
48
examples/template_tera/src/main.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
|
extern crate env_logger;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate tera;
|
||||||
|
|
||||||
|
use actix_web::{
|
||||||
|
http, error, middleware, server, App, HttpRequest, HttpResponse, Error};
|
||||||
|
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
template: tera::Tera, // <- store tera template in application state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(req: HttpRequest<State>) -> Result<HttpResponse, Error> {
|
||||||
|
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(HttpResponse::Ok()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
env_logger::init();
|
||||||
|
let sys = actix::System::new("tera-example");
|
||||||
|
|
||||||
|
server::new(|| {
|
||||||
|
let tera = compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*"));
|
||||||
|
|
||||||
|
App::with_state(State{template: tera})
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
.resource("/", |r| r.method(http::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-----
|
49
examples/tls/src/main.rs
Normal file
49
examples/tls/src/main.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#![allow(unused_variables)]
|
||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
|
extern crate env_logger;
|
||||||
|
extern crate openssl;
|
||||||
|
|
||||||
|
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
|
||||||
|
use actix_web::{
|
||||||
|
http, middleware, server, App, HttpRequest, HttpResponse, Error};
|
||||||
|
|
||||||
|
|
||||||
|
/// simple handle
|
||||||
|
fn index(req: HttpRequest) -> Result<HttpResponse, Error> {
|
||||||
|
println!("{:?}", req);
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.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");
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
|
||||||
|
server::new(
|
||||||
|
|| App::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(http::Method::GET).f(|req| {
|
||||||
|
HttpResponse::Found()
|
||||||
|
.header("LOCATION", "/index.html")
|
||||||
|
.finish()
|
||||||
|
})))
|
||||||
|
.bind("127.0.0.1:8443").unwrap()
|
||||||
|
.start_ssl(builder).unwrap();
|
||||||
|
|
||||||
|
println!("Started http server: 127.0.0.1:8443");
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
10
examples/unix-socket/Cargo.toml
Normal file
10
examples/unix-socket/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "unix-socket"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Messense Lv <messense@icloud.com>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
env_logger = "0.5"
|
||||||
|
actix = "0.5"
|
||||||
|
actix-web = { path = "../../" }
|
||||||
|
tokio-uds = "0.1"
|
14
examples/unix-socket/README.md
Normal file
14
examples/unix-socket/README.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
## Unix domain socket example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ curl --unix-socket /tmp/actix-uds.socket http://localhost/
|
||||||
|
Hello world!
|
||||||
|
```
|
||||||
|
|
||||||
|
Although this will only one thread for handling incoming connections
|
||||||
|
according to the
|
||||||
|
[documentation](https://actix.github.io/actix-web/actix_web/struct.HttpServer.html#method.start_incoming).
|
||||||
|
|
||||||
|
And it does not delete the socket file (`/tmp/actix-uds.socket`) when stopping
|
||||||
|
the server so it will fail to start next time you run it unless you delete
|
||||||
|
the socket file manually.
|
32
examples/unix-socket/src/main.rs
Normal file
32
examples/unix-socket/src/main.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
|
extern crate env_logger;
|
||||||
|
extern crate tokio_uds;
|
||||||
|
|
||||||
|
use actix::*;
|
||||||
|
use actix_web::{middleware, server, App, HttpRequest};
|
||||||
|
use tokio_uds::UnixListener;
|
||||||
|
|
||||||
|
|
||||||
|
fn index(_req: HttpRequest) -> &'static str {
|
||||||
|
"Hello world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
env_logger::init();
|
||||||
|
let sys = actix::System::new("unix-socket");
|
||||||
|
|
||||||
|
let listener = UnixListener::bind(
|
||||||
|
"/tmp/actix-uds.socket", Arbiter::handle()).expect("bind failed");
|
||||||
|
server::new(
|
||||||
|
|| App::new()
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
.resource("/index.html", |r| r.f(|_| "Hello world!"))
|
||||||
|
.resource("/", |r| r.f(index)))
|
||||||
|
.start_incoming(listener.incoming(), false);
|
||||||
|
|
||||||
|
println!("Started http server: /tmp/actix-uds.socket");
|
||||||
|
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"
|
43
examples/web-cors/backend/src/main.rs
Normal file
43
examples/web-cors/backend/src/main.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#[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;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use actix_web::{http, middleware, server, App};
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
server::new(
|
||||||
|
|| App::new()
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
.resource("/user/info", |r| {
|
||||||
|
middleware::cors::Cors::build()
|
||||||
|
.allowed_origin("http://localhost:1234")
|
||||||
|
.allowed_methods(vec!["GET", "POST"])
|
||||||
|
.allowed_headers(
|
||||||
|
vec![http::header::AUTHORIZATION,
|
||||||
|
http::header::ACCEPT,
|
||||||
|
http::header::CONTENT_TYPE])
|
||||||
|
.max_age(3600)
|
||||||
|
.finish().expect("Can not create CORS middleware")
|
||||||
|
.register(r);
|
||||||
|
r.method(http::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::{AsyncResponder, Error, HttpMessage, HttpResponse, HttpRequest};
|
||||||
|
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(HttpResponse::Ok().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"
|
name = "websocket-example"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
workspace = "../.."
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "server"
|
name = "server"
|
||||||
@ -24,6 +25,5 @@ serde = "1.0"
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
|
||||||
#actix = "0.3"
|
actix = "0.5"
|
||||||
actix = { git = "https://github.com/actix/actix.git" }
|
actix-web = { path="../../" }
|
||||||
actix-web = { path = "../../" }
|
|
||||||
|
@ -9,17 +9,15 @@ Added features:
|
|||||||
* Chat server runs in separate thread
|
* Chat server runs in separate thread
|
||||||
* Tcp listener runs in separate thread
|
* Tcp listener runs in separate thread
|
||||||
|
|
||||||
|
|
||||||
## Server
|
## Server
|
||||||
|
|
||||||
Chat server listens for incoming tcp connections. Server can access several types of message:
|
Chat server listens for incoming tcp connections. Server can access several types of message:
|
||||||
|
|
||||||
* `\list` - list all available rooms
|
* `\list` - list all available rooms
|
||||||
* `\join name` - join room, if room does not exist, create new one
|
* `\join name` - join room, if room does not exist, create new one
|
||||||
* `\name name` - set session name
|
* `\name name` - set session name
|
||||||
* `some message` - just string, send messsage to all peers in same room
|
* `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
|
* client has to send heartbeat `Ping` messages, if server does not receive a heartbeat message for 10 seconds connection gets dropped
|
||||||
message for 10 seconds connection gets droppped
|
|
||||||
|
|
||||||
To start server use command: `cargo run --bin server`
|
To start server use command: `cargo run --bin server`
|
||||||
|
|
||||||
@ -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`
|
To run client use command: `cargo run --bin client`
|
||||||
|
|
||||||
|
|
||||||
## WebSocket Browser 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 bytes;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
@ -12,6 +12,9 @@ use std::{io, net, process, thread};
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
|
use tokio_io::AsyncRead;
|
||||||
|
use tokio_io::io::WriteHalf;
|
||||||
|
use tokio_io::codec::FramedRead;
|
||||||
use tokio_core::net::TcpStream;
|
use tokio_core::net::TcpStream;
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
@ -26,7 +29,12 @@ fn main() {
|
|||||||
Arbiter::handle().spawn(
|
Arbiter::handle().spawn(
|
||||||
TcpStream::connect(&addr, Arbiter::handle())
|
TcpStream::connect(&addr, Arbiter::handle())
|
||||||
.and_then(|stream| {
|
.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
|
// start console loop
|
||||||
thread::spawn(move|| {
|
thread::spawn(move|| {
|
||||||
@ -37,7 +45,7 @@ fn main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
addr.send(ClientCommand(cmd));
|
addr.do_send(ClientCommand(cmd));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -54,43 +62,48 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct ChatClient;
|
struct ChatClient {
|
||||||
|
framed: actix::io::FramedWrite<WriteHalf<TcpStream>, codec::ClientChatCodec>,
|
||||||
struct ClientCommand(String);
|
|
||||||
|
|
||||||
impl ResponseType for ClientCommand {
|
|
||||||
type Item = ();
|
|
||||||
type Error = ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for ChatClient {
|
#[derive(Message)]
|
||||||
type Context = FramedContext<Self>;
|
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
|
// start heartbeats otherwise server will disconnect after 10 seconds
|
||||||
self.hb(ctx)
|
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 {
|
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| {
|
ctx.run_later(Duration::new(1, 0), |act, ctx| {
|
||||||
if ctx.send(codec::ChatRequest::Ping).is_ok() {
|
act.framed.write(codec::ChatRequest::Ping);
|
||||||
act.hb(ctx);
|
act.hb(ctx);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl actix::io::WriteHandler<io::Error> for ChatClient {}
|
||||||
|
|
||||||
/// Handle stdin commands
|
/// Handle stdin commands
|
||||||
impl Handler<ClientCommand> for ChatClient
|
impl Handler<ClientCommand> for ChatClient {
|
||||||
{
|
type Result = ();
|
||||||
fn handle(&mut self, msg: ClientCommand, ctx: &mut FramedContext<Self>)
|
|
||||||
-> Response<Self, ClientCommand>
|
fn handle(&mut self, msg: ClientCommand, _: &mut Context<Self>) {
|
||||||
{
|
|
||||||
let m = msg.0.trim();
|
let m = msg.0.trim();
|
||||||
if m.is_empty() {
|
if m.is_empty() {
|
||||||
return Self::empty()
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// we check for /sss type of messages
|
// we check for /sss type of messages
|
||||||
@ -98,11 +111,11 @@ impl Handler<ClientCommand> for ChatClient
|
|||||||
let v: Vec<&str> = m.splitn(2, ' ').collect();
|
let v: Vec<&str> = m.splitn(2, ' ').collect();
|
||||||
match v[0] {
|
match v[0] {
|
||||||
"/list" => {
|
"/list" => {
|
||||||
let _ = ctx.send(codec::ChatRequest::List);
|
self.framed.write(codec::ChatRequest::List);
|
||||||
},
|
},
|
||||||
"/join" => {
|
"/join" => {
|
||||||
if v.len() == 2 {
|
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 {
|
} else {
|
||||||
println!("!!! room name is required");
|
println!("!!! room name is required");
|
||||||
}
|
}
|
||||||
@ -110,35 +123,16 @@ impl Handler<ClientCommand> for ChatClient
|
|||||||
_ => println!("!!! unknown command"),
|
_ => println!("!!! unknown command"),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let _ = ctx.send(codec::ChatRequest::Message(m.to_owned()));
|
self.framed.write(codec::ChatRequest::Message(m.to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Server communication
|
/// Server communication
|
||||||
|
|
||||||
impl FramedActor for ChatClient {
|
|
||||||
type Io = TcpStream;
|
|
||||||
type Codec = codec::ClientChatCodec;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StreamHandler<codec::ChatResponse, io::Error> for ChatClient {
|
impl StreamHandler<codec::ChatResponse, io::Error> for ChatClient {
|
||||||
|
|
||||||
fn finished(&mut self, _: &mut FramedContext<Self>) {
|
fn handle(&mut self, msg: codec::ChatResponse, _: &mut Context<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>
|
|
||||||
{
|
|
||||||
match msg {
|
match msg {
|
||||||
codec::ChatResponse::Message(ref msg) => {
|
codec::ChatResponse::Message(ref msg) => {
|
||||||
println!("message: {}", 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 byteorder::{BigEndian , ByteOrder};
|
||||||
use bytes::{BytesMut, BufMut};
|
use bytes::{BytesMut, BufMut};
|
||||||
use tokio_io::codec::{Encoder, Decoder};
|
use tokio_io::codec::{Encoder, Decoder};
|
||||||
use actix::ResponseType;
|
|
||||||
|
|
||||||
/// Client request
|
/// Client request
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug, Message)]
|
||||||
#[serde(tag="cmd", content="data")]
|
#[serde(tag="cmd", content="data")]
|
||||||
pub enum ChatRequest {
|
pub enum ChatRequest {
|
||||||
/// List rooms
|
/// List rooms
|
||||||
@ -20,13 +19,8 @@ pub enum ChatRequest {
|
|||||||
Ping
|
Ping
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseType for ChatRequest {
|
|
||||||
type Item = ();
|
|
||||||
type Error = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Server response
|
/// Server response
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug, Message)]
|
||||||
#[serde(tag="cmd", content="data")]
|
#[serde(tag="cmd", content="data")]
|
||||||
pub enum ChatResponse {
|
pub enum ChatResponse {
|
||||||
Ping,
|
Ping,
|
||||||
@ -41,11 +35,6 @@ pub enum ChatResponse {
|
|||||||
Message(String),
|
Message(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseType for ChatResponse {
|
|
||||||
type Item = ();
|
|
||||||
type Error = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Codec for Client -> Server transport
|
/// Codec for Client -> Server transport
|
||||||
pub struct ChatCodec;
|
pub struct ChatCodec;
|
||||||
|
|
||||||
|
@ -10,23 +10,35 @@ extern crate serde;
|
|||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use] extern crate serde_derive;
|
#[macro_use] extern crate serde_derive;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
extern crate actix;
|
extern crate actix;
|
||||||
extern crate actix_web;
|
extern crate actix_web;
|
||||||
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use actix::*;
|
use actix::*;
|
||||||
use actix_web::*;
|
use actix_web::server::HttpServer;
|
||||||
|
use actix_web::{http, fs, ws, App, HttpRequest, HttpResponse, Error};
|
||||||
|
|
||||||
mod codec;
|
mod codec;
|
||||||
mod server;
|
mod server;
|
||||||
mod session;
|
mod session;
|
||||||
|
|
||||||
|
|
||||||
/// This is our websocket route state, this state is shared with all route instances
|
/// This is our websocket route state, this state is shared with all route instances
|
||||||
/// via `HttpContext::state()`
|
/// via `HttpContext::state()`
|
||||||
struct WsChatSessionState {
|
struct WsChatSessionState {
|
||||||
addr: SyncAddress<server::ChatServer>,
|
addr: Addr<Syn, server::ChatServer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Entry point for our route
|
||||||
|
fn chat_route(req: HttpRequest<WsChatSessionState>) -> Result<HttpResponse, Error> {
|
||||||
|
ws::start(
|
||||||
|
req,
|
||||||
|
WsChatSession {
|
||||||
|
id: 0,
|
||||||
|
hb: Instant::now(),
|
||||||
|
room: "Main".to_owned(),
|
||||||
|
name: None})
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WsChatSession {
|
struct WsChatSession {
|
||||||
@ -41,50 +53,53 @@ struct WsChatSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for WsChatSession {
|
impl Actor for WsChatSession {
|
||||||
type Context = HttpContext<Self>;
|
type Context = ws::WebsocketContext<Self, WsChatSessionState>;
|
||||||
}
|
|
||||||
|
|
||||||
/// Entry point for our route
|
/// Method is called on actor start.
|
||||||
impl Route for WsChatSession {
|
/// We register ws session with ChatServer
|
||||||
type State = WsChatSessionState;
|
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,
|
fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
|
||||||
payload: Payload, ctx: &mut HttpContext<Self>) -> RouteResult<Self>
|
// notify chat server
|
||||||
{
|
ctx.state().addr.do_send(server::Disconnect{id: self.id});
|
||||||
// websocket handshakre, it may fail if request is not websocket request
|
Running::Stop
|
||||||
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})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle messages from chat server, we simply send it to peer websocket
|
/// Handle messages from chat server, we simply send it to peer websocket
|
||||||
impl Handler<session::Message> for WsChatSession {
|
impl Handler<session::Message> for WsChatSession {
|
||||||
fn handle(&mut self, msg: session::Message, ctx: &mut HttpContext<Self>)
|
type Result = ();
|
||||||
-> Response<Self, session::Message>
|
|
||||||
{
|
fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) {
|
||||||
ws::WsWriter::text(ctx, &msg.0);
|
ctx.text(msg.0);
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// WebSocket message handler
|
/// WebSocket message handler
|
||||||
impl Handler<ws::Message> for WsChatSession {
|
impl StreamHandler<ws::Message, ws::ProtocolError> for WsChatSession {
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>)
|
|
||||||
-> Response<Self, ws::Message>
|
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||||
{
|
|
||||||
println!("WEBSOCKET MESSAGE: {:?}", msg);
|
println!("WEBSOCKET MESSAGE: {:?}", msg);
|
||||||
match msg {
|
match msg {
|
||||||
ws::Message::Ping(msg) =>
|
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||||
ws::WsWriter::pong(ctx, &msg),
|
ws::Message::Pong(msg) => self.hb = Instant::now(),
|
||||||
ws::Message::Pong(msg) =>
|
|
||||||
self.hb = Instant::now(),
|
|
||||||
ws::Message::Text(text) => {
|
ws::Message::Text(text) => {
|
||||||
let m = text.trim();
|
let m = text.trim();
|
||||||
// we check for /sss type of messages
|
// we check for /sss type of messages
|
||||||
@ -94,17 +109,19 @@ impl Handler<ws::Message> for WsChatSession {
|
|||||||
"/list" => {
|
"/list" => {
|
||||||
// Send ListRooms message to chat server and wait for response
|
// Send ListRooms message to chat server and wait for response
|
||||||
println!("List rooms");
|
println!("List rooms");
|
||||||
ctx.state().addr.call(self, server::ListRooms).then(|res, _, ctx| {
|
ctx.state().addr.send(server::ListRooms)
|
||||||
match res {
|
.into_actor(self)
|
||||||
Ok(Ok(rooms)) => {
|
.then(|res, _, ctx| {
|
||||||
for room in rooms {
|
match res {
|
||||||
ws::WsWriter::text(ctx, &room);
|
Ok(rooms) => {
|
||||||
}
|
for room in rooms {
|
||||||
},
|
ctx.text(room);
|
||||||
_ => println!("Something is wrong"),
|
}
|
||||||
}
|
},
|
||||||
fut::ok(())
|
_ => println!("Something is wrong"),
|
||||||
}).wait(ctx)
|
}
|
||||||
|
fut::ok(())
|
||||||
|
}).wait(ctx)
|
||||||
// .wait(ctx) pauses all events in context,
|
// .wait(ctx) pauses all events in context,
|
||||||
// so actor wont receive any new messages until it get list
|
// so actor wont receive any new messages until it get list
|
||||||
// of rooms back
|
// of rooms back
|
||||||
@ -112,23 +129,22 @@ impl Handler<ws::Message> for WsChatSession {
|
|||||||
"/join" => {
|
"/join" => {
|
||||||
if v.len() == 2 {
|
if v.len() == 2 {
|
||||||
self.room = v[1].to_owned();
|
self.room = v[1].to_owned();
|
||||||
ctx.state().addr.send(
|
ctx.state().addr.do_send(
|
||||||
server::Join{id: self.id, name: self.room.clone()});
|
server::Join{id: self.id, name: self.room.clone()});
|
||||||
|
|
||||||
ws::WsWriter::text(ctx, "joined");
|
ctx.text("joined");
|
||||||
} else {
|
} else {
|
||||||
ws::WsWriter::text(ctx, "!!! room name is required");
|
ctx.text("!!! room name is required");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/name" => {
|
"/name" => {
|
||||||
if v.len() == 2 {
|
if v.len() == 2 {
|
||||||
self.name = Some(v[1].to_owned());
|
self.name = Some(v[1].to_owned());
|
||||||
} else {
|
} else {
|
||||||
ws::WsWriter::text(ctx, "!!! name is required");
|
ctx.text("!!! name is required");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => ws::WsWriter::text(
|
_ => ctx.text(format!("!!! unknown command: {:?}", m)),
|
||||||
ctx, &format!("!!! unknown command: {:?}", m)),
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let msg = if let Some(ref name) = self.name {
|
let msg = if let Some(ref name) = self.name {
|
||||||
@ -137,7 +153,7 @@ impl Handler<ws::Message> for WsChatSession {
|
|||||||
m.to_owned()
|
m.to_owned()
|
||||||
};
|
};
|
||||||
// send message to chat server
|
// send message to chat server
|
||||||
ctx.state().addr.send(
|
ctx.state().addr.do_send(
|
||||||
server::Message{id: self.id,
|
server::Message{id: self.id,
|
||||||
msg: msg,
|
msg: msg,
|
||||||
room: self.room.clone()})
|
room: self.room.clone()})
|
||||||
@ -145,82 +161,49 @@ impl Handler<ws::Message> for WsChatSession {
|
|||||||
},
|
},
|
||||||
ws::Message::Binary(bin) =>
|
ws::Message::Binary(bin) =>
|
||||||
println!("Unexpected binary"),
|
println!("Unexpected binary"),
|
||||||
ws::Message::Closed | ws::Message::Error => {
|
ws::Message::Close(_) => {
|
||||||
ctx.stop();
|
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() {
|
fn main() {
|
||||||
let _ = env_logger::init();
|
let _ = env_logger::init();
|
||||||
let sys = actix::System::new("websocket-example");
|
let sys = actix::System::new("websocket-example");
|
||||||
|
|
||||||
// Start chat server actor in separate thread
|
// Start chat server actor in separate thread
|
||||||
let server: SyncAddress<_> =
|
let server: Addr<Syn, _> = Arbiter::start(|_| server::ChatServer::default());
|
||||||
Arbiter::start(|_| server::ChatServer::default());
|
|
||||||
|
|
||||||
// Start tcp server in separate thread
|
// Start tcp server in separate thread
|
||||||
let srv = server.clone();
|
let srv = server.clone();
|
||||||
Arbiter::new("tcp-server").send::<msgs::Execute>(
|
Arbiter::new("tcp-server").do_send::<msgs::Execute>(
|
||||||
msgs::Execute::new(move || {
|
msgs::Execute::new(move || {
|
||||||
session::TcpServer::new("127.0.0.1:12345", srv);
|
session::TcpServer::new("127.0.0.1:12345", srv);
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Websocket sessions state
|
|
||||||
let state = WsChatSessionState { addr: server };
|
|
||||||
|
|
||||||
// Create Http server with websocket support
|
// Create Http server with websocket support
|
||||||
HttpServer::new(
|
HttpServer::new(
|
||||||
Application::builder("/", state)
|
move || {
|
||||||
// redirect to websocket.html
|
// Websocket sessions state
|
||||||
.resource("/", |r|
|
let state = WsChatSessionState { addr: server.clone() };
|
||||||
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();
|
|
||||||
|
|
||||||
|
App::with_state(state)
|
||||||
|
// redirect to websocket.html
|
||||||
|
.resource("/", |r| r.method(http::Method::GET).f(|_| {
|
||||||
|
HttpResponse::Found()
|
||||||
|
.header("LOCATION", "/static/websocket.html")
|
||||||
|
.finish()
|
||||||
|
}))
|
||||||
|
// websocket
|
||||||
|
.resource("/ws/", |r| r.route().f(chat_route))
|
||||||
|
// static resources
|
||||||
|
.handler("/static/", fs::StaticFiles::new("static/"))
|
||||||
|
})
|
||||||
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
println!("Started http server: 127.0.0.1:8080");
|
||||||
let _ = sys.run();
|
let _ = sys.run();
|
||||||
}
|
}
|
||||||
|
@ -12,29 +12,20 @@ use session;
|
|||||||
/// Message for chat server communications
|
/// Message for chat server communications
|
||||||
|
|
||||||
/// New chat session is created
|
/// New chat session is created
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(usize)]
|
||||||
pub struct Connect {
|
pub struct Connect {
|
||||||
pub addr: Box<Subscriber<session::Message> + Send>,
|
pub addr: Recipient<Syn, session::Message>,
|
||||||
}
|
|
||||||
|
|
||||||
/// Response type for Connect message
|
|
||||||
///
|
|
||||||
/// Chat server returns unique session id
|
|
||||||
impl ResponseType for Connect {
|
|
||||||
type Item = usize;
|
|
||||||
type Error = ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Session is disconnected
|
/// Session is disconnected
|
||||||
|
#[derive(Message)]
|
||||||
pub struct Disconnect {
|
pub struct Disconnect {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseType for Disconnect {
|
|
||||||
type Item = ();
|
|
||||||
type Error = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send message to specific room
|
/// Send message to specific room
|
||||||
|
#[derive(Message)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
/// Id of the client session
|
/// Id of the client session
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
@ -44,20 +35,15 @@ pub struct Message {
|
|||||||
pub room: String,
|
pub room: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseType for Message {
|
|
||||||
type Item = ();
|
|
||||||
type Error = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List of available rooms
|
/// List of available rooms
|
||||||
pub struct ListRooms;
|
pub struct ListRooms;
|
||||||
|
|
||||||
impl ResponseType for ListRooms {
|
impl actix::Message for ListRooms {
|
||||||
type Item = Vec<String>;
|
type Result = Vec<String>;
|
||||||
type Error = ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Join room, if room does not exists create new one.
|
/// Join room, if room does not exists create new one.
|
||||||
|
#[derive(Message)]
|
||||||
pub struct Join {
|
pub struct Join {
|
||||||
/// Client id
|
/// Client id
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
@ -65,15 +51,10 @@ pub struct Join {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseType for Join {
|
|
||||||
type Item = ();
|
|
||||||
type Error = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `ChatServer` manages chat rooms and responsible for coordinating chat session.
|
/// `ChatServer` manages chat rooms and responsible for coordinating chat session.
|
||||||
/// implementation is super primitive
|
/// implementation is super primitive
|
||||||
pub struct ChatServer {
|
pub struct ChatServer {
|
||||||
sessions: HashMap<usize, Box<Subscriber<session::Message> + Send>>,
|
sessions: HashMap<usize, Recipient<Syn, session::Message>>,
|
||||||
rooms: HashMap<String, HashSet<usize>>,
|
rooms: HashMap<String, HashSet<usize>>,
|
||||||
rng: RefCell<ThreadRng>,
|
rng: RefCell<ThreadRng>,
|
||||||
}
|
}
|
||||||
@ -99,7 +80,7 @@ impl ChatServer {
|
|||||||
for id in sessions {
|
for id in sessions {
|
||||||
if *id != skip_id {
|
if *id != skip_id {
|
||||||
if let Some(addr) = self.sessions.get(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
|
/// Register new session and assign unique id to this session
|
||||||
impl Handler<Connect> for ChatServer {
|
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");
|
println!("Someone joined");
|
||||||
|
|
||||||
// notify all users in same room
|
// 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);
|
self.rooms.get_mut(&"Main".to_owned()).unwrap().insert(id);
|
||||||
|
|
||||||
// send id back
|
// send id back
|
||||||
Self::reply(id)
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for Disconnect message.
|
/// Handler for Disconnect message.
|
||||||
impl Handler<Disconnect> for ChatServer {
|
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");
|
println!("Someone disconnected");
|
||||||
|
|
||||||
let mut rooms: Vec<String> = Vec::new();
|
let mut rooms: Vec<String> = Vec::new();
|
||||||
@ -158,40 +141,39 @@ impl Handler<Disconnect> for ChatServer {
|
|||||||
for room in rooms {
|
for room in rooms {
|
||||||
self.send_message(&room, "Someone disconnected", 0);
|
self.send_message(&room, "Someone disconnected", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for Message message.
|
/// Handler for Message message.
|
||||||
impl Handler<Message> for ChatServer {
|
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.send_message(&msg.room, msg.msg.as_str(), msg.id);
|
||||||
|
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for `ListRooms` message.
|
/// Handler for `ListRooms` message.
|
||||||
impl Handler<ListRooms> for ChatServer {
|
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();
|
let mut rooms = Vec::new();
|
||||||
|
|
||||||
for key in self.rooms.keys() {
|
for key in self.rooms.keys() {
|
||||||
rooms.push(key.to_owned())
|
rooms.push(key.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::reply(rooms)
|
MessageResult(rooms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Join room, send disconnect message to old room
|
/// Join room, send disconnect message to old room
|
||||||
/// send join message to new room
|
/// send join message to new room
|
||||||
impl Handler<Join> for ChatServer {
|
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 Join {id, name} = msg;
|
||||||
let mut rooms = Vec::new();
|
let mut rooms = Vec::new();
|
||||||
|
|
||||||
@ -211,7 +193,5 @@ impl Handler<Join> for ChatServer {
|
|||||||
}
|
}
|
||||||
self.send_message(&name, "Someone connected", id);
|
self.send_message(&name, "Someone connected", id);
|
||||||
self.rooms.get_mut(&name).unwrap().insert(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::str::FromStr;
|
||||||
use std::time::{Instant, Duration};
|
use std::time::{Instant, Duration};
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
|
use tokio_io::AsyncRead;
|
||||||
|
use tokio_io::io::WriteHalf;
|
||||||
|
use tokio_io::codec::FramedRead;
|
||||||
use tokio_core::net::{TcpStream, TcpListener};
|
use tokio_core::net::{TcpStream, TcpListener};
|
||||||
|
|
||||||
use actix::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
use server::{self, ChatServer};
|
use server::{self, ChatServer};
|
||||||
use codec::{ChatRequest, ChatResponse, ChatCodec};
|
use codec::{ChatRequest, ChatResponse, ChatCodec};
|
||||||
|
|
||||||
|
|
||||||
/// Chat server sends this messages to session
|
/// Chat server sends this messages to session
|
||||||
|
#[derive(Message)]
|
||||||
pub struct Message(pub String);
|
pub struct Message(pub String);
|
||||||
|
|
||||||
impl ResponseType for Message {
|
/// `ChatSession` actor is responsible for tcp peer communications.
|
||||||
type Item = ();
|
|
||||||
type Error = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `ChatSession` actor is responsible for tcp peer communitions.
|
|
||||||
pub struct ChatSession {
|
pub struct ChatSession {
|
||||||
/// unique session id
|
/// unique session id
|
||||||
id: usize,
|
id: usize,
|
||||||
/// this is address of chat server
|
/// 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.
|
/// Client must send ping at least once per 10 seconds, otherwise we drop connection.
|
||||||
hb: Instant,
|
hb: Instant,
|
||||||
/// joined room
|
/// joined room
|
||||||
room: String,
|
room: String,
|
||||||
|
/// Framed wrapper
|
||||||
|
framed: actix::io::FramedWrite<WriteHalf<TcpStream>, ChatCodec>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for ChatSession {
|
impl Actor for ChatSession {
|
||||||
/// For tcp communication we are going to use `FramedContext`.
|
/// For tcp communication we are going to use `FramedContext`.
|
||||||
/// It is convinient wrapper around `Framed` object from `tokio_io`
|
/// It is convenient wrapper around `Framed` object from `tokio_io`
|
||||||
type Context = FramedContext<Self>;
|
type Context = Context<Self>;
|
||||||
}
|
|
||||||
|
|
||||||
/// To use `FramedContext` we have to define Io type and Codec
|
fn started(&mut self, ctx: &mut Self::Context) {
|
||||||
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>) {
|
|
||||||
// we'll start heartbeat process on session start.
|
// we'll start heartbeat process on session start.
|
||||||
self.hb(ctx);
|
self.hb(ctx);
|
||||||
|
|
||||||
// register self in chat server. `AsyncContext::wait` register
|
// register self in chat server. `AsyncContext::wait` register
|
||||||
// future within context, but context waits until this future resolves
|
// future within context, but context waits until this future resolves
|
||||||
// before processing any other events.
|
// before processing any other events.
|
||||||
self.addr.call(self, server::Connect{addr: ctx.sync_subscriber()}).then(|res, act, ctx| {
|
let addr: Addr<Syn, _> = ctx.address();
|
||||||
match res {
|
self.addr.send(server::Connect{addr: addr.recipient()})
|
||||||
Ok(Ok(res)) => act.id = res,
|
.into_actor(self)
|
||||||
// something is wrong with chat server
|
.then(|res, act, ctx| {
|
||||||
_ => ctx.stop(),
|
match res {
|
||||||
}
|
Ok(res) => act.id = res,
|
||||||
fut::ok(())
|
// something is wrong with chat server
|
||||||
}).wait(ctx);
|
_ => 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
|
// notify chat server
|
||||||
self.addr.send(server::Disconnect{id: self.id});
|
self.addr.do_send(server::Disconnect{id: self.id});
|
||||||
|
Running::Stop
|
||||||
ctx.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
|
/// To use `Framed` we have to define Io type and Codec
|
||||||
/// termination of the tcp stream.
|
impl StreamHandler<ChatRequest, io::Error> for ChatSession {
|
||||||
fn error(&mut self, _: io::Error, ctx: &mut FramedContext<Self>) {
|
|
||||||
ctx.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is main event loop for client requests
|
/// This is main event loop for client requests
|
||||||
fn handle(&mut self, msg: ChatRequest, ctx: &mut FramedContext<Self>)
|
fn handle(&mut self, msg: ChatRequest, ctx: &mut Context<Self>) {
|
||||||
-> Response<Self, ChatRequest>
|
|
||||||
{
|
|
||||||
match msg {
|
match msg {
|
||||||
ChatRequest::List => {
|
ChatRequest::List => {
|
||||||
// Send ListRooms message to chat server and wait for response
|
// Send ListRooms message to chat server and wait for response
|
||||||
println!("List rooms");
|
println!("List rooms");
|
||||||
self.addr.call(self, server::ListRooms).then(|res, _, ctx| {
|
self.addr.send(server::ListRooms)
|
||||||
match res {
|
.into_actor(self)
|
||||||
Ok(Ok(rooms)) => {
|
.then(|res, act, ctx| {
|
||||||
let _ = ctx.send(ChatResponse::Rooms(rooms));
|
match res {
|
||||||
},
|
Ok(rooms) => {
|
||||||
_ => println!("Something is wrong"),
|
act.framed.write(ChatResponse::Rooms(rooms));
|
||||||
}
|
},
|
||||||
fut::ok(())
|
_ => println!("Something is wrong"),
|
||||||
}).wait(ctx)
|
}
|
||||||
|
actix::fut::ok(())
|
||||||
|
}).wait(ctx)
|
||||||
// .wait(ctx) pauses all events in context,
|
// .wait(ctx) pauses all events in context,
|
||||||
// so actor wont receive any new messages until it get list of rooms back
|
// so actor wont receive any new messages until it get list of rooms back
|
||||||
},
|
},
|
||||||
ChatRequest::Join(name) => {
|
ChatRequest::Join(name) => {
|
||||||
println!("Join to room: {}", name);
|
println!("Join to room: {}", name);
|
||||||
self.room = name.clone();
|
self.room = name.clone();
|
||||||
self.addr.send(server::Join{id: self.id, name: name.clone()});
|
self.addr.do_send(server::Join{id: self.id, name: name.clone()});
|
||||||
let _ = ctx.send(ChatResponse::Joined(name));
|
self.framed.write(ChatResponse::Joined(name));
|
||||||
},
|
},
|
||||||
ChatRequest::Message(message) => {
|
ChatRequest::Message(message) => {
|
||||||
// send message to chat server
|
// send message to chat server
|
||||||
println!("Peer message: {}", message);
|
println!("Peer message: {}", message);
|
||||||
self.addr.send(
|
self.addr.do_send(
|
||||||
server::Message{id: self.id,
|
server::Message{id: self.id,
|
||||||
msg: message, room:
|
msg: message, room:
|
||||||
self.room.clone()})
|
self.room.clone()})
|
||||||
@ -119,35 +108,32 @@ impl Handler<ChatRequest, io::Error> for ChatSession {
|
|||||||
ChatRequest::Ping =>
|
ChatRequest::Ping =>
|
||||||
self.hb = Instant::now(),
|
self.hb = Instant::now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for Message, chat server sends this message, we just send string to peer
|
/// Handler for Message, chat server sends this message, we just send string to peer
|
||||||
impl Handler<Message> for ChatSession {
|
impl Handler<Message> for ChatSession {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
fn handle(&mut self, msg: Message, ctx: &mut FramedContext<Self>)
|
fn handle(&mut self, msg: Message, ctx: &mut Context<Self>) {
|
||||||
-> Response<Self, Message>
|
|
||||||
{
|
|
||||||
// send message to peer
|
// send message to peer
|
||||||
let _ = ctx.send(ChatResponse::Message(msg.0));
|
self.framed.write(ChatResponse::Message(msg.0));
|
||||||
|
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper methods
|
/// Helper methods
|
||||||
impl ChatSession {
|
impl ChatSession {
|
||||||
|
|
||||||
pub fn new(addr: SyncAddress<ChatServer>) -> ChatSession {
|
pub fn new(addr: Addr<Syn,ChatServer>,
|
||||||
ChatSession {id: 0, addr: addr, hb: Instant::now(), room: "Main".to_owned()}
|
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.
|
/// helper method that sends ping to client every second.
|
||||||
///
|
///
|
||||||
/// also this method check heartbeats from client
|
/// 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| {
|
ctx.run_later(Duration::new(1, 0), |act, ctx| {
|
||||||
// check client heartbeats
|
// check client heartbeats
|
||||||
if Instant::now().duration_since(act.hb) > Duration::new(10, 0) {
|
if Instant::now().duration_since(act.hb) > Duration::new(10, 0) {
|
||||||
@ -155,29 +141,28 @@ impl ChatSession {
|
|||||||
println!("Client heartbeat failed, disconnecting!");
|
println!("Client heartbeat failed, disconnecting!");
|
||||||
|
|
||||||
// notify chat server
|
// notify chat server
|
||||||
act.addr.send(server::Disconnect{id: act.id});
|
act.addr.do_send(server::Disconnect{id: act.id});
|
||||||
|
|
||||||
// stop actor
|
// stop actor
|
||||||
ctx.stop();
|
ctx.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.send(ChatResponse::Ping).is_ok() {
|
act.framed.write(ChatResponse::Ping);
|
||||||
// if we can not send message to sink, sink is closed (disconnected)
|
// if we can not send message to sink, sink is closed (disconnected)
|
||||||
act.hb(ctx);
|
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.
|
/// chat actors.
|
||||||
pub struct TcpServer {
|
pub struct TcpServer {
|
||||||
chat: SyncAddress<ChatServer>,
|
chat: Addr<Syn, ChatServer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TcpServer {
|
impl TcpServer {
|
||||||
pub fn new(s: &str, chat: SyncAddress<ChatServer>) {
|
pub fn new(s: &str, chat: Addr<Syn, ChatServer>) {
|
||||||
// Create server listener
|
// Create server listener
|
||||||
let addr = net::SocketAddr::from_str("127.0.0.1:12345").unwrap();
|
let addr = net::SocketAddr::from_str("127.0.0.1:12345").unwrap();
|
||||||
let listener = TcpListener::bind(&addr, Arbiter::handle()).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
|
// So to be able to handle this events `Server` actor has to implement
|
||||||
// stream handler `StreamHandler<(TcpStream, net::SocketAddr), io::Error>`
|
// stream handler `StreamHandler<(TcpStream, net::SocketAddr), io::Error>`
|
||||||
let _: () = TcpServer::create(|ctx| {
|
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}
|
TcpServer{chat: chat}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -200,27 +187,21 @@ impl Actor for TcpServer {
|
|||||||
type Context = Context<Self>;
|
type Context = Context<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
struct TcpConnect(TcpStream, net::SocketAddr);
|
struct TcpConnect(TcpStream, net::SocketAddr);
|
||||||
|
|
||||||
impl ResponseType for TcpConnect {
|
|
||||||
type Item = ();
|
|
||||||
type Error = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle stream of TcpStream's
|
/// 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>) {
|
||||||
|
|
||||||
fn handle(&mut self, msg: TcpConnect, _: &mut Context<Self>) -> Response<Self, TcpConnect>
|
|
||||||
{
|
|
||||||
// For each incoming connection we create `ChatSession` actor
|
// For each incoming connection we create `ChatSession` actor
|
||||||
// with out chat server address.
|
// with out chat server address.
|
||||||
let server = self.chat.clone();
|
let server = self.chat.clone();
|
||||||
let _: () = ChatSession::new(server).framed(msg.0, ChatCodec);
|
let _: () = ChatSession::create(|ctx| {
|
||||||
|
let (r, w) = msg.0.split();
|
||||||
// this is response for message, which is defined by `ResponseType` trait
|
ChatSession::add_stream(FramedRead::new(r, ChatCodec), ctx);
|
||||||
// in this case we just return unit.
|
ChatSession::new(server, actix::io::FramedWrite::new(w, ChatCodec, ctx))
|
||||||
Self::empty()
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 @@
|
|||||||
|
# websocket
|
||||||
|
|
||||||
|
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, ProtocolError, Client, ClientWriter};
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
let _ = env_logger::init();
|
||||||
|
let sys = actix::System::new("ws-example");
|
||||||
|
|
||||||
|
Arbiter::handle().spawn(
|
||||||
|
Client::new("http://127.0.0.1:8080/ws/")
|
||||||
|
.connect()
|
||||||
|
.map_err(|e| {
|
||||||
|
println!("Error: {}", e);
|
||||||
|
()
|
||||||
|
})
|
||||||
|
.map(|(reader, writer)| {
|
||||||
|
let addr: Addr<Syn, _> = ChatClient::create(|ctx| {
|
||||||
|
ChatClient::add_stream(reader, ctx);
|
||||||
|
ChatClient(writer)
|
||||||
|
});
|
||||||
|
|
||||||
|
// start console loop
|
||||||
|
thread::spawn(move|| {
|
||||||
|
loop {
|
||||||
|
let mut cmd = String::new();
|
||||||
|
if io::stdin().read_line(&mut cmd).is_err() {
|
||||||
|
println!("error");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addr.do_send(ClientCommand(cmd));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct ChatClient(ClientWriter);
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
|
struct ClientCommand(String);
|
||||||
|
|
||||||
|
impl Actor for ChatClient {
|
||||||
|
type Context = Context<Self>;
|
||||||
|
|
||||||
|
fn started(&mut self, ctx: &mut Context<Self>) {
|
||||||
|
// start heartbeats otherwise server will disconnect after 10 seconds
|
||||||
|
self.hb(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stopped(&mut self, _: &mut Context<Self>) {
|
||||||
|
println!("Disconnected");
|
||||||
|
|
||||||
|
// Stop application on disconnect
|
||||||
|
Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChatClient {
|
||||||
|
fn hb(&self, ctx: &mut Context<Self>) {
|
||||||
|
ctx.run_later(Duration::new(1, 0), |act, ctx| {
|
||||||
|
act.0.ping("");
|
||||||
|
act.hb(ctx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle stdin commands
|
||||||
|
impl Handler<ClientCommand> for ChatClient {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: ClientCommand, ctx: &mut Context<Self>) {
|
||||||
|
self.0.text(msg.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle server websocket messages
|
||||||
|
impl StreamHandler<Message, ProtocolError> for ChatClient {
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: Message, ctx: &mut Context<Self>) {
|
||||||
|
match msg {
|
||||||
|
Message::Text(txt) => println!("Server: {:?}", txt),
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn started(&mut self, ctx: &mut Context<Self>) {
|
||||||
|
println!("Connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finished(&mut self, ctx: &mut Context<Self>) {
|
||||||
|
println!("Server disconnected");
|
||||||
|
ctx.stop()
|
||||||
|
}
|
||||||
|
}
|
66
examples/websocket/src/main.rs
Normal file
66
examples/websocket/src/main.rs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
//! 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::prelude::*;
|
||||||
|
use actix_web::{
|
||||||
|
http, middleware, server, fs, ws, App, HttpRequest, HttpResponse, Error};
|
||||||
|
|
||||||
|
/// do websocket handshake and start `MyWebSocket` actor
|
||||||
|
fn ws_index(r: HttpRequest) -> Result<HttpResponse, Error> {
|
||||||
|
ws::start(r, MyWebSocket)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// websocket connection is long running connection, it easier
|
||||||
|
/// to handle with an actor
|
||||||
|
struct MyWebSocket;
|
||||||
|
|
||||||
|
impl Actor for MyWebSocket {
|
||||||
|
type Context = ws::WebsocketContext<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler for `ws::Message`
|
||||||
|
impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||||
|
// process websocket messages
|
||||||
|
println!("WS: {:?}", msg);
|
||||||
|
match msg {
|
||||||
|
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||||
|
ws::Message::Text(text) => ctx.text(text),
|
||||||
|
ws::Message::Binary(bin) => ctx.binary(bin),
|
||||||
|
ws::Message::Close(_) => {
|
||||||
|
ctx.stop();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
env_logger::init();
|
||||||
|
let sys = actix::System::new("ws-example");
|
||||||
|
|
||||||
|
server::new(
|
||||||
|
|| App::new()
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
// websocket route
|
||||||
|
.resource("/ws/", |r| r.method(http::Method::GET).f(ws_index))
|
||||||
|
// static files
|
||||||
|
.handler("/", fs::StaticFiles::new("../static/")
|
||||||
|
.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();
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user