mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-04 18:06:23 +02:00
Compare commits
770 Commits
Author | SHA1 | Date | |
---|---|---|---|
4862227df9 | |||
f6499d9ba5 | |||
7138bb2f29 | |||
8cb510293d | |||
040d9d2755 | |||
2043bb5ece | |||
a751df2589 | |||
f6e35a04f0 | |||
0925a7691a | |||
2988a84e5f | |||
6b10e1eff6 | |||
85672d1379 | |||
373f2e5028 | |||
f9f259e718 | |||
d43902ee7c | |||
a7ca5fa5d8 | |||
29a275b0f5 | |||
1af5aa3a3e | |||
bccd7c7671 | |||
2a8c2fb55e | |||
2dd57a48d6 | |||
22385505a3 | |||
5888f01317 | |||
b7a3fce17b | |||
bce05e4fcb | |||
3373847a14 | |||
8f64508887 | |||
30c84786b7 | |||
2e5f627050 | |||
2214492792 | |||
c43b6e3577 | |||
42d3e86941 | |||
b759dddf5a | |||
9570c1cccd | |||
da915972c0 | |||
cf976d296f | |||
9012cf43fe | |||
7d753eeb8c | |||
4395add1c7 | |||
35911b832a | |||
b8b90d9ec9 | |||
422a870cd7 | |||
db005af1af | |||
8e462c5944 | |||
86e44de787 | |||
d9988f3ab6 | |||
696152f763 | |||
f38a370b94 | |||
28b36c650a | |||
b22132d3d6 | |||
19ae5e9489 | |||
9aef34e768 | |||
bed961fe35 | |||
87824a9cf6 | |||
82920e1ac1 | |||
110605f50b | |||
00c97504b6 | |||
85012f947a | |||
62ba01fc15 | |||
5b7aed101a | |||
1c3b32169e | |||
cfa470db50 | |||
a5f7a67b4d | |||
185e710dc8 | |||
9070d59ea8 | |||
2a25caf2c5 | |||
7d96b92aa3 | |||
67e4cad281 | |||
080f232a0f | |||
ac3a76cd32 | |||
8058d15624 | |||
05a43a855e | |||
80339147b9 | |||
6af2f5d642 | |||
d7762297da | |||
d5606625a2 | |||
5d79114239 | |||
f559f23e1c | |||
6fd686ef98 | |||
4c5a63965e | |||
09aabc7b3b | |||
b6d26c9faf | |||
fec6047ddc | |||
445ea043dd | |||
0be5448597 | |||
0f27389e72 | |||
a9425a866b | |||
800c404c72 | |||
32212bad1f | |||
d1b73e30e0 | |||
c0cdc39ba9 | |||
8e8a68f90b | |||
989cd61236 | |||
33260c7b35 | |||
40ca9ba9c5 | |||
45682c04a8 | |||
348491b18c | |||
3d2226aa9e | |||
cf38183dcb | |||
e3dc6f0ca8 | |||
a5369aed8b | |||
ff0ab733e4 | |||
d1318a35a0 | |||
756227896b | |||
4fadff63f4 | |||
7bc7b4839b | |||
dda6ee95df | |||
765c38e7b9 | |||
6c44575923 | |||
fc7238baee | |||
edd22bb279 | |||
17c033030b | |||
3afdf3fa7e | |||
50fbef88fc | |||
c9069e9a3c | |||
65ca563579 | |||
3de9284592 | |||
5a9992736f | |||
0338767264 | |||
c5e8c1b710 | |||
b5594ae2a5 | |||
58d1f4a4aa | |||
b7d813eeba | |||
8e160ebda7 | |||
0093b7ea5a | |||
75eec8bd4f | |||
ebc59cf7b9 | |||
c2c4a5ba3f | |||
dbd093075d | |||
1be27e17f8 | |||
8b0fbb85d1 | |||
cfe6725eb4 | |||
f815c1c096 | |||
280eae4335 | |||
bd8cbfff35 | |||
da237611cb | |||
234c60d473 | |||
2f917f3700 | |||
311f0b23a9 | |||
a69c1e3de5 | |||
c427fd1241 | |||
adcb4e1492 | |||
3b1124c56c | |||
cafde76361 | |||
bfb93cae66 | |||
b5c1e42feb | |||
e884e7e84e | |||
877e177b60 | |||
27b6af2800 | |||
5c42b0902f | |||
247e8727cb | |||
362b14c2f7 | |||
261ad31b9a | |||
68cd5bdf68 | |||
26f37ec2e3 | |||
ef15646bd7 | |||
a5bbc455c0 | |||
6ec8352612 | |||
f0f19c14d2 | |||
daed502ee5 | |||
9d114d785e | |||
ea118edf56 | |||
e1db47d550 | |||
38fe8bebec | |||
c3f295182f | |||
b6ed778775 | |||
0f2aac1a27 | |||
70244c29e0 | |||
a7a062fb68 | |||
f3a73d7dde | |||
879b2b5bde | |||
33050f55a3 | |||
e4443226f6 | |||
342a194605 | |||
566b16c1f7 | |||
8261cf437d | |||
8a8e6add08 | |||
b79307cab1 | |||
4c646962a9 | |||
cb77f7e688 | |||
1bee528018 | |||
ad9aacf521 | |||
f8854f951c | |||
6d95e34552 | |||
6c765739d0 | |||
c8528e8920 | |||
0a080d9fb4 | |||
45b408526c | |||
1a91854270 | |||
99092fdf06 | |||
748ff389e4 | |||
b679b4cabc | |||
ed7cbaa772 | |||
e6bbda0efc | |||
94283a73c2 | |||
ffca416463 | |||
9cc7651c22 | |||
8af082d873 | |||
d4d3add17d | |||
ce6f9e848b | |||
d8e1fd102d | |||
e414a52b51 | |||
4d69e6d0b4 | |||
6f38d769a8 | |||
48f77578ea | |||
9b012b3304 | |||
a0344eebeb | |||
b9f6c313d4 | |||
ef420a8bdf | |||
0d54b6f38e | |||
9afc3b6737 | |||
ef88fc78d0 | |||
9dd66dfc22 | |||
87a822e093 | |||
3788887c92 | |||
785d0e24f0 | |||
818d0bc187 | |||
aee24d4af0 | |||
fee203b402 | |||
8681a346c6 | |||
1fdf6d13be | |||
3751656722 | |||
9151d61eda | |||
4fe2f6b763 | |||
5a7902ff9a | |||
172b514fef | |||
efb5d13280 | |||
f9f2ed04ab | |||
ce40ab307b | |||
f7ef8ae5a5 | |||
60d40df545 | |||
f7bd6eeedc | |||
a11f3c112f | |||
e9f59bc7d6 | |||
e970846167 | |||
56e0dc06c1 | |||
789af0bbf2 | |||
97b5410aad | |||
a6e07c06b6 | |||
31a301c9a6 | |||
5a37a8b813 | |||
c0c1817b5c | |||
82c888df22 | |||
936ba2a368 | |||
2d0b609c68 | |||
6467d34a32 | |||
2b616808c7 | |||
e5f7e4e481 | |||
d1da227ac5 | |||
960a8c425d | |||
f94fd9ebee | |||
67ee24f9a0 | |||
5004821cda | |||
ae7a0e993d | |||
984791187a | |||
b07c50860a | |||
eb0909b3a8 | |||
ca3fb11f8b | |||
47eb4e3d3d | |||
268c5d9238 | |||
86be54df71 | |||
ea018e0ad6 | |||
b799677532 | |||
8c7182f6e6 | |||
7298c7aabf | |||
7e0706a942 | |||
698f0a1849 | |||
8b8a3ac01d | |||
7ab23d082d | |||
913dce0a72 | |||
2a9b57f489 | |||
fce8dd275a | |||
3c472a2f66 | |||
dcb561584d | |||
593a66324f | |||
4a39216aa7 | |||
8d905c8504 | |||
33326ea41b | |||
0457fe4d61 | |||
cede817915 | |||
3bfed36fcc | |||
0ff5f5f448 | |||
2f476021d8 | |||
a61a1b0efe | |||
e041e9d3b7 | |||
890a7e70d6 | |||
47b7be4fd3 | |||
9c9eb62031 | |||
8d73c30dae | |||
d912bf8771 | |||
f414a491dd | |||
8f42fec9b2 | |||
8452c7a044 | |||
009ee4b3db | |||
3e0a71101c | |||
c8930b7b6b | |||
3f5a39a5b7 | |||
154cd3c5de | |||
80965d7a9a | |||
77becb9bc0 | |||
dde266b9ef | |||
34fd9f8148 | |||
a64205e502 | |||
844be8d9dd | |||
dffb7936fb | |||
ecd05662c0 | |||
6eee3d1083 | |||
6b43fc7068 | |||
fb582a6bca | |||
be2ceb7c66 | |||
7c71171602 | |||
4dcecd907b | |||
255cd4917d | |||
f48702042b | |||
690169db89 | |||
565bcfb561 | |||
36f933ce1d | |||
111b6835fa | |||
bf63be3bcd | |||
9f9e0b98ad | |||
556646aaec | |||
174fb0b5f4 | |||
836706653b | |||
17f1a2b92a | |||
3b08b16c11 | |||
68eb2f26c9 | |||
72757887c9 | |||
eb5dbd43ae | |||
1f1dfac3f9 | |||
2479b14aba | |||
ac24703512 | |||
db0091ba6f | |||
2159158c30 | |||
76d790425f | |||
90968d4333 | |||
577a509875 | |||
a9728abfc8 | |||
14d1b8e2b6 | |||
285c73e95e | |||
483db7028c | |||
082ff46041 | |||
f32e8f22c8 | |||
766dde7c42 | |||
b68687044e | |||
c9e84e9dd3 | |||
0126ac46fc | |||
9b7ea836d0 | |||
537b420d35 | |||
16906c5951 | |||
45e9aaa462 | |||
564cc15c04 | |||
8fd18d56a5 | |||
a5692d4ecf | |||
2d83f79433 | |||
f3ece74406 | |||
8de1f60347 | |||
b4252f8fd1 | |||
fe2b50a9ef | |||
d8ae8c3821 | |||
c9a026fabb | |||
64eca1546e | |||
b19fe98ff4 | |||
b393ddf879 | |||
7bb7d85c1d | |||
6e976153e7 | |||
03e758cee4 | |||
0d36b8f826 | |||
f82fa08d72 | |||
d6787e6c56 | |||
b9d870645f | |||
ef89430f9b | |||
953a0d4e4a | |||
5ea2d68438 | |||
d65a03f6ac | |||
b588b2bf5c | |||
d455e2cd13 | |||
9306631d6e | |||
f735da504b | |||
487a713ca0 | |||
095ad328ee | |||
a38afa0cec | |||
9619698543 | |||
4b1a471b35 | |||
b6039b0bff | |||
d8fa43034f | |||
92f993e054 | |||
c172deb0f3 | |||
dee6aed010 | |||
76f021a6e3 | |||
2f244ea028 | |||
5f5ddc8f01 | |||
8b473745cb | |||
18575ee1ee | |||
e58b38fd13 | |||
b043c34632 | |||
b748bf3b0d | |||
be12d5e6fc | |||
7c4941f868 | |||
d1f5c457c4 | |||
c26c5fd9a4 | |||
4a73d1c8c1 | |||
7c395fcc83 | |||
54c33a7aff | |||
47d80382b2 | |||
ba816a8562 | |||
6f75b0e95e | |||
b3cc43bb9b | |||
ecda97aadd | |||
8cda362866 | |||
3c6c1268c9 | |||
72908d974c | |||
c755d71a8b | |||
a817ddb57b | |||
44c36e93d1 | |||
c92ebc22d7 | |||
599fd6af93 | |||
fa81d97004 | |||
c54f045b39 | |||
cd11293c1f | |||
45325a5f75 | |||
a7c40024ce | |||
0af4d01fe4 | |||
bd6e18b7fe | |||
f66cf16823 | |||
03d6b04eef | |||
f37880d89c | |||
8b43574bd5 | |||
b07d0e712f | |||
acd7380865 | |||
0208dfb6b2 | |||
bb61dd41af | |||
58079b5bbe | |||
3623383e83 | |||
7036656ae4 | |||
32a2866449 | |||
35a4078434 | |||
4ca5d8bcfc | |||
a38acb41e5 | |||
31e23d4ab1 | |||
1aadfee6f7 | |||
76b644365f | |||
80f385e703 | |||
a1958deaae | |||
8d65468c58 | |||
195246573e | |||
e01102bda2 | |||
9b6343d54b | |||
d9a4fadaae | |||
48e05a2d87 | |||
70d0c5c700 | |||
d43ca96c5c | |||
bfd46e6a71 | |||
25b245ac72 | |||
eefbe19651 | |||
ab4e889f96 | |||
91235ac816 | |||
9c1bda3eca | |||
4a29f12876 | |||
368730f5f1 | |||
aa757a5be8 | |||
03ded62337 | |||
c72d1381a6 | |||
d98d723f97 | |||
eb6e618812 | |||
de222fe33b | |||
de49796fd1 | |||
a38c3985f6 | |||
492c072564 | |||
fd876efa68 | |||
c5b9bed478 | |||
3eba383cdc | |||
927f2e594e | |||
fa9edf2180 | |||
5ca904d1db | |||
2e7d323e1a | |||
b66566f610 | |||
2477afcf30 | |||
bcd03a9c62 | |||
f8af3ef7f4 | |||
f8b75c157f | |||
b7b61afacc | |||
507361c1df | |||
f6fd9e70f9 | |||
de8a09254d | |||
f89b7a9bb8 | |||
59244b203c | |||
2adf8a3a48 | |||
805dbea8e7 | |||
dc9a24a189 | |||
5528cf62f0 | |||
9880a95603 | |||
2579c49865 | |||
01a0f3f5a0 | |||
2c8d987241 | |||
813d1d6e66 | |||
48b02abee7 | |||
ce1081432b | |||
e9bdba57a0 | |||
f907be585e | |||
022f9800ed | |||
a9a54ac4c6 | |||
50b9fee3a7 | |||
bf9a90293f | |||
17ec3a3a26 | |||
5b4b885fd6 | |||
65b8197876 | |||
a826d113ee | |||
3a79505a44 | |||
5f3a7a6a52 | |||
6a7b097bcf | |||
30a36bed9d | |||
79818560b2 | |||
58cc0dfbc5 | |||
a9ea649348 | |||
634c5723a0 | |||
a5b5ff0894 | |||
5140fea8d1 | |||
333b4f57d3 | |||
827ca5eada | |||
ebc1f6eff9 | |||
a8567da3e2 | |||
113f5ad1a8 | |||
95f6277007 | |||
22c776f46e | |||
c0976bfa17 | |||
5e9ec4299c | |||
e05aba65de | |||
c5b18c6d30 | |||
94c5bb5cdd | |||
2ca0ea70c4 | |||
0b01884fca | |||
83168731fc | |||
7295846426 | |||
72bc1546c4 | |||
d39b531537 | |||
35e68723df | |||
0624f9b9d9 | |||
0e3820afdf | |||
839d67ac6a | |||
b517957761 | |||
d18f9c5905 | |||
76fcdc13a3 | |||
62a9b4c53c | |||
c570229351 | |||
d041df6c4b | |||
bc28e54976 | |||
26ab5cbd01 | |||
50c2a5ceb0 | |||
8dbbb0ee07 | |||
ca76dff5a7 | |||
88f66d49d0 | |||
be288fa00a | |||
5e6a0aa3df | |||
fd87eb59f8 | |||
81ac905c7b | |||
bb11fb3d24 | |||
23eea54776 | |||
2881859400 | |||
1686682c19 | |||
d04ff13955 | |||
e757dc5a71 | |||
be358db422 | |||
7df2d6b12a | |||
458e6bdcc2 | |||
0b0bbd6bd9 | |||
5617896780 | |||
2b803f30c9 | |||
9b152acc32 | |||
eb66685d1a | |||
b505e682d4 | |||
48e7013997 | |||
ff14633b3d | |||
37db7d8168 | |||
89bf12605d | |||
9fb0498437 | |||
b2a43a3c8d | |||
38063b9873 | |||
1045a6c6f0 | |||
7243c58fce | |||
fffaf2bb2d | |||
7becb95a97 | |||
a4b837a1c1 | |||
542315ce7f | |||
602d78b76c | |||
18b706d4fb | |||
94b41fd484 | |||
3a80cb7bf3 | |||
e4a85a53f4 | |||
1a45dbd768 | |||
7cff5d9ade | |||
c04e0fdec4 | |||
0f0fe5f148 | |||
e7f9f5b46d | |||
c3fbba2678 | |||
a88e97edba | |||
1f08100f6f | |||
ab60ec6e1d | |||
0fbd05009d | |||
fdb7419e24 | |||
191b53bd7c | |||
2d4ee0ee01 | |||
5bd5f67d79 | |||
5d8cbccfe9 | |||
8d5fa6ee71 | |||
084104d058 | |||
2c411a04a9 | |||
af0c8d893d | |||
691457fbfe | |||
2dafd9c681 | |||
12586db15c | |||
b847bda8ca | |||
2a543001e0 | |||
0f86c596fa | |||
6c55501252 | |||
961edfd21a | |||
7f0de705a3 | |||
c2ad65a61d | |||
3c93e0c654 | |||
a0f1ff7eb3 | |||
9f45cfe492 | |||
46e6641528 | |||
a3f124685a | |||
800f711cc1 | |||
7be4b1f399 | |||
eeae0ddab4 | |||
c1af59c618 | |||
d8a9606162 | |||
8038a52287 | |||
c273b7ac3f | |||
df21892b5b | |||
a255a6fb69 | |||
b693d5491b | |||
56a31ea0ee | |||
2a269f1111 | |||
fee30d6f47 | |||
476b1fb36a | |||
3b93bff602 | |||
d292c5023f | |||
ef6f310060 | |||
a6cbdde43f | |||
cbf4c61eb5 | |||
280c8d87f8 | |||
83bf852192 | |||
9d39f441e9 | |||
03d851680b | |||
0ddd018214 | |||
8219a7aebe | |||
6c906b08e1 | |||
220cbe40e5 | |||
74d0656d27 | |||
17c27ef42d | |||
b2e771df2c | |||
a5a36ff194 | |||
97e2bcd055 | |||
23cfa649f4 | |||
8791c0f880 | |||
16c212f853 | |||
3ee228005d | |||
7a743fa6b5 | |||
44e3df82f6 | |||
8d8f6bedad | |||
9e751de707 | |||
b16419348e | |||
3ccaa04575 | |||
d80b84c915 | |||
145010a2b0 | |||
3e98177fad | |||
d24752d9bc | |||
92fe2e96de | |||
3cf54bc0fd | |||
86dd732704 | |||
dfd8f1058e | |||
f5636f321b | |||
ae6c9cb7fa | |||
32052c2750 | |||
7d6deab9fb | |||
9e61c67128 | |||
13bb5f20d2 | |||
d14991ec96 | |||
45dec8d0c0 | |||
90e3aaaf8a | |||
4f7d45ee9c | |||
e1d2536d85 | |||
65700281e8 | |||
80f6b93714 | |||
5585465859 | |||
368103dd09 | |||
df7ffe14f2 | |||
36161aba99 | |||
9f5a91ae3c | |||
4e61e0db34 | |||
2dfccdd924 | |||
4358da9926 | |||
62fb75ff95 | |||
29a0feb415 | |||
dcc5eb7ace | |||
81f4e12a27 | |||
2f60a4b89d | |||
b03c7051ff | |||
8fff2c7595 | |||
052d5f0bc5 | |||
68cf32e848 | |||
a56e5113ee | |||
5127b85672 | |||
2d80c5053d | |||
d46854b315 | |||
47f836cd1b | |||
449709dd7e | |||
5a25fd95f5 | |||
b942bcc4a6 | |||
1107fdec9d | |||
04515e4697 | |||
93d99b5a49 | |||
e49910cdab | |||
e8a1850c79 | |||
afb81b6b8f | |||
4866a26578 | |||
2d75ced4ed | |||
7bcc258b09 | |||
d5fa0a9418 | |||
ce6d237cc1 | |||
70caa2552b | |||
ee7d58dd7f | |||
c4f4cadb43 | |||
978091cedb | |||
8198f5e10a | |||
6cd40df387 | |||
35ee5d36d8 | |||
e7ec0f9fd7 | |||
f4a47ef71e | |||
6b1a79fab8 | |||
ab73da4a1a | |||
e0c8da567c | |||
c10dedf7e4 | |||
ec192e0ab1 | |||
6d792d9948 | |||
1fe4315c94 | |||
381b90e9a1 | |||
2d18dba40a | |||
d2693d58a8 | |||
84bf282c17 | |||
b15b5e5246 | |||
52b3b0c362 | |||
64c4cefa8f | |||
7e8b231f57 | |||
8a344d0c94 | |||
4096089a3f | |||
b16f2d5f05 | |||
5baf15822a | |||
5368ce823e | |||
4effdf065b | |||
61970ab190 | |||
484b00a0f9 | |||
73bf2068aa | |||
1cda949204 | |||
ad6b823255 | |||
0f064db31d | |||
fd0bb54469 | |||
e27bbaa55c | |||
8a50eae1e2 | |||
38080f67b3 | |||
08504e0892 | |||
401c0ad809 | |||
b4b0deb7fa | |||
05ff35d383 | |||
29c3e8f7ea | |||
6657446433 | |||
46b9a9c887 | |||
b3cdb472d0 | |||
31e1aab9a4 |
@ -3,41 +3,19 @@ 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
|
|
||||||
CHANNEL: stable
|
|
||||||
- TARGET: i686-pc-windows-msvc
|
- TARGET: i686-pc-windows-msvc
|
||||||
CHANNEL: stable
|
CHANNEL: stable
|
||||||
- TARGET: x86_64-pc-windows-gnu
|
- TARGET: x86_64-pc-windows-gnu
|
||||||
CHANNEL: stable
|
CHANNEL: stable
|
||||||
- TARGET: x86_64-pc-windows-msvc
|
- TARGET: x86_64-pc-windows-msvc
|
||||||
CHANNEL: stable
|
CHANNEL: stable
|
||||||
# Beta channel
|
|
||||||
- TARGET: i686-pc-windows-gnu
|
|
||||||
CHANNEL: beta
|
|
||||||
- TARGET: i686-pc-windows-msvc
|
|
||||||
CHANNEL: beta
|
|
||||||
- TARGET: x86_64-pc-windows-gnu
|
|
||||||
CHANNEL: beta
|
|
||||||
- TARGET: x86_64-pc-windows-msvc
|
|
||||||
CHANNEL: beta
|
|
||||||
# Nightly channel
|
# Nightly channel
|
||||||
- TARGET: i686-pc-windows-gnu
|
|
||||||
CHANNEL: nightly-2017-12-21
|
|
||||||
- TARGET: i686-pc-windows-msvc
|
- TARGET: i686-pc-windows-msvc
|
||||||
CHANNEL: nightly-2017-12-21
|
CHANNEL: nightly
|
||||||
- TARGET: x86_64-pc-windows-gnu
|
- TARGET: x86_64-pc-windows-gnu
|
||||||
CHANNEL: nightly-2017-12-21
|
CHANNEL: nightly
|
||||||
- TARGET: x86_64-pc-windows-msvc
|
- TARGET: x86_64-pc-windows-msvc
|
||||||
CHANNEL: nightly-2017-12-21
|
CHANNEL: nightly
|
||||||
|
|
||||||
# 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)
|
||||||
@ -59,4 +37,4 @@ build: false
|
|||||||
|
|
||||||
# Equivalent to Travis' `script` phase
|
# Equivalent to Travis' `script` phase
|
||||||
test_script:
|
test_script:
|
||||||
- cargo test --no-default-features
|
- cargo test --no-default-features --features="flate2-rust"
|
||||||
|
64
.travis.yml
64
.travis.yml
@ -1,5 +1,5 @@
|
|||||||
language: rust
|
language: rust
|
||||||
sudo: false
|
sudo: required
|
||||||
dist: trusty
|
dist: trusty
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
@ -8,19 +8,11 @@ cache:
|
|||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- rust: 1.21.0
|
|
||||||
- rust: stable
|
- rust: stable
|
||||||
- rust: beta
|
- rust: beta
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
- rust: beta
|
|
||||||
|
|
||||||
#rust:
|
|
||||||
# - 1.21.0
|
|
||||||
# - stable
|
|
||||||
# - beta
|
|
||||||
# - nightly-2018-01-03
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
@ -30,67 +22,33 @@ env:
|
|||||||
before_install:
|
before_install:
|
||||||
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
|
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
|
||||||
- sudo apt-get update -qq
|
- sudo apt-get update -qq
|
||||||
- sudo apt-get install -qq libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
|
- sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
|
||||||
|
|
||||||
# Add clippy
|
# Add clippy
|
||||||
before_script:
|
before_script:
|
||||||
- |
|
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
|
||||||
( ( cargo install clippy && export CLIPPY=true ) || export CLIPPY=false );
|
|
||||||
fi
|
|
||||||
- export PATH=$PATH:~/.cargo/bin
|
- export PATH=$PATH:~/.cargo/bin
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
if [[ "$TRAVIS_RUST_VERSION" != "stable" ]]; then
|
||||||
cargo clean
|
cargo clean
|
||||||
USE_SKEPTIC=1 cargo test --features=alpn
|
cargo test --features="alpn,tls" -- --nocapture
|
||||||
else
|
|
||||||
cargo clean
|
|
||||||
cargo test -- --nocapture
|
|
||||||
# --features=alpn
|
|
||||||
fi
|
|
||||||
|
|
||||||
- |
|
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
|
||||||
cd examples/basics && cargo check && cd ../..
|
|
||||||
cd examples/hello-world && cargo check && cd ../..
|
|
||||||
cd examples/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
|
fi
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then
|
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
||||||
cargo clippy
|
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin
|
||||||
|
cargo tarpaulin --features="alpn,tls" --out Xml --no-count
|
||||||
|
bash <(curl -s https://codecov.io/bash)
|
||||||
|
echo "Uploaded code coverage"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 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 --features "alpn, tls, session" --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 &&
|
||||||
cargo install mdbook &&
|
|
||||||
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" == "1.21.0" ]]; then
|
|
||||||
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
|
|
||||||
USE_SKEPTIC=1 cargo tarpaulin --out Xml
|
|
||||||
bash <(curl -s https://codecov.io/bash)
|
|
||||||
echo "Uploaded code coverage"
|
|
||||||
fi
|
|
||||||
|
345
CHANGES.md
345
CHANGES.md
@ -1,5 +1,346 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
## [0.7.1] - 2018-07-21
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed default_resource 'not yet implemented' panic #410
|
||||||
|
|
||||||
|
|
||||||
|
## [0.7.0] - 2018-07-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Add `fs::StaticFileConfig` to provide means of customizing static
|
||||||
|
file services. It allows to map `mime` to `Content-Disposition`,
|
||||||
|
specify whether to use `ETag` and `Last-Modified` and allowed methods.
|
||||||
|
|
||||||
|
* Add `.has_prefixed_resource()` method to `router::ResourceInfo`
|
||||||
|
for route matching with prefix awareness
|
||||||
|
|
||||||
|
* Add `HttpMessage::readlines()` for reading line by line.
|
||||||
|
|
||||||
|
* Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests.
|
||||||
|
|
||||||
|
* Add method to configure custom error handler to `Form` extractor.
|
||||||
|
|
||||||
|
* Add methods to `HttpResponse` to retrieve, add, and delete cookies
|
||||||
|
|
||||||
|
* Add `.set_content_type()` and `.set_content_disposition()` methods
|
||||||
|
to `fs::NamedFile` to allow overriding the values inferred by default
|
||||||
|
|
||||||
|
* Add `fs::file_extension_to_mime()` helper function to get the MIME
|
||||||
|
type for a file extension
|
||||||
|
|
||||||
|
* Add `.content_disposition()` method to parse Content-Disposition of
|
||||||
|
multipart fields
|
||||||
|
|
||||||
|
* Re-export `actix::prelude::*` as `actix_web::actix` module.
|
||||||
|
|
||||||
|
* `HttpRequest::url_for_static()` for a named route with no variables segments
|
||||||
|
|
||||||
|
* Propagation of the application's default resource to scopes that haven't set a default resource.
|
||||||
|
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Min rustc version is 1.26
|
||||||
|
|
||||||
|
* Use tokio instead of tokio-core
|
||||||
|
|
||||||
|
* `CookieSessionBackend` sets percent encoded cookies for outgoing HTTP messages.
|
||||||
|
|
||||||
|
* Became possible to use enums with query extractor.
|
||||||
|
Issue [#371](https://github.com/actix/actix-web/issues/371).
|
||||||
|
[Example](https://github.com/actix/actix-web/blob/master/tests/test_handlers.rs#L94-L134)
|
||||||
|
|
||||||
|
* `HttpResponse::into_builder()` now moves cookies into the builder
|
||||||
|
instead of dropping them
|
||||||
|
|
||||||
|
* For safety and performance reasons `Handler::handle()` uses `&self` instead of `&mut self`
|
||||||
|
|
||||||
|
* `Handler::handle()` uses `&HttpRequest` instead of `HttpRequest`
|
||||||
|
|
||||||
|
* Added header `User-Agent: Actix-web/<current_version>` to default headers when building a request
|
||||||
|
|
||||||
|
* port `Extensions` type from http create, we don't need `Send + Sync`
|
||||||
|
|
||||||
|
* `HttpRequest::query()` returns `Ref<HashMap<String, String>>`
|
||||||
|
|
||||||
|
* `HttpRequest::cookies()` returns `Ref<Vec<Cookie<'static>>>`
|
||||||
|
|
||||||
|
* `StaticFiles::new()` returns `Result<StaticFiles<S>, Error>` instead of `StaticFiles<S>`
|
||||||
|
|
||||||
|
* `StaticFiles` uses the default handler if the file does not exist
|
||||||
|
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
* Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead.
|
||||||
|
|
||||||
|
* Remove `HttpMessage::range()`
|
||||||
|
|
||||||
|
|
||||||
|
## [0.6.15] - 2018-07-11
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fix h2 compatibility #352
|
||||||
|
|
||||||
|
* Fix duplicate tail of StaticFiles with index_file. #344
|
||||||
|
|
||||||
|
|
||||||
|
## [0.6.14] - 2018-06-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Allow to disable masking for websockets client
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* SendRequest execution fails with the "internal error: entered unreachable code" #329
|
||||||
|
|
||||||
|
|
||||||
|
## [0.6.13] - 2018-06-11
|
||||||
|
|
||||||
|
* http/2 end-of-frame is not set if body is empty bytes #307
|
||||||
|
|
||||||
|
* InternalError can trigger memory unsafety #301
|
||||||
|
|
||||||
|
|
||||||
|
## [0.6.12] - 2018-06-08
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Add `Host` filter #287
|
||||||
|
|
||||||
|
* Allow to filter applications
|
||||||
|
|
||||||
|
* Improved failure interoperability with downcasting #285
|
||||||
|
|
||||||
|
* Allow to use custom resolver for `ClientConnector`
|
||||||
|
|
||||||
|
|
||||||
|
## [0.6.11] - 2018-06-05
|
||||||
|
|
||||||
|
* Support chunked encoding for UrlEncoded body #262
|
||||||
|
|
||||||
|
* `HttpRequest::url_for()` for a named route with no variables segments #265
|
||||||
|
|
||||||
|
* `Middleware::response()` is not invoked if error result was returned by another `Middleware::start()` #255
|
||||||
|
|
||||||
|
* CORS: Do not validate Origin header on non-OPTION requests #271
|
||||||
|
|
||||||
|
* Fix multipart upload "Incomplete" error #282
|
||||||
|
|
||||||
|
|
||||||
|
## [0.6.10] - 2018-05-24
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Allow to use path without trailing slashes for scope registration #241
|
||||||
|
|
||||||
|
* Allow to set encoding for exact NamedFile #239
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* `TestServer::post()` actually sends `GET` request #240
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.9 (2018-05-22)
|
||||||
|
|
||||||
|
* Drop connection if request's payload is not fully consumed #236
|
||||||
|
|
||||||
|
* Fix streaming response with body compression
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.8 (2018-05-20)
|
||||||
|
|
||||||
|
* Fix scope resource path extractor #234
|
||||||
|
|
||||||
|
* Re-use tcp listener on pause/resume
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.7 (2018-05-17)
|
||||||
|
|
||||||
|
* Fix compilation with --no-default-features
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.6 (2018-05-17)
|
||||||
|
|
||||||
|
* Panic during middleware execution #226
|
||||||
|
|
||||||
|
* Add support for listen_tls/listen_ssl #224
|
||||||
|
|
||||||
|
* Implement extractor for `Session`
|
||||||
|
|
||||||
|
* Ranges header support for NamedFile #60
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.5 (2018-05-15)
|
||||||
|
|
||||||
|
* Fix error handling during request decoding #222
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.4 (2018-05-11)
|
||||||
|
|
||||||
|
* Fix segfault in ServerSettings::get_response_builder()
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.3 (2018-05-10)
|
||||||
|
|
||||||
|
* Add `Router::with_async()` method for async handler registration.
|
||||||
|
|
||||||
|
* Added error response functions for 501,502,503,504
|
||||||
|
|
||||||
|
* Fix client request timeout handling
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.2 (2018-05-09)
|
||||||
|
|
||||||
|
* WsWriter trait is optional.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.1 (2018-05-08)
|
||||||
|
|
||||||
|
* Fix http/2 payload streaming #215
|
||||||
|
|
||||||
|
* Fix connector's default `keep-alive` and `lifetime` settings #212
|
||||||
|
|
||||||
|
* Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214
|
||||||
|
|
||||||
|
* Allow to exclude certain endpoints from logging #211
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6.0 (2018-05-08)
|
||||||
|
|
||||||
|
* Add route scopes #202
|
||||||
|
|
||||||
|
* Allow to use ssl and non-ssl connections at the same time #206
|
||||||
|
|
||||||
|
* Websocket CloseCode Empty/Status is ambiguous #193
|
||||||
|
|
||||||
|
* Add Content-Disposition to NamedFile #204
|
||||||
|
|
||||||
|
* Allow to access Error's backtrace object
|
||||||
|
|
||||||
|
* Allow to override files listing renderer for `StaticFiles` #203
|
||||||
|
|
||||||
|
* Various extractor usability improvements #207
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.6 (2018-04-24)
|
||||||
|
|
||||||
|
* Make flate2 crate optional #200
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.5 (2018-04-24)
|
||||||
|
|
||||||
|
* Fix panic when Websocket is closed with no error code #191
|
||||||
|
|
||||||
|
* Allow to use rust backend for flate2 crate #199
|
||||||
|
|
||||||
|
## 0.5.4 (2018-04-19)
|
||||||
|
|
||||||
|
* Add identity service middleware
|
||||||
|
|
||||||
|
* Middleware response() is not invoked if there was an error in async handler #187
|
||||||
|
|
||||||
|
* Use Display formatting for InternalError Display implementation #188
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.3 (2018-04-18)
|
||||||
|
|
||||||
|
* Impossible to quote slashes in path parameters #182
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.2 (2018-04-16)
|
||||||
|
|
||||||
|
* Allow to configure StaticFiles's CpuPool, via static method or env variable
|
||||||
|
|
||||||
|
* Add support for custom handling of Json extractor errors #181
|
||||||
|
|
||||||
|
* Fix StaticFiles does not support percent encoded paths #177
|
||||||
|
|
||||||
|
* Fix Client Request with custom Body Stream halting on certain size requests #176
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.1 (2018-04-12)
|
||||||
|
|
||||||
|
* Client connector provides stats, `ClientConnector::stats()`
|
||||||
|
|
||||||
|
* Fix end-of-stream handling in parse_payload #173
|
||||||
|
|
||||||
|
* Fix StaticFiles generate a lot of threads #174
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0 (2018-04-10)
|
||||||
|
|
||||||
|
* Type-safe path/query/form parameter handling, using serde #70
|
||||||
|
|
||||||
|
* HttpResponse builder's methods `.body()`, `.finish()`, `.json()`
|
||||||
|
return `HttpResponse` instead of `Result`
|
||||||
|
|
||||||
|
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()`
|
||||||
|
|
||||||
|
* Added `signed` and `private` `CookieSessionBackend`s
|
||||||
|
|
||||||
|
* Added `HttpRequest::resource()`, returns current matched resource
|
||||||
|
|
||||||
|
* Added `ErrorHandlers` middleware
|
||||||
|
|
||||||
|
* Fix router cannot parse Non-ASCII characters in URL #137
|
||||||
|
|
||||||
|
* Fix client connection pooling
|
||||||
|
|
||||||
|
* Fix long client urls #129
|
||||||
|
|
||||||
|
* Fix panic on invalid URL characters #130
|
||||||
|
|
||||||
|
* Fix logger request duration calculation #152
|
||||||
|
|
||||||
|
* Fix prefix and static file serving #168
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.10 (2018-03-20)
|
||||||
|
|
||||||
|
* Use `Error` instead of `InternalError` for `error::ErrorXXXX` methods
|
||||||
|
|
||||||
|
* Allow to set client request timeout
|
||||||
|
|
||||||
|
* Allow to set client websocket handshake timeout
|
||||||
|
|
||||||
|
* Refactor `TestServer` configuration
|
||||||
|
|
||||||
|
* Fix server websockets big payloads support
|
||||||
|
|
||||||
|
* Fix http/2 date header generation
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.9 (2018-03-16)
|
||||||
|
|
||||||
|
* Allow to disable http/2 support
|
||||||
|
|
||||||
|
* Wake payload reading task when data is available
|
||||||
|
|
||||||
|
* Fix server keep-alive handling
|
||||||
|
|
||||||
|
* Send Query Parameters in client requests #120
|
||||||
|
|
||||||
|
* Move brotli encoding to a feature
|
||||||
|
|
||||||
|
* Add option of default handler for `StaticFiles` handler #57
|
||||||
|
|
||||||
|
* Add basic client connection pooling
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.8 (2018-03-12)
|
||||||
|
|
||||||
|
* Allow to set read buffer capacity for server request
|
||||||
|
|
||||||
|
* Handle WouldBlock error for socket accept call
|
||||||
|
|
||||||
|
|
||||||
## 0.4.7 (2018-03-11)
|
## 0.4.7 (2018-03-11)
|
||||||
|
|
||||||
* Fix panic on unknown content encoding
|
* Fix panic on unknown content encoding
|
||||||
@ -35,7 +376,7 @@
|
|||||||
* Better support for `NamedFile` type
|
* Better support for `NamedFile` type
|
||||||
|
|
||||||
* Add `ResponseError` impl for `SendRequestError`. This improves ergonomics of the client.
|
* Add `ResponseError` impl for `SendRequestError`. This improves ergonomics of the client.
|
||||||
|
|
||||||
* Add native-tls support for client
|
* Add native-tls support for client
|
||||||
|
|
||||||
* Allow client connection timeout to be set #108
|
* Allow client connection timeout to be set #108
|
||||||
@ -158,7 +499,7 @@
|
|||||||
|
|
||||||
* Server multi-threading
|
* Server multi-threading
|
||||||
|
|
||||||
* Gracefull shutdown support
|
* Graceful shutdown support
|
||||||
|
|
||||||
|
|
||||||
## 0.2.1 (2017-11-03)
|
## 0.2.1 (2017-11-03)
|
||||||
|
83
Cargo.toml
83
Cargo.toml
@ -1,22 +1,24 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "0.4.7"
|
version = "0.7.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix web is a simple, pragmatic, extremely fast, web framework for Rust."
|
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://github.com/actix/actix-web"
|
homepage = "https://actix.rs"
|
||||||
repository = "https://github.com/actix/actix-web.git"
|
repository = "https://github.com/actix/actix-web.git"
|
||||||
documentation = "https://docs.rs/actix-web/"
|
documentation = "https://actix.rs/api/actix-web/stable/actix_web/"
|
||||||
categories = ["network-programming", "asynchronous",
|
categories = ["network-programming", "asynchronous",
|
||||||
"web-programming::http-server",
|
"web-programming::http-server",
|
||||||
"web-programming::http-client",
|
"web-programming::http-client",
|
||||||
"web-programming::websocket"]
|
"web-programming::websocket"]
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
exclude = [".gitignore", ".travis.yml", ".cargo/config",
|
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||||
"appveyor.yml", "/examples/**"]
|
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
features = ["tls", "alpn", "session", "brotli", "flate2-c"]
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
travis-ci = { repository = "actix/actix-web", branch = "master" }
|
travis-ci = { repository = "actix/actix-web", branch = "master" }
|
||||||
appveyor = { repository = "fafhrd91/actix-web-hdy9d" }
|
appveyor = { repository = "fafhrd91/actix-web-hdy9d" }
|
||||||
@ -27,37 +29,42 @@ name = "actix_web"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["session"]
|
default = ["session", "brotli", "flate2-c"]
|
||||||
|
|
||||||
# tls
|
# tls
|
||||||
tls = ["native-tls", "tokio-tls"]
|
tls = ["native-tls", "tokio-tls"]
|
||||||
|
|
||||||
# openssl
|
# openssl
|
||||||
alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"]
|
alpn = ["openssl", "tokio-openssl"]
|
||||||
|
|
||||||
# sessions
|
# sessions feature, session require "ring" crate and c compiler
|
||||||
session = ["cookie/secure"]
|
session = ["cookie/secure"]
|
||||||
|
|
||||||
|
# brotli encoding, requires c compiler
|
||||||
|
brotli = ["brotli2"]
|
||||||
|
|
||||||
|
# miniz-sys backend for flate2 crate
|
||||||
|
flate2-c = ["flate2/miniz-sys"]
|
||||||
|
|
||||||
|
# rust backend for flate2 crate
|
||||||
|
flate2-rust = ["flate2/rust_backend"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = "^0.5.2"
|
actix = "0.7.0"
|
||||||
|
|
||||||
base64 = "0.9"
|
base64 = "0.9"
|
||||||
bitflags = "1.0"
|
bitflags = "1.0"
|
||||||
brotli2 = "^0.3.2"
|
|
||||||
failure = "0.1.1"
|
|
||||||
flate2 = "1.0"
|
|
||||||
h2 = "0.1"
|
h2 = "0.1"
|
||||||
|
htmlescape = "0.3"
|
||||||
http = "^0.1.5"
|
http = "^0.1.5"
|
||||||
httparse = "1.2"
|
httparse = "1.3"
|
||||||
http-range = "0.1"
|
|
||||||
libc = "0.2"
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
mime_guess = "2.0.0-alpha"
|
mime_guess = "2.0.0-alpha"
|
||||||
num_cpus = "1.0"
|
num_cpus = "1.0"
|
||||||
percent-encoding = "1.0"
|
percent-encoding = "1.0"
|
||||||
rand = "0.4"
|
rand = "0.5"
|
||||||
regex = "0.2"
|
regex = "1.0"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
sha1 = "0.6"
|
sha1 = "0.6"
|
||||||
@ -65,19 +72,29 @@ smallvec = "0.6"
|
|||||||
time = "0.1"
|
time = "0.1"
|
||||||
encoding = "0.2"
|
encoding = "0.2"
|
||||||
language-tags = "0.2"
|
language-tags = "0.2"
|
||||||
|
lazy_static = "1.0"
|
||||||
|
lazycell = "1.0.0"
|
||||||
|
parking_lot = "0.6"
|
||||||
url = { version="1.7", features=["query_encoding"] }
|
url = { version="1.7", features=["query_encoding"] }
|
||||||
cookie = { version="0.10", features=["percent-encode"] }
|
cookie = { version="0.10", features=["percent-encode"] }
|
||||||
|
brotli2 = { version="^0.3.2", optional = true }
|
||||||
|
flate2 = { version="1.0", optional = true, default-features = false }
|
||||||
|
|
||||||
|
failure = "=0.1.1"
|
||||||
|
|
||||||
# io
|
# io
|
||||||
mio = "^0.6.13"
|
mio = "^0.6.13"
|
||||||
net2 = "0.2"
|
net2 = "0.2"
|
||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
byteorder = "1"
|
byteorder = "1.2"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
futures-cpupool = "0.1"
|
futures-cpupool = "0.1"
|
||||||
|
slab = "0.4"
|
||||||
|
tokio = "0.1"
|
||||||
tokio-io = "0.1"
|
tokio-io = "0.1"
|
||||||
tokio-core = "0.1"
|
tokio-tcp = "0.1"
|
||||||
trust-dns-resolver = "0.8"
|
tokio-timer = "0.2"
|
||||||
|
tokio-reactor = "0.1"
|
||||||
|
|
||||||
# native-tls
|
# native-tls
|
||||||
native-tls = { version="0.1", optional = true }
|
native-tls = { version="0.1", optional = true }
|
||||||
@ -87,13 +104,15 @@ tokio-tls = { version="0.1", optional = true }
|
|||||||
openssl = { version="0.10", optional = true }
|
openssl = { version="0.10", optional = true }
|
||||||
tokio-openssl = { version="0.2", optional = true }
|
tokio-openssl = { version="0.2", optional = true }
|
||||||
|
|
||||||
|
# forked url_encoded
|
||||||
|
itoa = "0.4"
|
||||||
|
dtoa = "0.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.5"
|
env_logger = "0.5"
|
||||||
skeptic = "0.13"
|
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
skeptic = "0.13"
|
|
||||||
version_check = "0.1"
|
version_check = "0.1"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
@ -104,22 +123,4 @@ codegen-units = 1
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
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/",
|
|
||||||
]
|
]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2017 Nikilay Kim
|
Copyright (c) 2017 Nikolay Kim
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any
|
Permission is hereby granted, free of charge, to any
|
||||||
person obtaining a copy of this software and associated
|
person obtaining a copy of this software and associated
|
||||||
|
170
MIGRATION.md
Normal file
170
MIGRATION.md
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
## 0.7
|
||||||
|
|
||||||
|
* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload
|
||||||
|
use `HttpMessage::payload()` method.
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(req: HttpRequest) -> impl Responder {
|
||||||
|
req
|
||||||
|
.from_err()
|
||||||
|
.fold(...)
|
||||||
|
....
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
use `.payload()`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(req: HttpRequest) -> impl Responder {
|
||||||
|
req
|
||||||
|
.payload() // <- get request payload stream
|
||||||
|
.from_err()
|
||||||
|
.fold(...)
|
||||||
|
....
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html)
|
||||||
|
trait uses `&HttpRequest` instead of `&mut HttpRequest`.
|
||||||
|
|
||||||
|
* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead.
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(query: Query<..>, info: Json<MyStruct) -> impl Responder {}
|
||||||
|
```
|
||||||
|
|
||||||
|
use tuple of extractors and use `.with()` for registration:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index((query, json): (Query<..>, Json<MyStruct)) -> impl Responder {}
|
||||||
|
```
|
||||||
|
|
||||||
|
* `Handler::handle()` uses `&self` instead of `&mut self`
|
||||||
|
|
||||||
|
* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value
|
||||||
|
|
||||||
|
* Removed deprecated `HttpServer::threads()`, use
|
||||||
|
[HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead.
|
||||||
|
|
||||||
|
* Renamed `client::ClientConnectorError::Connector` to
|
||||||
|
`client::ClientConnectorError::Resolver`
|
||||||
|
|
||||||
|
* `Route::with()` does not return `ExtractorConfig`, to configure
|
||||||
|
extractor use `Route::with_config()`
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let app = App::new().resource("/index.html", |r| {
|
||||||
|
r.method(http::Method::GET)
|
||||||
|
.with(index)
|
||||||
|
.limit(4096); // <- limit size of the payload
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
use
|
||||||
|
|
||||||
|
```rust
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = App::new().resource("/index.html", |r| {
|
||||||
|
r.method(http::Method::GET)
|
||||||
|
.with_config(index, |cfg| { // <- register handler
|
||||||
|
cfg.limit(4096); // <- limit size of the payload
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* `Route::with_async()` does not return `ExtractorConfig`, to configure
|
||||||
|
extractor use `Route::with_async_config()`
|
||||||
|
|
||||||
|
|
||||||
|
## 0.6
|
||||||
|
|
||||||
|
* `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
|
||||||
|
|
||||||
|
* `ws::Message::Close` now includes optional close reason.
|
||||||
|
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
|
||||||
|
|
||||||
|
* `HttpServer::threads()` renamed to `HttpServer::workers()`.
|
||||||
|
|
||||||
|
* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated.
|
||||||
|
Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead.
|
||||||
|
|
||||||
|
* `HttpRequest::extensions()` returns read only reference to the request's Extension
|
||||||
|
`HttpRequest::extensions_mut()` returns mutable reference.
|
||||||
|
|
||||||
|
* Instead of
|
||||||
|
|
||||||
|
`use actix_web::middleware::{
|
||||||
|
CookieSessionBackend, CookieSessionError, RequestSession,
|
||||||
|
Session, SessionBackend, SessionImpl, SessionStorage};`
|
||||||
|
|
||||||
|
use `actix_web::middleware::session`
|
||||||
|
|
||||||
|
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError,
|
||||||
|
RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
|
||||||
|
|
||||||
|
* `FromRequest::from_request()` accepts mutable reference to a request
|
||||||
|
|
||||||
|
* `FromRequest::Result` has to implement `Into<Reply<Self>>`
|
||||||
|
|
||||||
|
* [`Responder::respond_to()`](
|
||||||
|
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
|
||||||
|
is generic over `S`
|
||||||
|
|
||||||
|
* Use `Query` extractor instead of HttpRequest::query()`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let q = Query::<HashMap<String, String>>::extract(req);
|
||||||
|
```
|
||||||
|
|
||||||
|
* Websocket operations are implemented as `WsWriter` trait.
|
||||||
|
you need to use `use actix_web::ws::WsWriter`
|
||||||
|
|
||||||
|
|
||||||
|
## 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`
|
14
Makefile
14
Makefile
@ -1,6 +1,6 @@
|
|||||||
.PHONY: default build test doc book clean
|
.PHONY: default build test doc book clean
|
||||||
|
|
||||||
CARGO_FLAGS := --features "$(FEATURES) alpn"
|
CARGO_FLAGS := --features "$(FEATURES) alpn tls"
|
||||||
|
|
||||||
default: test
|
default: test
|
||||||
|
|
||||||
@ -10,17 +10,5 @@ build:
|
|||||||
test: build clippy
|
test: build clippy
|
||||||
cargo test $(CARGO_FLAGS)
|
cargo test $(CARGO_FLAGS)
|
||||||
|
|
||||||
skeptic:
|
|
||||||
USE_SKEPTIC=1 cargo test $(CARGO_FLAGS)
|
|
||||||
|
|
||||||
# cd examples/word-count && python setup.py install && pytest -v tests
|
|
||||||
|
|
||||||
clippy:
|
|
||||||
if $$CLIPPY; then cargo clippy $(CARGO_FLAGS); fi
|
|
||||||
|
|
||||||
doc: build
|
doc: build
|
||||||
cargo doc --no-deps $(CARGO_FLAGS)
|
cargo doc --no-deps $(CARGO_FLAGS)
|
||||||
cd guide; mdbook build -d ../target/doc/guide/; cd ..
|
|
||||||
|
|
||||||
book:
|
|
||||||
cd guide; mdbook build -d ../target/doc/guide/; cd ..
|
|
||||||
|
75
README.md
75
README.md
@ -1,48 +1,49 @@
|
|||||||
# Actix web [](https://travis-ci.org/actix/actix-web) [](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-web) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
# Actix web [](https://travis-ci.org/actix/actix-web) [](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-web) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
Actix web is a simple, pragmatic, extremely fast, web framework for Rust.
|
Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
||||||
|
|
||||||
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.github.io/actix-web/guide/qs_13.html) protocols
|
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols
|
||||||
* Streaming and pipelining
|
* Streaming and pipelining
|
||||||
* Keep-alive and slow requests handling
|
* Keep-alive and slow requests handling
|
||||||
* Client/server [WebSockets](https://actix.github.io/actix-web/guide/qs_9.html) support
|
* Client/server [WebSockets](https://actix.rs/docs/websockets/) support
|
||||||
* Transparent content compression/decompression (br, gzip, deflate)
|
* Transparent content compression/decompression (br, gzip, deflate)
|
||||||
* Configurable [request routing](https://actix.github.io/actix-web/guide/qs_5.html)
|
* Configurable [request routing](https://actix.rs/docs/url-dispatch/)
|
||||||
* Graceful server shutdown
|
* Graceful server shutdown
|
||||||
* Multipart streams
|
* Multipart streams
|
||||||
* Static assets
|
* Static assets
|
||||||
* SSL support with openssl or native-tls
|
* SSL support with OpenSSL or `native-tls`
|
||||||
* Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging),
|
* Middlewares ([Logger](https://actix.rs/book/actix-web/sec-9-middlewares.html#logging),
|
||||||
[Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions),
|
[Session](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions),
|
||||||
[Redis sessions](https://github.com/actix/actix-redis),
|
[Redis sessions](https://github.com/actix/actix-redis),
|
||||||
[DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers),
|
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
|
||||||
[CORS](https://actix.github.io/actix-web/actix_web/middleware/cors/index.html),
|
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
|
||||||
[CSRF](https://actix.github.io/actix-web/actix_web/middleware/csrf/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).
|
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
|
||||||
|
* Built on top of [Actix actor framework](https://github.com/actix/actix)
|
||||||
|
|
||||||
## Documentation
|
## Documentation & community resources
|
||||||
|
|
||||||
* [User Guide](http://actix.github.io/actix-web/guide/)
|
* [User Guide](https://actix.rs/docs/)
|
||||||
* [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/)
|
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
|
||||||
* [API Documentation (Releases)](https://docs.rs/actix-web/)
|
* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/)
|
||||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||||
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
||||||
* Minimum supported Rust version: 1.21 or later
|
* Minimum supported Rust version: 1.26 or later
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
```rust,ignore
|
```rust
|
||||||
extern crate actix_web;
|
extern crate actix_web;
|
||||||
use actix_web::*;
|
use actix_web::{http, server, App, Path, Responder};
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> String {
|
fn index(info: Path<(u32, String)>) -> impl Responder {
|
||||||
format!("Hello {}!", &req.match_info()["name"])
|
format!("Hello {}! id:{}", info.1, info.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
HttpServer::new(
|
server::new(
|
||||||
|| Application::new()
|
|| App::new()
|
||||||
.resource("/{name}", |r| r.f(index)))
|
.route("/{id}/{name}/index.html", http::Method::GET, index))
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
@ -50,23 +51,25 @@ fn main() {
|
|||||||
|
|
||||||
### More examples
|
### More examples
|
||||||
|
|
||||||
* [Basics](https://github.com/actix/actix-web/tree/master/examples/basics/)
|
* [Basics](https://github.com/actix/examples/tree/master/basics/)
|
||||||
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/)
|
* [Stateful](https://github.com/actix/examples/tree/master/state/)
|
||||||
* [Protobuf support](https://github.com/actix/actix-web/tree/master/examples/protobuf/)
|
* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/)
|
||||||
* [Multipart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/)
|
* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/)
|
||||||
* [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket/)
|
* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/)
|
||||||
* [Tera templates](https://github.com/actix/actix-web/tree/master/examples/template_tera/)
|
* [Tera](https://github.com/actix/examples/tree/master/template_tera/) /
|
||||||
* [Diesel integration](https://github.com/actix/actix-web/tree/master/examples/diesel/)
|
[Askama](https://github.com/actix/examples/tree/master/template_askama/) templates
|
||||||
* [SSL / HTTP/2.0](https://github.com/actix/actix-web/tree/master/examples/tls/)
|
* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/)
|
||||||
* [Tcp/Websocket chat](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)
|
* [r2d2](https://github.com/actix/examples/tree/master/r2d2/)
|
||||||
* [Json](https://github.com/actix/actix-web/tree/master/examples/json/)
|
* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/)
|
||||||
|
* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/)
|
||||||
|
* [Json](https://github.com/actix/examples/tree/master/json/)
|
||||||
|
|
||||||
You may consider checking out
|
You may consider checking out
|
||||||
[this directory](https://github.com/actix/actix-web/tree/master/examples) for more examples.
|
[this directory](https://github.com/actix/examples/tree/master/) for more examples.
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext)
|
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext)
|
||||||
|
|
||||||
* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks).
|
* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks).
|
||||||
|
|
||||||
|
49
build.rs
49
build.rs
@ -1,48 +1,15 @@
|
|||||||
extern crate skeptic;
|
|
||||||
extern crate version_check;
|
extern crate version_check;
|
||||||
|
|
||||||
use std::{env, fs};
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo:rerun-if-env-changed=USE_SKEPTIC");
|
match version_check::is_min_version("1.26.0") {
|
||||||
let f = env::var("OUT_DIR").unwrap() + "/skeptic-tests.rs";
|
Some((true, _)) => println!("cargo:rustc-cfg=actix_impl_trait"),
|
||||||
if env::var("USE_SKEPTIC").is_ok() {
|
_ => (),
|
||||||
let _ = fs::remove_file(f);
|
};
|
||||||
// generates doc tests for `README.md`.
|
|
||||||
skeptic::generate_doc_tests(
|
|
||||||
&["README.md",
|
|
||||||
"guide/src/qs_1.md",
|
|
||||||
"guide/src/qs_2.md",
|
|
||||||
"guide/src/qs_3.md",
|
|
||||||
"guide/src/qs_3_5.md",
|
|
||||||
"guide/src/qs_4.md",
|
|
||||||
"guide/src/qs_4_5.md",
|
|
||||||
"guide/src/qs_5.md",
|
|
||||||
"guide/src/qs_7.md",
|
|
||||||
"guide/src/qs_8.md",
|
|
||||||
"guide/src/qs_9.md",
|
|
||||||
"guide/src/qs_10.md",
|
|
||||||
"guide/src/qs_12.md",
|
|
||||||
"guide/src/qs_13.md",
|
|
||||||
"guide/src/qs_14.md",
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
let _ = fs::File::create(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
match version_check::is_nightly() {
|
match version_check::is_nightly() {
|
||||||
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
|
Some(true) => {
|
||||||
Some(false) => (),
|
println!("cargo:rustc-cfg=actix_nightly");
|
||||||
None => (),
|
println!("cargo:rustc-cfg=actix_impl_trait");
|
||||||
};
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
fn main() {
|
|
||||||
match version_check::is_nightly() {
|
|
||||||
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
|
|
||||||
Some(false) => (),
|
Some(false) => (),
|
||||||
None => (),
|
None => (),
|
||||||
};
|
};
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "basics"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
futures = "*"
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path="../.." }
|
|
@ -1,20 +0,0 @@
|
|||||||
# basics
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/basics
|
|
||||||
cargo run
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
- [http://localhost:8080/index.html](http://localhost:8080/index.html)
|
|
||||||
- [http://localhost:8080/async/bob](http://localhost:8080/async/bob)
|
|
||||||
- [http://localhost:8080/user/bob/](http://localhost:8080/user/bob/) plain/text download
|
|
||||||
- [http://localhost:8080/test](http://localhost:8080/test) (return status switch GET or POST or other)
|
|
||||||
- [http://localhost:8080/static/index.html](http://localhost:8080/static/index.html)
|
|
||||||
- [http://localhost:8080/static/notexit](http://localhost:8080/static/notexit) display 404 page
|
|
@ -1,151 +0,0 @@
|
|||||||
#![allow(unused_variables)]
|
|
||||||
#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))]
|
|
||||||
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate futures;
|
|
||||||
use futures::Stream;
|
|
||||||
|
|
||||||
use std::{io, env};
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::middleware::RequestSession;
|
|
||||||
use futures::future::{FutureResult, result};
|
|
||||||
|
|
||||||
/// favicon handler
|
|
||||||
fn favicon(req: HttpRequest) -> Result<fs::NamedFile> {
|
|
||||||
Ok(fs::NamedFile::open("../static/favicon.ico")?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// simple index handler
|
|
||||||
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
// example of ...
|
|
||||||
if let Ok(ch) = req.poll() {
|
|
||||||
if let futures::Async::Ready(Some(d)) = ch {
|
|
||||||
println!("{}", String::from_utf8_lossy(d.as_ref()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// session
|
|
||||||
let mut counter = 1;
|
|
||||||
if let Some(count) = req.session().get::<i32>("counter")? {
|
|
||||||
println!("SESSION value: {}", count);
|
|
||||||
counter = count + 1;
|
|
||||||
req.session().set("counter", counter)?;
|
|
||||||
} else {
|
|
||||||
req.session().set("counter", counter)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// html
|
|
||||||
let html = format!(r#"<!DOCTYPE html><html><head><title>actix - basics</title><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /></head>
|
|
||||||
<body>
|
|
||||||
<h1>Welcome <img width="30px" height="30px" src="/static/actixLogo.png" /></h1>
|
|
||||||
session counter = {}
|
|
||||||
</body>
|
|
||||||
</html>"#, counter);
|
|
||||||
|
|
||||||
// response
|
|
||||||
Ok(HttpResponse::build(StatusCode::OK)
|
|
||||||
.content_type("text/html; charset=utf-8")
|
|
||||||
.body(&html).unwrap())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 404 handler
|
|
||||||
fn p404(req: HttpRequest) -> Result<HttpResponse> {
|
|
||||||
|
|
||||||
// html
|
|
||||||
let html = r#"<!DOCTYPE html><html><head><title>actix - basics</title><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /></head>
|
|
||||||
<body>
|
|
||||||
<a href="index.html">back to home</a>
|
|
||||||
<h1>404</h1>
|
|
||||||
</body>
|
|
||||||
</html>"#;
|
|
||||||
|
|
||||||
// response
|
|
||||||
Ok(HttpResponse::build(StatusCode::NOT_FOUND)
|
|
||||||
.content_type("text/html; charset=utf-8")
|
|
||||||
.body(html).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// async handler
|
|
||||||
fn index_async(req: HttpRequest) -> FutureResult<HttpResponse, Error>
|
|
||||||
{
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
result(HttpResponse::Ok()
|
|
||||||
.content_type("text/html")
|
|
||||||
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))
|
|
||||||
.map_err(|e| e.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// handler with path parameters like `/user/{name}/`
|
|
||||||
fn with_param(req: HttpRequest) -> Result<HttpResponse>
|
|
||||||
{
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
|
||||||
.content_type("test/plain")
|
|
||||||
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
env::set_var("RUST_LOG", "actix_web=debug");
|
|
||||||
env::set_var("RUST_BACKTRACE", "1");
|
|
||||||
env_logger::init();
|
|
||||||
let sys = actix::System::new("basic-example");
|
|
||||||
|
|
||||||
let addr = HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
// cookie session middleware
|
|
||||||
.middleware(middleware::SessionStorage::new(
|
|
||||||
middleware::CookieSessionBackend::build(&[0; 32])
|
|
||||||
.secure(false)
|
|
||||||
.finish()
|
|
||||||
))
|
|
||||||
// register favicon
|
|
||||||
.resource("/favicon.ico", |r| r.f(favicon))
|
|
||||||
// register simple route, handle all methods
|
|
||||||
.resource("/index.html", |r| r.f(index))
|
|
||||||
// with path parameters
|
|
||||||
.resource("/user/{name}/", |r| r.method(Method::GET).f(with_param))
|
|
||||||
// async handler
|
|
||||||
.resource("/async/{name}", |r| r.method(Method::GET).a(index_async))
|
|
||||||
.resource("/test", |r| r.f(|req| {
|
|
||||||
match *req.method() {
|
|
||||||
Method::GET => httpcodes::HTTPOk,
|
|
||||||
Method::POST => httpcodes::HTTPMethodNotAllowed,
|
|
||||||
_ => httpcodes::HTTPNotFound,
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.resource("/error.html", |r| r.f(|req| {
|
|
||||||
error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "test"))
|
|
||||||
}))
|
|
||||||
// static files
|
|
||||||
.handler("/static/", fs::StaticFiles::new("../static/", true))
|
|
||||||
// redirect
|
|
||||||
.resource("/", |r| r.method(Method::GET).f(|req| {
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
HttpResponse::Found()
|
|
||||||
.header("LOCATION", "/index.html")
|
|
||||||
.finish()
|
|
||||||
}))
|
|
||||||
// default
|
|
||||||
.default_resource(|r| {
|
|
||||||
r.method(Method::GET).f(p404);
|
|
||||||
r.route().filter(pred::Not(pred::Get())).f(|req| httpcodes::HTTPMethodNotAllowed);
|
|
||||||
}))
|
|
||||||
|
|
||||||
.bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080")
|
|
||||||
.shutdown_timeout(0) // <- Set shutdown timeout to 0 seconds (default 60s)
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Starting http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
DATABASE_URL=file:test.db
|
|
@ -1,19 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "diesel-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../" }
|
|
||||||
|
|
||||||
futures = "0.1"
|
|
||||||
uuid = { version = "0.5", features = ["serde", "v4"] }
|
|
||||||
serde = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
|
|
||||||
diesel = { version = "1.0.0-beta1", features = ["sqlite"] }
|
|
||||||
dotenv = "0.10"
|
|
@ -1,43 +0,0 @@
|
|||||||
# diesel
|
|
||||||
|
|
||||||
Diesel's `Getting Started` guide using SQLite for Actix web
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### init database sqlite
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install diesel_cli --no-default-features --features sqlite
|
|
||||||
cd actix-web/examples/diesel
|
|
||||||
echo "DATABASE_URL=file:test.db" > .env
|
|
||||||
diesel migration run
|
|
||||||
```
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# if ubuntu : sudo apt-get install libsqlite3-dev
|
|
||||||
# if fedora : sudo dnf install libsqlite3x-devel
|
|
||||||
cd actix-web/examples/diesel
|
|
||||||
cargo run (or ``cargo watch -x run``)
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
[http://127.0.0.1:8080/NAME](http://127.0.0.1:8080/NAME)
|
|
||||||
|
|
||||||
### sqlite client
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# if ubuntu : sudo apt-get install sqlite3
|
|
||||||
# if fedora : sudo dnf install sqlite3x
|
|
||||||
sqlite3 test.db
|
|
||||||
sqlite> .tables
|
|
||||||
sqlite> select * from users;
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Postgresql
|
|
||||||
|
|
||||||
You will also find another complete example of diesel+postgresql on [https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/actix](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/actix)
|
|
@ -1 +0,0 @@
|
|||||||
DROP TABLE users
|
|
@ -1,4 +0,0 @@
|
|||||||
CREATE TABLE users (
|
|
||||||
id VARCHAR NOT NULL PRIMARY KEY,
|
|
||||||
name VARCHAR NOT NULL
|
|
||||||
)
|
|
@ -1,52 +0,0 @@
|
|||||||
//! Db executor actor
|
|
||||||
use uuid;
|
|
||||||
use diesel;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix::prelude::*;
|
|
||||||
use diesel::prelude::*;
|
|
||||||
|
|
||||||
use models;
|
|
||||||
use schema;
|
|
||||||
|
|
||||||
/// This is db executor actor. We are going to run 3 of them in parallel.
|
|
||||||
pub struct DbExecutor(pub SqliteConnection);
|
|
||||||
|
|
||||||
/// This is only message that this actor can handle, but it is easy to extend number of
|
|
||||||
/// messages.
|
|
||||||
pub struct CreateUser {
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message for CreateUser {
|
|
||||||
type Result = Result<models::User, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for DbExecutor {
|
|
||||||
type Context = SyncContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler<CreateUser> for DbExecutor {
|
|
||||||
type Result = Result<models::User, Error>;
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result {
|
|
||||||
use self::schema::users::dsl::*;
|
|
||||||
|
|
||||||
let uuid = format!("{}", uuid::Uuid::new_v4());
|
|
||||||
let new_user = models::NewUser {
|
|
||||||
id: &uuid,
|
|
||||||
name: &msg.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
diesel::insert_into(users)
|
|
||||||
.values(&new_user)
|
|
||||||
.execute(&self.0)
|
|
||||||
.expect("Error inserting person");
|
|
||||||
|
|
||||||
let mut items = users
|
|
||||||
.filter(id.eq(&uuid))
|
|
||||||
.load::<models::User>(&self.0)
|
|
||||||
.expect("Error loading person");
|
|
||||||
|
|
||||||
Ok(items.pop().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
//! Actix web diesel example
|
|
||||||
//!
|
|
||||||
//! Diesel does not support tokio, so we have to run it in separate threads.
|
|
||||||
//! Actix supports sync actors by default, so we going to create sync actor that use diesel.
|
|
||||||
//! Technically sync actors are worker style actors, multiple of them
|
|
||||||
//! can run in parallel and process messages from same queue.
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate diesel;
|
|
||||||
extern crate uuid;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
use diesel::prelude::*;
|
|
||||||
use futures::future::Future;
|
|
||||||
|
|
||||||
mod db;
|
|
||||||
mod models;
|
|
||||||
mod schema;
|
|
||||||
|
|
||||||
use db::{CreateUser, DbExecutor};
|
|
||||||
|
|
||||||
|
|
||||||
/// State with DbExecutor address
|
|
||||||
struct State {
|
|
||||||
db: Addr<Syn, DbExecutor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Async request handler
|
|
||||||
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
let name = &req.match_info()["name"];
|
|
||||||
|
|
||||||
// send async `CreateUser` message to a `DbExecutor`
|
|
||||||
req.state().db.send(CreateUser{name: name.to_owned()})
|
|
||||||
.from_err()
|
|
||||||
.and_then(|res| {
|
|
||||||
match res {
|
|
||||||
Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?),
|
|
||||||
Err(_) => Ok(httpcodes::HTTPInternalServerError.into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("diesel-example");
|
|
||||||
|
|
||||||
// Start 3 db executor actors
|
|
||||||
let addr = SyncArbiter::start(3, || {
|
|
||||||
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start http server
|
|
||||||
let _addr = HttpServer::new(move || {
|
|
||||||
Application::with_state(State{db: addr.clone()})
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/{name}", |r| r.method(Method::GET).a(index))})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
use super::schema::users;
|
|
||||||
|
|
||||||
#[derive(Serialize, Queryable)]
|
|
||||||
pub struct User {
|
|
||||||
pub id: String,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Insertable)]
|
|
||||||
#[table_name = "users"]
|
|
||||||
pub struct NewUser<'a> {
|
|
||||||
pub id: &'a str,
|
|
||||||
pub name: &'a str,
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
table! {
|
|
||||||
users (id) {
|
|
||||||
id -> Text,
|
|
||||||
name -> Text,
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
@ -1,10 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "hello-world"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../" }
|
|
@ -1,28 +0,0 @@
|
|||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
|
|
||||||
fn index(_req: HttpRequest) -> &'static str {
|
|
||||||
"Hello world!"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("ws-example");
|
|
||||||
|
|
||||||
let _addr = HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/index.html", |r| r.f(|_| "Hello world!"))
|
|
||||||
.resource("/", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
[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"] }
|
|
@ -1,59 +0,0 @@
|
|||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use actix_web::*;
|
|
||||||
use futures::{Future, Stream};
|
|
||||||
|
|
||||||
|
|
||||||
/// 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::Error::from) // <- convert SendRequestError to an Error
|
|
||||||
.and_then(
|
|
||||||
|resp| resp.body() // <- this is MessageBody type, resolves to complete body
|
|
||||||
.from_err() // <- convet PayloadError to a Error
|
|
||||||
.and_then(|body| { // <- we got complete body, now send as server response
|
|
||||||
httpcodes::HttpOk.build()
|
|
||||||
.body(body)
|
|
||||||
.map_err(error::Error::from)
|
|
||||||
}))
|
|
||||||
.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::Error::from) // <- convert SendRequestError to an Error
|
|
||||||
.and_then(|resp| { // <- we received client response
|
|
||||||
httpcodes::HttpOk.build()
|
|
||||||
// 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())))
|
|
||||||
.map_err(|e| e.into()) // HttpOk::build() mayb return HttpError, we need to convert it to a Error
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
env_logger::init();
|
|
||||||
let sys = actix::System::new("http-proxy");
|
|
||||||
|
|
||||||
let _addr = HttpServer::new(
|
|
||||||
|| Application::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();
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "json-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bytes = "0.4"
|
|
||||||
futures = "0.1"
|
|
||||||
env_logger = "*"
|
|
||||||
|
|
||||||
serde = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
json = "*"
|
|
||||||
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path="../../" }
|
|
@ -1,48 +0,0 @@
|
|||||||
# json
|
|
||||||
|
|
||||||
Json's `Getting Started` guide using json (serde-json or json-rust) for Actix web
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/json
|
|
||||||
cargo run
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
With [Postman](https://www.getpostman.com/) or [Rested](moz-extension://60daeb1c-5b1b-4afd-9842-0579ed34dfcb/dist/index.html)
|
|
||||||
|
|
||||||
- POST / (embed serde-json):
|
|
||||||
|
|
||||||
- method : ``POST``
|
|
||||||
- url : ``http://127.0.0.1:8080/``
|
|
||||||
- header : ``Content-Type`` = ``application/json``
|
|
||||||
- body (raw) : ``{"name": "Test user", "number": 100}``
|
|
||||||
|
|
||||||
- POST /manual (manual serde-json):
|
|
||||||
|
|
||||||
- method : ``POST``
|
|
||||||
- url : ``http://127.0.0.1:8080/manual``
|
|
||||||
- header : ``Content-Type`` = ``application/json``
|
|
||||||
- body (raw) : ``{"name": "Test user", "number": 100}``
|
|
||||||
|
|
||||||
- POST /mjsonrust (manual json-rust):
|
|
||||||
|
|
||||||
- method : ``POST``
|
|
||||||
- url : ``http://127.0.0.1:8080/mjsonrust``
|
|
||||||
- header : ``Content-Type`` = ``application/json``
|
|
||||||
- body (raw) : ``{"name": "Test user", "number": 100}`` (you can also test ``{notjson}``)
|
|
||||||
|
|
||||||
### python client
|
|
||||||
|
|
||||||
- ``pip install aiohttp``
|
|
||||||
- ``python client.py``
|
|
||||||
|
|
||||||
if ubuntu :
|
|
||||||
|
|
||||||
- ``pip3 install aiohttp``
|
|
||||||
- ``python3 client.py``
|
|
@ -1,18 +0,0 @@
|
|||||||
# This script could be used for actix-web multipart example test
|
|
||||||
# just start server and run client.py
|
|
||||||
|
|
||||||
import json
|
|
||||||
import asyncio
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
async def req():
|
|
||||||
resp = await aiohttp.ClientSession().request(
|
|
||||||
"post", 'http://localhost:8080/',
|
|
||||||
data=json.dumps({"name": "Test user", "number": 100}),
|
|
||||||
headers={"content-type": "application/json"})
|
|
||||||
print(str(resp))
|
|
||||||
print(await resp.text())
|
|
||||||
assert 200 == resp.status
|
|
||||||
|
|
||||||
|
|
||||||
asyncio.get_event_loop().run_until_complete(req())
|
|
@ -1,99 +0,0 @@
|
|||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate bytes;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate serde_json;
|
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
#[macro_use] extern crate json;
|
|
||||||
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
use bytes::BytesMut;
|
|
||||||
use futures::{Future, Stream};
|
|
||||||
use json::JsonValue;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct MyObj {
|
|
||||||
name: String,
|
|
||||||
number: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This handler uses `HttpRequest::json()` for loading serde json object.
|
|
||||||
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
req.json()
|
|
||||||
.from_err() // convert all errors into `Error`
|
|
||||||
.and_then(|val: MyObj| {
|
|
||||||
println!("model: {:?}", val);
|
|
||||||
Ok(httpcodes::HTTPOk.build().json(val)?) // <- send response
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const MAX_SIZE: usize = 262_144; // max payload size is 256k
|
|
||||||
|
|
||||||
/// This handler manually load request payload and parse serde json
|
|
||||||
fn index_manual(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
// HttpRequest is stream of Bytes objects
|
|
||||||
req
|
|
||||||
// `Future::from_err` acts like `?` in that it coerces the error type from
|
|
||||||
// the future into the final error type
|
|
||||||
.from_err()
|
|
||||||
|
|
||||||
// `fold` will asynchronously read each chunk of the request body and
|
|
||||||
// call supplied closure, then it resolves to result of closure
|
|
||||||
.fold(BytesMut::new(), move |mut body, chunk| {
|
|
||||||
// limit max size of in-memory payload
|
|
||||||
if (body.len() + chunk.len()) > MAX_SIZE {
|
|
||||||
Err(error::ErrorBadRequest("overflow"))
|
|
||||||
} else {
|
|
||||||
body.extend_from_slice(&chunk);
|
|
||||||
Ok(body)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// `Future::and_then` can be used to merge an asynchronous workflow with a
|
|
||||||
// synchronous workflow
|
|
||||||
.and_then(|body| {
|
|
||||||
// body is loaded, now we can deserialize serde-json
|
|
||||||
let obj = serde_json::from_slice::<MyObj>(&body)?;
|
|
||||||
Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This handler manually load request payload and parse json-rust
|
|
||||||
fn index_mjsonrust(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
req.concat2()
|
|
||||||
.from_err()
|
|
||||||
.and_then(|body| {
|
|
||||||
// body is loaded, now we can deserialize json-rust
|
|
||||||
let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result
|
|
||||||
let injson: JsonValue = match result { Ok(v) => v, Err(e) => object!{"err" => e.to_string() } };
|
|
||||||
Ok(HttpResponse::build(StatusCode::OK)
|
|
||||||
.content_type("application/json")
|
|
||||||
.body(injson.dump()).unwrap())
|
|
||||||
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("json-example");
|
|
||||||
|
|
||||||
let addr = HttpServer::new(|| {
|
|
||||||
Application::new()
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/manual", |r| r.method(Method::POST).f(index_manual))
|
|
||||||
.resource("/mjsonrust", |r| r.method(Method::POST).f(index_mjsonrust))
|
|
||||||
.resource("/", |r| r.method(Method::POST).f(index))})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.shutdown_timeout(1)
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "juniper-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["pyros2097 <pyros2097@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../" }
|
|
||||||
|
|
||||||
futures = "0.1"
|
|
||||||
serde = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
|
|
||||||
juniper = "0.9.2"
|
|
@ -1,15 +0,0 @@
|
|||||||
# juniper
|
|
||||||
|
|
||||||
Juniper integration for Actix web
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/juniper
|
|
||||||
cargo run (or ``cargo watch -x run``)
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
[http://127.0.0.1:8080/graphiql](http://127.0.0.1:8080/graphiql)
|
|
@ -1,110 +0,0 @@
|
|||||||
//! Actix web juniper example
|
|
||||||
//!
|
|
||||||
//! A simple example integrating juniper in actix-web
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate juniper;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
use juniper::http::graphiql::graphiql_source;
|
|
||||||
use juniper::http::GraphQLRequest;
|
|
||||||
|
|
||||||
use futures::future::Future;
|
|
||||||
|
|
||||||
mod schema;
|
|
||||||
|
|
||||||
use schema::Schema;
|
|
||||||
use schema::create_schema;
|
|
||||||
|
|
||||||
struct State {
|
|
||||||
executor: Addr<Syn, GraphQLExecutor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct GraphQLData(GraphQLRequest);
|
|
||||||
|
|
||||||
impl Message for GraphQLData {
|
|
||||||
type Result = Result<String, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GraphQLExecutor {
|
|
||||||
schema: std::sync::Arc<Schema>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GraphQLExecutor {
|
|
||||||
fn new(schema: std::sync::Arc<Schema>) -> GraphQLExecutor {
|
|
||||||
GraphQLExecutor {
|
|
||||||
schema: schema,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for GraphQLExecutor {
|
|
||||||
type Context = SyncContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler<GraphQLData> for GraphQLExecutor {
|
|
||||||
type Result = Result<String, Error>;
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: GraphQLData, _: &mut Self::Context) -> Self::Result {
|
|
||||||
let res = msg.0.execute(&self.schema, &());
|
|
||||||
let res_text = serde_json::to_string(&res)?;
|
|
||||||
Ok(res_text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn graphiql(_req: HttpRequest<State>) -> Result<HttpResponse> {
|
|
||||||
let html = graphiql_source("http://127.0.0.1:8080/graphql");
|
|
||||||
Ok(HttpResponse::build(StatusCode::OK)
|
|
||||||
.content_type("text/html; charset=utf-8")
|
|
||||||
.body(html).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn graphql(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
let executor = req.state().executor.clone();
|
|
||||||
req.json()
|
|
||||||
.from_err()
|
|
||||||
.and_then(move |val: GraphQLData| {
|
|
||||||
executor.send(val)
|
|
||||||
.from_err()
|
|
||||||
.and_then(|res| {
|
|
||||||
match res {
|
|
||||||
Ok(user) => Ok(httpcodes::HTTPOk.build().body(user)?),
|
|
||||||
Err(_) => Ok(httpcodes::HTTPInternalServerError.into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("juniper-example");
|
|
||||||
|
|
||||||
let schema = std::sync::Arc::new(create_schema());
|
|
||||||
let addr = SyncArbiter::start(3, move || {
|
|
||||||
GraphQLExecutor::new(schema.clone())
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start http server
|
|
||||||
let _addr = HttpServer::new(move || {
|
|
||||||
Application::with_state(State{executor: addr.clone()})
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/graphql", |r| r.method(Method::POST).a(graphql))
|
|
||||||
.resource("/graphiql", |r| r.method(Method::GET).f(graphiql))})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
use juniper::FieldResult;
|
|
||||||
use juniper::RootNode;
|
|
||||||
|
|
||||||
#[derive(GraphQLEnum)]
|
|
||||||
enum Episode {
|
|
||||||
NewHope,
|
|
||||||
Empire,
|
|
||||||
Jedi,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
|
||||||
#[graphql(description = "A humanoid creature in the Star Wars universe")]
|
|
||||||
struct Human {
|
|
||||||
id: String,
|
|
||||||
name: String,
|
|
||||||
appears_in: Vec<Episode>,
|
|
||||||
home_planet: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(GraphQLInputObject)]
|
|
||||||
#[graphql(description = "A humanoid creature in the Star Wars universe")]
|
|
||||||
struct NewHuman {
|
|
||||||
name: String,
|
|
||||||
appears_in: Vec<Episode>,
|
|
||||||
home_planet: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct QueryRoot;
|
|
||||||
|
|
||||||
graphql_object!(QueryRoot: () |&self| {
|
|
||||||
field human(&executor, id: String) -> FieldResult<Human> {
|
|
||||||
Ok(Human{
|
|
||||||
id: "1234".to_owned(),
|
|
||||||
name: "Luke".to_owned(),
|
|
||||||
appears_in: vec![Episode::NewHope],
|
|
||||||
home_planet: "Mars".to_owned(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pub struct MutationRoot;
|
|
||||||
|
|
||||||
graphql_object!(MutationRoot: () |&self| {
|
|
||||||
field createHuman(&executor, new_human: NewHuman) -> FieldResult<Human> {
|
|
||||||
Ok(Human{
|
|
||||||
id: "1234".to_owned(),
|
|
||||||
name: new_human.name,
|
|
||||||
appears_in: new_human.appears_in,
|
|
||||||
home_planet: new_human.home_planet,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pub type Schema = RootNode<'static, QueryRoot, MutationRoot>;
|
|
||||||
|
|
||||||
pub fn create_schema() -> Schema {
|
|
||||||
Schema::new(QueryRoot {}, MutationRoot {})
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "multipart-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "multipart"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "*"
|
|
||||||
futures = "0.1"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path="../../" }
|
|
@ -1,24 +0,0 @@
|
|||||||
# multipart
|
|
||||||
|
|
||||||
Multipart's `Getting Started` guide for Actix web
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/multipart
|
|
||||||
cargo run (or ``cargo watch -x run``)
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### client
|
|
||||||
|
|
||||||
- ``pip install aiohttp``
|
|
||||||
- ``python client.py``
|
|
||||||
- you must see in server console multipart fields
|
|
||||||
|
|
||||||
if ubuntu :
|
|
||||||
|
|
||||||
- ``pip3 install aiohttp``
|
|
||||||
- ``python3 client.py``
|
|
@ -1,34 +0,0 @@
|
|||||||
# This script could be used for actix-web multipart example test
|
|
||||||
# just start server and run client.py
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
async def req1():
|
|
||||||
with aiohttp.MultipartWriter() as writer:
|
|
||||||
writer.append('test')
|
|
||||||
writer.append_json({'passed': True})
|
|
||||||
|
|
||||||
resp = await aiohttp.ClientSession().request(
|
|
||||||
"post", 'http://localhost:8080/multipart',
|
|
||||||
data=writer, headers=writer.headers)
|
|
||||||
print(resp)
|
|
||||||
assert 200 == resp.status
|
|
||||||
|
|
||||||
|
|
||||||
async def req2():
|
|
||||||
with aiohttp.MultipartWriter() as writer:
|
|
||||||
writer.append('test')
|
|
||||||
writer.append_json({'passed': True})
|
|
||||||
writer.append(open('src/main.rs'))
|
|
||||||
|
|
||||||
resp = await aiohttp.ClientSession().request(
|
|
||||||
"post", 'http://localhost:8080/multipart',
|
|
||||||
data=writer, headers=writer.headers)
|
|
||||||
print(resp)
|
|
||||||
assert 200 == resp.status
|
|
||||||
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.run_until_complete(req1())
|
|
||||||
loop.run_until_complete(req2())
|
|
@ -1,59 +0,0 @@
|
|||||||
#![allow(unused_variables)]
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate futures;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
use futures::{Future, Stream};
|
|
||||||
use futures::future::{result, Either};
|
|
||||||
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>>
|
|
||||||
{
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
req.multipart() // <- get multipart stream for current request
|
|
||||||
.from_err() // <- convert multipart errors
|
|
||||||
.and_then(|item| { // <- iterate over multipart items
|
|
||||||
match item {
|
|
||||||
// Handle multipart Field
|
|
||||||
multipart::MultipartItem::Field(field) => {
|
|
||||||
println!("==== FIELD ==== {:?}", field);
|
|
||||||
|
|
||||||
// Field in turn is stream of *Bytes* object
|
|
||||||
Either::A(
|
|
||||||
field.map_err(Error::from)
|
|
||||||
.map(|chunk| {
|
|
||||||
println!("-- CHUNK: \n{}",
|
|
||||||
std::str::from_utf8(&chunk).unwrap());})
|
|
||||||
.finish())
|
|
||||||
},
|
|
||||||
multipart::MultipartItem::Nested(mp) => {
|
|
||||||
// Or item could be nested Multipart stream
|
|
||||||
Either::B(result(Ok(())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finish() // <- Stream::finish() combinator from actix
|
|
||||||
.map(|_| httpcodes::HTTPOk.into())
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("multipart-example");
|
|
||||||
|
|
||||||
let addr = HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.middleware(middleware::Logger::default()) // <- logger
|
|
||||||
.resource("/multipart", |r| r.method(Method::POST).a(index)))
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Starting http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
[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="../../" }
|
|
@ -1,66 +0,0 @@
|
|||||||
# 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()
|
|
@ -1,55 +0,0 @@
|
|||||||
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 actix_web::*;
|
|
||||||
use futures::Future;
|
|
||||||
|
|
||||||
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(httpcodes::HTTPOk.build().protobuf(val)?) // <- send response
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("protobuf-example");
|
|
||||||
|
|
||||||
let addr = HttpServer::new(|| {
|
|
||||||
Application::new()
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/", |r| r.method(Method::POST).f(index))})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.shutdown_timeout(1)
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,169 +0,0 @@
|
|||||||
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::header::http::{CONTENT_TYPE, CONTENT_LENGTH};
|
|
||||||
use actix_web::{Responder, HttpMessage, HttpRequest, HttpResponse};
|
|
||||||
use actix_web::dev::HttpResponseBuilder;
|
|
||||||
use actix_web::error::{Error, PayloadError, ResponseError};
|
|
||||||
use actix_web::httpcodes::{HttpBadRequest, HttpPayloadTooLarge};
|
|
||||||
|
|
||||||
|
|
||||||
#[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 => HttpPayloadTooLarge.into(),
|
|
||||||
_ => HttpBadRequest.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)?)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
message MyObj {
|
|
||||||
int32 number = 1;
|
|
||||||
string name = 2;
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
# 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)
|
|
@ -1,20 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "r2d2-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../" }
|
|
||||||
|
|
||||||
futures = "0.1"
|
|
||||||
uuid = { version = "0.5", features = ["serde", "v4"] }
|
|
||||||
serde = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
|
|
||||||
r2d2 = "*"
|
|
||||||
r2d2_sqlite = "*"
|
|
||||||
rusqlite = "*"
|
|
@ -1,41 +0,0 @@
|
|||||||
//! Db executor actor
|
|
||||||
use std::io;
|
|
||||||
use uuid;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix::prelude::*;
|
|
||||||
use r2d2::Pool;
|
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
|
||||||
|
|
||||||
|
|
||||||
/// This is db executor actor. We are going to run 3 of them in parallel.
|
|
||||||
pub struct DbExecutor(pub Pool<SqliteConnectionManager>);
|
|
||||||
|
|
||||||
/// This is only message that this actor can handle, but it is easy to extend number of
|
|
||||||
/// messages.
|
|
||||||
pub struct CreateUser {
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message for CreateUser {
|
|
||||||
type Result = Result<String, io::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for DbExecutor {
|
|
||||||
type Context = SyncContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler<CreateUser> for DbExecutor {
|
|
||||||
type Result = Result<String, io::Error>;
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result {
|
|
||||||
let conn = self.0.get().unwrap();
|
|
||||||
|
|
||||||
let uuid = format!("{}", uuid::Uuid::new_v4());
|
|
||||||
conn.execute("INSERT INTO users (id, name) VALUES ($1, $2)",
|
|
||||||
&[&uuid, &msg.name]).unwrap();
|
|
||||||
|
|
||||||
Ok(conn.query_row("SELECT name FROM users WHERE id=$1", &[&uuid], |row| {
|
|
||||||
row.get(0)
|
|
||||||
}).map_err(|_| io::Error::new(io::ErrorKind::Other, "db error"))?)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
//! Actix web r2d2 example
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
extern crate uuid;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate r2d2;
|
|
||||||
extern crate r2d2_sqlite;
|
|
||||||
extern crate rusqlite;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
use futures::future::Future;
|
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
|
||||||
|
|
||||||
mod db;
|
|
||||||
use db::{CreateUser, DbExecutor};
|
|
||||||
|
|
||||||
|
|
||||||
/// State with DbExecutor address
|
|
||||||
struct State {
|
|
||||||
db: Addr<Syn, DbExecutor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Async request handler
|
|
||||||
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
let name = &req.match_info()["name"];
|
|
||||||
|
|
||||||
req.state().db.send(CreateUser{name: name.to_owned()})
|
|
||||||
.from_err()
|
|
||||||
.and_then(|res| {
|
|
||||||
match res {
|
|
||||||
Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?),
|
|
||||||
Err(_) => Ok(httpcodes::HTTPInternalServerError.into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=debug");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("r2d2-example");
|
|
||||||
|
|
||||||
// r2d2 pool
|
|
||||||
let manager = SqliteConnectionManager::file("test.db");
|
|
||||||
let pool = r2d2::Pool::new(manager).unwrap();
|
|
||||||
|
|
||||||
// Start db executor actors
|
|
||||||
let addr = SyncArbiter::start(3, move || DbExecutor(pool.clone()));
|
|
||||||
|
|
||||||
// Start http server
|
|
||||||
let _addr = HttpServer::new(move || {
|
|
||||||
Application::with_state(State{db: addr.clone()})
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/{name}", |r| r.method(Method::GET).a(index))})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
Binary file not shown.
@ -1,11 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "redis-session"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = "0.4"
|
|
||||||
actix-redis = { version = "0.2", features = ["web"] }
|
|
@ -1,48 +0,0 @@
|
|||||||
#![allow(unused_variables)]
|
|
||||||
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate actix_redis;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::middleware::RequestSession;
|
|
||||||
use actix_redis::RedisSessionBackend;
|
|
||||||
|
|
||||||
|
|
||||||
/// simple handler
|
|
||||||
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
// session
|
|
||||||
if let Some(count) = req.session().get::<i32>("counter")? {
|
|
||||||
println!("SESSION value: {}", count);
|
|
||||||
req.session().set("counter", count+1)?;
|
|
||||||
} else {
|
|
||||||
req.session().set("counter", 1)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok("Welcome!".into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info");
|
|
||||||
env_logger::init();
|
|
||||||
let sys = actix::System::new("basic-example");
|
|
||||||
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
// cookie session middleware
|
|
||||||
.middleware(middleware::SessionStorage::new(
|
|
||||||
RedisSessionBackend::new("127.0.0.1:6379", &[0; 32])
|
|
||||||
))
|
|
||||||
// register simple route, handle all methods
|
|
||||||
.resource("/", |r| r.f(index)))
|
|
||||||
.bind("0.0.0.0:8080").unwrap()
|
|
||||||
.threads(1)
|
|
||||||
.start();
|
|
||||||
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "state"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
futures = "*"
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../" }
|
|
@ -1,15 +0,0 @@
|
|||||||
# state
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/state
|
|
||||||
cargo run
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
- [http://localhost:8080/](http://localhost:8080/)
|
|
@ -1,76 +0,0 @@
|
|||||||
#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))]
|
|
||||||
//! There are two level of statefulness in actix-web. Application has state
|
|
||||||
//! that is shared across all handlers within same Application.
|
|
||||||
//! And individual handler can have state.
|
|
||||||
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use std::cell::Cell;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
/// Application state
|
|
||||||
struct AppState {
|
|
||||||
counter: Cell<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// somple handle
|
|
||||||
fn index(req: HttpRequest<AppState>) -> HttpResponse {
|
|
||||||
println!("{:?}", req);
|
|
||||||
req.state().counter.set(req.state().counter.get() + 1);
|
|
||||||
|
|
||||||
httpcodes::HTTPOk.with_body(
|
|
||||||
format!("Num of requests: {}", req.state().counter.get()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `MyWebSocket` counts how many messages it receives from peer,
|
|
||||||
/// websocket-client.py could be used for tests
|
|
||||||
struct MyWebSocket {
|
|
||||||
counter: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for MyWebSocket {
|
|
||||||
type Context = ws::WebsocketContext<Self, AppState>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
|
||||||
self.counter += 1;
|
|
||||||
println!("WS({}): {:?}", self.counter, msg);
|
|
||||||
match msg {
|
|
||||||
ws::Message::Ping(msg) => ctx.pong(&msg),
|
|
||||||
ws::Message::Text(text) => ctx.text(text),
|
|
||||||
ws::Message::Binary(bin) => ctx.binary(bin),
|
|
||||||
ws::Message::Close(_) => {
|
|
||||||
ctx.stop();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("ws-example");
|
|
||||||
|
|
||||||
let addr = HttpServer::new(
|
|
||||||
|| Application::with_state(AppState{counter: Cell::new(0)})
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
// websocket route
|
|
||||||
.resource(
|
|
||||||
"/ws/", |r|
|
|
||||||
r.method(Method::GET).f(|req| ws::start(req, MyWebSocket{counter: 0})))
|
|
||||||
// register simple handler, handle all methods
|
|
||||||
.resource("/", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
@ -1,90 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
|
|
||||||
</script>
|
|
||||||
<script language="javascript" type="text/javascript">
|
|
||||||
$(function() {
|
|
||||||
var conn = null;
|
|
||||||
function log(msg) {
|
|
||||||
var control = $('#log');
|
|
||||||
control.html(control.html() + msg + '<br/>');
|
|
||||||
control.scrollTop(control.scrollTop() + 1000);
|
|
||||||
}
|
|
||||||
function connect() {
|
|
||||||
disconnect();
|
|
||||||
var wsUri = (window.location.protocol=='https:'&&'wss://'||'ws://')+window.location.host + '/ws/';
|
|
||||||
conn = new WebSocket(wsUri);
|
|
||||||
log('Connecting...');
|
|
||||||
conn.onopen = function() {
|
|
||||||
log('Connected.');
|
|
||||||
update_ui();
|
|
||||||
};
|
|
||||||
conn.onmessage = function(e) {
|
|
||||||
log('Received: ' + e.data);
|
|
||||||
};
|
|
||||||
conn.onclose = function() {
|
|
||||||
log('Disconnected.');
|
|
||||||
conn = null;
|
|
||||||
update_ui();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function disconnect() {
|
|
||||||
if (conn != null) {
|
|
||||||
log('Disconnecting...');
|
|
||||||
conn.close();
|
|
||||||
conn = null;
|
|
||||||
update_ui();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function update_ui() {
|
|
||||||
var msg = '';
|
|
||||||
if (conn == null) {
|
|
||||||
$('#status').text('disconnected');
|
|
||||||
$('#connect').html('Connect');
|
|
||||||
} else {
|
|
||||||
$('#status').text('connected (' + conn.protocol + ')');
|
|
||||||
$('#connect').html('Disconnect');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$('#connect').click(function() {
|
|
||||||
if (conn == null) {
|
|
||||||
connect();
|
|
||||||
} else {
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
update_ui();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
$('#send').click(function() {
|
|
||||||
var text = $('#text').val();
|
|
||||||
log('Sending: ' + text);
|
|
||||||
conn.send(text);
|
|
||||||
$('#text').val('').focus();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
$('#text').keyup(function(e) {
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
$('#send').click();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h3>Chat!</h3>
|
|
||||||
<div>
|
|
||||||
<button id="connect">Connect</button> | Status:
|
|
||||||
<span id="status">disconnected</span>
|
|
||||||
</div>
|
|
||||||
<div id="log"
|
|
||||||
style="width:20em;height:15em;overflow:auto;border:1px solid black">
|
|
||||||
</div>
|
|
||||||
<form id="chatform" onsubmit="return false;">
|
|
||||||
<input id="text" type="text" />
|
|
||||||
<input id="send" type="button" value="Send" />
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,11 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "template-tera"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../" }
|
|
||||||
tera = "*"
|
|
@ -1,17 +0,0 @@
|
|||||||
# template_tera
|
|
||||||
|
|
||||||
Minimal example of using the template [tera](https://github.com/Keats/tera) that displays a form.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/template_tera
|
|
||||||
cargo run (or ``cargo watch -x run``)
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
- [http://localhost:8080](http://localhost:8080)
|
|
@ -1,47 +0,0 @@
|
|||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate tera;
|
|
||||||
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
|
|
||||||
struct State {
|
|
||||||
template: tera::Tera, // <- store tera template in application state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(req: HttpRequest<State>) -> Result<HttpResponse> {
|
|
||||||
let s = if let Some(name) = req.query().get("name") { // <- submitted form
|
|
||||||
let mut ctx = tera::Context::new();
|
|
||||||
ctx.add("name", &name.to_owned());
|
|
||||||
ctx.add("text", &"Welcome!".to_owned());
|
|
||||||
req.state().template.render("user.html", &ctx)
|
|
||||||
.map_err(|_| error::ErrorInternalServerError("Template error"))?
|
|
||||||
} else {
|
|
||||||
req.state().template.render("index.html", &tera::Context::new())
|
|
||||||
.map_err(|_| error::ErrorInternalServerError("Template error"))?
|
|
||||||
};
|
|
||||||
Ok(httpcodes::HTTPOk.build()
|
|
||||||
.content_type("text/html")
|
|
||||||
.body(s)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("tera-example");
|
|
||||||
|
|
||||||
let addr = HttpServer::new(|| {
|
|
||||||
let tera = compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*"));
|
|
||||||
|
|
||||||
Application::with_state(State{template: tera})
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/", |r| r.method(Method::GET).f(index))})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Actix web</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Welcome!</h1>
|
|
||||||
<p>
|
|
||||||
<h3>What is your name?</h3>
|
|
||||||
<form>
|
|
||||||
<input type="text" name="name" /><br/>
|
|
||||||
<p><input type="submit"></p>
|
|
||||||
</form>
|
|
||||||
</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Actix web</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Hi, {{ name }}!</h1>
|
|
||||||
<p>
|
|
||||||
{{ text }}
|
|
||||||
</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,15 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "tls-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "server"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "0.5"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../", features=["alpn"] }
|
|
||||||
openssl = { version="0.10" }
|
|
@ -1,16 +0,0 @@
|
|||||||
# tls example
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/tls
|
|
||||||
cargo run (or ``cargo watch -x run``)
|
|
||||||
# Started http server: 127.0.0.1:8443
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
- curl: ``curl -v https://127.0.0.1:8443/index.html --compress -k``
|
|
||||||
- browser: [https://127.0.0.1:8443/index.html](https://127.0.0.1:8080/index.html)
|
|
@ -1,50 +0,0 @@
|
|||||||
#![allow(unused_variables)]
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate openssl;
|
|
||||||
|
|
||||||
use actix_web::*;
|
|
||||||
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
|
|
||||||
|
|
||||||
|
|
||||||
/// simple handle
|
|
||||||
fn index(req: HttpRequest) -> Result<HttpResponse> {
|
|
||||||
println!("{:?}", req);
|
|
||||||
Ok(httpcodes::HTTPOk
|
|
||||||
.build()
|
|
||||||
.content_type("text/plain")
|
|
||||||
.body("Welcome!")?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
if ::std::env::var("RUST_LOG").is_err() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
}
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("ws-example");
|
|
||||||
|
|
||||||
// load ssl keys
|
|
||||||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
|
||||||
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
|
|
||||||
builder.set_certificate_chain_file("cert.pem").unwrap();
|
|
||||||
|
|
||||||
let addr = HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
// register simple handler, handle all methods
|
|
||||||
.resource("/index.html", |r| r.f(index))
|
|
||||||
// with path parameters
|
|
||||||
.resource("/", |r| r.method(Method::GET).f(|req| {
|
|
||||||
httpcodes::HTTPFound
|
|
||||||
.build()
|
|
||||||
.header("LOCATION", "/index.html")
|
|
||||||
.body(Body::Empty)
|
|
||||||
})))
|
|
||||||
.bind("127.0.0.1:8443").unwrap()
|
|
||||||
.start_ssl(builder).unwrap();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8443");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
[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"
|
|
@ -1,14 +0,0 @@
|
|||||||
## 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.
|
|
@ -1,31 +0,0 @@
|
|||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate tokio_uds;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
use tokio_uds::UnixListener;
|
|
||||||
|
|
||||||
|
|
||||||
fn index(_req: HttpRequest) -> &'static str {
|
|
||||||
"Hello world!"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("unix-socket");
|
|
||||||
|
|
||||||
let listener = UnixListener::bind("/tmp/actix-uds.socket", Arbiter::handle()).expect("bind failed");
|
|
||||||
let _addr = HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/index.html", |r| r.f(|_| "Hello world!"))
|
|
||||||
.resource("/", |r| r.f(index)))
|
|
||||||
.start_incoming(listener.incoming(), false);
|
|
||||||
|
|
||||||
println!("Started http server: /tmp/actix-uds.socket");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
# Actix Web CORS example
|
|
||||||
|
|
||||||
## start
|
|
||||||
1 - backend server
|
|
||||||
```bash
|
|
||||||
$ cd web-cors/backend
|
|
||||||
$ cargo run
|
|
||||||
```
|
|
||||||
2 - frontend server
|
|
||||||
```bash
|
|
||||||
$ cd web-cors/frontend
|
|
||||||
$ npm install
|
|
||||||
$ npm run dev
|
|
||||||
```
|
|
||||||
then open browser 'http://localhost:1234/'
|
|
4
examples/web-cors/backend/.gitignore
vendored
4
examples/web-cors/backend/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
/target/
|
|
||||||
**/*.rs.bk
|
|
||||||
Cargo.lock
|
|
@ -1,17 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "actix-web-cors"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["krircc <krircc@aliyun.com>"]
|
|
||||||
workspace = "../../../"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
serde = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
http = "0.1"
|
|
||||||
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path = "../../../" }
|
|
||||||
dotenv = "0.10"
|
|
||||||
env_logger = "0.5"
|
|
||||||
futures = "0.1"
|
|
@ -1,45 +0,0 @@
|
|||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate http;
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use http::header;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::middleware::cors;
|
|
||||||
|
|
||||||
mod user;
|
|
||||||
use user::info;
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
let sys = actix::System::new("Actix-web-CORS");
|
|
||||||
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
.resource("/user/info", |r| {
|
|
||||||
cors::Cors::build()
|
|
||||||
.allowed_origin("http://localhost:1234")
|
|
||||||
.allowed_methods(vec!["GET", "POST"])
|
|
||||||
.allowed_headers(
|
|
||||||
vec![header::AUTHORIZATION,
|
|
||||||
header::ACCEPT, header::CONTENT_TYPE])
|
|
||||||
.max_age(3600)
|
|
||||||
.finish().expect("Can not create CORS middleware")
|
|
||||||
.register(r);
|
|
||||||
r.method(Method::POST).a(info);
|
|
||||||
}))
|
|
||||||
.bind("127.0.0.1:8000").unwrap()
|
|
||||||
.shutdown_timeout(200)
|
|
||||||
.start();
|
|
||||||
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
use actix_web::*;
|
|
||||||
use futures::Future;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Deserialize,Serialize, Debug)]
|
|
||||||
struct Info {
|
|
||||||
username: String,
|
|
||||||
email: String,
|
|
||||||
password: String,
|
|
||||||
confirm_password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn info(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
req.json()
|
|
||||||
.from_err()
|
|
||||||
.and_then(|res: Info| {
|
|
||||||
Ok(httpcodes::HTTPOk.build().json(res)?)
|
|
||||||
}).responder()
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": ["env"]
|
|
||||||
}
|
|
14
examples/web-cors/frontend/.gitignore
vendored
14
examples/web-cors/frontend/.gitignore
vendored
@ -1,14 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
node_modules/
|
|
||||||
/dist/
|
|
||||||
.cache
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.idea
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
||||||
<title>webapp</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script src="./src/main.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "actix-web-cors",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "webapp",
|
|
||||||
"main": "main.js",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "rm -rf dist/ && NODE_ENV=development parcel index.html",
|
|
||||||
"build": "NODE_ENV=production parcel build index.html",
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"vue": "^2.5.13",
|
|
||||||
"vue-router": "^3.0.1",
|
|
||||||
"axios": "^0.17.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"babel-preset-env": "^1.6.1",
|
|
||||||
"parcel-bundler": "^1.4.1",
|
|
||||||
"parcel-plugin-vue": "^1.5.0"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,145 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="app">
|
|
||||||
<div id="content">
|
|
||||||
<div id="title">
|
|
||||||
<a to="#">SignUp</a>
|
|
||||||
</div>
|
|
||||||
<input type="text" name="username" placeholder="Username" v-model="Username" required />
|
|
||||||
<input type="text" name="email" placeholder="E-mail" v-model="Email" required />
|
|
||||||
<input type="password" name="password" placeholder="Password" v-model="Password" required/>
|
|
||||||
<input type="password" name="confirm_password" placeholder="Confirm password" v-model="ConfirmPassword" required/><br/>
|
|
||||||
|
|
||||||
<button id="submit" @click="signup">Sign up</button>
|
|
||||||
|
|
||||||
<div id="user-info">
|
|
||||||
<p>Click Above 'Sign up' Button <br> Then Get Your Signup Info!</p>
|
|
||||||
<p>email : {{ email }}</p>
|
|
||||||
<p>username :{{ username }}</p>
|
|
||||||
<p>password : {{ password }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
export default {
|
|
||||||
name: 'app',
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
Username: '',
|
|
||||||
Email: '',
|
|
||||||
Password: '',
|
|
||||||
ConfirmPassword: '',
|
|
||||||
|
|
||||||
email: '',
|
|
||||||
username: '',
|
|
||||||
password: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
signup () {
|
|
||||||
var username = this.Username
|
|
||||||
var email = this.Email
|
|
||||||
var password = this.Password
|
|
||||||
var confirm_password = this.ConfirmPassword
|
|
||||||
console.log(email)
|
|
||||||
axios.post('http://localhost:8000/user/info', {
|
|
||||||
username: username,
|
|
||||||
email: email,
|
|
||||||
password: password,
|
|
||||||
confirm_password: confirm_password
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
console.log(response.data)
|
|
||||||
this.email = response.data.email
|
|
||||||
this.username = response.data.username
|
|
||||||
this.password = response.data.password
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.log(e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
#content {
|
|
||||||
width: 250px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding-top: 33px;
|
|
||||||
}
|
|
||||||
#title {
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color:bisque;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
input[type="text"],
|
|
||||||
input[type="password"] {
|
|
||||||
margin: 6px auto auto;
|
|
||||||
width: 250px;
|
|
||||||
height: 36px;
|
|
||||||
border: none;
|
|
||||||
border-bottom: 1px solid #AAA;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
#submit {
|
|
||||||
margin: 10px 0 20px 0;
|
|
||||||
width: 250px;
|
|
||||||
height: 33px;
|
|
||||||
background-color:bisque;
|
|
||||||
border: none;
|
|
||||||
border-radius: 2px;
|
|
||||||
font-family: 'Roboto', sans-serif;
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
transition: 0.1s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
input[type="checkbox"] {
|
|
||||||
margin-top: 11px;
|
|
||||||
}
|
|
||||||
dialog {
|
|
||||||
top: 50%;
|
|
||||||
width: 80%;
|
|
||||||
border: 5px solid rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
dialog::backdrop{
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.7);
|
|
||||||
}
|
|
||||||
#closeDialog {
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: none;
|
|
||||||
font-size: 1rem;
|
|
||||||
padding: 0.4rem 0.8em;
|
|
||||||
background: #eb9816;
|
|
||||||
border-bottom: 1px solid #f1b75c;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#closeDialog:hover, #closeDialog:focus {
|
|
||||||
opacity: 0.92;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
#user-info {
|
|
||||||
width: 250px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding-top: 44px;
|
|
||||||
}
|
|
||||||
@media only screen and (min-width: 600px) {
|
|
||||||
#content {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding-top: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,11 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
import App from './app'
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el: '#app',
|
|
||||||
render: h => h(App)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (module.hot) {
|
|
||||||
module.hot.accept();
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "websocket-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "server"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "client"
|
|
||||||
path = "src/client.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
rand = "*"
|
|
||||||
bytes = "0.4"
|
|
||||||
byteorder = "1.1"
|
|
||||||
futures = "0.1"
|
|
||||||
tokio-io = "0.1"
|
|
||||||
tokio-core = "0.1"
|
|
||||||
env_logger = "*"
|
|
||||||
|
|
||||||
serde = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path="../../" }
|
|
@ -1,32 +0,0 @@
|
|||||||
# Websocket chat example
|
|
||||||
|
|
||||||
This is extension of the
|
|
||||||
[actix chat example](https://github.com/actix/actix/tree/master/examples/chat)
|
|
||||||
|
|
||||||
Added features:
|
|
||||||
|
|
||||||
* Browser WebSocket client
|
|
||||||
* Chat server runs in separate thread
|
|
||||||
* Tcp listener runs in separate thread
|
|
||||||
|
|
||||||
## Server
|
|
||||||
|
|
||||||
Chat server listens for incoming tcp connections. Server can access several types of message:
|
|
||||||
|
|
||||||
* `\list` - list all available rooms
|
|
||||||
* `\join name` - join room, if room does not exist, create new one
|
|
||||||
* `\name name` - set session name
|
|
||||||
* `some message` - just string, send message to all peers in same room
|
|
||||||
* client has to send heartbeat `Ping` messages, if server does not receive a heartbeat message for 10 seconds connection gets dropped
|
|
||||||
|
|
||||||
To start server use command: `cargo run --bin server`
|
|
||||||
|
|
||||||
## Client
|
|
||||||
|
|
||||||
Client connects to server. Reads input from stdin and sends to server.
|
|
||||||
|
|
||||||
To run client use command: `cargo run --bin client`
|
|
||||||
|
|
||||||
## WebSocket Browser Client
|
|
||||||
|
|
||||||
Open url: [http://localhost:8080/](http://localhost:8080/)
|
|
@ -1,72 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""websocket cmd client for wssrv.py example."""
|
|
||||||
import argparse
|
|
||||||
import asyncio
|
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
|
|
||||||
def start_client(loop, url):
|
|
||||||
name = input('Please enter your name: ')
|
|
||||||
|
|
||||||
# send request
|
|
||||||
ws = yield from aiohttp.ClientSession().ws_connect(url, autoclose=False, autoping=False)
|
|
||||||
|
|
||||||
# input reader
|
|
||||||
def stdin_callback():
|
|
||||||
line = sys.stdin.buffer.readline().decode('utf-8')
|
|
||||||
if not line:
|
|
||||||
loop.stop()
|
|
||||||
else:
|
|
||||||
ws.send_str(name + ': ' + line)
|
|
||||||
loop.add_reader(sys.stdin.fileno(), stdin_callback)
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
def dispatch():
|
|
||||||
while True:
|
|
||||||
msg = yield from ws.receive()
|
|
||||||
|
|
||||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
|
||||||
print('Text: ', msg.data.strip())
|
|
||||||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
|
||||||
print('Binary: ', msg.data)
|
|
||||||
elif msg.type == aiohttp.WSMsgType.PING:
|
|
||||||
ws.pong()
|
|
||||||
elif msg.type == aiohttp.WSMsgType.PONG:
|
|
||||||
print('Pong received')
|
|
||||||
else:
|
|
||||||
if msg.type == aiohttp.WSMsgType.CLOSE:
|
|
||||||
yield from ws.close()
|
|
||||||
elif msg.type == aiohttp.WSMsgType.ERROR:
|
|
||||||
print('Error during receive %s' % ws.exception())
|
|
||||||
elif msg.type == aiohttp.WSMsgType.CLOSED:
|
|
||||||
pass
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
yield from dispatch()
|
|
||||||
|
|
||||||
|
|
||||||
ARGS = argparse.ArgumentParser(
|
|
||||||
description="websocket console client for wssrv.py example.")
|
|
||||||
ARGS.add_argument(
|
|
||||||
'--host', action="store", dest='host',
|
|
||||||
default='127.0.0.1', help='Host name')
|
|
||||||
ARGS.add_argument(
|
|
||||||
'--port', action="store", dest='port',
|
|
||||||
default=8080, type=int, help='Port number')
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
args = ARGS.parse_args()
|
|
||||||
if ':' in args.host:
|
|
||||||
args.host, port = args.host.split(':', 1)
|
|
||||||
args.port = int(port)
|
|
||||||
|
|
||||||
url = 'http://{}:{}/ws/'.format(args.host, args.port)
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.add_signal_handler(signal.SIGINT, loop.stop)
|
|
||||||
asyncio.Task(start_client(loop, url))
|
|
||||||
loop.run_forever()
|
|
@ -1,153 +0,0 @@
|
|||||||
#[macro_use] extern crate actix;
|
|
||||||
extern crate bytes;
|
|
||||||
extern crate byteorder;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate tokio_io;
|
|
||||||
extern crate tokio_core;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
|
|
||||||
use std::{io, net, process, thread};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::time::Duration;
|
|
||||||
use futures::Future;
|
|
||||||
use tokio_io::AsyncRead;
|
|
||||||
use tokio_io::io::WriteHalf;
|
|
||||||
use tokio_io::codec::FramedRead;
|
|
||||||
use tokio_core::net::TcpStream;
|
|
||||||
use actix::prelude::*;
|
|
||||||
|
|
||||||
mod codec;
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let sys = actix::System::new("chat-client");
|
|
||||||
|
|
||||||
// Connect to server
|
|
||||||
let addr = net::SocketAddr::from_str("127.0.0.1:12345").unwrap();
|
|
||||||
Arbiter::handle().spawn(
|
|
||||||
TcpStream::connect(&addr, Arbiter::handle())
|
|
||||||
.and_then(|stream| {
|
|
||||||
let addr: Addr<Syn, _> = ChatClient::create(|ctx| {
|
|
||||||
let (r, w) = stream.split();
|
|
||||||
ChatClient::add_stream(FramedRead::new(r, codec::ClientChatCodec), ctx);
|
|
||||||
ChatClient{
|
|
||||||
framed: actix::io::FramedWrite::new(
|
|
||||||
w, codec::ClientChatCodec, ctx)}});
|
|
||||||
|
|
||||||
// start console loop
|
|
||||||
thread::spawn(move|| {
|
|
||||||
loop {
|
|
||||||
let mut cmd = String::new();
|
|
||||||
if io::stdin().read_line(&mut cmd).is_err() {
|
|
||||||
println!("error");
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
addr.do_send(ClientCommand(cmd));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
futures::future::ok(())
|
|
||||||
})
|
|
||||||
.map_err(|e| {
|
|
||||||
println!("Can not connect to server: {}", e);
|
|
||||||
process::exit(1)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
println!("Running chat client");
|
|
||||||
sys.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct ChatClient {
|
|
||||||
framed: actix::io::FramedWrite<WriteHalf<TcpStream>, codec::ClientChatCodec>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Message)]
|
|
||||||
struct ClientCommand(String);
|
|
||||||
|
|
||||||
impl Actor for ChatClient {
|
|
||||||
type Context = Context<Self>;
|
|
||||||
|
|
||||||
fn started(&mut self, ctx: &mut Context<Self>) {
|
|
||||||
// start heartbeats otherwise server will disconnect after 10 seconds
|
|
||||||
self.hb(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stopped(&mut self, _: &mut Context<Self>) {
|
|
||||||
println!("Disconnected");
|
|
||||||
|
|
||||||
// Stop application on disconnect
|
|
||||||
Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChatClient {
|
|
||||||
fn hb(&self, ctx: &mut Context<Self>) {
|
|
||||||
ctx.run_later(Duration::new(1, 0), |act, ctx| {
|
|
||||||
act.framed.write(codec::ChatRequest::Ping);
|
|
||||||
act.hb(ctx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl actix::io::WriteHandler<io::Error> for ChatClient {}
|
|
||||||
|
|
||||||
/// Handle stdin commands
|
|
||||||
impl Handler<ClientCommand> for ChatClient {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ClientCommand, _: &mut Context<Self>) {
|
|
||||||
let m = msg.0.trim();
|
|
||||||
if m.is_empty() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// we check for /sss type of messages
|
|
||||||
if m.starts_with('/') {
|
|
||||||
let v: Vec<&str> = m.splitn(2, ' ').collect();
|
|
||||||
match v[0] {
|
|
||||||
"/list" => {
|
|
||||||
self.framed.write(codec::ChatRequest::List);
|
|
||||||
},
|
|
||||||
"/join" => {
|
|
||||||
if v.len() == 2 {
|
|
||||||
self.framed.write(codec::ChatRequest::Join(v[1].to_owned()));
|
|
||||||
} else {
|
|
||||||
println!("!!! room name is required");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => println!("!!! unknown command"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.framed.write(codec::ChatRequest::Message(m.to_owned()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Server communication
|
|
||||||
|
|
||||||
impl StreamHandler<codec::ChatResponse, io::Error> for ChatClient {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: codec::ChatResponse, _: &mut Context<Self>) {
|
|
||||||
match msg {
|
|
||||||
codec::ChatResponse::Message(ref msg) => {
|
|
||||||
println!("message: {}", msg);
|
|
||||||
}
|
|
||||||
codec::ChatResponse::Joined(ref msg) => {
|
|
||||||
println!("!!! joined: {}", msg);
|
|
||||||
}
|
|
||||||
codec::ChatResponse::Rooms(rooms) => {
|
|
||||||
println!("\n!!! Available rooms:");
|
|
||||||
for room in rooms {
|
|
||||||
println!("{}", room);
|
|
||||||
}
|
|
||||||
println!("");
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
use std::io;
|
|
||||||
use serde_json as json;
|
|
||||||
use byteorder::{BigEndian , ByteOrder};
|
|
||||||
use bytes::{BytesMut, BufMut};
|
|
||||||
use tokio_io::codec::{Encoder, Decoder};
|
|
||||||
|
|
||||||
/// Client request
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Message)]
|
|
||||||
#[serde(tag="cmd", content="data")]
|
|
||||||
pub enum ChatRequest {
|
|
||||||
/// List rooms
|
|
||||||
List,
|
|
||||||
/// Join rooms
|
|
||||||
Join(String),
|
|
||||||
/// Send message
|
|
||||||
Message(String),
|
|
||||||
/// Ping
|
|
||||||
Ping
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Server response
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Message)]
|
|
||||||
#[serde(tag="cmd", content="data")]
|
|
||||||
pub enum ChatResponse {
|
|
||||||
Ping,
|
|
||||||
|
|
||||||
/// List of rooms
|
|
||||||
Rooms(Vec<String>),
|
|
||||||
|
|
||||||
/// Joined
|
|
||||||
Joined(String),
|
|
||||||
|
|
||||||
/// Message
|
|
||||||
Message(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Codec for Client -> Server transport
|
|
||||||
pub struct ChatCodec;
|
|
||||||
|
|
||||||
impl Decoder for ChatCodec
|
|
||||||
{
|
|
||||||
type Item = ChatRequest;
|
|
||||||
type Error = io::Error;
|
|
||||||
|
|
||||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
|
||||||
let size = {
|
|
||||||
if src.len() < 2 {
|
|
||||||
return Ok(None)
|
|
||||||
}
|
|
||||||
BigEndian::read_u16(src.as_ref()) as usize
|
|
||||||
};
|
|
||||||
|
|
||||||
if src.len() >= size + 2 {
|
|
||||||
src.split_to(2);
|
|
||||||
let buf = src.split_to(size);
|
|
||||||
Ok(Some(json::from_slice::<ChatRequest>(&buf)?))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encoder for ChatCodec
|
|
||||||
{
|
|
||||||
type Item = ChatResponse;
|
|
||||||
type Error = io::Error;
|
|
||||||
|
|
||||||
fn encode(&mut self, msg: ChatResponse, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
|
||||||
let msg = json::to_string(&msg).unwrap();
|
|
||||||
let msg_ref: &[u8] = msg.as_ref();
|
|
||||||
|
|
||||||
dst.reserve(msg_ref.len() + 2);
|
|
||||||
dst.put_u16::<BigEndian>(msg_ref.len() as u16);
|
|
||||||
dst.put(msg_ref);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Codec for Server -> Client transport
|
|
||||||
pub struct ClientChatCodec;
|
|
||||||
|
|
||||||
impl Decoder for ClientChatCodec
|
|
||||||
{
|
|
||||||
type Item = ChatResponse;
|
|
||||||
type Error = io::Error;
|
|
||||||
|
|
||||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
|
||||||
let size = {
|
|
||||||
if src.len() < 2 {
|
|
||||||
return Ok(None)
|
|
||||||
}
|
|
||||||
BigEndian::read_u16(src.as_ref()) as usize
|
|
||||||
};
|
|
||||||
|
|
||||||
if src.len() >= size + 2 {
|
|
||||||
src.split_to(2);
|
|
||||||
let buf = src.split_to(size);
|
|
||||||
Ok(Some(json::from_slice::<ChatResponse>(&buf)?))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encoder for ClientChatCodec
|
|
||||||
{
|
|
||||||
type Item = ChatRequest;
|
|
||||||
type Error = io::Error;
|
|
||||||
|
|
||||||
fn encode(&mut self, msg: ChatRequest, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
|
||||||
let msg = json::to_string(&msg).unwrap();
|
|
||||||
let msg_ref: &[u8] = msg.as_ref();
|
|
||||||
|
|
||||||
dst.reserve(msg_ref.len() + 2);
|
|
||||||
dst.put_u16::<BigEndian>(msg_ref.len() as u16);
|
|
||||||
dst.put(msg_ref);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,209 +0,0 @@
|
|||||||
#![allow(unused_variables)]
|
|
||||||
extern crate rand;
|
|
||||||
extern crate bytes;
|
|
||||||
extern crate byteorder;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate tokio_io;
|
|
||||||
extern crate tokio_core;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
mod codec;
|
|
||||||
mod server;
|
|
||||||
mod session;
|
|
||||||
|
|
||||||
/// This is our websocket route state, this state is shared with all route instances
|
|
||||||
/// via `HttpContext::state()`
|
|
||||||
struct WsChatSessionState {
|
|
||||||
addr: Addr<Syn, server::ChatServer>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Entry point for our route
|
|
||||||
fn chat_route(req: HttpRequest<WsChatSessionState>) -> Result<HttpResponse> {
|
|
||||||
ws::start(
|
|
||||||
req,
|
|
||||||
WsChatSession {
|
|
||||||
id: 0,
|
|
||||||
hb: Instant::now(),
|
|
||||||
room: "Main".to_owned(),
|
|
||||||
name: None})
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WsChatSession {
|
|
||||||
/// unique session id
|
|
||||||
id: usize,
|
|
||||||
/// Client must send ping at least once per 10 seconds, otherwise we drop connection.
|
|
||||||
hb: Instant,
|
|
||||||
/// joined room
|
|
||||||
room: String,
|
|
||||||
/// peer name
|
|
||||||
name: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for WsChatSession {
|
|
||||||
type Context = ws::WebsocketContext<Self, WsChatSessionState>;
|
|
||||||
|
|
||||||
/// Method is called on actor start.
|
|
||||||
/// We register ws session with ChatServer
|
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
|
||||||
// register self in chat server. `AsyncContext::wait` register
|
|
||||||
// future within context, but context waits until this future resolves
|
|
||||||
// before processing any other events.
|
|
||||||
// HttpContext::state() is instance of WsChatSessionState, state is shared across all
|
|
||||||
// routes within application
|
|
||||||
let addr: Addr<Syn, _> = ctx.address();
|
|
||||||
ctx.state().addr.send(server::Connect{addr: addr.recipient()})
|
|
||||||
.into_actor(self)
|
|
||||||
.then(|res, act, ctx| {
|
|
||||||
match res {
|
|
||||||
Ok(res) => act.id = res,
|
|
||||||
// something is wrong with chat server
|
|
||||||
_ => ctx.stop(),
|
|
||||||
}
|
|
||||||
fut::ok(())
|
|
||||||
}).wait(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
|
|
||||||
// notify chat server
|
|
||||||
ctx.state().addr.do_send(server::Disconnect{id: self.id});
|
|
||||||
Running::Stop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle messages from chat server, we simply send it to peer websocket
|
|
||||||
impl Handler<session::Message> for WsChatSession {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) {
|
|
||||||
ctx.text(msg.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// WebSocket message handler
|
|
||||||
impl StreamHandler<ws::Message, ws::ProtocolError> for WsChatSession {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
|
||||||
println!("WEBSOCKET MESSAGE: {:?}", msg);
|
|
||||||
match msg {
|
|
||||||
ws::Message::Ping(msg) => ctx.pong(&msg),
|
|
||||||
ws::Message::Pong(msg) => self.hb = Instant::now(),
|
|
||||||
ws::Message::Text(text) => {
|
|
||||||
let m = text.trim();
|
|
||||||
// we check for /sss type of messages
|
|
||||||
if m.starts_with('/') {
|
|
||||||
let v: Vec<&str> = m.splitn(2, ' ').collect();
|
|
||||||
match v[0] {
|
|
||||||
"/list" => {
|
|
||||||
// Send ListRooms message to chat server and wait for response
|
|
||||||
println!("List rooms");
|
|
||||||
ctx.state().addr.send(server::ListRooms)
|
|
||||||
.into_actor(self)
|
|
||||||
.then(|res, _, ctx| {
|
|
||||||
match res {
|
|
||||||
Ok(rooms) => {
|
|
||||||
for room in rooms {
|
|
||||||
ctx.text(room);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => println!("Something is wrong"),
|
|
||||||
}
|
|
||||||
fut::ok(())
|
|
||||||
}).wait(ctx)
|
|
||||||
// .wait(ctx) pauses all events in context,
|
|
||||||
// so actor wont receive any new messages until it get list
|
|
||||||
// of rooms back
|
|
||||||
},
|
|
||||||
"/join" => {
|
|
||||||
if v.len() == 2 {
|
|
||||||
self.room = v[1].to_owned();
|
|
||||||
ctx.state().addr.do_send(
|
|
||||||
server::Join{id: self.id, name: self.room.clone()});
|
|
||||||
|
|
||||||
ctx.text("joined");
|
|
||||||
} else {
|
|
||||||
ctx.text("!!! room name is required");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/name" => {
|
|
||||||
if v.len() == 2 {
|
|
||||||
self.name = Some(v[1].to_owned());
|
|
||||||
} else {
|
|
||||||
ctx.text("!!! name is required");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => ctx.text(format!("!!! unknown command: {:?}", m)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let msg = if let Some(ref name) = self.name {
|
|
||||||
format!("{}: {}", name, m)
|
|
||||||
} else {
|
|
||||||
m.to_owned()
|
|
||||||
};
|
|
||||||
// send message to chat server
|
|
||||||
ctx.state().addr.do_send(
|
|
||||||
server::Message{id: self.id,
|
|
||||||
msg: msg,
|
|
||||||
room: self.room.clone()})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ws::Message::Binary(bin) =>
|
|
||||||
println!("Unexpected binary"),
|
|
||||||
ws::Message::Close(_) => {
|
|
||||||
ctx.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("websocket-example");
|
|
||||||
|
|
||||||
// Start chat server actor in separate thread
|
|
||||||
let server: Addr<Syn, _> = Arbiter::start(|_| server::ChatServer::default());
|
|
||||||
|
|
||||||
// Start tcp server in separate thread
|
|
||||||
let srv = server.clone();
|
|
||||||
Arbiter::new("tcp-server").do_send::<msgs::Execute>(
|
|
||||||
msgs::Execute::new(move || {
|
|
||||||
session::TcpServer::new("127.0.0.1:12345", srv);
|
|
||||||
Ok(())
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Create Http server with websocket support
|
|
||||||
let addr = HttpServer::new(
|
|
||||||
move || {
|
|
||||||
// Websocket sessions state
|
|
||||||
let state = WsChatSessionState { addr: server.clone() };
|
|
||||||
|
|
||||||
Application::with_state(state)
|
|
||||||
// redirect to websocket.html
|
|
||||||
.resource("/", |r| r.method(Method::GET).f(|_| {
|
|
||||||
httpcodes::HTTPFound
|
|
||||||
.build()
|
|
||||||
.header("LOCATION", "/static/websocket.html")
|
|
||||||
.finish()
|
|
||||||
}))
|
|
||||||
// websocket
|
|
||||||
.resource("/ws/", |r| r.route().f(chat_route))
|
|
||||||
// static resources
|
|
||||||
.handler("/static/", fs::StaticFiles::new("static/", true))
|
|
||||||
})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,197 +0,0 @@
|
|||||||
//! `ChatServer` is an actor. It maintains list of connection client session.
|
|
||||||
//! And manages available rooms. Peers send messages to other peers in same
|
|
||||||
//! room through `ChatServer`.
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use rand::{self, Rng, ThreadRng};
|
|
||||||
use actix::prelude::*;
|
|
||||||
|
|
||||||
use session;
|
|
||||||
|
|
||||||
/// Message for chat server communications
|
|
||||||
|
|
||||||
/// New chat session is created
|
|
||||||
#[derive(Message)]
|
|
||||||
#[rtype(usize)]
|
|
||||||
pub struct Connect {
|
|
||||||
pub addr: Recipient<Syn, session::Message>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Session is disconnected
|
|
||||||
#[derive(Message)]
|
|
||||||
pub struct Disconnect {
|
|
||||||
pub id: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send message to specific room
|
|
||||||
#[derive(Message)]
|
|
||||||
pub struct Message {
|
|
||||||
/// Id of the client session
|
|
||||||
pub id: usize,
|
|
||||||
/// Peer message
|
|
||||||
pub msg: String,
|
|
||||||
/// Room name
|
|
||||||
pub room: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List of available rooms
|
|
||||||
pub struct ListRooms;
|
|
||||||
|
|
||||||
impl actix::Message for ListRooms {
|
|
||||||
type Result = Vec<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Join room, if room does not exists create new one.
|
|
||||||
#[derive(Message)]
|
|
||||||
pub struct Join {
|
|
||||||
/// Client id
|
|
||||||
pub id: usize,
|
|
||||||
/// Room name
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `ChatServer` manages chat rooms and responsible for coordinating chat session.
|
|
||||||
/// implementation is super primitive
|
|
||||||
pub struct ChatServer {
|
|
||||||
sessions: HashMap<usize, Recipient<Syn, session::Message>>,
|
|
||||||
rooms: HashMap<String, HashSet<usize>>,
|
|
||||||
rng: RefCell<ThreadRng>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ChatServer {
|
|
||||||
fn default() -> ChatServer {
|
|
||||||
// default room
|
|
||||||
let mut rooms = HashMap::new();
|
|
||||||
rooms.insert("Main".to_owned(), HashSet::new());
|
|
||||||
|
|
||||||
ChatServer {
|
|
||||||
sessions: HashMap::new(),
|
|
||||||
rooms: rooms,
|
|
||||||
rng: RefCell::new(rand::thread_rng()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChatServer {
|
|
||||||
/// Send message to all users in the room
|
|
||||||
fn send_message(&self, room: &str, message: &str, skip_id: usize) {
|
|
||||||
if let Some(sessions) = self.rooms.get(room) {
|
|
||||||
for id in sessions {
|
|
||||||
if *id != skip_id {
|
|
||||||
if let Some(addr) = self.sessions.get(id) {
|
|
||||||
let _ = addr.do_send(session::Message(message.to_owned()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make actor from `ChatServer`
|
|
||||||
impl Actor for ChatServer {
|
|
||||||
/// We are going to use simple Context, we just need ability to communicate
|
|
||||||
/// with other actors.
|
|
||||||
type Context = Context<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for Connect message.
|
|
||||||
///
|
|
||||||
/// Register new session and assign unique id to this session
|
|
||||||
impl Handler<Connect> for ChatServer {
|
|
||||||
type Result = usize;
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: Connect, _: &mut Context<Self>) -> Self::Result {
|
|
||||||
println!("Someone joined");
|
|
||||||
|
|
||||||
// notify all users in same room
|
|
||||||
self.send_message(&"Main".to_owned(), "Someone joined", 0);
|
|
||||||
|
|
||||||
// register session with random id
|
|
||||||
let id = self.rng.borrow_mut().gen::<usize>();
|
|
||||||
self.sessions.insert(id, msg.addr);
|
|
||||||
|
|
||||||
// auto join session to Main room
|
|
||||||
self.rooms.get_mut(&"Main".to_owned()).unwrap().insert(id);
|
|
||||||
|
|
||||||
// send id back
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for Disconnect message.
|
|
||||||
impl Handler<Disconnect> for ChatServer {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) {
|
|
||||||
println!("Someone disconnected");
|
|
||||||
|
|
||||||
let mut rooms: Vec<String> = Vec::new();
|
|
||||||
|
|
||||||
// remove address
|
|
||||||
if self.sessions.remove(&msg.id).is_some() {
|
|
||||||
// remove session from all rooms
|
|
||||||
for (name, sessions) in &mut self.rooms {
|
|
||||||
if sessions.remove(&msg.id) {
|
|
||||||
rooms.push(name.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// send message to other users
|
|
||||||
for room in rooms {
|
|
||||||
self.send_message(&room, "Someone disconnected", 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for Message message.
|
|
||||||
impl Handler<Message> for ChatServer {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: Message, _: &mut Context<Self>) {
|
|
||||||
self.send_message(&msg.room, msg.msg.as_str(), msg.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for `ListRooms` message.
|
|
||||||
impl Handler<ListRooms> for ChatServer {
|
|
||||||
type Result = MessageResult<ListRooms>;
|
|
||||||
|
|
||||||
fn handle(&mut self, _: ListRooms, _: &mut Context<Self>) -> Self::Result {
|
|
||||||
let mut rooms = Vec::new();
|
|
||||||
|
|
||||||
for key in self.rooms.keys() {
|
|
||||||
rooms.push(key.to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageResult(rooms)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Join room, send disconnect message to old room
|
|
||||||
/// send join message to new room
|
|
||||||
impl Handler<Join> for ChatServer {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: Join, _: &mut Context<Self>) {
|
|
||||||
let Join {id, name} = msg;
|
|
||||||
let mut rooms = Vec::new();
|
|
||||||
|
|
||||||
// remove session from all rooms
|
|
||||||
for (n, sessions) in &mut self.rooms {
|
|
||||||
if sessions.remove(&id) {
|
|
||||||
rooms.push(n.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// send message to other users
|
|
||||||
for room in rooms {
|
|
||||||
self.send_message(&room, "Someone disconnected", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.rooms.get_mut(&name).is_none() {
|
|
||||||
self.rooms.insert(name.clone(), HashSet::new());
|
|
||||||
}
|
|
||||||
self.send_message(&name, "Someone connected", id);
|
|
||||||
self.rooms.get_mut(&name).unwrap().insert(id);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,207 +0,0 @@
|
|||||||
//! `ClientSession` is an actor, it manages peer tcp connection and
|
|
||||||
//! proxies commands from peer to `ChatServer`.
|
|
||||||
use std::{io, net};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::time::{Instant, Duration};
|
|
||||||
use futures::Stream;
|
|
||||||
use tokio_io::AsyncRead;
|
|
||||||
use tokio_io::io::WriteHalf;
|
|
||||||
use tokio_io::codec::FramedRead;
|
|
||||||
use tokio_core::net::{TcpStream, TcpListener};
|
|
||||||
|
|
||||||
use actix::prelude::*;
|
|
||||||
|
|
||||||
use server::{self, ChatServer};
|
|
||||||
use codec::{ChatRequest, ChatResponse, ChatCodec};
|
|
||||||
|
|
||||||
|
|
||||||
/// Chat server sends this messages to session
|
|
||||||
#[derive(Message)]
|
|
||||||
pub struct Message(pub String);
|
|
||||||
|
|
||||||
/// `ChatSession` actor is responsible for tcp peer communications.
|
|
||||||
pub struct ChatSession {
|
|
||||||
/// unique session id
|
|
||||||
id: usize,
|
|
||||||
/// this is address of chat server
|
|
||||||
addr: Addr<Syn, ChatServer>,
|
|
||||||
/// Client must send ping at least once per 10 seconds, otherwise we drop connection.
|
|
||||||
hb: Instant,
|
|
||||||
/// joined room
|
|
||||||
room: String,
|
|
||||||
/// Framed wrapper
|
|
||||||
framed: actix::io::FramedWrite<WriteHalf<TcpStream>, ChatCodec>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for ChatSession {
|
|
||||||
/// For tcp communication we are going to use `FramedContext`.
|
|
||||||
/// It is convenient wrapper around `Framed` object from `tokio_io`
|
|
||||||
type Context = Context<Self>;
|
|
||||||
|
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
|
||||||
// we'll start heartbeat process on session start.
|
|
||||||
self.hb(ctx);
|
|
||||||
|
|
||||||
// register self in chat server. `AsyncContext::wait` register
|
|
||||||
// future within context, but context waits until this future resolves
|
|
||||||
// before processing any other events.
|
|
||||||
let addr: Addr<Syn, _> = ctx.address();
|
|
||||||
self.addr.send(server::Connect{addr: addr.recipient()})
|
|
||||||
.into_actor(self)
|
|
||||||
.then(|res, act, ctx| {
|
|
||||||
match res {
|
|
||||||
Ok(res) => act.id = res,
|
|
||||||
// something is wrong with chat server
|
|
||||||
_ => ctx.stop(),
|
|
||||||
}
|
|
||||||
actix::fut::ok(())
|
|
||||||
}).wait(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
|
|
||||||
// notify chat server
|
|
||||||
self.addr.do_send(server::Disconnect{id: self.id});
|
|
||||||
Running::Stop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl actix::io::WriteHandler<io::Error> for ChatSession {}
|
|
||||||
|
|
||||||
/// To use `Framed` we have to define Io type and Codec
|
|
||||||
impl StreamHandler<ChatRequest, io::Error> for ChatSession {
|
|
||||||
|
|
||||||
/// This is main event loop for client requests
|
|
||||||
fn handle(&mut self, msg: ChatRequest, ctx: &mut Context<Self>) {
|
|
||||||
match msg {
|
|
||||||
ChatRequest::List => {
|
|
||||||
// Send ListRooms message to chat server and wait for response
|
|
||||||
println!("List rooms");
|
|
||||||
self.addr.send(server::ListRooms)
|
|
||||||
.into_actor(self)
|
|
||||||
.then(|res, act, ctx| {
|
|
||||||
match res {
|
|
||||||
Ok(rooms) => {
|
|
||||||
act.framed.write(ChatResponse::Rooms(rooms));
|
|
||||||
},
|
|
||||||
_ => println!("Something is wrong"),
|
|
||||||
}
|
|
||||||
actix::fut::ok(())
|
|
||||||
}).wait(ctx)
|
|
||||||
// .wait(ctx) pauses all events in context,
|
|
||||||
// so actor wont receive any new messages until it get list of rooms back
|
|
||||||
},
|
|
||||||
ChatRequest::Join(name) => {
|
|
||||||
println!("Join to room: {}", name);
|
|
||||||
self.room = name.clone();
|
|
||||||
self.addr.do_send(server::Join{id: self.id, name: name.clone()});
|
|
||||||
self.framed.write(ChatResponse::Joined(name));
|
|
||||||
},
|
|
||||||
ChatRequest::Message(message) => {
|
|
||||||
// send message to chat server
|
|
||||||
println!("Peer message: {}", message);
|
|
||||||
self.addr.do_send(
|
|
||||||
server::Message{id: self.id,
|
|
||||||
msg: message, room:
|
|
||||||
self.room.clone()})
|
|
||||||
}
|
|
||||||
// we update heartbeat time on ping from peer
|
|
||||||
ChatRequest::Ping =>
|
|
||||||
self.hb = Instant::now(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for Message, chat server sends this message, we just send string to peer
|
|
||||||
impl Handler<Message> for ChatSession {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: Message, ctx: &mut Context<Self>) {
|
|
||||||
// send message to peer
|
|
||||||
self.framed.write(ChatResponse::Message(msg.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper methods
|
|
||||||
impl ChatSession {
|
|
||||||
|
|
||||||
pub fn new(addr: Addr<Syn,ChatServer>,
|
|
||||||
framed: actix::io::FramedWrite<WriteHalf<TcpStream>, ChatCodec>) -> ChatSession {
|
|
||||||
ChatSession {id: 0, addr: addr, hb: Instant::now(),
|
|
||||||
room: "Main".to_owned(), framed: framed}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// helper method that sends ping to client every second.
|
|
||||||
///
|
|
||||||
/// also this method check heartbeats from client
|
|
||||||
fn hb(&self, ctx: &mut Context<Self>) {
|
|
||||||
ctx.run_later(Duration::new(1, 0), |act, ctx| {
|
|
||||||
// check client heartbeats
|
|
||||||
if Instant::now().duration_since(act.hb) > Duration::new(10, 0) {
|
|
||||||
// heartbeat timed out
|
|
||||||
println!("Client heartbeat failed, disconnecting!");
|
|
||||||
|
|
||||||
// notify chat server
|
|
||||||
act.addr.do_send(server::Disconnect{id: act.id});
|
|
||||||
|
|
||||||
// stop actor
|
|
||||||
ctx.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
act.framed.write(ChatResponse::Ping);
|
|
||||||
// if we can not send message to sink, sink is closed (disconnected)
|
|
||||||
act.hb(ctx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Define tcp server that will accept incoming tcp connection and create
|
|
||||||
/// chat actors.
|
|
||||||
pub struct TcpServer {
|
|
||||||
chat: Addr<Syn, ChatServer>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TcpServer {
|
|
||||||
pub fn new(s: &str, chat: Addr<Syn, ChatServer>) {
|
|
||||||
// Create server listener
|
|
||||||
let addr = net::SocketAddr::from_str("127.0.0.1:12345").unwrap();
|
|
||||||
let listener = TcpListener::bind(&addr, Arbiter::handle()).unwrap();
|
|
||||||
|
|
||||||
// Our chat server `Server` is an actor, first we need to start it
|
|
||||||
// and then add stream on incoming tcp connections to it.
|
|
||||||
// TcpListener::incoming() returns stream of the (TcpStream, net::SocketAddr) items
|
|
||||||
// So to be able to handle this events `Server` actor has to implement
|
|
||||||
// stream handler `StreamHandler<(TcpStream, net::SocketAddr), io::Error>`
|
|
||||||
let _: () = TcpServer::create(|ctx| {
|
|
||||||
ctx.add_message_stream(listener.incoming()
|
|
||||||
.map_err(|_| ())
|
|
||||||
.map(|(t, a)| TcpConnect(t, a)));
|
|
||||||
TcpServer{chat: chat}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make actor from `Server`
|
|
||||||
impl Actor for TcpServer {
|
|
||||||
/// Every actor has to provide execution `Context` in which it can run.
|
|
||||||
type Context = Context<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Message)]
|
|
||||||
struct TcpConnect(TcpStream, net::SocketAddr);
|
|
||||||
|
|
||||||
/// Handle stream of TcpStream's
|
|
||||||
impl Handler<TcpConnect> for TcpServer {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: TcpConnect, _: &mut Context<Self>) {
|
|
||||||
// For each incoming connection we create `ChatSession` actor
|
|
||||||
// with out chat server address.
|
|
||||||
let server = self.chat.clone();
|
|
||||||
let _: () = ChatSession::create(|ctx| {
|
|
||||||
let (r, w) = msg.0.split();
|
|
||||||
ChatSession::add_stream(FramedRead::new(r, ChatCodec), ctx);
|
|
||||||
ChatSession::new(server, actix::io::FramedWrite::new(w, ChatCodec, ctx))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
|
|
||||||
</script>
|
|
||||||
<script language="javascript" type="text/javascript">
|
|
||||||
$(function() {
|
|
||||||
var conn = null;
|
|
||||||
function log(msg) {
|
|
||||||
var control = $('#log');
|
|
||||||
control.html(control.html() + msg + '<br/>');
|
|
||||||
control.scrollTop(control.scrollTop() + 1000);
|
|
||||||
}
|
|
||||||
function connect() {
|
|
||||||
disconnect();
|
|
||||||
var wsUri = (window.location.protocol=='https:'&&'wss://'||'ws://')+window.location.host + '/ws/';
|
|
||||||
conn = new WebSocket(wsUri);
|
|
||||||
log('Connecting...');
|
|
||||||
conn.onopen = function() {
|
|
||||||
log('Connected.');
|
|
||||||
update_ui();
|
|
||||||
};
|
|
||||||
conn.onmessage = function(e) {
|
|
||||||
log('Received: ' + e.data);
|
|
||||||
};
|
|
||||||
conn.onclose = function() {
|
|
||||||
log('Disconnected.');
|
|
||||||
conn = null;
|
|
||||||
update_ui();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function disconnect() {
|
|
||||||
if (conn != null) {
|
|
||||||
log('Disconnecting...');
|
|
||||||
conn.close();
|
|
||||||
conn = null;
|
|
||||||
update_ui();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function update_ui() {
|
|
||||||
var msg = '';
|
|
||||||
if (conn == null) {
|
|
||||||
$('#status').text('disconnected');
|
|
||||||
$('#connect').html('Connect');
|
|
||||||
} else {
|
|
||||||
$('#status').text('connected (' + conn.protocol + ')');
|
|
||||||
$('#connect').html('Disconnect');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$('#connect').click(function() {
|
|
||||||
if (conn == null) {
|
|
||||||
connect();
|
|
||||||
} else {
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
update_ui();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
$('#send').click(function() {
|
|
||||||
var text = $('#text').val();
|
|
||||||
log('Sending: ' + text);
|
|
||||||
conn.send(text);
|
|
||||||
$('#text').val('').focus();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
$('#text').keyup(function(e) {
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
$('#send').click();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h3>Chat!</h3>
|
|
||||||
<div>
|
|
||||||
<button id="connect">Connect</button> | Status:
|
|
||||||
<span id="status">disconnected</span>
|
|
||||||
</div>
|
|
||||||
<div id="log"
|
|
||||||
style="width:20em;height:15em;overflow:auto;border:1px solid black">
|
|
||||||
</div>
|
|
||||||
<form id="chatform" onsubmit="return false;">
|
|
||||||
<input id="text" type="text" />
|
|
||||||
<input id="send" type="button" value="Send" />
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,20 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "websocket"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
|
||||||
workspace = "../.."
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "server"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "client"
|
|
||||||
path = "src/client.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
env_logger = "*"
|
|
||||||
futures = "0.1"
|
|
||||||
tokio-core = "0.1"
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = { path="../../" }
|
|
@ -1,27 +0,0 @@
|
|||||||
# websockect
|
|
||||||
|
|
||||||
Simple echo websocket server.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd actix-web/examples/websocket
|
|
||||||
cargo run
|
|
||||||
# Started http server: 127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### web client
|
|
||||||
|
|
||||||
- [http://localhost:8080/ws/index.html](http://localhost:8080/ws/index.html)
|
|
||||||
|
|
||||||
### python client
|
|
||||||
|
|
||||||
- ``pip install aiohttp``
|
|
||||||
- ``python websocket-client.py``
|
|
||||||
|
|
||||||
if ubuntu :
|
|
||||||
|
|
||||||
- ``pip3 install aiohttp``
|
|
||||||
- ``python3 websocket-client.py``
|
|
@ -1,113 +0,0 @@
|
|||||||
//! Simple websocket client.
|
|
||||||
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate tokio_core;
|
|
||||||
|
|
||||||
use std::{io, thread};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use futures::Future;
|
|
||||||
use actix_web::ws::{Message, ProtocolError, Client, ClientWriter};
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("ws-example");
|
|
||||||
|
|
||||||
Arbiter::handle().spawn(
|
|
||||||
Client::new("http://127.0.0.1:8080/ws/")
|
|
||||||
.connect()
|
|
||||||
.map_err(|e| {
|
|
||||||
println!("Error: {}", e);
|
|
||||||
()
|
|
||||||
})
|
|
||||||
.map(|(reader, writer)| {
|
|
||||||
let addr: Addr<Syn, _> = ChatClient::create(|ctx| {
|
|
||||||
ChatClient::add_stream(reader, ctx);
|
|
||||||
ChatClient(writer)
|
|
||||||
});
|
|
||||||
|
|
||||||
// start console loop
|
|
||||||
thread::spawn(move|| {
|
|
||||||
loop {
|
|
||||||
let mut cmd = String::new();
|
|
||||||
if io::stdin().read_line(&mut cmd).is_err() {
|
|
||||||
println!("error");
|
|
||||||
return
|
|
||||||
}
|
|
||||||
addr.do_send(ClientCommand(cmd));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct ChatClient(ClientWriter);
|
|
||||||
|
|
||||||
#[derive(Message)]
|
|
||||||
struct ClientCommand(String);
|
|
||||||
|
|
||||||
impl Actor for ChatClient {
|
|
||||||
type Context = Context<Self>;
|
|
||||||
|
|
||||||
fn started(&mut self, ctx: &mut Context<Self>) {
|
|
||||||
// start heartbeats otherwise server will disconnect after 10 seconds
|
|
||||||
self.hb(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stopped(&mut self, _: &mut Context<Self>) {
|
|
||||||
println!("Disconnected");
|
|
||||||
|
|
||||||
// Stop application on disconnect
|
|
||||||
Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChatClient {
|
|
||||||
fn hb(&self, ctx: &mut Context<Self>) {
|
|
||||||
ctx.run_later(Duration::new(1, 0), |act, ctx| {
|
|
||||||
act.0.ping("");
|
|
||||||
act.hb(ctx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle stdin commands
|
|
||||||
impl Handler<ClientCommand> for ChatClient {
|
|
||||||
type Result = ();
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ClientCommand, ctx: &mut Context<Self>) {
|
|
||||||
self.0.text(msg.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle server websocket messages
|
|
||||||
impl StreamHandler<Message, ProtocolError> for ChatClient {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: Message, ctx: &mut Context<Self>) {
|
|
||||||
match msg {
|
|
||||||
Message::Text(txt) => println!("Server: {:?}", txt),
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn started(&mut self, ctx: &mut Context<Self>) {
|
|
||||||
println!("Connected");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finished(&mut self, ctx: &mut Context<Self>) {
|
|
||||||
println!("Server disconnected");
|
|
||||||
ctx.stop()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
//! Simple echo websocket server.
|
|
||||||
//! Open `http://localhost:8080/ws/index.html` in browser
|
|
||||||
//! or [python console client](https://github.com/actix/actix-web/blob/master/examples/websocket-client.py)
|
|
||||||
//! could be used for testing.
|
|
||||||
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
/// do websocket handshake and start `MyWebSocket` actor
|
|
||||||
fn ws_index(r: HttpRequest) -> Result<HttpResponse> {
|
|
||||||
ws::start(r, MyWebSocket)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// websocket connection is long running connection, it easier
|
|
||||||
/// to handle with an actor
|
|
||||||
struct MyWebSocket;
|
|
||||||
|
|
||||||
impl Actor for MyWebSocket {
|
|
||||||
type Context = ws::WebsocketContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for `ws::Message`
|
|
||||||
impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
|
||||||
// process websocket messages
|
|
||||||
println!("WS: {:?}", msg);
|
|
||||||
match msg {
|
|
||||||
ws::Message::Ping(msg) => ctx.pong(&msg),
|
|
||||||
ws::Message::Text(text) => ctx.text(text),
|
|
||||||
ws::Message::Binary(bin) => ctx.binary(bin),
|
|
||||||
ws::Message::Close(_) => {
|
|
||||||
ctx.stop();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("ws-example");
|
|
||||||
|
|
||||||
let _addr = HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
// enable logger
|
|
||||||
.middleware(middleware::Logger::default())
|
|
||||||
// websocket route
|
|
||||||
.resource("/ws/", |r| r.method(Method::GET).f(ws_index))
|
|
||||||
// static files
|
|
||||||
.handler("/", fs::StaticFiles::new("../static/", true)
|
|
||||||
.index_file("index.html")))
|
|
||||||
// start http server on 127.0.0.1:8080
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""websocket cmd client for wssrv.py example."""
|
|
||||||
import argparse
|
|
||||||
import asyncio
|
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
|
|
||||||
def start_client(loop, url):
|
|
||||||
name = input('Please enter your name: ')
|
|
||||||
|
|
||||||
# send request
|
|
||||||
ws = yield from aiohttp.ClientSession().ws_connect(url, autoclose=False, autoping=False)
|
|
||||||
|
|
||||||
# input reader
|
|
||||||
def stdin_callback():
|
|
||||||
line = sys.stdin.buffer.readline().decode('utf-8')
|
|
||||||
if not line:
|
|
||||||
loop.stop()
|
|
||||||
else:
|
|
||||||
ws.send_str(name + ': ' + line)
|
|
||||||
loop.add_reader(sys.stdin.fileno(), stdin_callback)
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
def dispatch():
|
|
||||||
while True:
|
|
||||||
msg = yield from ws.receive()
|
|
||||||
|
|
||||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
|
||||||
print('Text: ', msg.data.strip())
|
|
||||||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
|
||||||
print('Binary: ', msg.data)
|
|
||||||
elif msg.type == aiohttp.WSMsgType.PING:
|
|
||||||
ws.pong()
|
|
||||||
elif msg.type == aiohttp.WSMsgType.PONG:
|
|
||||||
print('Pong received')
|
|
||||||
else:
|
|
||||||
if msg.type == aiohttp.WSMsgType.CLOSE:
|
|
||||||
yield from ws.close()
|
|
||||||
elif msg.type == aiohttp.WSMsgType.ERROR:
|
|
||||||
print('Error during receive %s' % ws.exception())
|
|
||||||
elif msg.type == aiohttp.WSMsgType.CLOSED:
|
|
||||||
pass
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
yield from dispatch()
|
|
||||||
|
|
||||||
|
|
||||||
ARGS = argparse.ArgumentParser(
|
|
||||||
description="websocket console client for wssrv.py example.")
|
|
||||||
ARGS.add_argument(
|
|
||||||
'--host', action="store", dest='host',
|
|
||||||
default='127.0.0.1', help='Host name')
|
|
||||||
ARGS.add_argument(
|
|
||||||
'--port', action="store", dest='port',
|
|
||||||
default=8080, type=int, help='Port number')
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
args = ARGS.parse_args()
|
|
||||||
if ':' in args.host:
|
|
||||||
args.host, port = args.host.split(':', 1)
|
|
||||||
args.port = int(port)
|
|
||||||
|
|
||||||
url = 'http://{}:{}/ws/'.format(args.host, args.port)
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.add_signal_handler(signal.SIGINT, loop.stop)
|
|
||||||
asyncio.Task(start_client(loop, url))
|
|
||||||
loop.run_forever()
|
|
@ -1,7 +0,0 @@
|
|||||||
[book]
|
|
||||||
title = "Actix web"
|
|
||||||
description = "Actix web framework guide"
|
|
||||||
author = "Actix Project and Contributors"
|
|
||||||
|
|
||||||
[output.html]
|
|
||||||
google-analytics = "UA-110322332-1"
|
|
@ -1,16 +0,0 @@
|
|||||||
# Summary
|
|
||||||
|
|
||||||
[Quickstart](./qs_1.md)
|
|
||||||
- [Getting Started](./qs_2.md)
|
|
||||||
- [Application](./qs_3.md)
|
|
||||||
- [Server](./qs_3_5.md)
|
|
||||||
- [Handler](./qs_4.md)
|
|
||||||
- [Errors](./qs_4_5.md)
|
|
||||||
- [URL Dispatch](./qs_5.md)
|
|
||||||
- [Request & Response](./qs_7.md)
|
|
||||||
- [Testing](./qs_8.md)
|
|
||||||
- [Middlewares](./qs_10.md)
|
|
||||||
- [Static file handling](./qs_12.md)
|
|
||||||
- [WebSockets](./qs_9.md)
|
|
||||||
- [HTTP/2](./qs_13.md)
|
|
||||||
- [Database integration](./qs_14.md)
|
|
@ -1,34 +0,0 @@
|
|||||||
# Quick start
|
|
||||||
|
|
||||||
Before you can start writing a actix web application, you’ll need a version of Rust installed.
|
|
||||||
We recommend you use rustup to install or configure such a version.
|
|
||||||
|
|
||||||
## Install Rust
|
|
||||||
|
|
||||||
Before we begin, we need to install Rust using the [rustup](https://www.rustup.rs/) installer:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl https://sh.rustup.rs -sSf | sh
|
|
||||||
```
|
|
||||||
|
|
||||||
If you already have rustup installed, run this command to ensure you have the latest version of Rust:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rustup update
|
|
||||||
```
|
|
||||||
|
|
||||||
Actix web framework requires rust version 1.21 and up.
|
|
||||||
|
|
||||||
## Running Examples
|
|
||||||
|
|
||||||
The fastest way to start experimenting with actix web is to clone the actix web repository
|
|
||||||
and run the included examples in the examples/ directory. The following set of
|
|
||||||
commands runs the `basics` example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/actix/actix-web
|
|
||||||
cd actix-web/examples/basics
|
|
||||||
cargo run
|
|
||||||
```
|
|
||||||
|
|
||||||
Check [examples/](https://github.com/actix/actix-web/tree/master/examples) directory for more examples.
|
|
@ -1,212 +0,0 @@
|
|||||||
# Middlewares
|
|
||||||
|
|
||||||
Actix middlewares system allows to add additional behavior to request/response processing.
|
|
||||||
Middleware can hook into incoming request process and modify request or halt request
|
|
||||||
processing and return response early. Also it can hook into response processing.
|
|
||||||
|
|
||||||
Typically middlewares involves in following actions:
|
|
||||||
|
|
||||||
* Pre-process the Request
|
|
||||||
* Post-process a Response
|
|
||||||
* Modify application state
|
|
||||||
* Access external services (redis, logging, sessions)
|
|
||||||
|
|
||||||
Middlewares are registered for each application and get executed in same order as
|
|
||||||
registration order. In general, *middleware* is a type that implements
|
|
||||||
[*Middleware trait*](../actix_web/middlewares/trait.Middleware.html). Each method
|
|
||||||
in this trait has default implementation. Each method can return result immediately
|
|
||||||
or *future* object.
|
|
||||||
|
|
||||||
Here is example of simple middleware that adds request and response headers:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate http;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use http::{header, HttpTryFrom};
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::middleware::{Middleware, Started, Response};
|
|
||||||
|
|
||||||
struct Headers; // <- Our middleware
|
|
||||||
|
|
||||||
/// Middleware implementation, middlewares are generic over application state,
|
|
||||||
/// so you can access state with `HttpRequest::state()` method.
|
|
||||||
impl<S> Middleware<S> for Headers {
|
|
||||||
|
|
||||||
/// Method is called when request is ready. It may return
|
|
||||||
/// future, which should resolve before next middleware get called.
|
|
||||||
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
|
||||||
req.headers_mut().insert(
|
|
||||||
header::CONTENT_TYPE, header::HeaderValue::from_static("text/plain"));
|
|
||||||
Ok(Started::Done)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Method is called when handler returns response,
|
|
||||||
/// but before sending http message to peer.
|
|
||||||
fn response(&self, req: &mut HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> {
|
|
||||||
resp.headers_mut().insert(
|
|
||||||
header::HeaderName::try_from("X-VERSION").unwrap(),
|
|
||||||
header::HeaderValue::from_static("0.2"));
|
|
||||||
Ok(Response::Done(resp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.middleware(Headers) // <- Register middleware, this method could be called multiple times
|
|
||||||
.resource("/", |r| r.h(httpcodes::HttpOk));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Active provides several useful middlewares, like *logging*, *user sessions*, etc.
|
|
||||||
|
|
||||||
|
|
||||||
## Logging
|
|
||||||
|
|
||||||
Logging is implemented as middleware.
|
|
||||||
It is common to register logging middleware as first middleware for application.
|
|
||||||
Logging middleware has to be registered for each application. *Logger* middleware
|
|
||||||
uses standard log crate to log information. You should enable logger for *actix_web*
|
|
||||||
package to see access log. ([env_logger](https://docs.rs/env_logger/*/env_logger/) or similar)
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
Create `Logger` middleware with the specified `format`.
|
|
||||||
Default `Logger` could be created with `default` method, it uses the default format:
|
|
||||||
|
|
||||||
```ignore
|
|
||||||
%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
|
||||||
```
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
use actix_web::Application;
|
|
||||||
use actix_web::middleware::Logger;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
Application::new()
|
|
||||||
.middleware(Logger::default())
|
|
||||||
.middleware(Logger::new("%a %{User-Agent}i"))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Here is example of default logging format:
|
|
||||||
|
|
||||||
```
|
|
||||||
INFO:actix_web::middleware::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397
|
|
||||||
INFO:actix_web::middleware::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] "GET /index.html HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0" 0.000646
|
|
||||||
```
|
|
||||||
|
|
||||||
### Format
|
|
||||||
|
|
||||||
`%%` The percent sign
|
|
||||||
|
|
||||||
`%a` Remote IP-address (IP-address of proxy if using reverse proxy)
|
|
||||||
|
|
||||||
`%t` Time when the request was started to process
|
|
||||||
|
|
||||||
`%P` The process ID of the child that serviced the request
|
|
||||||
|
|
||||||
`%r` First line of request
|
|
||||||
|
|
||||||
`%s` Response status code
|
|
||||||
|
|
||||||
`%b` Size of response in bytes, including HTTP headers
|
|
||||||
|
|
||||||
`%T` Time taken to serve the request, in seconds with floating fraction in .06f format
|
|
||||||
|
|
||||||
`%D` Time taken to serve the request, in milliseconds
|
|
||||||
|
|
||||||
`%{FOO}i` request.headers['FOO']
|
|
||||||
|
|
||||||
`%{FOO}o` response.headers['FOO']
|
|
||||||
|
|
||||||
`%{FOO}e` os.environ['FOO']
|
|
||||||
|
|
||||||
|
|
||||||
## Default headers
|
|
||||||
|
|
||||||
To set default response headers `DefaultHeaders` middleware could be used.
|
|
||||||
*DefaultHeaders* middleware does not set header if response headers already contains
|
|
||||||
specified header.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let app = Application::new()
|
|
||||||
.middleware(
|
|
||||||
middleware::DefaultHeaders::build()
|
|
||||||
.header("X-Version", "0.2")
|
|
||||||
.finish())
|
|
||||||
.resource("/test", |r| {
|
|
||||||
r.method(Method::GET).f(|req| httpcodes::HttpOk);
|
|
||||||
r.method(Method::HEAD).f(|req| httpcodes::HttpMethodNotAllowed);
|
|
||||||
})
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## User sessions
|
|
||||||
|
|
||||||
Actix provides general solution for session management.
|
|
||||||
[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware can be
|
|
||||||
use with different backend types to store session data in different backends.
|
|
||||||
By default only cookie session backend is implemented. Other backend implementations
|
|
||||||
could be added later.
|
|
||||||
|
|
||||||
[*Cookie session backend*](../actix_web/middleware/struct.CookieSessionBackend.html)
|
|
||||||
uses signed cookies as session storage. *Cookie session backend* creates sessions which
|
|
||||||
are limited to storing fewer than 4000 bytes of data (as the payload must fit into a
|
|
||||||
single cookie). Internal server error get generated if session contains more than 4000 bytes.
|
|
||||||
|
|
||||||
You need to pass a random value to the constructor of *CookieSessionBackend*.
|
|
||||||
This is private key for cookie session. When this value is changed, all session data is lost.
|
|
||||||
Note that whatever you write into your session is visible by the user (but not modifiable).
|
|
||||||
|
|
||||||
In general case, you create
|
|
||||||
[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware
|
|
||||||
and initializes it with specific backend implementation, like *CookieSessionBackend*.
|
|
||||||
To access session data
|
|
||||||
[*HttpRequest::session()*](../actix_web/middleware/trait.RequestSession.html#tymethod.session)
|
|
||||||
method has to be used. This method returns
|
|
||||||
[*Session*](../actix_web/middleware/struct.Session.html) object, which allows to get or set
|
|
||||||
session data.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend};
|
|
||||||
|
|
||||||
fn index(mut req: HttpRequest) -> Result<&'static str> {
|
|
||||||
// access session data
|
|
||||||
if let Some(count) = req.session().get::<i32>("counter")? {
|
|
||||||
println!("SESSION value: {}", count);
|
|
||||||
req.session().set("counter", count+1)?;
|
|
||||||
} else {
|
|
||||||
req.session().set("counter", 1)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok("Welcome!")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
# let sys = actix::System::new("basic-example");
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.middleware(SessionStorage::new( // <- create session middleware
|
|
||||||
CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend
|
|
||||||
.secure(false)
|
|
||||||
.finish()
|
|
||||||
)))
|
|
||||||
.bind("127.0.0.1:59880").unwrap()
|
|
||||||
.start();
|
|
||||||
# actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
|
||||||
# let _ = sys.run();
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,49 +0,0 @@
|
|||||||
# Static file handling
|
|
||||||
|
|
||||||
## Individual file
|
|
||||||
|
|
||||||
It is possible to serve static files with custom path pattern and `NamedFile`. To
|
|
||||||
match path tail we can use `[.*]` regex.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Result<fs::NamedFile> {
|
|
||||||
let path: PathBuf = req.match_info().query("tail")?;
|
|
||||||
Ok(fs::NamedFile::open(path)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Directory
|
|
||||||
|
|
||||||
To serve files from specific directory and sub-directories `StaticFiles` could be used.
|
|
||||||
`StaticFiles` must be registered with `Application::handler()` method otherwise
|
|
||||||
it won't be able to server sub-paths.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::new()
|
|
||||||
.handler("/static", fs::StaticFiles::new(".", true))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
First parameter is a base directory. Second parameter is *show_index*, if it is set to *true*
|
|
||||||
directory listing would be returned for directories, if it is set to *false*
|
|
||||||
then *404 Not Found* would be returned instead of directory listing.
|
|
||||||
|
|
||||||
Instead of showing files listing for directory, it is possible to redirect to specific
|
|
||||||
index file. Use
|
|
||||||
[*StaticFiles::index_file()*](../actix_web/s/struct.StaticFiles.html#method.index_file)
|
|
||||||
method to configure this redirect.
|
|
@ -1,44 +0,0 @@
|
|||||||
# HTTP/2.0
|
|
||||||
|
|
||||||
Actix web automatically upgrades connection to *HTTP/2.0* if possible.
|
|
||||||
|
|
||||||
## Negotiation
|
|
||||||
|
|
||||||
*HTTP/2.0* protocol over tls without prior knowledge requires
|
|
||||||
[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only
|
|
||||||
`rust-openssl` has support. Turn on `alpn` feature to enable `alpn` negotiation.
|
|
||||||
With enable `alpn` feature `HttpServer` provides
|
|
||||||
[serve_tls](../actix_web/struct.HttpServer.html#method.serve_tls) method.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
actix-web = { version = "0.3.3", features=["alpn"] }
|
|
||||||
openssl = { version="0.10", features = ["v110"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
use std::fs::File;
|
|
||||||
use actix_web::*;
|
|
||||||
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// load ssl keys
|
|
||||||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
|
||||||
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
|
|
||||||
builder.set_certificate_chain_file("cert.pem").unwrap();
|
|
||||||
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.resource("/index.html", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8080").unwrap();
|
|
||||||
.serve_ssl(builder).unwrap();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Upgrade to *HTTP/2.0* schema described in
|
|
||||||
[rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) is not supported.
|
|
||||||
Starting *HTTP/2* with prior knowledge is supported for both clear text connection
|
|
||||||
and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.section.3.4)
|
|
||||||
|
|
||||||
Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls)
|
|
||||||
for concrete example.
|
|
@ -1,127 +0,0 @@
|
|||||||
# Database integration
|
|
||||||
|
|
||||||
## Diesel
|
|
||||||
|
|
||||||
At the moment of 1.0 release Diesel does not support asynchronous operations.
|
|
||||||
But it possible to use `actix` synchronous actor system as a db interface api.
|
|
||||||
Technically sync actors are worker style actors, multiple of them
|
|
||||||
can be run in parallel and process messages from same queue (sync actors work in mpmc mode).
|
|
||||||
|
|
||||||
Let's create simple db api that can insert new user row into sqlite table.
|
|
||||||
We have to define sync actor and connection that this actor will use. Same approach
|
|
||||||
could be used for other databases.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
use actix::prelude::*;
|
|
||||||
|
|
||||||
struct DbExecutor(SqliteConnection);
|
|
||||||
|
|
||||||
impl Actor for DbExecutor {
|
|
||||||
type Context = SyncContext<Self>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is definition of our actor. Now we need to define *create user* message and response.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
struct CreateUser {
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message for CreateUser {
|
|
||||||
type Result = Result<User, Error>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
We can send `CreateUser` message to `DbExecutor` actor, and as result we get
|
|
||||||
`User` model. Now we need to define actual handler implementation for this message.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
impl Handler<CreateUser> for DbExecutor {
|
|
||||||
type Result = Result<User, Error>;
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result
|
|
||||||
{
|
|
||||||
use self::schema::users::dsl::*;
|
|
||||||
|
|
||||||
// Create insertion model
|
|
||||||
let uuid = format!("{}", uuid::Uuid::new_v4());
|
|
||||||
let new_user = models::NewUser {
|
|
||||||
id: &uuid,
|
|
||||||
name: &msg.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
// normal diesl operations
|
|
||||||
diesel::insert_into(users)
|
|
||||||
.values(&new_user)
|
|
||||||
.execute(&self.0)
|
|
||||||
.expect("Error inserting person");
|
|
||||||
|
|
||||||
let mut items = users
|
|
||||||
.filter(id.eq(&uuid))
|
|
||||||
.load::<models::User>(&self.0)
|
|
||||||
.expect("Error loading person");
|
|
||||||
|
|
||||||
Ok(items.pop().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
That is it. Now we can use *DbExecutor* actor from any http handler or middleware.
|
|
||||||
All we need is to start *DbExecutor* actors and store address in a state where http handler
|
|
||||||
can access it.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
/// This is state where we will store *DbExecutor* address.
|
|
||||||
struct State {
|
|
||||||
db: Addr<Syn, DbExecutor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let sys = actix::System::new("diesel-example");
|
|
||||||
|
|
||||||
// Start 3 parallel db executors
|
|
||||||
let addr = SyncArbiter::start(3, || {
|
|
||||||
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start http server
|
|
||||||
HttpServer::new(move || {
|
|
||||||
Application::with_state(State{db: addr.clone()})
|
|
||||||
.resource("/{name}", |r| r.method(Method::GET).a(index))})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start().unwrap();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And finally we can use address in a request handler. We get message response
|
|
||||||
asynchronously, so handler needs to return future object, also `Route::a()` needs to be
|
|
||||||
used for async handler registration.
|
|
||||||
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
/// Async handler
|
|
||||||
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
let name = &req.match_info()["name"];
|
|
||||||
|
|
||||||
// Send message to `DbExecutor` actor
|
|
||||||
req.state().db.send(CreateUser{name: name.to_owned()})
|
|
||||||
.from_err()
|
|
||||||
.and_then(|res| {
|
|
||||||
match res {
|
|
||||||
Ok(user) => Ok(httpcodes::HttpOk.build().json(user)?),
|
|
||||||
Err(_) => Ok(httpcodes::HttpInternalServerError.into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Full example is available in
|
|
||||||
[examples directory](https://github.com/actix/actix-web/tree/master/examples/diesel/).
|
|
||||||
|
|
||||||
More information on sync actors could be found in
|
|
||||||
[actix documentation](https://docs.rs/actix/0.5.0/actix/sync/index.html).
|
|
@ -1,98 +0,0 @@
|
|||||||
# Getting Started
|
|
||||||
|
|
||||||
Let’s create and run our first actix web application. We’ll create a new Cargo project
|
|
||||||
that depends on actix web and then run the application.
|
|
||||||
|
|
||||||
In previous section we already installed required rust version. Now let's create new cargo projects.
|
|
||||||
|
|
||||||
## Hello, world!
|
|
||||||
|
|
||||||
Let’s write our first actix web application! Start by creating a new binary-based
|
|
||||||
Cargo project and changing into the new directory:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo new hello-world --bin
|
|
||||||
cd hello-world
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, add actix and actix web as dependencies of your project by ensuring your Cargo.toml
|
|
||||||
contains the following:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = "0.4"
|
|
||||||
```
|
|
||||||
|
|
||||||
In order to implement a web server, first we need to create a request handler.
|
|
||||||
|
|
||||||
A request handler is a function that accepts a `HttpRequest` instance as its only parameter
|
|
||||||
and returns a type that can be converted into `HttpResponse`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::*;
|
|
||||||
fn index(req: HttpRequest) -> &'static str {
|
|
||||||
"Hello world!"
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, create an `Application` instance and register the
|
|
||||||
request handler with the application's `resource` on a particular *HTTP method* and *path*::
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::*;
|
|
||||||
# fn index(req: HttpRequest) -> &'static str {
|
|
||||||
# "Hello world!"
|
|
||||||
# }
|
|
||||||
# fn main() {
|
|
||||||
Application::new()
|
|
||||||
.resource("/", |r| r.f(index));
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
After that, application instance can be used with `HttpServer` to listen for incoming
|
|
||||||
connections. Server accepts function that should return `HttpHandler` instance:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.resource("/", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8088")?
|
|
||||||
.run();
|
|
||||||
```
|
|
||||||
|
|
||||||
That's it. Now, compile and run the program with cargo run.
|
|
||||||
Head over to ``http://localhost:8088/`` to see the results.
|
|
||||||
|
|
||||||
Here is full source of main.rs file:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# use std::thread;
|
|
||||||
extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> &'static str {
|
|
||||||
"Hello world!"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
# // In the doctest suite we can't run blocking code - deliberately leak a thread
|
|
||||||
# // If copying this example in show-all mode make sure you skip the thread spawn
|
|
||||||
# // call.
|
|
||||||
# thread::spawn(|| {
|
|
||||||
HttpServer::new(
|
|
||||||
|| Application::new()
|
|
||||||
.resource("/", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088")
|
|
||||||
.run();
|
|
||||||
# });
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note on `actix` crate. Actix web framework is built on top of actix actor library.
|
|
||||||
`actix::System` initializes actor system, `HttpServer` is an actor and must run within
|
|
||||||
properly configured actix system. For more information please check
|
|
||||||
[actix documentation](https://actix.github.io/actix/actix/)
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user