mirror of
https://github.com/fafhrd91/actix-web
synced 2025-08-19 20:35:36 +02:00
Compare commits
576 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46db09428c | ||
|
|
90eef31cc0 | ||
|
|
86af02156b | ||
|
|
ac9fc662c6 | ||
|
|
0745a1a9f8 | ||
|
|
b1635bc0e6 | ||
|
|
08c7743bb8 | ||
|
|
68c5d6e6d6 | ||
|
|
c386353337 | ||
|
|
9aab382ea8 | ||
|
|
389cb13cd6 | ||
|
|
6a93178479 | ||
|
|
cd9901c928 | ||
|
|
1ef0eed0bd | ||
|
|
61b1030882 | ||
|
|
7065c540e1 | ||
|
|
aed3933ae8 | ||
|
|
5b7740dee3 | ||
|
|
1a0bf32ec7 | ||
|
|
9ab586e24e | ||
|
|
62f1c90c8d | ||
|
|
2677d325a7 | ||
|
|
8e354021d4 | ||
|
|
3b536ee96c | ||
|
|
cfd9a56ff7 | ||
|
|
5f91f5eda6 | ||
|
|
42d5d48e71 | ||
|
|
960274ada8 | ||
|
|
f383f618b5 | ||
|
|
c04b4678f1 | ||
|
|
dd948f836e | ||
|
|
63a443fce0 | ||
|
|
d145136e56 | ||
|
|
32145cf6c3 | ||
|
|
ec8aef6b43 | ||
|
|
f45038bbfe | ||
|
|
c63838bb71 | ||
|
|
4d17a9afcc | ||
|
|
65e9201b4d | ||
|
|
c3ad516f56 | ||
|
|
93b1c5fd46 | ||
|
|
4e7fac08b9 | ||
|
|
07f6ca4b71 | ||
|
|
03d988b898 | ||
|
|
cfad5bf1f3 | ||
|
|
10678a22af | ||
|
|
7ae5a43877 | ||
|
|
1e1a4f846e | ||
|
|
49eea3bf76 | ||
|
|
b0677aa029 | ||
|
|
401ea574c0 | ||
|
|
bbcd618304 | ||
|
|
1f68ce8541 | ||
|
|
2710f70e39 | ||
|
|
ae5c4dfb78 | ||
|
|
d7379bd10b | ||
|
|
b59712c439 | ||
|
|
724668910b | ||
|
|
61c7534e03 | ||
|
|
f8b176de9e | ||
|
|
c8505bb53f | ||
|
|
eed377e773 | ||
|
|
f3ce6574e4 | ||
|
|
f007860a16 | ||
|
|
fdfadb52e1 | ||
|
|
368f73513a | ||
|
|
c674ea9126 | ||
|
|
7c78797d9b | ||
|
|
84edc57fd9 | ||
|
|
127af92541 | ||
|
|
e4686f6c8d | ||
|
|
1bac65de4c | ||
|
|
16945a554a | ||
|
|
91af3ca148 | ||
|
|
2217a152cb | ||
|
|
c1e0b4f322 | ||
|
|
5966ee6192 | ||
|
|
4aac3d6a92 | ||
|
|
e95babf8d3 | ||
|
|
f2d42e5e77 | ||
|
|
0f1c80ccc6 | ||
|
|
fc5088b55e | ||
|
|
bec37fdbd5 | ||
|
|
4b59ae2476 | ||
|
|
d0fc9d7b99 | ||
|
|
1ff86e5ac4 | ||
|
|
ecfda64f6d | ||
|
|
0bca21ec6d | ||
|
|
3173c9fa83 | ||
|
|
85445ea809 | ||
|
|
d57579d700 | ||
|
|
b6a1cfa6ad | ||
|
|
9f1417af30 | ||
|
|
0aa0f326f7 | ||
|
|
dbb4fab4f7 | ||
|
|
6f3e70a92a | ||
|
|
a63d3f9a7a | ||
|
|
a3cfc24232 | ||
|
|
6a61138bf8 | ||
|
|
7cf9af9b55 | ||
|
|
c9a52e3197 | ||
|
|
1907102685 | ||
|
|
52195bbf16 | ||
|
|
59deb4b40d | ||
|
|
782eeb5ded | ||
|
|
1b298142e3 | ||
|
|
0dc96658f2 | ||
|
|
f40153fca4 | ||
|
|
764103566d | ||
|
|
bfb2f2e9e1 | ||
|
|
599e6b3385 | ||
|
|
03e318f446 | ||
|
|
7449884ce3 | ||
|
|
bbe69e5b8d | ||
|
|
9d1eefc38f | ||
|
|
d65c72b44d | ||
|
|
c3f8b5cf22 | ||
|
|
70a3f317d3 | ||
|
|
513c8ec1ce | ||
|
|
04608b2ea6 | ||
|
|
70b45659e2 | ||
|
|
e0ae6b10cd | ||
|
|
003b05b095 | ||
|
|
cdb57b840e | ||
|
|
002bb24b26 | ||
|
|
51982b3fec | ||
|
|
4251b0bc10 | ||
|
|
42f3773bec | ||
|
|
86fdbb47a5 | ||
|
|
4ca9fd2ad1 | ||
|
|
f0f67072ae | ||
|
|
24d1228943 | ||
|
|
b7a73e0a4f | ||
|
|
968c81e267 | ||
|
|
d5957a8466 | ||
|
|
f2f05e7715 | ||
|
|
3439f55288 | ||
|
|
0425e2776f | ||
|
|
6464f96f8b | ||
|
|
a2b170fec9 | ||
|
|
0b42cae082 | ||
|
|
c313c003a4 | ||
|
|
3fa23f5e10 | ||
|
|
2d51831899 | ||
|
|
e59abfd716 | ||
|
|
66881d7dd1 | ||
|
|
a42a8a2321 | ||
|
|
2341656173 | ||
|
|
487519acec | ||
|
|
af6caa92c8 | ||
|
|
3ccbce6bc8 | ||
|
|
797b52ecbf | ||
|
|
4bab50c861 | ||
|
|
5906971b6d | ||
|
|
8393d09a0f | ||
|
|
c3ae9997fc | ||
|
|
d39dcc58cd | ||
|
|
471a3e9806 | ||
|
|
48ef18ffa9 | ||
|
|
9ef7a9c182 | ||
|
|
3dafe6c251 | ||
|
|
8dfc34e785 | ||
|
|
810995ade0 | ||
|
|
1716380f08 | ||
|
|
e9c139bdea | ||
|
|
cf54be2f17 | ||
|
|
f39b520a2d | ||
|
|
89f414477c | ||
|
|
986f19af86 | ||
|
|
e680541e10 | ||
|
|
56bc900a82 | ||
|
|
bdc9a8bb07 | ||
|
|
8fe30a5b66 | ||
|
|
a8405d0686 | ||
|
|
eb1e9a785f | ||
|
|
248bd388ca | ||
|
|
9f5641c85b | ||
|
|
d9c7cd96a6 | ||
|
|
bf7779a9a3 | ||
|
|
cc3fbd27e0 | ||
|
|
26629aafa5 | ||
|
|
2ab7dbadce | ||
|
|
2e8d67e2ae | ||
|
|
43b6828ab5 | ||
|
|
e4ce6dfbdf | ||
|
|
6b9fa2c3d9 | ||
|
|
5713d93158 | ||
|
|
cfe4829a56 | ||
|
|
b69774db61 | ||
|
|
542782f28a | ||
|
|
7c8dc4c201 | ||
|
|
7a11c2eac1 | ||
|
|
8eb9eb4247 | ||
|
|
992f7a11b3 | ||
|
|
30769e3072 | ||
|
|
57f991280c | ||
|
|
85acc3f8df | ||
|
|
5bd82d4f03 | ||
|
|
58a079bd10 | ||
|
|
16546a707f | ||
|
|
86a5afb5ca | ||
|
|
9c80d3aa77 | ||
|
|
954f1a0b0f | ||
|
|
f4fba5f481 | ||
|
|
995f819eae | ||
|
|
85e7548088 | ||
|
|
900fd5a98e | ||
|
|
84b27db218 | ||
|
|
ac9180ac46 | ||
|
|
e34b5c08ba | ||
|
|
f3f1e04853 | ||
|
|
036cf5e867 | ||
|
|
e61ef7dee4 | ||
|
|
9a10d8aa7a | ||
|
|
f8e5d7c6c1 | ||
|
|
8c89c90c50 | ||
|
|
e9c1889df4 | ||
|
|
0da3fdcb09 | ||
|
|
a5f80a25ff | ||
|
|
6d9a1cadad | ||
|
|
97ada3d3d0 | ||
|
|
115f59dd14 | ||
|
|
972b008a6e | ||
|
|
246eafb8d2 | ||
|
|
dca4c110dd | ||
|
|
58230b15b9 | ||
|
|
aa1e75f071 | ||
|
|
2071ea0532 | ||
|
|
3bd43090fb | ||
|
|
4dba531bf9 | ||
|
|
2072c933ba | ||
|
|
7bc0ace52d | ||
|
|
4c4d0d2745 | ||
|
|
28a855214b | ||
|
|
196da6d570 | ||
|
|
b4ed564e5d | ||
|
|
80fbc2e9ec | ||
|
|
f58065082e | ||
|
|
6048817ba7 | ||
|
|
e408b68744 | ||
|
|
b878613e10 | ||
|
|
85b275bb2b | ||
|
|
d6abd2fe22 | ||
|
|
b79a9aaec7 | ||
|
|
b9586b3f71 | ||
|
|
d3b12d885e | ||
|
|
f21386708a | ||
|
|
b48a2d4d7b | ||
|
|
35b754a3ab | ||
|
|
1079c5c562 | ||
|
|
f4bb7efa89 | ||
|
|
0099091e96 | ||
|
|
c352a69d54 | ||
|
|
f5347ec897 | ||
|
|
b367f07d56 | ||
|
|
6a75a3d683 | ||
|
|
56b924e155 | ||
|
|
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 |
@@ -1,37 +1,15 @@
|
||||
environment:
|
||||
global:
|
||||
PROJECT_NAME: actix
|
||||
PROJECT_NAME: actix-web
|
||||
matrix:
|
||||
# Stable channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: 1.24.0
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: 1.24.0
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: 1.24.0
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: 1.24.0
|
||||
# Stable channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: stable
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: stable
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: stable
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
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
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: nightly
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
@@ -59,4 +37,5 @@ build: false
|
||||
|
||||
# Equivalent to Travis' `script` phase
|
||||
test_script:
|
||||
- cargo clean
|
||||
- cargo test --no-default-features --features="flate2-rust"
|
||||
|
||||
20
.travis.yml
20
.travis.yml
@@ -1,5 +1,5 @@
|
||||
language: rust
|
||||
sudo: false
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
cache:
|
||||
@@ -8,7 +8,6 @@ cache:
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- rust: 1.24.0
|
||||
- rust: stable
|
||||
- rust: beta
|
||||
- rust: nightly
|
||||
@@ -23,7 +22,7 @@ env:
|
||||
before_install:
|
||||
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
|
||||
- sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
|
||||
|
||||
# Add clippy
|
||||
before_script:
|
||||
@@ -31,14 +30,17 @@ before_script:
|
||||
|
||||
script:
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" != "1.24.0" ]]; then
|
||||
if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then
|
||||
cargo clean
|
||||
cargo test --features="alpn,tls" -- --nocapture
|
||||
cargo check --features rust-tls
|
||||
cargo check --features ssl
|
||||
cargo check --features tls
|
||||
cargo test --features="ssl,tls,rust-tls,uds" -- --nocapture
|
||||
fi
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" == "1.24.0" ]]; then
|
||||
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
|
||||
USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count
|
||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
||||
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin
|
||||
RUST_BACKTRACE=1 cargo tarpaulin --features="ssl,tls,rust-tls" --out Xml
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
echo "Uploaded code coverage"
|
||||
fi
|
||||
@@ -47,7 +49,7 @@ script:
|
||||
after_success:
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
||||
cargo doc --features "alpn, tls, session" --no-deps &&
|
||||
cargo doc --features "ssl,tls,rust-tls,session" --no-deps &&
|
||||
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
||||
git clone https://github.com/davisp/ghp-import.git &&
|
||||
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
|
||||
|
||||
383
CHANGES.md
383
CHANGES.md
@@ -1,9 +1,386 @@
|
||||
# Changes
|
||||
|
||||
## [0.6.11] - 2018-06-05
|
||||
## [0.7.16] - 2018-12-11
|
||||
|
||||
### Added
|
||||
|
||||
* Implement `FromRequest` extractor for `Either<A,B>`
|
||||
|
||||
* Implement `ResponseError` for `SendError`
|
||||
|
||||
|
||||
## [0.7.15] - 2018-12-05
|
||||
|
||||
### Changed
|
||||
|
||||
* `ClientConnector::resolver` now accepts `Into<Recipient>` instead of `Addr`. It enables user to implement own resolver.
|
||||
|
||||
* `QueryConfig` and `PathConfig` are made public.
|
||||
|
||||
* `AsyncResult::async` is changed to `AsyncResult::future` as `async` is reserved keyword in 2018 edition.
|
||||
|
||||
### Added
|
||||
|
||||
* By default, `Path` extractor now percent decode all characters. This behaviour can be disabled
|
||||
with `PathConfig::default().disable_decoding()`
|
||||
|
||||
|
||||
## [0.7.14] - 2018-11-14
|
||||
|
||||
### Added
|
||||
|
||||
* Add method to configure custom error handler to `Query` and `Path` extractors.
|
||||
|
||||
* Add method to configure `SameSite` option in `CookieIdentityPolicy`.
|
||||
|
||||
* By default, `Path` extractor now percent decode all characters. This behaviour can be disabled
|
||||
with `PathConfig::default().disable_decoding()`
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix websockets connection drop if request contains "content-length" header #567
|
||||
|
||||
* Fix keep-alive timer reset
|
||||
|
||||
* HttpServer now treats streaming bodies the same for HTTP/1.x protocols. #549
|
||||
|
||||
* Set nodelay for socket #560
|
||||
|
||||
|
||||
## [0.7.13] - 2018-10-14
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed rustls support
|
||||
|
||||
* HttpServer not sending streamed request body on HTTP/2 requests #544
|
||||
|
||||
|
||||
## [0.7.12] - 2018-10-10
|
||||
|
||||
### Changed
|
||||
|
||||
* Set min version for actix
|
||||
|
||||
* Set min version for actix-net
|
||||
|
||||
|
||||
## [0.7.11] - 2018-10-09
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed 204 responses for http/2
|
||||
|
||||
|
||||
## [0.7.10] - 2018-10-09
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed panic during graceful shutdown
|
||||
|
||||
|
||||
## [0.7.9] - 2018-10-09
|
||||
|
||||
### Added
|
||||
|
||||
* Added client shutdown timeout setting
|
||||
|
||||
* Added slow request timeout setting
|
||||
|
||||
* Respond with 408 response on slow request timeout #523
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* HTTP1 decoding errors are reported to the client. #512
|
||||
|
||||
* Correctly compose multiple allowed origins in CORS. #517
|
||||
|
||||
* Websocket server finished() isn't called if client disconnects #511
|
||||
|
||||
* Responses with the following codes: 100, 101, 102, 204 -- are sent without Content-Length header. #521
|
||||
|
||||
* Correct usage of `no_http2` flag in `bind_*` methods. #519
|
||||
|
||||
|
||||
## [0.7.8] - 2018-09-17
|
||||
|
||||
### Added
|
||||
|
||||
* Use server `Keep-Alive` setting as slow request timeout #439
|
||||
|
||||
### Changed
|
||||
|
||||
* Use 5 seconds keep-alive timer by default.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed wrong error message for i16 type #510
|
||||
|
||||
|
||||
## [0.7.7] - 2018-09-11
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix linked list of HttpChannels #504
|
||||
|
||||
* Fix requests to TestServer fail #508
|
||||
|
||||
|
||||
## [0.7.6] - 2018-09-07
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix system_exit in HttpServer #501
|
||||
|
||||
* Fix parsing of route param containin regexes with repetition #500
|
||||
|
||||
### Changes
|
||||
|
||||
* Unhide `SessionBackend` and `SessionImpl` traits #455
|
||||
|
||||
|
||||
## [0.7.5] - 2018-09-04
|
||||
|
||||
### Added
|
||||
|
||||
* Added the ability to pass a custom `TlsConnector`.
|
||||
|
||||
* Allow to register handlers on scope level #465
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* Handle socket read disconnect
|
||||
|
||||
* Handling scoped paths without leading slashes #460
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* Read client response until eof if connection header set to close #464
|
||||
|
||||
|
||||
## [0.7.4] - 2018-08-23
|
||||
|
||||
### Added
|
||||
|
||||
* Added `HttpServer::maxconn()` and `HttpServer::maxconnrate()`,
|
||||
accept backpressure #250
|
||||
|
||||
* Allow to customize connection handshake process via `HttpServer::listen_with()`
|
||||
and `HttpServer::bind_with()` methods
|
||||
|
||||
* Support making client connections via `tokio-uds`'s `UnixStream` when "uds" feature is enabled #472
|
||||
|
||||
### Changed
|
||||
|
||||
* It is allowed to use function with up to 10 parameters for handler with `extractor parameters`.
|
||||
`Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
|
||||
even for handler with one parameter.
|
||||
|
||||
* native-tls - 0.2
|
||||
|
||||
* `Content-Disposition` is re-worked. Its parser is now more robust and handles quoted content better. See #461
|
||||
|
||||
### Fixed
|
||||
|
||||
* Use zlib instead of raw deflate for decoding and encoding payloads with
|
||||
`Content-Encoding: deflate`.
|
||||
|
||||
* Fixed headers formating for CORS Middleware Access-Control-Expose-Headers #436
|
||||
|
||||
* Fix adding multiple response headers #446
|
||||
|
||||
* Client includes port in HOST header when it is not default(e.g. not 80 and 443). #448
|
||||
|
||||
* Panic during access without routing being set #452
|
||||
|
||||
* Fixed http/2 error handling
|
||||
|
||||
### Deprecated
|
||||
|
||||
* `HttpServer::no_http2()` is deprecated, use `OpensslAcceptor::with_flags()` or
|
||||
`RustlsAcceptor::with_flags()` instead
|
||||
|
||||
* `HttpServer::listen_tls()`, `HttpServer::listen_ssl()`, `HttpServer::listen_rustls()` have been
|
||||
deprecated in favor of `HttpServer::listen_with()` with specific `acceptor`.
|
||||
|
||||
* `HttpServer::bind_tls()`, `HttpServer::bind_ssl()`, `HttpServer::bind_rustls()` have been
|
||||
deprecated in favor of `HttpServer::bind_with()` with specific `acceptor`.
|
||||
|
||||
|
||||
## [0.7.3] - 2018-08-01
|
||||
|
||||
### Added
|
||||
|
||||
* Support HTTP/2 with rustls #36
|
||||
|
||||
* Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed failure 0.1.2 compatibility
|
||||
|
||||
* Do not override HOST header for client request #428
|
||||
|
||||
* Gz streaming, use `flate2::write::GzDecoder` #228
|
||||
|
||||
* HttpRequest::url_for is not working with scopes #429
|
||||
|
||||
* Fixed headers' formating for CORS Middleware `Access-Control-Expose-Headers` header value to HTTP/1.1 & HTTP/2 spec-compliant format #436
|
||||
|
||||
|
||||
## [0.7.2] - 2018-07-26
|
||||
|
||||
### Added
|
||||
|
||||
* Add implementation of `FromRequest<S>` for `Option<T>` and `Result<T, Error>`
|
||||
|
||||
* Allow to handle application prefix, i.e. allow to handle `/app` path
|
||||
for application with `/app` prefix.
|
||||
Check [`App::prefix()`](https://actix.rs/actix-web/actix_web/struct.App.html#method.prefix)
|
||||
api doc.
|
||||
|
||||
* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies
|
||||
|
||||
### Changed
|
||||
|
||||
* Upgrade to cookie 0.11
|
||||
|
||||
* Removed the timestamp from the default logger middleware
|
||||
|
||||
### Fixed
|
||||
|
||||
* Missing response header "content-encoding" #421
|
||||
|
||||
* Fix stream draining for http/2 connections #290
|
||||
|
||||
|
||||
## [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
|
||||
@@ -19,7 +396,7 @@
|
||||
|
||||
### Added
|
||||
|
||||
* Allow to use path without traling slashes for scope registration #241
|
||||
* Allow to use path without trailing slashes for scope registration #241
|
||||
|
||||
* Allow to set encoding for exact NamedFile #239
|
||||
|
||||
@@ -380,7 +757,7 @@
|
||||
|
||||
* Server multi-threading
|
||||
|
||||
* Gracefull shutdown support
|
||||
* Graceful shutdown support
|
||||
|
||||
|
||||
## 0.2.1 (2017-11-03)
|
||||
|
||||
78
Cargo.toml
78
Cargo.toml
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "0.6.11"
|
||||
version = "0.7.16"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||
readme = "README.md"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
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",
|
||||
"web-programming::http-server",
|
||||
"web-programming::http-client",
|
||||
@@ -16,6 +16,9 @@ license = "MIT/Apache-2.0"
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||
build = "build.rs"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["tls", "ssl", "rust-tls", "session", "brotli", "flate2-c"]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "actix/actix-web", branch = "master" }
|
||||
appveyor = { repository = "fafhrd91/actix-web-hdy9d" }
|
||||
@@ -26,13 +29,22 @@ name = "actix_web"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["session", "brotli", "flate2-c"]
|
||||
default = ["session", "brotli", "flate2-c", "cell"]
|
||||
|
||||
# tls
|
||||
tls = ["native-tls", "tokio-tls"]
|
||||
tls = ["native-tls", "tokio-tls", "actix-net/tls"]
|
||||
|
||||
# openssl
|
||||
alpn = ["openssl", "tokio-openssl"]
|
||||
ssl = ["openssl", "tokio-openssl", "actix-net/ssl"]
|
||||
|
||||
# deprecated, use "ssl"
|
||||
alpn = ["openssl", "tokio-openssl", "actix-net/ssl"]
|
||||
|
||||
# rustls
|
||||
rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots", "actix-net/rust-tls"]
|
||||
|
||||
# unix sockets
|
||||
uds = ["tokio-uds"]
|
||||
|
||||
# sessions feature, session require "ring" crate and c compiler
|
||||
session = ["cookie/secure"]
|
||||
@@ -46,62 +58,76 @@ flate2-c = ["flate2/miniz-sys"]
|
||||
# rust backend for flate2 crate
|
||||
flate2-rust = ["flate2/rust_backend"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["tls", "alpn", "session", "brotli", "flate2-c"]
|
||||
cell = ["actix-net/cell"]
|
||||
|
||||
[dependencies]
|
||||
actix = "^0.5.5"
|
||||
actix = "0.7.9"
|
||||
actix-net = "0.2.2"
|
||||
|
||||
base64 = "0.9"
|
||||
askama_escape = "0.1.0"
|
||||
base64 = "0.10"
|
||||
bitflags = "1.0"
|
||||
failure = "0.1.1"
|
||||
failure = "^0.1.2"
|
||||
h2 = "0.1"
|
||||
http = "^0.1.5"
|
||||
httparse = "1.2"
|
||||
http-range = "0.1"
|
||||
libc = "0.2"
|
||||
http = "^0.1.8"
|
||||
httparse = "1.3"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
mime_guess = "2.0.0-alpha"
|
||||
num_cpus = "1.0"
|
||||
percent-encoding = "1.0"
|
||||
rand = "0.4"
|
||||
rand = "0.6"
|
||||
regex = "1.0"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.5"
|
||||
sha1 = "0.6"
|
||||
smallvec = "0.6"
|
||||
time = "0.1"
|
||||
encoding = "0.2"
|
||||
language-tags = "0.2"
|
||||
lazy_static = "1.0"
|
||||
lazycell = "1.0.0"
|
||||
parking_lot = "0.7"
|
||||
serde_urlencoded = "^0.5.3"
|
||||
url = { version="1.7", features=["query_encoding"] }
|
||||
cookie = { version="0.10", features=["percent-encode"] }
|
||||
cookie = { version="0.11", features=["percent-encode"] }
|
||||
brotli2 = { version="^0.3.2", optional = true }
|
||||
flate2 = { version="1.0", optional = true, default-features = false }
|
||||
flate2 = { version="^1.0.2", optional = true, default-features = false }
|
||||
|
||||
# io
|
||||
mio = "^0.6.13"
|
||||
net2 = "0.2"
|
||||
bytes = "0.4"
|
||||
byteorder = "1"
|
||||
byteorder = "1.2"
|
||||
futures = "0.1"
|
||||
futures-cpupool = "0.1"
|
||||
slab = "0.4"
|
||||
tokio = "0.1"
|
||||
tokio-io = "0.1"
|
||||
tokio-core = "0.1"
|
||||
tokio-tcp = "0.1"
|
||||
tokio-timer = "0.2"
|
||||
tokio-reactor = "0.1"
|
||||
tokio-current-thread = "0.1"
|
||||
|
||||
# native-tls
|
||||
native-tls = { version="0.1", optional = true }
|
||||
tokio-tls = { version="0.1", optional = true }
|
||||
native-tls = { version="0.2", optional = true }
|
||||
tokio-tls = { version="0.2", optional = true }
|
||||
|
||||
# openssl
|
||||
openssl = { version="0.10", optional = true }
|
||||
tokio-openssl = { version="0.2", optional = true }
|
||||
|
||||
#rustls
|
||||
rustls = { version = "0.14", optional = true }
|
||||
tokio-rustls = { version = "0.8", optional = true }
|
||||
webpki = { version = "0.18", optional = true }
|
||||
webpki-roots = { version = "0.15", optional = true }
|
||||
|
||||
# unix sockets
|
||||
tokio-uds = { version="0.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.5"
|
||||
env_logger = "0.6"
|
||||
serde_derive = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
@@ -111,9 +137,3 @@ version_check = "0.1"
|
||||
lto = true
|
||||
opt-level = 3
|
||||
codegen-units = 1
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"./",
|
||||
"tools/wsload/",
|
||||
]
|
||||
|
||||
128
MIGRATION.md
128
MIGRATION.md
@@ -1,4 +1,128 @@
|
||||
## Migration from 0.5 to 0.6
|
||||
## 0.7.15
|
||||
|
||||
* The `' '` character is not percent decoded anymore before matching routes. If you need to use it in
|
||||
your routes, you should use `%20`.
|
||||
|
||||
instead of
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let app = App::new().resource("/my index", |r| {
|
||||
r.method(http::Method::GET)
|
||||
.with(index);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
use
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let app = App::new().resource("/my%20index", |r| {
|
||||
r.method(http::Method::GET)
|
||||
.with(index);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
* If you used `AsyncResult::async` you need to replace it with `AsyncResult::future`
|
||||
|
||||
|
||||
## 0.7.4
|
||||
|
||||
* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
|
||||
even for handler with one parameter.
|
||||
|
||||
|
||||
## 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`
|
||||
|
||||
@@ -50,7 +174,7 @@
|
||||
you need to use `use actix_web::ws::WsWriter`
|
||||
|
||||
|
||||
## Migration from 0.4 to 0.5
|
||||
## 0.5
|
||||
|
||||
* `HttpResponseBuilder::body()`, `.finish()`, `.json()`
|
||||
methods return `HttpResponse` instead of `Result<HttpResponse>`
|
||||
|
||||
2
Makefile
2
Makefile
@@ -1,6 +1,6 @@
|
||||
.PHONY: default build test doc book clean
|
||||
|
||||
CARGO_FLAGS := --features "$(FEATURES) alpn"
|
||||
CARGO_FLAGS := --features "$(FEATURES) alpn tls"
|
||||
|
||||
default: test
|
||||
|
||||
|
||||
19
README.md
19
README.md
@@ -8,27 +8,22 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
||||
* Client/server [WebSockets](https://actix.rs/docs/websockets/) support
|
||||
* Transparent content compression/decompression (br, gzip, deflate)
|
||||
* Configurable [request routing](https://actix.rs/docs/url-dispatch/)
|
||||
* Graceful server shutdown
|
||||
* Multipart streams
|
||||
* Static assets
|
||||
* SSL support with OpenSSL or `native-tls`
|
||||
* Middlewares ([Logger](https://actix.rs/book/actix-web/sec-9-middlewares.html#logging),
|
||||
[Session](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions),
|
||||
[Redis sessions](https://github.com/actix/actix-redis),
|
||||
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
|
||||
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
|
||||
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
|
||||
* Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/))
|
||||
* 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)
|
||||
* Experimental [Async/Await](https://github.com/mehcode/actix-web-async-await) support.
|
||||
|
||||
## Documentation & community resources
|
||||
|
||||
* [User Guide](https://actix.rs/docs/)
|
||||
* [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)
|
||||
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
||||
* Minimum supported Rust version: 1.24 or later
|
||||
* Minimum supported Rust version: 1.26 or later
|
||||
|
||||
## Example
|
||||
|
||||
@@ -56,7 +51,7 @@ fn main() {
|
||||
* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/)
|
||||
* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/)
|
||||
* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/)
|
||||
* [Tera](https://github.com/actix/examples/tree/master/template_tera/) /
|
||||
* [Tera](https://github.com/actix/examples/tree/master/template_tera/) /
|
||||
[Askama](https://github.com/actix/examples/tree/master/template_askama/) templates
|
||||
* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/)
|
||||
* [r2d2](https://github.com/actix/examples/tree/master/r2d2/)
|
||||
@@ -69,9 +64,7 @@ You may consider checking out
|
||||
|
||||
## Benchmarks
|
||||
|
||||
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext)
|
||||
|
||||
* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks).
|
||||
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
max_width = 89
|
||||
reorder_imports = true
|
||||
wrap_comments = true
|
||||
#wrap_comments = true
|
||||
fn_args_density = "Compressed"
|
||||
#use_small_heuristics = false
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
76
src/body.rs
76
src/body.rs
@@ -1,6 +1,6 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::Stream;
|
||||
use std::rc::Rc;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, mem};
|
||||
|
||||
@@ -35,10 +35,8 @@ pub enum Binary {
|
||||
/// Static slice
|
||||
Slice(&'static [u8]),
|
||||
/// Shared string body
|
||||
SharedString(Rc<String>),
|
||||
/// Shared string body
|
||||
#[doc(hidden)]
|
||||
ArcSharedString(Arc<String>),
|
||||
SharedString(Arc<String>),
|
||||
/// Shared vec body
|
||||
SharedVec(Arc<Vec<u8>>),
|
||||
}
|
||||
@@ -130,17 +128,18 @@ impl From<Box<ActorHttpContext>> for Body {
|
||||
|
||||
impl Binary {
|
||||
#[inline]
|
||||
/// Returns `true` if body is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Length of body in bytes
|
||||
pub fn len(&self) -> usize {
|
||||
match *self {
|
||||
Binary::Bytes(ref bytes) => bytes.len(),
|
||||
Binary::Slice(slice) => slice.len(),
|
||||
Binary::SharedString(ref s) => s.len(),
|
||||
Binary::ArcSharedString(ref s) => s.len(),
|
||||
Binary::SharedVec(ref s) => s.len(),
|
||||
}
|
||||
}
|
||||
@@ -162,7 +161,6 @@ impl Clone for Binary {
|
||||
Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()),
|
||||
Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)),
|
||||
Binary::SharedString(ref s) => Binary::SharedString(s.clone()),
|
||||
Binary::ArcSharedString(ref s) => Binary::ArcSharedString(s.clone()),
|
||||
Binary::SharedVec(ref s) => Binary::SharedVec(s.clone()),
|
||||
}
|
||||
}
|
||||
@@ -174,7 +172,6 @@ impl Into<Bytes> for Binary {
|
||||
Binary::Bytes(bytes) => bytes,
|
||||
Binary::Slice(slice) => Bytes::from(slice),
|
||||
Binary::SharedString(s) => Bytes::from(s.as_str()),
|
||||
Binary::ArcSharedString(s) => Bytes::from(s.as_str()),
|
||||
Binary::SharedVec(s) => Bytes::from(AsRef::<[u8]>::as_ref(s.as_ref())),
|
||||
}
|
||||
}
|
||||
@@ -198,12 +195,30 @@ impl From<Vec<u8>> for Binary {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, [u8]>> for Binary {
|
||||
fn from(b: Cow<'static, [u8]>) -> Binary {
|
||||
match b {
|
||||
Cow::Borrowed(s) => Binary::Slice(s),
|
||||
Cow::Owned(vec) => Binary::Bytes(Bytes::from(vec)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Binary {
|
||||
fn from(s: String) -> Binary {
|
||||
Binary::Bytes(Bytes::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, str>> for Binary {
|
||||
fn from(s: Cow<'static, str>) -> Binary {
|
||||
match s {
|
||||
Cow::Borrowed(s) => Binary::Slice(s.as_ref()),
|
||||
Cow::Owned(s) => Binary::Bytes(Bytes::from(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a String> for Binary {
|
||||
fn from(s: &'a String) -> Binary {
|
||||
Binary::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s)))
|
||||
@@ -222,27 +237,15 @@ impl From<BytesMut> for Binary {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rc<String>> for Binary {
|
||||
fn from(body: Rc<String>) -> Binary {
|
||||
Binary::SharedString(body)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Rc<String>> for Binary {
|
||||
fn from(body: &'a Rc<String>) -> Binary {
|
||||
Binary::SharedString(Rc::clone(body))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<String>> for Binary {
|
||||
fn from(body: Arc<String>) -> Binary {
|
||||
Binary::ArcSharedString(body)
|
||||
Binary::SharedString(body)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Arc<String>> for Binary {
|
||||
fn from(body: &'a Arc<String>) -> Binary {
|
||||
Binary::ArcSharedString(Arc::clone(body))
|
||||
Binary::SharedString(Arc::clone(body))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +268,6 @@ impl AsRef<[u8]> for Binary {
|
||||
Binary::Bytes(ref bytes) => bytes.as_ref(),
|
||||
Binary::Slice(slice) => slice,
|
||||
Binary::SharedString(ref s) => s.as_bytes(),
|
||||
Binary::ArcSharedString(ref s) => s.as_bytes(),
|
||||
Binary::SharedVec(ref s) => s.as_ref().as_ref(),
|
||||
}
|
||||
}
|
||||
@@ -304,6 +306,16 @@ mod tests {
|
||||
assert_eq!(Binary::from("test").as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cow_str() {
|
||||
let cow: Cow<'static, str> = Cow::Borrowed("test");
|
||||
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||
let cow: Cow<'static, str> = Cow::Owned("test".to_owned());
|
||||
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_bytes() {
|
||||
assert_eq!(Binary::from(b"test".as_ref()).len(), 4);
|
||||
@@ -325,19 +337,13 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ref_string() {
|
||||
let b = Rc::new("test".to_owned());
|
||||
assert_eq!(Binary::from(&b).len(), 4);
|
||||
assert_eq!(Binary::from(&b).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rc_string() {
|
||||
let b = Rc::new("test".to_owned());
|
||||
assert_eq!(Binary::from(b.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
|
||||
assert_eq!(Binary::from(&b).len(), 4);
|
||||
assert_eq!(Binary::from(&b).as_ref(), b"test");
|
||||
fn test_cow_bytes() {
|
||||
let cow: Cow<'static, [u8]> = Cow::Borrowed(b"test");
|
||||
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||
let cow: Cow<'static, [u8]> = Cow::Owned(Vec::from("test"));
|
||||
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,29 +1,27 @@
|
||||
//! Http client api
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate actix;
|
||||
//! # extern crate actix_web;
|
||||
//! # extern crate actix;
|
||||
//! # extern crate futures;
|
||||
//! # extern crate tokio;
|
||||
//! # use futures::Future;
|
||||
//! # use std::process;
|
||||
//! use actix_web::client;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let sys = actix::System::new("test");
|
||||
//!
|
||||
//! actix::Arbiter::handle().spawn({
|
||||
//! client::get("http://www.rust-lang.org") // <- Create request builder
|
||||
//! actix::run(
|
||||
//! || client::get("http://www.rust-lang.org") // <- Create request builder
|
||||
//! .header("User-Agent", "Actix-web")
|
||||
//! .finish().unwrap()
|
||||
//! .send() // <- Send http request
|
||||
//! .map_err(|_| ())
|
||||
//! .and_then(|response| { // <- server http response
|
||||
//! println!("Response: {:?}", response);
|
||||
//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
//! # actix::System::current().stop();
|
||||
//! Ok(())
|
||||
//! })
|
||||
//! });
|
||||
//!
|
||||
//! sys.run();
|
||||
//! );
|
||||
//! }
|
||||
//! ```
|
||||
mod connector;
|
||||
@@ -38,6 +36,7 @@ pub use self::connector::{
|
||||
Pause, Resume,
|
||||
};
|
||||
pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError};
|
||||
pub(crate) use self::pipeline::Pipeline;
|
||||
pub use self::pipeline::{SendRequest, SendRequestError};
|
||||
pub use self::request::{ClientRequest, ClientRequestBuilder};
|
||||
pub use self::response::ClientResponse;
|
||||
@@ -60,30 +59,30 @@ impl ResponseError for SendRequestError {
|
||||
|
||||
/// Create request builder for `GET` requests
|
||||
///
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate actix;
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio;
|
||||
/// # extern crate env_logger;
|
||||
/// # use futures::Future;
|
||||
/// # use std::process;
|
||||
/// use actix_web::client;
|
||||
///
|
||||
/// fn main() {
|
||||
/// let sys = actix::System::new("test");
|
||||
///
|
||||
/// actix::Arbiter::handle().spawn({
|
||||
/// client::get("http://www.rust-lang.org") // <- Create request builder
|
||||
/// actix::run(
|
||||
/// || client::get("http://www.rust-lang.org") // <- Create request builder
|
||||
/// .header("User-Agent", "Actix-web")
|
||||
/// .finish().unwrap()
|
||||
/// .send() // <- Send http request
|
||||
/// .map_err(|_| ())
|
||||
/// .and_then(|response| { // <- server http response
|
||||
/// .and_then(|response| { // <- server http response
|
||||
/// println!("Response: {:?}", response);
|
||||
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
/// # actix::System::current().stop();
|
||||
/// Ok(())
|
||||
/// })
|
||||
/// });
|
||||
///
|
||||
/// sys.run();
|
||||
/// }),
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use std::mem;
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{Async, Poll};
|
||||
use http::header::{self, HeaderName, HeaderValue};
|
||||
use http::{HeaderMap, HttpTryFrom, StatusCode, Version};
|
||||
use http::{HeaderMap, StatusCode, Version};
|
||||
use httparse;
|
||||
use std::mem;
|
||||
|
||||
use error::{ParseError, PayloadError};
|
||||
|
||||
use server::h1decoder::EncodingDecoder;
|
||||
use server::{utils, IoStream};
|
||||
use server::h1decoder::{EncodingDecoder, HeaderIndex};
|
||||
use server::IoStream;
|
||||
|
||||
use super::response::ClientMessage;
|
||||
use super::ClientResponse;
|
||||
@@ -19,6 +20,7 @@ const MAX_HEADERS: usize = 96;
|
||||
#[derive(Default)]
|
||||
pub struct HttpResponseParser {
|
||||
decoder: Option<EncodingDecoder>,
|
||||
eof: bool, // indicate that we read payload until stream eof
|
||||
}
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
@@ -37,41 +39,42 @@ impl HttpResponseParser {
|
||||
where
|
||||
T: IoStream,
|
||||
{
|
||||
// if buf is empty parse_message will always return NotReady, let's avoid that
|
||||
if buf.is_empty() {
|
||||
match utils::read_from_io(io, buf) {
|
||||
Ok(Async::Ready(0)) => return Err(HttpResponseParserError::Disconnect),
|
||||
loop {
|
||||
// Don't call parser until we have data to parse.
|
||||
if !buf.is_empty() {
|
||||
match HttpResponseParser::parse_message(buf)
|
||||
.map_err(HttpResponseParserError::Error)?
|
||||
{
|
||||
Async::Ready((msg, info)) => {
|
||||
if let Some((decoder, eof)) = info {
|
||||
self.eof = eof;
|
||||
self.decoder = Some(decoder);
|
||||
} else {
|
||||
self.eof = false;
|
||||
self.decoder = None;
|
||||
}
|
||||
return Ok(Async::Ready(msg));
|
||||
}
|
||||
Async::NotReady => {
|
||||
if buf.len() >= MAX_BUFFER_SIZE {
|
||||
return Err(HttpResponseParserError::Error(
|
||||
ParseError::TooLarge,
|
||||
));
|
||||
}
|
||||
// Parser needs more data.
|
||||
}
|
||||
}
|
||||
}
|
||||
// Read some more data into the buffer for the parser.
|
||||
match io.read_available(buf) {
|
||||
Ok(Async::Ready((false, true))) => {
|
||||
return Err(HttpResponseParserError::Disconnect)
|
||||
}
|
||||
Ok(Async::Ready(_)) => (),
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(err) => return Err(HttpResponseParserError::Error(err.into())),
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
match HttpResponseParser::parse_message(buf)
|
||||
.map_err(HttpResponseParserError::Error)?
|
||||
{
|
||||
Async::Ready((msg, decoder)) => {
|
||||
self.decoder = decoder;
|
||||
return Ok(Async::Ready(msg));
|
||||
}
|
||||
Async::NotReady => {
|
||||
if buf.capacity() >= MAX_BUFFER_SIZE {
|
||||
return Err(HttpResponseParserError::Error(ParseError::TooLarge));
|
||||
}
|
||||
match utils::read_from_io(io, buf) {
|
||||
Ok(Async::Ready(0)) => {
|
||||
return Err(HttpResponseParserError::Disconnect)
|
||||
}
|
||||
Ok(Async::Ready(_)) => (),
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(err) => {
|
||||
return Err(HttpResponseParserError::Error(err.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_payload<T>(
|
||||
@@ -83,11 +86,11 @@ impl HttpResponseParser {
|
||||
if self.decoder.is_some() {
|
||||
loop {
|
||||
// read payload
|
||||
let (not_ready, stream_finished) = match utils::read_from_io(io, buf) {
|
||||
Ok(Async::Ready(0)) => (false, true),
|
||||
Err(err) => return Err(err.into()),
|
||||
let (not_ready, stream_finished) = match io.read_available(buf) {
|
||||
Ok(Async::Ready((_, true))) => (false, true),
|
||||
Ok(Async::Ready((_, false))) => (false, false),
|
||||
Ok(Async::NotReady) => (true, false),
|
||||
_ => (false, false),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
match self.decoder.as_mut().unwrap().decode(buf) {
|
||||
@@ -101,7 +104,12 @@ impl HttpResponseParser {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
if stream_finished {
|
||||
return Err(PayloadError::Incomplete);
|
||||
// read untile eof?
|
||||
if self.eof {
|
||||
return Ok(Async::Ready(None));
|
||||
} else {
|
||||
return Err(PayloadError::Incomplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
@@ -114,25 +122,24 @@ impl HttpResponseParser {
|
||||
|
||||
fn parse_message(
|
||||
buf: &mut BytesMut,
|
||||
) -> Poll<(ClientResponse, Option<EncodingDecoder>), ParseError> {
|
||||
// Parse http message
|
||||
let bytes_ptr = buf.as_ref().as_ptr() as usize;
|
||||
let mut headers: [httparse::Header; MAX_HEADERS] =
|
||||
unsafe { mem::uninitialized() };
|
||||
) -> Poll<(ClientResponse, Option<(EncodingDecoder, bool)>), ParseError> {
|
||||
// Unsafe: we read only this data only after httparse parses headers into.
|
||||
// performance bump for pipeline benchmarks.
|
||||
let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() };
|
||||
|
||||
let (len, version, status, headers_len) = {
|
||||
let b = unsafe {
|
||||
let b: &[u8] = buf;
|
||||
&*(b as *const _)
|
||||
};
|
||||
let mut resp = httparse::Response::new(&mut headers);
|
||||
match resp.parse(b)? {
|
||||
let mut parsed: [httparse::Header; MAX_HEADERS] =
|
||||
unsafe { mem::uninitialized() };
|
||||
|
||||
let mut resp = httparse::Response::new(&mut parsed);
|
||||
match resp.parse(buf)? {
|
||||
httparse::Status::Complete(len) => {
|
||||
let version = if resp.version.unwrap_or(1) == 1 {
|
||||
Version::HTTP_11
|
||||
} else {
|
||||
Version::HTTP_10
|
||||
};
|
||||
HeaderIndex::record(buf, resp.headers, &mut headers);
|
||||
let status = StatusCode::from_u16(resp.code.unwrap())
|
||||
.map_err(|_| ParseError::Status)?;
|
||||
|
||||
@@ -146,12 +153,13 @@ impl HttpResponseParser {
|
||||
|
||||
// convert headers
|
||||
let mut hdrs = HeaderMap::new();
|
||||
for header in headers[..headers_len].iter() {
|
||||
if let Ok(name) = HeaderName::try_from(header.name) {
|
||||
let v_start = header.value.as_ptr() as usize - bytes_ptr;
|
||||
let v_end = v_start + header.value.len();
|
||||
for idx in headers[..headers_len].iter() {
|
||||
if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) {
|
||||
// Unsafe: httparse check header value for valid utf-8
|
||||
let value = unsafe {
|
||||
HeaderValue::from_shared_unchecked(slice.slice(v_start, v_end))
|
||||
HeaderValue::from_shared_unchecked(
|
||||
slice.slice(idx.value.0, idx.value.1),
|
||||
)
|
||||
};
|
||||
hdrs.append(name, value);
|
||||
} else {
|
||||
@@ -160,12 +168,12 @@ impl HttpResponseParser {
|
||||
}
|
||||
|
||||
let decoder = if status == StatusCode::SWITCHING_PROTOCOLS {
|
||||
Some(EncodingDecoder::eof())
|
||||
Some((EncodingDecoder::eof(), true))
|
||||
} else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) {
|
||||
// Content-Length
|
||||
if let Ok(s) = len.to_str() {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
Some(EncodingDecoder::length(len))
|
||||
Some((EncodingDecoder::length(len), false))
|
||||
} else {
|
||||
debug!("illegal Content-Length: {:?}", len);
|
||||
return Err(ParseError::Header);
|
||||
@@ -176,7 +184,18 @@ impl HttpResponseParser {
|
||||
}
|
||||
} else if chunked(&hdrs)? {
|
||||
// Chunked encoding
|
||||
Some(EncodingDecoder::chunked())
|
||||
Some((EncodingDecoder::chunked(), false))
|
||||
} else if let Some(value) = hdrs.get(header::CONNECTION) {
|
||||
let close = if let Ok(s) = value.to_str() {
|
||||
s == "close"
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if close {
|
||||
Some((EncodingDecoder::eof(), true))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::unsync::oneshot;
|
||||
use futures::{Async, Future, Poll};
|
||||
use futures::sync::oneshot;
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use http::header::CONTENT_ENCODING;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{io, mem};
|
||||
use tokio_core::reactor::Timeout;
|
||||
use tokio_timer::Delay;
|
||||
|
||||
use actix::prelude::*;
|
||||
use actix_inner::dev::Request;
|
||||
use actix::{Addr, SystemService};
|
||||
|
||||
use super::HttpClientWriter;
|
||||
use super::{ClientConnector, ClientConnectorError, Connect, Connection};
|
||||
use super::{ClientRequest, ClientResponse};
|
||||
use super::{HttpResponseParser, HttpResponseParserError};
|
||||
use super::{
|
||||
ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect,
|
||||
Connection, HttpClientWriter, HttpResponseParser, HttpResponseParserError,
|
||||
};
|
||||
use body::{Body, BodyStream};
|
||||
use context::{ActorHttpContext, Frame};
|
||||
use error::Error;
|
||||
use error::PayloadError;
|
||||
use header::ContentEncoding;
|
||||
use http::{Method, Uri};
|
||||
use httpmessage::HttpMessage;
|
||||
use server::encoding::PayloadStream;
|
||||
use server::shared::SharedBytes;
|
||||
use server::input::PayloadStream;
|
||||
use server::WriterState;
|
||||
|
||||
/// A set of errors that can occur during request sending and response reading
|
||||
@@ -56,7 +57,7 @@ impl From<ClientConnectorError> for SendRequestError {
|
||||
|
||||
enum State {
|
||||
New,
|
||||
Connect(actix::dev::Request<Unsync, ClientConnector, Connect>),
|
||||
Connect(Request<ClientConnector, Connect>),
|
||||
Connection(Connection),
|
||||
Send(Box<Pipeline>),
|
||||
None,
|
||||
@@ -68,23 +69,30 @@ enum State {
|
||||
pub struct SendRequest {
|
||||
req: ClientRequest,
|
||||
state: State,
|
||||
conn: Addr<Unsync, ClientConnector>,
|
||||
conn: Option<Addr<ClientConnector>>,
|
||||
conn_timeout: Duration,
|
||||
wait_timeout: Duration,
|
||||
timeout: Option<Timeout>,
|
||||
timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
impl SendRequest {
|
||||
pub(crate) fn new(req: ClientRequest) -> SendRequest {
|
||||
SendRequest::with_connector(req, ClientConnector::from_registry())
|
||||
SendRequest {
|
||||
req,
|
||||
conn: None,
|
||||
state: State::New,
|
||||
timeout: None,
|
||||
wait_timeout: Duration::from_secs(5),
|
||||
conn_timeout: Duration::from_secs(1),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_connector(
|
||||
req: ClientRequest, conn: Addr<Unsync, ClientConnector>,
|
||||
req: ClientRequest, conn: Addr<ClientConnector>,
|
||||
) -> SendRequest {
|
||||
SendRequest {
|
||||
req,
|
||||
conn,
|
||||
conn: Some(conn),
|
||||
state: State::New,
|
||||
timeout: None,
|
||||
wait_timeout: Duration::from_secs(5),
|
||||
@@ -96,7 +104,7 @@ impl SendRequest {
|
||||
SendRequest {
|
||||
req,
|
||||
state: State::Connection(conn),
|
||||
conn: ClientConnector::from_registry(),
|
||||
conn: None,
|
||||
timeout: None,
|
||||
wait_timeout: Duration::from_secs(5),
|
||||
conn_timeout: Duration::from_secs(1),
|
||||
@@ -108,7 +116,7 @@ impl SendRequest {
|
||||
/// Request timeout is the total time before a response must be received.
|
||||
/// Default value is 5 seconds.
|
||||
pub fn timeout(mut self, timeout: Duration) -> Self {
|
||||
self.timeout = Some(Timeout::new(timeout, Arbiter::handle()).unwrap());
|
||||
self.timeout = Some(timeout);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -142,7 +150,12 @@ impl Future for SendRequest {
|
||||
|
||||
match state {
|
||||
State::New => {
|
||||
self.state = State::Connect(self.conn.send(Connect {
|
||||
let conn = if let Some(conn) = self.conn.take() {
|
||||
conn
|
||||
} else {
|
||||
ClientConnector::from_registry()
|
||||
};
|
||||
self.state = State::Connect(conn.send(Connect {
|
||||
uri: self.req.uri().clone(),
|
||||
wait_timeout: self.wait_timeout,
|
||||
conn_timeout: self.conn_timeout,
|
||||
@@ -160,11 +173,11 @@ impl Future for SendRequest {
|
||||
Err(_) => {
|
||||
return Err(SendRequestError::Connector(
|
||||
ClientConnectorError::Disconnected,
|
||||
))
|
||||
));
|
||||
}
|
||||
},
|
||||
State::Connection(conn) => {
|
||||
let mut writer = HttpClientWriter::new(SharedBytes::default());
|
||||
let mut writer = HttpClientWriter::new();
|
||||
writer.start(&mut self.req)?;
|
||||
|
||||
let body = match self.req.replace_body(Body::Empty) {
|
||||
@@ -173,9 +186,10 @@ impl Future for SendRequest {
|
||||
_ => IoBody::Done,
|
||||
};
|
||||
|
||||
let timeout = self.timeout.take().unwrap_or_else(|| {
|
||||
Timeout::new(Duration::from_secs(5), Arbiter::handle()).unwrap()
|
||||
});
|
||||
let timeout = self
|
||||
.timeout
|
||||
.take()
|
||||
.unwrap_or_else(|| Duration::from_secs(5));
|
||||
|
||||
let pl = Box::new(Pipeline {
|
||||
body,
|
||||
@@ -189,7 +203,9 @@ impl Future for SendRequest {
|
||||
decompress: None,
|
||||
should_decompress: self.req.response_decompress(),
|
||||
write_state: RunningState::Running,
|
||||
timeout: Some(timeout),
|
||||
timeout: Some(Delay::new(Instant::now() + timeout)),
|
||||
meth: self.req.method().clone(),
|
||||
path: self.req.uri().clone(),
|
||||
});
|
||||
self.state = State::Send(pl);
|
||||
}
|
||||
@@ -201,6 +217,9 @@ impl Future for SendRequest {
|
||||
|
||||
match pl.parse() {
|
||||
Ok(Async::Ready(mut resp)) => {
|
||||
if self.req.method() == Method::HEAD {
|
||||
pl.parser.take();
|
||||
}
|
||||
resp.set_pipeline(pl);
|
||||
return Ok(Async::Ready(resp));
|
||||
}
|
||||
@@ -208,7 +227,9 @@ impl Future for SendRequest {
|
||||
self.state = State::Send(pl);
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
Err(err) => return Err(SendRequestError::ParseError(err)),
|
||||
Err(err) => {
|
||||
return Err(SendRequestError::ParseError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
State::None => unreachable!(),
|
||||
@@ -217,7 +238,7 @@ impl Future for SendRequest {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Pipeline {
|
||||
pub struct Pipeline {
|
||||
body: IoBody,
|
||||
body_completed: bool,
|
||||
conn: Option<Connection>,
|
||||
@@ -229,7 +250,9 @@ pub(crate) struct Pipeline {
|
||||
decompress: Option<PayloadStream>,
|
||||
should_decompress: bool,
|
||||
write_state: RunningState,
|
||||
timeout: Option<Timeout>,
|
||||
timeout: Option<Delay>,
|
||||
meth: Method,
|
||||
path: Uri,
|
||||
}
|
||||
|
||||
enum IoBody {
|
||||
@@ -263,7 +286,11 @@ impl RunningState {
|
||||
impl Pipeline {
|
||||
fn release_conn(&mut self) {
|
||||
if let Some(conn) = self.conn.take() {
|
||||
conn.release()
|
||||
if self.meth == Method::HEAD {
|
||||
conn.close()
|
||||
} else {
|
||||
conn.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,13 +329,10 @@ impl Pipeline {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
pub(crate) fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
if self.conn.is_none() {
|
||||
return Ok(Async::Ready(None));
|
||||
}
|
||||
let conn: &mut Connection =
|
||||
unsafe { &mut *(self.conn.as_mut().unwrap() as *mut _) };
|
||||
|
||||
let mut need_run = false;
|
||||
|
||||
// need write?
|
||||
@@ -326,6 +350,8 @@ impl Pipeline {
|
||||
|
||||
// need read?
|
||||
if self.parser.is_some() {
|
||||
let conn: &mut Connection = self.conn.as_mut().unwrap();
|
||||
|
||||
loop {
|
||||
match self
|
||||
.parser
|
||||
@@ -380,7 +406,7 @@ impl Pipeline {
|
||||
match self.timeout.as_mut().unwrap().poll() {
|
||||
Ok(Async::Ready(())) => return Err(SendRequestError::Timeout),
|
||||
Ok(Async::NotReady) => (),
|
||||
Err(_) => unreachable!(),
|
||||
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e).into()),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -404,7 +430,7 @@ impl Pipeline {
|
||||
}
|
||||
Async::Ready(Some(chunk)) => {
|
||||
self.body = IoBody::Payload(body);
|
||||
self.writer.write(chunk.into())?
|
||||
self.writer.write(chunk.as_ref())?
|
||||
}
|
||||
Async::NotReady => {
|
||||
done = true;
|
||||
@@ -431,7 +457,8 @@ impl Pipeline {
|
||||
break 'outter;
|
||||
}
|
||||
Frame::Chunk(Some(chunk)) => {
|
||||
res = Some(self.writer.write(chunk)?)
|
||||
res =
|
||||
Some(self.writer.write(chunk.as_ref())?)
|
||||
}
|
||||
Frame::Drain(fut) => self.drain = Some(fut),
|
||||
}
|
||||
@@ -505,7 +532,22 @@ impl Pipeline {
|
||||
impl Drop for Pipeline {
|
||||
fn drop(&mut self) {
|
||||
if let Some(conn) = self.conn.take() {
|
||||
debug!(
|
||||
"Client http transaction is not completed, dropping connection: {:?} {:?}",
|
||||
self.meth,
|
||||
self.path,
|
||||
);
|
||||
conn.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Future that resolves to a complete request body.
|
||||
impl Stream for Box<Pipeline> {
|
||||
type Item = Bytes;
|
||||
type Error = PayloadError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
Pipeline::poll(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ use std::io::Write;
|
||||
use std::time::Duration;
|
||||
use std::{fmt, mem};
|
||||
|
||||
use actix::{Addr, Unsync};
|
||||
use actix::Addr;
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use cookie::{Cookie, CookieJar};
|
||||
use futures::Stream;
|
||||
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
use serde_urlencoded;
|
||||
use url::Url;
|
||||
|
||||
use super::connector::{ClientConnector, Connection};
|
||||
@@ -25,29 +26,27 @@ use httprequest::HttpRequest;
|
||||
/// An HTTP Client Request
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate actix;
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio;
|
||||
/// # use futures::Future;
|
||||
/// use actix_web::client::ClientRequest;
|
||||
/// # use std::process;
|
||||
/// use actix_web::client;
|
||||
///
|
||||
/// fn main() {
|
||||
/// let sys = actix::System::new("test");
|
||||
///
|
||||
/// actix::Arbiter::handle().spawn({
|
||||
/// ClientRequest::get("http://www.rust-lang.org") // <- Create request builder
|
||||
/// actix::run(
|
||||
/// || client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder
|
||||
/// .header("User-Agent", "Actix-web")
|
||||
/// .finish().unwrap()
|
||||
/// .send() // <- Send http request
|
||||
/// .map_err(|_| ())
|
||||
/// .and_then(|response| { // <- server http response
|
||||
/// .and_then(|response| { // <- server http response
|
||||
/// println!("Response: {:?}", response);
|
||||
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
/// # actix::System::current().stop();
|
||||
/// Ok(())
|
||||
/// })
|
||||
/// });
|
||||
///
|
||||
/// sys.run();
|
||||
/// }),
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub struct ClientRequest {
|
||||
@@ -67,7 +66,7 @@ pub struct ClientRequest {
|
||||
|
||||
enum ConnectionType {
|
||||
Default,
|
||||
Connector(Addr<Unsync, ClientConnector>),
|
||||
Connector(Addr<ClientConnector>),
|
||||
Connection(Connection),
|
||||
}
|
||||
|
||||
@@ -256,16 +255,16 @@ impl ClientRequest {
|
||||
|
||||
impl fmt::Debug for ClientRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let res = writeln!(
|
||||
writeln!(
|
||||
f,
|
||||
"\nClientRequest {:?} {}:{}",
|
||||
self.version, self.method, self.uri
|
||||
);
|
||||
let _ = writeln!(f, " headers:");
|
||||
)?;
|
||||
writeln!(f, " headers:")?;
|
||||
for (key, val) in self.headers.iter() {
|
||||
let _ = writeln!(f, " {:?}: {:?}", key, val);
|
||||
writeln!(f, " {:?}: {:?}", key, val)?;
|
||||
}
|
||||
res
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,10 +292,6 @@ impl ClientRequestBuilder {
|
||||
fn _uri(&mut self, url: &str) -> &mut Self {
|
||||
match Uri::try_from(url) {
|
||||
Ok(uri) => {
|
||||
// set request host header
|
||||
if let Some(host) = uri.host() {
|
||||
self.set_header(header::HOST, host);
|
||||
}
|
||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
||||
parts.uri = uri;
|
||||
}
|
||||
@@ -318,8 +313,7 @@ impl ClientRequestBuilder {
|
||||
/// Set HTTP method of this request.
|
||||
#[inline]
|
||||
pub fn get_method(&mut self) -> &Method {
|
||||
let parts =
|
||||
parts(&mut self.request, &self.err).expect("cannot reuse request builder");
|
||||
let parts = self.request.as_ref().expect("cannot reuse request builder");
|
||||
&parts.method
|
||||
}
|
||||
|
||||
@@ -347,7 +341,8 @@ impl ClientRequestBuilder {
|
||||
/// let req = client::ClientRequest::build()
|
||||
/// .set(http::header::Date::now())
|
||||
/// .set(http::header::ContentType(mime::TEXT_HTML))
|
||||
/// .finish().unwrap();
|
||||
/// .finish()
|
||||
/// .unwrap();
|
||||
/// }
|
||||
/// ```
|
||||
#[doc(hidden)]
|
||||
@@ -379,7 +374,8 @@ impl ClientRequestBuilder {
|
||||
/// let req = ClientRequest::build()
|
||||
/// .header("X-TEST", "value")
|
||||
/// .header(header::CONTENT_TYPE, "application/json")
|
||||
/// .finish().unwrap();
|
||||
/// .finish()
|
||||
/// .unwrap();
|
||||
/// }
|
||||
/// ```
|
||||
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
||||
@@ -421,6 +417,28 @@ impl ClientRequestBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a header only if it is not yet set.
|
||||
pub fn set_header_if_none<K, V>(&mut self, key: K, value: V) -> &mut Self
|
||||
where
|
||||
HeaderName: HttpTryFrom<K>,
|
||||
V: IntoHeaderValue,
|
||||
{
|
||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
||||
match HeaderName::try_from(key) {
|
||||
Ok(key) => if !parts.headers.contains_key(&key) {
|
||||
match value.try_into() {
|
||||
Ok(value) => {
|
||||
parts.headers.insert(key, value);
|
||||
}
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
}
|
||||
},
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
};
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set content encoding.
|
||||
///
|
||||
/// By default `ContentEncoding::Identity` is used.
|
||||
@@ -489,8 +507,10 @@ impl ClientRequestBuilder {
|
||||
/// .path("/")
|
||||
/// .secure(true)
|
||||
/// .http_only(true)
|
||||
/// .finish())
|
||||
/// .finish().unwrap();
|
||||
/// .finish(),
|
||||
/// )
|
||||
/// .finish()
|
||||
/// .unwrap();
|
||||
/// }
|
||||
/// ```
|
||||
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
|
||||
@@ -505,7 +525,7 @@ impl ClientRequestBuilder {
|
||||
}
|
||||
|
||||
/// Do not add default request headers.
|
||||
/// By default `Accept-Encoding` header is set.
|
||||
/// By default `Accept-Encoding` and `User-Agent` headers are set.
|
||||
pub fn no_default_headers(&mut self) -> &mut Self {
|
||||
self.default_headers = false;
|
||||
self
|
||||
@@ -541,7 +561,7 @@ impl ClientRequestBuilder {
|
||||
}
|
||||
|
||||
/// Send request using custom connector
|
||||
pub fn with_connector(&mut self, conn: Addr<Unsync, ClientConnector>) -> &mut Self {
|
||||
pub fn with_connector(&mut self, conn: Addr<ClientConnector>) -> &mut Self {
|
||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
||||
parts.conn = ConnectionType::Connector(conn);
|
||||
}
|
||||
@@ -601,10 +621,37 @@ impl ClientRequestBuilder {
|
||||
};
|
||||
|
||||
if https {
|
||||
self.header(header::ACCEPT_ENCODING, "br, gzip, deflate");
|
||||
self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate");
|
||||
} else {
|
||||
self.header(header::ACCEPT_ENCODING, "gzip, deflate");
|
||||
self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate");
|
||||
}
|
||||
|
||||
// set request host header
|
||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
||||
if let Some(host) = parts.uri.host() {
|
||||
if !parts.headers.contains_key(header::HOST) {
|
||||
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
|
||||
|
||||
let _ = match parts.uri.port_part().map(|port| port.as_u16()) {
|
||||
None | Some(80) | Some(443) => write!(wrt, "{}", host),
|
||||
Some(port) => write!(wrt, "{}:{}", host, port),
|
||||
};
|
||||
|
||||
match wrt.get_mut().take().freeze().try_into() {
|
||||
Ok(value) => {
|
||||
parts.headers.insert(header::HOST, value);
|
||||
}
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// user agent
|
||||
self.set_header_if_none(
|
||||
header::USER_AGENT,
|
||||
concat!("actix-web/", env!("CARGO_PKG_VERSION")),
|
||||
);
|
||||
}
|
||||
|
||||
let mut request = self.request.take().expect("cannot reuse request builder");
|
||||
@@ -644,6 +691,24 @@ impl ClientRequestBuilder {
|
||||
self.body(body)
|
||||
}
|
||||
|
||||
/// Set a urlencoded body and generate `ClientRequest`
|
||||
///
|
||||
/// `ClientRequestBuilder` can not be used after this call.
|
||||
pub fn form<T: Serialize>(&mut self, value: T) -> Result<ClientRequest, Error> {
|
||||
let body = serde_urlencoded::to_string(&value)?;
|
||||
|
||||
let contains = if let Some(parts) = parts(&mut self.request, &self.err) {
|
||||
parts.headers.contains_key(header::CONTENT_TYPE)
|
||||
} else {
|
||||
true
|
||||
};
|
||||
if !contains {
|
||||
self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded");
|
||||
}
|
||||
|
||||
self.body(body)
|
||||
}
|
||||
|
||||
/// Set a streaming body and generate `ClientRequest`.
|
||||
///
|
||||
/// `ClientRequestBuilder` can not be used after this call.
|
||||
@@ -686,16 +751,16 @@ fn parts<'a>(
|
||||
impl fmt::Debug for ClientRequestBuilder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let Some(ref parts) = self.request {
|
||||
let res = writeln!(
|
||||
writeln!(
|
||||
f,
|
||||
"\nClientRequestBuilder {:?} {}:{}",
|
||||
parts.version, parts.method, parts.uri
|
||||
);
|
||||
let _ = writeln!(f, " headers:");
|
||||
)?;
|
||||
writeln!(f, " headers:")?;
|
||||
for (key, val) in parts.headers.iter() {
|
||||
let _ = writeln!(f, " {:?}: {:?}", key, val);
|
||||
writeln!(f, " {:?}: {:?}", key, val)?;
|
||||
}
|
||||
res
|
||||
Ok(())
|
||||
} else {
|
||||
write!(f, "ClientRequestBuilder(Consumed)")
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
use std::cell::UnsafeCell;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::{fmt, str};
|
||||
|
||||
use bytes::Bytes;
|
||||
use cookie::Cookie;
|
||||
use futures::{Async, Poll, Stream};
|
||||
use http::header::{self, HeaderValue};
|
||||
use http::{HeaderMap, StatusCode, Version};
|
||||
|
||||
use error::{CookieParseError, PayloadError};
|
||||
use error::CookieParseError;
|
||||
use httpmessage::HttpMessage;
|
||||
|
||||
use super::pipeline::Pipeline;
|
||||
@@ -32,64 +29,59 @@ impl Default for ClientMessage {
|
||||
}
|
||||
|
||||
/// An HTTP Client response
|
||||
pub struct ClientResponse(Rc<UnsafeCell<ClientMessage>>, Option<Box<Pipeline>>);
|
||||
pub struct ClientResponse(ClientMessage, RefCell<Option<Box<Pipeline>>>);
|
||||
|
||||
impl HttpMessage for ClientResponse {
|
||||
type Stream = Box<Pipeline>;
|
||||
|
||||
/// Get the headers from the response.
|
||||
#[inline]
|
||||
fn headers(&self) -> &HeaderMap {
|
||||
&self.as_ref().headers
|
||||
&self.0.headers
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn payload(&self) -> Box<Pipeline> {
|
||||
self.1
|
||||
.borrow_mut()
|
||||
.take()
|
||||
.expect("Payload is already consumed.")
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientResponse {
|
||||
pub(crate) fn new(msg: ClientMessage) -> ClientResponse {
|
||||
ClientResponse(Rc::new(UnsafeCell::new(msg)), None)
|
||||
ClientResponse(msg, RefCell::new(None))
|
||||
}
|
||||
|
||||
pub(crate) fn set_pipeline(&mut self, pl: Box<Pipeline>) {
|
||||
self.1 = Some(pl);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &ClientMessage {
|
||||
unsafe { &*self.0.get() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
|
||||
fn as_mut(&self) -> &mut ClientMessage {
|
||||
unsafe { &mut *self.0.get() }
|
||||
*self.1.borrow_mut() = Some(pl);
|
||||
}
|
||||
|
||||
/// Get the HTTP version of this response.
|
||||
#[inline]
|
||||
pub fn version(&self) -> Version {
|
||||
self.as_ref().version
|
||||
self.0.version
|
||||
}
|
||||
|
||||
/// Get the status from the server.
|
||||
#[inline]
|
||||
pub fn status(&self) -> StatusCode {
|
||||
self.as_ref().status
|
||||
self.0.status
|
||||
}
|
||||
|
||||
/// Load response cookies.
|
||||
pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> {
|
||||
if self.as_ref().cookies.is_none() {
|
||||
let msg = self.as_mut();
|
||||
let mut cookies = Vec::new();
|
||||
for val in msg.headers.get_all(header::SET_COOKIE).iter() {
|
||||
let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?;
|
||||
cookies.push(Cookie::parse_encoded(s)?.into_owned());
|
||||
}
|
||||
msg.cookies = Some(cookies)
|
||||
pub fn cookies(&self) -> Result<Vec<Cookie<'static>>, CookieParseError> {
|
||||
let mut cookies = Vec::new();
|
||||
for val in self.0.headers.get_all(header::SET_COOKIE).iter() {
|
||||
let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?;
|
||||
cookies.push(Cookie::parse_encoded(s)?.into_owned());
|
||||
}
|
||||
Ok(self.as_ref().cookies.as_ref().unwrap())
|
||||
Ok(cookies)
|
||||
}
|
||||
|
||||
/// Return request cookie.
|
||||
pub fn cookie(&self, name: &str) -> Option<&Cookie> {
|
||||
pub fn cookie(&self, name: &str) -> Option<Cookie> {
|
||||
if let Ok(cookies) = self.cookies() {
|
||||
for cookie in cookies {
|
||||
if cookie.name() == name {
|
||||
@@ -103,26 +95,12 @@ impl ClientResponse {
|
||||
|
||||
impl fmt::Debug for ClientResponse {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let res = writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status());
|
||||
let _ = writeln!(f, " headers:");
|
||||
writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status())?;
|
||||
writeln!(f, " headers:")?;
|
||||
for (key, val) in self.headers().iter() {
|
||||
let _ = writeln!(f, " {:?}: {:?}", key, val);
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// Future that resolves to a complete request body.
|
||||
impl Stream for ClientResponse {
|
||||
type Item = Bytes;
|
||||
type Error = PayloadError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if let Some(ref mut pl) = self.1 {
|
||||
pl.poll()
|
||||
} else {
|
||||
Ok(Async::Ready(None))
|
||||
writeln!(f, " {:?}: {:?}", key, val)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,11 +110,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let resp = ClientResponse::new(ClientMessage::default());
|
||||
resp.as_mut()
|
||||
let mut resp = ClientResponse::new(ClientMessage::default());
|
||||
resp.0
|
||||
.headers
|
||||
.insert(header::COOKIE, HeaderValue::from_static("cookie1=value1"));
|
||||
resp.as_mut()
|
||||
resp.0
|
||||
.headers
|
||||
.insert(header::COOKIE, HeaderValue::from_static("cookie2=value2"));
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
|
||||
#![cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(redundant_field_names)
|
||||
)]
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Write as FmtWrite;
|
||||
@@ -8,7 +11,7 @@ use std::io::{self, Write};
|
||||
use brotli2::write::BrotliEncoder;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::write::{DeflateEncoder, GzEncoder};
|
||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::Compression;
|
||||
use futures::{Async, Poll};
|
||||
@@ -21,8 +24,7 @@ use tokio_io::AsyncWrite;
|
||||
|
||||
use body::{Binary, Body};
|
||||
use header::ContentEncoding;
|
||||
use server::encoding::{ContentEncoder, TransferEncoding};
|
||||
use server::shared::SharedBytes;
|
||||
use server::output::{ContentEncoder, Output, TransferEncoding};
|
||||
use server::WriterState;
|
||||
|
||||
use client::ClientRequest;
|
||||
@@ -42,21 +44,18 @@ pub(crate) struct HttpClientWriter {
|
||||
flags: Flags,
|
||||
written: u64,
|
||||
headers_size: u32,
|
||||
buffer: SharedBytes,
|
||||
buffer: Output,
|
||||
buffer_capacity: usize,
|
||||
encoder: ContentEncoder,
|
||||
}
|
||||
|
||||
impl HttpClientWriter {
|
||||
pub fn new(buffer: SharedBytes) -> HttpClientWriter {
|
||||
let encoder = ContentEncoder::Identity(TransferEncoding::eof(buffer.clone()));
|
||||
pub fn new() -> HttpClientWriter {
|
||||
HttpClientWriter {
|
||||
flags: Flags::empty(),
|
||||
written: 0,
|
||||
headers_size: 0,
|
||||
buffer_capacity: 0,
|
||||
buffer,
|
||||
encoder,
|
||||
buffer: Output::Buffer(BytesMut::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +75,7 @@ impl HttpClientWriter {
|
||||
&mut self, stream: &mut T,
|
||||
) -> io::Result<WriterState> {
|
||||
while !self.buffer.is_empty() {
|
||||
match stream.write(self.buffer.as_ref()) {
|
||||
match stream.write(self.buffer.as_ref().as_ref()) {
|
||||
Ok(0) => {
|
||||
self.disconnected();
|
||||
return Ok(WriterState::Done);
|
||||
@@ -98,21 +97,35 @@ impl HttpClientWriter {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Writer<'a>(pub &'a mut BytesMut);
|
||||
|
||||
impl<'a> io::Write for Writer<'a> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.0.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpClientWriter {
|
||||
pub fn start(&mut self, msg: &mut ClientRequest) -> io::Result<()> {
|
||||
// prepare task
|
||||
self.buffer = content_encoder(self.buffer.take(), msg);
|
||||
self.flags.insert(Flags::STARTED);
|
||||
self.encoder = content_encoder(self.buffer.clone(), msg);
|
||||
|
||||
if msg.upgrade() {
|
||||
self.flags.insert(Flags::UPGRADE);
|
||||
}
|
||||
|
||||
// render message
|
||||
{
|
||||
// output buffer
|
||||
let buffer = self.buffer.as_mut();
|
||||
|
||||
// status line
|
||||
writeln!(
|
||||
self.buffer,
|
||||
Writer(buffer),
|
||||
"{} {} {:?}\r",
|
||||
msg.method(),
|
||||
msg.uri()
|
||||
@@ -120,10 +133,9 @@ impl HttpClientWriter {
|
||||
.map(|u| u.as_str())
|
||||
.unwrap_or("/"),
|
||||
msg.version()
|
||||
)?;
|
||||
).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
|
||||
// write headers
|
||||
let mut buffer = self.buffer.get_mut();
|
||||
if let Body::Binary(ref bytes) = *msg.body() {
|
||||
buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
|
||||
} else {
|
||||
@@ -143,33 +155,29 @@ impl HttpClientWriter {
|
||||
// set date header
|
||||
if !msg.headers().contains_key(DATE) {
|
||||
buffer.extend_from_slice(b"date: ");
|
||||
set_date(&mut buffer);
|
||||
set_date(buffer);
|
||||
buffer.extend_from_slice(b"\r\n\r\n");
|
||||
} else {
|
||||
buffer.extend_from_slice(b"\r\n");
|
||||
}
|
||||
self.headers_size = buffer.len() as u32;
|
||||
}
|
||||
self.headers_size = self.buffer.len() as u32;
|
||||
|
||||
if msg.body().is_binary() {
|
||||
if let Body::Binary(bytes) = msg.replace_body(Body::Empty) {
|
||||
self.written += bytes.len() as u64;
|
||||
self.encoder.write(bytes)?;
|
||||
}
|
||||
} else {
|
||||
self.buffer_capacity = msg.write_buffer_capacity();
|
||||
if msg.body().is_binary() {
|
||||
if let Body::Binary(bytes) = msg.replace_body(Body::Empty) {
|
||||
self.written += bytes.len() as u64;
|
||||
self.buffer.write(bytes.as_ref())?;
|
||||
}
|
||||
} else {
|
||||
self.buffer_capacity = msg.write_buffer_capacity();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write(&mut self, payload: Binary) -> io::Result<WriterState> {
|
||||
pub fn write(&mut self, payload: &[u8]) -> io::Result<WriterState> {
|
||||
self.written += payload.len() as u64;
|
||||
if !self.flags.contains(Flags::DISCONNECTED) {
|
||||
if self.flags.contains(Flags::UPGRADE) {
|
||||
self.buffer.extend(payload);
|
||||
} else {
|
||||
self.encoder.write(payload)?;
|
||||
}
|
||||
self.buffer.write(payload)?;
|
||||
}
|
||||
|
||||
if self.buffer.len() > self.buffer_capacity {
|
||||
@@ -180,9 +188,7 @@ impl HttpClientWriter {
|
||||
}
|
||||
|
||||
pub fn write_eof(&mut self) -> io::Result<()> {
|
||||
self.encoder.write_eof()?;
|
||||
|
||||
if self.encoder.is_eof() {
|
||||
if self.buffer.write_eof()? {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
@@ -210,7 +216,7 @@ impl HttpClientWriter {
|
||||
}
|
||||
}
|
||||
|
||||
fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder {
|
||||
fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output {
|
||||
let version = req.version();
|
||||
let mut body = req.replace_body(Body::Empty);
|
||||
let mut encoding = req.content_encoding();
|
||||
@@ -218,45 +224,57 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
|
||||
let transfer = match body {
|
||||
Body::Empty => {
|
||||
req.headers_mut().remove(CONTENT_LENGTH);
|
||||
TransferEncoding::length(0, buf)
|
||||
return Output::Empty(buf);
|
||||
}
|
||||
Body::Binary(ref mut bytes) => {
|
||||
if encoding.is_compression() {
|
||||
let tmp = SharedBytes::default();
|
||||
let transfer = TransferEncoding::eof(tmp.clone());
|
||||
let mut enc = match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
||||
DeflateEncoder::new(transfer, Compression::default()),
|
||||
),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
|
||||
transfer,
|
||||
Compression::default(),
|
||||
)),
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => {
|
||||
ContentEncoder::Br(BrotliEncoder::new(transfer, 5))
|
||||
}
|
||||
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
|
||||
ContentEncoding::Auto => unreachable!(),
|
||||
};
|
||||
// TODO return error!
|
||||
let _ = enc.write(bytes.clone());
|
||||
let _ = enc.write_eof();
|
||||
*bytes = Binary::from(tmp.take());
|
||||
#[cfg(any(feature = "flate2", feature = "brotli"))]
|
||||
{
|
||||
if encoding.is_compression() {
|
||||
let mut tmp = BytesMut::new();
|
||||
let mut transfer = TransferEncoding::eof(tmp);
|
||||
let mut enc = match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
||||
ZlibEncoder::new(transfer, Compression::default()),
|
||||
),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
|
||||
transfer,
|
||||
Compression::default(),
|
||||
)),
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => {
|
||||
ContentEncoder::Br(BrotliEncoder::new(transfer, 5))
|
||||
}
|
||||
ContentEncoding::Auto | ContentEncoding::Identity => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
// TODO return error!
|
||||
let _ = enc.write(bytes.as_ref());
|
||||
let _ = enc.write_eof();
|
||||
*bytes = Binary::from(enc.buf_mut().take());
|
||||
|
||||
req.headers_mut().insert(
|
||||
CONTENT_ENCODING,
|
||||
HeaderValue::from_static(encoding.as_str()),
|
||||
);
|
||||
encoding = ContentEncoding::Identity;
|
||||
req.headers_mut().insert(
|
||||
CONTENT_ENCODING,
|
||||
HeaderValue::from_static(encoding.as_str()),
|
||||
);
|
||||
encoding = ContentEncoding::Identity;
|
||||
}
|
||||
let mut b = BytesMut::new();
|
||||
let _ = write!(b, "{}", bytes.len());
|
||||
req.headers_mut()
|
||||
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
|
||||
TransferEncoding::eof(buf)
|
||||
}
|
||||
#[cfg(not(any(feature = "flate2", feature = "brotli")))]
|
||||
{
|
||||
let mut b = BytesMut::new();
|
||||
let _ = write!(b, "{}", bytes.len());
|
||||
req.headers_mut()
|
||||
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
|
||||
TransferEncoding::eof(buf)
|
||||
}
|
||||
let mut b = BytesMut::new();
|
||||
let _ = write!(b, "{}", bytes.len());
|
||||
req.headers_mut()
|
||||
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
|
||||
TransferEncoding::eof(buf)
|
||||
}
|
||||
Body::Streaming(_) | Body::Actor(_) => {
|
||||
if req.upgrade() {
|
||||
@@ -285,26 +303,24 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
|
||||
}
|
||||
|
||||
req.replace_body(body);
|
||||
match encoding {
|
||||
let enc = match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
|
||||
transfer,
|
||||
Compression::default(),
|
||||
)),
|
||||
ContentEncoding::Deflate => {
|
||||
ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::default()))
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => {
|
||||
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default()))
|
||||
}
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 5)),
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => {
|
||||
ContentEncoder::Identity(transfer)
|
||||
}
|
||||
}
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => return Output::TE(transfer),
|
||||
};
|
||||
Output::Encoder(enc)
|
||||
}
|
||||
|
||||
fn streaming_encoding(
|
||||
buf: SharedBytes, version: Version, req: &mut ClientRequest,
|
||||
buf: BytesMut, version: Version, req: &mut ClientRequest,
|
||||
) -> TransferEncoding {
|
||||
if req.chunked() {
|
||||
// Enable transfer encoding
|
||||
|
||||
122
src/context.rs
122
src/context.rs
@@ -1,14 +1,17 @@
|
||||
extern crate actix;
|
||||
|
||||
use futures::sync::oneshot;
|
||||
use futures::sync::oneshot::Sender;
|
||||
use futures::unsync::oneshot;
|
||||
use futures::{Async, Future, Poll};
|
||||
use smallvec::SmallVec;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
|
||||
use actix::fut::ActorFuture;
|
||||
use actix::{
|
||||
use self::actix::dev::{
|
||||
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope,
|
||||
};
|
||||
use self::actix::fut::ActorFuture;
|
||||
use self::actix::{
|
||||
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
|
||||
Syn, Unsync,
|
||||
};
|
||||
|
||||
use body::{Binary, Body};
|
||||
@@ -40,7 +43,7 @@ pub struct HttpContext<A, S = ()>
|
||||
where
|
||||
A: Actor<Context = HttpContext<A, S>>,
|
||||
{
|
||||
inner: ContextImpl<A>,
|
||||
inner: ContextParts<A>,
|
||||
stream: Option<SmallVec<[Frame; 4]>>,
|
||||
request: HttpRequest<S>,
|
||||
disconnected: bool,
|
||||
@@ -90,15 +93,9 @@ where
|
||||
fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
|
||||
self.inner.cancel_future(handle)
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
fn unsync_address(&mut self) -> Addr<Unsync, A> {
|
||||
self.inner.unsync_address()
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
fn sync_address(&mut self) -> Addr<Syn, A> {
|
||||
self.inner.sync_address()
|
||||
fn address(&self) -> Addr<A> {
|
||||
self.inner.address()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,21 +104,33 @@ where
|
||||
A: Actor<Context = Self>,
|
||||
{
|
||||
#[inline]
|
||||
pub fn new(req: HttpRequest<S>, actor: A) -> HttpContext<A, S> {
|
||||
HttpContext::from_request(req).actor(actor)
|
||||
}
|
||||
pub fn from_request(req: HttpRequest<S>) -> HttpContext<A, S> {
|
||||
HttpContext {
|
||||
inner: ContextImpl::new(None),
|
||||
/// Create a new HTTP Context from a request and an actor
|
||||
pub fn create(req: HttpRequest<S>, actor: A) -> Body {
|
||||
let mb = Mailbox::default();
|
||||
let ctx = HttpContext {
|
||||
inner: ContextParts::new(mb.sender_producer()),
|
||||
stream: None,
|
||||
request: req,
|
||||
disconnected: false,
|
||||
}
|
||||
};
|
||||
Body::Actor(Box::new(HttpContextFut::new(ctx, actor, mb)))
|
||||
}
|
||||
#[inline]
|
||||
pub fn actor(mut self, actor: A) -> HttpContext<A, S> {
|
||||
self.inner.set_actor(actor);
|
||||
self
|
||||
|
||||
/// Create a new HTTP Context
|
||||
pub fn with_factory<F>(req: HttpRequest<S>, f: F) -> Body
|
||||
where
|
||||
F: FnOnce(&mut Self) -> A + 'static,
|
||||
{
|
||||
let mb = Mailbox::default();
|
||||
let mut ctx = HttpContext {
|
||||
inner: ContextParts::new(mb.sender_producer()),
|
||||
stream: None,
|
||||
request: req,
|
||||
disconnected: false,
|
||||
};
|
||||
|
||||
let act = f(&mut ctx);
|
||||
Body::Actor(Box::new(HttpContextFut::new(ctx, act, mb)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +169,6 @@ where
|
||||
/// Returns drain future
|
||||
pub fn drain(&mut self) -> Drain<A> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.inner.modify();
|
||||
self.add_frame(Frame::Drain(tx));
|
||||
Drain::new(rx)
|
||||
}
|
||||
@@ -179,7 +187,6 @@ where
|
||||
if let Some(s) = self.stream.as_mut() {
|
||||
s.push(frame)
|
||||
}
|
||||
self.inner.modify();
|
||||
}
|
||||
|
||||
/// Handle of the running future
|
||||
@@ -190,32 +197,55 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> ActorHttpContext for HttpContext<A, S>
|
||||
impl<A, S> AsyncContextParts<A> for HttpContext<A, S>
|
||||
where
|
||||
A: Actor<Context = Self>,
|
||||
{
|
||||
fn parts(&mut self) -> &mut ContextParts<A> {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
struct HttpContextFut<A, S>
|
||||
where
|
||||
A: Actor<Context = HttpContext<A, S>>,
|
||||
{
|
||||
fut: ContextFut<A, HttpContext<A, S>>,
|
||||
}
|
||||
|
||||
impl<A, S> HttpContextFut<A, S>
|
||||
where
|
||||
A: Actor<Context = HttpContext<A, S>>,
|
||||
{
|
||||
fn new(ctx: HttpContext<A, S>, act: A, mailbox: Mailbox<A>) -> Self {
|
||||
let fut = ContextFut::new(ctx, act, mailbox);
|
||||
HttpContextFut { fut }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> ActorHttpContext for HttpContextFut<A, S>
|
||||
where
|
||||
A: Actor<Context = HttpContext<A, S>>,
|
||||
S: 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn disconnected(&mut self) {
|
||||
self.disconnected = true;
|
||||
self.stop();
|
||||
self.fut.ctx().disconnected = true;
|
||||
self.fut.ctx().stop();
|
||||
}
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error> {
|
||||
let ctx: &mut HttpContext<A, S> =
|
||||
unsafe { &mut *(self as &mut HttpContext<A, S> as *mut _) };
|
||||
|
||||
if self.inner.alive() {
|
||||
match self.inner.poll(ctx) {
|
||||
if self.fut.alive() {
|
||||
match self.fut.poll() {
|
||||
Ok(Async::NotReady) | Ok(Async::Ready(())) => (),
|
||||
Err(_) => return Err(ErrorInternalServerError("error")),
|
||||
}
|
||||
}
|
||||
|
||||
// frames
|
||||
if let Some(data) = self.stream.take() {
|
||||
if let Some(data) = self.fut.ctx().stream.take() {
|
||||
Ok(Async::Ready(Some(data)))
|
||||
} else if self.inner.alive() {
|
||||
} else if self.fut.alive() {
|
||||
Ok(Async::NotReady)
|
||||
} else {
|
||||
Ok(Async::Ready(None))
|
||||
@@ -223,33 +253,25 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, M, S> ToEnvelope<Syn, A, M> for HttpContext<A, S>
|
||||
impl<A, M, S> ToEnvelope<A, M> for HttpContext<A, S>
|
||||
where
|
||||
A: Actor<Context = HttpContext<A, S>> + Handler<M>,
|
||||
M: Message + Send + 'static,
|
||||
M::Result: Send,
|
||||
{
|
||||
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> SyncEnvelope<A> {
|
||||
SyncEnvelope::new(msg, tx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> From<HttpContext<A, S>> for Body
|
||||
where
|
||||
A: Actor<Context = HttpContext<A, S>>,
|
||||
S: 'static,
|
||||
{
|
||||
fn from(ctx: HttpContext<A, S>) -> Body {
|
||||
Body::Actor(Box::new(ctx))
|
||||
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> Envelope<A> {
|
||||
Envelope::new(msg, tx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume a future
|
||||
pub struct Drain<A> {
|
||||
fut: oneshot::Receiver<()>,
|
||||
_a: PhantomData<A>,
|
||||
}
|
||||
|
||||
impl<A> Drain<A> {
|
||||
/// Create a drain from a future
|
||||
pub fn new(fut: oneshot::Receiver<()>) -> Self {
|
||||
Drain {
|
||||
fut,
|
||||
|
||||
87
src/de.rs
87
src/de.rs
@@ -1,9 +1,10 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use serde::de::{self, Deserializer, Error as DeError, Visitor};
|
||||
use std::borrow::Cow;
|
||||
use std::convert::AsRef;
|
||||
use std::slice::Iter;
|
||||
|
||||
use httprequest::HttpRequest;
|
||||
use param::ParamsIter;
|
||||
use uri::RESERVED_QUOTER;
|
||||
|
||||
macro_rules! unsupported_type {
|
||||
($trait_fn:ident, $name:expr) => {
|
||||
@@ -15,6 +16,20 @@ macro_rules! unsupported_type {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! percent_decode_if_needed {
|
||||
($value:expr, $decode:expr) => {
|
||||
if $decode {
|
||||
if let Some(ref mut value) = RESERVED_QUOTER.requote($value.as_bytes()) {
|
||||
Rc::make_mut(value).parse()
|
||||
} else {
|
||||
$value.parse()
|
||||
}
|
||||
} else {
|
||||
$value.parse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! parse_single_value {
|
||||
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
|
||||
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
@@ -25,11 +40,11 @@ macro_rules! parse_single_value {
|
||||
format!("wrong number of parameters: {} expected 1",
|
||||
self.req.match_info().len()).as_str()))
|
||||
} else {
|
||||
let v = self.req.match_info()[0].parse().map_err(
|
||||
|_| de::value::Error::custom(
|
||||
format!("can not parse {:?} to a {}",
|
||||
&self.req.match_info()[0], $tp)))?;
|
||||
visitor.$visit_fn(v)
|
||||
let v_parsed = percent_decode_if_needed!(&self.req.match_info()[0], self.decode)
|
||||
.map_err(|_| de::value::Error::custom(
|
||||
format!("can not parse {:?} to a {}", &self.req.match_info()[0], $tp)
|
||||
))?;
|
||||
visitor.$visit_fn(v_parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,11 +52,12 @@ macro_rules! parse_single_value {
|
||||
|
||||
pub struct PathDeserializer<'de, S: 'de> {
|
||||
req: &'de HttpRequest<S>,
|
||||
decode: bool,
|
||||
}
|
||||
|
||||
impl<'de, S: 'de> PathDeserializer<'de, S> {
|
||||
pub fn new(req: &'de HttpRequest<S>) -> Self {
|
||||
PathDeserializer { req }
|
||||
pub fn new(req: &'de HttpRequest<S>, decode: bool) -> Self {
|
||||
PathDeserializer { req, decode }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +71,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
visitor.visit_map(ParamsDeserializer {
|
||||
params: self.req.match_info().iter(),
|
||||
current: None,
|
||||
decode: self.decode,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -109,6 +126,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
} else {
|
||||
visitor.visit_seq(ParamsSeq {
|
||||
params: self.req.match_info().iter(),
|
||||
decode: self.decode,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -130,6 +148,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
} else {
|
||||
visitor.visit_seq(ParamsSeq {
|
||||
params: self.req.match_info().iter(),
|
||||
decode: self.decode,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -143,28 +162,13 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
Err(de::value::Error::custom("unsupported type: enum"))
|
||||
}
|
||||
|
||||
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
if self.req.match_info().len() != 1 {
|
||||
Err(de::value::Error::custom(
|
||||
format!(
|
||||
"wrong number of parameters: {} expected 1",
|
||||
self.req.match_info().len()
|
||||
).as_str(),
|
||||
))
|
||||
} else {
|
||||
visitor.visit_str(&self.req.match_info()[0])
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_seq(ParamsSeq {
|
||||
params: self.req.match_info().iter(),
|
||||
decode: self.decode,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -177,7 +181,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
parse_single_value!(deserialize_bool, visit_bool, "bool");
|
||||
parse_single_value!(deserialize_i8, visit_i8, "i8");
|
||||
parse_single_value!(deserialize_i16, visit_i16, "i16");
|
||||
parse_single_value!(deserialize_i32, visit_i32, "i16");
|
||||
parse_single_value!(deserialize_i32, visit_i32, "i32");
|
||||
parse_single_value!(deserialize_i64, visit_i64, "i64");
|
||||
parse_single_value!(deserialize_u8, visit_u8, "u8");
|
||||
parse_single_value!(deserialize_u16, visit_u16, "u16");
|
||||
@@ -186,13 +190,16 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
parse_single_value!(deserialize_f32, visit_f32, "f32");
|
||||
parse_single_value!(deserialize_f64, visit_f64, "f64");
|
||||
parse_single_value!(deserialize_string, visit_string, "String");
|
||||
parse_single_value!(deserialize_str, visit_string, "String");
|
||||
parse_single_value!(deserialize_byte_buf, visit_string, "String");
|
||||
parse_single_value!(deserialize_char, visit_char, "char");
|
||||
|
||||
}
|
||||
|
||||
struct ParamsDeserializer<'de> {
|
||||
params: Iter<'de, (Cow<'de, str>, Cow<'de, str>)>,
|
||||
params: ParamsIter<'de>,
|
||||
current: Option<(&'de str, &'de str)>,
|
||||
decode: bool,
|
||||
}
|
||||
|
||||
impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
|
||||
@@ -202,10 +209,7 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
|
||||
where
|
||||
K: de::DeserializeSeed<'de>,
|
||||
{
|
||||
self.current = self
|
||||
.params
|
||||
.next()
|
||||
.map(|&(ref k, ref v)| (k.as_ref(), v.as_ref()));
|
||||
self.current = self.params.next().map(|ref item| (item.0, item.1));
|
||||
match self.current {
|
||||
Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)),
|
||||
None => Ok(None),
|
||||
@@ -217,7 +221,7 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
|
||||
V: de::DeserializeSeed<'de>,
|
||||
{
|
||||
if let Some((_, value)) = self.current.take() {
|
||||
seed.deserialize(Value { value })
|
||||
seed.deserialize(Value { value, decode: self.decode })
|
||||
} else {
|
||||
Err(de::value::Error::custom("unexpected item"))
|
||||
}
|
||||
@@ -257,16 +261,18 @@ macro_rules! parse_value {
|
||||
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where V: Visitor<'de>
|
||||
{
|
||||
let v = self.value.parse().map_err(
|
||||
|_| de::value::Error::custom(
|
||||
format!("can not parse {:?} to a {}", self.value, $tp)))?;
|
||||
visitor.$visit_fn(v)
|
||||
let v_parsed = percent_decode_if_needed!(&self.value, self.decode)
|
||||
.map_err(|_| de::value::Error::custom(
|
||||
format!("can not parse {:?} to a {}", &self.value, $tp)
|
||||
))?;
|
||||
visitor.$visit_fn(v_parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Value<'de> {
|
||||
value: &'de str,
|
||||
decode: bool,
|
||||
}
|
||||
|
||||
impl<'de> Deserializer<'de> for Value<'de> {
|
||||
@@ -381,7 +387,8 @@ impl<'de> Deserializer<'de> for Value<'de> {
|
||||
}
|
||||
|
||||
struct ParamsSeq<'de> {
|
||||
params: Iter<'de, (Cow<'de, str>, Cow<'de, str>)>,
|
||||
params: ParamsIter<'de>,
|
||||
decode: bool,
|
||||
}
|
||||
|
||||
impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
|
||||
@@ -392,9 +399,7 @@ impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
|
||||
T: de::DeserializeSeed<'de>,
|
||||
{
|
||||
match self.params.next() {
|
||||
Some(item) => Ok(Some(seed.deserialize(Value {
|
||||
value: item.1.as_ref(),
|
||||
})?)),
|
||||
Some(item) => Ok(Some(seed.deserialize(Value { value: item.1, decode: self.decode })?)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
574
src/error.rs
574
src/error.rs
@@ -1,21 +1,22 @@
|
||||
//! Error and Result module
|
||||
use std::cell::RefCell;
|
||||
use std::io::Error as IoError;
|
||||
use std::str::Utf8Error;
|
||||
use std::string::FromUtf8Error;
|
||||
use std::sync::Mutex;
|
||||
use std::{fmt, io, result};
|
||||
|
||||
use actix::MailboxError;
|
||||
use actix::{MailboxError, SendError};
|
||||
use cookie;
|
||||
use failure::{self, Backtrace, Fail};
|
||||
use futures::Canceled;
|
||||
use http::uri::InvalidUri;
|
||||
use http::{header, Error as HttpError, StatusCode};
|
||||
use http2::Error as Http2Error;
|
||||
use http_range::HttpRangeParseError;
|
||||
use httparse;
|
||||
use serde::de::value::Error as DeError;
|
||||
use serde_json::error::Error as JsonError;
|
||||
use serde_urlencoded::ser::Error as FormError;
|
||||
use tokio_timer::Error as TimerError;
|
||||
pub use url::ParseError as UrlParseError;
|
||||
|
||||
// re-exports
|
||||
@@ -23,7 +24,7 @@ pub use cookie::ParseError as CookieParseError;
|
||||
|
||||
use handler::Responder;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use httpresponse::{HttpResponse, HttpResponseParts};
|
||||
|
||||
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
|
||||
/// for actix web operations
|
||||
@@ -33,21 +34,45 @@ use httpresponse::HttpResponse;
|
||||
/// `Result`.
|
||||
pub type Result<T, E = Error> = result::Result<T, E>;
|
||||
|
||||
/// General purpose actix web error
|
||||
/// General purpose actix web error.
|
||||
///
|
||||
/// An actix web error is used to carry errors from `failure` or `std::error`
|
||||
/// through actix in a convenient way. It can be created through
|
||||
/// converting errors with `into()`.
|
||||
///
|
||||
/// Whenever it is created from an external object a response error is created
|
||||
/// for it that can be used to create an http response from it this means that
|
||||
/// if you have access to an actix `Error` you can always get a
|
||||
/// `ResponseError` reference from it.
|
||||
pub struct Error {
|
||||
cause: Box<ResponseError>,
|
||||
backtrace: Option<Backtrace>,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Returns a reference to the underlying cause of this Error.
|
||||
// this should return &Fail but needs this https://github.com/rust-lang/rust/issues/5665
|
||||
/// Deprecated way to reference the underlying response error.
|
||||
#[deprecated(
|
||||
since = "0.6.0",
|
||||
note = "please use `Error::as_response_error()` instead"
|
||||
)]
|
||||
pub fn cause(&self) -> &ResponseError {
|
||||
self.cause.as_ref()
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying cause of this `Error` as `Fail`
|
||||
pub fn as_fail(&self) -> &Fail {
|
||||
self.cause.as_fail()
|
||||
}
|
||||
|
||||
/// Returns the reference to the underlying `ResponseError`.
|
||||
pub fn as_response_error(&self) -> &ResponseError {
|
||||
self.cause.as_ref()
|
||||
}
|
||||
|
||||
/// Returns a reference to the Backtrace carried by this error, if it
|
||||
/// carries one.
|
||||
///
|
||||
/// This uses the same `Backtrace` type that `failure` uses.
|
||||
pub fn backtrace(&self) -> &Backtrace {
|
||||
if let Some(bt) = self.cause.backtrace() {
|
||||
bt
|
||||
@@ -55,10 +80,54 @@ impl Error {
|
||||
self.backtrace.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to downcast this `Error` to a particular `Fail` type by
|
||||
/// reference.
|
||||
///
|
||||
/// If the underlying error is not of type `T`, this will return `None`.
|
||||
pub fn downcast_ref<T: Fail>(&self) -> Option<&T> {
|
||||
// in the most trivial way the cause is directly of the requested type.
|
||||
if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) {
|
||||
return Some(rv);
|
||||
}
|
||||
|
||||
// in the more complex case the error has been constructed from a failure
|
||||
// error. This happens because we implement From<failure::Error> by
|
||||
// calling compat() and then storing it here. In failure this is
|
||||
// represented by a failure::Error being wrapped in a failure::Compat.
|
||||
//
|
||||
// So we first downcast into that compat, to then further downcast through
|
||||
// the failure's Error downcasting system into the original failure.
|
||||
let compat: Option<&failure::Compat<failure::Error>> =
|
||||
Fail::downcast_ref(self.cause.as_fail());
|
||||
compat.and_then(|e| e.get_ref().downcast_ref())
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait to downcast a response error into a fail.
|
||||
///
|
||||
/// This is currently not exposed because it's unclear if this is the best way
|
||||
/// to achieve the downcasting on `Error` for which this is needed.
|
||||
#[doc(hidden)]
|
||||
pub trait InternalResponseErrorAsFail {
|
||||
#[doc(hidden)]
|
||||
fn as_fail(&self) -> &Fail;
|
||||
#[doc(hidden)]
|
||||
fn as_mut_fail(&mut self) -> &mut Fail;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl<T: ResponseError> InternalResponseErrorAsFail for T {
|
||||
fn as_fail(&self) -> &Fail {
|
||||
self
|
||||
}
|
||||
fn as_mut_fail(&mut self) -> &mut Fail {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Error that can be converted to `HttpResponse`
|
||||
pub trait ResponseError: Fail {
|
||||
pub trait ResponseError: Fail + InternalResponseErrorAsFail {
|
||||
/// Create response for error
|
||||
///
|
||||
/// Internal server error is generated by default.
|
||||
@@ -67,6 +136,10 @@ pub trait ResponseError: Fail {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ResponseError for SendError<T>
|
||||
where T: Send + Sync + 'static {
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.cause, f)
|
||||
@@ -111,11 +184,9 @@ impl<T: ResponseError> From<T> for Error {
|
||||
}
|
||||
|
||||
/// Compatibility for `failure::Error`
|
||||
impl<T> ResponseError for failure::Compat<T>
|
||||
where
|
||||
T: fmt::Display + fmt::Debug + Sync + Send + 'static,
|
||||
{
|
||||
}
|
||||
impl<T> ResponseError for failure::Compat<T> where
|
||||
T: fmt::Display + fmt::Debug + Sync + Send + 'static
|
||||
{}
|
||||
|
||||
impl From<failure::Error> for Error {
|
||||
fn from(err: failure::Error) -> Error {
|
||||
@@ -126,6 +197,12 @@ impl From<failure::Error> for Error {
|
||||
/// `InternalServerError` for `JsonError`
|
||||
impl ResponseError for JsonError {}
|
||||
|
||||
/// `InternalServerError` for `FormError`
|
||||
impl ResponseError for FormError {}
|
||||
|
||||
/// `InternalServerError` for `TimerError`
|
||||
impl ResponseError for TimerError {}
|
||||
|
||||
/// `InternalServerError` for `UrlParseError`
|
||||
impl ResponseError for UrlParseError {}
|
||||
|
||||
@@ -289,8 +366,18 @@ impl From<IoError> for PayloadError {
|
||||
}
|
||||
}
|
||||
|
||||
/// `InternalServerError` for `PayloadError`
|
||||
impl ResponseError for PayloadError {}
|
||||
/// `PayloadError` returns two possible results:
|
||||
///
|
||||
/// - `Overflow` returns `PayloadTooLarge`
|
||||
/// - Other errors returns `BadRequest`
|
||||
impl ResponseError for PayloadError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
match *self {
|
||||
PayloadError::Overflow => HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE),
|
||||
_ => HttpResponse::new(StatusCode::BAD_REQUEST),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `BadRequest` for `cookie::ParseError`
|
||||
impl ResponseError for cookie::ParseError {
|
||||
@@ -299,37 +386,6 @@ impl ResponseError for cookie::ParseError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Http range header parsing error
|
||||
#[derive(Fail, PartialEq, Debug)]
|
||||
pub enum HttpRangeError {
|
||||
/// Returned if range is invalid.
|
||||
#[fail(display = "Range header is invalid")]
|
||||
InvalidRange,
|
||||
/// Returned if first-byte-pos of all of the byte-range-spec
|
||||
/// values is greater than the content size.
|
||||
/// See `https://github.com/golang/go/commit/aa9b3d7`
|
||||
#[fail(
|
||||
display = "First-byte-pos of all of the byte-range-spec values is greater than the content size"
|
||||
)]
|
||||
NoOverlap,
|
||||
}
|
||||
|
||||
/// Return `BadRequest` for `HttpRangeError`
|
||||
impl ResponseError for HttpRangeError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::with_body(StatusCode::BAD_REQUEST, "Invalid Range header provided")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpRangeParseError> for HttpRangeError {
|
||||
fn from(err: HttpRangeParseError) -> HttpRangeError {
|
||||
match err {
|
||||
HttpRangeParseError::InvalidRange => HttpRangeError::InvalidRange,
|
||||
HttpRangeParseError::NoOverlap => HttpRangeError::NoOverlap,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of errors that can occur during parsing multipart streams
|
||||
#[derive(Fail, Debug)]
|
||||
pub enum MultipartError {
|
||||
@@ -413,8 +469,10 @@ pub enum UrlencodedError {
|
||||
/// Can not decode chunked transfer encoding
|
||||
#[fail(display = "Can not decode chunked transfer encoding")]
|
||||
Chunked,
|
||||
/// Payload size is bigger than 256k
|
||||
#[fail(display = "Payload size is bigger than 256k")]
|
||||
/// Payload size is bigger than allowed. (default: 256kB)
|
||||
#[fail(
|
||||
display = "Urlencoded payload size is bigger than allowed. (default: 256kB)"
|
||||
)]
|
||||
Overflow,
|
||||
/// Payload size is now known
|
||||
#[fail(display = "Payload size is now known")]
|
||||
@@ -454,8 +512,8 @@ impl From<PayloadError> for UrlencodedError {
|
||||
/// A set of errors that can occur during parsing json payloads
|
||||
#[derive(Fail, Debug)]
|
||||
pub enum JsonPayloadError {
|
||||
/// Payload size is bigger than 256k
|
||||
#[fail(display = "Payload size is bigger than 256k")]
|
||||
/// Payload size is bigger than allowed. (default: 256kB)
|
||||
#[fail(display = "Json payload size is bigger than allowed. (default: 256kB)")]
|
||||
Overflow,
|
||||
/// Content type error
|
||||
#[fail(display = "Content type error")]
|
||||
@@ -492,6 +550,30 @@ impl From<JsonError> for JsonPayloadError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Error type returned when reading body as lines.
|
||||
pub enum ReadlinesError {
|
||||
/// Error when decoding a line.
|
||||
EncodingError,
|
||||
/// Payload error.
|
||||
PayloadError(PayloadError),
|
||||
/// Line limit exceeded.
|
||||
LimitOverflow,
|
||||
/// ContentType error.
|
||||
ContentTypeError(ContentTypeError),
|
||||
}
|
||||
|
||||
impl From<PayloadError> for ReadlinesError {
|
||||
fn from(err: PayloadError) -> Self {
|
||||
ReadlinesError::PayloadError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ContentTypeError> for ReadlinesError {
|
||||
fn from(err: ContentTypeError) -> Self {
|
||||
ReadlinesError::ContentTypeError(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors which can occur when attempting to interpret a segment string as a
|
||||
/// valid path segment.
|
||||
#[derive(Fail, Debug, PartialEq)]
|
||||
@@ -517,12 +599,13 @@ impl ResponseError for UriSegmentError {
|
||||
/// Errors which can occur when attempting to generate resource uri.
|
||||
#[derive(Fail, Debug, PartialEq)]
|
||||
pub enum UrlGenerationError {
|
||||
/// Resource not found
|
||||
#[fail(display = "Resource not found")]
|
||||
ResourceNotFound,
|
||||
/// Not all path pattern covered
|
||||
#[fail(display = "Not all path pattern covered")]
|
||||
NotEnoughElements,
|
||||
#[fail(display = "Router is not available")]
|
||||
RouterNotAvailable,
|
||||
/// URL parse error
|
||||
#[fail(display = "{}", _0)]
|
||||
ParseError(#[cause] UrlParseError),
|
||||
}
|
||||
@@ -536,6 +619,24 @@ impl From<UrlParseError> for UrlGenerationError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors which can occur when serving static files.
|
||||
#[derive(Fail, Debug, PartialEq)]
|
||||
pub enum StaticFileError {
|
||||
/// Path is not a directory
|
||||
#[fail(display = "Path is not a directory. Unable to serve static files")]
|
||||
IsNotDirectory,
|
||||
/// Cannot render directory
|
||||
#[fail(display = "Unable to render directory without index file")]
|
||||
IsDirectory,
|
||||
}
|
||||
|
||||
/// Return `NotFound` for `StaticFileError`
|
||||
impl ResponseError for StaticFileError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::new(StatusCode::NOT_FOUND)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper type that can wrap any error and generate custom response.
|
||||
///
|
||||
/// In following example any `io::Error` will be converted into "BAD REQUEST"
|
||||
@@ -548,8 +649,8 @@ impl From<UrlParseError> for UrlGenerationError {
|
||||
/// use actix_web::fs::NamedFile;
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> Result<fs::NamedFile> {
|
||||
/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?;
|
||||
/// Ok(f)
|
||||
/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?;
|
||||
/// Ok(f)
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
@@ -559,12 +660,9 @@ pub struct InternalError<T> {
|
||||
backtrace: Backtrace,
|
||||
}
|
||||
|
||||
unsafe impl<T> Sync for InternalError<T> {}
|
||||
unsafe impl<T> Send for InternalError<T> {}
|
||||
|
||||
enum InternalErrorType {
|
||||
Status(StatusCode),
|
||||
Response(RefCell<Option<HttpResponse>>),
|
||||
Response(Box<Mutex<Option<HttpResponseParts>>>),
|
||||
}
|
||||
|
||||
impl<T> InternalError<T> {
|
||||
@@ -579,9 +677,10 @@ impl<T> InternalError<T> {
|
||||
|
||||
/// Create `InternalError` with predefined `HttpResponse`.
|
||||
pub fn from_response(cause: T, response: HttpResponse) -> Self {
|
||||
let resp = response.into_parts();
|
||||
InternalError {
|
||||
cause,
|
||||
status: InternalErrorType::Response(RefCell::new(Some(response))),
|
||||
status: InternalErrorType::Response(Box::new(Mutex::new(Some(resp)))),
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
@@ -622,8 +721,8 @@ where
|
||||
match self.status {
|
||||
InternalErrorType::Status(st) => HttpResponse::new(st),
|
||||
InternalErrorType::Response(ref resp) => {
|
||||
if let Some(resp) = resp.borrow_mut().take() {
|
||||
resp
|
||||
if let Some(resp) = resp.lock().unwrap().take() {
|
||||
HttpResponse::from_parts(resp)
|
||||
} else {
|
||||
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
@@ -664,6 +763,16 @@ where
|
||||
InternalError::new(err, StatusCode::UNAUTHORIZED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *PAYMENT_REQUIRED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorPaymentRequired<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::PAYMENT_REQUIRED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *FORBIDDEN*
|
||||
/// response.
|
||||
#[allow(non_snake_case)]
|
||||
@@ -694,6 +803,26 @@ where
|
||||
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *NOT
|
||||
/// ACCEPTABLE* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorNotAcceptable<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::NOT_ACCEPTABLE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *PROXY
|
||||
/// AUTHENTICATION REQUIRED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorProxyAuthenticationRequired<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::PROXY_AUTHENTICATION_REQUIRED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *REQUEST
|
||||
/// TIMEOUT* response.
|
||||
#[allow(non_snake_case)]
|
||||
@@ -724,6 +853,16 @@ where
|
||||
InternalError::new(err, StatusCode::GONE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *LENGTH
|
||||
/// REQUIRED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorLengthRequired<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::LENGTH_REQUIRED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *PRECONDITION FAILED* response.
|
||||
#[allow(non_snake_case)]
|
||||
@@ -734,6 +873,46 @@ where
|
||||
InternalError::new(err, StatusCode::PRECONDITION_FAILED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *PAYLOAD TOO LARGE* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorPayloadTooLarge<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::PAYLOAD_TOO_LARGE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *URI TOO LONG* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorUriTooLong<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::URI_TOO_LONG).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *UNSUPPORTED MEDIA TYPE* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorUnsupportedMediaType<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::UNSUPPORTED_MEDIA_TYPE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *RANGE NOT SATISFIABLE* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorRangeNotSatisfiable<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::RANGE_NOT_SATISFIABLE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *EXPECTATION FAILED* response.
|
||||
#[allow(non_snake_case)]
|
||||
@@ -744,6 +923,106 @@ where
|
||||
InternalError::new(err, StatusCode::EXPECTATION_FAILED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *IM A TEAPOT* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorImATeapot<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::IM_A_TEAPOT).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *MISDIRECTED REQUEST* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorMisdirectedRequest<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::MISDIRECTED_REQUEST).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *UNPROCESSABLE ENTITY* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorUnprocessableEntity<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::UNPROCESSABLE_ENTITY).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *LOCKED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorLocked<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::LOCKED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *FAILED DEPENDENCY* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorFailedDependency<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::FAILED_DEPENDENCY).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *UPGRADE REQUIRED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorUpgradeRequired<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::UPGRADE_REQUIRED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *PRECONDITION REQUIRED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorPreconditionRequired<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::PRECONDITION_REQUIRED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *TOO MANY REQUESTS* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorTooManyRequests<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::TOO_MANY_REQUESTS).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *REQUEST HEADER FIELDS TOO LARGE* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorRequestHeaderFieldsTooLarge<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *UNAVAILABLE FOR LEGAL REASONS* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorUnavailableForLegalReasons<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *INTERNAL SERVER ERROR* response.
|
||||
#[allow(non_snake_case)]
|
||||
@@ -794,6 +1073,66 @@ where
|
||||
InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *HTTP VERSION NOT SUPPORTED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorHttpVersionNotSupported<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::HTTP_VERSION_NOT_SUPPORTED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *VARIANT ALSO NEGOTIATES* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorVariantAlsoNegotiates<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::VARIANT_ALSO_NEGOTIATES).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *INSUFFICIENT STORAGE* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorInsufficientStorage<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::INSUFFICIENT_STORAGE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *LOOP DETECTED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorLoopDetected<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::LOOP_DETECTED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *NOT EXTENDED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorNotExtended<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::NOT_EXTENDED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *NETWORK AUTHENTICATION REQUIRED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorNetworkAuthenticationRequired<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -818,9 +1157,6 @@ mod tests {
|
||||
let resp: HttpResponse = ParseError::Incomplete.error_response();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let resp: HttpResponse = HttpRangeError::InvalidRange.error_response();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let resp: HttpResponse = CookieParseError::EmptyName.error_response();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
@@ -833,7 +1169,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cause() {
|
||||
fn test_as_fail() {
|
||||
let orig = io::Error::new(io::ErrorKind::Other, "other");
|
||||
let desc = orig.description().to_owned();
|
||||
let e = ParseError::Io(orig);
|
||||
@@ -851,7 +1187,7 @@ mod tests {
|
||||
let orig = io::Error::new(io::ErrorKind::Other, "other");
|
||||
let desc = orig.description().to_owned();
|
||||
let e = Error::from(orig);
|
||||
assert_eq!(format!("{}", e.cause()), desc);
|
||||
assert_eq!(format!("{}", e.as_fail()), desc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -870,14 +1206,6 @@ mod tests {
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range_error() {
|
||||
let e: HttpRangeError = HttpRangeParseError::InvalidRange.into();
|
||||
assert_eq!(e, HttpRangeError::InvalidRange);
|
||||
let e: HttpRangeError = HttpRangeParseError::NoOverlap.into();
|
||||
assert_eq!(e, HttpRangeError::NoOverlap);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expect_error() {
|
||||
let resp: HttpResponse = ExpectError::Encoding.error_response();
|
||||
@@ -950,6 +1278,32 @@ mod tests {
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_downcasting_direct() {
|
||||
#[derive(Debug, Fail)]
|
||||
#[fail(display = "demo error")]
|
||||
struct DemoError;
|
||||
|
||||
impl ResponseError for DemoError {}
|
||||
|
||||
let err: Error = DemoError.into();
|
||||
let err_ref: &DemoError = err.downcast_ref().unwrap();
|
||||
assert_eq!(err_ref.to_string(), "demo error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_downcasting_compat() {
|
||||
#[derive(Debug, Fail)]
|
||||
#[fail(display = "demo error")]
|
||||
struct DemoError;
|
||||
|
||||
impl ResponseError for DemoError {}
|
||||
|
||||
let err: Error = failure::Error::from(DemoError).into();
|
||||
let err_ref: &DemoError = err.downcast_ref().unwrap();
|
||||
assert_eq!(err_ref.to_string(), "demo error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_helpers() {
|
||||
let r: HttpResponse = ErrorBadRequest("err").into();
|
||||
@@ -958,6 +1312,9 @@ mod tests {
|
||||
let r: HttpResponse = ErrorUnauthorized("err").into();
|
||||
assert_eq!(r.status(), StatusCode::UNAUTHORIZED);
|
||||
|
||||
let r: HttpResponse = ErrorPaymentRequired("err").into();
|
||||
assert_eq!(r.status(), StatusCode::PAYMENT_REQUIRED);
|
||||
|
||||
let r: HttpResponse = ErrorForbidden("err").into();
|
||||
assert_eq!(r.status(), StatusCode::FORBIDDEN);
|
||||
|
||||
@@ -967,6 +1324,12 @@ mod tests {
|
||||
let r: HttpResponse = ErrorMethodNotAllowed("err").into();
|
||||
assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
|
||||
let r: HttpResponse = ErrorNotAcceptable("err").into();
|
||||
assert_eq!(r.status(), StatusCode::NOT_ACCEPTABLE);
|
||||
|
||||
let r: HttpResponse = ErrorProxyAuthenticationRequired("err").into();
|
||||
assert_eq!(r.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED);
|
||||
|
||||
let r: HttpResponse = ErrorRequestTimeout("err").into();
|
||||
assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT);
|
||||
|
||||
@@ -976,12 +1339,57 @@ mod tests {
|
||||
let r: HttpResponse = ErrorGone("err").into();
|
||||
assert_eq!(r.status(), StatusCode::GONE);
|
||||
|
||||
let r: HttpResponse = ErrorLengthRequired("err").into();
|
||||
assert_eq!(r.status(), StatusCode::LENGTH_REQUIRED);
|
||||
|
||||
let r: HttpResponse = ErrorPreconditionFailed("err").into();
|
||||
assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED);
|
||||
|
||||
let r: HttpResponse = ErrorPayloadTooLarge("err").into();
|
||||
assert_eq!(r.status(), StatusCode::PAYLOAD_TOO_LARGE);
|
||||
|
||||
let r: HttpResponse = ErrorUriTooLong("err").into();
|
||||
assert_eq!(r.status(), StatusCode::URI_TOO_LONG);
|
||||
|
||||
let r: HttpResponse = ErrorUnsupportedMediaType("err").into();
|
||||
assert_eq!(r.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
|
||||
|
||||
let r: HttpResponse = ErrorRangeNotSatisfiable("err").into();
|
||||
assert_eq!(r.status(), StatusCode::RANGE_NOT_SATISFIABLE);
|
||||
|
||||
let r: HttpResponse = ErrorExpectationFailed("err").into();
|
||||
assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED);
|
||||
|
||||
let r: HttpResponse = ErrorImATeapot("err").into();
|
||||
assert_eq!(r.status(), StatusCode::IM_A_TEAPOT);
|
||||
|
||||
let r: HttpResponse = ErrorMisdirectedRequest("err").into();
|
||||
assert_eq!(r.status(), StatusCode::MISDIRECTED_REQUEST);
|
||||
|
||||
let r: HttpResponse = ErrorUnprocessableEntity("err").into();
|
||||
assert_eq!(r.status(), StatusCode::UNPROCESSABLE_ENTITY);
|
||||
|
||||
let r: HttpResponse = ErrorLocked("err").into();
|
||||
assert_eq!(r.status(), StatusCode::LOCKED);
|
||||
|
||||
let r: HttpResponse = ErrorFailedDependency("err").into();
|
||||
assert_eq!(r.status(), StatusCode::FAILED_DEPENDENCY);
|
||||
|
||||
let r: HttpResponse = ErrorUpgradeRequired("err").into();
|
||||
assert_eq!(r.status(), StatusCode::UPGRADE_REQUIRED);
|
||||
|
||||
let r: HttpResponse = ErrorPreconditionRequired("err").into();
|
||||
assert_eq!(r.status(), StatusCode::PRECONDITION_REQUIRED);
|
||||
|
||||
let r: HttpResponse = ErrorTooManyRequests("err").into();
|
||||
assert_eq!(r.status(), StatusCode::TOO_MANY_REQUESTS);
|
||||
|
||||
let r: HttpResponse = ErrorRequestHeaderFieldsTooLarge("err").into();
|
||||
assert_eq!(r.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE);
|
||||
|
||||
let r: HttpResponse = ErrorUnavailableForLegalReasons("err").into();
|
||||
assert_eq!(r.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS);
|
||||
|
||||
let r: HttpResponse = ErrorInternalServerError("err").into();
|
||||
assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
|
||||
@@ -996,5 +1404,23 @@ mod tests {
|
||||
|
||||
let r: HttpResponse = ErrorGatewayTimeout("err").into();
|
||||
assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT);
|
||||
|
||||
let r: HttpResponse = ErrorHttpVersionNotSupported("err").into();
|
||||
assert_eq!(r.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED);
|
||||
|
||||
let r: HttpResponse = ErrorVariantAlsoNegotiates("err").into();
|
||||
assert_eq!(r.status(), StatusCode::VARIANT_ALSO_NEGOTIATES);
|
||||
|
||||
let r: HttpResponse = ErrorInsufficientStorage("err").into();
|
||||
assert_eq!(r.status(), StatusCode::INSUFFICIENT_STORAGE);
|
||||
|
||||
let r: HttpResponse = ErrorLoopDetected("err").into();
|
||||
assert_eq!(r.status(), StatusCode::LOOP_DETECTED);
|
||||
|
||||
let r: HttpResponse = ErrorNotExtended("err").into();
|
||||
assert_eq!(r.status(), StatusCode::NOT_EXTENDED);
|
||||
|
||||
let r: HttpResponse = ErrorNetworkAuthenticationRequired("err").into();
|
||||
assert_eq!(r.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
114
src/extensions.rs
Normal file
114
src/extensions.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::hash::{BuildHasherDefault, Hasher};
|
||||
|
||||
struct IdHasher {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl Default for IdHasher {
|
||||
fn default() -> IdHasher {
|
||||
IdHasher { id: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Hasher for IdHasher {
|
||||
fn write(&mut self, bytes: &[u8]) {
|
||||
for &x in bytes {
|
||||
self.id.wrapping_add(u64::from(x));
|
||||
}
|
||||
}
|
||||
|
||||
fn write_u64(&mut self, u: u64) {
|
||||
self.id = u;
|
||||
}
|
||||
|
||||
fn finish(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
type AnyMap = HashMap<TypeId, Box<Any>, BuildHasherDefault<IdHasher>>;
|
||||
|
||||
#[derive(Default)]
|
||||
/// A type map of request extensions.
|
||||
pub struct Extensions {
|
||||
map: AnyMap,
|
||||
}
|
||||
|
||||
impl Extensions {
|
||||
/// Create an empty `Extensions`.
|
||||
#[inline]
|
||||
pub fn new() -> Extensions {
|
||||
Extensions {
|
||||
map: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a type into this `Extensions`.
|
||||
///
|
||||
/// If a extension of this type already existed, it will
|
||||
/// be returned.
|
||||
pub fn insert<T: 'static>(&mut self, val: T) {
|
||||
self.map.insert(TypeId::of::<T>(), Box::new(val));
|
||||
}
|
||||
|
||||
/// Get a reference to a type previously inserted on this `Extensions`.
|
||||
pub fn get<T: 'static>(&self) -> Option<&T> {
|
||||
self.map
|
||||
.get(&TypeId::of::<T>())
|
||||
.and_then(|boxed| (&**boxed as &(Any + 'static)).downcast_ref())
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a type previously inserted on this `Extensions`.
|
||||
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||
self.map
|
||||
.get_mut(&TypeId::of::<T>())
|
||||
.and_then(|boxed| (&mut **boxed as &mut (Any + 'static)).downcast_mut())
|
||||
}
|
||||
|
||||
/// Remove a type from this `Extensions`.
|
||||
///
|
||||
/// If a extension of this type existed, it will be returned.
|
||||
pub fn remove<T: 'static>(&mut self) -> Option<T> {
|
||||
self.map.remove(&TypeId::of::<T>()).and_then(|boxed| {
|
||||
(boxed as Box<Any + 'static>)
|
||||
.downcast()
|
||||
.ok()
|
||||
.map(|boxed| *boxed)
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear the `Extensions` of all inserted extensions.
|
||||
#[inline]
|
||||
pub fn clear(&mut self) {
|
||||
self.map.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Extensions {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Extensions").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extensions() {
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct MyType(i32);
|
||||
|
||||
let mut extensions = Extensions::new();
|
||||
|
||||
extensions.insert(5i32);
|
||||
extensions.insert(MyType(10));
|
||||
|
||||
assert_eq!(extensions.get(), Some(&5i32));
|
||||
assert_eq!(extensions.get_mut(), Some(&mut 5i32));
|
||||
|
||||
assert_eq!(extensions.remove::<i32>(), Some(5i32));
|
||||
assert!(extensions.get::<i32>().is_none());
|
||||
|
||||
assert_eq!(extensions.get::<bool>(), None);
|
||||
assert_eq!(extensions.get(), Some(&MyType(10)));
|
||||
}
|
||||
833
src/extractor.rs
833
src/extractor.rs
File diff suppressed because it is too large
Load Diff
120
src/handler.rs
120
src/handler.rs
@@ -5,8 +5,10 @@ use futures::future::{err, ok, Future};
|
||||
use futures::{Async, Poll};
|
||||
|
||||
use error::Error;
|
||||
use http::StatusCode;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use resource::DefaultResource;
|
||||
|
||||
/// Trait defines object that could be registered as route handler
|
||||
#[allow(unused_variables)]
|
||||
@@ -15,7 +17,7 @@ pub trait Handler<S>: 'static {
|
||||
type Result: Responder;
|
||||
|
||||
/// Handle request
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result;
|
||||
fn handle(&self, req: &HttpRequest<S>) -> Self::Result;
|
||||
}
|
||||
|
||||
/// Trait implemented by types that generate responses for clients.
|
||||
@@ -61,28 +63,30 @@ pub trait FromRequest<S>: Sized {
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// # use futures::future::Future;
|
||||
/// use actix_web::{AsyncResponder, Either, Error, HttpRequest, HttpResponse};
|
||||
/// use futures::future::result;
|
||||
/// use actix_web::{Either, Error, HttpRequest, HttpResponse, AsyncResponder};
|
||||
///
|
||||
/// type RegisterResult = Either<HttpResponse, Box<Future<Item=HttpResponse, Error=Error>>>;
|
||||
///
|
||||
/// type RegisterResult =
|
||||
/// Either<HttpResponse, Box<Future<Item = HttpResponse, Error = Error>>>;
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> RegisterResult {
|
||||
/// if is_a_variant() { // <- choose variant A
|
||||
/// Either::A(
|
||||
/// HttpResponse::BadRequest().body("Bad data"))
|
||||
/// if is_a_variant() {
|
||||
/// // <- choose variant A
|
||||
/// Either::A(HttpResponse::BadRequest().body("Bad data"))
|
||||
/// } else {
|
||||
/// Either::B( // <- variant B
|
||||
/// Either::B(
|
||||
/// // <- variant B
|
||||
/// result(Ok(HttpResponse::Ok()
|
||||
/// .content_type("text/html")
|
||||
/// .body("Hello!")))
|
||||
/// .responder())
|
||||
/// .content_type("text/html")
|
||||
/// .body("Hello!")))
|
||||
/// .responder(),
|
||||
/// )
|
||||
/// }
|
||||
/// }
|
||||
/// # fn is_a_variant() -> bool { true }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Either<A, B> {
|
||||
/// First branch of the type
|
||||
A(A),
|
||||
@@ -130,6 +134,26 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Responder for Option<T>
|
||||
where
|
||||
T: Responder,
|
||||
{
|
||||
type Item = AsyncResult<HttpResponse>;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to<S: 'static>(
|
||||
self, req: &HttpRequest<S>,
|
||||
) -> Result<AsyncResult<HttpResponse>, Error> {
|
||||
match self {
|
||||
Some(t) => match t.respond_to(req) {
|
||||
Ok(val) => Ok(val.into()),
|
||||
Err(err) => Err(err.into()),
|
||||
},
|
||||
None => Ok(req.build_response(StatusCode::NOT_FOUND).finish().into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience trait that converts `Future` object to a `Boxed` future
|
||||
///
|
||||
/// For example loading json from request's body is async operation.
|
||||
@@ -138,16 +162,17 @@ where
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// use futures::future::Future;
|
||||
/// use actix_web::{
|
||||
/// App, HttpRequest, HttpResponse, HttpMessage, Error, AsyncResponder};
|
||||
/// App, AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse,
|
||||
/// };
|
||||
/// use futures::future::Future;
|
||||
///
|
||||
/// #[derive(Deserialize, Debug)]
|
||||
/// struct MyObj {
|
||||
/// name: String,
|
||||
/// }
|
||||
///
|
||||
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
|
||||
/// req.json() // <- get JsonBody future
|
||||
/// .from_err()
|
||||
/// .and_then(|val: MyObj| { // <- deserialized value
|
||||
@@ -159,6 +184,7 @@ where
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub trait AsyncResponder<I, E>: Sized {
|
||||
/// Convert to a boxed future
|
||||
fn responder(self) -> Box<Future<Item = I, Error = E>>;
|
||||
}
|
||||
|
||||
@@ -176,12 +202,12 @@ where
|
||||
/// Handler<S> for Fn()
|
||||
impl<F, R, S> Handler<S> for F
|
||||
where
|
||||
F: Fn(HttpRequest<S>) -> R + 'static,
|
||||
F: Fn(&HttpRequest<S>) -> R + 'static,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
type Result = R;
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> R {
|
||||
fn handle(&self, req: &HttpRequest<S>) -> R {
|
||||
(self)(req)
|
||||
}
|
||||
}
|
||||
@@ -224,7 +250,7 @@ pub(crate) enum AsyncResultItem<I, E> {
|
||||
impl<I, E> AsyncResult<I, E> {
|
||||
/// Create async response
|
||||
#[inline]
|
||||
pub fn async(fut: Box<Future<Item = I, Error = E>>) -> AsyncResult<I, E> {
|
||||
pub fn future(fut: Box<Future<Item = I, Error = E>>) -> AsyncResult<I, E> {
|
||||
AsyncResult(Some(AsyncResultItem::Future(fut)))
|
||||
}
|
||||
|
||||
@@ -327,13 +353,17 @@ impl<T, E: Into<Error>> From<Result<T, E>> for AsyncResult<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E: Into<Error>> From<Result<Box<Future<Item = T, Error = Error>>, E>>
|
||||
for AsyncResult<T>
|
||||
impl<T, E> From<Result<Box<Future<Item = T, Error = E>>, E>> for AsyncResult<T>
|
||||
where
|
||||
T: 'static,
|
||||
E: Into<Error> + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn from(res: Result<Box<Future<Item = T, Error = Error>>, E>) -> Self {
|
||||
fn from(res: Result<Box<Future<Item = T, Error = E>>, E>) -> Self {
|
||||
match res {
|
||||
Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(fut))),
|
||||
Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(Box::new(
|
||||
fut.map_err(|e| e.into()),
|
||||
)))),
|
||||
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
|
||||
}
|
||||
}
|
||||
@@ -371,13 +401,20 @@ where
|
||||
},
|
||||
Err(e) => err(e),
|
||||
});
|
||||
Ok(AsyncResult::async(Box::new(fut)))
|
||||
Ok(AsyncResult::future(Box::new(fut)))
|
||||
}
|
||||
}
|
||||
|
||||
// /// Trait defines object that could be registered as resource route
|
||||
pub(crate) trait RouteHandler<S>: 'static {
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse>;
|
||||
fn handle(&self, &HttpRequest<S>) -> AsyncResult<HttpResponse>;
|
||||
|
||||
fn has_default_resource(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn default_resource(&mut self, _: DefaultResource<S>) {}
|
||||
|
||||
fn finish(&mut self) {}
|
||||
}
|
||||
|
||||
/// Route handler wrapper for Handler
|
||||
@@ -408,8 +445,8 @@ where
|
||||
R: Responder + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
match self.h.handle(req.clone()).respond_to(&req) {
|
||||
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
match self.h.handle(req).respond_to(req) {
|
||||
Ok(reply) => reply.into(),
|
||||
Err(err) => AsyncResult::err(err.into()),
|
||||
}
|
||||
@@ -419,7 +456,7 @@ where
|
||||
/// Async route handler
|
||||
pub(crate) struct AsyncHandler<S, H, F, R, E>
|
||||
where
|
||||
H: Fn(HttpRequest<S>) -> F + 'static,
|
||||
H: Fn(&HttpRequest<S>) -> F + 'static,
|
||||
F: Future<Item = R, Error = E> + 'static,
|
||||
R: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
@@ -431,7 +468,7 @@ where
|
||||
|
||||
impl<S, H, F, R, E> AsyncHandler<S, H, F, R, E>
|
||||
where
|
||||
H: Fn(HttpRequest<S>) -> F + 'static,
|
||||
H: Fn(&HttpRequest<S>) -> F + 'static,
|
||||
F: Future<Item = R, Error = E> + 'static,
|
||||
R: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
@@ -447,14 +484,15 @@ where
|
||||
|
||||
impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
|
||||
where
|
||||
H: Fn(HttpRequest<S>) -> F + 'static,
|
||||
H: Fn(&HttpRequest<S>) -> F + 'static,
|
||||
F: Future<Item = R, Error = E> + 'static,
|
||||
R: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
let fut = (self.h)(req.clone()).map_err(|e| e.into()).then(move |r| {
|
||||
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
let req = req.clone();
|
||||
let fut = (self.h)(&req).map_err(|e| e.into()).then(move |r| {
|
||||
match r.respond_to(&req) {
|
||||
Ok(reply) => match reply.into().into() {
|
||||
AsyncResultItem::Ok(resp) => Either::A(ok(resp)),
|
||||
@@ -464,7 +502,7 @@ where
|
||||
Err(e) => Either::A(err(e)),
|
||||
}
|
||||
});
|
||||
AsyncResult::async(Box::new(fut))
|
||||
AsyncResult::future(Box::new(fut))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,10 +517,12 @@ where
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Path, State, http};
|
||||
/// use actix_web::{http, App, Path, State};
|
||||
///
|
||||
/// /// Application state
|
||||
/// struct MyApp {msg: &'static str}
|
||||
/// struct MyApp {
|
||||
/// msg: &'static str,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
@@ -490,15 +530,15 @@ where
|
||||
/// }
|
||||
///
|
||||
/// /// extract path info using serde
|
||||
/// fn index(data: (State<MyApp>, Path<Info>)) -> String {
|
||||
/// let (state, path) = data;
|
||||
/// fn index(state: State<MyApp>, path: Path<Info>) -> String {
|
||||
/// format!("{} {}!", state.msg, path.username)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::with_state(MyApp{msg: "Welcome"}).resource(
|
||||
/// "/{username}/index.html", // <- define path parameters
|
||||
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||
/// let app = App::with_state(MyApp { msg: "Welcome" }).resource(
|
||||
/// "/{username}/index.html", // <- define path parameters
|
||||
/// |r| r.method(http::Method::GET).with(index),
|
||||
/// ); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
pub struct State<S>(HttpRequest<S>);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,6 +35,7 @@ header! {
|
||||
}
|
||||
|
||||
impl Date {
|
||||
/// Create a date instance set to the current system time
|
||||
pub fn now() -> Date {
|
||||
Date(SystemTime::now().into())
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ pub use self::accept_language::AcceptLanguage;
|
||||
pub use self::accept::Accept;
|
||||
pub use self::allow::Allow;
|
||||
pub use self::cache_control::{CacheControl, CacheDirective};
|
||||
//pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam};
|
||||
pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam};
|
||||
pub use self::content_language::ContentLanguage;
|
||||
pub use self::content_range::{ContentRange, ContentRangeSpec};
|
||||
pub use self::content_type::ContentType;
|
||||
@@ -74,8 +74,6 @@ macro_rules! test_header {
|
||||
($id:ident, $raw:expr) => {
|
||||
#[test]
|
||||
fn $id() {
|
||||
#[allow(unused, deprecated)]
|
||||
use std::ascii::AsciiExt;
|
||||
use test;
|
||||
let raw = $raw;
|
||||
let a: Vec<Vec<u8>> = raw.iter().map(|x| x.to_vec()).collect();
|
||||
@@ -336,7 +334,7 @@ mod accept_language;
|
||||
mod accept;
|
||||
mod allow;
|
||||
mod cache_control;
|
||||
//mod content_disposition;
|
||||
mod content_disposition;
|
||||
mod content_language;
|
||||
mod content_range;
|
||||
mod content_type;
|
||||
|
||||
@@ -8,6 +8,7 @@ use bytes::{Bytes, BytesMut};
|
||||
use mime::Mime;
|
||||
use modhttp::header::GetAll;
|
||||
use modhttp::Error as HttpError;
|
||||
use percent_encoding;
|
||||
|
||||
pub use modhttp::header::*;
|
||||
|
||||
@@ -40,7 +41,7 @@ pub trait IntoHeaderValue: Sized {
|
||||
/// The type returned in the event of a conversion error.
|
||||
type Error: Into<HttpError>;
|
||||
|
||||
/// Cast from PyObject to a concrete Python object type.
|
||||
/// Try to convert value to a Header value.
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error>;
|
||||
}
|
||||
|
||||
@@ -127,16 +128,18 @@ pub enum ContentEncoding {
|
||||
|
||||
impl ContentEncoding {
|
||||
#[inline]
|
||||
pub fn is_compression(&self) -> bool {
|
||||
match *self {
|
||||
/// Is the content compressed?
|
||||
pub fn is_compression(self) -> bool {
|
||||
match self {
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match *self {
|
||||
/// Convert content encoding to string
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => "br",
|
||||
#[cfg(feature = "flate2")]
|
||||
@@ -149,8 +152,8 @@ impl ContentEncoding {
|
||||
|
||||
#[inline]
|
||||
/// default quality value
|
||||
pub fn quality(&self) -> f64 {
|
||||
match *self {
|
||||
pub fn quality(self) -> f64 {
|
||||
match self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => 1.1,
|
||||
#[cfg(feature = "flate2")]
|
||||
@@ -220,8 +223,7 @@ pub fn from_comma_delimited<T: FromStr>(
|
||||
.filter_map(|x| match x.trim() {
|
||||
"" => None,
|
||||
y => Some(y),
|
||||
})
|
||||
.filter_map(|x| x.trim().parse().ok()),
|
||||
}).filter_map(|x| x.trim().parse().ok()),
|
||||
)
|
||||
}
|
||||
Ok(result)
|
||||
@@ -257,3 +259,213 @@ where
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// From hyper v0.11.27 src/header/parsing.rs
|
||||
|
||||
/// The value part of an extended parameter consisting of three parts:
|
||||
/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`),
|
||||
/// and a character sequence representing the actual value (`value`), separated by single quote
|
||||
/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ExtendedValue {
|
||||
/// The character set that is used to encode the `value` to a string.
|
||||
pub charset: Charset,
|
||||
/// The human language details of the `value`, if available.
|
||||
pub language_tag: Option<LanguageTag>,
|
||||
/// The parameter value, as expressed in octets.
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Parses extended header parameter values (`ext-value`), as defined in
|
||||
/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
|
||||
///
|
||||
/// Extended values are denoted by parameter names that end with `*`.
|
||||
///
|
||||
/// ## ABNF
|
||||
///
|
||||
/// ```text
|
||||
/// ext-value = charset "'" [ language ] "'" value-chars
|
||||
/// ; like RFC 2231's <extended-initial-value>
|
||||
/// ; (see [RFC2231], Section 7)
|
||||
///
|
||||
/// charset = "UTF-8" / "ISO-8859-1" / mime-charset
|
||||
///
|
||||
/// mime-charset = 1*mime-charsetc
|
||||
/// mime-charsetc = ALPHA / DIGIT
|
||||
/// / "!" / "#" / "$" / "%" / "&"
|
||||
/// / "+" / "-" / "^" / "_" / "`"
|
||||
/// / "{" / "}" / "~"
|
||||
/// ; as <mime-charset> in Section 2.3 of [RFC2978]
|
||||
/// ; except that the single quote is not included
|
||||
/// ; SHOULD be registered in the IANA charset registry
|
||||
///
|
||||
/// language = <Language-Tag, defined in [RFC5646], Section 2.1>
|
||||
///
|
||||
/// value-chars = *( pct-encoded / attr-char )
|
||||
///
|
||||
/// pct-encoded = "%" HEXDIG HEXDIG
|
||||
/// ; see [RFC3986], Section 2.1
|
||||
///
|
||||
/// attr-char = ALPHA / DIGIT
|
||||
/// / "!" / "#" / "$" / "&" / "+" / "-" / "."
|
||||
/// / "^" / "_" / "`" / "|" / "~"
|
||||
/// ; token except ( "*" / "'" / "%" )
|
||||
/// ```
|
||||
pub fn parse_extended_value(val: &str) -> Result<ExtendedValue, ::error::ParseError> {
|
||||
// Break into three pieces separated by the single-quote character
|
||||
let mut parts = val.splitn(3, '\'');
|
||||
|
||||
// Interpret the first piece as a Charset
|
||||
let charset: Charset = match parts.next() {
|
||||
None => return Err(::error::ParseError::Header),
|
||||
Some(n) => FromStr::from_str(n).map_err(|_| ::error::ParseError::Header)?,
|
||||
};
|
||||
|
||||
// Interpret the second piece as a language tag
|
||||
let language_tag: Option<LanguageTag> = match parts.next() {
|
||||
None => return Err(::error::ParseError::Header),
|
||||
Some("") => None,
|
||||
Some(s) => match s.parse() {
|
||||
Ok(lt) => Some(lt),
|
||||
Err(_) => return Err(::error::ParseError::Header),
|
||||
},
|
||||
};
|
||||
|
||||
// Interpret the third piece as a sequence of value characters
|
||||
let value: Vec<u8> = match parts.next() {
|
||||
None => return Err(::error::ParseError::Header),
|
||||
Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(),
|
||||
};
|
||||
|
||||
Ok(ExtendedValue {
|
||||
value,
|
||||
charset,
|
||||
language_tag,
|
||||
})
|
||||
}
|
||||
|
||||
impl fmt::Display for ExtendedValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let encoded_value = percent_encoding::percent_encode(
|
||||
&self.value[..],
|
||||
self::percent_encoding_http::HTTP_VALUE,
|
||||
);
|
||||
if let Some(ref lang) = self.language_tag {
|
||||
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
|
||||
} else {
|
||||
write!(f, "{}''{}", self.charset, encoded_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Percent encode a sequence of bytes with a character set defined in
|
||||
/// [https://tools.ietf.org/html/rfc5987#section-3.2][url]
|
||||
///
|
||||
/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2
|
||||
pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result {
|
||||
let encoded =
|
||||
percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE);
|
||||
fmt::Display::fmt(&encoded, f)
|
||||
}
|
||||
mod percent_encoding_http {
|
||||
use percent_encoding;
|
||||
|
||||
// internal module because macro is hard-coded to make a public item
|
||||
// but we don't want to public export this item
|
||||
define_encode_set! {
|
||||
// This encode set is used for HTTP header values and is defined at
|
||||
// https://tools.ietf.org/html/rfc5987#section-3.2
|
||||
pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | {
|
||||
' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?',
|
||||
'[', '\\', ']', '{', '}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{parse_extended_value, ExtendedValue};
|
||||
use header::shared::Charset;
|
||||
use language_tags::LanguageTag;
|
||||
|
||||
#[test]
|
||||
fn test_parse_extended_value_with_encoding_and_language_tag() {
|
||||
let expected_language_tag = "en".parse::<LanguageTag>().unwrap();
|
||||
// RFC 5987, Section 3.2.2
|
||||
// Extended notation, using the Unicode character U+00A3 (POUND SIGN)
|
||||
let result = parse_extended_value("iso-8859-1'en'%A3%20rates");
|
||||
assert!(result.is_ok());
|
||||
let extended_value = result.unwrap();
|
||||
assert_eq!(Charset::Iso_8859_1, extended_value.charset);
|
||||
assert!(extended_value.language_tag.is_some());
|
||||
assert_eq!(expected_language_tag, extended_value.language_tag.unwrap());
|
||||
assert_eq!(
|
||||
vec![163, b' ', b'r', b'a', b't', b'e', b's'],
|
||||
extended_value.value
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_extended_value_with_encoding() {
|
||||
// RFC 5987, Section 3.2.2
|
||||
// Extended notation, using the Unicode characters U+00A3 (POUND SIGN)
|
||||
// and U+20AC (EURO SIGN)
|
||||
let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
|
||||
assert!(result.is_ok());
|
||||
let extended_value = result.unwrap();
|
||||
assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset);
|
||||
assert!(extended_value.language_tag.is_none());
|
||||
assert_eq!(
|
||||
vec![
|
||||
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
||||
b't', b'e', b's',
|
||||
],
|
||||
extended_value.value
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_extended_value_missing_language_tag_and_encoding() {
|
||||
// From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2
|
||||
let result = parse_extended_value("foo%20bar.html");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_extended_value_partially_formatted() {
|
||||
let result = parse_extended_value("UTF-8'missing third part");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_extended_value_partially_formatted_blank() {
|
||||
let result = parse_extended_value("blank second part'");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fmt_extended_value_with_encoding_and_language_tag() {
|
||||
let extended_value = ExtendedValue {
|
||||
charset: Charset::Iso_8859_1,
|
||||
language_tag: Some("en".parse().expect("Could not parse language tag")),
|
||||
value: vec![163, b' ', b'r', b'a', b't', b'e', b's'],
|
||||
};
|
||||
assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fmt_extended_value_with_encoding() {
|
||||
let extended_value = ExtendedValue {
|
||||
charset: Charset::Ext("UTF-8".to_string()),
|
||||
language_tag: None,
|
||||
value: vec![
|
||||
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
||||
b't', b'e', b's',
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
"UTF-8''%C2%A3%20and%20%E2%82%AC%20rates",
|
||||
format!("{}", extended_value)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![allow(unused, deprecated)]
|
||||
use std::ascii::AsciiExt;
|
||||
use std::fmt::{self, Display};
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -68,7 +66,7 @@ pub enum Charset {
|
||||
}
|
||||
|
||||
impl Charset {
|
||||
fn name(&self) -> &str {
|
||||
fn label(&self) -> &str {
|
||||
match *self {
|
||||
Us_Ascii => "US-ASCII",
|
||||
Iso_8859_1 => "ISO-8859-1",
|
||||
@@ -92,7 +90,7 @@ impl Charset {
|
||||
Iso_8859_8_E => "ISO-8859-8-E",
|
||||
Iso_8859_8_I => "ISO-8859-8-I",
|
||||
Gb2312 => "GB2312",
|
||||
Big5 => "5",
|
||||
Big5 => "big5",
|
||||
Koi8_R => "KOI8-R",
|
||||
Ext(ref s) => s,
|
||||
}
|
||||
@@ -101,7 +99,7 @@ impl Charset {
|
||||
|
||||
impl Display for Charset {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(self.name())
|
||||
f.write_str(self.label())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +129,7 @@ impl FromStr for Charset {
|
||||
"ISO-8859-8-E" => Iso_8859_8_E,
|
||||
"ISO-8859-8-I" => Iso_8859_8_I,
|
||||
"GB2312" => Gb2312,
|
||||
"5" => Big5,
|
||||
"big5" => Big5,
|
||||
"KOI8-R" => Koi8_R,
|
||||
s => Ext(s.to_owned()),
|
||||
})
|
||||
|
||||
@@ -161,7 +161,7 @@ impl IntoHeaderValue for EntityTag {
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
let mut wrt = Writer::new();
|
||||
write!(wrt, "{}", self).unwrap();
|
||||
unsafe { Ok(HeaderValue::from_shared_unchecked(wrt.take())) }
|
||||
HeaderValue::from_shared(wrt.take())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,11 +64,7 @@ impl IntoHeaderValue for HttpDate {
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
let mut wrt = BytesMut::with_capacity(29).writer();
|
||||
write!(wrt, "{}", self.0.rfc822()).unwrap();
|
||||
unsafe {
|
||||
Ok(HeaderValue::from_shared_unchecked(
|
||||
wrt.get_mut().take().freeze(),
|
||||
))
|
||||
}
|
||||
HeaderValue::from_shared(wrt.get_mut().take().freeze())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![allow(unused, deprecated)]
|
||||
use std::ascii::AsciiExt;
|
||||
use std::cmp;
|
||||
use std::default::Default;
|
||||
use std::fmt;
|
||||
|
||||
185
src/helpers.rs
185
src/helpers.rs
@@ -36,7 +36,7 @@ use httpresponse::HttpResponse;
|
||||
/// # use actix_web::*;
|
||||
/// use actix_web::http::NormalizePath;
|
||||
///
|
||||
/// # fn index(req: HttpRequest) -> HttpResponse {
|
||||
/// # fn index(req: &HttpRequest) -> HttpResponse {
|
||||
/// # HttpResponse::Ok().into()
|
||||
/// # }
|
||||
/// fn main() {
|
||||
@@ -86,57 +86,41 @@ impl NormalizePath {
|
||||
impl<S> Handler<S> for NormalizePath {
|
||||
type Result = HttpResponse;
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
if let Some(router) = req.router() {
|
||||
let query = req.query_string();
|
||||
if self.merge {
|
||||
// merge slashes
|
||||
let p = self.re_merge.replace_all(req.path(), "/");
|
||||
if p.len() != req.path().len() {
|
||||
if router.has_route(p.as_ref()) {
|
||||
fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
|
||||
let query = req.query_string();
|
||||
if self.merge {
|
||||
// merge slashes
|
||||
let p = self.re_merge.replace_all(req.path(), "/");
|
||||
if p.len() != req.path().len() {
|
||||
if req.resource().has_prefixed_resource(p.as_ref()) {
|
||||
let p = if !query.is_empty() {
|
||||
p + "?" + query
|
||||
} else {
|
||||
p
|
||||
};
|
||||
return HttpResponse::build(self.redirect)
|
||||
.header(header::LOCATION, p.as_ref())
|
||||
.finish();
|
||||
}
|
||||
// merge slashes and append trailing slash
|
||||
if self.append && !p.ends_with('/') {
|
||||
let p = p.as_ref().to_owned() + "/";
|
||||
if req.resource().has_prefixed_resource(&p) {
|
||||
let p = if !query.is_empty() {
|
||||
p + "?" + query
|
||||
} else {
|
||||
p
|
||||
};
|
||||
return HttpResponse::build(self.redirect)
|
||||
.header(header::LOCATION, p.as_ref())
|
||||
.header(header::LOCATION, p.as_str())
|
||||
.finish();
|
||||
}
|
||||
// merge slashes and append trailing slash
|
||||
if self.append && !p.ends_with('/') {
|
||||
let p = p.as_ref().to_owned() + "/";
|
||||
if router.has_route(&p) {
|
||||
let p = if !query.is_empty() {
|
||||
p + "?" + query
|
||||
} else {
|
||||
p
|
||||
};
|
||||
return HttpResponse::build(self.redirect)
|
||||
.header(header::LOCATION, p.as_str())
|
||||
.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to remove trailing slash
|
||||
if p.ends_with('/') {
|
||||
let p = p.as_ref().trim_right_matches('/');
|
||||
if router.has_route(p) {
|
||||
let mut req = HttpResponse::build(self.redirect);
|
||||
return if !query.is_empty() {
|
||||
req.header(
|
||||
header::LOCATION,
|
||||
(p.to_owned() + "?" + query).as_str(),
|
||||
)
|
||||
} else {
|
||||
req.header(header::LOCATION, p)
|
||||
}.finish();
|
||||
}
|
||||
}
|
||||
} else if p.ends_with('/') {
|
||||
// try to remove trailing slash
|
||||
// try to remove trailing slash
|
||||
if p.ends_with('/') {
|
||||
let p = p.as_ref().trim_right_matches('/');
|
||||
if router.has_route(p) {
|
||||
if req.resource().has_prefixed_resource(p) {
|
||||
let mut req = HttpResponse::build(self.redirect);
|
||||
return if !query.is_empty() {
|
||||
req.header(
|
||||
@@ -148,22 +132,36 @@ impl<S> Handler<S> for NormalizePath {
|
||||
}.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
// append trailing slash
|
||||
if self.append && !req.path().ends_with('/') {
|
||||
let p = req.path().to_owned() + "/";
|
||||
if router.has_route(&p) {
|
||||
let p = if !query.is_empty() {
|
||||
p + "?" + query
|
||||
} else if p.ends_with('/') {
|
||||
// try to remove trailing slash
|
||||
let p = p.as_ref().trim_right_matches('/');
|
||||
if req.resource().has_prefixed_resource(p) {
|
||||
let mut req = HttpResponse::build(self.redirect);
|
||||
return if !query.is_empty() {
|
||||
req.header(
|
||||
header::LOCATION,
|
||||
(p.to_owned() + "?" + query).as_str(),
|
||||
)
|
||||
} else {
|
||||
p
|
||||
};
|
||||
return HttpResponse::build(self.redirect)
|
||||
.header(header::LOCATION, p.as_str())
|
||||
.finish();
|
||||
req.header(header::LOCATION, p)
|
||||
}.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
// append trailing slash
|
||||
if self.append && !req.path().ends_with('/') {
|
||||
let p = req.path().to_owned() + "/";
|
||||
if req.resource().has_prefixed_resource(&p) {
|
||||
let p = if !query.is_empty() {
|
||||
p + "?" + query
|
||||
} else {
|
||||
p
|
||||
};
|
||||
return HttpResponse::build(self.redirect)
|
||||
.header(header::LOCATION, p.as_str())
|
||||
.finish();
|
||||
}
|
||||
}
|
||||
HttpResponse::new(self.not_found)
|
||||
}
|
||||
}
|
||||
@@ -175,13 +173,13 @@ mod tests {
|
||||
use http::{header, Method};
|
||||
use test::TestRequest;
|
||||
|
||||
fn index(_req: HttpRequest) -> HttpResponse {
|
||||
fn index(_req: &HttpRequest) -> HttpResponse {
|
||||
HttpResponse::new(StatusCode::OK)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_path_trailing_slashes() {
|
||||
let mut app = App::new()
|
||||
let app = App::new()
|
||||
.resource("/resource1", |r| r.method(Method::GET).f(index))
|
||||
.resource("/resource2/", |r| r.method(Method::GET).f(index))
|
||||
.default_resource(|r| r.h(NormalizePath::default()))
|
||||
@@ -207,9 +205,59 @@ mod tests {
|
||||
("/resource2/?p1=1&p2=2", "", StatusCode::OK),
|
||||
];
|
||||
for (path, target, code) in params {
|
||||
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||
let req = TestRequest::with_uri(path).request();
|
||||
let resp = app.run(req);
|
||||
let r = resp.as_msg();
|
||||
let r = &resp.as_msg();
|
||||
assert_eq!(r.status(), code);
|
||||
if !target.is_empty() {
|
||||
assert_eq!(
|
||||
target,
|
||||
r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefixed_normalize_path_trailing_slashes() {
|
||||
let app = App::new()
|
||||
.prefix("/test")
|
||||
.resource("/resource1", |r| r.method(Method::GET).f(index))
|
||||
.resource("/resource2/", |r| r.method(Method::GET).f(index))
|
||||
.default_resource(|r| r.h(NormalizePath::default()))
|
||||
.finish();
|
||||
|
||||
// trailing slashes
|
||||
let params = vec![
|
||||
("/test/resource1", "", StatusCode::OK),
|
||||
(
|
||||
"/test/resource1/",
|
||||
"/test/resource1",
|
||||
StatusCode::MOVED_PERMANENTLY,
|
||||
),
|
||||
(
|
||||
"/test/resource2",
|
||||
"/test/resource2/",
|
||||
StatusCode::MOVED_PERMANENTLY,
|
||||
),
|
||||
("/test/resource2/", "", StatusCode::OK),
|
||||
("/test/resource1?p1=1&p2=2", "", StatusCode::OK),
|
||||
(
|
||||
"/test/resource1/?p1=1&p2=2",
|
||||
"/test/resource1?p1=1&p2=2",
|
||||
StatusCode::MOVED_PERMANENTLY,
|
||||
),
|
||||
(
|
||||
"/test/resource2?p1=1&p2=2",
|
||||
"/test/resource2/?p1=1&p2=2",
|
||||
StatusCode::MOVED_PERMANENTLY,
|
||||
),
|
||||
("/test/resource2/?p1=1&p2=2", "", StatusCode::OK),
|
||||
];
|
||||
for (path, target, code) in params {
|
||||
let req = TestRequest::with_uri(path).request();
|
||||
let resp = app.run(req);
|
||||
let r = &resp.as_msg();
|
||||
assert_eq!(r.status(), code);
|
||||
if !target.is_empty() {
|
||||
assert_eq!(
|
||||
@@ -222,7 +270,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_normalize_path_trailing_slashes_disabled() {
|
||||
let mut app = App::new()
|
||||
let app = App::new()
|
||||
.resource("/resource1", |r| r.method(Method::GET).f(index))
|
||||
.resource("/resource2/", |r| r.method(Method::GET).f(index))
|
||||
.default_resource(|r| {
|
||||
@@ -231,8 +279,7 @@ mod tests {
|
||||
true,
|
||||
StatusCode::MOVED_PERMANENTLY,
|
||||
))
|
||||
})
|
||||
.finish();
|
||||
}).finish();
|
||||
|
||||
// trailing slashes
|
||||
let params = vec![
|
||||
@@ -246,16 +293,16 @@ mod tests {
|
||||
("/resource2/?p1=1&p2=2", StatusCode::OK),
|
||||
];
|
||||
for (path, code) in params {
|
||||
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||
let req = TestRequest::with_uri(path).request();
|
||||
let resp = app.run(req);
|
||||
let r = resp.as_msg();
|
||||
let r = &resp.as_msg();
|
||||
assert_eq!(r.status(), code);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_path_merge_slashes() {
|
||||
let mut app = App::new()
|
||||
let app = App::new()
|
||||
.resource("/resource1", |r| r.method(Method::GET).f(index))
|
||||
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
|
||||
.default_resource(|r| r.h(NormalizePath::default()))
|
||||
@@ -329,9 +376,9 @@ mod tests {
|
||||
),
|
||||
];
|
||||
for (path, target, code) in params {
|
||||
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||
let req = TestRequest::with_uri(path).request();
|
||||
let resp = app.run(req);
|
||||
let r = resp.as_msg();
|
||||
let r = &resp.as_msg();
|
||||
assert_eq!(r.status(), code);
|
||||
if !target.is_empty() {
|
||||
assert_eq!(
|
||||
@@ -344,7 +391,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_normalize_path_merge_and_append_slashes() {
|
||||
let mut app = App::new()
|
||||
let app = App::new()
|
||||
.resource("/resource1", |r| r.method(Method::GET).f(index))
|
||||
.resource("/resource2/", |r| r.method(Method::GET).f(index))
|
||||
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
|
||||
@@ -509,9 +556,9 @@ mod tests {
|
||||
),
|
||||
];
|
||||
for (path, target, code) in params {
|
||||
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||
let req = TestRequest::with_uri(path).request();
|
||||
let resp = app.run(req);
|
||||
let r = resp.as_msg();
|
||||
let r = &resp.as_msg();
|
||||
assert_eq!(r.status(), code);
|
||||
if !target.is_empty() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -5,7 +5,7 @@ use httpresponse::{HttpResponse, HttpResponseBuilder};
|
||||
|
||||
macro_rules! STATIC_RESP {
|
||||
($name:ident, $status:expr) => {
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_snake_case, missing_docs)]
|
||||
pub fn $name() -> HttpResponseBuilder {
|
||||
HttpResponse::build($status)
|
||||
}
|
||||
|
||||
@@ -3,26 +3,31 @@ use encoding::all::UTF_8;
|
||||
use encoding::label::encoding_from_whatwg_label;
|
||||
use encoding::types::{DecoderTrap, Encoding};
|
||||
use encoding::EncodingRef;
|
||||
use futures::{Future, Poll, Stream};
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use http::{header, HeaderMap};
|
||||
use http_range::HttpRange;
|
||||
use mime::Mime;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_urlencoded;
|
||||
use std::str;
|
||||
|
||||
use error::{
|
||||
ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError,
|
||||
ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError,
|
||||
};
|
||||
use header::Header;
|
||||
use json::JsonBody;
|
||||
use multipart::Multipart;
|
||||
|
||||
/// Trait that implements general purpose operations on http messages
|
||||
pub trait HttpMessage {
|
||||
pub trait HttpMessage: Sized {
|
||||
/// Type of message payload stream
|
||||
type Stream: Stream<Item = Bytes, Error = PayloadError> + Sized;
|
||||
|
||||
/// Read the message headers.
|
||||
fn headers(&self) -> &HeaderMap;
|
||||
|
||||
/// Message payload stream
|
||||
fn payload(&self) -> Self::Stream;
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Get a header
|
||||
fn get_header<H: Header>(&self) -> Option<H>
|
||||
@@ -94,17 +99,6 @@ pub trait HttpMessage {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses Range HTTP header string as per RFC 2616.
|
||||
/// `size` is full size of response (file).
|
||||
fn range(&self, size: u64) -> Result<Vec<HttpRange>, HttpRangeError> {
|
||||
if let Some(range) = self.headers().get(header::RANGE) {
|
||||
HttpRange::parse(unsafe { str::from_utf8_unchecked(range.as_bytes()) }, size)
|
||||
.map_err(|e| e.into())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// Load http message body.
|
||||
///
|
||||
/// By default only 256Kb payload reads to a memory, then
|
||||
@@ -118,10 +112,11 @@ pub trait HttpMessage {
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{
|
||||
/// AsyncResponder, FutureResponse, HttpMessage, HttpRequest, HttpResponse,
|
||||
/// };
|
||||
/// use bytes::Bytes;
|
||||
/// use futures::future::Future;
|
||||
/// use actix_web::{HttpMessage, HttpRequest, HttpResponse,
|
||||
/// FutureResponse, AsyncResponder};
|
||||
///
|
||||
/// fn index(mut req: HttpRequest) -> FutureResponse<HttpResponse> {
|
||||
/// req.body() // <- get Body future
|
||||
@@ -134,10 +129,7 @@ pub trait HttpMessage {
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
fn body(self) -> MessageBody<Self>
|
||||
where
|
||||
Self: Stream<Item = Bytes, Error = PayloadError> + Sized,
|
||||
{
|
||||
fn body(&self) -> MessageBody<Self> {
|
||||
MessageBody::new(self)
|
||||
}
|
||||
|
||||
@@ -157,7 +149,7 @@ pub trait HttpMessage {
|
||||
/// # extern crate futures;
|
||||
/// # use futures::Future;
|
||||
/// # use std::collections::HashMap;
|
||||
/// use actix_web::{HttpMessage, HttpRequest, HttpResponse, FutureResponse};
|
||||
/// use actix_web::{FutureResponse, HttpMessage, HttpRequest, HttpResponse};
|
||||
///
|
||||
/// fn index(mut req: HttpRequest) -> FutureResponse<HttpResponse> {
|
||||
/// Box::new(
|
||||
@@ -166,14 +158,12 @@ pub trait HttpMessage {
|
||||
/// .and_then(|params| { // <- url encoded parameters
|
||||
/// println!("==== BODY ==== {:?}", params);
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// }))
|
||||
/// }),
|
||||
/// )
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
fn urlencoded<T: DeserializeOwned>(self) -> UrlEncoded<Self, T>
|
||||
where
|
||||
Self: Stream<Item = Bytes, Error = PayloadError> + Sized,
|
||||
{
|
||||
fn urlencoded<T: DeserializeOwned>(&self) -> UrlEncoded<Self, T> {
|
||||
UrlEncoded::new(self)
|
||||
}
|
||||
|
||||
@@ -192,14 +182,14 @@ pub trait HttpMessage {
|
||||
/// # extern crate futures;
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::*;
|
||||
/// use futures::future::{Future, ok};
|
||||
/// use futures::future::{ok, Future};
|
||||
///
|
||||
/// #[derive(Deserialize, Debug)]
|
||||
/// struct MyObj {
|
||||
/// name: String,
|
||||
/// }
|
||||
///
|
||||
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
|
||||
/// req.json() // <- get JsonBody future
|
||||
/// .from_err()
|
||||
/// .and_then(|val: MyObj| { // <- deserialized value
|
||||
@@ -209,10 +199,7 @@ pub trait HttpMessage {
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
fn json<T: DeserializeOwned>(self) -> JsonBody<Self, T>
|
||||
where
|
||||
Self: Stream<Item = Bytes, Error = PayloadError> + Sized,
|
||||
{
|
||||
fn json<T: DeserializeOwned>(&self) -> JsonBody<Self, T> {
|
||||
JsonBody::new(self)
|
||||
}
|
||||
|
||||
@@ -223,16 +210,16 @@ pub trait HttpMessage {
|
||||
/// ## Server example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate env_logger;
|
||||
/// # extern crate futures;
|
||||
/// # extern crate actix;
|
||||
/// # use std::str;
|
||||
/// # use actix::*;
|
||||
/// # use actix_web::*;
|
||||
/// # use actix::FinishStream;
|
||||
/// # use futures::{Future, Stream};
|
||||
/// # use futures::future::{ok, result, Either};
|
||||
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
|
||||
/// req.multipart().from_err() // <- get multipart stream for current request
|
||||
/// .and_then(|item| match item { // <- iterate over multipart items
|
||||
/// multipart::MultipartItem::Field(field) => {
|
||||
@@ -252,29 +239,191 @@ pub trait HttpMessage {
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
fn multipart(self) -> Multipart<Self>
|
||||
where
|
||||
Self: Stream<Item = Bytes, Error = PayloadError> + Sized,
|
||||
{
|
||||
fn multipart(&self) -> Multipart<Self::Stream> {
|
||||
let boundary = Multipart::boundary(self.headers());
|
||||
Multipart::new(boundary, self)
|
||||
Multipart::new(boundary, self.payload())
|
||||
}
|
||||
|
||||
/// Return stream of lines.
|
||||
fn readlines(&self) -> Readlines<Self> {
|
||||
Readlines::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stream to read request line by line.
|
||||
pub struct Readlines<T: HttpMessage> {
|
||||
stream: T::Stream,
|
||||
buff: BytesMut,
|
||||
limit: usize,
|
||||
checked_buff: bool,
|
||||
encoding: EncodingRef,
|
||||
err: Option<ReadlinesError>,
|
||||
}
|
||||
|
||||
impl<T: HttpMessage> Readlines<T> {
|
||||
/// Create a new stream to read request line by line.
|
||||
fn new(req: &T) -> Self {
|
||||
let encoding = match req.encoding() {
|
||||
Ok(enc) => enc,
|
||||
Err(err) => return Self::err(req, err.into()),
|
||||
};
|
||||
|
||||
Readlines {
|
||||
stream: req.payload(),
|
||||
buff: BytesMut::with_capacity(262_144),
|
||||
limit: 262_144,
|
||||
checked_buff: true,
|
||||
err: None,
|
||||
encoding,
|
||||
}
|
||||
}
|
||||
|
||||
/// Change max line size. By default max size is 256Kb
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
self.limit = limit;
|
||||
self
|
||||
}
|
||||
|
||||
fn err(req: &T, err: ReadlinesError) -> Self {
|
||||
Readlines {
|
||||
stream: req.payload(),
|
||||
buff: BytesMut::new(),
|
||||
limit: 262_144,
|
||||
checked_buff: true,
|
||||
encoding: UTF_8,
|
||||
err: Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HttpMessage + 'static> Stream for Readlines<T> {
|
||||
type Item = String;
|
||||
type Error = ReadlinesError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if let Some(err) = self.err.take() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// check if there is a newline in the buffer
|
||||
if !self.checked_buff {
|
||||
let mut found: Option<usize> = None;
|
||||
for (ind, b) in self.buff.iter().enumerate() {
|
||||
if *b == b'\n' {
|
||||
found = Some(ind);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(ind) = found {
|
||||
// check if line is longer than limit
|
||||
if ind + 1 > self.limit {
|
||||
return Err(ReadlinesError::LimitOverflow);
|
||||
}
|
||||
let enc: *const Encoding = self.encoding as *const Encoding;
|
||||
let line = if enc == UTF_8 {
|
||||
str::from_utf8(&self.buff.split_to(ind + 1))
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
.to_owned()
|
||||
} else {
|
||||
self.encoding
|
||||
.decode(&self.buff.split_to(ind + 1), DecoderTrap::Strict)
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
};
|
||||
return Ok(Async::Ready(Some(line)));
|
||||
}
|
||||
self.checked_buff = true;
|
||||
}
|
||||
// poll req for more bytes
|
||||
match self.stream.poll() {
|
||||
Ok(Async::Ready(Some(mut bytes))) => {
|
||||
// check if there is a newline in bytes
|
||||
let mut found: Option<usize> = None;
|
||||
for (ind, b) in bytes.iter().enumerate() {
|
||||
if *b == b'\n' {
|
||||
found = Some(ind);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(ind) = found {
|
||||
// check if line is longer than limit
|
||||
if ind + 1 > self.limit {
|
||||
return Err(ReadlinesError::LimitOverflow);
|
||||
}
|
||||
let enc: *const Encoding = self.encoding as *const Encoding;
|
||||
let line = if enc == UTF_8 {
|
||||
str::from_utf8(&bytes.split_to(ind + 1))
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
.to_owned()
|
||||
} else {
|
||||
self.encoding
|
||||
.decode(&bytes.split_to(ind + 1), DecoderTrap::Strict)
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
};
|
||||
// extend buffer with rest of the bytes;
|
||||
self.buff.extend_from_slice(&bytes);
|
||||
self.checked_buff = false;
|
||||
return Ok(Async::Ready(Some(line)));
|
||||
}
|
||||
self.buff.extend_from_slice(&bytes);
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Ok(Async::Ready(None)) => {
|
||||
if self.buff.is_empty() {
|
||||
return Ok(Async::Ready(None));
|
||||
}
|
||||
if self.buff.len() > self.limit {
|
||||
return Err(ReadlinesError::LimitOverflow);
|
||||
}
|
||||
let enc: *const Encoding = self.encoding as *const Encoding;
|
||||
let line = if enc == UTF_8 {
|
||||
str::from_utf8(&self.buff)
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
.to_owned()
|
||||
} else {
|
||||
self.encoding
|
||||
.decode(&self.buff, DecoderTrap::Strict)
|
||||
.map_err(|_| ReadlinesError::EncodingError)?
|
||||
};
|
||||
self.buff.clear();
|
||||
Ok(Async::Ready(Some(line)))
|
||||
}
|
||||
Err(e) => Err(ReadlinesError::from(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Future that resolves to a complete http message body.
|
||||
pub struct MessageBody<T> {
|
||||
pub struct MessageBody<T: HttpMessage> {
|
||||
limit: usize,
|
||||
req: Option<T>,
|
||||
length: Option<usize>,
|
||||
stream: Option<T::Stream>,
|
||||
err: Option<PayloadError>,
|
||||
fut: Option<Box<Future<Item = Bytes, Error = PayloadError>>>,
|
||||
}
|
||||
|
||||
impl<T> MessageBody<T> {
|
||||
/// Create `RequestBody` for request.
|
||||
pub fn new(req: T) -> MessageBody<T> {
|
||||
impl<T: HttpMessage> MessageBody<T> {
|
||||
/// Create `MessageBody` for request.
|
||||
pub fn new(req: &T) -> MessageBody<T> {
|
||||
let mut len = None;
|
||||
if let Some(l) = req.headers().get(header::CONTENT_LENGTH) {
|
||||
if let Ok(s) = l.to_str() {
|
||||
if let Ok(l) = s.parse::<usize>() {
|
||||
len = Some(l)
|
||||
} else {
|
||||
return Self::err(PayloadError::UnknownLength);
|
||||
}
|
||||
} else {
|
||||
return Self::err(PayloadError::UnknownLength);
|
||||
}
|
||||
}
|
||||
|
||||
MessageBody {
|
||||
limit: 262_144,
|
||||
req: Some(req),
|
||||
length: len,
|
||||
stream: Some(req.payload()),
|
||||
fut: None,
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,67 +432,113 @@ impl<T> MessageBody<T> {
|
||||
self.limit = limit;
|
||||
self
|
||||
}
|
||||
|
||||
fn err(e: PayloadError) -> Self {
|
||||
MessageBody {
|
||||
stream: None,
|
||||
limit: 262_144,
|
||||
fut: None,
|
||||
err: Some(e),
|
||||
length: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Future for MessageBody<T>
|
||||
where
|
||||
T: HttpMessage + Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
T: HttpMessage + 'static,
|
||||
{
|
||||
type Item = Bytes;
|
||||
type Error = PayloadError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(req) = self.req.take() {
|
||||
if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
|
||||
if let Ok(s) = len.to_str() {
|
||||
if let Ok(len) = s.parse::<usize>() {
|
||||
if len > self.limit {
|
||||
return Err(PayloadError::Overflow);
|
||||
}
|
||||
} else {
|
||||
return Err(PayloadError::UnknownLength);
|
||||
}
|
||||
} else {
|
||||
return Err(PayloadError::UnknownLength);
|
||||
}
|
||||
}
|
||||
|
||||
// future
|
||||
let limit = self.limit;
|
||||
self.fut = Some(Box::new(
|
||||
req.from_err()
|
||||
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(PayloadError::Overflow)
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok(body)
|
||||
}
|
||||
})
|
||||
.map(|body| body.freeze()),
|
||||
));
|
||||
if let Some(ref mut fut) = self.fut {
|
||||
return fut.poll();
|
||||
}
|
||||
|
||||
self.fut
|
||||
.as_mut()
|
||||
.expect("UrlEncoded could not be used second time")
|
||||
.poll()
|
||||
if let Some(err) = self.err.take() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
if let Some(len) = self.length.take() {
|
||||
if len > self.limit {
|
||||
return Err(PayloadError::Overflow);
|
||||
}
|
||||
}
|
||||
|
||||
// future
|
||||
let limit = self.limit;
|
||||
self.fut = Some(Box::new(
|
||||
self.stream
|
||||
.take()
|
||||
.expect("Can not be used second time")
|
||||
.from_err()
|
||||
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(PayloadError::Overflow)
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok(body)
|
||||
}
|
||||
}).map(|body| body.freeze()),
|
||||
));
|
||||
self.poll()
|
||||
}
|
||||
}
|
||||
|
||||
/// Future that resolves to a parsed urlencoded values.
|
||||
pub struct UrlEncoded<T, U> {
|
||||
req: Option<T>,
|
||||
pub struct UrlEncoded<T: HttpMessage, U> {
|
||||
stream: Option<T::Stream>,
|
||||
limit: usize,
|
||||
length: Option<usize>,
|
||||
encoding: EncodingRef,
|
||||
err: Option<UrlencodedError>,
|
||||
fut: Option<Box<Future<Item = U, Error = UrlencodedError>>>,
|
||||
}
|
||||
|
||||
impl<T, U> UrlEncoded<T, U> {
|
||||
pub fn new(req: T) -> UrlEncoded<T, U> {
|
||||
impl<T: HttpMessage, U> UrlEncoded<T, U> {
|
||||
/// Create a new future to URL encode a request
|
||||
pub fn new(req: &T) -> UrlEncoded<T, U> {
|
||||
// check content type
|
||||
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
|
||||
return Self::err(UrlencodedError::ContentType);
|
||||
}
|
||||
let encoding = match req.encoding() {
|
||||
Ok(enc) => enc,
|
||||
Err(_) => return Self::err(UrlencodedError::ContentType),
|
||||
};
|
||||
|
||||
let mut len = None;
|
||||
if let Some(l) = req.headers().get(header::CONTENT_LENGTH) {
|
||||
if let Ok(s) = l.to_str() {
|
||||
if let Ok(l) = s.parse::<usize>() {
|
||||
len = Some(l)
|
||||
} else {
|
||||
return Self::err(UrlencodedError::UnknownLength);
|
||||
}
|
||||
} else {
|
||||
return Self::err(UrlencodedError::UnknownLength);
|
||||
}
|
||||
};
|
||||
|
||||
UrlEncoded {
|
||||
req: Some(req),
|
||||
encoding,
|
||||
stream: Some(req.payload()),
|
||||
limit: 262_144,
|
||||
length: len,
|
||||
fut: None,
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn err(e: UrlencodedError) -> Self {
|
||||
UrlEncoded {
|
||||
stream: None,
|
||||
limit: 262_144,
|
||||
fut: None,
|
||||
err: Some(e),
|
||||
length: None,
|
||||
encoding: UTF_8,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,66 +551,57 @@ impl<T, U> UrlEncoded<T, U> {
|
||||
|
||||
impl<T, U> Future for UrlEncoded<T, U>
|
||||
where
|
||||
T: HttpMessage + Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
T: HttpMessage + 'static,
|
||||
U: DeserializeOwned + 'static,
|
||||
{
|
||||
type Item = U;
|
||||
type Error = UrlencodedError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(req) = self.req.take() {
|
||||
if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
|
||||
if let Ok(s) = len.to_str() {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
if len > 262_144 {
|
||||
return Err(UrlencodedError::Overflow);
|
||||
}
|
||||
} else {
|
||||
return Err(UrlencodedError::UnknownLength);
|
||||
}
|
||||
} else {
|
||||
return Err(UrlencodedError::UnknownLength);
|
||||
}
|
||||
}
|
||||
|
||||
// check content type
|
||||
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
|
||||
return Err(UrlencodedError::ContentType);
|
||||
}
|
||||
let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?;
|
||||
|
||||
// future
|
||||
let limit = self.limit;
|
||||
let fut = req
|
||||
.from_err()
|
||||
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(UrlencodedError::Overflow)
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok(body)
|
||||
}
|
||||
})
|
||||
.and_then(move |body| {
|
||||
let enc: *const Encoding = encoding as *const Encoding;
|
||||
if enc == UTF_8 {
|
||||
serde_urlencoded::from_bytes::<U>(&body)
|
||||
.map_err(|_| UrlencodedError::Parse)
|
||||
} else {
|
||||
let body = encoding
|
||||
.decode(&body, DecoderTrap::Strict)
|
||||
.map_err(|_| UrlencodedError::Parse)?;
|
||||
serde_urlencoded::from_str::<U>(&body)
|
||||
.map_err(|_| UrlencodedError::Parse)
|
||||
}
|
||||
});
|
||||
self.fut = Some(Box::new(fut));
|
||||
if let Some(ref mut fut) = self.fut {
|
||||
return fut.poll();
|
||||
}
|
||||
|
||||
self.fut
|
||||
.as_mut()
|
||||
if let Some(err) = self.err.take() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// payload size
|
||||
let limit = self.limit;
|
||||
if let Some(len) = self.length.take() {
|
||||
if len > limit {
|
||||
return Err(UrlencodedError::Overflow);
|
||||
}
|
||||
}
|
||||
|
||||
// future
|
||||
let encoding = self.encoding;
|
||||
let fut = self
|
||||
.stream
|
||||
.take()
|
||||
.expect("UrlEncoded could not be used second time")
|
||||
.poll()
|
||||
.from_err()
|
||||
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(UrlencodedError::Overflow)
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok(body)
|
||||
}
|
||||
}).and_then(move |body| {
|
||||
if (encoding as *const Encoding) == UTF_8 {
|
||||
serde_urlencoded::from_bytes::<U>(&body)
|
||||
.map_err(|_| UrlencodedError::Parse)
|
||||
} else {
|
||||
let body = encoding
|
||||
.decode(&body, DecoderTrap::Strict)
|
||||
.map_err(|_| UrlencodedError::Parse)?;
|
||||
serde_urlencoded::from_str::<U>(&body)
|
||||
.map_err(|_| UrlencodedError::Parse)
|
||||
}
|
||||
});
|
||||
self.fut = Some(Box::new(fut));
|
||||
self.poll()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,10 +611,7 @@ mod tests {
|
||||
use encoding::all::ISO_8859_2;
|
||||
use encoding::Encoding;
|
||||
use futures::Async;
|
||||
use http::{Method, Uri, Version};
|
||||
use httprequest::HttpRequest;
|
||||
use mime;
|
||||
use std::str::FromStr;
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
@@ -439,7 +622,7 @@ mod tests {
|
||||
TestRequest::with_header("content-type", "application/json; charset=utf=8")
|
||||
.finish();
|
||||
assert_eq!(req.content_type(), "application/json");
|
||||
let req = HttpRequest::default();
|
||||
let req = TestRequest::default().finish();
|
||||
assert_eq!(req.content_type(), "");
|
||||
}
|
||||
|
||||
@@ -447,7 +630,7 @@ mod tests {
|
||||
fn test_mime_type() {
|
||||
let req = TestRequest::with_header("content-type", "application/json").finish();
|
||||
assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON));
|
||||
let req = HttpRequest::default();
|
||||
let req = TestRequest::default().finish();
|
||||
assert_eq!(req.mime_type().unwrap(), None);
|
||||
let req =
|
||||
TestRequest::with_header("content-type", "application/json; charset=utf-8")
|
||||
@@ -469,7 +652,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_encoding() {
|
||||
let req = HttpRequest::default();
|
||||
let req = TestRequest::default().finish();
|
||||
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
|
||||
|
||||
let req = TestRequest::with_header("content-type", "application/json").finish();
|
||||
@@ -497,47 +680,20 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_request_range_header() {
|
||||
let req = HttpRequest::default();
|
||||
let ranges = req.range(100).unwrap();
|
||||
assert!(ranges.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_range_header() {
|
||||
let req = TestRequest::with_header(header::RANGE, "bytes=0-4").finish();
|
||||
let ranges = req.range(100).unwrap();
|
||||
assert_eq!(ranges.len(), 1);
|
||||
assert_eq!(ranges[0].start, 0);
|
||||
assert_eq!(ranges[0].length, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunked() {
|
||||
let req = HttpRequest::default();
|
||||
let req = TestRequest::default().finish();
|
||||
assert!(!req.chunked().unwrap());
|
||||
|
||||
let req =
|
||||
TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
|
||||
assert!(req.chunked().unwrap());
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
let s = unsafe {
|
||||
str::from_utf8_unchecked(b"some va\xadscc\xacas0xsdasdlue".as_ref())
|
||||
};
|
||||
|
||||
headers.insert(
|
||||
header::TRANSFER_ENCODING,
|
||||
header::HeaderValue::from_str(s).unwrap(),
|
||||
);
|
||||
let req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
headers,
|
||||
None,
|
||||
);
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::TRANSFER_ENCODING,
|
||||
Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"),
|
||||
).finish();
|
||||
assert!(req.chunked().is_err());
|
||||
}
|
||||
|
||||
@@ -576,7 +732,7 @@ mod tests {
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
).header(header::CONTENT_LENGTH, "xxxx")
|
||||
.finish();
|
||||
.finish();
|
||||
assert_eq!(
|
||||
req.urlencoded::<Info>().poll().err().unwrap(),
|
||||
UrlencodedError::UnknownLength
|
||||
@@ -586,7 +742,7 @@ mod tests {
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
).header(header::CONTENT_LENGTH, "1000000")
|
||||
.finish();
|
||||
.finish();
|
||||
assert_eq!(
|
||||
req.urlencoded::<Info>().poll().err().unwrap(),
|
||||
UrlencodedError::Overflow
|
||||
@@ -603,13 +759,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_urlencoded() {
|
||||
let mut req = TestRequest::with_header(
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
).header(header::CONTENT_LENGTH, "11")
|
||||
.finish();
|
||||
req.payload_mut()
|
||||
.unread_data(Bytes::from_static(b"hello=world"));
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.finish();
|
||||
|
||||
let result = req.urlencoded::<Info>().poll().ok().unwrap();
|
||||
assert_eq!(
|
||||
@@ -619,13 +774,12 @@ mod tests {
|
||||
})
|
||||
);
|
||||
|
||||
let mut req = TestRequest::with_header(
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded; charset=utf-8",
|
||||
).header(header::CONTENT_LENGTH, "11")
|
||||
.finish();
|
||||
req.payload_mut()
|
||||
.unread_data(Bytes::from_static(b"hello=world"));
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.finish();
|
||||
|
||||
let result = req.urlencoded().poll().ok().unwrap();
|
||||
assert_eq!(
|
||||
@@ -650,19 +804,52 @@ mod tests {
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.payload_mut().unread_data(Bytes::from_static(b"test"));
|
||||
let req = TestRequest::default()
|
||||
.set_payload(Bytes::from_static(b"test"))
|
||||
.finish();
|
||||
match req.body().poll().ok().unwrap() {
|
||||
Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")),
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.payload_mut()
|
||||
.unread_data(Bytes::from_static(b"11111111111111"));
|
||||
let req = TestRequest::default()
|
||||
.set_payload(Bytes::from_static(b"11111111111111"))
|
||||
.finish();
|
||||
match req.body().limit(5).poll().err().unwrap() {
|
||||
PayloadError::Overflow => (),
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readlines() {
|
||||
let req = TestRequest::default()
|
||||
.set_payload(Bytes::from_static(
|
||||
b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\
|
||||
industry. Lorem Ipsum has been the industry's standard dummy\n\
|
||||
Contrary to popular belief, Lorem Ipsum is not simply random text.",
|
||||
)).finish();
|
||||
let mut r = Readlines::new(&req);
|
||||
match r.poll().ok().unwrap() {
|
||||
Async::Ready(Some(s)) => assert_eq!(
|
||||
s,
|
||||
"Lorem Ipsum is simply dummy text of the printing and typesetting\n"
|
||||
),
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
match r.poll().ok().unwrap() {
|
||||
Async::Ready(Some(s)) => assert_eq!(
|
||||
s,
|
||||
"industry. Lorem Ipsum has been the industry's standard dummy\n"
|
||||
),
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
match r.poll().ok().unwrap() {
|
||||
Async::Ready(Some(s)) => assert_eq!(
|
||||
s,
|
||||
"Contrary to popular belief, Lorem Ipsum is not simply random text."
|
||||
),
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,283 +1,180 @@
|
||||
//! HTTP Request message related code.
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(transmute_ptr_to_ptr))]
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::{cmp, fmt, io, mem, str};
|
||||
use std::{fmt, str};
|
||||
|
||||
use bytes::Bytes;
|
||||
use cookie::Cookie;
|
||||
use failure;
|
||||
use futures::{Async, Poll, Stream};
|
||||
use futures_cpupool::CpuPool;
|
||||
use http::{header, Extensions, HeaderMap, Method, StatusCode, Uri, Version};
|
||||
use tokio_io::AsyncRead;
|
||||
use http::{header, HeaderMap, Method, StatusCode, Uri, Version};
|
||||
use url::{form_urlencoded, Url};
|
||||
|
||||
use body::Body;
|
||||
use error::{CookieParseError, PayloadError, UrlGenerationError};
|
||||
use error::{CookieParseError, UrlGenerationError};
|
||||
use extensions::Extensions;
|
||||
use handler::FromRequest;
|
||||
use httpmessage::HttpMessage;
|
||||
use httpresponse::{HttpResponse, HttpResponseBuilder};
|
||||
use info::ConnectionInfo;
|
||||
use param::Params;
|
||||
use payload::Payload;
|
||||
use router::{Resource, Router};
|
||||
use server::helpers::SharedHttpInnerMessage;
|
||||
use uri::Url as InnerUrl;
|
||||
use router::ResourceInfo;
|
||||
use server::Request;
|
||||
|
||||
bitflags! {
|
||||
pub(crate) struct MessageFlags: u8 {
|
||||
const KEEPALIVE = 0b0000_0010;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HttpInnerMessage {
|
||||
pub version: Version,
|
||||
pub method: Method,
|
||||
pub(crate) url: InnerUrl,
|
||||
pub(crate) flags: MessageFlags,
|
||||
pub headers: HeaderMap,
|
||||
pub extensions: Extensions,
|
||||
pub params: Params<'static>,
|
||||
pub addr: Option<SocketAddr>,
|
||||
pub payload: Option<Payload>,
|
||||
pub info: Option<ConnectionInfo<'static>>,
|
||||
pub prefix: u16,
|
||||
resource: RouterResource,
|
||||
}
|
||||
|
||||
struct Query(Params<'static>);
|
||||
struct Query(HashMap<String, String>);
|
||||
struct Cookies(Vec<Cookie<'static>>);
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
enum RouterResource {
|
||||
Notset,
|
||||
Normal(u16),
|
||||
/// An HTTP Request
|
||||
pub struct HttpRequest<S = ()> {
|
||||
req: Option<Request>,
|
||||
state: Rc<S>,
|
||||
resource: ResourceInfo,
|
||||
}
|
||||
|
||||
impl Default for HttpInnerMessage {
|
||||
fn default() -> HttpInnerMessage {
|
||||
HttpInnerMessage {
|
||||
method: Method::GET,
|
||||
url: InnerUrl::default(),
|
||||
version: Version::HTTP_11,
|
||||
headers: HeaderMap::with_capacity(16),
|
||||
flags: MessageFlags::empty(),
|
||||
params: Params::new(),
|
||||
addr: None,
|
||||
payload: None,
|
||||
extensions: Extensions::new(),
|
||||
info: None,
|
||||
prefix: 0,
|
||||
resource: RouterResource::Notset,
|
||||
impl<S> HttpMessage for HttpRequest<S> {
|
||||
type Stream = Payload;
|
||||
|
||||
#[inline]
|
||||
fn headers(&self) -> &HeaderMap {
|
||||
self.request().headers()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn payload(&self) -> Payload {
|
||||
if let Some(payload) = self.request().inner.payload.borrow_mut().take() {
|
||||
payload
|
||||
} else {
|
||||
Payload::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpInnerMessage {
|
||||
/// Checks if a connection should be kept alive.
|
||||
#[inline]
|
||||
pub fn keep_alive(&self) -> bool {
|
||||
self.flags.contains(MessageFlags::KEEPALIVE)
|
||||
}
|
||||
impl<S> Deref for HttpRequest<S> {
|
||||
type Target = Request;
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn reset(&mut self) {
|
||||
self.headers.clear();
|
||||
self.extensions.clear();
|
||||
self.params.clear();
|
||||
self.addr = None;
|
||||
self.info = None;
|
||||
self.flags = MessageFlags::empty();
|
||||
self.payload = None;
|
||||
self.prefix = 0;
|
||||
self.resource = RouterResource::Notset;
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref RESOURCE: Resource = Resource::unset();
|
||||
}
|
||||
|
||||
/// An HTTP Request
|
||||
pub struct HttpRequest<S = ()>(SharedHttpInnerMessage, Option<Rc<S>>, Option<Router>);
|
||||
|
||||
impl HttpRequest<()> {
|
||||
/// Construct a new Request.
|
||||
#[inline]
|
||||
pub fn new(
|
||||
method: Method, uri: Uri, version: Version, headers: HeaderMap,
|
||||
payload: Option<Payload>,
|
||||
) -> HttpRequest {
|
||||
let url = InnerUrl::new(uri);
|
||||
HttpRequest(
|
||||
SharedHttpInnerMessage::from_message(HttpInnerMessage {
|
||||
method,
|
||||
url,
|
||||
version,
|
||||
headers,
|
||||
payload,
|
||||
params: Params::new(),
|
||||
extensions: Extensions::new(),
|
||||
addr: None,
|
||||
info: None,
|
||||
prefix: 0,
|
||||
flags: MessageFlags::empty(),
|
||||
resource: RouterResource::Notset,
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||
pub(crate) fn from_message(msg: SharedHttpInnerMessage) -> HttpRequest {
|
||||
HttpRequest(msg, None, None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Construct new http request with state.
|
||||
pub fn with_state<S>(self, state: Rc<S>, router: Router) -> HttpRequest<S> {
|
||||
HttpRequest(self.0, Some(state), Some(router))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> HttpMessage for HttpRequest<S> {
|
||||
#[inline]
|
||||
fn headers(&self) -> &HeaderMap {
|
||||
&self.as_ref().headers
|
||||
fn deref(&self) -> &Request {
|
||||
self.request()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> HttpRequest<S> {
|
||||
#[inline]
|
||||
pub(crate) fn new(
|
||||
req: Request, state: Rc<S>, resource: ResourceInfo,
|
||||
) -> HttpRequest<S> {
|
||||
HttpRequest {
|
||||
state,
|
||||
resource,
|
||||
req: Some(req),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Construct new http request with state.
|
||||
pub fn change_state<NS>(&self, state: Rc<NS>) -> HttpRequest<NS> {
|
||||
HttpRequest(self.0.clone(), Some(state), self.2.clone())
|
||||
pub(crate) fn with_state<NS>(&self, state: Rc<NS>) -> HttpRequest<NS> {
|
||||
HttpRequest {
|
||||
state,
|
||||
req: self.req.as_ref().map(|r| r.clone()),
|
||||
resource: self.resource.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Construct new http request without state.
|
||||
/// Construct new http request with empty state.
|
||||
pub fn drop_state(&self) -> HttpRequest {
|
||||
HttpRequest(self.0.clone(), None, self.2.clone())
|
||||
}
|
||||
|
||||
/// get mutable reference for inner message
|
||||
/// mutable reference should not be returned as result for request's method
|
||||
#[inline(always)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
|
||||
pub(crate) fn as_mut(&self) -> &mut HttpInnerMessage {
|
||||
self.0.get_mut()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
|
||||
fn as_ref(&self) -> &HttpInnerMessage {
|
||||
self.0.get_ref()
|
||||
HttpRequest {
|
||||
state: Rc::new(()),
|
||||
req: self.req.as_ref().map(|r| r.clone()),
|
||||
resource: self.resource.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn get_inner(&mut self) -> &mut HttpInnerMessage {
|
||||
self.as_mut()
|
||||
/// Construct new http request with new RouteInfo.
|
||||
pub(crate) fn with_route_info(&self, mut resource: ResourceInfo) -> HttpRequest<S> {
|
||||
resource.merge(&self.resource);
|
||||
|
||||
HttpRequest {
|
||||
resource,
|
||||
req: self.req.as_ref().map(|r| r.clone()),
|
||||
state: self.state.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared application state
|
||||
#[inline]
|
||||
pub fn state(&self) -> &S {
|
||||
self.1.as_ref().unwrap()
|
||||
&self.state
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Server request
|
||||
pub fn request(&self) -> &Request {
|
||||
self.req.as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// Request extensions
|
||||
#[inline]
|
||||
pub fn extensions(&self) -> &Extensions {
|
||||
&self.as_ref().extensions
|
||||
pub fn extensions(&self) -> Ref<Extensions> {
|
||||
self.request().extensions()
|
||||
}
|
||||
|
||||
/// Mutable reference to a the request's extensions
|
||||
#[inline]
|
||||
pub fn extensions_mut(&mut self) -> &mut Extensions {
|
||||
&mut self.as_mut().extensions
|
||||
pub fn extensions_mut(&self) -> RefMut<Extensions> {
|
||||
self.request().extensions_mut()
|
||||
}
|
||||
|
||||
/// Default `CpuPool`
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
pub fn cpu_pool(&self) -> &CpuPool {
|
||||
self.router()
|
||||
.expect("HttpRequest has to have Router instance")
|
||||
.server_settings()
|
||||
.cpu_pool()
|
||||
self.request().server_settings().cpu_pool()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Create http response
|
||||
pub fn response(&self, status: StatusCode, body: Body) -> HttpResponse {
|
||||
if let Some(router) = self.router() {
|
||||
router.server_settings().get_response(status, body)
|
||||
} else {
|
||||
HttpResponse::with_body(status, body)
|
||||
}
|
||||
self.request().server_settings().get_response(status, body)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Create http response builder
|
||||
pub fn build_response(&self, status: StatusCode) -> HttpResponseBuilder {
|
||||
if let Some(router) = self.router() {
|
||||
router.server_settings().get_response_builder(status)
|
||||
} else {
|
||||
HttpResponse::build(status)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn prefix_len(&self) -> u16 {
|
||||
self.as_ref().prefix as u16
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn set_prefix_len(&mut self, len: u16) {
|
||||
self.as_mut().prefix = len;
|
||||
self.request()
|
||||
.server_settings()
|
||||
.get_response_builder(status)
|
||||
}
|
||||
|
||||
/// Read the Request Uri.
|
||||
#[inline]
|
||||
pub fn uri(&self) -> &Uri {
|
||||
self.as_ref().url.uri()
|
||||
self.request().inner.url.uri()
|
||||
}
|
||||
|
||||
/// Read the Request method.
|
||||
#[inline]
|
||||
pub fn method(&self) -> &Method {
|
||||
&self.as_ref().method
|
||||
&self.request().inner.method
|
||||
}
|
||||
|
||||
/// Read the Request Version.
|
||||
#[inline]
|
||||
pub fn version(&self) -> Version {
|
||||
self.as_ref().version
|
||||
}
|
||||
|
||||
///Returns mutable Request's headers.
|
||||
///
|
||||
///This is intended to be used by middleware.
|
||||
#[inline]
|
||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.as_mut().headers
|
||||
self.request().inner.version
|
||||
}
|
||||
|
||||
/// The target path of this Request.
|
||||
#[inline]
|
||||
pub fn path(&self) -> &str {
|
||||
self.as_ref().url.path()
|
||||
self.request().inner.url.path()
|
||||
}
|
||||
|
||||
/// Get *ConnectionInfo* for correct request.
|
||||
pub fn connection_info(&self) -> &ConnectionInfo {
|
||||
if self.as_ref().info.is_none() {
|
||||
let info: ConnectionInfo<'static> =
|
||||
unsafe { mem::transmute(ConnectionInfo::new(self)) };
|
||||
self.as_mut().info = Some(info);
|
||||
}
|
||||
self.as_ref().info.as_ref().unwrap()
|
||||
/// Get *ConnectionInfo* for the correct request.
|
||||
#[inline]
|
||||
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
|
||||
self.request().connection_info()
|
||||
}
|
||||
|
||||
/// Generate url for named resource
|
||||
@@ -307,22 +204,7 @@ impl<S> HttpRequest<S> {
|
||||
U: IntoIterator<Item = I>,
|
||||
I: AsRef<str>,
|
||||
{
|
||||
if self.router().is_none() {
|
||||
Err(UrlGenerationError::RouterNotAvailable)
|
||||
} else {
|
||||
let path = self.router().unwrap().resource_path(name, elements)?;
|
||||
if path.starts_with('/') {
|
||||
let conn = self.connection_info();
|
||||
Ok(Url::parse(&format!(
|
||||
"{}://{}{}",
|
||||
conn.scheme(),
|
||||
conn.host(),
|
||||
path
|
||||
))?)
|
||||
} else {
|
||||
Ok(Url::parse(&path)?)
|
||||
}
|
||||
}
|
||||
self.resource.url_for(&self, name, elements)
|
||||
}
|
||||
|
||||
/// Generate url for named resource
|
||||
@@ -334,25 +216,10 @@ impl<S> HttpRequest<S> {
|
||||
self.url_for(name, &NO_PARAMS)
|
||||
}
|
||||
|
||||
/// This method returns reference to current `Router` object.
|
||||
/// This method returns reference to current `ResourceInfo` object.
|
||||
#[inline]
|
||||
pub fn router(&self) -> Option<&Router> {
|
||||
self.2.as_ref()
|
||||
}
|
||||
|
||||
/// This method returns reference to matched `Resource` object.
|
||||
#[inline]
|
||||
pub fn resource(&self) -> &Resource {
|
||||
if let Some(ref router) = self.2 {
|
||||
if let RouterResource::Normal(idx) = self.as_ref().resource {
|
||||
return router.get_resource(idx as usize);
|
||||
}
|
||||
}
|
||||
&*RESOURCE
|
||||
}
|
||||
|
||||
pub(crate) fn set_resource(&mut self, res: usize) {
|
||||
self.as_mut().resource = RouterResource::Normal(res as u16);
|
||||
pub fn resource(&self) -> &ResourceInfo {
|
||||
&self.resource
|
||||
}
|
||||
|
||||
/// Peer socket address
|
||||
@@ -364,29 +231,19 @@ impl<S> HttpRequest<S> {
|
||||
/// be used.
|
||||
#[inline]
|
||||
pub fn peer_addr(&self) -> Option<SocketAddr> {
|
||||
self.as_ref().addr
|
||||
self.request().inner.addr
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_peer_addr(&mut self, addr: Option<SocketAddr>) {
|
||||
self.as_mut().addr = addr;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Get a reference to the Params object.
|
||||
/// Params is a container for url query parameters.
|
||||
pub fn query<'a>(&'a self) -> &'a Params {
|
||||
/// url query parameters.
|
||||
pub fn query(&self) -> Ref<HashMap<String, String>> {
|
||||
if self.extensions().get::<Query>().is_none() {
|
||||
let mut params: Params<'a> = Params::new();
|
||||
let mut query = HashMap::new();
|
||||
for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) {
|
||||
params.add(key, val);
|
||||
query.insert(key.as_ref().to_string(), val.to_string());
|
||||
}
|
||||
let params: Params<'static> = unsafe { mem::transmute(params) };
|
||||
self.as_mut().extensions.insert(Query(params));
|
||||
self.extensions_mut().insert(Query(query));
|
||||
}
|
||||
let params: &Params<'a> =
|
||||
unsafe { mem::transmute(&self.extensions().get::<Query>().unwrap().0) };
|
||||
params
|
||||
Ref::map(self.extensions(), |ext| &ext.get::<Query>().unwrap().0)
|
||||
}
|
||||
|
||||
/// The query string in the URL.
|
||||
@@ -402,29 +259,33 @@ impl<S> HttpRequest<S> {
|
||||
}
|
||||
|
||||
/// Load request cookies.
|
||||
pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> {
|
||||
if self.extensions().get::<Query>().is_none() {
|
||||
let msg = self.as_mut();
|
||||
#[inline]
|
||||
pub fn cookies(&self) -> Result<Ref<Vec<Cookie<'static>>>, CookieParseError> {
|
||||
if self.extensions().get::<Cookies>().is_none() {
|
||||
let mut cookies = Vec::new();
|
||||
for hdr in msg.headers.get_all(header::COOKIE) {
|
||||
let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
|
||||
for hdr in self.request().inner.headers.get_all(header::COOKIE) {
|
||||
let s =
|
||||
str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
|
||||
for cookie_str in s.split(';').map(|s| s.trim()) {
|
||||
if !cookie_str.is_empty() {
|
||||
cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
msg.extensions.insert(Cookies(cookies));
|
||||
self.extensions_mut().insert(Cookies(cookies));
|
||||
}
|
||||
Ok(&self.extensions().get::<Cookies>().unwrap().0)
|
||||
Ok(Ref::map(self.extensions(), |ext| {
|
||||
&ext.get::<Cookies>().unwrap().0
|
||||
}))
|
||||
}
|
||||
|
||||
/// Return request cookie.
|
||||
pub fn cookie(&self, name: &str) -> Option<&Cookie> {
|
||||
#[inline]
|
||||
pub fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
|
||||
if let Ok(cookies) = self.cookies() {
|
||||
for cookie in cookies {
|
||||
for cookie in cookies.iter() {
|
||||
if cookie.name() == name {
|
||||
return Some(cookie);
|
||||
return Some(cookie.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -445,68 +306,39 @@ impl<S> HttpRequest<S> {
|
||||
/// access the matched value for that segment.
|
||||
#[inline]
|
||||
pub fn match_info(&self) -> &Params {
|
||||
unsafe { mem::transmute(&self.as_ref().params) }
|
||||
}
|
||||
|
||||
/// Get mutable reference to request's Params.
|
||||
#[inline]
|
||||
pub fn match_info_mut(&mut self) -> &mut Params {
|
||||
unsafe { mem::transmute(&mut self.as_mut().params) }
|
||||
}
|
||||
|
||||
/// Checks if a connection should be kept alive.
|
||||
pub fn keep_alive(&self) -> bool {
|
||||
self.as_ref().flags.contains(MessageFlags::KEEPALIVE)
|
||||
&self.resource.match_info()
|
||||
}
|
||||
|
||||
/// Check if request requires connection upgrade
|
||||
pub(crate) fn upgrade(&self) -> bool {
|
||||
if let Some(conn) = self.as_ref().headers.get(header::CONNECTION) {
|
||||
if let Ok(s) = conn.to_str() {
|
||||
return s.to_lowercase().contains("upgrade");
|
||||
}
|
||||
}
|
||||
self.as_ref().method == Method::CONNECT
|
||||
self.request().upgrade()
|
||||
}
|
||||
|
||||
/// Set read buffer capacity
|
||||
///
|
||||
/// Default buffer capacity is 32Kb.
|
||||
pub fn set_read_buffer_capacity(&mut self, cap: usize) {
|
||||
if let Some(ref mut payload) = self.as_mut().payload {
|
||||
if let Some(payload) = self.request().inner.payload.borrow_mut().as_mut() {
|
||||
payload.set_read_buffer_capacity(cap)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn payload(&self) -> &Payload {
|
||||
let msg = self.as_mut();
|
||||
if msg.payload.is_none() {
|
||||
msg.payload = Some(Payload::empty());
|
||||
}
|
||||
msg.payload.as_ref().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn payload_mut(&mut self) -> &mut Payload {
|
||||
let msg = self.as_mut();
|
||||
if msg.payload.is_none() {
|
||||
msg.payload = Some(Payload::empty());
|
||||
}
|
||||
msg.payload.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HttpRequest<()> {
|
||||
/// Construct default request
|
||||
fn default() -> HttpRequest {
|
||||
HttpRequest(SharedHttpInnerMessage::default(), None, None)
|
||||
impl<S> Drop for HttpRequest<S> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(req) = self.req.take() {
|
||||
req.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Clone for HttpRequest<S> {
|
||||
fn clone(&self) -> HttpRequest<S> {
|
||||
HttpRequest(self.0.clone(), self.1.clone(), self.2.clone())
|
||||
HttpRequest {
|
||||
req: self.req.as_ref().map(|r| r.clone()),
|
||||
state: self.state.clone(),
|
||||
resource: self.resource.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,88 +352,34 @@ impl<S> FromRequest<S> for HttpRequest<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Stream for HttpRequest<S> {
|
||||
type Item = Bytes;
|
||||
type Error = PayloadError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
let msg = self.as_mut();
|
||||
if msg.payload.is_none() {
|
||||
Ok(Async::Ready(None))
|
||||
} else {
|
||||
msg.payload.as_mut().unwrap().poll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> io::Read for HttpRequest<S> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
if self.as_mut().payload.is_some() {
|
||||
match self.as_mut().payload.as_mut().unwrap().poll() {
|
||||
Ok(Async::Ready(Some(mut b))) => {
|
||||
let i = cmp::min(b.len(), buf.len());
|
||||
buf.copy_from_slice(&b.split_to(i)[..i]);
|
||||
|
||||
if !b.is_empty() {
|
||||
self.as_mut().payload.as_mut().unwrap().unread_data(b);
|
||||
}
|
||||
|
||||
if i < buf.len() {
|
||||
match self.read(&mut buf[i..]) {
|
||||
Ok(n) => Ok(i + n),
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(i),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
Ok(i)
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(None)) => Ok(0),
|
||||
Ok(Async::NotReady) => {
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "Not ready"))
|
||||
}
|
||||
Err(e) => Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
failure::Error::from(e).compat(),
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> AsyncRead for HttpRequest<S> {}
|
||||
|
||||
impl<S> fmt::Debug for HttpRequest<S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let res = writeln!(
|
||||
writeln!(
|
||||
f,
|
||||
"\nHttpRequest {:?} {}:{}",
|
||||
self.as_ref().version,
|
||||
self.as_ref().method,
|
||||
self.version(),
|
||||
self.method(),
|
||||
self.path()
|
||||
);
|
||||
)?;
|
||||
if !self.query_string().is_empty() {
|
||||
let _ = writeln!(f, " query: ?{:?}", self.query_string());
|
||||
writeln!(f, " query: ?{:?}", self.query_string())?;
|
||||
}
|
||||
if !self.match_info().is_empty() {
|
||||
let _ = writeln!(f, " params: {:?}", self.as_ref().params);
|
||||
writeln!(f, " params: {:?}", self.match_info())?;
|
||||
}
|
||||
let _ = writeln!(f, " headers:");
|
||||
for (key, val) in self.as_ref().headers.iter() {
|
||||
let _ = writeln!(f, " {:?}: {:?}", key, val);
|
||||
writeln!(f, " headers:")?;
|
||||
for (key, val) in self.headers().iter() {
|
||||
writeln!(f, " {:?}: {:?}", key, val)?;
|
||||
}
|
||||
res
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use resource::ResourceHandler;
|
||||
use router::Resource;
|
||||
use server::ServerSettings;
|
||||
use resource::Resource;
|
||||
use router::{ResourceDef, Router};
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
@@ -613,7 +391,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_no_request_cookies() {
|
||||
let req = HttpRequest::default();
|
||||
let req = TestRequest::default().finish();
|
||||
assert!(req.cookies().unwrap().is_empty());
|
||||
}
|
||||
|
||||
@@ -652,33 +430,27 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_match_info() {
|
||||
let mut req = TestRequest::with_uri("/value/?id=test").finish();
|
||||
let mut router = Router::<()>::default();
|
||||
router.register_resource(Resource::new(ResourceDef::new("/{key}/")));
|
||||
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let mut routes = Vec::new();
|
||||
routes.push((Resource::new("index", "/{key}/"), Some(resource)));
|
||||
let (router, _) = Router::new("", ServerSettings::default(), routes);
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
assert_eq!(req.match_info().get("key"), Some("value"));
|
||||
let req = TestRequest::with_uri("/value/?id=test").finish();
|
||||
let info = router.recognize(&req, &(), 0);
|
||||
assert_eq!(info.match_info().get("key"), Some("value"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_url_for() {
|
||||
let req2 = HttpRequest::default();
|
||||
assert_eq!(
|
||||
req2.url_for("unknown", &["test"]),
|
||||
Err(UrlGenerationError::RouterNotAvailable)
|
||||
);
|
||||
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
let mut router = Router::<()>::default();
|
||||
let mut resource = Resource::new(ResourceDef::new("/user/{name}.{ext}"));
|
||||
resource.name("index");
|
||||
let routes =
|
||||
vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))];
|
||||
let (router, _) = Router::new("/", ServerSettings::default(), routes);
|
||||
assert!(router.has_route("/user/test.html"));
|
||||
assert!(!router.has_route("/test/unknown"));
|
||||
router.register_resource(resource);
|
||||
|
||||
let info = router.default_route_info();
|
||||
assert!(!info.has_prefixed_resource("/use/"));
|
||||
assert!(info.has_resource("/user/test.html"));
|
||||
assert!(info.has_prefixed_resource("/user/test.html"));
|
||||
assert!(!info.has_resource("/test/unknown"));
|
||||
assert!(!info.has_prefixed_resource("/test/unknown"));
|
||||
|
||||
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org")
|
||||
.finish_with_router(router);
|
||||
@@ -700,16 +472,24 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_url_for_with_prefix() {
|
||||
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish();
|
||||
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
let mut resource = Resource::new(ResourceDef::new("/user/{name}.html"));
|
||||
resource.name("index");
|
||||
let routes = vec![(Resource::new("index", "/user/{name}.html"), Some(resource))];
|
||||
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
|
||||
assert!(router.has_route("/user/test.html"));
|
||||
assert!(!router.has_route("/prefix/user/test.html"));
|
||||
let mut router = Router::<()>::default();
|
||||
router.set_prefix("/prefix");
|
||||
router.register_resource(resource);
|
||||
|
||||
let req = req.with_state(Rc::new(()), router);
|
||||
let mut info = router.default_route_info();
|
||||
info.set_prefix(7);
|
||||
assert!(!info.has_prefixed_resource("/use/"));
|
||||
assert!(info.has_resource("/user/test.html"));
|
||||
assert!(!info.has_prefixed_resource("/user/test.html"));
|
||||
assert!(!info.has_resource("/prefix/user/test.html"));
|
||||
assert!(info.has_prefixed_resource("/prefix/user/test.html"));
|
||||
|
||||
let req = TestRequest::with_uri("/prefix/test")
|
||||
.prefix(7)
|
||||
.header(header::HOST, "www.rust-lang.org")
|
||||
.finish_with_router(router);
|
||||
let url = req.url_for("index", &["test"]);
|
||||
assert_eq!(
|
||||
url.ok().unwrap().as_str(),
|
||||
@@ -719,16 +499,23 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_url_for_static() {
|
||||
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish();
|
||||
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
let mut resource = Resource::new(ResourceDef::new("/index.html"));
|
||||
resource.name("index");
|
||||
let routes = vec![(Resource::new("index", "/index.html"), Some(resource))];
|
||||
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
|
||||
assert!(router.has_route("/index.html"));
|
||||
assert!(!router.has_route("/prefix/index.html"));
|
||||
let mut router = Router::<()>::default();
|
||||
router.set_prefix("/prefix");
|
||||
router.register_resource(resource);
|
||||
|
||||
let req = req.with_state(Rc::new(()), router);
|
||||
let mut info = router.default_route_info();
|
||||
info.set_prefix(7);
|
||||
assert!(info.has_resource("/index.html"));
|
||||
assert!(!info.has_prefixed_resource("/index.html"));
|
||||
assert!(!info.has_resource("/prefix/index.html"));
|
||||
assert!(info.has_prefixed_resource("/prefix/index.html"));
|
||||
|
||||
let req = TestRequest::with_uri("/prefix/test")
|
||||
.prefix(7)
|
||||
.header(header::HOST, "www.rust-lang.org")
|
||||
.finish_with_router(router);
|
||||
let url = req.url_for_static("index");
|
||||
assert_eq!(
|
||||
url.ok().unwrap().as_str(),
|
||||
@@ -738,18 +525,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_url_for_external() {
|
||||
let req = HttpRequest::default();
|
||||
let mut router = Router::<()>::default();
|
||||
router.register_external(
|
||||
"youtube",
|
||||
ResourceDef::external("https://youtube.com/watch/{video_id}"),
|
||||
);
|
||||
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let routes = vec![(
|
||||
Resource::external("youtube", "https://youtube.com/watch/{video_id}"),
|
||||
None,
|
||||
)];
|
||||
let (router, _) = Router::new::<()>("", ServerSettings::default(), routes);
|
||||
assert!(!router.has_route("https://youtube.com/watch/unknown"));
|
||||
let info = router.default_route_info();
|
||||
assert!(!info.has_resource("https://youtube.com/watch/unknown"));
|
||||
assert!(!info.has_prefixed_resource("https://youtube.com/watch/unknown"));
|
||||
|
||||
let req = req.with_state(Rc::new(()), router);
|
||||
let req = TestRequest::default().finish_with_router(router);
|
||||
let url = req.url_for("youtube", &["oHg5SJYRHA0"]);
|
||||
assert_eq!(
|
||||
url.ok().unwrap().as_str(),
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
//! Http response
|
||||
use std::cell::UnsafeCell;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::io::Write;
|
||||
use std::rc::Rc;
|
||||
use std::{fmt, mem, str};
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
@@ -36,30 +35,17 @@ pub enum ConnectionType {
|
||||
}
|
||||
|
||||
/// An HTTP Response
|
||||
pub struct HttpResponse(
|
||||
Option<Box<InnerHttpResponse>>,
|
||||
Rc<UnsafeCell<HttpResponsePool>>,
|
||||
);
|
||||
|
||||
impl Drop for HttpResponse {
|
||||
fn drop(&mut self) {
|
||||
if let Some(inner) = self.0.take() {
|
||||
HttpResponsePool::release(&self.1, inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct HttpResponse(Box<InnerHttpResponse>, &'static HttpResponsePool);
|
||||
|
||||
impl HttpResponse {
|
||||
#[inline(always)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||
#[inline]
|
||||
fn get_ref(&self) -> &InnerHttpResponse {
|
||||
self.0.as_ref().unwrap()
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||
#[inline]
|
||||
fn get_mut(&mut self) -> &mut InnerHttpResponse {
|
||||
self.0.as_mut().unwrap()
|
||||
self.0.as_mut()
|
||||
}
|
||||
|
||||
/// Create http response builder with specific status.
|
||||
@@ -86,25 +72,34 @@ impl HttpResponse {
|
||||
HttpResponsePool::with_body(status, body.into())
|
||||
}
|
||||
|
||||
/// Constructs a error response
|
||||
/// Constructs an error response
|
||||
#[inline]
|
||||
pub fn from_error(error: Error) -> HttpResponse {
|
||||
let mut resp = error.cause().error_response();
|
||||
let mut resp = error.as_response_error().error_response();
|
||||
resp.get_mut().error = Some(error);
|
||||
resp
|
||||
}
|
||||
|
||||
/// Convert `HttpResponse` to a `HttpResponseBuilder`
|
||||
#[inline]
|
||||
pub fn into_builder(mut self) -> HttpResponseBuilder {
|
||||
let response = self.0.take();
|
||||
let pool = Some(Rc::clone(&self.1));
|
||||
pub fn into_builder(self) -> HttpResponseBuilder {
|
||||
// If this response has cookies, load them into a jar
|
||||
let mut jar: Option<CookieJar> = None;
|
||||
for c in self.cookies() {
|
||||
if let Some(ref mut j) = jar {
|
||||
j.add_original(c.into_owned());
|
||||
} else {
|
||||
let mut j = CookieJar::new();
|
||||
j.add_original(c.into_owned());
|
||||
jar = Some(j);
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponseBuilder {
|
||||
response,
|
||||
pool,
|
||||
pool: self.1,
|
||||
response: Some(self.0),
|
||||
err: None,
|
||||
cookies: None, // TODO: convert set-cookie headers
|
||||
cookies: jar,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +127,51 @@ impl HttpResponse {
|
||||
&mut self.get_mut().headers
|
||||
}
|
||||
|
||||
/// Get an iterator for the cookies set by this response
|
||||
#[inline]
|
||||
pub fn cookies(&self) -> CookieIter {
|
||||
CookieIter {
|
||||
iter: self.get_ref().headers.get_all(header::SET_COOKIE).iter(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a cookie to this response
|
||||
#[inline]
|
||||
pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> {
|
||||
let h = &mut self.get_mut().headers;
|
||||
HeaderValue::from_str(&cookie.to_string())
|
||||
.map(|c| {
|
||||
h.append(header::SET_COOKIE, c);
|
||||
}).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Remove all cookies with the given name from this response. Returns
|
||||
/// the number of cookies removed.
|
||||
#[inline]
|
||||
pub fn del_cookie(&mut self, name: &str) -> usize {
|
||||
let h = &mut self.get_mut().headers;
|
||||
let vals: Vec<HeaderValue> = h
|
||||
.get_all(header::SET_COOKIE)
|
||||
.iter()
|
||||
.map(|v| v.to_owned())
|
||||
.collect();
|
||||
h.remove(header::SET_COOKIE);
|
||||
|
||||
let mut count: usize = 0;
|
||||
for v in vals {
|
||||
if let Ok(s) = v.to_str() {
|
||||
if let Ok(c) = Cookie::parse_encoded(s) {
|
||||
if c.name() == name {
|
||||
count += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
h.append(header::SET_COOKIE, v);
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
/// Get the response status code
|
||||
#[inline]
|
||||
pub fn status(&self) -> StatusCode {
|
||||
@@ -232,7 +272,7 @@ impl HttpResponse {
|
||||
self.get_mut().response_size = size;
|
||||
}
|
||||
|
||||
/// Set write buffer capacity
|
||||
/// Get write buffer capacity
|
||||
pub fn write_buffer_capacity(&self) -> usize {
|
||||
self.get_ref().write_capacity
|
||||
}
|
||||
@@ -241,6 +281,21 @@ impl HttpResponse {
|
||||
pub fn set_write_buffer_capacity(&mut self, cap: usize) {
|
||||
self.get_mut().write_capacity = cap;
|
||||
}
|
||||
|
||||
pub(crate) fn release(self) {
|
||||
self.1.release(self.0);
|
||||
}
|
||||
|
||||
pub(crate) fn into_parts(self) -> HttpResponseParts {
|
||||
self.0.into_parts()
|
||||
}
|
||||
|
||||
pub(crate) fn from_parts(parts: HttpResponseParts) -> HttpResponse {
|
||||
HttpResponse(
|
||||
Box::new(InnerHttpResponse::from_parts(parts)),
|
||||
HttpResponsePool::get_pool(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for HttpResponse {
|
||||
@@ -261,13 +316,31 @@ impl fmt::Debug for HttpResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CookieIter<'a> {
|
||||
iter: header::ValueIter<'a, HeaderValue>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for CookieIter<'a> {
|
||||
type Item = Cookie<'a>;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Cookie<'a>> {
|
||||
for v in self.iter.by_ref() {
|
||||
if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) {
|
||||
return Some(c);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// An HTTP response builder
|
||||
///
|
||||
/// This type can be used to construct an instance of `HttpResponse` through a
|
||||
/// builder-like pattern.
|
||||
pub struct HttpResponseBuilder {
|
||||
pool: &'static HttpResponsePool,
|
||||
response: Option<Box<InnerHttpResponse>>,
|
||||
pool: Option<Rc<UnsafeCell<HttpResponsePool>>>,
|
||||
err: Option<HttpError>,
|
||||
cookies: Option<CookieJar>,
|
||||
}
|
||||
@@ -297,11 +370,13 @@ impl HttpResponseBuilder {
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{HttpRequest, HttpResponse, Result, http};
|
||||
/// use actix_web::{http, HttpRequest, HttpResponse, Result};
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> Result<HttpResponse> {
|
||||
/// Ok(HttpResponse::Ok()
|
||||
/// .set(http::header::IfModifiedSince("Sun, 07 Nov 1994 08:48:37 GMT".parse()?))
|
||||
/// .set(http::header::IfModifiedSince(
|
||||
/// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?,
|
||||
/// ))
|
||||
/// .finish())
|
||||
/// }
|
||||
/// fn main() {}
|
||||
@@ -455,10 +530,10 @@ impl HttpResponseBuilder {
|
||||
/// .path("/")
|
||||
/// .secure(true)
|
||||
/// .http_only(true)
|
||||
/// .finish())
|
||||
/// .finish(),
|
||||
/// )
|
||||
/// .finish()
|
||||
/// }
|
||||
/// fn main() {}
|
||||
/// ```
|
||||
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
|
||||
if self.cookies.is_none() {
|
||||
@@ -471,8 +546,22 @@ impl HttpResponseBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Remove cookie, cookie has to be cookie from `HttpRequest::cookies()`
|
||||
/// method.
|
||||
/// Remove cookie
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{http, HttpRequest, HttpResponse, Result};
|
||||
///
|
||||
/// fn index(req: &HttpRequest) -> HttpResponse {
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
///
|
||||
/// if let Some(ref cookie) = req.cookie("name") {
|
||||
/// builder.del_cookie(cookie);
|
||||
/// }
|
||||
///
|
||||
/// builder.finish()
|
||||
/// }
|
||||
/// ```
|
||||
pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self {
|
||||
{
|
||||
if self.cookies.is_none() {
|
||||
@@ -541,7 +630,7 @@ impl HttpResponseBuilder {
|
||||
}
|
||||
}
|
||||
response.body = body.into();
|
||||
HttpResponse(Some(response), self.pool.take().unwrap())
|
||||
HttpResponse(response, self.pool)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -560,7 +649,14 @@ impl HttpResponseBuilder {
|
||||
///
|
||||
/// `HttpResponseBuilder` can not be used after this call.
|
||||
pub fn json<T: Serialize>(&mut self, value: T) -> HttpResponse {
|
||||
match serde_json::to_string(&value) {
|
||||
self.json2(&value)
|
||||
}
|
||||
|
||||
/// Set a json body and generate `HttpResponse`
|
||||
///
|
||||
/// `HttpResponseBuilder` can not be used after this call.
|
||||
pub fn json2<T: Serialize>(&mut self, value: &T) -> HttpResponse {
|
||||
match serde_json::to_string(value) {
|
||||
Ok(body) => {
|
||||
let contains = if let Some(parts) = parts(&mut self.response, &self.err)
|
||||
{
|
||||
@@ -589,8 +685,8 @@ impl HttpResponseBuilder {
|
||||
/// This method construct new `HttpResponseBuilder`
|
||||
pub fn take(&mut self) -> HttpResponseBuilder {
|
||||
HttpResponseBuilder {
|
||||
pool: self.pool,
|
||||
response: self.response.take(),
|
||||
pool: self.pool.take(),
|
||||
err: self.err.take(),
|
||||
cookies: self.cookies.take(),
|
||||
}
|
||||
@@ -770,13 +866,9 @@ impl<'a> From<&'a ClientResponse> for HttpResponseBuilder {
|
||||
|
||||
impl<'a, S> From<&'a HttpRequest<S>> for HttpResponseBuilder {
|
||||
fn from(req: &'a HttpRequest<S>) -> HttpResponseBuilder {
|
||||
if let Some(router) = req.router() {
|
||||
router
|
||||
.server_settings()
|
||||
.get_response_builder(StatusCode::OK)
|
||||
} else {
|
||||
HttpResponse::Ok()
|
||||
}
|
||||
req.request()
|
||||
.server_settings()
|
||||
.get_response_builder(StatusCode::OK)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,6 +887,17 @@ struct InnerHttpResponse {
|
||||
error: Option<Error>,
|
||||
}
|
||||
|
||||
pub(crate) struct HttpResponseParts {
|
||||
version: Option<Version>,
|
||||
headers: HeaderMap,
|
||||
status: StatusCode,
|
||||
reason: Option<&'static str>,
|
||||
body: Option<Bytes>,
|
||||
encoding: Option<ContentEncoding>,
|
||||
connection_type: Option<ConnectionType>,
|
||||
error: Option<Error>,
|
||||
}
|
||||
|
||||
impl InnerHttpResponse {
|
||||
#[inline]
|
||||
fn new(status: StatusCode, body: Body) -> InnerHttpResponse {
|
||||
@@ -812,38 +915,85 @@ impl InnerHttpResponse {
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// This is for failure, we can not have Send + Sync on Streaming and Actor response
|
||||
fn into_parts(mut self) -> HttpResponseParts {
|
||||
let body = match mem::replace(&mut self.body, Body::Empty) {
|
||||
Body::Empty => None,
|
||||
Body::Binary(mut bin) => Some(bin.take()),
|
||||
Body::Streaming(_) | Body::Actor(_) => {
|
||||
error!("Streaming or Actor body is not support by error response");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
HttpResponseParts {
|
||||
body,
|
||||
version: self.version,
|
||||
headers: self.headers,
|
||||
status: self.status,
|
||||
reason: self.reason,
|
||||
encoding: self.encoding,
|
||||
connection_type: self.connection_type,
|
||||
error: self.error,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_parts(parts: HttpResponseParts) -> InnerHttpResponse {
|
||||
let body = if let Some(ref body) = parts.body {
|
||||
Body::Binary(body.clone().into())
|
||||
} else {
|
||||
Body::Empty
|
||||
};
|
||||
|
||||
InnerHttpResponse {
|
||||
body,
|
||||
status: parts.status,
|
||||
version: parts.version,
|
||||
headers: parts.headers,
|
||||
reason: parts.reason,
|
||||
chunked: None,
|
||||
encoding: parts.encoding,
|
||||
connection_type: parts.connection_type,
|
||||
response_size: 0,
|
||||
write_capacity: MAX_WRITE_BUFFER_SIZE,
|
||||
error: parts.error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal use only! unsafe
|
||||
pub(crate) struct HttpResponsePool(VecDeque<Box<InnerHttpResponse>>);
|
||||
/// Internal use only!
|
||||
pub(crate) struct HttpResponsePool(RefCell<VecDeque<Box<InnerHttpResponse>>>);
|
||||
|
||||
thread_local!(static POOL: Rc<UnsafeCell<HttpResponsePool>> = HttpResponsePool::pool());
|
||||
thread_local!(static POOL: &'static HttpResponsePool = HttpResponsePool::pool());
|
||||
|
||||
impl HttpResponsePool {
|
||||
pub fn pool() -> Rc<UnsafeCell<HttpResponsePool>> {
|
||||
Rc::new(UnsafeCell::new(HttpResponsePool(VecDeque::with_capacity(
|
||||
128,
|
||||
))))
|
||||
fn pool() -> &'static HttpResponsePool {
|
||||
let pool = HttpResponsePool(RefCell::new(VecDeque::with_capacity(128)));
|
||||
Box::leak(Box::new(pool))
|
||||
}
|
||||
|
||||
pub fn get_pool() -> &'static HttpResponsePool {
|
||||
POOL.with(|p| *p)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_builder(
|
||||
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode,
|
||||
pool: &'static HttpResponsePool, status: StatusCode,
|
||||
) -> HttpResponseBuilder {
|
||||
let p = unsafe { &mut *pool.as_ref().get() };
|
||||
if let Some(mut msg) = p.0.pop_front() {
|
||||
if let Some(mut msg) = pool.0.borrow_mut().pop_front() {
|
||||
msg.status = status;
|
||||
HttpResponseBuilder {
|
||||
pool,
|
||||
response: Some(msg),
|
||||
pool: Some(Rc::clone(pool)),
|
||||
err: None,
|
||||
cookies: None,
|
||||
}
|
||||
} else {
|
||||
let msg = Box::new(InnerHttpResponse::new(status, Body::Empty));
|
||||
HttpResponseBuilder {
|
||||
pool,
|
||||
response: Some(msg),
|
||||
pool: Some(Rc::clone(pool)),
|
||||
err: None,
|
||||
cookies: None,
|
||||
}
|
||||
@@ -852,16 +1002,15 @@ impl HttpResponsePool {
|
||||
|
||||
#[inline]
|
||||
pub fn get_response(
|
||||
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode, body: Body,
|
||||
pool: &'static HttpResponsePool, status: StatusCode, body: Body,
|
||||
) -> HttpResponse {
|
||||
let p = unsafe { &mut *pool.as_ref().get() };
|
||||
if let Some(mut msg) = p.0.pop_front() {
|
||||
if let Some(mut msg) = pool.0.borrow_mut().pop_front() {
|
||||
msg.status = status;
|
||||
msg.body = body;
|
||||
HttpResponse(Some(msg), Rc::clone(pool))
|
||||
HttpResponse(msg, pool)
|
||||
} else {
|
||||
let msg = Box::new(InnerHttpResponse::new(status, body));
|
||||
HttpResponse(Some(msg), Rc::clone(pool))
|
||||
HttpResponse(msg, pool)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -875,13 +1024,10 @@ impl HttpResponsePool {
|
||||
POOL.with(|pool| HttpResponsePool::get_response(pool, status, body))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))]
|
||||
fn release(
|
||||
pool: &Rc<UnsafeCell<HttpResponsePool>>, mut inner: Box<InnerHttpResponse>,
|
||||
) {
|
||||
let pool = unsafe { &mut *pool.as_ref().get() };
|
||||
if pool.0.len() < 128 {
|
||||
#[inline]
|
||||
fn release(&self, mut inner: Box<InnerHttpResponse>) {
|
||||
let mut p = self.0.borrow_mut();
|
||||
if p.len() < 128 {
|
||||
inner.headers.clear();
|
||||
inner.version = None;
|
||||
inner.chunked = None;
|
||||
@@ -891,7 +1037,7 @@ impl HttpResponsePool {
|
||||
inner.response_size = 0;
|
||||
inner.error = None;
|
||||
inner.write_capacity = MAX_WRITE_BUFFER_SIZE;
|
||||
pool.0.push_front(inner);
|
||||
p.push_front(inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -902,10 +1048,10 @@ mod tests {
|
||||
use body::Binary;
|
||||
use http;
|
||||
use http::header::{HeaderValue, CONTENT_TYPE, COOKIE};
|
||||
use http::{Method, Uri};
|
||||
use std::str::FromStr;
|
||||
use time::Duration;
|
||||
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let resp = HttpResponse::Ok()
|
||||
@@ -918,17 +1064,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_response_cookies() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(COOKIE, HeaderValue::from_static("cookie1=value1"));
|
||||
headers.insert(COOKIE, HeaderValue::from_static("cookie2=value2"));
|
||||
|
||||
let req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
headers,
|
||||
None,
|
||||
);
|
||||
let req = TestRequest::default()
|
||||
.header(COOKIE, "cookie1=value1")
|
||||
.header(COOKIE, "cookie2=value2")
|
||||
.finish();
|
||||
let cookies = req.cookies().unwrap();
|
||||
|
||||
let resp = HttpResponse::Ok()
|
||||
@@ -939,8 +1078,7 @@ mod tests {
|
||||
.http_only(true)
|
||||
.max_age(Duration::days(1))
|
||||
.finish(),
|
||||
)
|
||||
.del_cookie(&cookies[0])
|
||||
).del_cookie(&cookies[0])
|
||||
.finish();
|
||||
|
||||
let mut val: Vec<_> = resp
|
||||
@@ -950,13 +1088,36 @@ mod tests {
|
||||
.map(|v| v.to_str().unwrap().to_owned())
|
||||
.collect();
|
||||
val.sort();
|
||||
assert!(val[0].starts_with("cookie2=; Max-Age=0;"));
|
||||
assert!(val[0].starts_with("cookie1=; Max-Age=0;"));
|
||||
assert_eq!(
|
||||
val[1],
|
||||
"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_response_cookies() {
|
||||
let mut r = HttpResponse::Ok()
|
||||
.cookie(http::Cookie::new("original", "val100"))
|
||||
.finish();
|
||||
|
||||
r.add_cookie(&http::Cookie::new("cookie2", "val200"))
|
||||
.unwrap();
|
||||
r.add_cookie(&http::Cookie::new("cookie2", "val250"))
|
||||
.unwrap();
|
||||
r.add_cookie(&http::Cookie::new("cookie3", "val300"))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(r.cookies().count(), 4);
|
||||
r.del_cookie("cookie2");
|
||||
|
||||
let mut iter = r.cookies();
|
||||
let v = iter.next().unwrap();
|
||||
assert_eq!((v.name(), v.value()), ("original", "val100"));
|
||||
let v = iter.next().unwrap();
|
||||
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_builder() {
|
||||
let resp = HttpResponse::Ok()
|
||||
@@ -1030,6 +1191,30 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json2() {
|
||||
let resp = HttpResponse::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]);
|
||||
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
|
||||
assert_eq!(ct, HeaderValue::from_static("application/json"));
|
||||
assert_eq!(
|
||||
*resp.body(),
|
||||
Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json2_ct() {
|
||||
let resp = HttpResponse::build(StatusCode::OK)
|
||||
.header(CONTENT_TYPE, "text/json")
|
||||
.json2(&vec!["v1", "v2", "v3"]);
|
||||
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
|
||||
assert_eq!(ct, HeaderValue::from_static("text/json"));
|
||||
assert_eq!(
|
||||
*resp.body(),
|
||||
Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))
|
||||
);
|
||||
}
|
||||
|
||||
impl Body {
|
||||
pub(crate) fn bin_ref(&self) -> &Binary {
|
||||
match *self {
|
||||
@@ -1041,7 +1226,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_into_response() {
|
||||
let req = HttpRequest::default();
|
||||
let req = TestRequest::default().finish();
|
||||
|
||||
let resp: HttpResponse = "test".into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
@@ -1164,11 +1349,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_into_builder() {
|
||||
let resp: HttpResponse = "test".into();
|
||||
let mut resp: HttpResponse = "test".into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
resp.add_cookie(&http::Cookie::new("cookie1", "val100"))
|
||||
.unwrap();
|
||||
|
||||
let mut builder = resp.into_builder();
|
||||
let resp = builder.status(StatusCode::BAD_REQUEST).finish();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let cookie = resp.cookies().next().unwrap();
|
||||
assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100"));
|
||||
}
|
||||
}
|
||||
|
||||
126
src/info.rs
126
src/info.rs
@@ -1,24 +1,26 @@
|
||||
use http::header::{self, HeaderName};
|
||||
use httpmessage::HttpMessage;
|
||||
use httprequest::HttpRequest;
|
||||
use std::str::FromStr;
|
||||
use server::Request;
|
||||
|
||||
const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR";
|
||||
const X_FORWARDED_HOST: &str = "X-FORWARDED-HOST";
|
||||
const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO";
|
||||
const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for";
|
||||
const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host";
|
||||
const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto";
|
||||
|
||||
/// `HttpRequest` connection information
|
||||
pub struct ConnectionInfo<'a> {
|
||||
scheme: &'a str,
|
||||
host: &'a str,
|
||||
remote: Option<&'a str>,
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ConnectionInfo {
|
||||
scheme: String,
|
||||
host: String,
|
||||
remote: Option<String>,
|
||||
peer: Option<String>,
|
||||
}
|
||||
|
||||
impl<'a> ConnectionInfo<'a> {
|
||||
impl ConnectionInfo {
|
||||
/// Create *ConnectionInfo* instance for a request.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
|
||||
pub fn new<S>(req: &'a HttpRequest<S>) -> ConnectionInfo<'a> {
|
||||
#[cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(cyclomatic_complexity)
|
||||
)]
|
||||
pub fn update(&mut self, req: &Request) {
|
||||
let mut host = None;
|
||||
let mut scheme = None;
|
||||
let mut remote = None;
|
||||
@@ -55,7 +57,7 @@ impl<'a> ConnectionInfo<'a> {
|
||||
if scheme.is_none() {
|
||||
if let Some(h) = req
|
||||
.headers()
|
||||
.get(HeaderName::from_str(X_FORWARDED_PROTO).unwrap())
|
||||
.get(HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap())
|
||||
{
|
||||
if let Ok(h) = h.to_str() {
|
||||
scheme = h.split(',').next().map(|v| v.trim());
|
||||
@@ -63,12 +65,8 @@ impl<'a> ConnectionInfo<'a> {
|
||||
}
|
||||
if scheme.is_none() {
|
||||
scheme = req.uri().scheme_part().map(|a| a.as_str());
|
||||
if scheme.is_none() {
|
||||
if let Some(router) = req.router() {
|
||||
if router.server_settings().secure() {
|
||||
scheme = Some("https")
|
||||
}
|
||||
}
|
||||
if scheme.is_none() && req.server_settings().secure() {
|
||||
scheme = Some("https")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +75,7 @@ impl<'a> ConnectionInfo<'a> {
|
||||
if host.is_none() {
|
||||
if let Some(h) = req
|
||||
.headers()
|
||||
.get(HeaderName::from_str(X_FORWARDED_HOST).unwrap())
|
||||
.get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap())
|
||||
{
|
||||
if let Ok(h) = h.to_str() {
|
||||
host = h.split(',').next().map(|v| v.trim());
|
||||
@@ -90,9 +88,7 @@ impl<'a> ConnectionInfo<'a> {
|
||||
if host.is_none() {
|
||||
host = req.uri().authority_part().map(|a| a.as_str());
|
||||
if host.is_none() {
|
||||
if let Some(router) = req.router() {
|
||||
host = Some(router.server_settings().host());
|
||||
}
|
||||
host = Some(req.server_settings().host());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,7 +98,7 @@ impl<'a> ConnectionInfo<'a> {
|
||||
if remote.is_none() {
|
||||
if let Some(h) = req
|
||||
.headers()
|
||||
.get(HeaderName::from_str(X_FORWARDED_FOR).unwrap())
|
||||
.get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap())
|
||||
{
|
||||
if let Ok(h) = h.to_str() {
|
||||
remote = h.split(',').next().map(|v| v.trim());
|
||||
@@ -114,12 +110,10 @@ impl<'a> ConnectionInfo<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionInfo {
|
||||
scheme: scheme.unwrap_or("http"),
|
||||
host: host.unwrap_or("localhost"),
|
||||
remote,
|
||||
peer,
|
||||
}
|
||||
self.scheme = scheme.unwrap_or("http").to_owned();
|
||||
self.host = host.unwrap_or("localhost").to_owned();
|
||||
self.remote = remote.map(|s| s.to_owned());
|
||||
self.peer = peer;
|
||||
}
|
||||
|
||||
/// Scheme of the request.
|
||||
@@ -131,7 +125,7 @@ impl<'a> ConnectionInfo<'a> {
|
||||
/// - Uri
|
||||
#[inline]
|
||||
pub fn scheme(&self) -> &str {
|
||||
self.scheme
|
||||
&self.scheme
|
||||
}
|
||||
|
||||
/// Hostname of the request.
|
||||
@@ -144,7 +138,7 @@ impl<'a> ConnectionInfo<'a> {
|
||||
/// - Uri
|
||||
/// - Server hostname
|
||||
pub fn host(&self) -> &str {
|
||||
self.host
|
||||
&self.host
|
||||
}
|
||||
|
||||
/// Remote IP of client initiated HTTP request.
|
||||
@@ -156,7 +150,7 @@ impl<'a> ConnectionInfo<'a> {
|
||||
/// - peer name of opened socket
|
||||
#[inline]
|
||||
pub fn remote(&self) -> Option<&str> {
|
||||
if let Some(r) = self.remote {
|
||||
if let Some(ref r) = self.remote {
|
||||
Some(r)
|
||||
} else if let Some(ref peer) = self.peer {
|
||||
Some(peer)
|
||||
@@ -169,60 +163,58 @@ impl<'a> ConnectionInfo<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::header::HeaderValue;
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_forwarded() {
|
||||
let req = HttpRequest::default();
|
||||
let info = ConnectionInfo::new(&req);
|
||||
let req = TestRequest::default().request();
|
||||
let mut info = ConnectionInfo::default();
|
||||
info.update(&req);
|
||||
assert_eq!(info.scheme(), "http");
|
||||
assert_eq!(info.host(), "localhost");
|
||||
assert_eq!(info.host(), "localhost:8080");
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.headers_mut().insert(
|
||||
header::FORWARDED,
|
||||
HeaderValue::from_static(
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::FORWARDED,
|
||||
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
|
||||
),
|
||||
);
|
||||
).request();
|
||||
|
||||
let info = ConnectionInfo::new(&req);
|
||||
let mut info = ConnectionInfo::default();
|
||||
info.update(&req);
|
||||
assert_eq!(info.scheme(), "https");
|
||||
assert_eq!(info.host(), "rust-lang.org");
|
||||
assert_eq!(info.remote(), Some("192.0.2.60"));
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.headers_mut()
|
||||
.insert(header::HOST, HeaderValue::from_static("rust-lang.org"));
|
||||
let req = TestRequest::default()
|
||||
.header(header::HOST, "rust-lang.org")
|
||||
.request();
|
||||
|
||||
let info = ConnectionInfo::new(&req);
|
||||
let mut info = ConnectionInfo::default();
|
||||
info.update(&req);
|
||||
assert_eq!(info.scheme(), "http");
|
||||
assert_eq!(info.host(), "rust-lang.org");
|
||||
assert_eq!(info.remote(), None);
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.headers_mut().insert(
|
||||
HeaderName::from_str(X_FORWARDED_FOR).unwrap(),
|
||||
HeaderValue::from_static("192.0.2.60"),
|
||||
);
|
||||
let info = ConnectionInfo::new(&req);
|
||||
let req = TestRequest::default()
|
||||
.header(X_FORWARDED_FOR, "192.0.2.60")
|
||||
.request();
|
||||
let mut info = ConnectionInfo::default();
|
||||
info.update(&req);
|
||||
assert_eq!(info.remote(), Some("192.0.2.60"));
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.headers_mut().insert(
|
||||
HeaderName::from_str(X_FORWARDED_HOST).unwrap(),
|
||||
HeaderValue::from_static("192.0.2.60"),
|
||||
);
|
||||
let info = ConnectionInfo::new(&req);
|
||||
let req = TestRequest::default()
|
||||
.header(X_FORWARDED_HOST, "192.0.2.60")
|
||||
.request();
|
||||
let mut info = ConnectionInfo::default();
|
||||
info.update(&req);
|
||||
assert_eq!(info.host(), "192.0.2.60");
|
||||
assert_eq!(info.remote(), None);
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.headers_mut().insert(
|
||||
HeaderName::from_str(X_FORWARDED_PROTO).unwrap(),
|
||||
HeaderValue::from_static("https"),
|
||||
);
|
||||
let info = ConnectionInfo::new(&req);
|
||||
let req = TestRequest::default()
|
||||
.header(X_FORWARDED_PROTO, "https")
|
||||
.request();
|
||||
let mut info = ConnectionInfo::default();
|
||||
info.update(&req);
|
||||
assert_eq!(info.scheme(), "https");
|
||||
}
|
||||
}
|
||||
|
||||
230
src/json.rs
230
src/json.rs
@@ -1,4 +1,4 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use bytes::BytesMut;
|
||||
use futures::{Future, Poll, Stream};
|
||||
use http::header::CONTENT_LENGTH;
|
||||
use std::fmt;
|
||||
@@ -10,7 +10,7 @@ use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
|
||||
use error::{Error, JsonPayloadError, PayloadError};
|
||||
use error::{Error, JsonPayloadError};
|
||||
use handler::{FromRequest, Responder};
|
||||
use http::StatusCode;
|
||||
use httpmessage::HttpMessage;
|
||||
@@ -69,7 +69,9 @@ use httpresponse::HttpResponse;
|
||||
/// }
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> Result<Json<MyObj>> {
|
||||
/// Ok(Json(MyObj{name: req.match_info().query("name")?}))
|
||||
/// Ok(Json(MyObj {
|
||||
/// name: req.match_info().query("name")?,
|
||||
/// }))
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
@@ -138,12 +140,12 @@ where
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
|
||||
let req = req.clone();
|
||||
let req2 = req.clone();
|
||||
let err = Rc::clone(&cfg.ehandler);
|
||||
Box::new(
|
||||
JsonBody::new(req.clone())
|
||||
JsonBody::new(req)
|
||||
.limit(cfg.limit)
|
||||
.map_err(move |e| (*err)(e, req))
|
||||
.map_err(move |e| (*err)(e, &req2))
|
||||
.map(Json),
|
||||
)
|
||||
}
|
||||
@@ -154,7 +156,7 @@ where
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Json, HttpResponse, Result, http, error};
|
||||
/// use actix_web::{error, http, App, HttpResponse, Json, Result};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
@@ -167,21 +169,21 @@ where
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/index.html", |r| {
|
||||
/// r.method(http::Method::POST)
|
||||
/// .with(index)
|
||||
/// .limit(4096) // <- change json extractor configuration
|
||||
/// .error_handler(|err, req| { // <- create custom error response
|
||||
/// error::InternalError::from_response(
|
||||
/// err, HttpResponse::Conflict().finish()).into()
|
||||
/// });
|
||||
/// });
|
||||
/// let app = App::new().resource("/index.html", |r| {
|
||||
/// r.method(http::Method::POST)
|
||||
/// .with_config(index, |cfg| {
|
||||
/// cfg.0.limit(4096) // <- change json extractor configuration
|
||||
/// .error_handler(|err, req| { // <- create custom error response
|
||||
/// error::InternalError::from_response(
|
||||
/// err, HttpResponse::Conflict().finish()).into()
|
||||
/// });
|
||||
/// })
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
pub struct JsonConfig<S> {
|
||||
limit: usize,
|
||||
ehandler: Rc<Fn(JsonPayloadError, HttpRequest<S>) -> Error>,
|
||||
ehandler: Rc<Fn(JsonPayloadError, &HttpRequest<S>) -> Error>,
|
||||
}
|
||||
|
||||
impl<S> JsonConfig<S> {
|
||||
@@ -194,7 +196,7 @@ impl<S> JsonConfig<S> {
|
||||
/// Set custom error handler
|
||||
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(JsonPayloadError, HttpRequest<S>) -> Error + 'static,
|
||||
F: Fn(JsonPayloadError, &HttpRequest<S>) -> Error + 'static,
|
||||
{
|
||||
self.ehandler = Rc::new(f);
|
||||
self
|
||||
@@ -223,15 +225,15 @@ impl<S> Default for JsonConfig<S> {
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse};
|
||||
/// use futures::future::Future;
|
||||
/// use actix_web::{AsyncResponder, HttpRequest, HttpResponse, HttpMessage, Error};
|
||||
///
|
||||
/// #[derive(Deserialize, Debug)]
|
||||
/// struct MyObj {
|
||||
/// name: String,
|
||||
/// }
|
||||
///
|
||||
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
|
||||
/// req.json() // <- get JsonBody future
|
||||
/// .from_err()
|
||||
/// .and_then(|val: MyObj| { // <- deserialized value
|
||||
@@ -241,19 +243,48 @@ impl<S> Default for JsonConfig<S> {
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub struct JsonBody<T, U: DeserializeOwned> {
|
||||
pub struct JsonBody<T: HttpMessage, U: DeserializeOwned> {
|
||||
limit: usize,
|
||||
req: Option<T>,
|
||||
length: Option<usize>,
|
||||
stream: Option<T::Stream>,
|
||||
err: Option<JsonPayloadError>,
|
||||
fut: Option<Box<Future<Item = U, Error = JsonPayloadError>>>,
|
||||
}
|
||||
|
||||
impl<T, U: DeserializeOwned> JsonBody<T, U> {
|
||||
impl<T: HttpMessage, U: DeserializeOwned> JsonBody<T, U> {
|
||||
/// Create `JsonBody` for request.
|
||||
pub fn new(req: T) -> Self {
|
||||
pub fn new(req: &T) -> Self {
|
||||
// check content-type
|
||||
let json = if let Ok(Some(mime)) = req.mime_type() {
|
||||
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !json {
|
||||
return JsonBody {
|
||||
limit: 262_144,
|
||||
length: None,
|
||||
stream: None,
|
||||
fut: None,
|
||||
err: Some(JsonPayloadError::ContentType),
|
||||
};
|
||||
}
|
||||
|
||||
let mut len = None;
|
||||
if let Some(l) = req.headers().get(CONTENT_LENGTH) {
|
||||
if let Ok(s) = l.to_str() {
|
||||
if let Ok(l) = s.parse::<usize>() {
|
||||
len = Some(l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonBody {
|
||||
limit: 262_144,
|
||||
req: Some(req),
|
||||
length: len,
|
||||
stream: Some(req.payload()),
|
||||
fut: None,
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,56 +295,41 @@ impl<T, U: DeserializeOwned> JsonBody<T, U> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U: DeserializeOwned + 'static> Future for JsonBody<T, U>
|
||||
where
|
||||
T: HttpMessage + Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
{
|
||||
impl<T: HttpMessage + 'static, U: DeserializeOwned + 'static> Future for JsonBody<T, U> {
|
||||
type Item = U;
|
||||
type Error = JsonPayloadError;
|
||||
|
||||
fn poll(&mut self) -> Poll<U, JsonPayloadError> {
|
||||
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(JsonPayloadError::Overflow);
|
||||
}
|
||||
} else {
|
||||
return Err(JsonPayloadError::Overflow);
|
||||
}
|
||||
}
|
||||
}
|
||||
// check content-type
|
||||
|
||||
let json = if let Ok(Some(mime)) = req.mime_type() {
|
||||
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !json {
|
||||
return Err(JsonPayloadError::ContentType);
|
||||
}
|
||||
|
||||
let limit = self.limit;
|
||||
let fut = req
|
||||
.from_err()
|
||||
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(JsonPayloadError::Overflow)
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok(body)
|
||||
}
|
||||
})
|
||||
.and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
|
||||
self.fut = Some(Box::new(fut));
|
||||
if let Some(ref mut fut) = self.fut {
|
||||
return fut.poll();
|
||||
}
|
||||
|
||||
self.fut
|
||||
.as_mut()
|
||||
if let Some(err) = self.err.take() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let limit = self.limit;
|
||||
if let Some(len) = self.length.take() {
|
||||
if len > limit {
|
||||
return Err(JsonPayloadError::Overflow);
|
||||
}
|
||||
}
|
||||
|
||||
let fut = self
|
||||
.stream
|
||||
.take()
|
||||
.expect("JsonBody could not be used second time")
|
||||
.poll()
|
||||
.from_err()
|
||||
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(JsonPayloadError::Overflow)
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok(body)
|
||||
}
|
||||
}).and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
|
||||
self.fut = Some(Box::new(fut));
|
||||
self.poll()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +341,8 @@ mod tests {
|
||||
use http::header;
|
||||
|
||||
use handler::Handler;
|
||||
use with::{ExtractorConfig, With};
|
||||
use test::TestRequest;
|
||||
use with::With;
|
||||
|
||||
impl PartialEq for JsonPayloadError {
|
||||
fn eq(&self, other: &JsonPayloadError) -> bool {
|
||||
@@ -353,7 +370,7 @@ mod tests {
|
||||
let json = Json(MyObject {
|
||||
name: "test".to_owned(),
|
||||
});
|
||||
let resp = json.respond_to(&HttpRequest::default()).unwrap();
|
||||
let resp = json.respond_to(&TestRequest::default().finish()).unwrap();
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
"application/json"
|
||||
@@ -362,41 +379,39 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_json_body() {
|
||||
let req = HttpRequest::default();
|
||||
let req = TestRequest::default().finish();
|
||||
let mut json = req.json::<MyObject>();
|
||||
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/text"),
|
||||
);
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/text"),
|
||||
).finish();
|
||||
let mut json = req.json::<MyObject>();
|
||||
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
);
|
||||
req.headers_mut().insert(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("10000"),
|
||||
);
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
).header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("10000"),
|
||||
).finish();
|
||||
let mut json = req.json::<MyObject>().limit(100);
|
||||
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow);
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
);
|
||||
req.headers_mut().insert(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
);
|
||||
req.payload_mut()
|
||||
.unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
).header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.finish();
|
||||
|
||||
let mut json = req.json::<MyObject>();
|
||||
assert_eq!(
|
||||
json.poll().ok().unwrap(),
|
||||
@@ -408,24 +423,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_with_json() {
|
||||
let mut cfg = ExtractorConfig::<_, Json<MyObject>>::default();
|
||||
let mut cfg = JsonConfig::default();
|
||||
cfg.limit(4096);
|
||||
let mut handler = With::new(|data: Json<MyObject>| data, cfg);
|
||||
let handler = With::new(|data: Json<MyObject>| data, cfg);
|
||||
|
||||
let req = HttpRequest::default();
|
||||
assert!(handler.handle(req).as_err().is_some());
|
||||
let req = TestRequest::default().finish();
|
||||
assert!(handler.handle(&req).as_err().is_some());
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.headers_mut().insert(
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
);
|
||||
req.headers_mut().insert(
|
||||
).header(
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("16"),
|
||||
);
|
||||
req.payload_mut()
|
||||
.unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
|
||||
assert!(handler.handle(req).as_err().is_none())
|
||||
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.finish();
|
||||
assert!(handler.handle(&req).as_err().is_none())
|
||||
}
|
||||
}
|
||||
|
||||
102
src/lib.rs
102
src/lib.rs
@@ -2,21 +2,21 @@
|
||||
//! for Rust.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use actix_web::{server, App, Path};
|
||||
//! use actix_web::{server, App, Path, Responder};
|
||||
//! # use std::thread;
|
||||
//!
|
||||
//! fn index(info: Path<(String, u32)>) -> String {
|
||||
//! format!("Hello {}! id:{}", info.0, info.1)
|
||||
//! fn index(info: Path<(String, u32)>) -> impl Responder {
|
||||
//! format!("Hello {}! id:{}", info.0, info.1)
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! # thread::spawn(|| {
|
||||
//! server::new(
|
||||
//! || App::new()
|
||||
//! .resource("/{name}/{id}/index.html", |r| r.with(index)))
|
||||
//! .bind("127.0.0.1:8080").unwrap()
|
||||
//! # thread::spawn(|| {
|
||||
//! server::new(|| {
|
||||
//! App::new().resource("/{name}/{id}/index.html", |r| r.with(index))
|
||||
//! }).bind("127.0.0.1:8080")
|
||||
//! .unwrap()
|
||||
//! .run();
|
||||
//! # });
|
||||
//! # });
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
@@ -59,29 +59,30 @@
|
||||
//! * SSL support with OpenSSL or `native-tls`
|
||||
//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`)
|
||||
//! * Built on top of [Actix actor framework](https://github.com/actix/actix)
|
||||
//! * Supported Rust version: 1.24 or later
|
||||
//! * Supported Rust version: 1.26 or later
|
||||
//!
|
||||
//! ## Package feature
|
||||
//!
|
||||
//! * `tls` - enables ssl support via `native-tls` crate
|
||||
//! * `alpn` - enables ssl support via `openssl` crate, require for `http/2`
|
||||
//! support
|
||||
//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2`
|
||||
//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2`
|
||||
//! * `uds` - enables support for making client requests via Unix Domain Sockets.
|
||||
//! Unix only. Not necessary for *serving* requests.
|
||||
//! * `session` - enables session support, includes `ring` crate as
|
||||
//! dependency
|
||||
//! * `brotli` - enables `brotli` compression support, requires `c`
|
||||
//! compiler
|
||||
//! * `flate-c` - enables `gzip`, `deflate` compression support, requires
|
||||
//! * `flate2-c` - enables `gzip`, `deflate` compression support, requires
|
||||
//! `c` compiler
|
||||
//! * `flate-rust` - experimental rust based implementation for
|
||||
//! * `flate2-rust` - experimental rust based implementation for
|
||||
//! `gzip`, `deflate` compression.
|
||||
//!
|
||||
#![cfg_attr(actix_nightly, feature(
|
||||
specialization, // for impl ErrorResponse for std::error::Error
|
||||
extern_prelude,
|
||||
tool_lints,
|
||||
))]
|
||||
#![cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(decimal_literal_representation, suspicious_arithmetic_impl)
|
||||
)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
@@ -99,21 +100,28 @@ extern crate failure;
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
extern crate askama_escape;
|
||||
extern crate cookie;
|
||||
extern crate futures_cpupool;
|
||||
extern crate http as modhttp;
|
||||
extern crate http_range;
|
||||
extern crate httparse;
|
||||
extern crate language_tags;
|
||||
extern crate libc;
|
||||
extern crate lazycell;
|
||||
extern crate mime;
|
||||
extern crate mime_guess;
|
||||
extern crate mio;
|
||||
extern crate net2;
|
||||
extern crate parking_lot;
|
||||
extern crate rand;
|
||||
extern crate slab;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio;
|
||||
extern crate tokio_current_thread;
|
||||
extern crate tokio_io;
|
||||
extern crate tokio_reactor;
|
||||
extern crate tokio_tcp;
|
||||
extern crate tokio_timer;
|
||||
#[cfg(all(unix, feature = "uds"))]
|
||||
extern crate tokio_uds;
|
||||
extern crate url;
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
@@ -124,12 +132,15 @@ extern crate encoding;
|
||||
extern crate flate2;
|
||||
extern crate h2 as http2;
|
||||
extern crate num_cpus;
|
||||
extern crate serde_urlencoded;
|
||||
#[macro_use]
|
||||
extern crate percent_encoding;
|
||||
extern crate serde_json;
|
||||
extern crate serde_urlencoded;
|
||||
extern crate smallvec;
|
||||
|
||||
extern crate actix_net;
|
||||
#[macro_use]
|
||||
extern crate actix;
|
||||
extern crate actix as actix_inner;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
@@ -145,10 +156,20 @@ extern crate openssl;
|
||||
#[cfg(feature = "openssl")]
|
||||
extern crate tokio_openssl;
|
||||
|
||||
#[cfg(feature = "rust-tls")]
|
||||
extern crate rustls;
|
||||
#[cfg(feature = "rust-tls")]
|
||||
extern crate tokio_rustls;
|
||||
#[cfg(feature = "rust-tls")]
|
||||
extern crate webpki;
|
||||
#[cfg(feature = "rust-tls")]
|
||||
extern crate webpki_roots;
|
||||
|
||||
mod application;
|
||||
mod body;
|
||||
mod context;
|
||||
mod de;
|
||||
mod extensions;
|
||||
mod extractor;
|
||||
mod handler;
|
||||
mod header;
|
||||
@@ -182,6 +203,7 @@ pub use application::App;
|
||||
pub use body::{Binary, Body};
|
||||
pub use context::HttpContext;
|
||||
pub use error::{Error, ResponseError, Result};
|
||||
pub use extensions::Extensions;
|
||||
pub use extractor::{Form, Path, Query};
|
||||
pub use handler::{
|
||||
AsyncResponder, Either, FromRequest, FutureResponse, Responder, State,
|
||||
@@ -191,10 +213,17 @@ pub use httprequest::HttpRequest;
|
||||
pub use httpresponse::HttpResponse;
|
||||
pub use json::Json;
|
||||
pub use scope::Scope;
|
||||
pub use server::Request;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "0.6.2", note = "please use `use actix_web::ws::WsWriter`")]
|
||||
pub use ws::WsWriter;
|
||||
pub mod actix {
|
||||
//! Re-exports [actix's](https://docs.rs/actix/) prelude
|
||||
pub use super::actix_inner::actors::resolver;
|
||||
pub use super::actix_inner::actors::signal;
|
||||
pub use super::actix_inner::fut;
|
||||
pub use super::actix_inner::msgs;
|
||||
pub use super::actix_inner::prelude::*;
|
||||
pub use super::actix_inner::{run, spawn};
|
||||
}
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
pub(crate) const HAS_OPENSSL: bool = true;
|
||||
@@ -206,6 +235,11 @@ pub(crate) const HAS_TLS: bool = true;
|
||||
#[cfg(not(feature = "tls"))]
|
||||
pub(crate) const HAS_TLS: bool = false;
|
||||
|
||||
#[cfg(feature = "rust-tls")]
|
||||
pub(crate) const HAS_RUSTLS: bool = true;
|
||||
#[cfg(not(feature = "rust-tls"))]
|
||||
pub(crate) const HAS_RUSTLS: bool = false;
|
||||
|
||||
pub mod dev {
|
||||
//! The `actix-web` prelude for library developers
|
||||
//!
|
||||
@@ -219,17 +253,18 @@ pub mod dev {
|
||||
|
||||
pub use body::BodyStream;
|
||||
pub use context::Drain;
|
||||
pub use extractor::{FormConfig, PayloadConfig};
|
||||
pub use extractor::{FormConfig, PayloadConfig, QueryConfig, PathConfig, EitherConfig, EitherCollisionStrategy};
|
||||
pub use handler::{AsyncResult, Handler};
|
||||
pub use httpmessage::{MessageBody, UrlEncoded};
|
||||
pub use httpmessage::{MessageBody, Readlines, UrlEncoded};
|
||||
pub use httpresponse::HttpResponseBuilder;
|
||||
pub use info::ConnectionInfo;
|
||||
pub use json::{JsonBody, JsonConfig};
|
||||
pub use param::{FromParam, Params};
|
||||
pub use resource::ResourceHandler;
|
||||
pub use payload::{Payload, PayloadBuffer};
|
||||
pub use pipeline::Pipeline;
|
||||
pub use resource::Resource;
|
||||
pub use route::Route;
|
||||
pub use router::{Resource, ResourceType, Router};
|
||||
pub use with::ExtractorConfig;
|
||||
pub use router::{ResourceDef, ResourceInfo, ResourceType, Router};
|
||||
}
|
||||
|
||||
pub mod http {
|
||||
@@ -242,12 +277,15 @@ pub mod http {
|
||||
pub use modhttp::{uri, Error, Extensions, HeaderMap, HttpTryFrom, Uri};
|
||||
|
||||
pub use cookie::{Cookie, CookieBuilder};
|
||||
pub use http_range::HttpRange;
|
||||
|
||||
pub use helpers::NormalizePath;
|
||||
|
||||
/// Various http headers
|
||||
pub mod header {
|
||||
pub use header::*;
|
||||
pub use header::{
|
||||
Charset, ContentDisposition, DispositionParam, DispositionType, LanguageTag,
|
||||
};
|
||||
}
|
||||
pub use header::ContentEncoding;
|
||||
pub use httpresponse::ConnectionType;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//! constructed backend.
|
||||
//!
|
||||
//! Cors middleware could be used as parameter for `App::middleware()` or
|
||||
//! `ResourceHandler::middleware()` methods. But you have to use
|
||||
//! `Resource::middleware()` methods. But you have to use
|
||||
//! `Cors::for_app()` method to support *preflight* OPTIONS request.
|
||||
//!
|
||||
//!
|
||||
@@ -59,7 +59,9 @@ use httpmessage::HttpMessage;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use middleware::{Middleware, Response, Started};
|
||||
use resource::ResourceHandler;
|
||||
use resource::Resource;
|
||||
use router::ResourceDef;
|
||||
use server::Request;
|
||||
|
||||
/// A set of errors that can occur during processing CORS
|
||||
#[derive(Debug, Fail)]
|
||||
@@ -207,6 +209,7 @@ impl Default for Cors {
|
||||
}
|
||||
|
||||
impl Cors {
|
||||
/// Build a new CORS middleware instance
|
||||
pub fn build() -> CorsBuilder<()> {
|
||||
CorsBuilder {
|
||||
cors: Some(Inner {
|
||||
@@ -275,14 +278,16 @@ impl Cors {
|
||||
/// adds route for *OPTIONS* preflight requests.
|
||||
///
|
||||
/// It is possible to register *Cors* middleware with
|
||||
/// `ResourceHandler::middleware()` method, but in that case *Cors*
|
||||
/// `Resource::middleware()` method, but in that case *Cors*
|
||||
/// middleware wont be able to handle *OPTIONS* requests.
|
||||
pub fn register<S: 'static>(self, resource: &mut ResourceHandler<S>) {
|
||||
resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok());
|
||||
pub fn register<S: 'static>(self, resource: &mut Resource<S>) {
|
||||
resource
|
||||
.method(Method::OPTIONS)
|
||||
.h(|_: &_| HttpResponse::Ok());
|
||||
resource.middleware(self);
|
||||
}
|
||||
|
||||
fn validate_origin<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CorsError> {
|
||||
fn validate_origin(&self, req: &Request) -> Result<(), CorsError> {
|
||||
if let Some(hdr) = req.headers().get(header::ORIGIN) {
|
||||
if let Ok(origin) = hdr.to_str() {
|
||||
return match self.inner.origins {
|
||||
@@ -302,9 +307,7 @@ impl Cors {
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_allowed_method<S>(
|
||||
&self, req: &mut HttpRequest<S>,
|
||||
) -> Result<(), CorsError> {
|
||||
fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> {
|
||||
if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) {
|
||||
if let Ok(meth) = hdr.to_str() {
|
||||
if let Ok(method) = Method::try_from(meth) {
|
||||
@@ -322,9 +325,7 @@ impl Cors {
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_allowed_headers<S>(
|
||||
&self, req: &mut HttpRequest<S>,
|
||||
) -> Result<(), CorsError> {
|
||||
fn validate_allowed_headers(&self, req: &Request) -> Result<(), CorsError> {
|
||||
match self.inner.headers {
|
||||
AllOrSome::All => Ok(()),
|
||||
AllOrSome::Some(ref allowed_headers) => {
|
||||
@@ -355,11 +356,11 @@ impl Cors {
|
||||
}
|
||||
|
||||
impl<S> Middleware<S> for Cors {
|
||||
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
|
||||
if self.inner.preflight && Method::OPTIONS == *req.method() {
|
||||
self.validate_origin(req)?;
|
||||
self.validate_allowed_method(req)?;
|
||||
self.validate_allowed_headers(req)?;
|
||||
self.validate_allowed_method(&req)?;
|
||||
self.validate_allowed_headers(&req)?;
|
||||
|
||||
// allowed headers
|
||||
let headers = if let Some(headers) = self.inner.headers.as_ref() {
|
||||
@@ -386,12 +387,10 @@ impl<S> Middleware<S> for Cors {
|
||||
header::ACCESS_CONTROL_MAX_AGE,
|
||||
format!("{}", max_age).as_str(),
|
||||
);
|
||||
})
|
||||
.if_some(headers, |headers, resp| {
|
||||
}).if_some(headers, |headers, resp| {
|
||||
let _ =
|
||||
resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers);
|
||||
})
|
||||
.if_true(self.inner.origins.is_all(), |resp| {
|
||||
}).if_true(self.inner.origins.is_all(), |resp| {
|
||||
if self.inner.send_wildcard {
|
||||
resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||
} else {
|
||||
@@ -401,17 +400,14 @@ impl<S> Middleware<S> for Cors {
|
||||
origin.clone(),
|
||||
);
|
||||
}
|
||||
})
|
||||
.if_true(self.inner.origins.is_some(), |resp| {
|
||||
}).if_true(self.inner.origins.is_some(), |resp| {
|
||||
resp.header(
|
||||
header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||
self.inner.origins_str.as_ref().unwrap().clone(),
|
||||
);
|
||||
})
|
||||
.if_true(self.inner.supports_credentials, |resp| {
|
||||
}).if_true(self.inner.supports_credentials, |resp| {
|
||||
resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||
})
|
||||
.header(
|
||||
}).header(
|
||||
header::ACCESS_CONTROL_ALLOW_METHODS,
|
||||
&self
|
||||
.inner
|
||||
@@ -419,8 +415,7 @@ impl<S> Middleware<S> for Cors {
|
||||
.iter()
|
||||
.fold(String::new(), |s, v| s + "," + v.as_str())
|
||||
.as_str()[1..],
|
||||
)
|
||||
.finish(),
|
||||
).finish(),
|
||||
))
|
||||
} else {
|
||||
// Only check requests with a origin header.
|
||||
@@ -433,7 +428,7 @@ impl<S> Middleware<S> for Cors {
|
||||
}
|
||||
|
||||
fn response(
|
||||
&self, req: &mut HttpRequest<S>, mut resp: HttpResponse,
|
||||
&self, req: &HttpRequest<S>, mut resp: HttpResponse,
|
||||
) -> Result<Response> {
|
||||
match self.inner.origins {
|
||||
AllOrSome::All => {
|
||||
@@ -515,7 +510,7 @@ pub struct CorsBuilder<S = ()> {
|
||||
methods: bool,
|
||||
error: Option<http::Error>,
|
||||
expose_hdrs: HashSet<HeaderName>,
|
||||
resources: Vec<(String, ResourceHandler<S>)>,
|
||||
resources: Vec<Resource<S>>,
|
||||
app: Option<App<S>>,
|
||||
}
|
||||
|
||||
@@ -795,13 +790,13 @@ impl<S: 'static> CorsBuilder<S> {
|
||||
/// ```
|
||||
pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut CorsBuilder<S>
|
||||
where
|
||||
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static,
|
||||
F: FnOnce(&mut Resource<S>) -> R + 'static,
|
||||
{
|
||||
// add resource handler
|
||||
let mut handler = ResourceHandler::default();
|
||||
f(&mut handler);
|
||||
let mut resource = Resource::new(ResourceDef::new(path));
|
||||
f(&mut resource);
|
||||
|
||||
self.resources.push((path.to_owned(), handler));
|
||||
self.resources.push(resource);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -831,15 +826,15 @@ impl<S: 'static> CorsBuilder<S> {
|
||||
if let AllOrSome::Some(ref origins) = cors.origins {
|
||||
let s = origins
|
||||
.iter()
|
||||
.fold(String::new(), |s, v| s + &v.to_string());
|
||||
cors.origins_str = Some(HeaderValue::try_from(s.as_str()).unwrap());
|
||||
.fold(String::new(), |s, v| format!("{}, {}", s, v));
|
||||
cors.origins_str = Some(HeaderValue::try_from(&s[2..]).unwrap());
|
||||
}
|
||||
|
||||
if !self.expose_hdrs.is_empty() {
|
||||
cors.expose_hdrs = Some(
|
||||
self.expose_hdrs
|
||||
.iter()
|
||||
.fold(String::new(), |s, v| s + v.as_str())[1..]
|
||||
.fold(String::new(), |s, v| format!("{}, {}", s, v.as_str()))[2..]
|
||||
.to_owned(),
|
||||
);
|
||||
}
|
||||
@@ -878,9 +873,9 @@ impl<S: 'static> CorsBuilder<S> {
|
||||
.expect("CorsBuilder has to be constructed with Cors::for_app(app)");
|
||||
|
||||
// register resources
|
||||
for (path, mut resource) in self.resources.drain(..) {
|
||||
for mut resource in self.resources.drain(..) {
|
||||
cors.clone().register(&mut resource);
|
||||
app.register_resource(&path, resource);
|
||||
app.register_resource(resource);
|
||||
}
|
||||
|
||||
app
|
||||
@@ -944,10 +939,9 @@ mod tests {
|
||||
#[test]
|
||||
fn validate_origin_allows_all_origins() {
|
||||
let cors = Cors::default();
|
||||
let mut req =
|
||||
TestRequest::with_header("Origin", "https://www.example.com").finish();
|
||||
let req = TestRequest::with_header("Origin", "https://www.example.com").finish();
|
||||
|
||||
assert!(cors.start(&mut req).ok().unwrap().is_done())
|
||||
assert!(cors.start(&req).ok().unwrap().is_done())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -960,29 +954,28 @@ mod tests {
|
||||
.allowed_header(header::CONTENT_TYPE)
|
||||
.finish();
|
||||
|
||||
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
.method(Method::OPTIONS)
|
||||
.finish();
|
||||
|
||||
assert!(cors.start(&mut req).is_err());
|
||||
assert!(cors.start(&req).is_err());
|
||||
|
||||
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "put")
|
||||
.method(Method::OPTIONS)
|
||||
.finish();
|
||||
|
||||
assert!(cors.start(&mut req).is_err());
|
||||
assert!(cors.start(&req).is_err());
|
||||
|
||||
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
|
||||
.header(
|
||||
header::ACCESS_CONTROL_REQUEST_HEADERS,
|
||||
"AUTHORIZATION,ACCEPT",
|
||||
)
|
||||
.method(Method::OPTIONS)
|
||||
).method(Method::OPTIONS)
|
||||
.finish();
|
||||
|
||||
let resp = cors.start(&mut req).unwrap().response();
|
||||
let resp = cors.start(&req).unwrap().response();
|
||||
assert_eq!(
|
||||
&b"*"[..],
|
||||
resp.headers()
|
||||
@@ -1006,17 +999,17 @@ mod tests {
|
||||
// as_bytes());
|
||||
|
||||
Rc::get_mut(&mut cors.inner).unwrap().preflight = false;
|
||||
assert!(cors.start(&mut req).unwrap().is_done());
|
||||
assert!(cors.start(&req).unwrap().is_done());
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// #[should_panic(expected = "MissingOrigin")]
|
||||
// fn test_validate_missing_origin() {
|
||||
// let mut cors = Cors::build()
|
||||
// let cors = Cors::build()
|
||||
// .allowed_origin("https://www.example.com")
|
||||
// .finish();
|
||||
// let mut req = HttpRequest::default();
|
||||
// cors.start(&mut req).unwrap();
|
||||
// cors.start(&req).unwrap();
|
||||
// }
|
||||
|
||||
#[test]
|
||||
@@ -1026,10 +1019,10 @@ mod tests {
|
||||
.allowed_origin("https://www.example.com")
|
||||
.finish();
|
||||
|
||||
let mut req = TestRequest::with_header("Origin", "https://www.unknown.com")
|
||||
let req = TestRequest::with_header("Origin", "https://www.unknown.com")
|
||||
.method(Method::GET)
|
||||
.finish();
|
||||
cors.start(&mut req).unwrap();
|
||||
cors.start(&req).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1038,30 +1031,30 @@ mod tests {
|
||||
.allowed_origin("https://www.example.com")
|
||||
.finish();
|
||||
|
||||
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
.method(Method::GET)
|
||||
.finish();
|
||||
|
||||
assert!(cors.start(&mut req).unwrap().is_done());
|
||||
assert!(cors.start(&req).unwrap().is_done());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_origin_response() {
|
||||
let cors = Cors::build().finish();
|
||||
|
||||
let mut req = TestRequest::default().method(Method::GET).finish();
|
||||
let req = TestRequest::default().method(Method::GET).finish();
|
||||
let resp: HttpResponse = HttpResponse::Ok().into();
|
||||
let resp = cors.response(&mut req, resp).unwrap().response();
|
||||
let resp = cors.response(&req, resp).unwrap().response();
|
||||
assert!(
|
||||
resp.headers()
|
||||
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
||||
.is_none()
|
||||
);
|
||||
|
||||
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
.method(Method::OPTIONS)
|
||||
.finish();
|
||||
let resp = cors.response(&mut req, resp).unwrap().response();
|
||||
let resp = cors.response(&req, resp).unwrap().response();
|
||||
assert_eq!(
|
||||
&b"https://www.example.com"[..],
|
||||
resp.headers()
|
||||
@@ -1073,21 +1066,23 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_response() {
|
||||
let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
|
||||
let cors = Cors::build()
|
||||
.send_wildcard()
|
||||
.disable_preflight()
|
||||
.max_age(3600)
|
||||
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
|
||||
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
|
||||
.allowed_headers(exposed_headers.clone())
|
||||
.expose_headers(exposed_headers.clone())
|
||||
.allowed_header(header::CONTENT_TYPE)
|
||||
.finish();
|
||||
|
||||
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
let req = TestRequest::with_header("Origin", "https://www.example.com")
|
||||
.method(Method::OPTIONS)
|
||||
.finish();
|
||||
|
||||
let resp: HttpResponse = HttpResponse::Ok().into();
|
||||
let resp = cors.response(&mut req, resp).unwrap().response();
|
||||
let resp = cors.response(&req, resp).unwrap().response();
|
||||
assert_eq!(
|
||||
&b"*"[..],
|
||||
resp.headers()
|
||||
@@ -1100,9 +1095,25 @@ mod tests {
|
||||
resp.headers().get(header::VARY).unwrap().as_bytes()
|
||||
);
|
||||
|
||||
{
|
||||
let headers = resp
|
||||
.headers()
|
||||
.get(header::ACCESS_CONTROL_EXPOSE_HEADERS)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.split(',')
|
||||
.map(|s| s.trim())
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
for h in exposed_headers {
|
||||
assert!(headers.contains(&h.as_str()));
|
||||
}
|
||||
}
|
||||
|
||||
let resp: HttpResponse =
|
||||
HttpResponse::Ok().header(header::VARY, "Accept").finish();
|
||||
let resp = cors.response(&mut req, resp).unwrap().response();
|
||||
let resp = cors.response(&req, resp).unwrap().response();
|
||||
assert_eq!(
|
||||
&b"Accept, Origin"[..],
|
||||
resp.headers().get(header::VARY).unwrap().as_bytes()
|
||||
@@ -1111,16 +1122,29 @@ mod tests {
|
||||
let cors = Cors::build()
|
||||
.disable_vary_header()
|
||||
.allowed_origin("https://www.example.com")
|
||||
.allowed_origin("https://www.google.com")
|
||||
.finish();
|
||||
let resp: HttpResponse = HttpResponse::Ok().into();
|
||||
let resp = cors.response(&mut req, resp).unwrap().response();
|
||||
assert_eq!(
|
||||
&b"https://www.example.com"[..],
|
||||
resp.headers()
|
||||
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
||||
.unwrap()
|
||||
.as_bytes()
|
||||
);
|
||||
let resp = cors.response(&req, resp).unwrap().response();
|
||||
|
||||
let origins_str = resp
|
||||
.headers()
|
||||
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
|
||||
if origins_str.starts_with("https://www.example.com") {
|
||||
assert_eq!(
|
||||
"https://www.example.com, https://www.google.com",
|
||||
origins_str
|
||||
);
|
||||
} else {
|
||||
assert_eq!(
|
||||
"https://www.google.com, https://www.example.com",
|
||||
origins_str
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -15,25 +15,25 @@
|
||||
//! the allowed origins.
|
||||
//!
|
||||
//! Use [`CsrfFilter::allow_xhr()`](struct.CsrfFilter.html#method.allow_xhr)
|
||||
//! if you want to allow requests with unsafe methods via
|
||||
//! if you want to allow requests with unprotected methods via
|
||||
//! [CORS](../cors/struct.Cors.html).
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//! # extern crate actix_web;
|
||||
//! use actix_web::{http, App, HttpRequest, HttpResponse};
|
||||
//! use actix_web::middleware::csrf;
|
||||
//! use actix_web::{http, App, HttpRequest, HttpResponse};
|
||||
//!
|
||||
//! fn handle_post(_: HttpRequest) -> &'static str {
|
||||
//! fn handle_post(_: &HttpRequest) -> &'static str {
|
||||
//! "This action should only be triggered with requests from the same site"
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let app = App::new()
|
||||
//! .middleware(
|
||||
//! csrf::CsrfFilter::new()
|
||||
//! .allowed_origin("https://www.example.com"))
|
||||
//! csrf::CsrfFilter::new().allowed_origin("https://www.example.com"),
|
||||
//! )
|
||||
//! .resource("/", |r| {
|
||||
//! r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
||||
//! r.method(http::Method::POST).f(handle_post);
|
||||
@@ -50,10 +50,10 @@ use std::collections::HashSet;
|
||||
use bytes::Bytes;
|
||||
use error::{ResponseError, Result};
|
||||
use http::{header, HeaderMap, HttpTryFrom, Uri};
|
||||
use httpmessage::HttpMessage;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use middleware::{Middleware, Started};
|
||||
use server::Request;
|
||||
|
||||
/// Potential cross-site request forgery detected.
|
||||
#[derive(Debug, Fail)]
|
||||
@@ -76,7 +76,7 @@ impl ResponseError for CsrfError {
|
||||
}
|
||||
|
||||
fn uri_origin(uri: &Uri) -> Option<String> {
|
||||
match (uri.scheme_part(), uri.host(), uri.port()) {
|
||||
match (uri.scheme_part(), uri.host(), uri.port_part().map(|port| port.as_u16())) {
|
||||
(Some(scheme), Some(host), Some(port)) => {
|
||||
Some(format!("{}://{}:{}", scheme, host, port))
|
||||
}
|
||||
@@ -93,8 +93,7 @@ fn origin(headers: &HeaderMap) -> Option<Result<Cow<str>, CsrfError>> {
|
||||
.to_str()
|
||||
.map_err(|_| CsrfError::BadOrigin)
|
||||
.map(|o| o.into())
|
||||
})
|
||||
.or_else(|| {
|
||||
}).or_else(|| {
|
||||
headers.get(header::REFERER).map(|referer| {
|
||||
Uri::try_from(Bytes::from(referer.as_bytes()))
|
||||
.ok()
|
||||
@@ -120,13 +119,12 @@ fn origin(headers: &HeaderMap) -> Option<Result<Cow<str>, CsrfError>> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::App;
|
||||
/// use actix_web::middleware::csrf;
|
||||
/// use actix_web::App;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let app = App::new().middleware(
|
||||
/// csrf::CsrfFilter::new()
|
||||
/// .allowed_origin("https://www.example.com"));
|
||||
/// let app = App::new()
|
||||
/// .middleware(csrf::CsrfFilter::new().allowed_origin("https://www.example.com"));
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Default)]
|
||||
@@ -176,7 +174,7 @@ impl CsrfFilter {
|
||||
///
|
||||
/// The filter is conservative by default, but it should be safe to allow
|
||||
/// missing `Origin` headers because a cross-site attacker cannot prevent
|
||||
/// the browser from sending `Origin` on unsafe requests.
|
||||
/// the browser from sending `Origin` on unprotected requests.
|
||||
pub fn allow_missing_origin(mut self) -> CsrfFilter {
|
||||
self.allow_missing_origin = true;
|
||||
self
|
||||
@@ -188,7 +186,7 @@ impl CsrfFilter {
|
||||
self
|
||||
}
|
||||
|
||||
fn validate<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CsrfError> {
|
||||
fn validate(&self, req: &Request) -> Result<(), CsrfError> {
|
||||
let is_upgrade = req.headers().contains_key(header::UPGRADE);
|
||||
let is_safe = req.method().is_safe() && (self.allow_upgrade || !is_upgrade);
|
||||
|
||||
@@ -210,7 +208,7 @@ impl CsrfFilter {
|
||||
}
|
||||
|
||||
impl<S> Middleware<S> for CsrfFilter {
|
||||
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
|
||||
self.validate(req)?;
|
||||
Ok(Started::Done)
|
||||
}
|
||||
@@ -226,35 +224,35 @@ mod tests {
|
||||
fn test_safe() {
|
||||
let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
|
||||
|
||||
let mut req = TestRequest::with_header("Origin", "https://www.w3.org")
|
||||
let req = TestRequest::with_header("Origin", "https://www.w3.org")
|
||||
.method(Method::HEAD)
|
||||
.finish();
|
||||
|
||||
assert!(csrf.start(&mut req).is_ok());
|
||||
assert!(csrf.start(&req).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_csrf() {
|
||||
let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
|
||||
|
||||
let mut req = TestRequest::with_header("Origin", "https://www.w3.org")
|
||||
let req = TestRequest::with_header("Origin", "https://www.w3.org")
|
||||
.method(Method::POST)
|
||||
.finish();
|
||||
|
||||
assert!(csrf.start(&mut req).is_err());
|
||||
assert!(csrf.start(&req).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_referer() {
|
||||
let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
|
||||
|
||||
let mut req = TestRequest::with_header(
|
||||
let req = TestRequest::with_header(
|
||||
"Referer",
|
||||
"https://www.example.com/some/path?query=param",
|
||||
).method(Method::POST)
|
||||
.finish();
|
||||
.finish();
|
||||
|
||||
assert!(csrf.start(&mut req).is_ok());
|
||||
assert!(csrf.start(&req).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -265,13 +263,13 @@ mod tests {
|
||||
.allowed_origin("https://www.example.com")
|
||||
.allow_upgrade();
|
||||
|
||||
let mut req = TestRequest::with_header("Origin", "https://cswsh.com")
|
||||
let req = TestRequest::with_header("Origin", "https://cswsh.com")
|
||||
.header("Connection", "Upgrade")
|
||||
.header("Upgrade", "websocket")
|
||||
.method(Method::GET)
|
||||
.finish();
|
||||
|
||||
assert!(strict_csrf.start(&mut req).is_err());
|
||||
assert!(lax_csrf.start(&mut req).is_ok());
|
||||
assert!(strict_csrf.start(&req).is_err());
|
||||
assert!(lax_csrf.start(&req).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,11 @@ use middleware::{Middleware, Response};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .middleware(
|
||||
/// middleware::DefaultHeaders::new()
|
||||
/// .header("X-Version", "0.2"))
|
||||
/// .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
|
||||
/// .resource("/test", |r| {
|
||||
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
||||
/// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
|
||||
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
||||
/// r.method(http::Method::HEAD)
|
||||
/// .f(|_| HttpResponse::MethodNotAllowed());
|
||||
/// })
|
||||
/// .finish();
|
||||
/// }
|
||||
@@ -75,9 +74,7 @@ impl DefaultHeaders {
|
||||
}
|
||||
|
||||
impl<S> Middleware<S> for DefaultHeaders {
|
||||
fn response(
|
||||
&self, _: &mut HttpRequest<S>, mut resp: HttpResponse,
|
||||
) -> Result<Response> {
|
||||
fn response(&self, _: &HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> {
|
||||
for (key, value) in self.headers.iter() {
|
||||
if !resp.headers().contains_key(key) {
|
||||
resp.headers_mut().insert(key, value.clone());
|
||||
@@ -98,22 +95,23 @@ impl<S> Middleware<S> for DefaultHeaders {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::header::CONTENT_TYPE;
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_default_headers() {
|
||||
let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001");
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
let req = TestRequest::default().finish();
|
||||
|
||||
let resp = HttpResponse::Ok().finish();
|
||||
let resp = match mw.response(&mut req, resp) {
|
||||
let resp = match mw.response(&req, resp) {
|
||||
Ok(Response::Done(resp)) => resp,
|
||||
_ => panic!(),
|
||||
};
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||
|
||||
let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish();
|
||||
let resp = match mw.response(&mut req, resp) {
|
||||
let resp = match mw.response(&req, resp) {
|
||||
Ok(Response::Done(resp)) => resp,
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use middleware::{Middleware, Response};
|
||||
|
||||
type ErrorHandler<S> = Fn(&mut HttpRequest<S>, HttpResponse) -> Result<Response>;
|
||||
type ErrorHandler<S> = Fn(&HttpRequest<S>, HttpResponse) -> Result<Response>;
|
||||
|
||||
/// `Middleware` for allowing custom handlers for responses.
|
||||
///
|
||||
@@ -18,23 +18,25 @@ type ErrorHandler<S> = Fn(&mut HttpRequest<S>, HttpResponse) -> Result<Response>
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::middleware::{ErrorHandlers, Response};
|
||||
/// use actix_web::{http, App, HttpRequest, HttpResponse, Result};
|
||||
/// use actix_web::middleware::{Response, ErrorHandlers};
|
||||
///
|
||||
/// fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||
/// let mut builder = resp.into_builder();
|
||||
/// builder.header(http::header::CONTENT_TYPE, "application/json");
|
||||
/// Ok(Response::Done(builder.into()))
|
||||
/// fn render_500<S>(_: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||
/// let mut builder = resp.into_builder();
|
||||
/// builder.header(http::header::CONTENT_TYPE, "application/json");
|
||||
/// Ok(Response::Done(builder.into()))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .middleware(
|
||||
/// ErrorHandlers::new()
|
||||
/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500))
|
||||
/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500),
|
||||
/// )
|
||||
/// .resource("/test", |r| {
|
||||
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
||||
/// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
|
||||
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
||||
/// r.method(http::Method::HEAD)
|
||||
/// .f(|_| HttpResponse::MethodNotAllowed());
|
||||
/// })
|
||||
/// .finish();
|
||||
/// }
|
||||
@@ -60,7 +62,7 @@ impl<S> ErrorHandlers<S> {
|
||||
/// Register error handler for specified status code
|
||||
pub fn handler<F>(mut self, status: StatusCode, handler: F) -> Self
|
||||
where
|
||||
F: Fn(&mut HttpRequest<S>, HttpResponse) -> Result<Response> + 'static,
|
||||
F: Fn(&HttpRequest<S>, HttpResponse) -> Result<Response> + 'static,
|
||||
{
|
||||
self.handlers.insert(status, Box::new(handler));
|
||||
self
|
||||
@@ -68,9 +70,7 @@ impl<S> ErrorHandlers<S> {
|
||||
}
|
||||
|
||||
impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
|
||||
fn response(
|
||||
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
|
||||
) -> Result<Response> {
|
||||
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||
if let Some(handler) = self.handlers.get(&resp.status()) {
|
||||
handler(req, resp)
|
||||
} else {
|
||||
@@ -82,10 +82,14 @@ impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use error::{Error, ErrorInternalServerError};
|
||||
use http::header::CONTENT_TYPE;
|
||||
use http::StatusCode;
|
||||
use httpmessage::HttpMessage;
|
||||
use middleware::Started;
|
||||
use test::{self, TestRequest};
|
||||
|
||||
fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||
fn render_500<S>(_: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||
let mut builder = resp.into_builder();
|
||||
builder.header(CONTENT_TYPE, "0001");
|
||||
Ok(Response::Done(builder.into()))
|
||||
@@ -96,7 +100,7 @@ mod tests {
|
||||
let mw =
|
||||
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
let mut req = TestRequest::default().finish();
|
||||
let resp = HttpResponse::InternalServerError().finish();
|
||||
let resp = match mw.response(&mut req, resp) {
|
||||
Ok(Response::Done(resp)) => resp,
|
||||
@@ -111,4 +115,27 @@ mod tests {
|
||||
};
|
||||
assert!(!resp.headers().contains_key(CONTENT_TYPE));
|
||||
}
|
||||
|
||||
struct MiddlewareOne;
|
||||
|
||||
impl<S> Middleware<S> for MiddlewareOne {
|
||||
fn start(&self, _: &HttpRequest<S>) -> Result<Started, Error> {
|
||||
Err(ErrorInternalServerError("middleware error"))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_middleware_start_error() {
|
||||
let mut srv = test::TestServer::new(move |app| {
|
||||
app.middleware(
|
||||
ErrorHandlers::new()
|
||||
.handler(StatusCode::INTERNAL_SERVER_ERROR, render_500),
|
||||
).middleware(MiddlewareOne)
|
||||
.handler(|_| HttpResponse::Ok())
|
||||
});
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! [**IdentityService**](struct.IdentityService.html) middleware can be
|
||||
//! used with different policies types to store identity information.
|
||||
//!
|
||||
//! Bu default, only cookie identity policy is implemented. Other backend
|
||||
//! By default, only cookie identity policy is implemented. Other backend
|
||||
//! implementations can be added separately.
|
||||
//!
|
||||
//! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html)
|
||||
@@ -48,7 +48,7 @@
|
||||
//! ```
|
||||
use std::rc::Rc;
|
||||
|
||||
use cookie::{Cookie, CookieJar, Key};
|
||||
use cookie::{Cookie, CookieJar, Key, SameSite};
|
||||
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
|
||||
use futures::Future;
|
||||
use time::Duration;
|
||||
@@ -62,8 +62,8 @@ use middleware::{Middleware, Response, Started};
|
||||
/// The helper trait to obtain your identity from a request.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::*;
|
||||
/// use actix_web::middleware::identity::RequestIdentity;
|
||||
/// use actix_web::*;
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> Result<String> {
|
||||
/// // access request identity
|
||||
@@ -80,7 +80,7 @@ use middleware::{Middleware, Response, Started};
|
||||
/// }
|
||||
///
|
||||
/// fn logout(mut req: HttpRequest) -> HttpResponse {
|
||||
/// req.forget(); // <- remove identity
|
||||
/// req.forget(); // <- remove identity
|
||||
/// HttpResponse::Ok().finish()
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
@@ -88,31 +88,31 @@ use middleware::{Middleware, Response, Started};
|
||||
pub trait RequestIdentity {
|
||||
/// Return the claimed identity of the user associated request or
|
||||
/// ``None`` if no identity can be found associated with the request.
|
||||
fn identity(&self) -> Option<&str>;
|
||||
fn identity(&self) -> Option<String>;
|
||||
|
||||
/// Remember identity.
|
||||
fn remember(&mut self, identity: String);
|
||||
fn remember(&self, identity: String);
|
||||
|
||||
/// This method is used to 'forget' the current identity on subsequent
|
||||
/// requests.
|
||||
fn forget(&mut self);
|
||||
fn forget(&self);
|
||||
}
|
||||
|
||||
impl<S> RequestIdentity for HttpRequest<S> {
|
||||
fn identity(&self) -> Option<&str> {
|
||||
fn identity(&self) -> Option<String> {
|
||||
if let Some(id) = self.extensions().get::<IdentityBox>() {
|
||||
return id.0.identity();
|
||||
return id.0.identity().map(|s| s.to_owned());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn remember(&mut self, identity: String) {
|
||||
fn remember(&self, identity: String) {
|
||||
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
|
||||
return id.0.remember(identity);
|
||||
return id.0.as_mut().remember(identity);
|
||||
}
|
||||
}
|
||||
|
||||
fn forget(&mut self) {
|
||||
fn forget(&self) {
|
||||
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
|
||||
return id.0.forget();
|
||||
}
|
||||
@@ -121,10 +121,15 @@ impl<S> RequestIdentity for HttpRequest<S> {
|
||||
|
||||
/// An identity
|
||||
pub trait Identity: 'static {
|
||||
/// Return the claimed identity of the user associated request or
|
||||
/// ``None`` if no identity can be found associated with the request.
|
||||
fn identity(&self) -> Option<&str>;
|
||||
|
||||
/// Remember identity.
|
||||
fn remember(&mut self, key: String);
|
||||
|
||||
/// This method is used to 'forget' the current identity on subsequent
|
||||
/// requests.
|
||||
fn forget(&mut self);
|
||||
|
||||
/// Write session to storage backend.
|
||||
@@ -133,28 +138,30 @@ pub trait Identity: 'static {
|
||||
|
||||
/// Identity policy definition.
|
||||
pub trait IdentityPolicy<S>: Sized + 'static {
|
||||
/// The associated identity
|
||||
type Identity: Identity;
|
||||
|
||||
/// The return type of the middleware
|
||||
type Future: Future<Item = Self::Identity, Error = Error>;
|
||||
|
||||
/// Parse the session from request and load data from a service identity.
|
||||
fn from_request(&self, request: &mut HttpRequest<S>) -> Self::Future;
|
||||
fn from_request(&self, request: &HttpRequest<S>) -> Self::Future;
|
||||
}
|
||||
|
||||
/// Request identity middleware
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix;
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
|
||||
/// use actix_web::App;
|
||||
/// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().middleware(
|
||||
/// IdentityService::new( // <- create identity middleware
|
||||
/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
|
||||
/// let app = App::new().middleware(IdentityService::new(
|
||||
/// // <- create identity middleware
|
||||
/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
|
||||
/// .name("auth-cookie")
|
||||
/// .secure(false))
|
||||
/// );
|
||||
/// .secure(false),
|
||||
/// ));
|
||||
/// }
|
||||
/// ```
|
||||
pub struct IdentityService<T> {
|
||||
@@ -170,33 +177,22 @@ impl<T> IdentityService<T> {
|
||||
|
||||
struct IdentityBox(Box<Identity>);
|
||||
|
||||
#[doc(hidden)]
|
||||
unsafe impl Send for IdentityBox {}
|
||||
#[doc(hidden)]
|
||||
unsafe impl Sync for IdentityBox {}
|
||||
|
||||
impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
|
||||
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||
let mut req = req.clone();
|
||||
|
||||
let fut = self
|
||||
.backend
|
||||
.from_request(&mut req)
|
||||
.then(move |res| match res {
|
||||
Ok(id) => {
|
||||
req.extensions_mut().insert(IdentityBox(Box::new(id)));
|
||||
FutOk(None)
|
||||
}
|
||||
Err(err) => FutErr(err),
|
||||
});
|
||||
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
|
||||
let req = req.clone();
|
||||
let fut = self.backend.from_request(&req).then(move |res| match res {
|
||||
Ok(id) => {
|
||||
req.extensions_mut().insert(IdentityBox(Box::new(id)));
|
||||
FutOk(None)
|
||||
}
|
||||
Err(err) => FutErr(err),
|
||||
});
|
||||
Ok(Started::Future(Box::new(fut)))
|
||||
}
|
||||
|
||||
fn response(
|
||||
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
|
||||
) -> Result<Response> {
|
||||
if let Some(mut id) = req.extensions_mut().remove::<IdentityBox>() {
|
||||
id.0.write(resp)
|
||||
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||
if let Some(ref mut id) = req.extensions_mut().get_mut::<IdentityBox>() {
|
||||
id.0.as_mut().write(resp)
|
||||
} else {
|
||||
Ok(Response::Done(resp))
|
||||
}
|
||||
@@ -241,6 +237,7 @@ struct CookieIdentityInner {
|
||||
domain: Option<String>,
|
||||
secure: bool,
|
||||
max_age: Option<Duration>,
|
||||
same_site: Option<SameSite>,
|
||||
}
|
||||
|
||||
impl CookieIdentityInner {
|
||||
@@ -252,6 +249,7 @@ impl CookieIdentityInner {
|
||||
domain: None,
|
||||
secure: true,
|
||||
max_age: None,
|
||||
same_site: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,6 +270,10 @@ impl CookieIdentityInner {
|
||||
cookie.set_max_age(max_age);
|
||||
}
|
||||
|
||||
if let Some(same_site) = self.same_site {
|
||||
cookie.set_same_site(same_site);
|
||||
}
|
||||
|
||||
let mut jar = CookieJar::new();
|
||||
if some {
|
||||
jar.private(&self.key).add(cookie);
|
||||
@@ -289,9 +291,9 @@ impl CookieIdentityInner {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load<S>(&self, req: &mut HttpRequest<S>) -> Option<String> {
|
||||
fn load<S>(&self, req: &HttpRequest<S>) -> Option<String> {
|
||||
if let Ok(cookies) = req.cookies() {
|
||||
for cookie in cookies {
|
||||
for cookie in cookies.iter() {
|
||||
if cookie.name() == self.name {
|
||||
let mut jar = CookieJar::new();
|
||||
jar.add_original(cookie.clone());
|
||||
@@ -318,17 +320,18 @@ impl CookieIdentityInner {
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
|
||||
/// use actix_web::App;
|
||||
/// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().middleware(
|
||||
/// IdentityService::new( // <- create identity middleware
|
||||
/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy
|
||||
/// let app = App::new().middleware(IdentityService::new(
|
||||
/// // <- create identity middleware
|
||||
/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy
|
||||
/// .domain("www.rust-lang.org")
|
||||
/// .name("actix_auth")
|
||||
/// .path("/")
|
||||
/// .secure(true)));
|
||||
/// .secure(true),
|
||||
/// ));
|
||||
/// }
|
||||
/// ```
|
||||
pub struct CookieIdentityPolicy(Rc<CookieIdentityInner>);
|
||||
@@ -373,13 +376,19 @@ impl CookieIdentityPolicy {
|
||||
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `same_site` field in the session cookie being built.
|
||||
pub fn same_site(mut self, same_site: SameSite) -> Self {
|
||||
Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> IdentityPolicy<S> for CookieIdentityPolicy {
|
||||
type Identity = CookieIdentity;
|
||||
type Future = FutureResult<CookieIdentity, Error>;
|
||||
|
||||
fn from_request(&self, req: &mut HttpRequest<S>) -> Self::Future {
|
||||
fn from_request(&self, req: &HttpRequest<S>) -> Self::Future {
|
||||
let identity = self.0.load(req);
|
||||
FutOk(CookieIdentity {
|
||||
identity,
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use libc;
|
||||
use regex::Regex;
|
||||
use time;
|
||||
|
||||
@@ -26,13 +25,13 @@ use middleware::{Finished, Middleware, Started};
|
||||
/// default format:
|
||||
///
|
||||
/// ```ignore
|
||||
/// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
||||
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
||||
/// ```
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// extern crate env_logger;
|
||||
/// use actix_web::App;
|
||||
/// use actix_web::middleware::Logger;
|
||||
/// use actix_web::App;
|
||||
///
|
||||
/// fn main() {
|
||||
/// std::env::set_var("RUST_LOG", "actix_web=info");
|
||||
@@ -53,8 +52,6 @@ use middleware::{Finished, Middleware, Started};
|
||||
///
|
||||
/// `%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
|
||||
@@ -97,7 +94,7 @@ impl Default for Logger {
|
||||
/// Create `Logger` middleware with format:
|
||||
///
|
||||
/// ```ignore
|
||||
/// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
||||
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
||||
/// ```
|
||||
fn default() -> Logger {
|
||||
Logger {
|
||||
@@ -110,7 +107,7 @@ impl Default for Logger {
|
||||
struct StartTime(time::Tm);
|
||||
|
||||
impl Logger {
|
||||
fn log<S>(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) {
|
||||
fn log<S>(&self, req: &HttpRequest<S>, resp: &HttpResponse) {
|
||||
if let Some(entry_time) = req.extensions().get::<StartTime>() {
|
||||
let render = |fmt: &mut Formatter| {
|
||||
for unit in &self.format.0 {
|
||||
@@ -124,14 +121,14 @@ impl Logger {
|
||||
}
|
||||
|
||||
impl<S> Middleware<S> for Logger {
|
||||
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
|
||||
if !self.exclude.contains(req.path()) {
|
||||
req.extensions_mut().insert(StartTime(time::now()));
|
||||
}
|
||||
Ok(Started::Done)
|
||||
}
|
||||
|
||||
fn finish(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) -> Finished {
|
||||
fn finish(&self, req: &HttpRequest<S>, resp: &HttpResponse) -> Finished {
|
||||
self.log(req, resp);
|
||||
Finished::Done
|
||||
}
|
||||
@@ -146,7 +143,7 @@ struct Format(Vec<FormatText>);
|
||||
impl Default for Format {
|
||||
/// Return the default formatting style for the `Logger`:
|
||||
fn default() -> Format {
|
||||
Format::new(r#"%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#)
|
||||
Format::new(r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +178,6 @@ impl Format {
|
||||
"%" => FormatText::Percent,
|
||||
"a" => FormatText::RemoteAddr,
|
||||
"t" => FormatText::RequestTime,
|
||||
"P" => FormatText::Pid,
|
||||
"r" => FormatText::RequestLine,
|
||||
"s" => FormatText::ResponseStatus,
|
||||
"b" => FormatText::ResponseSize,
|
||||
@@ -205,7 +201,6 @@ impl Format {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FormatText {
|
||||
Str(String),
|
||||
Pid,
|
||||
Percent,
|
||||
RequestLine,
|
||||
RequestTime,
|
||||
@@ -247,7 +242,6 @@ impl FormatText {
|
||||
}
|
||||
FormatText::ResponseStatus => resp.status().as_u16().fmt(fmt),
|
||||
FormatText::ResponseSize => resp.response_size().fmt(fmt),
|
||||
FormatText::Pid => unsafe { libc::getpid().fmt(fmt) },
|
||||
FormatText::Time => {
|
||||
let rt = time::now() - entry_time;
|
||||
let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0;
|
||||
@@ -314,38 +308,30 @@ impl<'a> fmt::Display for FormatDisplay<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::header::{self, HeaderMap};
|
||||
use http::{Method, StatusCode, Uri, Version};
|
||||
use std::str::FromStr;
|
||||
use time;
|
||||
|
||||
use super::*;
|
||||
use http::{header, StatusCode};
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_logger() {
|
||||
let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test");
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
let req = TestRequest::with_header(
|
||||
header::USER_AGENT,
|
||||
header::HeaderValue::from_static("ACTIX-WEB"),
|
||||
);
|
||||
let mut req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
headers,
|
||||
None,
|
||||
);
|
||||
).finish();
|
||||
let resp = HttpResponse::build(StatusCode::OK)
|
||||
.header("X-Test", "ttt")
|
||||
.force_close()
|
||||
.finish();
|
||||
|
||||
match logger.start(&mut req) {
|
||||
match logger.start(&req) {
|
||||
Ok(Started::Done) => (),
|
||||
_ => panic!(),
|
||||
};
|
||||
match logger.finish(&mut req, &resp) {
|
||||
match logger.finish(&req, &resp) {
|
||||
Finished::Done => (),
|
||||
_ => panic!(),
|
||||
}
|
||||
@@ -364,18 +350,10 @@ mod tests {
|
||||
fn test_default_format() {
|
||||
let format = Format::default();
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
let req = TestRequest::with_header(
|
||||
header::USER_AGENT,
|
||||
header::HeaderValue::from_static("ACTIX-WEB"),
|
||||
);
|
||||
let req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
headers,
|
||||
None,
|
||||
);
|
||||
).finish();
|
||||
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
|
||||
let entry_time = time::now();
|
||||
|
||||
@@ -390,13 +368,7 @@ mod tests {
|
||||
assert!(s.contains("200 0"));
|
||||
assert!(s.contains("ACTIX-WEB"));
|
||||
|
||||
let req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/?test").unwrap(),
|
||||
Version::HTTP_11,
|
||||
HeaderMap::new(),
|
||||
None,
|
||||
);
|
||||
let req = TestRequest::with_uri("/?test").finish();
|
||||
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
|
||||
let entry_time = time::now();
|
||||
|
||||
|
||||
@@ -51,20 +51,18 @@ pub enum Finished {
|
||||
pub trait Middleware<S>: 'static {
|
||||
/// 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> {
|
||||
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
|
||||
Ok(Started::Done)
|
||||
}
|
||||
|
||||
/// Method is called when handler returns response,
|
||||
/// but before sending http message to peer.
|
||||
fn response(
|
||||
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
|
||||
) -> Result<Response> {
|
||||
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||
Ok(Response::Done(resp))
|
||||
}
|
||||
|
||||
/// Method is called after body stream get sent to peer.
|
||||
fn finish(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) -> Finished {
|
||||
fn finish(&self, req: &HttpRequest<S>, resp: &HttpResponse) -> Finished {
|
||||
Finished::Done
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
//! session data.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate actix;
|
||||
//! # extern crate actix_web;
|
||||
//! # extern crate actix;
|
||||
//! use actix_web::{server, App, HttpRequest, Result};
|
||||
//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend};
|
||||
//!
|
||||
@@ -50,17 +50,17 @@
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let sys = actix::System::new("basic-example");
|
||||
//! server::new(
|
||||
//! || App::new().middleware(
|
||||
//! SessionStorage::new( // <- create session middleware
|
||||
//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend
|
||||
//! .secure(false)
|
||||
//! )))
|
||||
//! .bind("127.0.0.1:59880").unwrap()
|
||||
//! .start();
|
||||
//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
//! let _ = sys.run();
|
||||
//! actix::System::run(|| {
|
||||
//! server::new(
|
||||
//! || App::new().middleware(
|
||||
//! SessionStorage::new( // <- create session middleware
|
||||
//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend
|
||||
//! .secure(false)
|
||||
//! )))
|
||||
//! .bind("127.0.0.1:59880").unwrap()
|
||||
//! .start();
|
||||
//! # actix::System::current().stop();
|
||||
//! });
|
||||
//! }
|
||||
//! ```
|
||||
use std::cell::RefCell;
|
||||
@@ -69,7 +69,7 @@ use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use cookie::{Cookie, CookieJar, Key};
|
||||
use cookie::{Cookie, CookieJar, Key, SameSite};
|
||||
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
|
||||
use futures::Future;
|
||||
use http::header::{self, HeaderValue};
|
||||
@@ -88,13 +88,13 @@ use middleware::{Middleware, Response, Started};
|
||||
/// The helper trait to obtain your session data from a request.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::*;
|
||||
/// use actix_web::middleware::session::RequestSession;
|
||||
/// use actix_web::*;
|
||||
///
|
||||
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
|
||||
/// // access session data
|
||||
/// if let Some(count) = req.session().get::<i32>("counter")? {
|
||||
/// req.session().set("counter", count+1)?;
|
||||
/// req.session().set("counter", count + 1)?;
|
||||
/// } else {
|
||||
/// req.session().set("counter", 1)?;
|
||||
/// }
|
||||
@@ -104,6 +104,7 @@ use middleware::{Middleware, Response, Started};
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub trait RequestSession {
|
||||
/// Get the session from the request
|
||||
fn session(&self) -> Session;
|
||||
}
|
||||
|
||||
@@ -123,13 +124,13 @@ impl<S> RequestSession for HttpRequest<S> {
|
||||
/// method. `RequestSession` trait is implemented for `HttpRequest`.
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::*;
|
||||
/// use actix_web::middleware::session::RequestSession;
|
||||
/// use actix_web::*;
|
||||
///
|
||||
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
|
||||
/// // access session data
|
||||
/// if let Some(count) = req.session().get::<i32>("counter")? {
|
||||
/// req.session().set("counter", count+1)?;
|
||||
/// req.session().set("counter", count + 1)?;
|
||||
/// } else {
|
||||
/// req.session().set("counter", 1)?;
|
||||
/// }
|
||||
@@ -200,7 +201,7 @@ impl Session {
|
||||
/// fn index(session: Session) -> Result<&'static str> {
|
||||
/// // access session data
|
||||
/// if let Some(count) = session.get::<i32>("counter")? {
|
||||
/// session.set("counter", count+1)?;
|
||||
/// session.set("counter", count + 1)?;
|
||||
/// } else {
|
||||
/// session.set("counter", 1)?;
|
||||
/// }
|
||||
@@ -221,25 +222,19 @@ impl<S> FromRequest<S> for Session {
|
||||
|
||||
struct SessionImplCell(RefCell<Box<SessionImpl>>);
|
||||
|
||||
#[doc(hidden)]
|
||||
unsafe impl Send for SessionImplCell {}
|
||||
#[doc(hidden)]
|
||||
unsafe impl Sync for SessionImplCell {}
|
||||
|
||||
/// Session storage middleware
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix;
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::middleware::session::{CookieSessionBackend, SessionStorage};
|
||||
/// use actix_web::App;
|
||||
/// use actix_web::middleware::session::{SessionStorage, CookieSessionBackend};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().middleware(
|
||||
/// SessionStorage::new( // <- create session middleware
|
||||
/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend
|
||||
/// .secure(false))
|
||||
/// );
|
||||
/// let app = App::new().middleware(SessionStorage::new(
|
||||
/// // <- create session middleware
|
||||
/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend
|
||||
/// .secure(false),
|
||||
/// ));
|
||||
/// }
|
||||
/// ```
|
||||
pub struct SessionStorage<T, S>(T, PhantomData<S>);
|
||||
@@ -252,7 +247,7 @@ impl<S, T: SessionBackend<S>> SessionStorage<T, S> {
|
||||
}
|
||||
|
||||
impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
|
||||
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
|
||||
let mut req = req.clone();
|
||||
|
||||
let fut = self.0.from_request(&mut req).then(move |res| match res {
|
||||
@@ -266,10 +261,8 @@ impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
|
||||
Ok(Started::Future(Box::new(fut)))
|
||||
}
|
||||
|
||||
fn response(
|
||||
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
|
||||
) -> Result<Response> {
|
||||
if let Some(s_box) = req.extensions_mut().remove::<Arc<SessionImplCell>>() {
|
||||
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||
if let Some(s_box) = req.extensions().get::<Arc<SessionImplCell>>() {
|
||||
s_box.0.borrow_mut().write(resp)
|
||||
} else {
|
||||
Ok(Response::Done(resp))
|
||||
@@ -278,14 +271,17 @@ impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
|
||||
}
|
||||
|
||||
/// A simple key-value storage interface that is internally used by `Session`.
|
||||
#[doc(hidden)]
|
||||
pub trait SessionImpl: 'static {
|
||||
/// Get session value by key
|
||||
fn get(&self, key: &str) -> Option<&str>;
|
||||
|
||||
/// Set session value
|
||||
fn set(&mut self, key: &str, value: String);
|
||||
|
||||
/// Remove specific key from session
|
||||
fn remove(&mut self, key: &str);
|
||||
|
||||
/// Remove all values from session
|
||||
fn clear(&mut self);
|
||||
|
||||
/// Write session to storage backend.
|
||||
@@ -293,9 +289,10 @@ pub trait SessionImpl: 'static {
|
||||
}
|
||||
|
||||
/// Session's storage backend trait definition.
|
||||
#[doc(hidden)]
|
||||
pub trait SessionBackend<S>: Sized + 'static {
|
||||
/// Session item
|
||||
type Session: SessionImpl;
|
||||
/// Future that reads session
|
||||
type ReadFuture: Future<Item = Self::Session, Error = Error>;
|
||||
|
||||
/// Parse the session from request and load data from a storage backend.
|
||||
@@ -366,7 +363,9 @@ struct CookieSessionInner {
|
||||
path: String,
|
||||
domain: Option<String>,
|
||||
secure: bool,
|
||||
http_only: bool,
|
||||
max_age: Option<Duration>,
|
||||
same_site: Option<SameSite>,
|
||||
}
|
||||
|
||||
impl CookieSessionInner {
|
||||
@@ -378,7 +377,9 @@ impl CookieSessionInner {
|
||||
path: "/".to_owned(),
|
||||
domain: None,
|
||||
secure: true,
|
||||
http_only: true,
|
||||
max_age: None,
|
||||
same_site: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,7 +395,7 @@ impl CookieSessionInner {
|
||||
let mut cookie = Cookie::new(self.name.clone(), value);
|
||||
cookie.set_path(self.path.clone());
|
||||
cookie.set_secure(self.secure);
|
||||
cookie.set_http_only(true);
|
||||
cookie.set_http_only(self.http_only);
|
||||
|
||||
if let Some(ref domain) = self.domain {
|
||||
cookie.set_domain(domain.clone());
|
||||
@@ -404,6 +405,10 @@ impl CookieSessionInner {
|
||||
cookie.set_max_age(max_age);
|
||||
}
|
||||
|
||||
if let Some(same_site) = self.same_site {
|
||||
cookie.set_same_site(same_site);
|
||||
}
|
||||
|
||||
let mut jar = CookieJar::new();
|
||||
|
||||
match self.security {
|
||||
@@ -412,7 +417,7 @@ impl CookieSessionInner {
|
||||
}
|
||||
|
||||
for cookie in jar.delta() {
|
||||
let val = HeaderValue::from_str(&cookie.to_string())?;
|
||||
let val = HeaderValue::from_str(&cookie.encoded().to_string())?;
|
||||
resp.headers_mut().append(header::SET_COOKIE, val);
|
||||
}
|
||||
|
||||
@@ -421,7 +426,7 @@ impl CookieSessionInner {
|
||||
|
||||
fn load<S>(&self, req: &mut HttpRequest<S>) -> HashMap<String, String> {
|
||||
if let Ok(cookies) = req.cookies() {
|
||||
for cookie in cookies {
|
||||
for cookie in cookies.iter() {
|
||||
if cookie.name() == self.name {
|
||||
let mut jar = CookieJar::new();
|
||||
jar.add_original(cookie.clone());
|
||||
@@ -466,6 +471,9 @@ impl CookieSessionInner {
|
||||
/// all session data is lost. The constructors will panic if the key is less
|
||||
/// than 32 bytes in length.
|
||||
///
|
||||
/// The backend relies on `cookie` crate to create and read cookies.
|
||||
/// By default all cookies are percent encoded, but certain symbols may
|
||||
/// cause troubles when reading cookie, if they are not properly percent encoded.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -531,6 +539,18 @@ impl CookieSessionBackend {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `http_only` field in the session cookie being built.
|
||||
pub fn http_only(mut self, value: bool) -> CookieSessionBackend {
|
||||
Rc::get_mut(&mut self.0).unwrap().http_only = value;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `same_site` field in the session cookie being built.
|
||||
pub fn same_site(mut self, value: SameSite) -> CookieSessionBackend {
|
||||
Rc::get_mut(&mut self.0).unwrap().same_site = Some(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `max-age` field in the session cookie being built.
|
||||
pub fn max_age(mut self, value: Duration) -> CookieSessionBackend {
|
||||
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
|
||||
@@ -564,8 +584,7 @@ mod tests {
|
||||
App::new()
|
||||
.middleware(SessionStorage::new(
|
||||
CookieSessionBackend::signed(&[0; 32]).secure(false),
|
||||
))
|
||||
.resource("/", |r| {
|
||||
)).resource("/", |r| {
|
||||
r.f(|req| {
|
||||
let _ = req.session().set("counter", 100);
|
||||
"test"
|
||||
@@ -584,8 +603,7 @@ mod tests {
|
||||
App::new()
|
||||
.middleware(SessionStorage::new(
|
||||
CookieSessionBackend::signed(&[0; 32]).secure(false),
|
||||
))
|
||||
.resource("/", |r| {
|
||||
)).resource("/", |r| {
|
||||
r.with(|ses: Session| {
|
||||
let _ = ses.set("counter", 100);
|
||||
"test"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Multipart requests support
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{RefCell, UnsafeCell};
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use std::{cmp, fmt};
|
||||
@@ -7,13 +7,13 @@ use std::{cmp, fmt};
|
||||
use bytes::Bytes;
|
||||
use futures::task::{current as current_task, Task};
|
||||
use futures::{Async, Poll, Stream};
|
||||
use http::header::{self, HeaderMap, HeaderName, HeaderValue};
|
||||
use http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue};
|
||||
use http::HttpTryFrom;
|
||||
use httparse;
|
||||
use mime;
|
||||
|
||||
use error::{MultipartError, ParseError, PayloadError};
|
||||
use payload::PayloadHelper;
|
||||
use payload::PayloadBuffer;
|
||||
|
||||
const MAX_HEADERS: usize = 32;
|
||||
|
||||
@@ -97,7 +97,7 @@ where
|
||||
safety: Safety::new(),
|
||||
inner: Some(Rc::new(RefCell::new(InnerMultipart {
|
||||
boundary,
|
||||
payload: PayloadRef::new(PayloadHelper::new(stream)),
|
||||
payload: PayloadRef::new(PayloadBuffer::new(stream)),
|
||||
state: InnerState::FirstBoundary,
|
||||
item: InnerMultipartItem::None,
|
||||
}))),
|
||||
@@ -133,7 +133,7 @@ impl<S> InnerMultipart<S>
|
||||
where
|
||||
S: Stream<Item = Bytes, Error = PayloadError>,
|
||||
{
|
||||
fn read_headers(payload: &mut PayloadHelper<S>) -> Poll<HeaderMap, MultipartError> {
|
||||
fn read_headers(payload: &mut PayloadBuffer<S>) -> Poll<HeaderMap, MultipartError> {
|
||||
match payload.read_until(b"\r\n\r\n")? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(None) => Err(MultipartError::Incomplete),
|
||||
@@ -164,7 +164,7 @@ where
|
||||
}
|
||||
|
||||
fn read_boundary(
|
||||
payload: &mut PayloadHelper<S>, boundary: &str,
|
||||
payload: &mut PayloadBuffer<S>, boundary: &str,
|
||||
) -> Poll<bool, MultipartError> {
|
||||
// TODO: need to read epilogue
|
||||
match payload.readline()? {
|
||||
@@ -190,7 +190,7 @@ where
|
||||
}
|
||||
|
||||
fn skip_until_boundary(
|
||||
payload: &mut PayloadHelper<S>, boundary: &str,
|
||||
payload: &mut PayloadBuffer<S>, boundary: &str,
|
||||
) -> Poll<bool, MultipartError> {
|
||||
let mut eof = false;
|
||||
loop {
|
||||
@@ -399,13 +399,28 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a map of headers
|
||||
pub fn headers(&self) -> &HeaderMap {
|
||||
&self.headers
|
||||
}
|
||||
|
||||
/// Get the content type of the field
|
||||
pub fn content_type(&self) -> &mime::Mime {
|
||||
&self.ct
|
||||
}
|
||||
|
||||
/// Get the content disposition of the field, if it exists
|
||||
pub fn content_disposition(&self) -> Option<ContentDisposition> {
|
||||
// RFC 7578: 'Each part MUST contain a Content-Disposition header field
|
||||
// where the disposition type is "form-data".'
|
||||
if let Some(content_disposition) =
|
||||
self.headers.get(::http::header::CONTENT_DISPOSITION)
|
||||
{
|
||||
ContentDisposition::from_raw(content_disposition).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Stream for Field<S>
|
||||
@@ -426,13 +441,13 @@ where
|
||||
|
||||
impl<S> fmt::Debug for Field<S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let res = writeln!(f, "\nMultipartField: {}", self.ct);
|
||||
let _ = writeln!(f, " boundary: {}", self.inner.borrow().boundary);
|
||||
let _ = writeln!(f, " headers:");
|
||||
writeln!(f, "\nMultipartField: {}", self.ct)?;
|
||||
writeln!(f, " boundary: {}", self.inner.borrow().boundary)?;
|
||||
writeln!(f, " headers:")?;
|
||||
for (key, val) in self.headers.iter() {
|
||||
let _ = writeln!(f, " {:?}: {:?}", key, val);
|
||||
writeln!(f, " {:?}: {:?}", key, val)?;
|
||||
}
|
||||
res
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,7 +490,7 @@ where
|
||||
/// Reads body part content chunk of the specified size.
|
||||
/// The body part must has `Content-Length` header with proper value.
|
||||
fn read_len(
|
||||
payload: &mut PayloadHelper<S>, size: &mut u64,
|
||||
payload: &mut PayloadBuffer<S>, size: &mut u64,
|
||||
) -> Poll<Option<Bytes>, MultipartError> {
|
||||
if *size == 0 {
|
||||
Ok(Async::Ready(None))
|
||||
@@ -488,7 +503,7 @@ where
|
||||
*size -= len;
|
||||
let ch = chunk.split_to(len as usize);
|
||||
if !chunk.is_empty() {
|
||||
payload.unread_data(chunk);
|
||||
payload.unprocessed(chunk);
|
||||
}
|
||||
Ok(Async::Ready(Some(ch)))
|
||||
}
|
||||
@@ -500,14 +515,14 @@ where
|
||||
/// Reads content chunk of body part with unknown length.
|
||||
/// The `Content-Length` header for body part is not necessary.
|
||||
fn read_stream(
|
||||
payload: &mut PayloadHelper<S>, boundary: &str,
|
||||
payload: &mut PayloadBuffer<S>, boundary: &str,
|
||||
) -> Poll<Option<Bytes>, MultipartError> {
|
||||
match payload.read_until(b"\r")? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(None) => Err(MultipartError::Incomplete),
|
||||
Async::Ready(Some(mut chunk)) => {
|
||||
if chunk.len() == 1 {
|
||||
payload.unread_data(chunk);
|
||||
payload.unprocessed(chunk);
|
||||
match payload.read_exact(boundary.len() + 4)? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(None) => Err(MultipartError::Incomplete),
|
||||
@@ -516,12 +531,12 @@ where
|
||||
&& &chunk[2..4] == b"--"
|
||||
&& &chunk[4..] == boundary.as_bytes()
|
||||
{
|
||||
payload.unread_data(chunk);
|
||||
payload.unprocessed(chunk);
|
||||
Ok(Async::Ready(None))
|
||||
} else {
|
||||
// \r might be part of data stream
|
||||
let ch = chunk.split_to(1);
|
||||
payload.unread_data(chunk);
|
||||
payload.unprocessed(chunk);
|
||||
Ok(Async::Ready(Some(ch)))
|
||||
}
|
||||
}
|
||||
@@ -529,7 +544,7 @@ where
|
||||
} else {
|
||||
let to = chunk.len() - 1;
|
||||
let ch = chunk.split_to(to);
|
||||
payload.unread_data(chunk);
|
||||
payload.unprocessed(chunk);
|
||||
Ok(Async::Ready(Some(ch)))
|
||||
}
|
||||
}
|
||||
@@ -577,26 +592,27 @@ where
|
||||
}
|
||||
|
||||
struct PayloadRef<S> {
|
||||
payload: Rc<PayloadHelper<S>>,
|
||||
payload: Rc<UnsafeCell<PayloadBuffer<S>>>,
|
||||
}
|
||||
|
||||
impl<S> PayloadRef<S>
|
||||
where
|
||||
S: Stream<Item = Bytes, Error = PayloadError>,
|
||||
{
|
||||
fn new(payload: PayloadHelper<S>) -> PayloadRef<S> {
|
||||
fn new(payload: PayloadBuffer<S>) -> PayloadRef<S> {
|
||||
PayloadRef {
|
||||
payload: Rc::new(payload),
|
||||
payload: Rc::new(payload.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadHelper<S>>
|
||||
fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadBuffer<S>>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
// Unsafe: Invariant is inforced by Safety Safety is used as ref counter,
|
||||
// only top most ref can have mutable access to payload.
|
||||
if s.current() {
|
||||
let payload: &mut PayloadHelper<S> =
|
||||
unsafe { &mut *(self.payload.as_ref() as *const _ as *mut _) };
|
||||
let payload: &mut PayloadBuffer<S> = unsafe { &mut *self.payload.get() };
|
||||
Some(payload)
|
||||
} else {
|
||||
None
|
||||
@@ -666,7 +682,7 @@ mod tests {
|
||||
use bytes::Bytes;
|
||||
use futures::future::{lazy, result};
|
||||
use payload::{Payload, PayloadWriter};
|
||||
use tokio_core::reactor::Core;
|
||||
use tokio::runtime::current_thread::Runtime;
|
||||
|
||||
#[test]
|
||||
fn test_boundary() {
|
||||
@@ -713,14 +729,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_multipart() {
|
||||
Core::new()
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.run(lazy(|| {
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::new(false);
|
||||
|
||||
let bytes = Bytes::from(
|
||||
"testasdadsad\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
|
||||
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
|
||||
test\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
|
||||
@@ -736,6 +753,15 @@ mod tests {
|
||||
match multipart.poll() {
|
||||
Ok(Async::Ready(Some(item))) => match item {
|
||||
MultipartItem::Field(mut field) => {
|
||||
{
|
||||
use http::header::{DispositionParam, DispositionType};
|
||||
let cd = field.content_disposition().unwrap();
|
||||
assert_eq!(cd.disposition, DispositionType::FormData);
|
||||
assert_eq!(
|
||||
cd.parameters[0],
|
||||
DispositionParam::Name("file".into())
|
||||
);
|
||||
}
|
||||
assert_eq!(field.content_type().type_(), mime::TEXT);
|
||||
assert_eq!(field.content_type().subtype(), mime::PLAIN);
|
||||
|
||||
@@ -784,7 +810,6 @@ mod tests {
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
}))
|
||||
.unwrap();
|
||||
})).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
191
src/param.rs
191
src/param.rs
@@ -1,13 +1,14 @@
|
||||
use http::StatusCode;
|
||||
use smallvec::SmallVec;
|
||||
use std;
|
||||
use std::borrow::Cow;
|
||||
use std::ops::Index;
|
||||
use std::path::PathBuf;
|
||||
use std::slice::Iter;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
|
||||
use http::StatusCode;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use error::{InternalError, ResponseError, UriSegmentError};
|
||||
use uri::{Url, RESERVED_QUOTER};
|
||||
|
||||
/// A trait to abstract the idea of creating a new instance of a type from a
|
||||
/// path parameter.
|
||||
@@ -19,72 +20,103 @@ pub trait FromParam: Sized {
|
||||
fn from_param(s: &str) -> Result<Self, Self::Err>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum ParamItem {
|
||||
Static(&'static str),
|
||||
UrlSegment(u16, u16),
|
||||
}
|
||||
|
||||
/// Route match information
|
||||
///
|
||||
/// If resource path contains variable patterns, `Params` stores this variables.
|
||||
#[derive(Debug)]
|
||||
pub struct Params<'a>(SmallVec<[(Cow<'a, str>, Cow<'a, str>); 3]>);
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Params {
|
||||
url: Url,
|
||||
pub(crate) tail: u16,
|
||||
pub(crate) segments: SmallVec<[(Rc<String>, ParamItem); 3]>,
|
||||
}
|
||||
|
||||
impl<'a> Params<'a> {
|
||||
pub(crate) fn new() -> Params<'a> {
|
||||
Params(SmallVec::new())
|
||||
impl Params {
|
||||
pub(crate) fn new() -> Params {
|
||||
Params {
|
||||
url: Url::default(),
|
||||
tail: 0,
|
||||
segments: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_url(url: &Url) -> Params {
|
||||
Params {
|
||||
url: url.clone(),
|
||||
tail: 0,
|
||||
segments: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
self.segments.clear();
|
||||
}
|
||||
|
||||
pub(crate) fn add<N, V>(&mut self, name: N, value: V)
|
||||
where
|
||||
N: Into<Cow<'a, str>>,
|
||||
V: Into<Cow<'a, str>>,
|
||||
{
|
||||
self.0.push((name.into(), value.into()));
|
||||
pub(crate) fn set_tail(&mut self, tail: u16) {
|
||||
self.tail = tail;
|
||||
}
|
||||
|
||||
pub(crate) fn set<N, V>(&mut self, name: N, value: V)
|
||||
where
|
||||
N: Into<Cow<'a, str>>,
|
||||
V: Into<Cow<'a, str>>,
|
||||
{
|
||||
let name = name.into();
|
||||
let value = value.into();
|
||||
for item in &mut self.0 {
|
||||
if item.0 == name {
|
||||
item.1 = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.0.push((name, value));
|
||||
pub(crate) fn set_url(&mut self, url: Url) {
|
||||
self.url = url;
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&mut self, name: &str) {
|
||||
for idx in (0..self.0.len()).rev() {
|
||||
if self.0[idx].0 == name {
|
||||
self.0.remove(idx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
pub(crate) fn add(&mut self, name: Rc<String>, value: ParamItem) {
|
||||
self.segments.push((name, value));
|
||||
}
|
||||
|
||||
pub(crate) fn add_static(&mut self, name: &str, value: &'static str) {
|
||||
self.segments
|
||||
.push((Rc::new(name.to_string()), ParamItem::Static(value)));
|
||||
}
|
||||
|
||||
/// Check if there are any matched patterns
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
self.segments.is_empty()
|
||||
}
|
||||
|
||||
/// Check number of extracted parameters
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
self.segments.len()
|
||||
}
|
||||
|
||||
/// Get matched parameter by name without type conversion
|
||||
pub fn get(&'a self, key: &str) -> Option<&'a str> {
|
||||
for item in self.0.iter() {
|
||||
if key == item.0 {
|
||||
return Some(item.1.as_ref());
|
||||
pub fn get(&self, key: &str) -> Option<&str> {
|
||||
for item in self.segments.iter() {
|
||||
if key == item.0.as_str() {
|
||||
return match item.1 {
|
||||
ParamItem::Static(ref s) => Some(&s),
|
||||
ParamItem::UrlSegment(s, e) => {
|
||||
Some(&self.url.path()[(s as usize)..(e as usize)])
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
None
|
||||
if key == "tail" {
|
||||
Some(&self.url.path()[(self.tail as usize)..])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get URL-decoded matched parameter by name without type conversion
|
||||
pub fn get_decoded(&self, key: &str) -> Option<String> {
|
||||
self.get(key).map(|value| {
|
||||
if let Some(ref mut value) = RESERVED_QUOTER.requote(value.as_bytes()) {
|
||||
Rc::make_mut(value).to_string()
|
||||
} else {
|
||||
value.to_string()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get unprocessed part of path
|
||||
pub fn unprocessed(&self) -> &str {
|
||||
&self.url.path()[(self.tail as usize)..]
|
||||
}
|
||||
|
||||
/// Get matched `FromParam` compatible parameter by name.
|
||||
@@ -96,12 +128,12 @@ impl<'a> Params<'a> {
|
||||
/// # extern crate actix_web;
|
||||
/// # use actix_web::*;
|
||||
/// fn index(req: HttpRequest) -> Result<String> {
|
||||
/// let ivalue: isize = req.match_info().query("val")?;
|
||||
/// Ok(format!("isuze value: {:?}", ivalue))
|
||||
/// let ivalue: isize = req.match_info().query("val")?;
|
||||
/// Ok(format!("isuze value: {:?}", ivalue))
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub fn query<T: FromParam>(&'a self, key: &str) -> Result<T, <T as FromParam>::Err> {
|
||||
pub fn query<T: FromParam>(&self, key: &str) -> Result<T, <T as FromParam>::Err> {
|
||||
if let Some(s) = self.get(key) {
|
||||
T::from_param(s)
|
||||
} else {
|
||||
@@ -110,25 +142,57 @@ impl<'a> Params<'a> {
|
||||
}
|
||||
|
||||
/// Return iterator to items in parameter container
|
||||
pub fn iter(&self) -> Iter<(Cow<'a, str>, Cow<'a, str>)> {
|
||||
self.0.iter()
|
||||
pub fn iter(&self) -> ParamsIter {
|
||||
ParamsIter {
|
||||
idx: 0,
|
||||
params: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c: 'a> Index<&'b str> for &'c Params<'a> {
|
||||
#[derive(Debug)]
|
||||
pub struct ParamsIter<'a> {
|
||||
idx: usize,
|
||||
params: &'a Params,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ParamsIter<'a> {
|
||||
type Item = (&'a str, &'a str);
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<(&'a str, &'a str)> {
|
||||
if self.idx < self.params.len() {
|
||||
let idx = self.idx;
|
||||
let res = match self.params.segments[idx].1 {
|
||||
ParamItem::Static(ref s) => &s,
|
||||
ParamItem::UrlSegment(s, e) => {
|
||||
&self.params.url.path()[(s as usize)..(e as usize)]
|
||||
}
|
||||
};
|
||||
self.idx += 1;
|
||||
return Some((&self.params.segments[idx].0, res));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Index<&'a str> for Params {
|
||||
type Output = str;
|
||||
|
||||
fn index(&self, name: &'b str) -> &str {
|
||||
fn index(&self, name: &'a str) -> &str {
|
||||
self.get(name)
|
||||
.expect("Value for parameter is not available")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'c: 'a> Index<usize> for &'c Params<'a> {
|
||||
impl Index<usize> for Params {
|
||||
type Output = str;
|
||||
|
||||
fn index(&self, idx: usize) -> &str {
|
||||
self.0[idx].1.as_ref()
|
||||
match self.segments[idx].1 {
|
||||
ParamItem::Static(ref s) => &s,
|
||||
ParamItem::UrlSegment(s, e) => &self.url.path()[(s as usize)..(e as usize)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +247,6 @@ macro_rules! FROM_STR {
|
||||
($type:ty) => {
|
||||
impl FromParam for $type {
|
||||
type Err = InternalError<<$type as FromStr>::Err>;
|
||||
|
||||
fn from_param(val: &str) -> Result<Self, Self::Err> {
|
||||
<$type as FromStr>::from_str(val)
|
||||
.map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST))
|
||||
@@ -248,4 +311,24 @@ mod tests {
|
||||
Ok(PathBuf::from_iter(vec!["seg2"]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_param_by_name() {
|
||||
let mut params = Params::new();
|
||||
params.add_static("item1", "path");
|
||||
params.add_static("item2", "http%3A%2F%2Flocalhost%3A80%2Ffoo");
|
||||
|
||||
assert_eq!(params.get("item0"), None);
|
||||
assert_eq!(params.get_decoded("item0"), None);
|
||||
assert_eq!(params.get("item1"), Some("path"));
|
||||
assert_eq!(params.get_decoded("item1"), Some("path".to_string()));
|
||||
assert_eq!(
|
||||
params.get("item2"),
|
||||
Some("http%3A%2F%2Flocalhost%3A80%2Ffoo")
|
||||
);
|
||||
assert_eq!(
|
||||
params.get_decoded("item2"),
|
||||
Some("http://localhost:80/foo".to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
110
src/payload.rs
110
src/payload.rs
@@ -1,6 +1,8 @@
|
||||
//! Payload stream
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::task::{current as current_task, Task};
|
||||
#[cfg(not(test))]
|
||||
use futures::task::current as current_task;
|
||||
use futures::task::Task;
|
||||
use futures::{Async, Poll, Stream};
|
||||
use std::cell::RefCell;
|
||||
use std::cmp;
|
||||
@@ -59,20 +61,14 @@ impl Payload {
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates EOF of payload
|
||||
#[inline]
|
||||
pub fn eof(&self) -> bool {
|
||||
self.inner.borrow().eof()
|
||||
}
|
||||
|
||||
/// Length of the data in this payload
|
||||
#[inline]
|
||||
#[cfg(test)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.borrow().len()
|
||||
}
|
||||
|
||||
/// Is payload empty
|
||||
#[inline]
|
||||
#[cfg(test)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.borrow().len() == 0
|
||||
}
|
||||
@@ -225,12 +221,7 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn eof(&self) -> bool {
|
||||
self.items.is_empty() && self.eof
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(test)]
|
||||
fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
@@ -291,18 +282,20 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PayloadHelper<S> {
|
||||
/// Payload buffer
|
||||
pub struct PayloadBuffer<S> {
|
||||
len: usize,
|
||||
items: VecDeque<Bytes>,
|
||||
stream: S,
|
||||
}
|
||||
|
||||
impl<S> PayloadHelper<S>
|
||||
impl<S> PayloadBuffer<S>
|
||||
where
|
||||
S: Stream<Item = Bytes, Error = PayloadError>,
|
||||
{
|
||||
/// Create new `PayloadBuffer` instance
|
||||
pub fn new(stream: S) -> Self {
|
||||
PayloadHelper {
|
||||
PayloadBuffer {
|
||||
len: 0,
|
||||
items: VecDeque::new(),
|
||||
stream,
|
||||
@@ -327,6 +320,7 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
/// Read first available chunk of bytes
|
||||
#[inline]
|
||||
pub fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
if let Some(data) = self.items.pop_front() {
|
||||
@@ -341,6 +335,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if buffer contains enough bytes
|
||||
#[inline]
|
||||
pub fn can_read(&mut self, size: usize) -> Poll<Option<bool>, PayloadError> {
|
||||
if size <= self.len {
|
||||
@@ -354,6 +349,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Return reference to the first chunk of data
|
||||
#[inline]
|
||||
pub fn get_chunk(&mut self) -> Poll<Option<&[u8]>, PayloadError> {
|
||||
if self.items.is_empty() {
|
||||
@@ -369,6 +365,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Read exact number of bytes
|
||||
#[inline]
|
||||
pub fn read_exact(&mut self, size: usize) -> Poll<Option<Bytes>, PayloadError> {
|
||||
if size <= self.len {
|
||||
@@ -403,8 +400,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove specified amount if bytes from buffer
|
||||
#[inline]
|
||||
pub fn drop_payload(&mut self, size: usize) {
|
||||
pub fn drop_bytes(&mut self, size: usize) {
|
||||
if size <= self.len {
|
||||
self.len -= size;
|
||||
|
||||
@@ -421,6 +419,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy buffered data
|
||||
pub fn copy(&mut self, size: usize) -> Poll<Option<BytesMut>, PayloadError> {
|
||||
if size <= self.len {
|
||||
let mut buf = BytesMut::with_capacity(size);
|
||||
@@ -442,6 +441,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Read until specified ending
|
||||
pub fn read_until(&mut self, line: &[u8]) -> Poll<Option<Bytes>, PayloadError> {
|
||||
let mut idx = 0;
|
||||
let mut num = 0;
|
||||
@@ -497,24 +497,25 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Read bytes until new line delimiter
|
||||
pub fn readline(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
self.read_until(b"\n")
|
||||
}
|
||||
|
||||
pub fn unread_data(&mut self, data: Bytes) {
|
||||
/// Put unprocessed data back to the buffer
|
||||
pub fn unprocessed(&mut self, data: Bytes) {
|
||||
self.len += data.len();
|
||||
self.items.push_front(data);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Get remaining data from the buffer
|
||||
pub fn remaining(&mut self) -> Bytes {
|
||||
self.items
|
||||
.iter_mut()
|
||||
.fold(BytesMut::new(), |mut b, c| {
|
||||
b.extend_from_slice(c);
|
||||
b
|
||||
})
|
||||
.freeze()
|
||||
}).freeze()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,7 +525,7 @@ mod tests {
|
||||
use failure::Fail;
|
||||
use futures::future::{lazy, result};
|
||||
use std::io;
|
||||
use tokio_core::reactor::Core;
|
||||
use tokio::runtime::current_thread::Runtime;
|
||||
|
||||
#[test]
|
||||
fn test_error() {
|
||||
@@ -542,28 +543,27 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
Core::new()
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.run(lazy(|| {
|
||||
.block_on(lazy(|| {
|
||||
let (_, payload) = Payload::new(false);
|
||||
let mut payload = PayloadHelper::new(payload);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(payload.len, 0);
|
||||
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
}))
|
||||
.unwrap();
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eof() {
|
||||
Core::new()
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.run(lazy(|| {
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::new(false);
|
||||
let mut payload = PayloadHelper::new(payload);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
|
||||
sender.feed_data(Bytes::from("data"));
|
||||
@@ -578,17 +578,16 @@ mod tests {
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
}))
|
||||
.unwrap();
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_err() {
|
||||
Core::new()
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.run(lazy(|| {
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::new(false);
|
||||
let mut payload = PayloadHelper::new(payload);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
|
||||
|
||||
@@ -596,17 +595,16 @@ mod tests {
|
||||
payload.readany().err().unwrap();
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
}))
|
||||
.unwrap();
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readany() {
|
||||
Core::new()
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.run(lazy(|| {
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::new(false);
|
||||
let mut payload = PayloadHelper::new(payload);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
sender.feed_data(Bytes::from("line1"));
|
||||
sender.feed_data(Bytes::from("line2"));
|
||||
@@ -625,17 +623,16 @@ mod tests {
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
}))
|
||||
.unwrap();
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readexactly() {
|
||||
Core::new()
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.run(lazy(|| {
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::new(false);
|
||||
let mut payload = PayloadHelper::new(payload);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap());
|
||||
|
||||
@@ -659,17 +656,16 @@ mod tests {
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
}))
|
||||
.unwrap();
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readuntil() {
|
||||
Core::new()
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.run(lazy(|| {
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::new(false);
|
||||
let mut payload = PayloadHelper::new(payload);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap());
|
||||
|
||||
@@ -693,15 +689,14 @@ mod tests {
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
}))
|
||||
.unwrap();
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unread_data() {
|
||||
Core::new()
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.run(lazy(|| {
|
||||
.block_on(lazy(|| {
|
||||
let (_, mut payload) = Payload::new(false);
|
||||
|
||||
payload.unread_data(Bytes::from("data"));
|
||||
@@ -715,7 +710,6 @@ mod tests {
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
}))
|
||||
.unwrap();
|
||||
})).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
529
src/pipeline.rs
529
src/pipeline.rs
@@ -1,13 +1,11 @@
|
||||
use std::cell::UnsafeCell;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use std::{io, mem};
|
||||
|
||||
use futures::unsync::oneshot;
|
||||
use futures::sync::oneshot;
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use log::Level::Debug;
|
||||
|
||||
use application::Inner;
|
||||
use body::{Body, BodyStream};
|
||||
use context::{ActorHttpContext, Frame};
|
||||
use error::Error;
|
||||
@@ -18,22 +16,19 @@ use httpresponse::HttpResponse;
|
||||
use middleware::{Finished, Middleware, Response, Started};
|
||||
use server::{HttpHandlerTask, Writer, WriterState};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum HandlerType {
|
||||
Normal(usize),
|
||||
Handler(usize),
|
||||
Default,
|
||||
}
|
||||
|
||||
pub(crate) trait PipelineHandler<S> {
|
||||
#[doc(hidden)]
|
||||
pub trait PipelineHandler<S> {
|
||||
fn encoding(&self) -> ContentEncoding;
|
||||
|
||||
fn handle(
|
||||
&mut self, req: HttpRequest<S>, htype: HandlerType,
|
||||
) -> AsyncResult<HttpResponse>;
|
||||
fn handle(&self, &HttpRequest<S>) -> AsyncResult<HttpResponse>;
|
||||
}
|
||||
|
||||
pub(crate) struct Pipeline<S, H>(PipelineInfo<S>, PipelineState<S, H>);
|
||||
#[doc(hidden)]
|
||||
pub struct Pipeline<S: 'static, H>(
|
||||
PipelineInfo<S>,
|
||||
PipelineState<S, H>,
|
||||
Rc<Vec<Box<Middleware<S>>>>,
|
||||
);
|
||||
|
||||
enum PipelineState<S, H> {
|
||||
None,
|
||||
@@ -47,64 +42,31 @@ enum PipelineState<S, H> {
|
||||
}
|
||||
|
||||
impl<S: 'static, H: PipelineHandler<S>> PipelineState<S, H> {
|
||||
fn is_response(&self) -> bool {
|
||||
fn poll(
|
||||
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
|
||||
) -> Option<PipelineState<S, H>> {
|
||||
match *self {
|
||||
PipelineState::Response(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
|
||||
match *self {
|
||||
PipelineState::Starting(ref mut state) => state.poll(info),
|
||||
PipelineState::Handler(ref mut state) => state.poll(info),
|
||||
PipelineState::RunMiddlewares(ref mut state) => state.poll(info),
|
||||
PipelineState::Finishing(ref mut state) => state.poll(info),
|
||||
PipelineState::Starting(ref mut state) => state.poll(info, mws),
|
||||
PipelineState::Handler(ref mut state) => state.poll(info, mws),
|
||||
PipelineState::RunMiddlewares(ref mut state) => state.poll(info, mws),
|
||||
PipelineState::Finishing(ref mut state) => state.poll(info, mws),
|
||||
PipelineState::Completed(ref mut state) => state.poll(info),
|
||||
PipelineState::Response(_) | PipelineState::None | PipelineState::Error => {
|
||||
None
|
||||
}
|
||||
PipelineState::Response(ref mut state) => state.poll(info, mws),
|
||||
PipelineState::None | PipelineState::Error => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PipelineInfo<S> {
|
||||
req: UnsafeCell<HttpRequest<S>>,
|
||||
struct PipelineInfo<S: 'static> {
|
||||
req: HttpRequest<S>,
|
||||
count: u16,
|
||||
mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||
context: Option<Box<ActorHttpContext>>,
|
||||
error: Option<Error>,
|
||||
disconnected: Option<bool>,
|
||||
encoding: ContentEncoding,
|
||||
}
|
||||
|
||||
impl<S> PipelineInfo<S> {
|
||||
fn new(req: HttpRequest<S>) -> PipelineInfo<S> {
|
||||
PipelineInfo {
|
||||
req: UnsafeCell::new(req),
|
||||
count: 0,
|
||||
mws: Rc::new(Vec::new()),
|
||||
error: None,
|
||||
context: None,
|
||||
disconnected: None,
|
||||
encoding: ContentEncoding::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn req(&self) -> &HttpRequest<S> {
|
||||
unsafe { &*self.req.get() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
|
||||
fn req_mut(&self) -> &mut HttpRequest<S> {
|
||||
#[allow(mutable_transmutes)]
|
||||
unsafe {
|
||||
&mut *self.req.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> PipelineInfo<S> {
|
||||
fn poll_context(&mut self) -> Poll<(), Error> {
|
||||
if let Some(ref mut context) = self.context {
|
||||
match context.poll() {
|
||||
@@ -119,31 +81,20 @@ impl<S> PipelineInfo<S> {
|
||||
}
|
||||
|
||||
impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
|
||||
pub fn new(
|
||||
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||
handler: Rc<UnsafeCell<H>>, htype: HandlerType,
|
||||
pub(crate) fn new(
|
||||
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>, handler: Rc<H>,
|
||||
) -> Pipeline<S, H> {
|
||||
let mut info = PipelineInfo {
|
||||
mws,
|
||||
req: UnsafeCell::new(req),
|
||||
req,
|
||||
count: 0,
|
||||
error: None,
|
||||
context: None,
|
||||
disconnected: None,
|
||||
encoding: unsafe { &*handler.get() }.encoding(),
|
||||
encoding: handler.encoding(),
|
||||
};
|
||||
let state = StartMiddlewares::init(&mut info, handler, htype);
|
||||
let state = StartMiddlewares::init(&mut info, &mws, handler);
|
||||
|
||||
Pipeline(info, state)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pipeline<(), Inner<()>> {
|
||||
pub fn error<R: Into<HttpResponse>>(err: R) -> Box<HttpHandlerTask> {
|
||||
Box::new(Pipeline::<(), Inner<()>>(
|
||||
PipelineInfo::new(HttpRequest::default()),
|
||||
ProcessResponse::init(err.into()),
|
||||
))
|
||||
Pipeline(info, state, mws)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,29 +119,26 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
|
||||
}
|
||||
|
||||
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
|
||||
let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) };
|
||||
let mut state = mem::replace(&mut self.1, PipelineState::None);
|
||||
|
||||
loop {
|
||||
if self.1.is_response() {
|
||||
let state = mem::replace(&mut self.1, PipelineState::None);
|
||||
if let PipelineState::Response(st) = state {
|
||||
match st.poll_io(io, info) {
|
||||
Ok(state) => {
|
||||
self.1 = state;
|
||||
if let Some(error) = self.0.error.take() {
|
||||
return Err(error);
|
||||
} else {
|
||||
return Ok(Async::Ready(self.is_done()));
|
||||
}
|
||||
}
|
||||
Err(state) => {
|
||||
self.1 = state;
|
||||
return Ok(Async::NotReady);
|
||||
if let PipelineState::Response(st) = state {
|
||||
match st.poll_io(io, &mut self.0, &self.2) {
|
||||
Ok(state) => {
|
||||
self.1 = state;
|
||||
if let Some(error) = self.0.error.take() {
|
||||
return Err(error);
|
||||
} else {
|
||||
return Ok(Async::Ready(self.is_done()));
|
||||
}
|
||||
}
|
||||
Err(state) => {
|
||||
self.1 = state;
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
}
|
||||
}
|
||||
match self.1 {
|
||||
match state {
|
||||
PipelineState::None => return Ok(Async::Ready(true)),
|
||||
PipelineState::Error => {
|
||||
return Err(
|
||||
@@ -200,27 +148,32 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match self.1.poll(info) {
|
||||
Some(state) => self.1 = state,
|
||||
None => return Ok(Async::NotReady),
|
||||
match state.poll(&mut self.0, &self.2) {
|
||||
Some(st) => state = st,
|
||||
None => {
|
||||
return {
|
||||
self.1 = state;
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(&mut self) -> Poll<(), Error> {
|
||||
let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) };
|
||||
|
||||
fn poll_completed(&mut self) -> Poll<(), Error> {
|
||||
let mut state = mem::replace(&mut self.1, PipelineState::None);
|
||||
loop {
|
||||
match self.1 {
|
||||
match state {
|
||||
PipelineState::None | PipelineState::Error => {
|
||||
return Ok(Async::Ready(()))
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Some(state) = self.1.poll(info) {
|
||||
self.1 = state;
|
||||
if let Some(st) = state.poll(&mut self.0, &self.2) {
|
||||
state = st;
|
||||
} else {
|
||||
self.1 = state;
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
}
|
||||
@@ -231,76 +184,88 @@ type Fut = Box<Future<Item = Option<HttpResponse>, Error = Error>>;
|
||||
|
||||
/// Middlewares start executor
|
||||
struct StartMiddlewares<S, H> {
|
||||
hnd: Rc<UnsafeCell<H>>,
|
||||
htype: HandlerType,
|
||||
hnd: Rc<H>,
|
||||
fut: Option<Fut>,
|
||||
_s: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
||||
fn init(
|
||||
info: &mut PipelineInfo<S>, hnd: Rc<UnsafeCell<H>>, htype: HandlerType,
|
||||
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>], hnd: Rc<H>,
|
||||
) -> PipelineState<S, H> {
|
||||
// execute middlewares, we need this stage because middlewares could be
|
||||
// non-async and we can move to next state immediately
|
||||
let len = info.mws.len() as u16;
|
||||
let len = mws.len() as u16;
|
||||
|
||||
loop {
|
||||
if info.count == len {
|
||||
let reply = unsafe { &mut *hnd.get() }.handle(info.req().clone(), htype);
|
||||
return WaitingResponse::init(info, reply);
|
||||
let reply = hnd.handle(&info.req);
|
||||
return WaitingResponse::init(info, mws, reply);
|
||||
} else {
|
||||
match info.mws[info.count as usize].start(info.req_mut()) {
|
||||
match mws[info.count as usize].start(&info.req) {
|
||||
Ok(Started::Done) => info.count += 1,
|
||||
Ok(Started::Response(resp)) => {
|
||||
return RunMiddlewares::init(info, resp)
|
||||
return RunMiddlewares::init(info, mws, resp);
|
||||
}
|
||||
Ok(Started::Future(fut)) => {
|
||||
return PipelineState::Starting(StartMiddlewares {
|
||||
hnd,
|
||||
htype,
|
||||
fut: Some(fut),
|
||||
_s: PhantomData,
|
||||
})
|
||||
}
|
||||
Err(err) => return RunMiddlewares::init(info, err.into()),
|
||||
Err(err) => {
|
||||
return RunMiddlewares::init(info, mws, err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
|
||||
let len = info.mws.len() as u16;
|
||||
fn poll(
|
||||
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
|
||||
) -> Option<PipelineState<S, H>> {
|
||||
let len = mws.len() as u16;
|
||||
|
||||
'outer: loop {
|
||||
match self.fut.as_mut().unwrap().poll() {
|
||||
Ok(Async::NotReady) => return None,
|
||||
Ok(Async::NotReady) => {
|
||||
return None;
|
||||
}
|
||||
Ok(Async::Ready(resp)) => {
|
||||
info.count += 1;
|
||||
if let Some(resp) = resp {
|
||||
return Some(RunMiddlewares::init(info, resp));
|
||||
return Some(RunMiddlewares::init(info, mws, resp));
|
||||
}
|
||||
loop {
|
||||
if info.count == len {
|
||||
let reply = unsafe { &mut *self.hnd.get() }
|
||||
.handle(info.req().clone(), self.htype);
|
||||
return Some(WaitingResponse::init(info, reply));
|
||||
let reply = self.hnd.handle(&info.req);
|
||||
return Some(WaitingResponse::init(info, mws, reply));
|
||||
} else {
|
||||
match info.mws[info.count as usize].start(info.req_mut()) {
|
||||
let res = mws[info.count as usize].start(&info.req);
|
||||
match res {
|
||||
Ok(Started::Done) => info.count += 1,
|
||||
Ok(Started::Response(resp)) => {
|
||||
return Some(RunMiddlewares::init(info, resp));
|
||||
return Some(RunMiddlewares::init(info, mws, resp));
|
||||
}
|
||||
Ok(Started::Future(fut)) => {
|
||||
self.fut = Some(fut);
|
||||
continue 'outer;
|
||||
}
|
||||
Err(err) => {
|
||||
return Some(RunMiddlewares::init(info, err.into()))
|
||||
return Some(RunMiddlewares::init(
|
||||
info,
|
||||
mws,
|
||||
err.into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => return Some(RunMiddlewares::init(info, err.into())),
|
||||
Err(err) => {
|
||||
return Some(RunMiddlewares::init(info, mws, err.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,11 +281,12 @@ struct WaitingResponse<S, H> {
|
||||
impl<S: 'static, H> WaitingResponse<S, H> {
|
||||
#[inline]
|
||||
fn init(
|
||||
info: &mut PipelineInfo<S>, reply: AsyncResult<HttpResponse>,
|
||||
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
|
||||
reply: AsyncResult<HttpResponse>,
|
||||
) -> PipelineState<S, H> {
|
||||
match reply.into() {
|
||||
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
|
||||
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
|
||||
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, mws, resp),
|
||||
AsyncResultItem::Err(err) => RunMiddlewares::init(info, mws, err.into()),
|
||||
AsyncResultItem::Future(fut) => PipelineState::Handler(WaitingResponse {
|
||||
fut,
|
||||
_s: PhantomData,
|
||||
@@ -329,11 +295,13 @@ impl<S: 'static, H> WaitingResponse<S, H> {
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
|
||||
fn poll(
|
||||
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
|
||||
) -> Option<PipelineState<S, H>> {
|
||||
match self.fut.poll() {
|
||||
Ok(Async::NotReady) => None,
|
||||
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
|
||||
Err(err) => Some(RunMiddlewares::init(info, err.into())),
|
||||
Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, mws, resp)),
|
||||
Err(err) => Some(RunMiddlewares::init(info, mws, err.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,15 +316,18 @@ struct RunMiddlewares<S, H> {
|
||||
|
||||
impl<S: 'static, H> RunMiddlewares<S, H> {
|
||||
#[inline]
|
||||
fn init(info: &mut PipelineInfo<S>, mut resp: HttpResponse) -> PipelineState<S, H> {
|
||||
fn init(
|
||||
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>], mut resp: HttpResponse,
|
||||
) -> PipelineState<S, H> {
|
||||
if info.count == 0 {
|
||||
return ProcessResponse::init(resp);
|
||||
}
|
||||
let mut curr = 0;
|
||||
let len = info.mws.len();
|
||||
let len = mws.len();
|
||||
|
||||
loop {
|
||||
resp = match info.mws[curr].response(info.req_mut(), resp) {
|
||||
let state = mws[curr].response(&info.req, resp);
|
||||
resp = match state {
|
||||
Err(err) => {
|
||||
info.count = (curr + 1) as u16;
|
||||
return ProcessResponse::init(err.into());
|
||||
@@ -375,14 +346,16 @@ impl<S: 'static, H> RunMiddlewares<S, H> {
|
||||
fut: Some(fut),
|
||||
_s: PhantomData,
|
||||
_h: PhantomData,
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
|
||||
let len = info.mws.len();
|
||||
fn poll(
|
||||
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
|
||||
) -> Option<PipelineState<S, H>> {
|
||||
let len = mws.len();
|
||||
|
||||
loop {
|
||||
// poll latest fut
|
||||
@@ -399,7 +372,8 @@ impl<S: 'static, H> RunMiddlewares<S, H> {
|
||||
if self.curr == len {
|
||||
return Some(ProcessResponse::init(resp));
|
||||
} else {
|
||||
match info.mws[self.curr].response(info.req_mut(), resp) {
|
||||
let state = mws[self.curr].response(&info.req, resp);
|
||||
match state {
|
||||
Err(err) => return Some(ProcessResponse::init(err.into())),
|
||||
Ok(Response::Done(r)) => {
|
||||
self.curr += 1;
|
||||
@@ -417,7 +391,7 @@ impl<S: 'static, H> RunMiddlewares<S, H> {
|
||||
}
|
||||
|
||||
struct ProcessResponse<S, H> {
|
||||
resp: HttpResponse,
|
||||
resp: Option<HttpResponse>,
|
||||
iostate: IOState,
|
||||
running: RunningState,
|
||||
drain: Option<oneshot::Sender<()>>,
|
||||
@@ -425,7 +399,7 @@ struct ProcessResponse<S, H> {
|
||||
_h: PhantomData<H>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum RunningState {
|
||||
Running,
|
||||
Paused,
|
||||
@@ -458,7 +432,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
#[inline]
|
||||
fn init(resp: HttpResponse) -> PipelineState<S, H> {
|
||||
PipelineState::Response(ProcessResponse {
|
||||
resp,
|
||||
resp: Some(resp),
|
||||
iostate: IOState::Response,
|
||||
running: RunningState::Running,
|
||||
drain: None,
|
||||
@@ -467,8 +441,82 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
})
|
||||
}
|
||||
|
||||
fn poll(
|
||||
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
|
||||
) -> Option<PipelineState<S, H>> {
|
||||
// connection is dead at this point
|
||||
match mem::replace(&mut self.iostate, IOState::Done) {
|
||||
IOState::Response => Some(FinishingMiddlewares::init(
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
)),
|
||||
IOState::Payload(_) => Some(FinishingMiddlewares::init(
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
)),
|
||||
IOState::Actor(mut ctx) => {
|
||||
if info.disconnected.take().is_some() {
|
||||
ctx.disconnected();
|
||||
}
|
||||
loop {
|
||||
match ctx.poll() {
|
||||
Ok(Async::Ready(Some(vec))) => {
|
||||
if vec.is_empty() {
|
||||
continue;
|
||||
}
|
||||
for frame in vec {
|
||||
match frame {
|
||||
Frame::Chunk(None) => {
|
||||
info.context = Some(ctx);
|
||||
return Some(FinishingMiddlewares::init(
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
));
|
||||
}
|
||||
Frame::Chunk(Some(_)) => (),
|
||||
Frame::Drain(fut) => {
|
||||
let _ = fut.send(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(None)) => {
|
||||
return Some(FinishingMiddlewares::init(
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
))
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
self.iostate = IOState::Actor(ctx);
|
||||
return None;
|
||||
}
|
||||
Err(err) => {
|
||||
info.context = Some(ctx);
|
||||
info.error = Some(err);
|
||||
return Some(FinishingMiddlewares::init(
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
IOState::Done => Some(FinishingMiddlewares::init(
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_io(
|
||||
mut self, io: &mut Writer, info: &mut PipelineInfo<S>,
|
||||
mws: &[Box<Middleware<S>>],
|
||||
) -> Result<PipelineState<S, H>, PipelineState<S, H>> {
|
||||
loop {
|
||||
if self.drain.is_none() && self.running != RunningState::Paused {
|
||||
@@ -476,32 +524,39 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
'inner: loop {
|
||||
let result = match mem::replace(&mut self.iostate, IOState::Done) {
|
||||
IOState::Response => {
|
||||
let encoding =
|
||||
self.resp.content_encoding().unwrap_or(info.encoding);
|
||||
let encoding = self
|
||||
.resp
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.content_encoding()
|
||||
.unwrap_or(info.encoding);
|
||||
|
||||
let result = match io.start(
|
||||
info.req_mut().get_inner(),
|
||||
&mut self.resp,
|
||||
&info.req,
|
||||
self.resp.as_mut().unwrap(),
|
||||
encoding,
|
||||
) {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
info.error = Some(err.into());
|
||||
return Ok(FinishingMiddlewares::init(
|
||||
info, self.resp,
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(err) = self.resp.error() {
|
||||
if self.resp.status().is_server_error() {
|
||||
if let Some(err) = self.resp.as_ref().unwrap().error() {
|
||||
if self.resp.as_ref().unwrap().status().is_server_error()
|
||||
{
|
||||
error!(
|
||||
"Error occured during request handling, status: {} {}",
|
||||
self.resp.status(), err
|
||||
"Error occurred during request handling, status: {} {}",
|
||||
self.resp.as_ref().unwrap().status(), err
|
||||
);
|
||||
} else {
|
||||
warn!(
|
||||
"Error occured during request handling: {}",
|
||||
"Error occurred during request handling: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
@@ -511,7 +566,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
}
|
||||
|
||||
// always poll stream or actor for the first time
|
||||
match self.resp.replace_body(Body::Empty) {
|
||||
match self.resp.as_mut().unwrap().replace_body(Body::Empty) {
|
||||
Body::Streaming(stream) => {
|
||||
self.iostate = IOState::Payload(stream);
|
||||
continue 'inner;
|
||||
@@ -530,18 +585,22 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
if let Err(err) = io.write_eof() {
|
||||
info.error = Some(err.into());
|
||||
return Ok(FinishingMiddlewares::init(
|
||||
info, self.resp,
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
));
|
||||
}
|
||||
break;
|
||||
}
|
||||
Ok(Async::Ready(Some(chunk))) => {
|
||||
self.iostate = IOState::Payload(body);
|
||||
match io.write(chunk.into()) {
|
||||
match io.write(&chunk.into()) {
|
||||
Err(err) => {
|
||||
info.error = Some(err.into());
|
||||
return Ok(FinishingMiddlewares::init(
|
||||
info, self.resp,
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
));
|
||||
}
|
||||
Ok(result) => result,
|
||||
@@ -553,7 +612,11 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
}
|
||||
Err(err) => {
|
||||
info.error = Some(err);
|
||||
return Ok(FinishingMiddlewares::init(info, self.resp));
|
||||
return Ok(FinishingMiddlewares::init(
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
));
|
||||
}
|
||||
},
|
||||
IOState::Actor(mut ctx) => {
|
||||
@@ -575,25 +638,30 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
info.error = Some(err.into());
|
||||
return Ok(
|
||||
FinishingMiddlewares::init(
|
||||
info, self.resp,
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
),
|
||||
);
|
||||
}
|
||||
break 'inner;
|
||||
}
|
||||
Frame::Chunk(Some(chunk)) => {
|
||||
match io.write(chunk) {
|
||||
Err(err) => {
|
||||
info.error = Some(err.into());
|
||||
return Ok(
|
||||
FinishingMiddlewares::init(
|
||||
info, self.resp,
|
||||
),
|
||||
);
|
||||
}
|
||||
Ok(result) => res = Some(result),
|
||||
Frame::Chunk(Some(chunk)) => match io
|
||||
.write(&chunk)
|
||||
{
|
||||
Err(err) => {
|
||||
info.context = Some(ctx);
|
||||
info.error = Some(err.into());
|
||||
return Ok(
|
||||
FinishingMiddlewares::init(
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(result) => res = Some(result),
|
||||
},
|
||||
Frame::Drain(fut) => self.drain = Some(fut),
|
||||
}
|
||||
}
|
||||
@@ -610,9 +678,12 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
info.context = Some(ctx);
|
||||
info.error = Some(err);
|
||||
return Ok(FinishingMiddlewares::init(
|
||||
info, self.resp,
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -645,8 +716,18 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
}
|
||||
Ok(Async::NotReady) => return Err(PipelineState::Response(self)),
|
||||
Err(err) => {
|
||||
if let IOState::Actor(mut ctx) =
|
||||
mem::replace(&mut self.iostate, IOState::Done)
|
||||
{
|
||||
ctx.disconnected();
|
||||
info.context = Some(ctx);
|
||||
}
|
||||
info.error = Some(err.into());
|
||||
return Ok(FinishingMiddlewares::init(info, self.resp));
|
||||
return Ok(FinishingMiddlewares::init(
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -660,11 +741,19 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
info.error = Some(err.into());
|
||||
return Ok(FinishingMiddlewares::init(info, self.resp));
|
||||
return Ok(FinishingMiddlewares::init(
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
self.resp.set_response_size(io.written());
|
||||
Ok(FinishingMiddlewares::init(info, self.resp))
|
||||
self.resp.as_mut().unwrap().set_response_size(io.written());
|
||||
Ok(FinishingMiddlewares::init(
|
||||
info,
|
||||
mws,
|
||||
self.resp.take().unwrap(),
|
||||
))
|
||||
}
|
||||
_ => Err(PipelineState::Response(self)),
|
||||
}
|
||||
@@ -673,7 +762,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
|
||||
/// Middlewares start executor
|
||||
struct FinishingMiddlewares<S, H> {
|
||||
resp: HttpResponse,
|
||||
resp: Option<HttpResponse>,
|
||||
fut: Option<Box<Future<Item = (), Error = Error>>>,
|
||||
_s: PhantomData<S>,
|
||||
_h: PhantomData<H>,
|
||||
@@ -681,17 +770,20 @@ struct FinishingMiddlewares<S, H> {
|
||||
|
||||
impl<S: 'static, H> FinishingMiddlewares<S, H> {
|
||||
#[inline]
|
||||
fn init(info: &mut PipelineInfo<S>, resp: HttpResponse) -> PipelineState<S, H> {
|
||||
fn init(
|
||||
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>], resp: HttpResponse,
|
||||
) -> PipelineState<S, H> {
|
||||
if info.count == 0 {
|
||||
resp.release();
|
||||
Completed::init(info)
|
||||
} else {
|
||||
let mut state = FinishingMiddlewares {
|
||||
resp,
|
||||
resp: Some(resp),
|
||||
fut: None,
|
||||
_s: PhantomData,
|
||||
_h: PhantomData,
|
||||
};
|
||||
if let Some(st) = state.poll(info) {
|
||||
if let Some(st) = state.poll(info, mws) {
|
||||
st
|
||||
} else {
|
||||
PipelineState::Finishing(state)
|
||||
@@ -699,7 +791,9 @@ impl<S: 'static, H> FinishingMiddlewares<S, H> {
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
|
||||
fn poll(
|
||||
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
|
||||
) -> Option<PipelineState<S, H>> {
|
||||
loop {
|
||||
// poll latest fut
|
||||
let not_ready = if let Some(ref mut fut) = self.fut {
|
||||
@@ -719,13 +813,17 @@ impl<S: 'static, H> FinishingMiddlewares<S, H> {
|
||||
}
|
||||
self.fut = None;
|
||||
if info.count == 0 {
|
||||
self.resp.take().unwrap().release();
|
||||
return Some(Completed::init(info));
|
||||
}
|
||||
|
||||
info.count -= 1;
|
||||
match info.mws[info.count as usize].finish(info.req_mut(), &self.resp) {
|
||||
let state =
|
||||
mws[info.count as usize].finish(&info.req, self.resp.as_ref().unwrap());
|
||||
match state {
|
||||
Finished::Done => {
|
||||
if info.count == 0 {
|
||||
self.resp.take().unwrap().release();
|
||||
return Some(Completed::init(info));
|
||||
}
|
||||
}
|
||||
@@ -750,7 +848,13 @@ impl<S, H> Completed<S, H> {
|
||||
if info.context.is_none() {
|
||||
PipelineState::None
|
||||
} else {
|
||||
PipelineState::Completed(Completed(PhantomData, PhantomData))
|
||||
match info.poll_context() {
|
||||
Ok(Async::NotReady) => {
|
||||
PipelineState::Completed(Completed(PhantomData, PhantomData))
|
||||
}
|
||||
Ok(Async::Ready(())) => PipelineState::None,
|
||||
Err(_) => PipelineState::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,68 +867,3 @@ impl<S, H> Completed<S, H> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use actix::*;
|
||||
use context::HttpContext;
|
||||
use futures::future::{lazy, result};
|
||||
use tokio_core::reactor::Core;
|
||||
|
||||
impl<S, H> PipelineState<S, H> {
|
||||
fn is_none(&self) -> Option<bool> {
|
||||
if let PipelineState::None = *self {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn completed(self) -> Option<Completed<S, H>> {
|
||||
if let PipelineState::Completed(c) = self {
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MyActor;
|
||||
impl Actor for MyActor {
|
||||
type Context = HttpContext<MyActor>;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completed() {
|
||||
Core::new()
|
||||
.unwrap()
|
||||
.run(lazy(|| {
|
||||
let mut info = PipelineInfo::new(HttpRequest::default());
|
||||
Completed::<(), Inner<()>>::init(&mut info)
|
||||
.is_none()
|
||||
.unwrap();
|
||||
|
||||
let req = HttpRequest::default();
|
||||
let mut ctx = HttpContext::new(req.clone(), MyActor);
|
||||
let addr: Addr<Unsync, _> = ctx.address();
|
||||
let mut info = PipelineInfo::new(req);
|
||||
info.context = Some(Box::new(ctx));
|
||||
let mut state = Completed::<(), Inner<()>>::init(&mut info)
|
||||
.completed()
|
||||
.unwrap();
|
||||
|
||||
assert!(state.poll(&mut info).is_none());
|
||||
let pp = Pipeline(info, PipelineState::Completed(state));
|
||||
assert!(!pp.is_done());
|
||||
|
||||
let Pipeline(mut info, st) = pp;
|
||||
let mut st = st.completed().unwrap();
|
||||
drop(addr);
|
||||
|
||||
assert!(st.poll(&mut info).unwrap().is_none().unwrap());
|
||||
|
||||
result(Ok::<_, ()>(()))
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
249
src/pred.rs
249
src/pred.rs
@@ -1,10 +1,10 @@
|
||||
//! Route match predicates
|
||||
#![allow(non_snake_case)]
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use http;
|
||||
use http::{header, HttpTryFrom};
|
||||
use httpmessage::HttpMessage;
|
||||
use httprequest::HttpRequest;
|
||||
use std::marker::PhantomData;
|
||||
use server::message::Request;
|
||||
|
||||
/// Trait defines resource route predicate.
|
||||
/// Predicate can modify request object. It is also possible to
|
||||
@@ -12,7 +12,7 @@ use std::marker::PhantomData;
|
||||
/// Extensions container available via `HttpRequest::extensions()` method.
|
||||
pub trait Predicate<S> {
|
||||
/// Check if request matches predicate
|
||||
fn check(&self, &mut HttpRequest<S>) -> bool;
|
||||
fn check(&self, &Request, &S) -> bool;
|
||||
}
|
||||
|
||||
/// Return predicate that matches if any of supplied predicate matches.
|
||||
@@ -22,10 +22,11 @@ pub trait Predicate<S> {
|
||||
/// use actix_web::{pred, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// App::new()
|
||||
/// .resource("/index.html", |r| r.route()
|
||||
/// App::new().resource("/index.html", |r| {
|
||||
/// r.route()
|
||||
/// .filter(pred::Any(pred::Get()).or(pred::Post()))
|
||||
/// .f(|r| HttpResponse::MethodNotAllowed()));
|
||||
/// .f(|r| HttpResponse::MethodNotAllowed())
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
pub fn Any<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AnyPredicate<S> {
|
||||
@@ -44,9 +45,9 @@ impl<S> AnyPredicate<S> {
|
||||
}
|
||||
|
||||
impl<S: 'static> Predicate<S> for AnyPredicate<S> {
|
||||
fn check(&self, req: &mut HttpRequest<S>) -> bool {
|
||||
fn check(&self, req: &Request, state: &S) -> bool {
|
||||
for p in &self.0 {
|
||||
if p.check(req) {
|
||||
if p.check(req, state) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -61,11 +62,14 @@ impl<S: 'static> Predicate<S> for AnyPredicate<S> {
|
||||
/// use actix_web::{pred, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// App::new()
|
||||
/// .resource("/index.html", |r| r.route()
|
||||
/// .filter(pred::All(pred::Get())
|
||||
/// .and(pred::Header("content-type", "plain/text")))
|
||||
/// .f(|_| HttpResponse::MethodNotAllowed()));
|
||||
/// App::new().resource("/index.html", |r| {
|
||||
/// r.route()
|
||||
/// .filter(
|
||||
/// pred::All(pred::Get())
|
||||
/// .and(pred::Header("content-type", "text/plain")),
|
||||
/// )
|
||||
/// .f(|_| HttpResponse::MethodNotAllowed())
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
pub fn All<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AllPredicate<S> {
|
||||
@@ -84,9 +88,9 @@ impl<S> AllPredicate<S> {
|
||||
}
|
||||
|
||||
impl<S: 'static> Predicate<S> for AllPredicate<S> {
|
||||
fn check(&self, req: &mut HttpRequest<S>) -> bool {
|
||||
fn check(&self, req: &Request, state: &S) -> bool {
|
||||
for p in &self.0 {
|
||||
if !p.check(req) {
|
||||
if !p.check(req, state) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -103,8 +107,8 @@ pub fn Not<S: 'static, P: Predicate<S> + 'static>(pred: P) -> NotPredicate<S> {
|
||||
pub struct NotPredicate<S>(Box<Predicate<S>>);
|
||||
|
||||
impl<S: 'static> Predicate<S> for NotPredicate<S> {
|
||||
fn check(&self, req: &mut HttpRequest<S>) -> bool {
|
||||
!self.0.check(req)
|
||||
fn check(&self, req: &Request, state: &S) -> bool {
|
||||
!self.0.check(req, state)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +117,7 @@ impl<S: 'static> Predicate<S> for NotPredicate<S> {
|
||||
pub struct MethodPredicate<S>(http::Method, PhantomData<S>);
|
||||
|
||||
impl<S: 'static> Predicate<S> for MethodPredicate<S> {
|
||||
fn check(&self, req: &mut HttpRequest<S>) -> bool {
|
||||
fn check(&self, req: &Request, _: &S) -> bool {
|
||||
*req.method() == self.0
|
||||
}
|
||||
}
|
||||
@@ -184,7 +188,7 @@ pub fn Header<S: 'static>(
|
||||
pub struct HeaderPredicate<S>(header::HeaderName, header::HeaderValue, PhantomData<S>);
|
||||
|
||||
impl<S: 'static> Predicate<S> for HeaderPredicate<S> {
|
||||
fn check(&self, req: &mut HttpRequest<S>) -> bool {
|
||||
fn check(&self, req: &Request, _: &S) -> bool {
|
||||
if let Some(val) = req.headers().get(&self.0) {
|
||||
return val == self.1;
|
||||
}
|
||||
@@ -192,148 +196,133 @@ impl<S: 'static> Predicate<S> for HeaderPredicate<S> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return predicate that matches if request contains specified Host name.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{pred, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// App::new().resource("/index.html", |r| {
|
||||
/// r.route()
|
||||
/// .filter(pred::Host("www.rust-lang.org"))
|
||||
/// .f(|_| HttpResponse::MethodNotAllowed())
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
pub fn Host<S: 'static, H: AsRef<str>>(host: H) -> HostPredicate<S> {
|
||||
HostPredicate(host.as_ref().to_string(), None, PhantomData)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct HostPredicate<S>(String, Option<String>, PhantomData<S>);
|
||||
|
||||
impl<S> HostPredicate<S> {
|
||||
/// Set reuest scheme to match
|
||||
pub fn scheme<H: AsRef<str>>(&mut self, scheme: H) {
|
||||
self.1 = Some(scheme.as_ref().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Predicate<S> for HostPredicate<S> {
|
||||
fn check(&self, req: &Request, _: &S) -> bool {
|
||||
let info = req.connection_info();
|
||||
if let Some(ref scheme) = self.1 {
|
||||
self.0 == info.host() && scheme == info.scheme()
|
||||
} else {
|
||||
self.0 == info.host()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::header::{self, HeaderMap};
|
||||
use http::{Method, Uri, Version};
|
||||
use std::str::FromStr;
|
||||
use http::{header, Method};
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_header() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
let req = TestRequest::with_header(
|
||||
header::TRANSFER_ENCODING,
|
||||
header::HeaderValue::from_static("chunked"),
|
||||
);
|
||||
let mut req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
headers,
|
||||
None,
|
||||
);
|
||||
).finish();
|
||||
|
||||
let pred = Header("transfer-encoding", "chunked");
|
||||
assert!(pred.check(&mut req));
|
||||
assert!(pred.check(&req, req.state()));
|
||||
|
||||
let pred = Header("transfer-encoding", "other");
|
||||
assert!(!pred.check(&mut req));
|
||||
assert!(!pred.check(&req, req.state()));
|
||||
|
||||
let pred = Header("content-type", "other");
|
||||
assert!(!pred.check(&mut req));
|
||||
assert!(!pred.check(&req, req.state()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_host() {
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::HOST,
|
||||
header::HeaderValue::from_static("www.rust-lang.org"),
|
||||
).finish();
|
||||
|
||||
let pred = Host("www.rust-lang.org");
|
||||
assert!(pred.check(&req, req.state()));
|
||||
|
||||
let pred = Host("localhost");
|
||||
assert!(!pred.check(&req, req.state()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_methods() {
|
||||
let mut req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
HeaderMap::new(),
|
||||
None,
|
||||
);
|
||||
let mut req2 = HttpRequest::new(
|
||||
Method::POST,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
HeaderMap::new(),
|
||||
None,
|
||||
);
|
||||
let req = TestRequest::default().finish();
|
||||
let req2 = TestRequest::default().method(Method::POST).finish();
|
||||
|
||||
assert!(Get().check(&mut req));
|
||||
assert!(!Get().check(&mut req2));
|
||||
assert!(Post().check(&mut req2));
|
||||
assert!(!Post().check(&mut req));
|
||||
assert!(Get().check(&req, req.state()));
|
||||
assert!(!Get().check(&req2, req2.state()));
|
||||
assert!(Post().check(&req2, req2.state()));
|
||||
assert!(!Post().check(&req, req.state()));
|
||||
|
||||
let mut r = HttpRequest::new(
|
||||
Method::PUT,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
HeaderMap::new(),
|
||||
None,
|
||||
);
|
||||
assert!(Put().check(&mut r));
|
||||
assert!(!Put().check(&mut req));
|
||||
let r = TestRequest::default().method(Method::PUT).finish();
|
||||
assert!(Put().check(&r, r.state()));
|
||||
assert!(!Put().check(&req, req.state()));
|
||||
|
||||
let mut r = HttpRequest::new(
|
||||
Method::DELETE,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
HeaderMap::new(),
|
||||
None,
|
||||
);
|
||||
assert!(Delete().check(&mut r));
|
||||
assert!(!Delete().check(&mut req));
|
||||
let r = TestRequest::default().method(Method::DELETE).finish();
|
||||
assert!(Delete().check(&r, r.state()));
|
||||
assert!(!Delete().check(&req, req.state()));
|
||||
|
||||
let mut r = HttpRequest::new(
|
||||
Method::HEAD,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
HeaderMap::new(),
|
||||
None,
|
||||
);
|
||||
assert!(Head().check(&mut r));
|
||||
assert!(!Head().check(&mut req));
|
||||
let r = TestRequest::default().method(Method::HEAD).finish();
|
||||
assert!(Head().check(&r, r.state()));
|
||||
assert!(!Head().check(&req, req.state()));
|
||||
|
||||
let mut r = HttpRequest::new(
|
||||
Method::OPTIONS,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
HeaderMap::new(),
|
||||
None,
|
||||
);
|
||||
assert!(Options().check(&mut r));
|
||||
assert!(!Options().check(&mut req));
|
||||
let r = TestRequest::default().method(Method::OPTIONS).finish();
|
||||
assert!(Options().check(&r, r.state()));
|
||||
assert!(!Options().check(&req, req.state()));
|
||||
|
||||
let mut r = HttpRequest::new(
|
||||
Method::CONNECT,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
HeaderMap::new(),
|
||||
None,
|
||||
);
|
||||
assert!(Connect().check(&mut r));
|
||||
assert!(!Connect().check(&mut req));
|
||||
let r = TestRequest::default().method(Method::CONNECT).finish();
|
||||
assert!(Connect().check(&r, r.state()));
|
||||
assert!(!Connect().check(&req, req.state()));
|
||||
|
||||
let mut r = HttpRequest::new(
|
||||
Method::PATCH,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
HeaderMap::new(),
|
||||
None,
|
||||
);
|
||||
assert!(Patch().check(&mut r));
|
||||
assert!(!Patch().check(&mut req));
|
||||
let r = TestRequest::default().method(Method::PATCH).finish();
|
||||
assert!(Patch().check(&r, r.state()));
|
||||
assert!(!Patch().check(&req, req.state()));
|
||||
|
||||
let mut r = HttpRequest::new(
|
||||
Method::TRACE,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
HeaderMap::new(),
|
||||
None,
|
||||
);
|
||||
assert!(Trace().check(&mut r));
|
||||
assert!(!Trace().check(&mut req));
|
||||
let r = TestRequest::default().method(Method::TRACE).finish();
|
||||
assert!(Trace().check(&r, r.state()));
|
||||
assert!(!Trace().check(&req, req.state()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_preds() {
|
||||
let mut r = HttpRequest::new(
|
||||
Method::TRACE,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
HeaderMap::new(),
|
||||
None,
|
||||
);
|
||||
let r = TestRequest::default().method(Method::TRACE).finish();
|
||||
|
||||
assert!(Not(Get()).check(&mut r));
|
||||
assert!(!Not(Trace()).check(&mut r));
|
||||
assert!(Not(Get()).check(&r, r.state()));
|
||||
assert!(!Not(Trace()).check(&r, r.state()));
|
||||
|
||||
assert!(All(Trace()).and(Trace()).check(&mut r));
|
||||
assert!(!All(Get()).and(Trace()).check(&mut r));
|
||||
assert!(All(Trace()).and(Trace()).check(&r, r.state()));
|
||||
assert!(!All(Get()).and(Trace()).check(&r, r.state()));
|
||||
|
||||
assert!(Any(Get()).or(Trace()).check(&mut r));
|
||||
assert!(!Any(Get()).or(Get()).check(&mut r));
|
||||
assert!(Any(Get()).or(Trace()).check(&r, r.state()));
|
||||
assert!(!Any(Get()).or(Get()).check(&r, r.state()));
|
||||
}
|
||||
}
|
||||
|
||||
197
src/resource.rs
197
src/resource.rs
@@ -1,8 +1,8 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use futures::Future;
|
||||
use http::{Method, StatusCode};
|
||||
use http::Method;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use error::Error;
|
||||
@@ -12,6 +12,11 @@ use httpresponse::HttpResponse;
|
||||
use middleware::Middleware;
|
||||
use pred;
|
||||
use route::Route;
|
||||
use router::ResourceDef;
|
||||
use with::WithFactory;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct RouteId(usize);
|
||||
|
||||
/// *Resource* is an entry in route table which corresponds to requested URL.
|
||||
///
|
||||
@@ -33,45 +38,39 @@ use route::Route;
|
||||
/// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok()))
|
||||
/// .finish();
|
||||
/// }
|
||||
pub struct ResourceHandler<S = ()> {
|
||||
name: String,
|
||||
state: PhantomData<S>,
|
||||
pub struct Resource<S = ()> {
|
||||
rdef: ResourceDef,
|
||||
routes: SmallVec<[Route<S>; 3]>,
|
||||
middlewares: Rc<Vec<Box<Middleware<S>>>>,
|
||||
}
|
||||
|
||||
impl<S> Default for ResourceHandler<S> {
|
||||
fn default() -> Self {
|
||||
ResourceHandler {
|
||||
name: String::new(),
|
||||
state: PhantomData,
|
||||
impl<S> Resource<S> {
|
||||
/// Create new resource with specified resource definition
|
||||
pub fn new(rdef: ResourceDef) -> Self {
|
||||
Resource {
|
||||
rdef,
|
||||
routes: SmallVec::new(),
|
||||
middlewares: Rc::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> ResourceHandler<S> {
|
||||
pub(crate) fn default_not_found() -> Self {
|
||||
ResourceHandler {
|
||||
name: String::new(),
|
||||
state: PhantomData,
|
||||
routes: SmallVec::new(),
|
||||
middlewares: Rc::new(Vec::new()),
|
||||
}
|
||||
/// Name of the resource
|
||||
pub(crate) fn get_name(&self) -> &str {
|
||||
self.rdef.name()
|
||||
}
|
||||
|
||||
/// Set resource name
|
||||
pub fn name<T: Into<String>>(&mut self, name: T) {
|
||||
self.name = name.into();
|
||||
pub fn name(&mut self, name: &str) {
|
||||
self.rdef.set_name(name);
|
||||
}
|
||||
|
||||
pub(crate) fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
/// Resource definition
|
||||
pub fn rdef(&self) -> &ResourceDef {
|
||||
&self.rdef
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> ResourceHandler<S> {
|
||||
impl<S: 'static> Resource<S> {
|
||||
/// Register a new route and return mutable reference to *Route* object.
|
||||
/// *Route* is used for route configuration, i.e. adding predicates,
|
||||
/// setting up handler.
|
||||
@@ -82,11 +81,12 @@ impl<S: 'static> ResourceHandler<S> {
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .resource(
|
||||
/// "/", |r| r.route()
|
||||
/// .filter(pred::Any(pred::Get()).or(pred::Put()))
|
||||
/// .filter(pred::Header("Content-Type", "text/plain"))
|
||||
/// .f(|r| HttpResponse::Ok()))
|
||||
/// .resource("/", |r| {
|
||||
/// r.route()
|
||||
/// .filter(pred::Any(pred::Get()).or(pred::Put()))
|
||||
/// .filter(pred::Header("Content-Type", "text/plain"))
|
||||
/// .f(|r| HttpResponse::Ok())
|
||||
/// })
|
||||
/// .finish();
|
||||
/// }
|
||||
/// ```
|
||||
@@ -127,10 +127,21 @@ impl<S: 'static> ResourceHandler<S> {
|
||||
|
||||
/// Register a new route and add method check to route.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::*;
|
||||
/// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
|
||||
///
|
||||
/// App::new().resource("/", |r| r.method(http::Method::GET).f(index));
|
||||
/// ```
|
||||
///
|
||||
/// This is shortcut for:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// Application::resource("/", |r| r.route().filter(pred::Get()).f(index)
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # use actix_web::*;
|
||||
/// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
|
||||
/// App::new().resource("/", |r| r.route().filter(pred::Get()).f(index));
|
||||
/// ```
|
||||
pub fn method(&mut self, method: Method) -> &mut Route<S> {
|
||||
self.routes.push(Route::default());
|
||||
@@ -139,10 +150,21 @@ impl<S: 'static> ResourceHandler<S> {
|
||||
|
||||
/// Register a new route and add handler object.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::*;
|
||||
/// fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() }
|
||||
///
|
||||
/// App::new().resource("/", |r| r.h(handler));
|
||||
/// ```
|
||||
///
|
||||
/// This is shortcut for:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// Application::resource("/", |r| r.route().h(handler)
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # use actix_web::*;
|
||||
/// # fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() }
|
||||
/// App::new().resource("/", |r| r.route().h(handler));
|
||||
/// ```
|
||||
pub fn h<H: Handler<S>>(&mut self, handler: H) {
|
||||
self.routes.push(Route::default());
|
||||
@@ -151,14 +173,25 @@ impl<S: 'static> ResourceHandler<S> {
|
||||
|
||||
/// Register a new route and add handler function.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::*;
|
||||
/// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
|
||||
///
|
||||
/// App::new().resource("/", |r| r.f(index));
|
||||
/// ```
|
||||
///
|
||||
/// This is shortcut for:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// Application::resource("/", |r| r.route().f(index)
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # use actix_web::*;
|
||||
/// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
|
||||
/// App::new().resource("/", |r| r.route().f(index));
|
||||
/// ```
|
||||
pub fn f<F, R>(&mut self, handler: F)
|
||||
where
|
||||
F: Fn(HttpRequest<S>) -> R + 'static,
|
||||
F: Fn(&HttpRequest<S>) -> R + 'static,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
self.routes.push(Route::default());
|
||||
@@ -167,14 +200,25 @@ impl<S: 'static> ResourceHandler<S> {
|
||||
|
||||
/// Register a new route and add handler.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::*;
|
||||
/// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
|
||||
///
|
||||
/// App::new().resource("/", |r| r.with(index));
|
||||
/// ```
|
||||
///
|
||||
/// This is shortcut for:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// Application::resource("/", |r| r.route().with(index)
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # use actix_web::*;
|
||||
/// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
|
||||
/// App::new().resource("/", |r| r.route().with(index));
|
||||
/// ```
|
||||
pub fn with<T, F, R>(&mut self, handler: F)
|
||||
where
|
||||
F: Fn(T) -> R + 'static,
|
||||
F: WithFactory<T, S, R>,
|
||||
R: Responder + 'static,
|
||||
T: FromRequest<S> + 'static,
|
||||
{
|
||||
@@ -184,10 +228,30 @@ impl<S: 'static> ResourceHandler<S> {
|
||||
|
||||
/// Register a new route and add async handler.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// use actix_web::*;
|
||||
/// use futures::future::Future;
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
/// unimplemented!()
|
||||
/// }
|
||||
///
|
||||
/// App::new().resource("/", |r| r.with_async(index));
|
||||
/// ```
|
||||
///
|
||||
/// This is shortcut for:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// Application::resource("/", |r| r.route().with_async(index)
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// # use actix_web::*;
|
||||
/// # use futures::future::Future;
|
||||
/// # fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// App::new().resource("/", |r| r.route().with_async(index));
|
||||
/// ```
|
||||
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
|
||||
where
|
||||
@@ -214,22 +278,47 @@ impl<S: 'static> ResourceHandler<S> {
|
||||
.push(Box::new(mw));
|
||||
}
|
||||
|
||||
pub(crate) fn handle(
|
||||
&mut self, mut req: HttpRequest<S>, default: Option<&mut ResourceHandler<S>>,
|
||||
) -> AsyncResult<HttpResponse> {
|
||||
for route in &mut self.routes {
|
||||
if route.check(&mut req) {
|
||||
return if self.middlewares.is_empty() {
|
||||
route.handle(req)
|
||||
} else {
|
||||
route.compose(req, Rc::clone(&self.middlewares))
|
||||
};
|
||||
#[inline]
|
||||
pub(crate) fn get_route_id(&self, req: &HttpRequest<S>) -> Option<RouteId> {
|
||||
for idx in 0..self.routes.len() {
|
||||
if (&self.routes[idx]).check(req) {
|
||||
return Some(RouteId(idx));
|
||||
}
|
||||
}
|
||||
if let Some(resource) = default {
|
||||
resource.handle(req, None)
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn handle(
|
||||
&self, id: RouteId, req: &HttpRequest<S>,
|
||||
) -> AsyncResult<HttpResponse> {
|
||||
if self.middlewares.is_empty() {
|
||||
(&self.routes[id.0]).handle(req)
|
||||
} else {
|
||||
AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND))
|
||||
(&self.routes[id.0]).compose(req.clone(), Rc::clone(&self.middlewares))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Default resource
|
||||
pub struct DefaultResource<S>(Rc<Resource<S>>);
|
||||
|
||||
impl<S> Deref for DefaultResource<S> {
|
||||
type Target = Resource<S>;
|
||||
|
||||
fn deref(&self) -> &Resource<S> {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Clone for DefaultResource<S> {
|
||||
fn clone(&self) -> Self {
|
||||
DefaultResource(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> From<Resource<S>> for DefaultResource<S> {
|
||||
fn from(res: Resource<S>) -> Self {
|
||||
DefaultResource(Rc::new(res))
|
||||
}
|
||||
}
|
||||
|
||||
225
src/route.rs
225
src/route.rs
@@ -1,4 +1,3 @@
|
||||
use std::cell::UnsafeCell;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -17,7 +16,7 @@ use middleware::{
|
||||
Started as MiddlewareStarted,
|
||||
};
|
||||
use pred::Predicate;
|
||||
use with::{ExtractorConfig, With, With2, With3, WithAsync};
|
||||
use with::{WithAsyncFactory, WithFactory};
|
||||
|
||||
/// Resource route definition
|
||||
///
|
||||
@@ -32,16 +31,17 @@ impl<S: 'static> Default for Route<S> {
|
||||
fn default() -> Route<S> {
|
||||
Route {
|
||||
preds: Vec::new(),
|
||||
handler: InnerHandler::new(|_| HttpResponse::new(StatusCode::NOT_FOUND)),
|
||||
handler: InnerHandler::new(|_: &_| HttpResponse::new(StatusCode::NOT_FOUND)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Route<S> {
|
||||
#[inline]
|
||||
pub(crate) fn check(&self, req: &mut HttpRequest<S>) -> bool {
|
||||
pub(crate) fn check(&self, req: &HttpRequest<S>) -> bool {
|
||||
let state = req.state();
|
||||
for pred in &self.preds {
|
||||
if !pred.check(req) {
|
||||
if !pred.check(req, state) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -49,15 +49,15 @@ impl<S: 'static> Route<S> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
pub(crate) fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
self.handler.handle(req)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn compose(
|
||||
&mut self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||
&self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||
) -> AsyncResult<HttpResponse> {
|
||||
AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone())))
|
||||
AsyncResult::future(Box::new(Compose::new(req, mws, self.handler.clone())))
|
||||
}
|
||||
|
||||
/// Add match predicate to route.
|
||||
@@ -90,7 +90,7 @@ impl<S: 'static> Route<S> {
|
||||
/// during route configuration, so it does not return reference to self.
|
||||
pub fn f<F, R>(&mut self, handler: F)
|
||||
where
|
||||
F: Fn(HttpRequest<S>) -> R + 'static,
|
||||
F: Fn(&HttpRequest<S>) -> R + 'static,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
self.handler = InnerHandler::new(handler);
|
||||
@@ -99,7 +99,7 @@ impl<S: 'static> Route<S> {
|
||||
/// Set async handler function.
|
||||
pub fn a<H, R, F, E>(&mut self, handler: H)
|
||||
where
|
||||
H: Fn(HttpRequest<S>) -> F + 'static,
|
||||
H: Fn(&HttpRequest<S>) -> F + 'static,
|
||||
F: Future<Item = R, Error = E> + 'static,
|
||||
R: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
@@ -134,8 +134,7 @@ impl<S: 'static> Route<S> {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// It is possible to use tuples for specifing multiple extractors for one
|
||||
/// handler function.
|
||||
/// It is possible to use multiple extractors for one handler function.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate bytes;
|
||||
@@ -152,9 +151,9 @@ impl<S: 'static> Route<S> {
|
||||
///
|
||||
/// /// extract path info using serde
|
||||
/// fn index(
|
||||
/// info: (Path<Info>, Query<HashMap<String, String>>, Json<Info>),
|
||||
/// path: Path<Info>, query: Query<HashMap<String, String>>, body: Json<Info>,
|
||||
/// ) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}!", info.0.username))
|
||||
/// Ok(format!("Welcome {}!", path.username))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
@@ -164,15 +163,49 @@ impl<S: 'static> Route<S> {
|
||||
/// ); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
pub fn with<T, F, R>(&mut self, handler: F) -> ExtractorConfig<S, T>
|
||||
pub fn with<T, F, R>(&mut self, handler: F)
|
||||
where
|
||||
F: Fn(T) -> R + 'static,
|
||||
F: WithFactory<T, S, R> + 'static,
|
||||
R: Responder + 'static,
|
||||
T: FromRequest<S> + 'static,
|
||||
{
|
||||
let cfg = ExtractorConfig::default();
|
||||
self.h(With::new(handler, Clone::clone(&cfg)));
|
||||
cfg
|
||||
self.h(handler.create());
|
||||
}
|
||||
|
||||
/// Set handler function. Same as `.with()` but it allows to configure
|
||||
/// extractor. Configuration closure accepts config objects as tuple.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{http, App, Path, Result};
|
||||
///
|
||||
/// /// extract text data from request
|
||||
/// fn index(body: String) -> Result<String> {
|
||||
/// Ok(format!("Body {}!", body))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource("/index.html", |r| {
|
||||
/// r.method(http::Method::GET)
|
||||
/// .with_config(index, |cfg| { // <- register handler
|
||||
/// cfg.0.limit(4096); // <- limit size of the payload
|
||||
/// })
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
pub fn with_config<T, F, R, C>(&mut self, handler: F, cfg_f: C)
|
||||
where
|
||||
F: WithFactory<T, S, R>,
|
||||
R: Responder + 'static,
|
||||
T: FromRequest<S> + 'static,
|
||||
C: FnOnce(&mut T::Config),
|
||||
{
|
||||
let mut cfg = <T::Config as Default>::default();
|
||||
cfg_f(&mut cfg);
|
||||
self.h(handler.create_with_config(cfg));
|
||||
}
|
||||
|
||||
/// Set async handler function, use request extractor for parameters.
|
||||
@@ -204,125 +237,88 @@ impl<S: 'static> Route<S> {
|
||||
/// ); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
pub fn with_async<T, F, R, I, E>(&mut self, handler: F) -> ExtractorConfig<S, T>
|
||||
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
|
||||
where
|
||||
F: Fn(T) -> R + 'static,
|
||||
F: WithAsyncFactory<T, S, R, I, E>,
|
||||
R: Future<Item = I, Error = E> + 'static,
|
||||
I: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
T: FromRequest<S> + 'static,
|
||||
{
|
||||
let cfg = ExtractorConfig::default();
|
||||
self.h(WithAsync::new(handler, Clone::clone(&cfg)));
|
||||
cfg
|
||||
self.h(handler.create());
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Set handler function, use request extractor for both parameters.
|
||||
/// Set async handler function, use request extractor for parameters.
|
||||
/// This method allows to configure extractor. Configuration closure
|
||||
/// accepts config objects as tuple.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{http, App, Path, Query, Result};
|
||||
/// use actix_web::{http, App, Error, Form};
|
||||
/// use futures::Future;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct PParam {
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct QParam {
|
||||
/// count: u32,
|
||||
/// }
|
||||
///
|
||||
/// /// extract path and query information using serde
|
||||
/// fn index(p: Path<PParam>, q: Query<QParam>) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}!", p.username))
|
||||
/// /// extract path info using serde
|
||||
/// fn index(info: Form<Info>) -> Box<Future<Item = &'static str, Error = Error>> {
|
||||
/// unimplemented!()
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/{username}/index.html", // <- define path parameters
|
||||
/// |r| r.method(http::Method::GET).with2(index),
|
||||
/// |r| r.method(http::Method::GET)
|
||||
/// .with_async_config(index, |cfg| {
|
||||
/// cfg.0.limit(4096);
|
||||
/// }),
|
||||
/// ); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
pub fn with2<T1, T2, F, R>(
|
||||
&mut self, handler: F,
|
||||
) -> (ExtractorConfig<S, T1>, ExtractorConfig<S, T2>)
|
||||
pub fn with_async_config<T, F, R, I, E, C>(&mut self, handler: F, cfg: C)
|
||||
where
|
||||
F: Fn(T1, T2) -> R + 'static,
|
||||
R: Responder + 'static,
|
||||
T1: FromRequest<S> + 'static,
|
||||
T2: FromRequest<S> + 'static,
|
||||
F: WithAsyncFactory<T, S, R, I, E>,
|
||||
R: Future<Item = I, Error = E> + 'static,
|
||||
I: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
T: FromRequest<S> + 'static,
|
||||
C: FnOnce(&mut T::Config),
|
||||
{
|
||||
let cfg1 = ExtractorConfig::default();
|
||||
let cfg2 = ExtractorConfig::default();
|
||||
self.h(With2::new(
|
||||
handler,
|
||||
Clone::clone(&cfg1),
|
||||
Clone::clone(&cfg2),
|
||||
));
|
||||
(cfg1, cfg2)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Set handler function, use request extractor for all parameters.
|
||||
pub fn with3<T1, T2, T3, F, R>(
|
||||
&mut self, handler: F,
|
||||
) -> (
|
||||
ExtractorConfig<S, T1>,
|
||||
ExtractorConfig<S, T2>,
|
||||
ExtractorConfig<S, T3>,
|
||||
)
|
||||
where
|
||||
F: Fn(T1, T2, T3) -> R + 'static,
|
||||
R: Responder + 'static,
|
||||
T1: FromRequest<S> + 'static,
|
||||
T2: FromRequest<S> + 'static,
|
||||
T3: FromRequest<S> + 'static,
|
||||
{
|
||||
let cfg1 = ExtractorConfig::default();
|
||||
let cfg2 = ExtractorConfig::default();
|
||||
let cfg3 = ExtractorConfig::default();
|
||||
self.h(With3::new(
|
||||
handler,
|
||||
Clone::clone(&cfg1),
|
||||
Clone::clone(&cfg2),
|
||||
Clone::clone(&cfg3),
|
||||
));
|
||||
(cfg1, cfg2, cfg3)
|
||||
let mut extractor_cfg = <T::Config as Default>::default();
|
||||
cfg(&mut extractor_cfg);
|
||||
self.h(handler.create_with_config(extractor_cfg));
|
||||
}
|
||||
}
|
||||
|
||||
/// `RouteHandler` wrapper. This struct is required because it needs to be
|
||||
/// shared for resource level middlewares.
|
||||
struct InnerHandler<S>(Rc<UnsafeCell<Box<RouteHandler<S>>>>);
|
||||
struct InnerHandler<S>(Rc<Box<RouteHandler<S>>>);
|
||||
|
||||
impl<S: 'static> InnerHandler<S> {
|
||||
#[inline]
|
||||
fn new<H: Handler<S>>(h: H) -> Self {
|
||||
InnerHandler(Rc::new(UnsafeCell::new(Box::new(WrapHandler::new(h)))))
|
||||
InnerHandler(Rc::new(Box::new(WrapHandler::new(h))))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn async<H, R, F, E>(h: H) -> Self
|
||||
where
|
||||
H: Fn(HttpRequest<S>) -> F + 'static,
|
||||
H: Fn(&HttpRequest<S>) -> F + 'static,
|
||||
F: Future<Item = R, Error = E> + 'static,
|
||||
R: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
{
|
||||
InnerHandler(Rc::new(UnsafeCell::new(Box::new(AsyncHandler::new(h)))))
|
||||
InnerHandler(Rc::new(Box::new(AsyncHandler::new(h))))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn handle(&self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
// reason: handler is unique per thread, handler get called from async code only
|
||||
let h = unsafe { &mut *self.0.as_ref().get() };
|
||||
h.handle(req)
|
||||
pub fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
self.0.handle(req)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,23 +408,27 @@ type Fut = Box<Future<Item = Option<HttpResponse>, Error = Error>>;
|
||||
impl<S: 'static> StartMiddlewares<S> {
|
||||
fn init(info: &mut ComposeInfo<S>) -> ComposeState<S> {
|
||||
let len = info.mws.len();
|
||||
|
||||
loop {
|
||||
if info.count == len {
|
||||
let reply = info.handler.handle(info.req.clone());
|
||||
let reply = info.handler.handle(&info.req);
|
||||
return WaitingResponse::init(info, reply);
|
||||
} else {
|
||||
match info.mws[info.count].start(&mut info.req) {
|
||||
let result = info.mws[info.count].start(&info.req);
|
||||
match result {
|
||||
Ok(MiddlewareStarted::Done) => info.count += 1,
|
||||
Ok(MiddlewareStarted::Response(resp)) => {
|
||||
return RunMiddlewares::init(info, resp)
|
||||
return RunMiddlewares::init(info, resp);
|
||||
}
|
||||
Ok(MiddlewareStarted::Future(fut)) => {
|
||||
return ComposeState::Starting(StartMiddlewares {
|
||||
fut: Some(fut),
|
||||
_s: PhantomData,
|
||||
})
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
return RunMiddlewares::init(info, err.into());
|
||||
}
|
||||
Err(err) => return RunMiddlewares::init(info, err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,9 +436,12 @@ impl<S: 'static> StartMiddlewares<S> {
|
||||
|
||||
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
|
||||
let len = info.mws.len();
|
||||
|
||||
'outer: loop {
|
||||
match self.fut.as_mut().unwrap().poll() {
|
||||
Ok(Async::NotReady) => return None,
|
||||
Ok(Async::NotReady) => {
|
||||
return None;
|
||||
}
|
||||
Ok(Async::Ready(resp)) => {
|
||||
info.count += 1;
|
||||
if let Some(resp) = resp {
|
||||
@@ -446,10 +449,11 @@ impl<S: 'static> StartMiddlewares<S> {
|
||||
}
|
||||
loop {
|
||||
if info.count == len {
|
||||
let reply = info.handler.handle(info.req.clone());
|
||||
let reply = info.handler.handle(&info.req);
|
||||
return Some(WaitingResponse::init(info, reply));
|
||||
} else {
|
||||
match info.mws[info.count].start(&mut info.req) {
|
||||
let result = info.mws[info.count].start(&info.req);
|
||||
match result {
|
||||
Ok(MiddlewareStarted::Done) => info.count += 1,
|
||||
Ok(MiddlewareStarted::Response(resp)) => {
|
||||
return Some(RunMiddlewares::init(info, resp));
|
||||
@@ -459,21 +463,25 @@ impl<S: 'static> StartMiddlewares<S> {
|
||||
continue 'outer;
|
||||
}
|
||||
Err(err) => {
|
||||
return Some(RunMiddlewares::init(info, err.into()))
|
||||
return Some(RunMiddlewares::init(info, err.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => return Some(RunMiddlewares::init(info, err.into())),
|
||||
Err(err) => {
|
||||
return Some(RunMiddlewares::init(info, err.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type HandlerFuture = Future<Item = HttpResponse, Error = Error>;
|
||||
|
||||
// waiting for response
|
||||
struct WaitingResponse<S> {
|
||||
fut: Box<Future<Item = HttpResponse, Error = Error>>,
|
||||
fut: Box<HandlerFuture>,
|
||||
_s: PhantomData<S>,
|
||||
}
|
||||
|
||||
@@ -483,8 +491,8 @@ impl<S: 'static> WaitingResponse<S> {
|
||||
info: &mut ComposeInfo<S>, reply: AsyncResult<HttpResponse>,
|
||||
) -> ComposeState<S> {
|
||||
match reply.into() {
|
||||
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
|
||||
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
|
||||
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
|
||||
AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse {
|
||||
fut,
|
||||
_s: PhantomData,
|
||||
@@ -495,7 +503,7 @@ impl<S: 'static> WaitingResponse<S> {
|
||||
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
|
||||
match self.fut.poll() {
|
||||
Ok(Async::NotReady) => None,
|
||||
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
|
||||
Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)),
|
||||
Err(err) => Some(RunMiddlewares::init(info, err.into())),
|
||||
}
|
||||
}
|
||||
@@ -514,7 +522,8 @@ impl<S: 'static> RunMiddlewares<S> {
|
||||
let len = info.mws.len();
|
||||
|
||||
loop {
|
||||
resp = match info.mws[curr].response(&mut info.req, resp) {
|
||||
let state = info.mws[curr].response(&info.req, resp);
|
||||
resp = match state {
|
||||
Err(err) => {
|
||||
info.count = curr + 1;
|
||||
return FinishingMiddlewares::init(info, err.into());
|
||||
@@ -532,7 +541,7 @@ impl<S: 'static> RunMiddlewares<S> {
|
||||
curr,
|
||||
fut: Some(fut),
|
||||
_s: PhantomData,
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -556,7 +565,8 @@ impl<S: 'static> RunMiddlewares<S> {
|
||||
if self.curr == len {
|
||||
return Some(FinishingMiddlewares::init(info, resp));
|
||||
} else {
|
||||
match info.mws[self.curr].response(&mut info.req, resp) {
|
||||
let state = info.mws[self.curr].response(&info.req, resp);
|
||||
match state {
|
||||
Err(err) => {
|
||||
return Some(FinishingMiddlewares::init(info, err.into()))
|
||||
}
|
||||
@@ -624,9 +634,10 @@ impl<S: 'static> FinishingMiddlewares<S> {
|
||||
}
|
||||
|
||||
info.count -= 1;
|
||||
match info.mws[info.count as usize]
|
||||
.finish(&mut info.req, self.resp.as_ref().unwrap())
|
||||
{
|
||||
|
||||
let state = info.mws[info.count as usize]
|
||||
.finish(&info.req, self.resp.as_ref().unwrap());
|
||||
match state {
|
||||
MiddlewareFinished::Done => {
|
||||
if info.count == 0 {
|
||||
return Some(Response::init(self.resp.take().unwrap()));
|
||||
|
||||
1343
src/router.rs
1343
src/router.rs
File diff suppressed because it is too large
Load Diff
595
src/scope.rs
595
src/scope.rs
File diff suppressed because it is too large
Load Diff
383
src/server/acceptor.rs
Normal file
383
src/server/acceptor.rs
Normal file
@@ -0,0 +1,383 @@
|
||||
use std::time::Duration;
|
||||
use std::{fmt, net};
|
||||
|
||||
use actix_net::server::ServerMessage;
|
||||
use actix_net::service::{NewService, Service};
|
||||
use futures::future::{err, ok, Either, FutureResult};
|
||||
use futures::{Async, Future, Poll};
|
||||
use tokio_reactor::Handle;
|
||||
use tokio_tcp::TcpStream;
|
||||
use tokio_timer::{sleep, Delay};
|
||||
|
||||
use super::error::AcceptorError;
|
||||
use super::IoStream;
|
||||
|
||||
/// This trait indicates types that can create acceptor service for http server.
|
||||
pub trait AcceptorServiceFactory: Send + Clone + 'static {
|
||||
type Io: IoStream + Send;
|
||||
type NewService: NewService<Request = TcpStream, Response = Self::Io>;
|
||||
|
||||
fn create(&self) -> Self::NewService;
|
||||
}
|
||||
|
||||
impl<F, T> AcceptorServiceFactory for F
|
||||
where
|
||||
F: Fn() -> T + Send + Clone + 'static,
|
||||
T::Response: IoStream + Send,
|
||||
T: NewService<Request = TcpStream>,
|
||||
T::InitError: fmt::Debug,
|
||||
{
|
||||
type Io = T::Response;
|
||||
type NewService = T;
|
||||
|
||||
fn create(&self) -> T {
|
||||
(self)()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Default acceptor service convert `TcpStream` to a `tokio_tcp::TcpStream`
|
||||
pub(crate) struct DefaultAcceptor;
|
||||
|
||||
impl AcceptorServiceFactory for DefaultAcceptor {
|
||||
type Io = TcpStream;
|
||||
type NewService = DefaultAcceptor;
|
||||
|
||||
fn create(&self) -> Self::NewService {
|
||||
DefaultAcceptor
|
||||
}
|
||||
}
|
||||
|
||||
impl NewService for DefaultAcceptor {
|
||||
type Request = TcpStream;
|
||||
type Response = TcpStream;
|
||||
type Error = ();
|
||||
type InitError = ();
|
||||
type Service = DefaultAcceptor;
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self) -> Self::Future {
|
||||
ok(DefaultAcceptor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for DefaultAcceptor {
|
||||
type Request = TcpStream;
|
||||
type Response = TcpStream;
|
||||
type Error = ();
|
||||
type Future = FutureResult<Self::Response, Self::Error>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||
ok(req)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TcpAcceptor<T> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T, E> TcpAcceptor<T>
|
||||
where
|
||||
T: NewService<Request = TcpStream, Error = AcceptorError<E>>,
|
||||
T::InitError: fmt::Debug,
|
||||
{
|
||||
pub(crate) fn new(inner: T) -> Self {
|
||||
TcpAcceptor { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> NewService for TcpAcceptor<T>
|
||||
where
|
||||
T: NewService<Request = TcpStream, Error = AcceptorError<E>>,
|
||||
T::InitError: fmt::Debug,
|
||||
{
|
||||
type Request = net::TcpStream;
|
||||
type Response = T::Response;
|
||||
type Error = AcceptorError<E>;
|
||||
type InitError = T::InitError;
|
||||
type Service = TcpAcceptorService<T::Service>;
|
||||
type Future = TcpAcceptorResponse<T>;
|
||||
|
||||
fn new_service(&self) -> Self::Future {
|
||||
TcpAcceptorResponse {
|
||||
fut: self.inner.new_service(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TcpAcceptorResponse<T>
|
||||
where
|
||||
T: NewService<Request = TcpStream>,
|
||||
T::InitError: fmt::Debug,
|
||||
{
|
||||
fut: T::Future,
|
||||
}
|
||||
|
||||
impl<T> Future for TcpAcceptorResponse<T>
|
||||
where
|
||||
T: NewService<Request = TcpStream>,
|
||||
T::InitError: fmt::Debug,
|
||||
{
|
||||
type Item = TcpAcceptorService<T::Service>;
|
||||
type Error = T::InitError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll() {
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Ok(Async::Ready(service)) => {
|
||||
Ok(Async::Ready(TcpAcceptorService { inner: service }))
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Can not create accetor service: {:?}", e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TcpAcceptorService<T> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T, E> Service for TcpAcceptorService<T>
|
||||
where
|
||||
T: Service<Request = TcpStream, Error = AcceptorError<E>>,
|
||||
{
|
||||
type Request = net::TcpStream;
|
||||
type Response = T::Response;
|
||||
type Error = AcceptorError<E>;
|
||||
type Future = Either<T::Future, FutureResult<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.inner.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||
let stream = TcpStream::from_std(req, &Handle::default()).map_err(|e| {
|
||||
error!("Can not convert to an async tcp stream: {}", e);
|
||||
AcceptorError::Io(e)
|
||||
});
|
||||
|
||||
match stream {
|
||||
Ok(stream) => Either::A(self.inner.call(stream)),
|
||||
Err(e) => Either::B(err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Acceptor timeout middleware
|
||||
///
|
||||
/// Applies timeout to request prcoessing.
|
||||
pub struct AcceptorTimeout<T> {
|
||||
inner: T,
|
||||
timeout: Duration,
|
||||
}
|
||||
|
||||
impl<T: NewService> AcceptorTimeout<T> {
|
||||
/// Create new `AcceptorTimeout` instance. timeout is in milliseconds.
|
||||
pub fn new(timeout: u64, inner: T) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
timeout: Duration::from_millis(timeout),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NewService> NewService for AcceptorTimeout<T> {
|
||||
type Request = T::Request;
|
||||
type Response = T::Response;
|
||||
type Error = AcceptorError<T::Error>;
|
||||
type InitError = T::InitError;
|
||||
type Service = AcceptorTimeoutService<T::Service>;
|
||||
type Future = AcceptorTimeoutFut<T>;
|
||||
|
||||
fn new_service(&self) -> Self::Future {
|
||||
AcceptorTimeoutFut {
|
||||
fut: self.inner.new_service(),
|
||||
timeout: self.timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct AcceptorTimeoutFut<T: NewService> {
|
||||
fut: T::Future,
|
||||
timeout: Duration,
|
||||
}
|
||||
|
||||
impl<T: NewService> Future for AcceptorTimeoutFut<T> {
|
||||
type Item = AcceptorTimeoutService<T::Service>;
|
||||
type Error = T::InitError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let inner = try_ready!(self.fut.poll());
|
||||
Ok(Async::Ready(AcceptorTimeoutService {
|
||||
inner,
|
||||
timeout: self.timeout,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Acceptor timeout service
|
||||
///
|
||||
/// Applies timeout to request prcoessing.
|
||||
pub struct AcceptorTimeoutService<T> {
|
||||
inner: T,
|
||||
timeout: Duration,
|
||||
}
|
||||
|
||||
impl<T: Service> Service for AcceptorTimeoutService<T> {
|
||||
type Request = T::Request;
|
||||
type Response = T::Response;
|
||||
type Error = AcceptorError<T::Error>;
|
||||
type Future = AcceptorTimeoutResponse<T>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.inner.poll_ready().map_err(AcceptorError::Service)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||
AcceptorTimeoutResponse {
|
||||
fut: self.inner.call(req),
|
||||
sleep: sleep(self.timeout),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct AcceptorTimeoutResponse<T: Service> {
|
||||
fut: T::Future,
|
||||
sleep: Delay,
|
||||
}
|
||||
|
||||
impl<T: Service> Future for AcceptorTimeoutResponse<T> {
|
||||
type Item = T::Response;
|
||||
type Error = AcceptorError<T::Error>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll().map_err(AcceptorError::Service)? {
|
||||
Async::NotReady => match self.sleep.poll() {
|
||||
Err(_) => Err(AcceptorError::Timeout),
|
||||
Ok(Async::Ready(_)) => Err(AcceptorError::Timeout),
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
},
|
||||
Async::Ready(resp) => Ok(Async::Ready(resp)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ServerMessageAcceptor<T> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T> ServerMessageAcceptor<T>
|
||||
where
|
||||
T: NewService<Request = net::TcpStream>,
|
||||
{
|
||||
pub(crate) fn new(inner: T) -> Self {
|
||||
ServerMessageAcceptor { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> NewService for ServerMessageAcceptor<T>
|
||||
where
|
||||
T: NewService<Request = net::TcpStream>,
|
||||
{
|
||||
type Request = ServerMessage;
|
||||
type Response = ();
|
||||
type Error = T::Error;
|
||||
type InitError = T::InitError;
|
||||
type Service = ServerMessageAcceptorService<T::Service>;
|
||||
type Future = ServerMessageAcceptorResponse<T>;
|
||||
|
||||
fn new_service(&self) -> Self::Future {
|
||||
ServerMessageAcceptorResponse {
|
||||
fut: self.inner.new_service(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ServerMessageAcceptorResponse<T>
|
||||
where
|
||||
T: NewService<Request = net::TcpStream>,
|
||||
{
|
||||
fut: T::Future,
|
||||
}
|
||||
|
||||
impl<T> Future for ServerMessageAcceptorResponse<T>
|
||||
where
|
||||
T: NewService<Request = net::TcpStream>,
|
||||
{
|
||||
type Item = ServerMessageAcceptorService<T::Service>;
|
||||
type Error = T::InitError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll()? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(service) => Ok(Async::Ready(ServerMessageAcceptorService {
|
||||
inner: service,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ServerMessageAcceptorService<T> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T> Service for ServerMessageAcceptorService<T>
|
||||
where
|
||||
T: Service<Request = net::TcpStream>,
|
||||
{
|
||||
type Request = ServerMessage;
|
||||
type Response = ();
|
||||
type Error = T::Error;
|
||||
type Future =
|
||||
Either<ServerMessageAcceptorServiceFut<T>, FutureResult<(), Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.inner.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||
match req {
|
||||
ServerMessage::Connect(stream) => {
|
||||
Either::A(ServerMessageAcceptorServiceFut {
|
||||
fut: self.inner.call(stream),
|
||||
})
|
||||
}
|
||||
ServerMessage::Shutdown(_) => Either::B(ok(())),
|
||||
ServerMessage::ForceShutdown => {
|
||||
// self.settings
|
||||
// .head()
|
||||
// .traverse(|proto: &mut HttpProtocol<TcpStream, H>| proto.shutdown());
|
||||
Either::B(ok(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ServerMessageAcceptorServiceFut<T: Service> {
|
||||
fut: T::Future,
|
||||
}
|
||||
|
||||
impl<T> Future for ServerMessageAcceptorServiceFut<T>
|
||||
where
|
||||
T: Service,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = T::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll()? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(_) => Ok(Async::Ready(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
134
src/server/builder.rs
Normal file
134
src/server/builder.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
use std::{fmt, net};
|
||||
|
||||
use actix_net::either::Either;
|
||||
use actix_net::server::{Server, ServiceFactory};
|
||||
use actix_net::service::{NewService, NewServiceExt};
|
||||
|
||||
use super::acceptor::{
|
||||
AcceptorServiceFactory, AcceptorTimeout, ServerMessageAcceptor, TcpAcceptor,
|
||||
};
|
||||
use super::error::AcceptorError;
|
||||
use super::handler::IntoHttpHandler;
|
||||
use super::service::{HttpService, StreamConfiguration};
|
||||
use super::settings::{ServerSettings, ServiceConfig};
|
||||
use super::KeepAlive;
|
||||
|
||||
pub(crate) trait ServiceProvider {
|
||||
fn register(
|
||||
&self,
|
||||
server: Server,
|
||||
lst: net::TcpListener,
|
||||
host: String,
|
||||
addr: net::SocketAddr,
|
||||
keep_alive: KeepAlive,
|
||||
secure: bool,
|
||||
client_timeout: u64,
|
||||
client_shutdown: u64,
|
||||
) -> Server;
|
||||
}
|
||||
|
||||
/// Utility type that builds complete http pipeline
|
||||
pub(crate) struct HttpServiceBuilder<F, H, A>
|
||||
where
|
||||
F: Fn() -> H + Send + Clone,
|
||||
{
|
||||
factory: F,
|
||||
acceptor: A,
|
||||
}
|
||||
|
||||
impl<F, H, A> HttpServiceBuilder<F, H, A>
|
||||
where
|
||||
F: Fn() -> H + Send + Clone + 'static,
|
||||
H: IntoHttpHandler,
|
||||
A: AcceptorServiceFactory,
|
||||
<A::NewService as NewService>::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create http service builder
|
||||
pub fn new(factory: F, acceptor: A) -> Self {
|
||||
Self { factory, acceptor }
|
||||
}
|
||||
|
||||
fn finish(
|
||||
&self,
|
||||
host: String,
|
||||
addr: net::SocketAddr,
|
||||
keep_alive: KeepAlive,
|
||||
secure: bool,
|
||||
client_timeout: u64,
|
||||
client_shutdown: u64,
|
||||
) -> impl ServiceFactory {
|
||||
let factory = self.factory.clone();
|
||||
let acceptor = self.acceptor.clone();
|
||||
move || {
|
||||
let app = (factory)().into_handler();
|
||||
let settings = ServiceConfig::new(
|
||||
app,
|
||||
keep_alive,
|
||||
client_timeout,
|
||||
client_shutdown,
|
||||
ServerSettings::new(addr, &host, false),
|
||||
);
|
||||
|
||||
if secure {
|
||||
Either::B(ServerMessageAcceptor::new(
|
||||
TcpAcceptor::new(AcceptorTimeout::new(
|
||||
client_timeout,
|
||||
acceptor.create(),
|
||||
)).map_err(|_| ())
|
||||
.map_init_err(|_| ())
|
||||
.and_then(StreamConfiguration::new().nodelay(true))
|
||||
.and_then(
|
||||
HttpService::new(settings)
|
||||
.map_init_err(|_| ())
|
||||
.map_err(|_| ()),
|
||||
),
|
||||
))
|
||||
} else {
|
||||
Either::A(ServerMessageAcceptor::new(
|
||||
TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service))
|
||||
.map_err(|_| ())
|
||||
.map_init_err(|_| ())
|
||||
.and_then(StreamConfiguration::new().nodelay(true))
|
||||
.and_then(
|
||||
HttpService::new(settings)
|
||||
.map_init_err(|_| ())
|
||||
.map_err(|_| ()),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, H, A> ServiceProvider for HttpServiceBuilder<F, H, A>
|
||||
where
|
||||
F: Fn() -> H + Send + Clone + 'static,
|
||||
A: AcceptorServiceFactory,
|
||||
<A::NewService as NewService>::InitError: fmt::Debug,
|
||||
H: IntoHttpHandler,
|
||||
{
|
||||
fn register(
|
||||
&self,
|
||||
server: Server,
|
||||
lst: net::TcpListener,
|
||||
host: String,
|
||||
addr: net::SocketAddr,
|
||||
keep_alive: KeepAlive,
|
||||
secure: bool,
|
||||
client_timeout: u64,
|
||||
client_shutdown: u64,
|
||||
) -> Server {
|
||||
server.listen2(
|
||||
"actix-web",
|
||||
lst,
|
||||
self.finish(
|
||||
host,
|
||||
addr,
|
||||
keep_alive,
|
||||
secure,
|
||||
client_timeout,
|
||||
client_shutdown,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,43 @@
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
use std::rc::Rc;
|
||||
use std::{io, ptr, time};
|
||||
use std::net::Shutdown;
|
||||
use std::{io, mem, time};
|
||||
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use bytes::{Buf, BufMut, BytesMut};
|
||||
use futures::{Async, Future, Poll};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio_timer::Delay;
|
||||
|
||||
use super::settings::WorkerSettings;
|
||||
use super::{h1, h2, utils, HttpHandler, IoStream};
|
||||
use super::error::HttpDispatchError;
|
||||
use super::settings::ServiceConfig;
|
||||
use super::{h1, h2, HttpHandler, IoStream};
|
||||
use http::StatusCode;
|
||||
|
||||
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
|
||||
|
||||
enum HttpProtocol<T: IoStream, H: 'static> {
|
||||
H1(h1::Http1<T, H>),
|
||||
pub(crate) enum HttpProtocol<T: IoStream, H: HttpHandler + 'static> {
|
||||
H1(h1::Http1Dispatcher<T, H>),
|
||||
H2(h2::Http2<T, H>),
|
||||
Unknown(Rc<WorkerSettings<H>>, Option<SocketAddr>, T, BytesMut),
|
||||
Unknown(ServiceConfig<H>, T, BytesMut),
|
||||
None,
|
||||
}
|
||||
|
||||
// impl<T: IoStream, H: HttpHandler + 'static> HttpProtocol<T, H> {
|
||||
// fn shutdown_(&mut self) {
|
||||
// match self {
|
||||
// HttpProtocol::H1(ref mut h1) => {
|
||||
// let io = h1.io();
|
||||
// let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0)));
|
||||
// let _ = IoStream::shutdown(io, Shutdown::Both);
|
||||
// }
|
||||
// HttpProtocol::H2(ref mut h2) => h2.shutdown(),
|
||||
// HttpProtocol::Unknown(_, io, _) => {
|
||||
// let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0)));
|
||||
// let _ = IoStream::shutdown(io, Shutdown::Both);
|
||||
// }
|
||||
// HttpProtocol::None => (),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
enum ProtocolKind {
|
||||
Http1,
|
||||
Http2,
|
||||
@@ -28,8 +49,8 @@ where
|
||||
T: IoStream,
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
proto: Option<HttpProtocol<T, H>>,
|
||||
node: Option<Node<HttpChannel<T, H>>>,
|
||||
proto: HttpProtocol<T, H>,
|
||||
ka_timeout: Option<Delay>,
|
||||
}
|
||||
|
||||
impl<T, H> HttpChannel<T, H>
|
||||
@@ -37,45 +58,12 @@ where
|
||||
T: IoStream,
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
settings: Rc<WorkerSettings<H>>, mut io: T, peer: Option<SocketAddr>,
|
||||
http2: bool,
|
||||
) -> HttpChannel<T, H> {
|
||||
settings.add_channel();
|
||||
let _ = io.set_nodelay(true);
|
||||
pub(crate) fn new(settings: ServiceConfig<H>, io: T) -> HttpChannel<T, H> {
|
||||
let ka_timeout = settings.client_timer();
|
||||
|
||||
if http2 {
|
||||
HttpChannel {
|
||||
node: None,
|
||||
proto: Some(HttpProtocol::H2(h2::Http2::new(
|
||||
settings,
|
||||
io,
|
||||
peer,
|
||||
Bytes::new(),
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
HttpChannel {
|
||||
node: None,
|
||||
proto: Some(HttpProtocol::Unknown(
|
||||
settings,
|
||||
peer,
|
||||
io,
|
||||
BytesMut::with_capacity(8192),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn shutdown(&mut self) {
|
||||
match self.proto {
|
||||
Some(HttpProtocol::H1(ref mut h1)) => {
|
||||
let io = h1.io();
|
||||
let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0)));
|
||||
let _ = IoStream::shutdown(io, Shutdown::Both);
|
||||
}
|
||||
Some(HttpProtocol::H2(ref mut h2)) => h2.shutdown(),
|
||||
_ => (),
|
||||
HttpChannel {
|
||||
ka_timeout,
|
||||
proto: HttpProtocol::Unknown(settings, io, BytesMut::with_capacity(8192)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,69 +74,57 @@ where
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
type Error = HttpDispatchError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if self.node.is_some() {
|
||||
let el = self as *mut _;
|
||||
self.node = Some(Node::new(el));
|
||||
let _ = match self.proto {
|
||||
Some(HttpProtocol::H1(ref mut h1)) => {
|
||||
self.node.as_ref().map(|n| h1.settings().head().insert(n))
|
||||
// keep-alive timer
|
||||
if self.ka_timeout.is_some() {
|
||||
match self.ka_timeout.as_mut().unwrap().poll() {
|
||||
Ok(Async::Ready(_)) => {
|
||||
trace!("Slow request timed out, close connection");
|
||||
let proto = mem::replace(&mut self.proto, HttpProtocol::None);
|
||||
if let HttpProtocol::Unknown(settings, io, buf) = proto {
|
||||
self.proto = HttpProtocol::H1(h1::Http1Dispatcher::for_error(
|
||||
settings,
|
||||
io,
|
||||
StatusCode::REQUEST_TIMEOUT,
|
||||
self.ka_timeout.take(),
|
||||
buf,
|
||||
));
|
||||
return self.poll();
|
||||
}
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
Some(HttpProtocol::H2(ref mut h2)) => {
|
||||
self.node.as_ref().map(|n| h2.settings().head().insert(n))
|
||||
}
|
||||
Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => {
|
||||
self.node.as_ref().map(|n| settings.head().insert(n))
|
||||
}
|
||||
None => unreachable!(),
|
||||
};
|
||||
Ok(Async::NotReady) => (),
|
||||
Err(_) => panic!("Something is really wrong"),
|
||||
}
|
||||
}
|
||||
|
||||
let mut is_eof = false;
|
||||
let kind = match self.proto {
|
||||
Some(HttpProtocol::H1(ref mut h1)) => {
|
||||
let result = h1.poll();
|
||||
match result {
|
||||
Ok(Async::Ready(())) | Err(_) => {
|
||||
h1.settings().remove_channel();
|
||||
if let Some(n) = self.node.as_mut() {
|
||||
n.remove()
|
||||
};
|
||||
HttpProtocol::H1(ref mut h1) => return h1.poll(),
|
||||
HttpProtocol::H2(ref mut h2) => return h2.poll(),
|
||||
HttpProtocol::Unknown(_, ref mut io, ref mut buf) => {
|
||||
let mut err = None;
|
||||
let mut disconnect = false;
|
||||
match io.read_available(buf) {
|
||||
Ok(Async::Ready((read_some, stream_closed))) => {
|
||||
is_eof = stream_closed;
|
||||
// Only disconnect if no data was read.
|
||||
if is_eof && !read_some {
|
||||
disconnect = true;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
err = Some(e.into());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Some(HttpProtocol::H2(ref mut h2)) => {
|
||||
let result = h2.poll();
|
||||
match result {
|
||||
Ok(Async::Ready(())) | Err(_) => {
|
||||
h2.settings().remove_channel();
|
||||
if let Some(n) = self.node.as_mut() {
|
||||
n.remove()
|
||||
};
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Some(HttpProtocol::Unknown(
|
||||
ref mut settings,
|
||||
_,
|
||||
ref mut io,
|
||||
ref mut buf,
|
||||
)) => {
|
||||
match utils::read_from_io(io, buf) {
|
||||
Ok(Async::Ready(0)) | Err(_) => {
|
||||
debug!("Ignored premature client disconnection");
|
||||
settings.remove_channel();
|
||||
if let Some(n) = self.node.as_mut() {
|
||||
n.remove()
|
||||
};
|
||||
return Err(());
|
||||
}
|
||||
_ => (),
|
||||
if disconnect {
|
||||
debug!("Ignored premature client disconnection");
|
||||
return Ok(Async::Ready(()));
|
||||
} else if let Some(e) = err {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
if buf.len() >= 14 {
|
||||
@@ -161,24 +137,30 @@ where
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
}
|
||||
None => unreachable!(),
|
||||
HttpProtocol::None => unreachable!(),
|
||||
};
|
||||
|
||||
// upgrade to specific http protocol
|
||||
if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() {
|
||||
let proto = mem::replace(&mut self.proto, HttpProtocol::None);
|
||||
if let HttpProtocol::Unknown(settings, io, buf) = proto {
|
||||
match kind {
|
||||
ProtocolKind::Http1 => {
|
||||
self.proto =
|
||||
Some(HttpProtocol::H1(h1::Http1::new(settings, io, addr, buf)));
|
||||
self.proto = HttpProtocol::H1(h1::Http1Dispatcher::new(
|
||||
settings,
|
||||
io,
|
||||
buf,
|
||||
is_eof,
|
||||
self.ka_timeout.take(),
|
||||
));
|
||||
return self.poll();
|
||||
}
|
||||
ProtocolKind::Http2 => {
|
||||
self.proto = Some(HttpProtocol::H2(h2::Http2::new(
|
||||
self.proto = HttpProtocol::H2(h2::Http2::new(
|
||||
settings,
|
||||
io,
|
||||
addr,
|
||||
buf.freeze(),
|
||||
)));
|
||||
self.ka_timeout.take(),
|
||||
));
|
||||
return self.poll();
|
||||
}
|
||||
}
|
||||
@@ -187,81 +169,45 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Node<T> {
|
||||
next: Option<*mut Node<()>>,
|
||||
prev: Option<*mut Node<()>>,
|
||||
element: *mut T,
|
||||
#[doc(hidden)]
|
||||
pub struct H1Channel<T, H>
|
||||
where
|
||||
T: IoStream,
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
proto: HttpProtocol<T, H>,
|
||||
}
|
||||
|
||||
impl<T> Node<T> {
|
||||
fn new(el: *mut T) -> Self {
|
||||
Node {
|
||||
next: None,
|
||||
prev: None,
|
||||
element: el,
|
||||
}
|
||||
}
|
||||
|
||||
fn insert<I>(&self, next: &Node<I>) {
|
||||
#[allow(mutable_transmutes)]
|
||||
unsafe {
|
||||
if let Some(ref next2) = self.next {
|
||||
let n: &mut Node<()> =
|
||||
&mut *(next2.as_ref().unwrap() as *const _ as *mut _);
|
||||
n.prev = Some(next as *const _ as *mut _);
|
||||
}
|
||||
let slf: &mut Node<T> = &mut *(self as *const _ as *mut _);
|
||||
|
||||
slf.next = Some(next as *const _ as *mut _);
|
||||
|
||||
let next: &mut Node<T> = &mut *(next as *const _ as *mut _);
|
||||
next.prev = Some(slf as *const _ as *mut _);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&mut self) {
|
||||
unsafe {
|
||||
self.element = ptr::null_mut();
|
||||
let next = self.next.take();
|
||||
let mut prev = self.prev.take();
|
||||
|
||||
if let Some(ref mut prev) = prev {
|
||||
prev.as_mut().unwrap().next = next;
|
||||
}
|
||||
impl<T, H> H1Channel<T, H>
|
||||
where
|
||||
T: IoStream,
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
pub(crate) fn new(settings: ServiceConfig<H>, io: T) -> H1Channel<T, H> {
|
||||
H1Channel {
|
||||
proto: HttpProtocol::H1(h1::Http1Dispatcher::new(
|
||||
settings,
|
||||
io,
|
||||
BytesMut::with_capacity(8192),
|
||||
false,
|
||||
None,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<()> {
|
||||
pub(crate) fn head() -> Self {
|
||||
Node {
|
||||
next: None,
|
||||
prev: None,
|
||||
element: ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
impl<T, H> Future for H1Channel<T, H>
|
||||
where
|
||||
T: IoStream,
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = HttpDispatchError;
|
||||
|
||||
pub(crate) fn traverse<T, H>(&self)
|
||||
where
|
||||
T: IoStream,
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
let mut next = self.next.as_ref();
|
||||
loop {
|
||||
if let Some(n) = next {
|
||||
unsafe {
|
||||
let n: &Node<()> = &*(n.as_ref().unwrap() as *const _);
|
||||
next = n.next.as_ref();
|
||||
|
||||
if !n.element.is_null() {
|
||||
let ch: &mut HttpChannel<T, H> =
|
||||
&mut *(&mut *(n.element as *mut _) as *mut () as *mut _);
|
||||
ch.shutdown();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.proto {
|
||||
HttpProtocol::H1(ref mut h1) => h1.poll(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,6 +245,10 @@ where
|
||||
fn set_linger(&mut self, _: Option<time::Duration>) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
#[inline]
|
||||
fn set_keepalive(&mut self, _: Option<time::Duration>) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> io::Read for WrapperStream<T>
|
||||
|
||||
@@ -1,916 +0,0 @@
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::io::{Read, Write};
|
||||
use std::str::FromStr;
|
||||
use std::{cmp, io, mem};
|
||||
|
||||
#[cfg(feature = "brotli")]
|
||||
use brotli2::write::{BrotliDecoder, BrotliEncoder};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::read::GzDecoder;
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::Compression;
|
||||
use http::header::{
|
||||
HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
|
||||
TRANSFER_ENCODING,
|
||||
};
|
||||
use http::{HttpTryFrom, Method, Version};
|
||||
|
||||
use body::{Binary, Body};
|
||||
use error::PayloadError;
|
||||
use header::ContentEncoding;
|
||||
use httprequest::HttpInnerMessage;
|
||||
use httpresponse::HttpResponse;
|
||||
use payload::{PayloadSender, PayloadStatus, PayloadWriter};
|
||||
|
||||
use super::shared::SharedBytes;
|
||||
|
||||
pub(crate) enum PayloadType {
|
||||
Sender(PayloadSender),
|
||||
Encoding(Box<EncodedPayload>),
|
||||
}
|
||||
|
||||
impl PayloadType {
|
||||
#[cfg(any(feature = "brotli", feature = "flate2"))]
|
||||
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
|
||||
// check content-encoding
|
||||
let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) {
|
||||
if let Ok(enc) = enc.to_str() {
|
||||
ContentEncoding::from(enc)
|
||||
} else {
|
||||
ContentEncoding::Auto
|
||||
}
|
||||
} else {
|
||||
ContentEncoding::Auto
|
||||
};
|
||||
|
||||
match enc {
|
||||
ContentEncoding::Auto | ContentEncoding::Identity => {
|
||||
PayloadType::Sender(sender)
|
||||
}
|
||||
_ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
|
||||
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
|
||||
PayloadType::Sender(sender)
|
||||
}
|
||||
}
|
||||
|
||||
impl PayloadWriter for PayloadType {
|
||||
#[inline]
|
||||
fn set_error(&mut self, err: PayloadError) {
|
||||
match *self {
|
||||
PayloadType::Sender(ref mut sender) => sender.set_error(err),
|
||||
PayloadType::Encoding(ref mut enc) => enc.set_error(err),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn feed_eof(&mut self) {
|
||||
match *self {
|
||||
PayloadType::Sender(ref mut sender) => sender.feed_eof(),
|
||||
PayloadType::Encoding(ref mut enc) => enc.feed_eof(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn feed_data(&mut self, data: Bytes) {
|
||||
match *self {
|
||||
PayloadType::Sender(ref mut sender) => sender.feed_data(data),
|
||||
PayloadType::Encoding(ref mut enc) => enc.feed_data(data),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn need_read(&self) -> PayloadStatus {
|
||||
match *self {
|
||||
PayloadType::Sender(ref sender) => sender.need_read(),
|
||||
PayloadType::Encoding(ref enc) => enc.need_read(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload wrapper with content decompression support
|
||||
pub(crate) struct EncodedPayload {
|
||||
inner: PayloadSender,
|
||||
error: bool,
|
||||
payload: PayloadStream,
|
||||
}
|
||||
|
||||
impl EncodedPayload {
|
||||
pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload {
|
||||
EncodedPayload {
|
||||
inner,
|
||||
error: false,
|
||||
payload: PayloadStream::new(enc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PayloadWriter for EncodedPayload {
|
||||
fn set_error(&mut self, err: PayloadError) {
|
||||
self.inner.set_error(err)
|
||||
}
|
||||
|
||||
fn feed_eof(&mut self) {
|
||||
if !self.error {
|
||||
match self.payload.feed_eof() {
|
||||
Err(err) => {
|
||||
self.error = true;
|
||||
self.set_error(PayloadError::Io(err));
|
||||
}
|
||||
Ok(value) => {
|
||||
if let Some(b) = value {
|
||||
self.inner.feed_data(b);
|
||||
}
|
||||
self.inner.feed_eof();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn feed_data(&mut self, data: Bytes) {
|
||||
if self.error {
|
||||
return;
|
||||
}
|
||||
|
||||
match self.payload.feed_data(data) {
|
||||
Ok(Some(b)) => self.inner.feed_data(b),
|
||||
Ok(None) => (),
|
||||
Err(e) => {
|
||||
self.error = true;
|
||||
self.set_error(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn need_read(&self) -> PayloadStatus {
|
||||
self.inner.need_read()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Decoder {
|
||||
#[cfg(feature = "flate2")]
|
||||
Deflate(Box<DeflateDecoder<Writer>>),
|
||||
#[cfg(feature = "flate2")]
|
||||
Gzip(Option<Box<GzDecoder<Wrapper>>>),
|
||||
#[cfg(feature = "brotli")]
|
||||
Br(Box<BrotliDecoder<Writer>>),
|
||||
Identity,
|
||||
}
|
||||
|
||||
// should go after write::GzDecoder get implemented
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Wrapper {
|
||||
pub buf: BytesMut,
|
||||
pub eof: bool,
|
||||
}
|
||||
|
||||
impl io::Read for Wrapper {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let len = cmp::min(buf.len(), self.buf.len());
|
||||
buf[..len].copy_from_slice(&self.buf[..len]);
|
||||
self.buf.split_to(len);
|
||||
if len == 0 {
|
||||
if self.eof {
|
||||
Ok(0)
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
|
||||
}
|
||||
} else {
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for Wrapper {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.buf.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Writer {
|
||||
buf: BytesMut,
|
||||
}
|
||||
|
||||
impl Writer {
|
||||
fn new() -> Writer {
|
||||
Writer {
|
||||
buf: BytesMut::with_capacity(8192),
|
||||
}
|
||||
}
|
||||
fn take(&mut self) -> Bytes {
|
||||
self.buf.take().freeze()
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for Writer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.buf.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload stream with decompression support
|
||||
pub(crate) struct PayloadStream {
|
||||
decoder: Decoder,
|
||||
dst: BytesMut,
|
||||
}
|
||||
|
||||
impl PayloadStream {
|
||||
pub fn new(enc: ContentEncoding) -> PayloadStream {
|
||||
let dec = match enc {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => {
|
||||
Decoder::Br(Box::new(BrotliDecoder::new(Writer::new())))
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => {
|
||||
Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new())))
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => Decoder::Gzip(None),
|
||||
_ => Decoder::Identity,
|
||||
};
|
||||
PayloadStream {
|
||||
decoder: dec,
|
||||
dst: BytesMut::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PayloadStream {
|
||||
pub fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
|
||||
match self.decoder {
|
||||
#[cfg(feature = "brotli")]
|
||||
Decoder::Br(ref mut decoder) => match decoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
let b = writer.take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Gzip(ref mut decoder) => {
|
||||
if let Some(ref mut decoder) = *decoder {
|
||||
decoder.as_mut().get_mut().eof = true;
|
||||
|
||||
self.dst.reserve(8192);
|
||||
match decoder.read(unsafe { self.dst.bytes_mut() }) {
|
||||
Ok(n) => {
|
||||
unsafe { self.dst.advance_mut(n) };
|
||||
return Ok(Some(self.dst.take().freeze()));
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Deflate(ref mut decoder) => match decoder.try_finish() {
|
||||
Ok(_) => {
|
||||
let b = decoder.get_mut().take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Decoder::Identity => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
|
||||
match self.decoder {
|
||||
#[cfg(feature = "brotli")]
|
||||
Decoder::Br(ref mut decoder) => match decoder.write_all(&data) {
|
||||
Ok(_) => {
|
||||
decoder.flush()?;
|
||||
let b = decoder.get_mut().take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Gzip(ref mut decoder) => {
|
||||
if decoder.is_none() {
|
||||
*decoder = Some(Box::new(GzDecoder::new(Wrapper {
|
||||
buf: BytesMut::from(data),
|
||||
eof: false,
|
||||
})));
|
||||
} else {
|
||||
let _ = decoder.as_mut().unwrap().write(&data);
|
||||
}
|
||||
|
||||
loop {
|
||||
self.dst.reserve(8192);
|
||||
match decoder
|
||||
.as_mut()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.read(unsafe { self.dst.bytes_mut() })
|
||||
{
|
||||
Ok(n) => {
|
||||
if n != 0 {
|
||||
unsafe { self.dst.advance_mut(n) };
|
||||
}
|
||||
if n == 0 {
|
||||
return Ok(Some(self.dst.take().freeze()));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::WouldBlock
|
||||
&& !self.dst.is_empty()
|
||||
{
|
||||
return Ok(Some(self.dst.take().freeze()));
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
|
||||
Ok(_) => {
|
||||
decoder.flush()?;
|
||||
let b = decoder.get_mut().take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Decoder::Identity => Ok(Some(data)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum ContentEncoder {
|
||||
#[cfg(feature = "flate2")]
|
||||
Deflate(DeflateEncoder<TransferEncoding>),
|
||||
#[cfg(feature = "flate2")]
|
||||
Gzip(GzEncoder<TransferEncoding>),
|
||||
#[cfg(feature = "brotli")]
|
||||
Br(BrotliEncoder<TransferEncoding>),
|
||||
Identity(TransferEncoding),
|
||||
}
|
||||
|
||||
impl ContentEncoder {
|
||||
pub fn empty(bytes: SharedBytes) -> ContentEncoder {
|
||||
ContentEncoder::Identity(TransferEncoding::eof(bytes))
|
||||
}
|
||||
|
||||
pub fn for_server(
|
||||
buf: SharedBytes, req: &HttpInnerMessage, resp: &mut HttpResponse,
|
||||
response_encoding: ContentEncoding,
|
||||
) -> ContentEncoder {
|
||||
let version = resp.version().unwrap_or_else(|| req.version);
|
||||
let is_head = req.method == Method::HEAD;
|
||||
let mut len = 0;
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))]
|
||||
let has_body = match resp.body() {
|
||||
&Body::Empty => false,
|
||||
&Body::Binary(ref bin) => {
|
||||
len = bin.len();
|
||||
!(response_encoding == ContentEncoding::Auto && len < 96)
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// Enable content encoding only if response does not contain Content-Encoding
|
||||
// header
|
||||
#[cfg(any(feature = "brotli", feature = "flate2"))]
|
||||
let mut encoding = if has_body {
|
||||
let encoding = match response_encoding {
|
||||
ContentEncoding::Auto => {
|
||||
// negotiate content-encoding
|
||||
if let Some(val) = req.headers.get(ACCEPT_ENCODING) {
|
||||
if let Ok(enc) = val.to_str() {
|
||||
AcceptEncoding::parse(enc)
|
||||
} else {
|
||||
ContentEncoding::Identity
|
||||
}
|
||||
} else {
|
||||
ContentEncoding::Identity
|
||||
}
|
||||
}
|
||||
encoding => encoding,
|
||||
};
|
||||
if encoding.is_compression() {
|
||||
resp.headers_mut().insert(
|
||||
CONTENT_ENCODING,
|
||||
HeaderValue::from_static(encoding.as_str()),
|
||||
);
|
||||
}
|
||||
encoding
|
||||
} else {
|
||||
ContentEncoding::Identity
|
||||
};
|
||||
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
|
||||
let mut encoding = ContentEncoding::Identity;
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))]
|
||||
let mut transfer = match resp.body() {
|
||||
&Body::Empty => {
|
||||
if req.method != Method::HEAD {
|
||||
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||
}
|
||||
TransferEncoding::length(0, buf)
|
||||
}
|
||||
&Body::Binary(_) => {
|
||||
#[cfg(any(feature = "brotli", feature = "flate2"))]
|
||||
{
|
||||
if !(encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto)
|
||||
{
|
||||
let tmp = SharedBytes::default();
|
||||
let transfer = TransferEncoding::eof(tmp.clone());
|
||||
let mut enc = match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
||||
DeflateEncoder::new(transfer, Compression::fast()),
|
||||
),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => ContentEncoder::Gzip(
|
||||
GzEncoder::new(transfer, Compression::fast()),
|
||||
),
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => {
|
||||
ContentEncoder::Br(BrotliEncoder::new(transfer, 3))
|
||||
}
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let bin = resp.replace_body(Body::Empty).binary();
|
||||
|
||||
// TODO return error!
|
||||
let _ = enc.write(bin);
|
||||
let _ = enc.write_eof();
|
||||
let body = tmp.take();
|
||||
len = body.len();
|
||||
|
||||
encoding = ContentEncoding::Identity;
|
||||
resp.replace_body(Binary::from(body));
|
||||
}
|
||||
}
|
||||
|
||||
if is_head {
|
||||
let mut b = BytesMut::new();
|
||||
let _ = write!(b, "{}", len);
|
||||
resp.headers_mut().insert(
|
||||
CONTENT_LENGTH,
|
||||
HeaderValue::try_from(b.freeze()).unwrap(),
|
||||
);
|
||||
} else {
|
||||
// resp.headers_mut().remove(CONTENT_LENGTH);
|
||||
}
|
||||
TransferEncoding::eof(buf)
|
||||
}
|
||||
&Body::Streaming(_) | &Body::Actor(_) => {
|
||||
if resp.upgrade() {
|
||||
if version == Version::HTTP_2 {
|
||||
error!("Connection upgrade is forbidden for HTTP/2");
|
||||
}
|
||||
if encoding != ContentEncoding::Identity {
|
||||
encoding = ContentEncoding::Identity;
|
||||
resp.headers_mut().remove(CONTENT_ENCODING);
|
||||
}
|
||||
TransferEncoding::eof(buf)
|
||||
} else {
|
||||
if !(encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto)
|
||||
{
|
||||
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||
}
|
||||
ContentEncoder::streaming_encoding(buf, version, resp)
|
||||
}
|
||||
}
|
||||
};
|
||||
// check for head response
|
||||
if is_head {
|
||||
resp.set_body(Body::Empty);
|
||||
transfer.kind = TransferEncodingKind::Length(0);
|
||||
}
|
||||
|
||||
match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
|
||||
transfer,
|
||||
Compression::fast(),
|
||||
)),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => {
|
||||
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast()))
|
||||
}
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 3)),
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => {
|
||||
ContentEncoder::Identity(transfer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn streaming_encoding(
|
||||
buf: SharedBytes, version: Version, resp: &mut HttpResponse,
|
||||
) -> TransferEncoding {
|
||||
match resp.chunked() {
|
||||
Some(true) => {
|
||||
// Enable transfer encoding
|
||||
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||
if version == Version::HTTP_2 {
|
||||
resp.headers_mut().remove(TRANSFER_ENCODING);
|
||||
TransferEncoding::eof(buf)
|
||||
} else {
|
||||
resp.headers_mut()
|
||||
.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
|
||||
TransferEncoding::chunked(buf)
|
||||
}
|
||||
}
|
||||
Some(false) => TransferEncoding::eof(buf),
|
||||
None => {
|
||||
// if Content-Length is specified, then use it as length hint
|
||||
let (len, chunked) =
|
||||
if let Some(len) = resp.headers().get(CONTENT_LENGTH) {
|
||||
// Content-Length
|
||||
if let Ok(s) = len.to_str() {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
(Some(len), false)
|
||||
} else {
|
||||
error!("illegal Content-Length: {:?}", len);
|
||||
(None, false)
|
||||
}
|
||||
} else {
|
||||
error!("illegal Content-Length: {:?}", len);
|
||||
(None, false)
|
||||
}
|
||||
} else {
|
||||
(None, true)
|
||||
};
|
||||
|
||||
if !chunked {
|
||||
if let Some(len) = len {
|
||||
TransferEncoding::length(len, buf)
|
||||
} else {
|
||||
TransferEncoding::eof(buf)
|
||||
}
|
||||
} else {
|
||||
// Enable transfer encoding
|
||||
match version {
|
||||
Version::HTTP_11 => {
|
||||
resp.headers_mut().insert(
|
||||
TRANSFER_ENCODING,
|
||||
HeaderValue::from_static("chunked"),
|
||||
);
|
||||
TransferEncoding::chunked(buf)
|
||||
}
|
||||
_ => {
|
||||
resp.headers_mut().remove(TRANSFER_ENCODING);
|
||||
TransferEncoding::eof(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContentEncoder {
|
||||
#[inline]
|
||||
pub fn is_eof(&self) -> bool {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref encoder) => encoder.get_ref().is_eof(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_eof(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_eof(),
|
||||
ContentEncoder::Identity(ref encoder) => encoder.is_eof(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||
#[inline(always)]
|
||||
pub fn write_eof(&mut self) -> Result<bool, io::Error> {
|
||||
let encoder = mem::replace(
|
||||
self,
|
||||
ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty())),
|
||||
);
|
||||
|
||||
match encoder {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(encoder) => match encoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(true)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(encoder) => match encoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(true)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(encoder) => match encoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(true)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
ContentEncoder::Identity(mut writer) => {
|
||||
let res = writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||
#[inline(always)]
|
||||
pub fn write(&mut self, data: Binary) -> Result<(), io::Error> {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => {
|
||||
match encoder.write_all(data.as_ref()) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding br encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => {
|
||||
match encoder.write_all(data.as_ref()) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding gzip encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => {
|
||||
match encoder.write_all(data.as_ref()) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding deflate encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
ContentEncoder::Identity(ref mut encoder) => {
|
||||
encoder.encode(data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encoders to handle different Transfer-Encodings.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct TransferEncoding {
|
||||
kind: TransferEncodingKind,
|
||||
buffer: SharedBytes,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum TransferEncodingKind {
|
||||
/// An Encoder for when Transfer-Encoding includes `chunked`.
|
||||
Chunked(bool),
|
||||
/// An Encoder for when Content-Length is set.
|
||||
///
|
||||
/// Enforces that the body is not longer than the Content-Length header.
|
||||
Length(u64),
|
||||
/// An Encoder for when Content-Length is not known.
|
||||
///
|
||||
/// Application decides when to stop writing.
|
||||
Eof,
|
||||
}
|
||||
|
||||
impl TransferEncoding {
|
||||
#[inline]
|
||||
pub fn eof(bytes: SharedBytes) -> TransferEncoding {
|
||||
TransferEncoding {
|
||||
kind: TransferEncodingKind::Eof,
|
||||
buffer: bytes,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn chunked(bytes: SharedBytes) -> TransferEncoding {
|
||||
TransferEncoding {
|
||||
kind: TransferEncodingKind::Chunked(false),
|
||||
buffer: bytes,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn length(len: u64, bytes: SharedBytes) -> TransferEncoding {
|
||||
TransferEncoding {
|
||||
kind: TransferEncodingKind::Length(len),
|
||||
buffer: bytes,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_eof(&self) -> bool {
|
||||
match self.kind {
|
||||
TransferEncodingKind::Eof => true,
|
||||
TransferEncodingKind::Chunked(ref eof) => *eof,
|
||||
TransferEncodingKind::Length(ref remaining) => *remaining == 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode message. Return `EOF` state of encoder
|
||||
#[inline]
|
||||
pub fn encode(&mut self, mut msg: Binary) -> io::Result<bool> {
|
||||
match self.kind {
|
||||
TransferEncodingKind::Eof => {
|
||||
let eof = msg.is_empty();
|
||||
self.buffer.extend(msg);
|
||||
Ok(eof)
|
||||
}
|
||||
TransferEncodingKind::Chunked(ref mut eof) => {
|
||||
if *eof {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if msg.is_empty() {
|
||||
*eof = true;
|
||||
self.buffer.extend_from_slice(b"0\r\n\r\n");
|
||||
} else {
|
||||
let mut buf = BytesMut::new();
|
||||
writeln!(&mut buf, "{:X}\r", msg.len())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
self.buffer.reserve(buf.len() + msg.len() + 2);
|
||||
self.buffer.extend(buf.into());
|
||||
self.buffer.extend(msg);
|
||||
self.buffer.extend_from_slice(b"\r\n");
|
||||
}
|
||||
Ok(*eof)
|
||||
}
|
||||
TransferEncodingKind::Length(ref mut remaining) => {
|
||||
if *remaining > 0 {
|
||||
if msg.is_empty() {
|
||||
return Ok(*remaining == 0);
|
||||
}
|
||||
let len = cmp::min(*remaining, msg.len() as u64);
|
||||
self.buffer.extend(msg.take().split_to(len as usize).into());
|
||||
|
||||
*remaining -= len as u64;
|
||||
Ok(*remaining == 0)
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode eof. Return `EOF` state of encoder
|
||||
#[inline]
|
||||
pub fn encode_eof(&mut self) -> bool {
|
||||
match self.kind {
|
||||
TransferEncodingKind::Eof => true,
|
||||
TransferEncodingKind::Length(rem) => rem == 0,
|
||||
TransferEncodingKind::Chunked(ref mut eof) => {
|
||||
if !*eof {
|
||||
*eof = true;
|
||||
self.buffer.extend_from_slice(b"0\r\n\r\n");
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for TransferEncoding {
|
||||
#[inline]
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.encode(Binary::from_slice(buf))?;
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct AcceptEncoding {
|
||||
encoding: ContentEncoding,
|
||||
quality: f64,
|
||||
}
|
||||
|
||||
impl Eq for AcceptEncoding {}
|
||||
|
||||
impl Ord for AcceptEncoding {
|
||||
fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering {
|
||||
if self.quality > other.quality {
|
||||
cmp::Ordering::Less
|
||||
} else if self.quality < other.quality {
|
||||
cmp::Ordering::Greater
|
||||
} else {
|
||||
cmp::Ordering::Equal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for AcceptEncoding {
|
||||
fn partial_cmp(&self, other: &AcceptEncoding) -> Option<cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AcceptEncoding {
|
||||
fn eq(&self, other: &AcceptEncoding) -> bool {
|
||||
self.quality == other.quality
|
||||
}
|
||||
}
|
||||
|
||||
impl AcceptEncoding {
|
||||
fn new(tag: &str) -> Option<AcceptEncoding> {
|
||||
let parts: Vec<&str> = tag.split(';').collect();
|
||||
let encoding = match parts.len() {
|
||||
0 => return None,
|
||||
_ => ContentEncoding::from(parts[0]),
|
||||
};
|
||||
let quality = match parts.len() {
|
||||
1 => encoding.quality(),
|
||||
_ => match f64::from_str(parts[1]) {
|
||||
Ok(q) => q,
|
||||
Err(_) => 0.0,
|
||||
},
|
||||
};
|
||||
Some(AcceptEncoding { encoding, quality })
|
||||
}
|
||||
|
||||
/// Parse a raw Accept-Encoding header value into an ordered list.
|
||||
pub fn parse(raw: &str) -> ContentEncoding {
|
||||
let mut encodings: Vec<_> = raw
|
||||
.replace(' ', "")
|
||||
.split(',')
|
||||
.map(|l| AcceptEncoding::new(l))
|
||||
.collect();
|
||||
encodings.sort();
|
||||
|
||||
for enc in encodings {
|
||||
if let Some(enc) = enc {
|
||||
return enc.encoding;
|
||||
}
|
||||
}
|
||||
ContentEncoding::Identity
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_chunked_te() {
|
||||
let bytes = SharedBytes::default();
|
||||
let mut enc = TransferEncoding::chunked(bytes.clone());
|
||||
assert!(!enc.encode(Binary::from(b"test".as_ref())).ok().unwrap());
|
||||
assert!(enc.encode(Binary::from(b"".as_ref())).ok().unwrap());
|
||||
assert_eq!(
|
||||
bytes.get_mut().take().freeze(),
|
||||
Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
108
src/server/error.rs
Normal file
108
src/server/error.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use std::io;
|
||||
|
||||
use futures::{Async, Poll};
|
||||
use http2;
|
||||
|
||||
use super::{helpers, HttpHandlerTask, Writer};
|
||||
use http::{StatusCode, Version};
|
||||
use Error;
|
||||
|
||||
/// Errors produced by `AcceptorError` service.
|
||||
#[derive(Debug)]
|
||||
pub enum AcceptorError<T> {
|
||||
/// The inner service error
|
||||
Service(T),
|
||||
|
||||
/// Io specific error
|
||||
Io(io::Error),
|
||||
|
||||
/// The request did not complete within the specified timeout.
|
||||
Timeout,
|
||||
}
|
||||
|
||||
#[derive(Fail, Debug)]
|
||||
/// A set of errors that can occur during dispatching http requests
|
||||
pub enum HttpDispatchError {
|
||||
/// Application error
|
||||
#[fail(display = "Application specific error: {}", _0)]
|
||||
App(Error),
|
||||
|
||||
/// An `io::Error` that occurred while trying to read or write to a network
|
||||
/// stream.
|
||||
#[fail(display = "IO error: {}", _0)]
|
||||
Io(io::Error),
|
||||
|
||||
/// The first request did not complete within the specified timeout.
|
||||
#[fail(display = "The first request did not complete within the specified timeout")]
|
||||
SlowRequestTimeout,
|
||||
|
||||
/// Shutdown timeout
|
||||
#[fail(display = "Connection shutdown timeout")]
|
||||
ShutdownTimeout,
|
||||
|
||||
/// HTTP2 error
|
||||
#[fail(display = "HTTP2 error: {}", _0)]
|
||||
Http2(http2::Error),
|
||||
|
||||
/// Payload is not consumed
|
||||
#[fail(display = "Task is completed but request's payload is not consumed")]
|
||||
PayloadIsNotConsumed,
|
||||
|
||||
/// Malformed request
|
||||
#[fail(display = "Malformed request")]
|
||||
MalformedRequest,
|
||||
|
||||
/// Internal error
|
||||
#[fail(display = "Internal error")]
|
||||
InternalError,
|
||||
|
||||
/// Unknown error
|
||||
#[fail(display = "Unknown error")]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<Error> for HttpDispatchError {
|
||||
fn from(err: Error) -> Self {
|
||||
HttpDispatchError::App(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for HttpDispatchError {
|
||||
fn from(err: io::Error) -> Self {
|
||||
HttpDispatchError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<http2::Error> for HttpDispatchError {
|
||||
fn from(err: http2::Error) -> Self {
|
||||
HttpDispatchError::Http2(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ServerError(Version, StatusCode);
|
||||
|
||||
impl ServerError {
|
||||
pub fn err(ver: Version, status: StatusCode) -> Box<HttpHandlerTask> {
|
||||
Box::new(ServerError(ver, status))
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpHandlerTask for ServerError {
|
||||
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
|
||||
{
|
||||
let bytes = io.buffer();
|
||||
// Buffer should have sufficient capacity for status line
|
||||
// and extra space
|
||||
bytes.reserve(helpers::STATUS_LINE_BUF_SIZE + 1);
|
||||
helpers::write_status_line(self.0, self.1.as_u16(), bytes);
|
||||
}
|
||||
// Convert Status Code to Reason.
|
||||
let reason = self.1.canonical_reason().unwrap_or("");
|
||||
io.buffer().extend_from_slice(reason.as_bytes());
|
||||
// No response body.
|
||||
io.buffer().extend_from_slice(b"\r\ncontent-length: 0\r\n");
|
||||
// date header
|
||||
io.set_date();
|
||||
Ok(Async::Ready(true))
|
||||
}
|
||||
}
|
||||
875
src/server/h1.rs
875
src/server/h1.rs
File diff suppressed because it is too large
Load Diff
@@ -4,12 +4,11 @@ use bytes::{Bytes, BytesMut};
|
||||
use futures::{Async, Poll};
|
||||
use httparse;
|
||||
|
||||
use super::helpers::SharedHttpInnerMessage;
|
||||
use super::settings::WorkerSettings;
|
||||
use super::message::{MessageFlags, Request};
|
||||
use super::settings::ServiceConfig;
|
||||
use error::ParseError;
|
||||
use http::header::{HeaderName, HeaderValue};
|
||||
use http::{header, HttpTryFrom, Method, Uri, Version};
|
||||
use httprequest::MessageFlags;
|
||||
use uri::Url;
|
||||
|
||||
const MAX_BUFFER_SIZE: usize = 131_072;
|
||||
@@ -19,11 +18,9 @@ pub(crate) struct H1Decoder {
|
||||
decoder: Option<EncodingDecoder>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Message {
|
||||
Message {
|
||||
msg: SharedHttpInnerMessage,
|
||||
payload: bool,
|
||||
},
|
||||
Message { msg: Request, payload: bool },
|
||||
Chunk(Bytes),
|
||||
Eof,
|
||||
}
|
||||
@@ -46,7 +43,9 @@ impl H1Decoder {
|
||||
}
|
||||
|
||||
pub fn decode<H>(
|
||||
&mut self, src: &mut BytesMut, settings: &WorkerSettings<H>,
|
||||
&mut self,
|
||||
src: &mut BytesMut,
|
||||
settings: &ServiceConfig<H>,
|
||||
) -> Result<Option<Message>, DecoderError> {
|
||||
// read payload
|
||||
if self.decoder.is_some() {
|
||||
@@ -83,25 +82,27 @@ impl H1Decoder {
|
||||
}
|
||||
|
||||
fn parse_message<H>(
|
||||
&self, buf: &mut BytesMut, settings: &WorkerSettings<H>,
|
||||
) -> Poll<(SharedHttpInnerMessage, Option<EncodingDecoder>), ParseError> {
|
||||
&self,
|
||||
buf: &mut BytesMut,
|
||||
settings: &ServiceConfig<H>,
|
||||
) -> Poll<(Request, Option<EncodingDecoder>), ParseError> {
|
||||
// Parse http message
|
||||
let mut has_upgrade = false;
|
||||
let mut chunked = false;
|
||||
let mut content_length = None;
|
||||
|
||||
let msg = {
|
||||
let bytes_ptr = buf.as_ref().as_ptr() as usize;
|
||||
let mut headers: [httparse::Header; MAX_HEADERS] =
|
||||
// Unsafe: we read only this data only after httparse parses headers into.
|
||||
// performance bump for pipeline benchmarks.
|
||||
let mut headers: [HeaderIndex; MAX_HEADERS] =
|
||||
unsafe { mem::uninitialized() };
|
||||
|
||||
let (len, method, path, version, headers_len) = {
|
||||
let b = unsafe {
|
||||
let b: &[u8] = buf;
|
||||
&*(b as *const [u8])
|
||||
};
|
||||
let mut req = httparse::Request::new(&mut headers);
|
||||
match req.parse(b)? {
|
||||
let mut parsed: [httparse::Header; MAX_HEADERS] =
|
||||
unsafe { mem::uninitialized() };
|
||||
|
||||
let mut req = httparse::Request::new(&mut parsed);
|
||||
match req.parse(buf)? {
|
||||
httparse::Status::Complete(len) => {
|
||||
let method = Method::from_bytes(req.method.unwrap().as_bytes())
|
||||
.map_err(|_| ParseError::Method)?;
|
||||
@@ -111,6 +112,8 @@ impl H1Decoder {
|
||||
} else {
|
||||
Version::HTTP_10
|
||||
};
|
||||
HeaderIndex::record(buf, req.headers, &mut headers);
|
||||
|
||||
(len, method, path, version, req.headers.len())
|
||||
}
|
||||
httparse::Status::Partial => return Ok(Async::NotReady),
|
||||
@@ -120,22 +123,22 @@ impl H1Decoder {
|
||||
let slice = buf.split_to(len).freeze();
|
||||
|
||||
// convert headers
|
||||
let msg = settings.get_http_message();
|
||||
let mut msg = settings.get_request();
|
||||
{
|
||||
let msg_mut = msg.get_mut();
|
||||
msg_mut
|
||||
let inner = msg.inner_mut();
|
||||
inner
|
||||
.flags
|
||||
.get_mut()
|
||||
.set(MessageFlags::KEEPALIVE, version != Version::HTTP_10);
|
||||
|
||||
for header in headers[..headers_len].iter() {
|
||||
if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) {
|
||||
has_upgrade = has_upgrade || name == header::UPGRADE;
|
||||
|
||||
let v_start = header.value.as_ptr() as usize - bytes_ptr;
|
||||
let v_end = v_start + header.value.len();
|
||||
for idx in headers[..headers_len].iter() {
|
||||
if let Ok(name) =
|
||||
HeaderName::from_bytes(&slice[idx.name.0..idx.name.1])
|
||||
{
|
||||
// Unsafe: httparse check header value for valid utf-8
|
||||
let value = unsafe {
|
||||
HeaderValue::from_shared_unchecked(
|
||||
slice.slice(v_start, v_end),
|
||||
slice.slice(idx.value.0, idx.value.1),
|
||||
)
|
||||
};
|
||||
match name {
|
||||
@@ -168,27 +171,37 @@ impl H1Decoder {
|
||||
{
|
||||
true
|
||||
} else {
|
||||
version == Version::HTTP_11
|
||||
&& !(conn.contains("close")
|
||||
|| conn.contains("upgrade"))
|
||||
version == Version::HTTP_11 && !(conn
|
||||
.contains("close")
|
||||
|| conn.contains("upgrade"))
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
msg_mut.flags.set(MessageFlags::KEEPALIVE, ka);
|
||||
inner.flags.get_mut().set(MessageFlags::KEEPALIVE, ka);
|
||||
}
|
||||
header::UPGRADE => {
|
||||
has_upgrade = true;
|
||||
// check content-length, some clients (dart)
|
||||
// sends "content-length: 0" with websocket upgrade
|
||||
if let Ok(val) = value.to_str() {
|
||||
if val == "websocket" {
|
||||
content_length = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
msg_mut.headers.append(name, value);
|
||||
inner.headers.append(name, value);
|
||||
} else {
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
}
|
||||
|
||||
msg_mut.url = path;
|
||||
msg_mut.method = method;
|
||||
msg_mut.version = version;
|
||||
inner.url = path;
|
||||
inner.method = method;
|
||||
inner.version = version;
|
||||
}
|
||||
msg
|
||||
};
|
||||
@@ -200,7 +213,7 @@ impl H1Decoder {
|
||||
} else if let Some(len) = content_length {
|
||||
// Content-Length
|
||||
Some(EncodingDecoder::length(len))
|
||||
} else if has_upgrade || msg.get_ref().method == Method::CONNECT {
|
||||
} else if has_upgrade || msg.inner.method == Method::CONNECT {
|
||||
// upgrade(websocket) or connect
|
||||
Some(EncodingDecoder::eof())
|
||||
} else {
|
||||
@@ -211,6 +224,30 @@ impl H1Decoder {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct HeaderIndex {
|
||||
pub(crate) name: (usize, usize),
|
||||
pub(crate) value: (usize, usize),
|
||||
}
|
||||
|
||||
impl HeaderIndex {
|
||||
pub(crate) fn record(
|
||||
bytes: &[u8],
|
||||
headers: &[httparse::Header],
|
||||
indices: &mut [HeaderIndex],
|
||||
) {
|
||||
let bytes_ptr = bytes.as_ptr() as usize;
|
||||
for (header, indices) in headers.iter().zip(indices.iter_mut()) {
|
||||
let name_start = header.name.as_ptr() as usize - bytes_ptr;
|
||||
let name_end = name_start + header.name.len();
|
||||
indices.name = (name_start, name_end);
|
||||
let value_start = header.value.as_ptr() as usize - bytes_ptr;
|
||||
let value_end = value_start + header.value.len();
|
||||
indices.value = (value_start, value_end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decoders to handle different Transfer-Encodings.
|
||||
///
|
||||
/// If a message body does not include a Transfer-Encoding, it *should*
|
||||
@@ -345,7 +382,10 @@ macro_rules! byte (
|
||||
|
||||
impl ChunkedState {
|
||||
fn step(
|
||||
&self, body: &mut BytesMut, size: &mut u64, buf: &mut Option<Bytes>,
|
||||
&self,
|
||||
body: &mut BytesMut,
|
||||
size: &mut u64,
|
||||
buf: &mut Option<Bytes>,
|
||||
) -> Poll<ChunkedState, io::Error> {
|
||||
use self::ChunkedState::*;
|
||||
match *self {
|
||||
@@ -408,7 +448,8 @@ impl ChunkedState {
|
||||
}
|
||||
}
|
||||
fn read_size_lf(
|
||||
rdr: &mut BytesMut, size: &mut u64,
|
||||
rdr: &mut BytesMut,
|
||||
size: &mut u64,
|
||||
) -> Poll<ChunkedState, io::Error> {
|
||||
match byte!(rdr) {
|
||||
b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)),
|
||||
@@ -421,7 +462,9 @@ impl ChunkedState {
|
||||
}
|
||||
|
||||
fn read_body(
|
||||
rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option<Bytes>,
|
||||
rdr: &mut BytesMut,
|
||||
rem: &mut u64,
|
||||
buf: &mut Option<Bytes>,
|
||||
) -> Poll<ChunkedState, io::Error> {
|
||||
trace!("Chunked read, remaining={:?}", rem);
|
||||
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
|
||||
// #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures::{Async, Poll};
|
||||
use std::io;
|
||||
use std::rc::Rc;
|
||||
use tokio_io::AsyncWrite;
|
||||
|
||||
use super::encoding::ContentEncoder;
|
||||
use super::helpers;
|
||||
use super::settings::WorkerSettings;
|
||||
use super::shared::SharedBytes;
|
||||
use super::output::{Output, ResponseInfo, ResponseLength};
|
||||
use super::settings::ServiceConfig;
|
||||
use super::Request;
|
||||
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
|
||||
use body::{Binary, Body};
|
||||
use header::ContentEncoding;
|
||||
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE};
|
||||
use http::header::{
|
||||
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
|
||||
};
|
||||
use http::{Method, Version};
|
||||
use httprequest::HttpInnerMessage;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
|
||||
@@ -32,24 +33,20 @@ bitflags! {
|
||||
pub(crate) struct H1Writer<T: AsyncWrite, H: 'static> {
|
||||
flags: Flags,
|
||||
stream: T,
|
||||
encoder: ContentEncoder,
|
||||
written: u64,
|
||||
headers_size: u32,
|
||||
buffer: SharedBytes,
|
||||
buffer: Output,
|
||||
buffer_capacity: usize,
|
||||
settings: Rc<WorkerSettings<H>>,
|
||||
settings: ServiceConfig<H>,
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
|
||||
pub fn new(
|
||||
stream: T, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
|
||||
) -> H1Writer<T, H> {
|
||||
pub fn new(stream: T, settings: ServiceConfig<H>) -> H1Writer<T, H> {
|
||||
H1Writer {
|
||||
flags: Flags::KEEPALIVE,
|
||||
encoder: ContentEncoder::empty(buf.clone()),
|
||||
written: 0,
|
||||
headers_size: 0,
|
||||
buffer: buf,
|
||||
buffer: Output::Buffer(settings.get_bytes()),
|
||||
buffer_capacity: 0,
|
||||
stream,
|
||||
settings,
|
||||
@@ -65,20 +62,27 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
|
||||
self.flags = Flags::KEEPALIVE;
|
||||
}
|
||||
|
||||
pub fn flushed(&mut self) -> bool {
|
||||
self.buffer.is_empty()
|
||||
}
|
||||
|
||||
pub fn disconnected(&mut self) {
|
||||
self.buffer.take();
|
||||
self.flags.insert(Flags::DISCONNECTED);
|
||||
}
|
||||
|
||||
pub fn upgrade(&self) -> bool {
|
||||
self.flags.contains(Flags::UPGRADE)
|
||||
}
|
||||
|
||||
pub fn keepalive(&self) -> bool {
|
||||
self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE)
|
||||
}
|
||||
|
||||
fn write_data(&mut self, data: &[u8]) -> io::Result<usize> {
|
||||
fn write_data(stream: &mut T, data: &[u8]) -> io::Result<usize> {
|
||||
let mut written = 0;
|
||||
while written < data.len() {
|
||||
match self.stream.write(&data[written..]) {
|
||||
match stream.write(&data[written..]) {
|
||||
Ok(0) => {
|
||||
self.disconnected();
|
||||
return Err(io::Error::new(io::ErrorKind::WriteZero, ""));
|
||||
}
|
||||
Ok(n) => {
|
||||
@@ -94,6 +98,14 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite, H: 'static> Drop for H1Writer<T, H> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(bytes) = self.buffer.take_option() {
|
||||
self.settings.release_bytes(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
#[inline]
|
||||
fn written(&self) -> u64 {
|
||||
@@ -101,22 +113,21 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_date(&self, dst: &mut BytesMut) {
|
||||
self.settings.set_date(dst)
|
||||
fn set_date(&mut self) {
|
||||
self.settings.set_date(self.buffer.as_mut(), true)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn buffer(&self) -> &mut BytesMut {
|
||||
self.buffer.get_mut()
|
||||
fn buffer(&mut self) -> &mut BytesMut {
|
||||
self.buffer.as_mut()
|
||||
}
|
||||
|
||||
fn start(
|
||||
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
|
||||
encoding: ContentEncoding,
|
||||
&mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding,
|
||||
) -> io::Result<WriterState> {
|
||||
// prepare task
|
||||
self.encoder =
|
||||
ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
|
||||
let mut info = ResponseInfo::new(req.inner.method == Method::HEAD);
|
||||
self.buffer.for_server(&mut info, &req.inner, msg, encoding);
|
||||
if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) {
|
||||
self.flags = Flags::STARTED | Flags::KEEPALIVE;
|
||||
} else {
|
||||
@@ -124,7 +135,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
}
|
||||
|
||||
// Connection upgrade
|
||||
let version = msg.version().unwrap_or_else(|| req.version);
|
||||
let version = msg.version().unwrap_or_else(|| req.inner.version);
|
||||
if msg.upgrade() {
|
||||
self.flags.insert(Flags::UPGRADE);
|
||||
msg.headers_mut()
|
||||
@@ -144,37 +155,50 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
|
||||
// render message
|
||||
{
|
||||
let mut buffer = self.buffer.get_mut();
|
||||
// output buffer
|
||||
let mut buffer = self.buffer.as_mut();
|
||||
|
||||
let reason = msg.reason().as_bytes();
|
||||
let mut is_bin = if let Body::Binary(ref bytes) = body {
|
||||
if let Body::Binary(ref bytes) = body {
|
||||
buffer.reserve(
|
||||
256
|
||||
+ msg.headers().len() * AVERAGE_HEADER_SIZE
|
||||
256 + msg.headers().len() * AVERAGE_HEADER_SIZE
|
||||
+ bytes.len()
|
||||
+ reason.len(),
|
||||
);
|
||||
true
|
||||
} else {
|
||||
buffer.reserve(
|
||||
256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len(),
|
||||
);
|
||||
false
|
||||
};
|
||||
}
|
||||
|
||||
// status line
|
||||
helpers::write_status_line(version, msg.status().as_u16(), &mut buffer);
|
||||
SharedBytes::extend_from_slice_(buffer, reason);
|
||||
buffer.extend_from_slice(reason);
|
||||
|
||||
match body {
|
||||
Body::Empty => if req.method != Method::HEAD {
|
||||
SharedBytes::put_slice(buffer, b"\r\ncontent-length: 0\r\n");
|
||||
} else {
|
||||
SharedBytes::put_slice(buffer, b"\r\n");
|
||||
},
|
||||
Body::Binary(ref bytes) => {
|
||||
helpers::write_content_length(bytes.len(), &mut buffer)
|
||||
// content length
|
||||
let mut len_is_set = true;
|
||||
match info.length {
|
||||
ResponseLength::Chunked => {
|
||||
buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n")
|
||||
}
|
||||
_ => SharedBytes::put_slice(buffer, b"\r\n"),
|
||||
ResponseLength::Length(len) => {
|
||||
helpers::write_content_length(len, &mut buffer)
|
||||
}
|
||||
ResponseLength::Length64(len) => {
|
||||
buffer.extend_from_slice(b"\r\ncontent-length: ");
|
||||
write!(buffer.writer(), "{}", len)?;
|
||||
buffer.extend_from_slice(b"\r\n");
|
||||
}
|
||||
ResponseLength::Zero => {
|
||||
len_is_set = false;
|
||||
buffer.extend_from_slice(b"\r\n");
|
||||
}
|
||||
ResponseLength::None => buffer.extend_from_slice(b"\r\n"),
|
||||
}
|
||||
if let Some(ce) = info.content_encoding {
|
||||
buffer.extend_from_slice(b"content-encoding: ");
|
||||
buffer.extend_from_slice(ce.as_ref());
|
||||
buffer.extend_from_slice(b"\r\n");
|
||||
}
|
||||
|
||||
// write headers
|
||||
@@ -183,20 +207,37 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
let mut remaining = buffer.remaining_mut();
|
||||
let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) };
|
||||
for (key, value) in msg.headers() {
|
||||
if is_bin && key == CONTENT_LENGTH {
|
||||
is_bin = false;
|
||||
continue;
|
||||
match *key {
|
||||
TRANSFER_ENCODING => continue,
|
||||
CONTENT_ENCODING => if encoding != ContentEncoding::Identity {
|
||||
continue;
|
||||
},
|
||||
CONTENT_LENGTH => match info.length {
|
||||
ResponseLength::None => (),
|
||||
ResponseLength::Zero => {
|
||||
len_is_set = true;
|
||||
}
|
||||
_ => continue,
|
||||
},
|
||||
DATE => {
|
||||
has_date = true;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
has_date = has_date || key == DATE;
|
||||
|
||||
let v = value.as_ref();
|
||||
let k = key.as_str().as_bytes();
|
||||
let len = k.len() + v.len() + 4;
|
||||
if len > remaining {
|
||||
unsafe { buffer.advance_mut(pos) };
|
||||
unsafe {
|
||||
buffer.advance_mut(pos);
|
||||
}
|
||||
pos = 0;
|
||||
buffer.reserve(len);
|
||||
remaining = buffer.remaining_mut();
|
||||
buf = unsafe { &mut *(buffer.bytes_mut() as *mut _) };
|
||||
unsafe {
|
||||
buf = &mut *(buffer.bytes_mut() as *mut _);
|
||||
}
|
||||
}
|
||||
|
||||
buf[pos..pos + k.len()].copy_from_slice(k);
|
||||
@@ -209,21 +250,26 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
pos += 2;
|
||||
remaining -= len;
|
||||
}
|
||||
unsafe { buffer.advance_mut(pos) };
|
||||
unsafe {
|
||||
buffer.advance_mut(pos);
|
||||
}
|
||||
if !len_is_set {
|
||||
buffer.extend_from_slice(b"content-length: 0\r\n")
|
||||
}
|
||||
|
||||
// optimized date header, set_date writes \r\n
|
||||
if !has_date {
|
||||
self.settings.set_date(&mut buffer);
|
||||
self.settings.set_date(&mut buffer, true);
|
||||
} else {
|
||||
// msg eof
|
||||
SharedBytes::extend_from_slice_(buffer, b"\r\n");
|
||||
buffer.extend_from_slice(b"\r\n");
|
||||
}
|
||||
self.headers_size = buffer.len() as u32;
|
||||
}
|
||||
|
||||
if let Body::Binary(bytes) = body {
|
||||
self.written = bytes.len() as u64;
|
||||
self.encoder.write(bytes)?;
|
||||
self.buffer.write(bytes.as_ref())?;
|
||||
} else {
|
||||
// capacity, makes sense only for streaming or actor
|
||||
self.buffer_capacity = msg.write_buffer_capacity();
|
||||
@@ -233,7 +279,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
Ok(WriterState::Done)
|
||||
}
|
||||
|
||||
fn write(&mut self, payload: Binary) -> io::Result<WriterState> {
|
||||
fn write(&mut self, payload: &Binary) -> io::Result<WriterState> {
|
||||
self.written += payload.len() as u64;
|
||||
if !self.flags.contains(Flags::DISCONNECTED) {
|
||||
if self.flags.contains(Flags::STARTED) {
|
||||
@@ -241,21 +287,27 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
if self.flags.contains(Flags::UPGRADE) {
|
||||
if self.buffer.is_empty() {
|
||||
let pl: &[u8] = payload.as_ref();
|
||||
let n = self.write_data(pl)?;
|
||||
let n = match Self::write_data(&mut self.stream, pl) {
|
||||
Err(err) => {
|
||||
self.disconnected();
|
||||
return Err(err);
|
||||
}
|
||||
Ok(val) => val,
|
||||
};
|
||||
if n < pl.len() {
|
||||
self.buffer.extend_from_slice(&pl[n..]);
|
||||
self.buffer.write(&pl[n..])?;
|
||||
return Ok(WriterState::Done);
|
||||
}
|
||||
} else {
|
||||
self.buffer.extend(payload);
|
||||
self.buffer.write(payload.as_ref())?;
|
||||
}
|
||||
} else {
|
||||
// TODO: add warning, write after EOF
|
||||
self.encoder.write(payload)?;
|
||||
self.buffer.write(payload.as_ref())?;
|
||||
}
|
||||
} else {
|
||||
// could be response to EXCEPT header
|
||||
self.buffer.extend_from_slice(payload.as_ref())
|
||||
self.buffer.write(payload.as_ref())?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +319,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
}
|
||||
|
||||
fn write_eof(&mut self) -> io::Result<WriterState> {
|
||||
if !self.encoder.write_eof()? {
|
||||
if !self.buffer.write_eof()? {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Last payload item, but eof is not reached",
|
||||
@@ -281,10 +333,20 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
|
||||
#[inline]
|
||||
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> {
|
||||
if self.flags.contains(Flags::DISCONNECTED) {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "disconnected"));
|
||||
}
|
||||
|
||||
if !self.buffer.is_empty() {
|
||||
let buf: &[u8] =
|
||||
unsafe { &mut *(self.buffer.as_ref() as *const _ as *mut _) };
|
||||
let written = self.write_data(buf)?;
|
||||
let written = {
|
||||
match Self::write_data(&mut self.stream, self.buffer.as_ref().as_ref()) {
|
||||
Err(err) => {
|
||||
self.disconnected();
|
||||
return Err(err);
|
||||
}
|
||||
Ok(val) => val,
|
||||
}
|
||||
};
|
||||
let _ = self.buffer.split_to(written);
|
||||
if shutdown && !self.buffer.is_empty()
|
||||
|| (self.buffer.len() > self.buffer_capacity)
|
||||
@@ -293,9 +355,10 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
}
|
||||
}
|
||||
if shutdown {
|
||||
self.stream.poll_flush()?;
|
||||
self.stream.shutdown()
|
||||
} else {
|
||||
Ok(Async::Ready(()))
|
||||
Ok(self.stream.poll_flush()?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
348
src/server/h2.rs
348
src/server/h2.rs
@@ -1,37 +1,34 @@
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::SocketAddr;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use std::{cmp, io, mem};
|
||||
|
||||
use actix::Arbiter;
|
||||
use bytes::{Buf, Bytes};
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use http2::server::{self, Connection, Handshake, SendResponse};
|
||||
use http2::{Reason, RecvStream};
|
||||
use modhttp::request::Parts;
|
||||
use tokio_core::reactor::Timeout;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio_timer::Delay;
|
||||
|
||||
use error::PayloadError;
|
||||
use httpmessage::HttpMessage;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use error::{Error, PayloadError};
|
||||
use extensions::Extensions;
|
||||
use http::{StatusCode, Version};
|
||||
use payload::{Payload, PayloadStatus, PayloadWriter};
|
||||
use pipeline::Pipeline;
|
||||
use uri::Url;
|
||||
|
||||
use super::encoding::PayloadType;
|
||||
use super::error::{HttpDispatchError, ServerError};
|
||||
use super::h2writer::H2Writer;
|
||||
use super::settings::WorkerSettings;
|
||||
use super::{HttpHandler, HttpHandlerTask, Writer};
|
||||
use super::input::PayloadType;
|
||||
use super::settings::ServiceConfig;
|
||||
use super::{HttpHandler, HttpHandlerTask, IoStream, Writer};
|
||||
|
||||
bitflags! {
|
||||
struct Flags: u8 {
|
||||
const DISCONNECTED = 0b0000_0010;
|
||||
const DISCONNECTED = 0b0000_0001;
|
||||
const SHUTDOWN = 0b0000_0010;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,14 +36,16 @@ bitflags! {
|
||||
pub(crate) struct Http2<T, H>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
H: 'static,
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
flags: Flags,
|
||||
settings: Rc<WorkerSettings<H>>,
|
||||
settings: ServiceConfig<H>,
|
||||
addr: Option<SocketAddr>,
|
||||
state: State<IoWrapper<T>>,
|
||||
tasks: VecDeque<Entry<H>>,
|
||||
keepalive_timer: Option<Timeout>,
|
||||
extensions: Option<Rc<Extensions>>,
|
||||
ka_expire: Instant,
|
||||
ka_timer: Option<Delay>,
|
||||
}
|
||||
|
||||
enum State<T: AsyncRead + AsyncWrite> {
|
||||
@@ -57,97 +56,112 @@ enum State<T: AsyncRead + AsyncWrite> {
|
||||
|
||||
impl<T, H> Http2<T, H>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
T: IoStream + 'static,
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
pub fn new(
|
||||
settings: Rc<WorkerSettings<H>>, io: T, addr: Option<SocketAddr>, buf: Bytes,
|
||||
settings: ServiceConfig<H>,
|
||||
io: T,
|
||||
buf: Bytes,
|
||||
keepalive_timer: Option<Delay>,
|
||||
) -> Self {
|
||||
let addr = io.peer_addr();
|
||||
let extensions = io.extensions();
|
||||
|
||||
// keep-alive timeout
|
||||
let (ka_expire, ka_timer) = if let Some(delay) = keepalive_timer {
|
||||
(delay.deadline(), Some(delay))
|
||||
} else if let Some(delay) = settings.keep_alive_timer() {
|
||||
(delay.deadline(), Some(delay))
|
||||
} else {
|
||||
(settings.now(), None)
|
||||
};
|
||||
|
||||
Http2 {
|
||||
flags: Flags::empty(),
|
||||
tasks: VecDeque::new(),
|
||||
state: State::Handshake(server::handshake(IoWrapper {
|
||||
unread: Some(buf),
|
||||
unread: if buf.is_empty() { None } else { Some(buf) },
|
||||
inner: io,
|
||||
})),
|
||||
keepalive_timer: None,
|
||||
addr,
|
||||
settings,
|
||||
extensions,
|
||||
ka_expire,
|
||||
ka_timer,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn shutdown(&mut self) {
|
||||
self.state = State::Empty;
|
||||
self.tasks.clear();
|
||||
self.keepalive_timer.take();
|
||||
}
|
||||
pub fn poll(&mut self) -> Poll<(), HttpDispatchError> {
|
||||
self.poll_keepalive()?;
|
||||
|
||||
pub fn settings(&self) -> &WorkerSettings<H> {
|
||||
self.settings.as_ref()
|
||||
}
|
||||
|
||||
pub fn poll(&mut self) -> Poll<(), ()> {
|
||||
// server
|
||||
if let State::Connection(ref mut conn) = self.state {
|
||||
// keep-alive timer
|
||||
if let Some(ref mut timeout) = self.keepalive_timer {
|
||||
match timeout.poll() {
|
||||
Ok(Async::Ready(_)) => {
|
||||
trace!("Keep-alive timeout, close connection");
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
Ok(Async::NotReady) => (),
|
||||
Err(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
// shutdown connection
|
||||
if self.flags.contains(Flags::SHUTDOWN) {
|
||||
return conn.poll_close().map_err(|e| e.into());
|
||||
}
|
||||
|
||||
let mut not_ready = true;
|
||||
let disconnected = self.flags.contains(Flags::DISCONNECTED);
|
||||
|
||||
// check in-flight connections
|
||||
for item in &mut self.tasks {
|
||||
// read payload
|
||||
item.poll_payload();
|
||||
if !disconnected {
|
||||
item.poll_payload();
|
||||
}
|
||||
|
||||
if !item.flags.contains(EntryFlags::EOF) {
|
||||
let retry = item.payload.need_read() == PayloadStatus::Read;
|
||||
loop {
|
||||
match item.task.poll_io(&mut item.stream) {
|
||||
Ok(Async::Ready(ready)) => {
|
||||
if ready {
|
||||
if disconnected {
|
||||
item.flags.insert(EntryFlags::EOF);
|
||||
} else {
|
||||
let retry = item.payload.need_read() == PayloadStatus::Read;
|
||||
loop {
|
||||
match item.task.poll_io(&mut item.stream) {
|
||||
Ok(Async::Ready(ready)) => {
|
||||
if ready {
|
||||
item.flags.insert(
|
||||
EntryFlags::EOF | EntryFlags::FINISHED,
|
||||
);
|
||||
} else {
|
||||
item.flags.insert(EntryFlags::EOF);
|
||||
}
|
||||
not_ready = false;
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
if item.payload.need_read()
|
||||
== PayloadStatus::Read
|
||||
&& !retry
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Unhandled error: {}", err);
|
||||
item.flags.insert(
|
||||
EntryFlags::EOF | EntryFlags::FINISHED,
|
||||
EntryFlags::EOF
|
||||
| EntryFlags::ERROR
|
||||
| EntryFlags::WRITE_DONE,
|
||||
);
|
||||
} else {
|
||||
item.flags.insert(EntryFlags::EOF);
|
||||
}
|
||||
not_ready = false;
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
if item.payload.need_read() == PayloadStatus::Read
|
||||
&& !retry
|
||||
{
|
||||
continue;
|
||||
item.stream.reset(Reason::INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Unhandled error: {}", err);
|
||||
item.flags.insert(
|
||||
EntryFlags::EOF
|
||||
| EntryFlags::ERROR
|
||||
| EntryFlags::WRITE_DONE,
|
||||
);
|
||||
item.stream.reset(Reason::INTERNAL_ERROR);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if !item.flags.contains(EntryFlags::FINISHED) {
|
||||
match item.task.poll() {
|
||||
}
|
||||
|
||||
if item.flags.contains(EntryFlags::EOF)
|
||||
&& !item.flags.contains(EntryFlags::FINISHED)
|
||||
{
|
||||
match item.task.poll_completed() {
|
||||
Ok(Async::NotReady) => (),
|
||||
Ok(Async::Ready(_)) => {
|
||||
not_ready = false;
|
||||
item.flags.insert(EntryFlags::FINISHED);
|
||||
item.flags.insert(
|
||||
EntryFlags::FINISHED | EntryFlags::WRITE_DONE,
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
item.flags.insert(
|
||||
@@ -160,14 +174,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if !item.flags.contains(EntryFlags::WRITE_DONE) {
|
||||
if item.flags.contains(EntryFlags::FINISHED)
|
||||
&& !item.flags.contains(EntryFlags::WRITE_DONE)
|
||||
&& !disconnected
|
||||
{
|
||||
match item.stream.poll_completed(false) {
|
||||
Ok(Async::NotReady) => (),
|
||||
Ok(Async::Ready(_)) => {
|
||||
not_ready = false;
|
||||
item.flags.insert(EntryFlags::WRITE_DONE);
|
||||
}
|
||||
Err(_err) => {
|
||||
Err(_) => {
|
||||
item.flags.insert(EntryFlags::ERROR);
|
||||
}
|
||||
}
|
||||
@@ -176,7 +193,7 @@ where
|
||||
|
||||
// cleanup finished tasks
|
||||
while !self.tasks.is_empty() {
|
||||
if self.tasks[0].flags.contains(EntryFlags::EOF)
|
||||
if self.tasks[0].flags.contains(EntryFlags::FINISHED)
|
||||
&& self.tasks[0].flags.contains(EntryFlags::WRITE_DONE)
|
||||
|| self.tasks[0].flags.contains(EntryFlags::ERROR)
|
||||
{
|
||||
@@ -200,49 +217,30 @@ where
|
||||
not_ready = false;
|
||||
let (parts, body) = req.into_parts();
|
||||
|
||||
// stop keepalive timer
|
||||
self.keepalive_timer.take();
|
||||
// update keep-alive expire
|
||||
if self.ka_timer.is_some() {
|
||||
if let Some(expire) = self.settings.keep_alive_expire() {
|
||||
self.ka_expire = expire;
|
||||
}
|
||||
}
|
||||
|
||||
self.tasks.push_back(Entry::new(
|
||||
parts,
|
||||
body,
|
||||
resp,
|
||||
self.addr,
|
||||
&self.settings,
|
||||
self.settings.clone(),
|
||||
self.extensions.clone(),
|
||||
));
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
// start keep-alive timer
|
||||
if self.tasks.is_empty() {
|
||||
if self.settings.keep_alive_enabled() {
|
||||
let keep_alive = self.settings.keep_alive();
|
||||
if keep_alive > 0 && self.keepalive_timer.is_none() {
|
||||
trace!("Start keep-alive timer");
|
||||
let mut timeout = Timeout::new(
|
||||
Duration::new(keep_alive, 0),
|
||||
Arbiter::handle()).unwrap();
|
||||
// register timeout
|
||||
let _ = timeout.poll();
|
||||
self.keepalive_timer = Some(timeout);
|
||||
}
|
||||
} else {
|
||||
// keep-alive disable, drop connection
|
||||
return conn.poll_close().map_err(|e| {
|
||||
error!("Error during connection close: {}", e)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// keep-alive unset, rely on operating system
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(err) => {
|
||||
trace!("Connection error: {}", err);
|
||||
self.flags.insert(Flags::DISCONNECTED);
|
||||
self.flags.insert(Flags::SHUTDOWN);
|
||||
for entry in &mut self.tasks {
|
||||
entry.task.disconnected()
|
||||
}
|
||||
self.keepalive_timer.take();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,9 +248,7 @@ where
|
||||
if not_ready {
|
||||
if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED)
|
||||
{
|
||||
return conn
|
||||
.poll_close()
|
||||
.map_err(|e| error!("Error during connection close: {}", e));
|
||||
return conn.poll_close().map_err(|e| e.into());
|
||||
} else {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
@@ -267,7 +263,7 @@ where
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(err) => {
|
||||
trace!("Error handling connection: {}", err);
|
||||
return Err(());
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -276,6 +272,39 @@ where
|
||||
|
||||
self.poll()
|
||||
}
|
||||
|
||||
/// keep-alive timer. returns `true` is keep-alive, otherwise drop
|
||||
fn poll_keepalive(&mut self) -> Result<(), HttpDispatchError> {
|
||||
if let Some(ref mut timer) = self.ka_timer {
|
||||
match timer.poll() {
|
||||
Ok(Async::Ready(_)) => {
|
||||
// if we get timer during shutdown, just drop connection
|
||||
if self.flags.contains(Flags::SHUTDOWN) {
|
||||
return Err(HttpDispatchError::ShutdownTimeout);
|
||||
}
|
||||
if timer.deadline() >= self.ka_expire {
|
||||
// check for any outstanding request handling
|
||||
if self.tasks.is_empty() {
|
||||
return Err(HttpDispatchError::ShutdownTimeout);
|
||||
} else if let Some(dl) = self.settings.keep_alive_expire() {
|
||||
timer.reset(dl);
|
||||
let _ = timer.poll();
|
||||
}
|
||||
} else {
|
||||
timer.reset(self.ka_expire);
|
||||
let _ = timer.poll();
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady) => (),
|
||||
Err(e) => {
|
||||
error!("Timer error {:?}", e);
|
||||
return Err(HttpDispatchError::Unknown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
@@ -288,18 +317,48 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
struct Entry<H: 'static> {
|
||||
task: Box<HttpHandlerTask>,
|
||||
enum EntryPipe<H: HttpHandler> {
|
||||
Task(H::Task),
|
||||
Error(Box<HttpHandlerTask>),
|
||||
}
|
||||
|
||||
impl<H: HttpHandler> EntryPipe<H> {
|
||||
fn disconnected(&mut self) {
|
||||
match *self {
|
||||
EntryPipe::Task(ref mut task) => task.disconnected(),
|
||||
EntryPipe::Error(ref mut task) => task.disconnected(),
|
||||
}
|
||||
}
|
||||
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
|
||||
match *self {
|
||||
EntryPipe::Task(ref mut task) => task.poll_io(io),
|
||||
EntryPipe::Error(ref mut task) => task.poll_io(io),
|
||||
}
|
||||
}
|
||||
fn poll_completed(&mut self) -> Poll<(), Error> {
|
||||
match *self {
|
||||
EntryPipe::Task(ref mut task) => task.poll_completed(),
|
||||
EntryPipe::Error(ref mut task) => task.poll_completed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Entry<H: HttpHandler + 'static> {
|
||||
task: EntryPipe<H>,
|
||||
payload: PayloadType,
|
||||
recv: RecvStream,
|
||||
stream: H2Writer<H>,
|
||||
flags: EntryFlags,
|
||||
}
|
||||
|
||||
impl<H: 'static> Entry<H> {
|
||||
impl<H: HttpHandler + 'static> Entry<H> {
|
||||
fn new(
|
||||
parts: Parts, recv: RecvStream, resp: SendResponse<Bytes>,
|
||||
addr: Option<SocketAddr>, settings: &Rc<WorkerSettings<H>>,
|
||||
parts: Parts,
|
||||
recv: RecvStream,
|
||||
resp: SendResponse<Bytes>,
|
||||
addr: Option<SocketAddr>,
|
||||
settings: ServiceConfig<H>,
|
||||
extensions: Option<Rc<Extensions>>,
|
||||
) -> Entry<H>
|
||||
where
|
||||
H: HttpHandler + 'static,
|
||||
@@ -307,41 +366,36 @@ impl<H: 'static> Entry<H> {
|
||||
// Payload and Content-Encoding
|
||||
let (psender, payload) = Payload::new(false);
|
||||
|
||||
let msg = settings.get_http_message();
|
||||
msg.get_mut().url = Url::new(parts.uri);
|
||||
msg.get_mut().method = parts.method;
|
||||
msg.get_mut().version = parts.version;
|
||||
msg.get_mut().headers = parts.headers;
|
||||
msg.get_mut().payload = Some(payload);
|
||||
msg.get_mut().addr = addr;
|
||||
|
||||
let mut req = HttpRequest::from_message(msg);
|
||||
|
||||
// Payload sender
|
||||
let psender = PayloadType::new(req.headers(), psender);
|
||||
|
||||
// start request processing
|
||||
let mut task = None;
|
||||
for h in settings.handlers().iter_mut() {
|
||||
req = match h.handle(req) {
|
||||
Ok(t) => {
|
||||
task = Some(t);
|
||||
break;
|
||||
}
|
||||
Err(req) => req,
|
||||
}
|
||||
let mut msg = settings.get_request();
|
||||
{
|
||||
let inner = msg.inner_mut();
|
||||
inner.url = Url::new(parts.uri);
|
||||
inner.method = parts.method;
|
||||
inner.version = parts.version;
|
||||
inner.headers = parts.headers;
|
||||
inner.stream_extensions = extensions;
|
||||
*inner.payload.borrow_mut() = Some(payload);
|
||||
inner.addr = addr;
|
||||
}
|
||||
|
||||
// Payload sender
|
||||
let psender = PayloadType::new(msg.headers(), psender);
|
||||
|
||||
// start request processing
|
||||
let task = match settings.handler().handle(msg) {
|
||||
Ok(task) => EntryPipe::Task(task),
|
||||
Err(_) => EntryPipe::Error(ServerError::err(
|
||||
Version::HTTP_2,
|
||||
StatusCode::NOT_FOUND,
|
||||
)),
|
||||
};
|
||||
|
||||
Entry {
|
||||
task: task.unwrap_or_else(|| Pipeline::error(HttpResponse::NotFound())),
|
||||
payload: psender,
|
||||
stream: H2Writer::new(
|
||||
resp,
|
||||
settings.get_shared_bytes(),
|
||||
Rc::clone(settings),
|
||||
),
|
||||
flags: EntryFlags::empty(),
|
||||
task,
|
||||
recv,
|
||||
payload: psender,
|
||||
stream: H2Writer::new(resp, settings),
|
||||
flags: EntryFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
|
||||
#![cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(redundant_field_names)
|
||||
)]
|
||||
|
||||
use std::{cmp, io};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{Async, Poll};
|
||||
use http2::server::SendResponse;
|
||||
use http2::{Reason, SendStream};
|
||||
use modhttp::Response;
|
||||
use std::rc::Rc;
|
||||
use std::{cmp, io};
|
||||
|
||||
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
|
||||
use http::{HttpTryFrom, Version};
|
||||
|
||||
use super::encoding::ContentEncoder;
|
||||
use super::helpers;
|
||||
use super::settings::WorkerSettings;
|
||||
use super::shared::SharedBytes;
|
||||
use super::message::Request;
|
||||
use super::output::{Output, ResponseInfo, ResponseLength};
|
||||
use super::settings::ServiceConfig;
|
||||
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
|
||||
use body::{Binary, Body};
|
||||
use header::ContentEncoding;
|
||||
use httprequest::HttpInnerMessage;
|
||||
use http::header::{
|
||||
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
|
||||
};
|
||||
use http::{HttpTryFrom, Method, Version};
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
const CHUNK_SIZE: usize = 16_384;
|
||||
@@ -35,27 +38,23 @@ bitflags! {
|
||||
pub(crate) struct H2Writer<H: 'static> {
|
||||
respond: SendResponse<Bytes>,
|
||||
stream: Option<SendStream<Bytes>>,
|
||||
encoder: ContentEncoder,
|
||||
flags: Flags,
|
||||
written: u64,
|
||||
buffer: SharedBytes,
|
||||
buffer: Output,
|
||||
buffer_capacity: usize,
|
||||
settings: Rc<WorkerSettings<H>>,
|
||||
settings: ServiceConfig<H>,
|
||||
}
|
||||
|
||||
impl<H: 'static> H2Writer<H> {
|
||||
pub fn new(
|
||||
respond: SendResponse<Bytes>, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
|
||||
) -> H2Writer<H> {
|
||||
pub fn new(respond: SendResponse<Bytes>, settings: ServiceConfig<H>) -> H2Writer<H> {
|
||||
H2Writer {
|
||||
respond,
|
||||
settings,
|
||||
stream: None,
|
||||
encoder: ContentEncoder::empty(buf.clone()),
|
||||
flags: Flags::empty(),
|
||||
written: 0,
|
||||
buffer: buf,
|
||||
buffer: Output::Buffer(settings.get_bytes()),
|
||||
buffer_capacity: 0,
|
||||
respond,
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,70 +65,103 @@ impl<H: 'static> H2Writer<H> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: 'static> Drop for H2Writer<H> {
|
||||
fn drop(&mut self) {
|
||||
self.settings.release_bytes(self.buffer.take());
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: 'static> Writer for H2Writer<H> {
|
||||
fn written(&self) -> u64 {
|
||||
self.written
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_date(&self, dst: &mut BytesMut) {
|
||||
self.settings.set_date(dst)
|
||||
fn set_date(&mut self) {
|
||||
self.settings.set_date(self.buffer.as_mut(), true)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn buffer(&self) -> &mut BytesMut {
|
||||
self.buffer.get_mut()
|
||||
fn buffer(&mut self) -> &mut BytesMut {
|
||||
self.buffer.as_mut()
|
||||
}
|
||||
|
||||
fn start(
|
||||
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
|
||||
encoding: ContentEncoding,
|
||||
&mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding,
|
||||
) -> io::Result<WriterState> {
|
||||
// prepare response
|
||||
self.flags.insert(Flags::STARTED);
|
||||
self.encoder =
|
||||
ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
|
||||
if let Body::Empty = *msg.body() {
|
||||
self.flags.insert(Flags::EOF);
|
||||
let mut info = ResponseInfo::new(req.inner.method == Method::HEAD);
|
||||
self.buffer.for_server(&mut info, &req.inner, msg, encoding);
|
||||
|
||||
let mut has_date = false;
|
||||
let mut resp = Response::new(());
|
||||
let mut len_is_set = false;
|
||||
*resp.status_mut() = msg.status();
|
||||
*resp.version_mut() = Version::HTTP_2;
|
||||
for (key, value) in msg.headers().iter() {
|
||||
match *key {
|
||||
// http2 specific
|
||||
CONNECTION | TRANSFER_ENCODING => continue,
|
||||
CONTENT_ENCODING => if encoding != ContentEncoding::Identity {
|
||||
continue;
|
||||
},
|
||||
CONTENT_LENGTH => match info.length {
|
||||
ResponseLength::None => (),
|
||||
ResponseLength::Zero => {
|
||||
len_is_set = true;
|
||||
}
|
||||
_ => continue,
|
||||
},
|
||||
DATE => has_date = true,
|
||||
_ => (),
|
||||
}
|
||||
resp.headers_mut().append(key, value.clone());
|
||||
}
|
||||
|
||||
// http2 specific
|
||||
msg.headers_mut().remove(CONNECTION);
|
||||
msg.headers_mut().remove(TRANSFER_ENCODING);
|
||||
|
||||
// using helpers::date is quite a lot faster
|
||||
if !msg.headers().contains_key(DATE) {
|
||||
// set date header
|
||||
if !has_date {
|
||||
let mut bytes = BytesMut::with_capacity(29);
|
||||
self.settings.set_date_simple(&mut bytes);
|
||||
msg.headers_mut()
|
||||
self.settings.set_date(&mut bytes, false);
|
||||
resp.headers_mut()
|
||||
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap());
|
||||
}
|
||||
|
||||
let body = msg.replace_body(Body::Empty);
|
||||
match body {
|
||||
Body::Binary(ref bytes) => {
|
||||
// content length
|
||||
match info.length {
|
||||
ResponseLength::Zero => {
|
||||
if !len_is_set {
|
||||
resp.headers_mut()
|
||||
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
|
||||
}
|
||||
self.flags.insert(Flags::EOF);
|
||||
}
|
||||
ResponseLength::Length(len) => {
|
||||
let mut val = BytesMut::new();
|
||||
helpers::convert_usize(bytes.len(), &mut val);
|
||||
helpers::convert_usize(len, &mut val);
|
||||
let l = val.len();
|
||||
msg.headers_mut().insert(
|
||||
resp.headers_mut().insert(
|
||||
CONTENT_LENGTH,
|
||||
HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(),
|
||||
);
|
||||
}
|
||||
Body::Empty => {
|
||||
msg.headers_mut()
|
||||
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
|
||||
ResponseLength::Length64(len) => {
|
||||
let l = format!("{}", len);
|
||||
resp.headers_mut()
|
||||
.insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap());
|
||||
}
|
||||
ResponseLength::None => {
|
||||
self.flags.insert(Flags::EOF);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let mut resp = Response::new(());
|
||||
*resp.status_mut() = msg.status();
|
||||
*resp.version_mut() = Version::HTTP_2;
|
||||
for (key, value) in msg.headers().iter() {
|
||||
resp.headers_mut().insert(key, value.clone());
|
||||
if let Some(ce) = info.content_encoding {
|
||||
resp.headers_mut()
|
||||
.insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap());
|
||||
}
|
||||
|
||||
trace!("Response: {:?}", resp);
|
||||
|
||||
match self
|
||||
.respond
|
||||
.send_response(resp, self.flags.contains(Flags::EOF))
|
||||
@@ -138,17 +170,19 @@ impl<H: 'static> Writer for H2Writer<H> {
|
||||
Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")),
|
||||
}
|
||||
|
||||
trace!("Response: {:?}", msg);
|
||||
|
||||
let body = msg.replace_body(Body::Empty);
|
||||
if let Body::Binary(bytes) = body {
|
||||
self.flags.insert(Flags::EOF);
|
||||
self.written = bytes.len() as u64;
|
||||
self.encoder.write(bytes)?;
|
||||
if let Some(ref mut stream) = self.stream {
|
||||
self.flags.insert(Flags::RESERVED);
|
||||
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
|
||||
if bytes.is_empty() {
|
||||
Ok(WriterState::Done)
|
||||
} else {
|
||||
self.flags.insert(Flags::EOF);
|
||||
self.buffer.write(bytes.as_ref())?;
|
||||
if let Some(ref mut stream) = self.stream {
|
||||
self.flags.insert(Flags::RESERVED);
|
||||
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
|
||||
}
|
||||
Ok(WriterState::Pause)
|
||||
}
|
||||
Ok(WriterState::Pause)
|
||||
} else {
|
||||
msg.replace_body(body);
|
||||
self.buffer_capacity = msg.write_buffer_capacity();
|
||||
@@ -156,16 +190,14 @@ impl<H: 'static> Writer for H2Writer<H> {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, payload: Binary) -> io::Result<WriterState> {
|
||||
self.written = payload.len() as u64;
|
||||
|
||||
fn write(&mut self, payload: &Binary) -> io::Result<WriterState> {
|
||||
if !self.flags.contains(Flags::DISCONNECTED) {
|
||||
if self.flags.contains(Flags::STARTED) {
|
||||
// TODO: add warning, write after EOF
|
||||
self.encoder.write(payload)?;
|
||||
self.buffer.write(payload.as_ref())?;
|
||||
} else {
|
||||
// might be response for EXCEPT
|
||||
self.buffer.extend_from_slice(payload.as_ref())
|
||||
error!("Not supported");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,10 +209,8 @@ impl<H: 'static> Writer for H2Writer<H> {
|
||||
}
|
||||
|
||||
fn write_eof(&mut self) -> io::Result<WriterState> {
|
||||
self.encoder.write_eof()?;
|
||||
|
||||
self.flags.insert(Flags::EOF);
|
||||
if !self.encoder.is_eof() {
|
||||
if !self.buffer.write_eof()? {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Last payload item, but eof is not reached",
|
||||
@@ -221,14 +251,18 @@ impl<H: 'static> Writer for H2Writer<H> {
|
||||
let cap = cmp::min(self.buffer.len(), CHUNK_SIZE);
|
||||
stream.reserve_capacity(cap);
|
||||
} else {
|
||||
if eof {
|
||||
stream.reserve_capacity(0);
|
||||
continue;
|
||||
}
|
||||
self.flags.remove(Flags::RESERVED);
|
||||
return Ok(Async::NotReady);
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady)
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
}
|
||||
|
||||
208
src/server/handler.rs
Normal file
208
src/server/handler.rs
Normal file
@@ -0,0 +1,208 @@
|
||||
use futures::{Async, Future, Poll};
|
||||
|
||||
use super::message::Request;
|
||||
use super::Writer;
|
||||
use error::Error;
|
||||
|
||||
/// Low level http request handler
|
||||
#[allow(unused_variables)]
|
||||
pub trait HttpHandler: 'static {
|
||||
/// Request handling task
|
||||
type Task: HttpHandlerTask;
|
||||
|
||||
/// Handle request
|
||||
fn handle(&self, req: Request) -> Result<Self::Task, Request>;
|
||||
}
|
||||
|
||||
impl HttpHandler for Box<HttpHandler<Task = Box<HttpHandlerTask>>> {
|
||||
type Task = Box<HttpHandlerTask>;
|
||||
|
||||
fn handle(&self, req: Request) -> Result<Box<HttpHandlerTask>, Request> {
|
||||
self.as_ref().handle(req)
|
||||
}
|
||||
}
|
||||
|
||||
/// Low level http request handler
|
||||
pub trait HttpHandlerTask {
|
||||
/// Poll task, this method is used before or after *io* object is available
|
||||
fn poll_completed(&mut self) -> Poll<(), Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
/// Poll task when *io* object is available
|
||||
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error>;
|
||||
|
||||
/// Connection is disconnected
|
||||
fn disconnected(&mut self) {}
|
||||
}
|
||||
|
||||
impl HttpHandlerTask for Box<HttpHandlerTask> {
|
||||
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
|
||||
self.as_mut().poll_io(io)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct HttpHandlerTaskFut<T: HttpHandlerTask> {
|
||||
task: T,
|
||||
}
|
||||
|
||||
impl<T: HttpHandlerTask> HttpHandlerTaskFut<T> {
|
||||
pub(crate) fn new(task: T) -> Self {
|
||||
Self { task }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HttpHandlerTask> Future for HttpHandlerTaskFut<T> {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
self.task.poll_completed().map_err(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Conversion helper trait
|
||||
pub trait IntoHttpHandler {
|
||||
/// The associated type which is result of conversion.
|
||||
type Handler: HttpHandler;
|
||||
|
||||
/// Convert into `HttpHandler` object.
|
||||
fn into_handler(self) -> Self::Handler;
|
||||
}
|
||||
|
||||
impl<T: HttpHandler> IntoHttpHandler for T {
|
||||
type Handler = T;
|
||||
|
||||
fn into_handler(self) -> Self::Handler {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoHttpHandler> IntoHttpHandler for Vec<T> {
|
||||
type Handler = VecHttpHandler<T::Handler>;
|
||||
|
||||
fn into_handler(self) -> Self::Handler {
|
||||
VecHttpHandler(self.into_iter().map(|item| item.into_handler()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct VecHttpHandler<H: HttpHandler>(Vec<H>);
|
||||
|
||||
impl<H: HttpHandler> HttpHandler for VecHttpHandler<H> {
|
||||
type Task = H::Task;
|
||||
|
||||
fn handle(&self, mut req: Request) -> Result<Self::Task, Request> {
|
||||
for h in &self.0 {
|
||||
req = match h.handle(req) {
|
||||
Ok(task) => return Ok(task),
|
||||
Err(e) => e,
|
||||
};
|
||||
}
|
||||
Err(req)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! http_handler ({$EN:ident, $(($n:tt, $T:ident)),+} => {
|
||||
impl<$($T: HttpHandler,)+> HttpHandler for ($($T,)+) {
|
||||
type Task = $EN<$($T,)+>;
|
||||
|
||||
fn handle(&self, mut req: Request) -> Result<Self::Task, Request> {
|
||||
$(
|
||||
req = match self.$n.handle(req) {
|
||||
Ok(task) => return Ok($EN::$T(task)),
|
||||
Err(e) => e,
|
||||
};
|
||||
)+
|
||||
Err(req)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub enum $EN<$($T: HttpHandler,)+> {
|
||||
$($T ($T::Task),)+
|
||||
}
|
||||
|
||||
impl<$($T: HttpHandler,)+> HttpHandlerTask for $EN<$($T,)+>
|
||||
{
|
||||
fn poll_completed(&mut self) -> Poll<(), Error> {
|
||||
match self {
|
||||
$($EN :: $T(ref mut task) => task.poll_completed(),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
|
||||
match self {
|
||||
$($EN::$T(ref mut task) => task.poll_io(io),)+
|
||||
}
|
||||
}
|
||||
|
||||
/// Connection is disconnected
|
||||
fn disconnected(&mut self) {
|
||||
match self {
|
||||
$($EN::$T(ref mut task) => task.disconnected(),)+
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
http_handler!(HttpHandlerTask1, (0, A));
|
||||
http_handler!(HttpHandlerTask2, (0, A), (1, B));
|
||||
http_handler!(HttpHandlerTask3, (0, A), (1, B), (2, C));
|
||||
http_handler!(HttpHandlerTask4, (0, A), (1, B), (2, C), (3, D));
|
||||
http_handler!(HttpHandlerTask5, (0, A), (1, B), (2, C), (3, D), (4, E));
|
||||
http_handler!(
|
||||
HttpHandlerTask6,
|
||||
(0, A),
|
||||
(1, B),
|
||||
(2, C),
|
||||
(3, D),
|
||||
(4, E),
|
||||
(5, F)
|
||||
);
|
||||
http_handler!(
|
||||
HttpHandlerTask7,
|
||||
(0, A),
|
||||
(1, B),
|
||||
(2, C),
|
||||
(3, D),
|
||||
(4, E),
|
||||
(5, F),
|
||||
(6, G)
|
||||
);
|
||||
http_handler!(
|
||||
HttpHandlerTask8,
|
||||
(0, A),
|
||||
(1, B),
|
||||
(2, C),
|
||||
(3, D),
|
||||
(4, E),
|
||||
(5, F),
|
||||
(6, G),
|
||||
(7, H)
|
||||
);
|
||||
http_handler!(
|
||||
HttpHandlerTask9,
|
||||
(0, A),
|
||||
(1, B),
|
||||
(2, C),
|
||||
(3, D),
|
||||
(4, E),
|
||||
(5, F),
|
||||
(6, G),
|
||||
(7, H),
|
||||
(8, I)
|
||||
);
|
||||
http_handler!(
|
||||
HttpHandlerTask10,
|
||||
(0, A),
|
||||
(1, B),
|
||||
(2, C),
|
||||
(3, D),
|
||||
(4, E),
|
||||
(5, F),
|
||||
(6, G),
|
||||
(7, H),
|
||||
(8, I),
|
||||
(9, J)
|
||||
);
|
||||
@@ -1,102 +1,17 @@
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use http::Version;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
use std::{mem, ptr, slice};
|
||||
|
||||
use httprequest::HttpInnerMessage;
|
||||
|
||||
/// Internal use only! unsafe
|
||||
pub(crate) struct SharedMessagePool(RefCell<VecDeque<Rc<HttpInnerMessage>>>);
|
||||
|
||||
impl SharedMessagePool {
|
||||
pub fn new() -> SharedMessagePool {
|
||||
SharedMessagePool(RefCell::new(VecDeque::with_capacity(128)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self) -> Rc<HttpInnerMessage> {
|
||||
if let Some(msg) = self.0.borrow_mut().pop_front() {
|
||||
msg
|
||||
} else {
|
||||
Rc::new(HttpInnerMessage::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn release(&self, mut msg: Rc<HttpInnerMessage>) {
|
||||
let v = &mut self.0.borrow_mut();
|
||||
if v.len() < 128 {
|
||||
Rc::get_mut(&mut msg).unwrap().reset();
|
||||
v.push_front(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SharedHttpInnerMessage(
|
||||
Option<Rc<HttpInnerMessage>>,
|
||||
Option<Rc<SharedMessagePool>>,
|
||||
);
|
||||
|
||||
impl Drop for SharedHttpInnerMessage {
|
||||
fn drop(&mut self) {
|
||||
if let Some(ref pool) = self.1 {
|
||||
if let Some(msg) = self.0.take() {
|
||||
if Rc::strong_count(&msg) == 1 {
|
||||
pool.release(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for SharedHttpInnerMessage {
|
||||
fn clone(&self) -> SharedHttpInnerMessage {
|
||||
SharedHttpInnerMessage(self.0.clone(), self.1.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SharedHttpInnerMessage {
|
||||
fn default() -> SharedHttpInnerMessage {
|
||||
SharedHttpInnerMessage(Some(Rc::new(HttpInnerMessage::default())), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl SharedHttpInnerMessage {
|
||||
pub fn from_message(msg: HttpInnerMessage) -> SharedHttpInnerMessage {
|
||||
SharedHttpInnerMessage(Some(Rc::new(msg)), None)
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
msg: Rc<HttpInnerMessage>, pool: Rc<SharedMessagePool>,
|
||||
) -> SharedHttpInnerMessage {
|
||||
SharedHttpInnerMessage(Some(msg), Some(pool))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(mutable_transmutes)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
|
||||
pub fn get_mut(&self) -> &mut HttpInnerMessage {
|
||||
let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref();
|
||||
unsafe { &mut *(r as *const _ as *mut _) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||
pub fn get_ref(&self) -> &HttpInnerMessage {
|
||||
self.0.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
|
||||
2021222324252627282930313233343536373839\
|
||||
4041424344454647484950515253545556575859\
|
||||
6061626364656667686970717273747576777879\
|
||||
8081828384858687888990919293949596979899";
|
||||
|
||||
pub(crate) const STATUS_LINE_BUF_SIZE: usize = 13;
|
||||
|
||||
pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) {
|
||||
let mut buf: [u8; 13] = [
|
||||
let mut buf: [u8; STATUS_LINE_BUF_SIZE] = [
|
||||
b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', b' ', b' ', b' ', b' ', b' ',
|
||||
];
|
||||
match version {
|
||||
@@ -114,20 +29,24 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM
|
||||
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
|
||||
let four = n > 999;
|
||||
|
||||
// decode 2 more chars, if > 2 chars
|
||||
let d1 = (n % 100) << 1;
|
||||
n /= 100;
|
||||
curr -= 2;
|
||||
unsafe {
|
||||
// decode 2 more chars, if > 2 chars
|
||||
let d1 = (n % 100) << 1;
|
||||
n /= 100;
|
||||
curr -= 2;
|
||||
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2);
|
||||
}
|
||||
|
||||
// decode last 1 or 2 chars
|
||||
if n < 10 {
|
||||
curr -= 1;
|
||||
// decode last 1 or 2 chars
|
||||
if n < 10 {
|
||||
curr -= 1;
|
||||
unsafe {
|
||||
*buf_ptr.offset(curr) = (n as u8) + b'0';
|
||||
} else {
|
||||
let d1 = n << 1;
|
||||
curr -= 2;
|
||||
}
|
||||
} else {
|
||||
let d1 = n << 1;
|
||||
curr -= 2;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(
|
||||
lut_ptr.offset(d1 as isize),
|
||||
buf_ptr.offset(curr),
|
||||
@@ -159,7 +78,7 @@ pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
|
||||
let d1 = n << 1;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(
|
||||
DEC_DIGITS_LUT.as_ptr().offset(d1 as isize),
|
||||
DEC_DIGITS_LUT.as_ptr().add(d1),
|
||||
buf.as_mut_ptr().offset(18),
|
||||
2,
|
||||
);
|
||||
@@ -175,7 +94,7 @@ pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
|
||||
n /= 100;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(
|
||||
DEC_DIGITS_LUT.as_ptr().offset(d1 as isize),
|
||||
DEC_DIGITS_LUT.as_ptr().add(d1),
|
||||
buf.as_mut_ptr().offset(19),
|
||||
2,
|
||||
)
|
||||
@@ -199,37 +118,43 @@ pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
|
||||
let buf_ptr = buf.as_mut_ptr();
|
||||
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
|
||||
|
||||
unsafe {
|
||||
// eagerly decode 4 characters at a time
|
||||
while n >= 10_000 {
|
||||
let rem = (n % 10_000) as isize;
|
||||
n /= 10_000;
|
||||
// eagerly decode 4 characters at a time
|
||||
while n >= 10_000 {
|
||||
let rem = (n % 10_000) as isize;
|
||||
n /= 10_000;
|
||||
|
||||
let d1 = (rem / 100) << 1;
|
||||
let d2 = (rem % 100) << 1;
|
||||
curr -= 4;
|
||||
let d1 = (rem / 100) << 1;
|
||||
let d2 = (rem % 100) << 1;
|
||||
curr -= 4;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
|
||||
ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2);
|
||||
}
|
||||
}
|
||||
|
||||
// if we reach here numbers are <= 9999, so at most 4 chars long
|
||||
let mut n = n as isize; // possibly reduce 64bit math
|
||||
// if we reach here numbers are <= 9999, so at most 4 chars long
|
||||
let mut n = n as isize; // possibly reduce 64bit math
|
||||
|
||||
// decode 2 more chars, if > 2 chars
|
||||
if n >= 100 {
|
||||
let d1 = (n % 100) << 1;
|
||||
n /= 100;
|
||||
curr -= 2;
|
||||
// decode 2 more chars, if > 2 chars
|
||||
if n >= 100 {
|
||||
let d1 = (n % 100) << 1;
|
||||
n /= 100;
|
||||
curr -= 2;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
|
||||
}
|
||||
}
|
||||
|
||||
// decode last 1 or 2 chars
|
||||
if n < 10 {
|
||||
curr -= 1;
|
||||
// decode last 1 or 2 chars
|
||||
if n < 10 {
|
||||
curr -= 1;
|
||||
unsafe {
|
||||
*buf_ptr.offset(curr) = (n as u8) + b'0';
|
||||
} else {
|
||||
let d1 = n << 1;
|
||||
curr -= 2;
|
||||
}
|
||||
} else {
|
||||
let d1 = n << 1;
|
||||
curr -= 2;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
|
||||
}
|
||||
}
|
||||
|
||||
580
src/server/http.rs
Normal file
580
src/server/http.rs
Normal file
@@ -0,0 +1,580 @@
|
||||
use std::{fmt, io, mem, net};
|
||||
|
||||
use actix::{Addr, System};
|
||||
use actix_net::server::Server;
|
||||
use actix_net::service::NewService;
|
||||
use actix_net::ssl;
|
||||
|
||||
use net2::TcpBuilder;
|
||||
use num_cpus;
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
use native_tls::TlsAcceptor;
|
||||
|
||||
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
||||
use openssl::ssl::SslAcceptorBuilder;
|
||||
|
||||
#[cfg(feature = "rust-tls")]
|
||||
use rustls::ServerConfig;
|
||||
|
||||
use super::acceptor::{AcceptorServiceFactory, DefaultAcceptor};
|
||||
use super::builder::{HttpServiceBuilder, ServiceProvider};
|
||||
use super::{IntoHttpHandler, KeepAlive};
|
||||
|
||||
struct Socket {
|
||||
scheme: &'static str,
|
||||
lst: net::TcpListener,
|
||||
addr: net::SocketAddr,
|
||||
handler: Box<ServiceProvider>,
|
||||
}
|
||||
|
||||
/// An HTTP Server
|
||||
///
|
||||
/// By default it serves HTTP2 when HTTPs is enabled,
|
||||
/// in order to change it, use `ServerFlags` that can be provided
|
||||
/// to acceptor service.
|
||||
pub struct HttpServer<H, F>
|
||||
where
|
||||
H: IntoHttpHandler + 'static,
|
||||
F: Fn() -> H + Send + Clone,
|
||||
{
|
||||
pub(super) factory: F,
|
||||
pub(super) host: Option<String>,
|
||||
pub(super) keep_alive: KeepAlive,
|
||||
pub(super) client_timeout: u64,
|
||||
pub(super) client_shutdown: u64,
|
||||
backlog: i32,
|
||||
threads: usize,
|
||||
exit: bool,
|
||||
shutdown_timeout: u16,
|
||||
no_http2: bool,
|
||||
no_signals: bool,
|
||||
maxconn: usize,
|
||||
maxconnrate: usize,
|
||||
sockets: Vec<Socket>,
|
||||
}
|
||||
|
||||
impl<H, F> HttpServer<H, F>
|
||||
where
|
||||
H: IntoHttpHandler + 'static,
|
||||
F: Fn() -> H + Send + Clone + 'static,
|
||||
{
|
||||
/// Create new http server with application factory
|
||||
pub fn new(factory: F) -> HttpServer<H, F> {
|
||||
HttpServer {
|
||||
factory,
|
||||
threads: num_cpus::get(),
|
||||
host: None,
|
||||
backlog: 2048,
|
||||
keep_alive: KeepAlive::Timeout(5),
|
||||
shutdown_timeout: 30,
|
||||
exit: false,
|
||||
no_http2: false,
|
||||
no_signals: false,
|
||||
maxconn: 25_600,
|
||||
maxconnrate: 256,
|
||||
client_timeout: 5000,
|
||||
client_shutdown: 5000,
|
||||
sockets: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set number of workers to start.
|
||||
///
|
||||
/// By default http server uses number of available logical cpu as threads
|
||||
/// count.
|
||||
pub fn workers(mut self, num: usize) -> Self {
|
||||
self.threads = num;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the maximum number of pending connections.
|
||||
///
|
||||
/// This refers to the number of clients that can be waiting to be served.
|
||||
/// Exceeding this number results in the client getting an error when
|
||||
/// attempting to connect. It should only affect servers under significant
|
||||
/// load.
|
||||
///
|
||||
/// Generally set in the 64-2048 range. Default value is 2048.
|
||||
///
|
||||
/// This method should be called before `bind()` method call.
|
||||
pub fn backlog(mut self, num: i32) -> Self {
|
||||
self.backlog = num;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum per-worker number of concurrent connections.
|
||||
///
|
||||
/// All socket listeners will stop accepting connections when this limit is reached
|
||||
/// for each worker.
|
||||
///
|
||||
/// By default max connections is set to a 25k.
|
||||
pub fn maxconn(mut self, num: usize) -> Self {
|
||||
self.maxconn = num;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum per-worker concurrent connection establish process.
|
||||
///
|
||||
/// All listeners will stop accepting connections when this limit is reached. It
|
||||
/// can be used to limit the global SSL CPU usage.
|
||||
///
|
||||
/// By default max connections is set to a 256.
|
||||
pub fn maxconnrate(mut self, num: usize) -> Self {
|
||||
self.maxconnrate = num;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set server keep-alive setting.
|
||||
///
|
||||
/// By default keep alive is set to a 5 seconds.
|
||||
pub fn keep_alive<T: Into<KeepAlive>>(mut self, val: T) -> Self {
|
||||
self.keep_alive = val.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set server client timeout in milliseconds for first request.
|
||||
///
|
||||
/// Defines a timeout for reading client request header. If a client does not transmit
|
||||
/// the entire set headers within this time, the request is terminated with
|
||||
/// the 408 (Request Time-out) error.
|
||||
///
|
||||
/// To disable timeout set value to 0.
|
||||
///
|
||||
/// By default client timeout is set to 5000 milliseconds.
|
||||
pub fn client_timeout(mut self, val: u64) -> Self {
|
||||
self.client_timeout = val;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set server connection shutdown timeout in milliseconds.
|
||||
///
|
||||
/// Defines a timeout for shutdown connection. If a shutdown procedure does not complete
|
||||
/// within this time, the request is dropped.
|
||||
///
|
||||
/// To disable timeout set value to 0.
|
||||
///
|
||||
/// By default client timeout is set to 5000 milliseconds.
|
||||
pub fn client_shutdown(mut self, val: u64) -> Self {
|
||||
self.client_shutdown = val;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set server host name.
|
||||
///
|
||||
/// Host name is used by application router aa a hostname for url
|
||||
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
|
||||
/// html#method.host) documentation for more information.
|
||||
pub fn server_hostname(mut self, val: String) -> Self {
|
||||
self.host = Some(val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Stop actix system.
|
||||
///
|
||||
/// `SystemExit` message stops currently running system.
|
||||
pub fn system_exit(mut self) -> Self {
|
||||
self.exit = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable signal handling
|
||||
pub fn disable_signals(mut self) -> Self {
|
||||
self.no_signals = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Timeout for graceful workers shutdown.
|
||||
///
|
||||
/// After receiving a stop signal, workers have this much time to finish
|
||||
/// serving requests. Workers still alive after the timeout are force
|
||||
/// dropped.
|
||||
///
|
||||
/// By default shutdown timeout sets to 30 seconds.
|
||||
pub fn shutdown_timeout(mut self, sec: u16) -> Self {
|
||||
self.shutdown_timeout = sec;
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable `HTTP/2` support
|
||||
pub fn no_http2(mut self) -> Self {
|
||||
self.no_http2 = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Get addresses of bound sockets.
|
||||
pub fn addrs(&self) -> Vec<net::SocketAddr> {
|
||||
self.sockets.iter().map(|s| s.addr).collect()
|
||||
}
|
||||
|
||||
/// Get addresses of bound sockets and the scheme for it.
|
||||
///
|
||||
/// This is useful when the server is bound from different sources
|
||||
/// with some sockets listening on http and some listening on https
|
||||
/// and the user should be presented with an enumeration of which
|
||||
/// socket requires which protocol.
|
||||
pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> {
|
||||
self.sockets.iter().map(|s| (s.addr, s.scheme)).collect()
|
||||
}
|
||||
|
||||
/// Use listener for accepting incoming connection requests
|
||||
///
|
||||
/// HttpServer does not change any configuration for TcpListener,
|
||||
/// it needs to be configured before passing it to listen() method.
|
||||
pub fn listen(mut self, lst: net::TcpListener) -> Self {
|
||||
let addr = lst.local_addr().unwrap();
|
||||
self.sockets.push(Socket {
|
||||
lst,
|
||||
addr,
|
||||
scheme: "http",
|
||||
handler: Box::new(HttpServiceBuilder::new(
|
||||
self.factory.clone(),
|
||||
DefaultAcceptor,
|
||||
)),
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Use listener for accepting incoming connection requests
|
||||
pub fn listen_with<A>(mut self, lst: net::TcpListener, acceptor: A) -> Self
|
||||
where
|
||||
A: AcceptorServiceFactory,
|
||||
<A::NewService as NewService>::InitError: fmt::Debug,
|
||||
{
|
||||
let addr = lst.local_addr().unwrap();
|
||||
self.sockets.push(Socket {
|
||||
lst,
|
||||
addr,
|
||||
scheme: "https",
|
||||
handler: Box::new(HttpServiceBuilder::new(self.factory.clone(), acceptor)),
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
/// Use listener for accepting incoming tls connection requests
|
||||
///
|
||||
/// HttpServer does not change any configuration for TcpListener,
|
||||
/// it needs to be configured before passing it to listen() method.
|
||||
pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self {
|
||||
use actix_net::service::NewServiceExt;
|
||||
|
||||
self.listen_with(lst, move || {
|
||||
ssl::NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
||||
/// Use listener for accepting incoming tls connection requests
|
||||
///
|
||||
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||
pub fn listen_ssl(
|
||||
self, lst: net::TcpListener, builder: SslAcceptorBuilder,
|
||||
) -> io::Result<Self> {
|
||||
use super::{openssl_acceptor_with_flags, ServerFlags};
|
||||
use actix_net::service::NewServiceExt;
|
||||
|
||||
let flags = if self.no_http2 {
|
||||
ServerFlags::HTTP1
|
||||
} else {
|
||||
ServerFlags::HTTP1 | ServerFlags::HTTP2
|
||||
};
|
||||
|
||||
let acceptor = openssl_acceptor_with_flags(builder, flags)?;
|
||||
Ok(self.listen_with(lst, move || {
|
||||
ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ())
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rust-tls")]
|
||||
/// Use listener for accepting incoming tls connection requests
|
||||
///
|
||||
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||
pub fn listen_rustls(self, lst: net::TcpListener, config: ServerConfig) -> Self {
|
||||
use super::{RustlsAcceptor, ServerFlags};
|
||||
use actix_net::service::NewServiceExt;
|
||||
|
||||
// alpn support
|
||||
let flags = if self.no_http2 {
|
||||
ServerFlags::HTTP1
|
||||
} else {
|
||||
ServerFlags::HTTP1 | ServerFlags::HTTP2
|
||||
};
|
||||
|
||||
self.listen_with(lst, move || {
|
||||
RustlsAcceptor::with_flags(config.clone(), flags).map_err(|_| ())
|
||||
})
|
||||
}
|
||||
|
||||
/// The socket address to bind
|
||||
///
|
||||
/// To bind multiple addresses this method can be called multiple times.
|
||||
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> {
|
||||
let sockets = self.bind2(addr)?;
|
||||
|
||||
for lst in sockets {
|
||||
self = self.listen(lst);
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Start listening for incoming connections with supplied acceptor.
|
||||
#[doc(hidden)]
|
||||
#[cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(needless_pass_by_value)
|
||||
)]
|
||||
pub fn bind_with<S, A>(mut self, addr: S, acceptor: A) -> io::Result<Self>
|
||||
where
|
||||
S: net::ToSocketAddrs,
|
||||
A: AcceptorServiceFactory,
|
||||
<A::NewService as NewService>::InitError: fmt::Debug,
|
||||
{
|
||||
let sockets = self.bind2(addr)?;
|
||||
|
||||
for lst in sockets {
|
||||
let addr = lst.local_addr().unwrap();
|
||||
self.sockets.push(Socket {
|
||||
lst,
|
||||
addr,
|
||||
scheme: "https",
|
||||
handler: Box::new(HttpServiceBuilder::new(
|
||||
self.factory.clone(),
|
||||
acceptor.clone(),
|
||||
)),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn bind2<S: net::ToSocketAddrs>(
|
||||
&self, addr: S,
|
||||
) -> io::Result<Vec<net::TcpListener>> {
|
||||
let mut err = None;
|
||||
let mut succ = false;
|
||||
let mut sockets = Vec::new();
|
||||
for addr in addr.to_socket_addrs()? {
|
||||
match create_tcp_listener(addr, self.backlog) {
|
||||
Ok(lst) => {
|
||||
succ = true;
|
||||
sockets.push(lst);
|
||||
}
|
||||
Err(e) => err = Some(e),
|
||||
}
|
||||
}
|
||||
|
||||
if !succ {
|
||||
if let Some(e) = err.take() {
|
||||
Err(e)
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Can not bind to address.",
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(sockets)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
/// The ssl socket address to bind
|
||||
///
|
||||
/// To bind multiple addresses this method can be called multiple times.
|
||||
pub fn bind_tls<S: net::ToSocketAddrs>(
|
||||
self, addr: S, acceptor: TlsAcceptor,
|
||||
) -> io::Result<Self> {
|
||||
use actix_net::service::NewServiceExt;
|
||||
use actix_net::ssl::NativeTlsAcceptor;
|
||||
|
||||
self.bind_with(addr, move || {
|
||||
NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
||||
/// Start listening for incoming tls connections.
|
||||
///
|
||||
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||
pub fn bind_ssl<S>(self, addr: S, builder: SslAcceptorBuilder) -> io::Result<Self>
|
||||
where
|
||||
S: net::ToSocketAddrs,
|
||||
{
|
||||
use super::{openssl_acceptor_with_flags, ServerFlags};
|
||||
use actix_net::service::NewServiceExt;
|
||||
|
||||
// alpn support
|
||||
let flags = if self.no_http2 {
|
||||
ServerFlags::HTTP1
|
||||
} else {
|
||||
ServerFlags::HTTP1 | ServerFlags::HTTP2
|
||||
};
|
||||
|
||||
let acceptor = openssl_acceptor_with_flags(builder, flags)?;
|
||||
self.bind_with(addr, move || {
|
||||
ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "rust-tls")]
|
||||
/// Start listening for incoming tls connections.
|
||||
///
|
||||
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||
pub fn bind_rustls<S: net::ToSocketAddrs>(
|
||||
self, addr: S, builder: ServerConfig,
|
||||
) -> io::Result<Self> {
|
||||
use super::{RustlsAcceptor, ServerFlags};
|
||||
use actix_net::service::NewServiceExt;
|
||||
|
||||
// alpn support
|
||||
let flags = if self.no_http2 {
|
||||
ServerFlags::HTTP1
|
||||
} else {
|
||||
ServerFlags::HTTP1 | ServerFlags::HTTP2
|
||||
};
|
||||
|
||||
self.bind_with(addr, move || {
|
||||
RustlsAcceptor::with_flags(builder.clone(), flags).map_err(|_| ())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: IntoHttpHandler, F: Fn() -> H + Send + Clone> HttpServer<H, F> {
|
||||
/// Start listening for incoming connections.
|
||||
///
|
||||
/// This method starts number of http workers in separate threads.
|
||||
/// For each address this method starts separate thread which does
|
||||
/// `accept()` in a loop.
|
||||
///
|
||||
/// This methods panics if no socket addresses get bound.
|
||||
///
|
||||
/// This method requires to run within properly configured `Actix` system.
|
||||
///
|
||||
/// ```rust
|
||||
/// extern crate actix_web;
|
||||
/// extern crate actix;
|
||||
/// use actix_web::{server, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let sys = actix::System::new("example"); // <- create Actix system
|
||||
///
|
||||
/// server::new(|| App::new().resource("/", |r| r.h(|_: &_| HttpResponse::Ok())))
|
||||
/// .bind("127.0.0.1:0")
|
||||
/// .expect("Can not bind to 127.0.0.1:0")
|
||||
/// .start();
|
||||
/// # actix::System::current().stop();
|
||||
/// sys.run(); // <- Run actix system, this method starts all async processes
|
||||
/// }
|
||||
/// ```
|
||||
pub fn start(mut self) -> Addr<Server> {
|
||||
ssl::max_concurrent_ssl_connect(self.maxconnrate);
|
||||
|
||||
let mut srv = Server::new()
|
||||
.workers(self.threads)
|
||||
.maxconn(self.maxconn)
|
||||
.shutdown_timeout(self.shutdown_timeout);
|
||||
|
||||
srv = if self.exit { srv.system_exit() } else { srv };
|
||||
srv = if self.no_signals {
|
||||
srv.disable_signals()
|
||||
} else {
|
||||
srv
|
||||
};
|
||||
|
||||
let sockets = mem::replace(&mut self.sockets, Vec::new());
|
||||
|
||||
for socket in sockets {
|
||||
let host = self
|
||||
.host
|
||||
.as_ref()
|
||||
.map(|h| h.to_owned())
|
||||
.unwrap_or_else(|| format!("{}", socket.addr));
|
||||
let (secure, client_shutdown) = if socket.scheme == "https" {
|
||||
(true, self.client_shutdown)
|
||||
} else {
|
||||
(false, 0)
|
||||
};
|
||||
srv = socket.handler.register(
|
||||
srv,
|
||||
socket.lst,
|
||||
host,
|
||||
socket.addr,
|
||||
self.keep_alive,
|
||||
secure,
|
||||
self.client_timeout,
|
||||
client_shutdown,
|
||||
);
|
||||
}
|
||||
srv.start()
|
||||
}
|
||||
|
||||
/// Spawn new thread and start listening for incoming connections.
|
||||
///
|
||||
/// This method spawns new thread and starts new actix system. Other than
|
||||
/// that it is similar to `start()` method. This method blocks.
|
||||
///
|
||||
/// This methods panics if no socket addresses get bound.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # extern crate futures;
|
||||
/// # extern crate actix_web;
|
||||
/// # use futures::Future;
|
||||
/// use actix_web::*;
|
||||
///
|
||||
/// fn main() {
|
||||
/// HttpServer::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok())))
|
||||
/// .bind("127.0.0.1:0")
|
||||
/// .expect("Can not bind to 127.0.0.1:0")
|
||||
/// .run();
|
||||
/// }
|
||||
/// ```
|
||||
pub fn run(self) {
|
||||
let sys = System::new("http-server");
|
||||
self.start();
|
||||
sys.run();
|
||||
}
|
||||
|
||||
/// Register current http server as actix-net's server service
|
||||
pub fn register(self, mut srv: Server) -> Server {
|
||||
for socket in self.sockets {
|
||||
let host = self
|
||||
.host
|
||||
.as_ref()
|
||||
.map(|h| h.to_owned())
|
||||
.unwrap_or_else(|| format!("{}", socket.addr));
|
||||
let (secure, client_shutdown) = if socket.scheme == "https" {
|
||||
(true, self.client_shutdown)
|
||||
} else {
|
||||
(false, 0)
|
||||
};
|
||||
srv = socket.handler.register(
|
||||
srv,
|
||||
socket.lst,
|
||||
host,
|
||||
socket.addr,
|
||||
self.keep_alive,
|
||||
secure,
|
||||
self.client_timeout,
|
||||
client_shutdown,
|
||||
);
|
||||
}
|
||||
srv
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tcp_listener(
|
||||
addr: net::SocketAddr, backlog: i32,
|
||||
) -> io::Result<net::TcpListener> {
|
||||
let builder = match addr {
|
||||
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,
|
||||
net::SocketAddr::V6(_) => TcpBuilder::new_v6()?,
|
||||
};
|
||||
builder.reuse_address(true)?;
|
||||
builder.bind(addr)?;
|
||||
Ok(builder.listen(backlog)?)
|
||||
}
|
||||
69
src/server/incoming.rs
Normal file
69
src/server/incoming.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
//! Support for `Stream<Item=T::AsyncReady+AsyncWrite>`, deprecated!
|
||||
use std::{io, net};
|
||||
|
||||
use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message};
|
||||
use futures::{Future, Stream};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use super::channel::{HttpChannel, WrapperStream};
|
||||
use super::handler::{HttpHandler, IntoHttpHandler};
|
||||
use super::http::HttpServer;
|
||||
use super::settings::{ServerSettings, ServiceConfig};
|
||||
|
||||
impl<T: AsyncRead + AsyncWrite + 'static> Message for WrapperStream<T> {
|
||||
type Result = ();
|
||||
}
|
||||
|
||||
impl<H, F> HttpServer<H, F>
|
||||
where
|
||||
H: IntoHttpHandler,
|
||||
F: Fn() -> H + Send + Clone,
|
||||
{
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "0.7.8")]
|
||||
/// Start listening for incoming connections from a stream.
|
||||
///
|
||||
/// This method uses only one thread for handling incoming connections.
|
||||
pub fn start_incoming<T, S>(self, stream: S, secure: bool)
|
||||
where
|
||||
S: Stream<Item = T, Error = io::Error> + 'static,
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
{
|
||||
// set server settings
|
||||
let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
|
||||
let apps = (self.factory)().into_handler();
|
||||
let settings = ServiceConfig::new(
|
||||
apps,
|
||||
self.keep_alive,
|
||||
self.client_timeout,
|
||||
self.client_shutdown,
|
||||
ServerSettings::new(addr, "127.0.0.1:8080", secure),
|
||||
);
|
||||
|
||||
// start server
|
||||
HttpIncoming::create(move |ctx| {
|
||||
ctx.add_message_stream(stream.map_err(|_| ()).map(WrapperStream::new));
|
||||
HttpIncoming { settings }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct HttpIncoming<H: HttpHandler> {
|
||||
settings: ServiceConfig<H>,
|
||||
}
|
||||
|
||||
impl<H: HttpHandler> Actor for HttpIncoming<H> {
|
||||
type Context = Context<Self>;
|
||||
}
|
||||
|
||||
impl<T, H> Handler<WrapperStream<T>> for HttpIncoming<H>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
H: HttpHandler,
|
||||
{
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: WrapperStream<T>, _: &mut Context<Self>) -> Self::Result {
|
||||
Arbiter::spawn(HttpChannel::new(self.settings.clone(), msg).map_err(|_| ()));
|
||||
}
|
||||
}
|
||||
288
src/server/input.rs
Normal file
288
src/server/input.rs
Normal file
@@ -0,0 +1,288 @@
|
||||
use std::io::{self, Write};
|
||||
|
||||
#[cfg(feature = "brotli")]
|
||||
use brotli2::write::BrotliDecoder;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use error::PayloadError;
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::write::{GzDecoder, ZlibDecoder};
|
||||
use header::ContentEncoding;
|
||||
use http::header::{HeaderMap, CONTENT_ENCODING};
|
||||
use payload::{PayloadSender, PayloadStatus, PayloadWriter};
|
||||
|
||||
pub(crate) enum PayloadType {
|
||||
Sender(PayloadSender),
|
||||
Encoding(Box<EncodedPayload>),
|
||||
}
|
||||
|
||||
impl PayloadType {
|
||||
#[cfg(any(feature = "brotli", feature = "flate2"))]
|
||||
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
|
||||
// check content-encoding
|
||||
let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) {
|
||||
if let Ok(enc) = enc.to_str() {
|
||||
ContentEncoding::from(enc)
|
||||
} else {
|
||||
ContentEncoding::Auto
|
||||
}
|
||||
} else {
|
||||
ContentEncoding::Auto
|
||||
};
|
||||
|
||||
match enc {
|
||||
ContentEncoding::Auto | ContentEncoding::Identity => {
|
||||
PayloadType::Sender(sender)
|
||||
}
|
||||
_ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
|
||||
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
|
||||
PayloadType::Sender(sender)
|
||||
}
|
||||
}
|
||||
|
||||
impl PayloadWriter for PayloadType {
|
||||
#[inline]
|
||||
fn set_error(&mut self, err: PayloadError) {
|
||||
match *self {
|
||||
PayloadType::Sender(ref mut sender) => sender.set_error(err),
|
||||
PayloadType::Encoding(ref mut enc) => enc.set_error(err),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn feed_eof(&mut self) {
|
||||
match *self {
|
||||
PayloadType::Sender(ref mut sender) => sender.feed_eof(),
|
||||
PayloadType::Encoding(ref mut enc) => enc.feed_eof(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn feed_data(&mut self, data: Bytes) {
|
||||
match *self {
|
||||
PayloadType::Sender(ref mut sender) => sender.feed_data(data),
|
||||
PayloadType::Encoding(ref mut enc) => enc.feed_data(data),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn need_read(&self) -> PayloadStatus {
|
||||
match *self {
|
||||
PayloadType::Sender(ref sender) => sender.need_read(),
|
||||
PayloadType::Encoding(ref enc) => enc.need_read(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload wrapper with content decompression support
|
||||
pub(crate) struct EncodedPayload {
|
||||
inner: PayloadSender,
|
||||
error: bool,
|
||||
payload: PayloadStream,
|
||||
}
|
||||
|
||||
impl EncodedPayload {
|
||||
pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload {
|
||||
EncodedPayload {
|
||||
inner,
|
||||
error: false,
|
||||
payload: PayloadStream::new(enc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PayloadWriter for EncodedPayload {
|
||||
fn set_error(&mut self, err: PayloadError) {
|
||||
self.inner.set_error(err)
|
||||
}
|
||||
|
||||
fn feed_eof(&mut self) {
|
||||
if !self.error {
|
||||
match self.payload.feed_eof() {
|
||||
Err(err) => {
|
||||
self.error = true;
|
||||
self.set_error(PayloadError::Io(err));
|
||||
}
|
||||
Ok(value) => {
|
||||
if let Some(b) = value {
|
||||
self.inner.feed_data(b);
|
||||
}
|
||||
self.inner.feed_eof();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn feed_data(&mut self, data: Bytes) {
|
||||
if self.error {
|
||||
return;
|
||||
}
|
||||
|
||||
match self.payload.feed_data(data) {
|
||||
Ok(Some(b)) => self.inner.feed_data(b),
|
||||
Ok(None) => (),
|
||||
Err(e) => {
|
||||
self.error = true;
|
||||
self.set_error(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn need_read(&self) -> PayloadStatus {
|
||||
self.inner.need_read()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Decoder {
|
||||
#[cfg(feature = "flate2")]
|
||||
Deflate(Box<ZlibDecoder<Writer>>),
|
||||
#[cfg(feature = "flate2")]
|
||||
Gzip(Box<GzDecoder<Writer>>),
|
||||
#[cfg(feature = "brotli")]
|
||||
Br(Box<BrotliDecoder<Writer>>),
|
||||
Identity,
|
||||
}
|
||||
|
||||
pub(crate) struct Writer {
|
||||
buf: BytesMut,
|
||||
}
|
||||
|
||||
impl Writer {
|
||||
fn new() -> Writer {
|
||||
Writer {
|
||||
buf: BytesMut::with_capacity(8192),
|
||||
}
|
||||
}
|
||||
fn take(&mut self) -> Bytes {
|
||||
self.buf.take().freeze()
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for Writer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.buf.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload stream with decompression support
|
||||
pub(crate) struct PayloadStream {
|
||||
decoder: Decoder,
|
||||
}
|
||||
|
||||
impl PayloadStream {
|
||||
pub fn new(enc: ContentEncoding) -> PayloadStream {
|
||||
let decoder = match enc {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => {
|
||||
Decoder::Br(Box::new(BrotliDecoder::new(Writer::new())))
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => {
|
||||
Decoder::Deflate(Box::new(ZlibDecoder::new(Writer::new())))
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => {
|
||||
Decoder::Gzip(Box::new(GzDecoder::new(Writer::new())))
|
||||
}
|
||||
_ => Decoder::Identity,
|
||||
};
|
||||
PayloadStream { decoder }
|
||||
}
|
||||
}
|
||||
|
||||
impl PayloadStream {
|
||||
pub fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
|
||||
match self.decoder {
|
||||
#[cfg(feature = "brotli")]
|
||||
Decoder::Br(ref mut decoder) => match decoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
let b = writer.take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Gzip(ref mut decoder) => match decoder.try_finish() {
|
||||
Ok(_) => {
|
||||
let b = decoder.get_mut().take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Deflate(ref mut decoder) => match decoder.try_finish() {
|
||||
Ok(_) => {
|
||||
let b = decoder.get_mut().take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Decoder::Identity => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
|
||||
match self.decoder {
|
||||
#[cfg(feature = "brotli")]
|
||||
Decoder::Br(ref mut decoder) => match decoder.write_all(&data) {
|
||||
Ok(_) => {
|
||||
decoder.flush()?;
|
||||
let b = decoder.get_mut().take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Gzip(ref mut decoder) => match decoder.write_all(&data) {
|
||||
Ok(_) => {
|
||||
decoder.flush()?;
|
||||
let b = decoder.get_mut().take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
|
||||
Ok(_) => {
|
||||
decoder.flush()?;
|
||||
let b = decoder.get_mut().take();
|
||||
if !b.is_empty() {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Decoder::Identity => Ok(Some(data)),
|
||||
}
|
||||
}
|
||||
}
|
||||
284
src/server/message.rs
Normal file
284
src/server/message.rs
Normal file
@@ -0,0 +1,284 @@
|
||||
use std::cell::{Cell, Ref, RefCell, RefMut};
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::net::SocketAddr;
|
||||
use std::rc::Rc;
|
||||
|
||||
use http::{header, HeaderMap, Method, Uri, Version};
|
||||
|
||||
use extensions::Extensions;
|
||||
use httpmessage::HttpMessage;
|
||||
use info::ConnectionInfo;
|
||||
use payload::Payload;
|
||||
use server::ServerSettings;
|
||||
use uri::Url as InnerUrl;
|
||||
|
||||
bitflags! {
|
||||
pub(crate) struct MessageFlags: u8 {
|
||||
const KEEPALIVE = 0b0000_0001;
|
||||
const CONN_INFO = 0b0000_0010;
|
||||
}
|
||||
}
|
||||
|
||||
/// Request's context
|
||||
pub struct Request {
|
||||
pub(crate) inner: Rc<InnerRequest>,
|
||||
}
|
||||
|
||||
pub(crate) struct InnerRequest {
|
||||
pub(crate) version: Version,
|
||||
pub(crate) method: Method,
|
||||
pub(crate) url: InnerUrl,
|
||||
pub(crate) flags: Cell<MessageFlags>,
|
||||
pub(crate) headers: HeaderMap,
|
||||
pub(crate) extensions: RefCell<Extensions>,
|
||||
pub(crate) addr: Option<SocketAddr>,
|
||||
pub(crate) info: RefCell<ConnectionInfo>,
|
||||
pub(crate) payload: RefCell<Option<Payload>>,
|
||||
pub(crate) settings: ServerSettings,
|
||||
pub(crate) stream_extensions: Option<Rc<Extensions>>,
|
||||
pool: &'static RequestPool,
|
||||
}
|
||||
|
||||
impl InnerRequest {
|
||||
#[inline]
|
||||
/// Reset request instance
|
||||
pub fn reset(&mut self) {
|
||||
self.headers.clear();
|
||||
self.extensions.borrow_mut().clear();
|
||||
self.flags.set(MessageFlags::empty());
|
||||
*self.payload.borrow_mut() = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpMessage for Request {
|
||||
type Stream = Payload;
|
||||
|
||||
fn headers(&self) -> &HeaderMap {
|
||||
&self.inner.headers
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn payload(&self) -> Payload {
|
||||
if let Some(payload) = self.inner.payload.borrow_mut().take() {
|
||||
payload
|
||||
} else {
|
||||
Payload::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Request {
|
||||
/// Create new RequestContext instance
|
||||
pub(crate) fn new(pool: &'static RequestPool, settings: ServerSettings) -> Request {
|
||||
Request {
|
||||
inner: Rc::new(InnerRequest {
|
||||
pool,
|
||||
settings,
|
||||
method: Method::GET,
|
||||
url: InnerUrl::default(),
|
||||
version: Version::HTTP_11,
|
||||
headers: HeaderMap::with_capacity(16),
|
||||
flags: Cell::new(MessageFlags::empty()),
|
||||
addr: None,
|
||||
info: RefCell::new(ConnectionInfo::default()),
|
||||
payload: RefCell::new(None),
|
||||
extensions: RefCell::new(Extensions::new()),
|
||||
stream_extensions: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn inner(&self) -> &InnerRequest {
|
||||
self.inner.as_ref()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn inner_mut(&mut self) -> &mut InnerRequest {
|
||||
Rc::get_mut(&mut self.inner).expect("Multiple copies exist")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn url(&self) -> &InnerUrl {
|
||||
&self.inner().url
|
||||
}
|
||||
|
||||
/// Read the Request Uri.
|
||||
#[inline]
|
||||
pub fn uri(&self) -> &Uri {
|
||||
self.inner().url.uri()
|
||||
}
|
||||
|
||||
/// Read the Request method.
|
||||
#[inline]
|
||||
pub fn method(&self) -> &Method {
|
||||
&self.inner().method
|
||||
}
|
||||
|
||||
/// Read the Request Version.
|
||||
#[inline]
|
||||
pub fn version(&self) -> Version {
|
||||
self.inner().version
|
||||
}
|
||||
|
||||
/// The target path of this Request.
|
||||
#[inline]
|
||||
pub fn path(&self) -> &str {
|
||||
self.inner().url.path()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Returns Request's headers.
|
||||
pub fn headers(&self) -> &HeaderMap {
|
||||
&self.inner().headers
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Returns mutable Request's headers.
|
||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.inner_mut().headers
|
||||
}
|
||||
|
||||
/// Peer socket address
|
||||
///
|
||||
/// Peer address is actual socket address, if proxy is used in front of
|
||||
/// actix http server, then peer address would be address of this proxy.
|
||||
///
|
||||
/// To get client connection information `connection_info()` method should
|
||||
/// be used.
|
||||
pub fn peer_addr(&self) -> Option<SocketAddr> {
|
||||
self.inner().addr
|
||||
}
|
||||
|
||||
/// Checks if a connection should be kept alive.
|
||||
#[inline]
|
||||
pub fn keep_alive(&self) -> bool {
|
||||
self.inner().flags.get().contains(MessageFlags::KEEPALIVE)
|
||||
}
|
||||
|
||||
/// Request extensions
|
||||
#[inline]
|
||||
pub fn extensions(&self) -> Ref<Extensions> {
|
||||
self.inner().extensions.borrow()
|
||||
}
|
||||
|
||||
/// Mutable reference to a the request's extensions
|
||||
#[inline]
|
||||
pub fn extensions_mut(&self) -> RefMut<Extensions> {
|
||||
self.inner().extensions.borrow_mut()
|
||||
}
|
||||
|
||||
/// Check if request requires connection upgrade
|
||||
pub fn upgrade(&self) -> bool {
|
||||
if let Some(conn) = self.inner().headers.get(header::CONNECTION) {
|
||||
if let Ok(s) = conn.to_str() {
|
||||
return s.to_lowercase().contains("upgrade");
|
||||
}
|
||||
}
|
||||
self.inner().method == Method::CONNECT
|
||||
}
|
||||
|
||||
/// Get *ConnectionInfo* for the correct request.
|
||||
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
|
||||
if self.inner().flags.get().contains(MessageFlags::CONN_INFO) {
|
||||
self.inner().info.borrow()
|
||||
} else {
|
||||
let mut flags = self.inner().flags.get();
|
||||
flags.insert(MessageFlags::CONN_INFO);
|
||||
self.inner().flags.set(flags);
|
||||
self.inner().info.borrow_mut().update(self);
|
||||
self.inner().info.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
/// Io stream extensions
|
||||
#[inline]
|
||||
pub fn stream_extensions(&self) -> Option<&Extensions> {
|
||||
self.inner().stream_extensions.as_ref().map(|e| e.as_ref())
|
||||
}
|
||||
|
||||
/// Server settings
|
||||
#[inline]
|
||||
pub fn server_settings(&self) -> &ServerSettings {
|
||||
&self.inner().settings
|
||||
}
|
||||
|
||||
pub(crate) fn clone(&self) -> Self {
|
||||
Request {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn release(self) {
|
||||
let mut inner = self.inner;
|
||||
if let Some(r) = Rc::get_mut(&mut inner) {
|
||||
r.reset();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
inner.pool.release(inner);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Request {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"\nRequest {:?} {}:{}",
|
||||
self.version(),
|
||||
self.method(),
|
||||
self.path()
|
||||
)?;
|
||||
if let Some(q) = self.uri().query().as_ref() {
|
||||
writeln!(f, " query: ?{:?}", q)?;
|
||||
}
|
||||
writeln!(f, " headers:")?;
|
||||
for (key, val) in self.headers().iter() {
|
||||
writeln!(f, " {:?}: {:?}", key, val)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RequestPool(
|
||||
RefCell<VecDeque<Rc<InnerRequest>>>,
|
||||
RefCell<ServerSettings>,
|
||||
);
|
||||
|
||||
thread_local!(static POOL: &'static RequestPool = RequestPool::create());
|
||||
|
||||
impl RequestPool {
|
||||
fn create() -> &'static RequestPool {
|
||||
let pool = RequestPool(
|
||||
RefCell::new(VecDeque::with_capacity(128)),
|
||||
RefCell::new(ServerSettings::default()),
|
||||
);
|
||||
Box::leak(Box::new(pool))
|
||||
}
|
||||
|
||||
pub fn pool(settings: ServerSettings) -> &'static RequestPool {
|
||||
POOL.with(|p| {
|
||||
*p.1.borrow_mut() = settings;
|
||||
*p
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(pool: &'static RequestPool) -> Request {
|
||||
if let Some(msg) = pool.0.borrow_mut().pop_front() {
|
||||
Request { inner: msg }
|
||||
} else {
|
||||
Request::new(pool, pool.1.borrow().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Release request instance
|
||||
pub fn release(&self, msg: Rc<InnerRequest>) {
|
||||
let v = &mut self.0.borrow_mut();
|
||||
if v.len() < 128 {
|
||||
v.push_front(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,176 @@
|
||||
//! Http server
|
||||
use std::net::Shutdown;
|
||||
//! Http server module
|
||||
//!
|
||||
//! The module contains everything necessary to setup
|
||||
//! HTTP server.
|
||||
//!
|
||||
//! In order to start HTTP server, first you need to create and configure it
|
||||
//! using factory that can be supplied to [new](fn.new.html).
|
||||
//!
|
||||
//! ## Factory
|
||||
//!
|
||||
//! Factory is a function that returns Application, describing how
|
||||
//! to serve incoming HTTP requests.
|
||||
//!
|
||||
//! As the server uses worker pool, the factory function is restricted to trait bounds
|
||||
//! `Send + Clone + 'static` so that each worker would be able to accept Application
|
||||
//! without a need for synchronization.
|
||||
//!
|
||||
//! If you wish to share part of state among all workers you should
|
||||
//! wrap it in `Arc` and potentially synchronization primitive like
|
||||
//! [RwLock](https://doc.rust-lang.org/std/sync/struct.RwLock.html)
|
||||
//! If the wrapped type is not thread safe.
|
||||
//!
|
||||
//! Note though that locking is not advisable for asynchronous programming
|
||||
//! and you should minimize all locks in your request handlers
|
||||
//!
|
||||
//! ## HTTPS Support
|
||||
//!
|
||||
//! Actix-web provides support for major crates that provides TLS.
|
||||
//! Each TLS implementation is provided with [AcceptorService](trait.AcceptorService.html)
|
||||
//! that describes how HTTP Server accepts connections.
|
||||
//!
|
||||
//! For `bind` and `listen` there are corresponding `bind_ssl|tls|rustls` and `listen_ssl|tls|rustls` that accepts
|
||||
//! these services.
|
||||
//!
|
||||
//! **NOTE:** `native-tls` doesn't support `HTTP2` yet
|
||||
//!
|
||||
//! ## Signal handling and shutdown
|
||||
//!
|
||||
//! By default HTTP Server listens for system signals
|
||||
//! and, gracefully shuts down at most after 30 seconds.
|
||||
//!
|
||||
//! Both signal handling and shutdown timeout can be controlled
|
||||
//! using corresponding methods.
|
||||
//!
|
||||
//! If worker, for some reason, unable to shut down within timeout
|
||||
//! it is forcibly dropped.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//!extern crate actix;
|
||||
//!extern crate actix_web;
|
||||
//!extern crate rustls;
|
||||
//!
|
||||
//!use actix_web::{http, middleware, server, App, Error, HttpRequest, HttpResponse, Responder};
|
||||
//!use std::io::BufReader;
|
||||
//!use rustls::internal::pemfile::{certs, rsa_private_keys};
|
||||
//!use rustls::{NoClientAuth, ServerConfig};
|
||||
//!
|
||||
//!fn index(req: &HttpRequest) -> Result<HttpResponse, Error> {
|
||||
//! Ok(HttpResponse::Ok().content_type("text/plain").body("Welcome!"))
|
||||
//!}
|
||||
//!
|
||||
//!fn load_ssl() -> ServerConfig {
|
||||
//! use std::io::BufReader;
|
||||
//!
|
||||
//! const CERT: &'static [u8] = include_bytes!("../cert.pem");
|
||||
//! const KEY: &'static [u8] = include_bytes!("../key.pem");
|
||||
//!
|
||||
//! let mut cert = BufReader::new(CERT);
|
||||
//! let mut key = BufReader::new(KEY);
|
||||
//!
|
||||
//! let mut config = ServerConfig::new(NoClientAuth::new());
|
||||
//! let cert_chain = certs(&mut cert).unwrap();
|
||||
//! let mut keys = rsa_private_keys(&mut key).unwrap();
|
||||
//! config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
|
||||
//!
|
||||
//! config
|
||||
//!}
|
||||
//!
|
||||
//!fn main() {
|
||||
//! let sys = actix::System::new("http-server");
|
||||
//! // load ssl keys
|
||||
//! let config = load_ssl();
|
||||
//!
|
||||
//! // create and start server at once
|
||||
//! server::new(|| {
|
||||
//! App::new()
|
||||
//! // register simple handler, handle all methods
|
||||
//! .resource("/index.html", |r| r.f(index))
|
||||
//! }))
|
||||
//! }).bind_rustls("127.0.0.1:8443", config)
|
||||
//! .unwrap()
|
||||
//! .start();
|
||||
//!
|
||||
//! println!("Started http server: 127.0.0.1:8080");
|
||||
//! //Run system so that server would start accepting connections
|
||||
//! let _ = sys.run();
|
||||
//!}
|
||||
//! ```
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
use std::rc::Rc;
|
||||
use std::{io, time};
|
||||
|
||||
use actix;
|
||||
use bytes::BytesMut;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures::{Async, Poll};
|
||||
use tokio_core::net::TcpStream;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio_tcp::TcpStream;
|
||||
|
||||
pub use actix_net::server::{PauseServer, ResumeServer, StopServer};
|
||||
|
||||
pub(crate) mod acceptor;
|
||||
pub(crate) mod builder;
|
||||
mod channel;
|
||||
pub(crate) mod encoding;
|
||||
mod error;
|
||||
pub(crate) mod h1;
|
||||
pub(crate) mod h1decoder;
|
||||
mod h1writer;
|
||||
mod h2;
|
||||
mod h2writer;
|
||||
mod handler;
|
||||
pub(crate) mod helpers;
|
||||
mod settings;
|
||||
pub(crate) mod shared;
|
||||
mod srv;
|
||||
pub(crate) mod utils;
|
||||
mod worker;
|
||||
mod http;
|
||||
pub(crate) mod incoming;
|
||||
pub(crate) mod input;
|
||||
pub(crate) mod message;
|
||||
pub(crate) mod output;
|
||||
pub(crate) mod service;
|
||||
pub(crate) mod settings;
|
||||
mod ssl;
|
||||
|
||||
pub use self::handler::*;
|
||||
pub use self::http::HttpServer;
|
||||
pub use self::message::Request;
|
||||
pub use self::ssl::*;
|
||||
|
||||
pub use self::error::{AcceptorError, HttpDispatchError};
|
||||
pub use self::settings::ServerSettings;
|
||||
pub use self::srv::HttpServer;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use self::acceptor::AcceptorTimeout;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use self::settings::{ServiceConfig, ServiceConfigBuilder};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use self::service::{H1Service, HttpService, StreamConfiguration};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use self::helpers::write_content_length;
|
||||
|
||||
use body::Binary;
|
||||
use error::Error;
|
||||
use extensions::Extensions;
|
||||
use header::ContentEncoding;
|
||||
use httprequest::{HttpInnerMessage, HttpRequest};
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
/// max buffer size 64k
|
||||
pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536;
|
||||
|
||||
const LW_BUFFER_SIZE: usize = 4096;
|
||||
const HW_BUFFER_SIZE: usize = 32_768;
|
||||
|
||||
/// Create new http server with application factory.
|
||||
///
|
||||
/// This is shortcut for `server::HttpServer::new()` method.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix;
|
||||
/// # extern crate actix_web;
|
||||
/// use actix::*;
|
||||
/// # extern crate actix;
|
||||
/// use actix_web::{server, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let sys = actix::System::new("guide");
|
||||
/// let sys = actix::System::new("example"); // <- create Actix system
|
||||
///
|
||||
/// server::new(
|
||||
/// || App::new()
|
||||
@@ -56,19 +178,29 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536;
|
||||
/// .bind("127.0.0.1:59090").unwrap()
|
||||
/// .start();
|
||||
///
|
||||
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
/// let _ = sys.run();
|
||||
/// # actix::System::current().stop();
|
||||
/// sys.run();
|
||||
/// }
|
||||
/// ```
|
||||
pub fn new<F, U, H>(factory: F) -> HttpServer<H>
|
||||
pub fn new<F, H>(factory: F) -> HttpServer<H, F>
|
||||
where
|
||||
F: Fn() -> U + Sync + Send + 'static,
|
||||
U: IntoIterator<Item = H> + 'static,
|
||||
F: Fn() -> H + Send + Clone + 'static,
|
||||
H: IntoHttpHandler + 'static,
|
||||
{
|
||||
HttpServer::new(factory)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
bitflags! {
|
||||
///Flags that can be used to configure HTTP Server.
|
||||
pub struct ServerFlags: u8 {
|
||||
///Use HTTP1 protocol
|
||||
const HTTP1 = 0b0000_0001;
|
||||
///Use HTTP2 protocol
|
||||
const HTTP2 = 0b0000_0010;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
/// Server keep-alive setting
|
||||
pub enum KeepAlive {
|
||||
@@ -98,72 +230,6 @@ impl From<Option<usize>> for KeepAlive {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pause accepting incoming connections
|
||||
///
|
||||
/// If socket contains some pending connection, they might be dropped.
|
||||
/// All opened connection remains active.
|
||||
#[derive(Message)]
|
||||
pub struct PauseServer;
|
||||
|
||||
/// Resume accepting incoming connections
|
||||
#[derive(Message)]
|
||||
pub struct ResumeServer;
|
||||
|
||||
/// Stop incoming connection processing, stop all workers and exit.
|
||||
///
|
||||
/// If server starts with `spawn()` method, then spawned thread get terminated.
|
||||
pub struct StopServer {
|
||||
pub graceful: bool,
|
||||
}
|
||||
|
||||
impl actix::Message for StopServer {
|
||||
type Result = Result<(), ()>;
|
||||
}
|
||||
|
||||
/// Low level http request handler
|
||||
#[allow(unused_variables)]
|
||||
pub trait HttpHandler: 'static {
|
||||
/// Handle request
|
||||
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest>;
|
||||
}
|
||||
|
||||
impl HttpHandler for Box<HttpHandler> {
|
||||
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest> {
|
||||
self.as_mut().handle(req)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait HttpHandlerTask {
|
||||
/// Poll task, this method is used before or after *io* object is available
|
||||
fn poll(&mut self) -> Poll<(), Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
/// Poll task when *io* object is available
|
||||
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error>;
|
||||
|
||||
/// Connection is disconnected
|
||||
fn disconnected(&mut self) {}
|
||||
}
|
||||
|
||||
/// Conversion helper trait
|
||||
pub trait IntoHttpHandler {
|
||||
/// The associated type which is result of conversion.
|
||||
type Handler: HttpHandler;
|
||||
|
||||
/// Convert into `HttpHandler` object.
|
||||
fn into_handler(self, settings: ServerSettings) -> Self::Handler;
|
||||
}
|
||||
|
||||
impl<T: HttpHandler> IntoHttpHandler for T {
|
||||
type Handler = T;
|
||||
|
||||
fn into_handler(self, _: ServerSettings) -> Self::Handler {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
pub enum WriterState {
|
||||
@@ -178,18 +244,16 @@ pub trait Writer {
|
||||
fn written(&self) -> u64;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn set_date(&self, st: &mut BytesMut);
|
||||
fn set_date(&mut self);
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
|
||||
fn buffer(&self) -> &mut BytesMut;
|
||||
fn buffer(&mut self) -> &mut BytesMut;
|
||||
|
||||
fn start(
|
||||
&mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse,
|
||||
encoding: ContentEncoding,
|
||||
&mut self, req: &Request, resp: &mut HttpResponse, encoding: ContentEncoding,
|
||||
) -> io::Result<WriterState>;
|
||||
|
||||
fn write(&mut self, payload: Binary) -> io::Result<WriterState>;
|
||||
fn write(&mut self, payload: &Binary) -> io::Result<WriterState>;
|
||||
|
||||
fn write_eof(&mut self) -> io::Result<WriterState>;
|
||||
|
||||
@@ -201,9 +265,79 @@ pub trait Writer {
|
||||
pub trait IoStream: AsyncRead + AsyncWrite + 'static {
|
||||
fn shutdown(&mut self, how: Shutdown) -> io::Result<()>;
|
||||
|
||||
/// Returns the socket address of the remote peer of this TCP connection.
|
||||
fn peer_addr(&self) -> Option<SocketAddr> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Sets the value of the TCP_NODELAY option on this socket.
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>;
|
||||
|
||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
|
||||
|
||||
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
|
||||
|
||||
fn read_available(&mut self, buf: &mut BytesMut) -> Poll<(bool, bool), io::Error> {
|
||||
let mut read_some = false;
|
||||
loop {
|
||||
if buf.remaining_mut() < LW_BUFFER_SIZE {
|
||||
buf.reserve(HW_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
let read = unsafe { self.read(buf.bytes_mut()) };
|
||||
match read {
|
||||
Ok(n) => {
|
||||
if n == 0 {
|
||||
return Ok(Async::Ready((read_some, true)));
|
||||
} else {
|
||||
read_some = true;
|
||||
unsafe {
|
||||
buf.advance_mut(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return if e.kind() == io::ErrorKind::WouldBlock {
|
||||
if read_some {
|
||||
Ok(Async::Ready((read_some, false)))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
} else {
|
||||
Err(e)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extra io stream extensions
|
||||
fn extensions(&self) -> Option<Rc<Extensions>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "uds"))]
|
||||
impl IoStream for ::tokio_uds::UnixStream {
|
||||
#[inline]
|
||||
fn shutdown(&mut self, how: Shutdown) -> io::Result<()> {
|
||||
::tokio_uds::UnixStream::shutdown(self, how)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_linger(&mut self, _dur: Option<time::Duration>) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_keepalive(&mut self, _dur: Option<time::Duration>) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl IoStream for TcpStream {
|
||||
@@ -212,6 +346,11 @@ impl IoStream for TcpStream {
|
||||
TcpStream::shutdown(self, how)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn peer_addr(&self) -> Option<SocketAddr> {
|
||||
TcpStream::peer_addr(self).ok()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||
TcpStream::set_nodelay(self, nodelay)
|
||||
@@ -221,48 +360,9 @@ impl IoStream for TcpStream {
|
||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
TcpStream::set_linger(self, dur)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alpn")]
|
||||
use tokio_openssl::SslStream;
|
||||
|
||||
#[cfg(feature = "alpn")]
|
||||
impl IoStream for SslStream<TcpStream> {
|
||||
#[inline]
|
||||
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
|
||||
let _ = self.get_mut().shutdown();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||
self.get_mut().get_mut().set_nodelay(nodelay)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().get_mut().set_linger(dur)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
use tokio_tls::TlsStream;
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
impl IoStream for TlsStream<TcpStream> {
|
||||
#[inline]
|
||||
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
|
||||
let _ = self.get_mut().shutdown();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||
self.get_mut().get_mut().set_nodelay(nodelay)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().get_mut().set_linger(dur)
|
||||
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
TcpStream::set_keepalive(self, dur)
|
||||
}
|
||||
}
|
||||
|
||||
760
src/server/output.rs
Normal file
760
src/server/output.rs
Normal file
@@ -0,0 +1,760 @@
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
use std::{cmp, fmt, io, mem};
|
||||
|
||||
#[cfg(feature = "brotli")]
|
||||
use brotli2::write::BrotliEncoder;
|
||||
use bytes::BytesMut;
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::Compression;
|
||||
use http::header::{ACCEPT_ENCODING, CONTENT_LENGTH};
|
||||
use http::{StatusCode, Version};
|
||||
|
||||
use super::message::InnerRequest;
|
||||
use body::{Binary, Body};
|
||||
use header::ContentEncoding;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ResponseLength {
|
||||
Chunked,
|
||||
Zero,
|
||||
Length(usize),
|
||||
Length64(u64),
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ResponseInfo {
|
||||
head: bool,
|
||||
pub length: ResponseLength,
|
||||
pub content_encoding: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl ResponseInfo {
|
||||
pub fn new(head: bool) -> Self {
|
||||
ResponseInfo {
|
||||
head,
|
||||
length: ResponseLength::None,
|
||||
content_encoding: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Output {
|
||||
Empty(BytesMut),
|
||||
Buffer(BytesMut),
|
||||
Encoder(ContentEncoder),
|
||||
TE(TransferEncoding),
|
||||
Done,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub fn take(&mut self) -> BytesMut {
|
||||
match mem::replace(self, Output::Done) {
|
||||
Output::Empty(bytes) => bytes,
|
||||
Output::Buffer(bytes) => bytes,
|
||||
Output::Encoder(mut enc) => enc.take_buf(),
|
||||
Output::TE(mut te) => te.take(),
|
||||
Output::Done => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_option(&mut self) -> Option<BytesMut> {
|
||||
match mem::replace(self, Output::Done) {
|
||||
Output::Empty(bytes) => Some(bytes),
|
||||
Output::Buffer(bytes) => Some(bytes),
|
||||
Output::Encoder(mut enc) => Some(enc.take_buf()),
|
||||
Output::TE(mut te) => Some(te.take()),
|
||||
Output::Done => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ref(&mut self) -> &BytesMut {
|
||||
match self {
|
||||
Output::Empty(ref mut bytes) => bytes,
|
||||
Output::Buffer(ref mut bytes) => bytes,
|
||||
Output::Encoder(ref mut enc) => enc.buf_ref(),
|
||||
Output::TE(ref mut te) => te.buf_ref(),
|
||||
Output::Done => panic!(),
|
||||
}
|
||||
}
|
||||
pub fn as_mut(&mut self) -> &mut BytesMut {
|
||||
match self {
|
||||
Output::Empty(ref mut bytes) => bytes,
|
||||
Output::Buffer(ref mut bytes) => bytes,
|
||||
Output::Encoder(ref mut enc) => enc.buf_mut(),
|
||||
Output::TE(ref mut te) => te.buf_mut(),
|
||||
Output::Done => panic!(),
|
||||
}
|
||||
}
|
||||
pub fn split_to(&mut self, cap: usize) -> BytesMut {
|
||||
match self {
|
||||
Output::Empty(ref mut bytes) => bytes.split_to(cap),
|
||||
Output::Buffer(ref mut bytes) => bytes.split_to(cap),
|
||||
Output::Encoder(ref mut enc) => enc.buf_mut().split_to(cap),
|
||||
Output::TE(ref mut te) => te.buf_mut().split_to(cap),
|
||||
Output::Done => BytesMut::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Output::Empty(ref bytes) => bytes.len(),
|
||||
Output::Buffer(ref bytes) => bytes.len(),
|
||||
Output::Encoder(ref enc) => enc.len(),
|
||||
Output::TE(ref te) => te.len(),
|
||||
Output::Done => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Output::Empty(ref bytes) => bytes.is_empty(),
|
||||
Output::Buffer(ref bytes) => bytes.is_empty(),
|
||||
Output::Encoder(ref enc) => enc.is_empty(),
|
||||
Output::TE(ref te) => te.is_empty(),
|
||||
Output::Done => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
|
||||
match self {
|
||||
Output::Buffer(ref mut bytes) => {
|
||||
bytes.extend_from_slice(data);
|
||||
Ok(())
|
||||
}
|
||||
Output::Encoder(ref mut enc) => enc.write(data),
|
||||
Output::TE(ref mut te) => te.encode(data).map(|_| ()),
|
||||
Output::Empty(_) | Output::Done => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_eof(&mut self) -> Result<bool, io::Error> {
|
||||
match self {
|
||||
Output::Buffer(_) => Ok(true),
|
||||
Output::Encoder(ref mut enc) => enc.write_eof(),
|
||||
Output::TE(ref mut te) => Ok(te.encode_eof()),
|
||||
Output::Empty(_) | Output::Done => Ok(true),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn for_server(
|
||||
&mut self, info: &mut ResponseInfo, req: &InnerRequest, resp: &mut HttpResponse,
|
||||
response_encoding: ContentEncoding,
|
||||
) {
|
||||
let buf = self.take();
|
||||
let version = resp.version().unwrap_or_else(|| req.version);
|
||||
let mut len = 0;
|
||||
|
||||
let has_body = match resp.body() {
|
||||
Body::Empty => false,
|
||||
Body::Binary(ref bin) => {
|
||||
len = bin.len();
|
||||
!(response_encoding == ContentEncoding::Auto && len < 96)
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// Enable content encoding only if response does not contain Content-Encoding
|
||||
// header
|
||||
#[cfg(any(feature = "brotli", feature = "flate2"))]
|
||||
let mut encoding = if has_body {
|
||||
let encoding = match response_encoding {
|
||||
ContentEncoding::Auto => {
|
||||
// negotiate content-encoding
|
||||
if let Some(val) = req.headers.get(ACCEPT_ENCODING) {
|
||||
if let Ok(enc) = val.to_str() {
|
||||
AcceptEncoding::parse(enc)
|
||||
} else {
|
||||
ContentEncoding::Identity
|
||||
}
|
||||
} else {
|
||||
ContentEncoding::Identity
|
||||
}
|
||||
}
|
||||
encoding => encoding,
|
||||
};
|
||||
if encoding.is_compression() {
|
||||
info.content_encoding = Some(encoding.as_str());
|
||||
}
|
||||
encoding
|
||||
} else {
|
||||
ContentEncoding::Identity
|
||||
};
|
||||
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
|
||||
let mut encoding = ContentEncoding::Identity;
|
||||
|
||||
let transfer = match resp.body() {
|
||||
Body::Empty => {
|
||||
info.length = match resp.status() {
|
||||
StatusCode::NO_CONTENT
|
||||
| StatusCode::CONTINUE
|
||||
| StatusCode::SWITCHING_PROTOCOLS
|
||||
| StatusCode::PROCESSING => ResponseLength::None,
|
||||
_ => ResponseLength::Zero,
|
||||
};
|
||||
*self = Output::Empty(buf);
|
||||
return;
|
||||
}
|
||||
Body::Binary(_) => {
|
||||
#[cfg(any(feature = "brotli", feature = "flate2"))]
|
||||
{
|
||||
if !(encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto)
|
||||
{
|
||||
let mut tmp = BytesMut::new();
|
||||
let mut transfer = TransferEncoding::eof(tmp);
|
||||
let mut enc = match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
||||
ZlibEncoder::new(transfer, Compression::fast()),
|
||||
),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => ContentEncoder::Gzip(
|
||||
GzEncoder::new(transfer, Compression::fast()),
|
||||
),
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => {
|
||||
ContentEncoder::Br(BrotliEncoder::new(transfer, 3))
|
||||
}
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let bin = resp.replace_body(Body::Empty).binary();
|
||||
|
||||
// TODO return error!
|
||||
let _ = enc.write(bin.as_ref());
|
||||
let _ = enc.write_eof();
|
||||
let body = enc.buf_mut().take();
|
||||
len = body.len();
|
||||
resp.replace_body(Binary::from(body));
|
||||
}
|
||||
}
|
||||
|
||||
info.length = ResponseLength::Length(len);
|
||||
if info.head {
|
||||
*self = Output::Empty(buf);
|
||||
} else {
|
||||
*self = Output::Buffer(buf);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Body::Streaming(_) | Body::Actor(_) => {
|
||||
if resp.upgrade() {
|
||||
if version == Version::HTTP_2 {
|
||||
error!("Connection upgrade is forbidden for HTTP/2");
|
||||
}
|
||||
if encoding != ContentEncoding::Identity {
|
||||
encoding = ContentEncoding::Identity;
|
||||
info.content_encoding.take();
|
||||
}
|
||||
TransferEncoding::eof(buf)
|
||||
} else {
|
||||
if !(encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto)
|
||||
{
|
||||
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||
}
|
||||
Output::streaming_encoding(info, buf, version, resp)
|
||||
}
|
||||
}
|
||||
};
|
||||
// check for head response
|
||||
if info.head {
|
||||
resp.set_body(Body::Empty);
|
||||
*self = Output::Empty(transfer.buf.unwrap());
|
||||
return;
|
||||
}
|
||||
|
||||
let enc = match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => {
|
||||
ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::fast()))
|
||||
}
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => {
|
||||
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast()))
|
||||
}
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 3)),
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => {
|
||||
*self = Output::TE(transfer);
|
||||
return;
|
||||
}
|
||||
};
|
||||
*self = Output::Encoder(enc);
|
||||
}
|
||||
|
||||
fn streaming_encoding(
|
||||
info: &mut ResponseInfo, buf: BytesMut, version: Version,
|
||||
resp: &mut HttpResponse,
|
||||
) -> TransferEncoding {
|
||||
match resp.chunked() {
|
||||
Some(true) => {
|
||||
// Enable transfer encoding
|
||||
info.length = ResponseLength::Chunked;
|
||||
if version == Version::HTTP_2 {
|
||||
TransferEncoding::eof(buf)
|
||||
} else {
|
||||
TransferEncoding::chunked(buf)
|
||||
}
|
||||
}
|
||||
Some(false) => TransferEncoding::eof(buf),
|
||||
None => {
|
||||
// if Content-Length is specified, then use it as length hint
|
||||
let (len, chunked) =
|
||||
if let Some(len) = resp.headers().get(CONTENT_LENGTH) {
|
||||
// Content-Length
|
||||
if let Ok(s) = len.to_str() {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
(Some(len), false)
|
||||
} else {
|
||||
error!("illegal Content-Length: {:?}", len);
|
||||
(None, false)
|
||||
}
|
||||
} else {
|
||||
error!("illegal Content-Length: {:?}", len);
|
||||
(None, false)
|
||||
}
|
||||
} else {
|
||||
(None, true)
|
||||
};
|
||||
|
||||
if !chunked {
|
||||
if let Some(len) = len {
|
||||
info.length = ResponseLength::Length64(len);
|
||||
TransferEncoding::length(len, buf)
|
||||
} else {
|
||||
TransferEncoding::eof(buf)
|
||||
}
|
||||
} else {
|
||||
// Enable transfer encoding
|
||||
info.length = ResponseLength::Chunked;
|
||||
if version == Version::HTTP_2 {
|
||||
TransferEncoding::eof(buf)
|
||||
} else {
|
||||
TransferEncoding::chunked(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum ContentEncoder {
|
||||
#[cfg(feature = "flate2")]
|
||||
Deflate(ZlibEncoder<TransferEncoding>),
|
||||
#[cfg(feature = "flate2")]
|
||||
Gzip(GzEncoder<TransferEncoding>),
|
||||
#[cfg(feature = "brotli")]
|
||||
Br(BrotliEncoder<TransferEncoding>),
|
||||
Identity(TransferEncoding),
|
||||
}
|
||||
|
||||
impl fmt::Debug for ContentEncoder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"),
|
||||
ContentEncoder::Identity(_) => writeln!(f, "ContentEncoder(Identity)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContentEncoder {
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref encoder) => encoder.get_ref().len(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().len(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().len(),
|
||||
ContentEncoder::Identity(ref encoder) => encoder.len(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref encoder) => encoder.get_ref().is_empty(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_empty(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_empty(),
|
||||
ContentEncoder::Identity(ref encoder) => encoder.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn take_buf(&mut self) -> BytesMut {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
|
||||
ContentEncoder::Identity(ref mut encoder) => encoder.take(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn buf_mut(&mut self) -> &mut BytesMut {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_mut(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_mut(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_mut(),
|
||||
ContentEncoder::Identity(ref mut encoder) => encoder.buf_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn buf_ref(&mut self) -> &BytesMut {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_ref(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_ref(),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_ref(),
|
||||
ContentEncoder::Identity(ref mut encoder) => encoder.buf_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||
#[inline(always)]
|
||||
pub fn write_eof(&mut self) -> Result<bool, io::Error> {
|
||||
let encoder =
|
||||
mem::replace(self, ContentEncoder::Identity(TransferEncoding::empty()));
|
||||
|
||||
match encoder {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(encoder) => match encoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(true)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(encoder) => match encoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(true)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(encoder) => match encoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(true)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
ContentEncoder::Identity(mut writer) => {
|
||||
let res = writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||
#[inline(always)]
|
||||
pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding br encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding gzip encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding deflate encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
ContentEncoder::Identity(ref mut encoder) => {
|
||||
encoder.encode(data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encoders to handle different Transfer-Encodings.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TransferEncoding {
|
||||
buf: Option<BytesMut>,
|
||||
kind: TransferEncodingKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum TransferEncodingKind {
|
||||
/// An Encoder for when Transfer-Encoding includes `chunked`.
|
||||
Chunked(bool),
|
||||
/// An Encoder for when Content-Length is set.
|
||||
///
|
||||
/// Enforces that the body is not longer than the Content-Length header.
|
||||
Length(u64),
|
||||
/// An Encoder for when Content-Length is not known.
|
||||
///
|
||||
/// Application decides when to stop writing.
|
||||
Eof,
|
||||
}
|
||||
|
||||
impl TransferEncoding {
|
||||
fn take(&mut self) -> BytesMut {
|
||||
self.buf.take().unwrap()
|
||||
}
|
||||
|
||||
fn buf_ref(&mut self) -> &BytesMut {
|
||||
self.buf.as_ref().unwrap()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.buf.as_ref().unwrap().len()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.buf.as_ref().unwrap().is_empty()
|
||||
}
|
||||
|
||||
fn buf_mut(&mut self) -> &mut BytesMut {
|
||||
self.buf.as_mut().unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn empty() -> TransferEncoding {
|
||||
TransferEncoding {
|
||||
buf: None,
|
||||
kind: TransferEncodingKind::Eof,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn eof(buf: BytesMut) -> TransferEncoding {
|
||||
TransferEncoding {
|
||||
buf: Some(buf),
|
||||
kind: TransferEncodingKind::Eof,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn chunked(buf: BytesMut) -> TransferEncoding {
|
||||
TransferEncoding {
|
||||
buf: Some(buf),
|
||||
kind: TransferEncodingKind::Chunked(false),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn length(len: u64, buf: BytesMut) -> TransferEncoding {
|
||||
TransferEncoding {
|
||||
buf: Some(buf),
|
||||
kind: TransferEncodingKind::Length(len),
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode message. Return `EOF` state of encoder
|
||||
#[inline]
|
||||
pub fn encode(&mut self, msg: &[u8]) -> io::Result<bool> {
|
||||
match self.kind {
|
||||
TransferEncodingKind::Eof => {
|
||||
let eof = msg.is_empty();
|
||||
self.buf.as_mut().unwrap().extend_from_slice(msg);
|
||||
Ok(eof)
|
||||
}
|
||||
TransferEncodingKind::Chunked(ref mut eof) => {
|
||||
if *eof {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if msg.is_empty() {
|
||||
*eof = true;
|
||||
self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n");
|
||||
} else {
|
||||
let mut buf = BytesMut::new();
|
||||
writeln!(&mut buf, "{:X}\r", msg.len())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
|
||||
let b = self.buf.as_mut().unwrap();
|
||||
b.reserve(buf.len() + msg.len() + 2);
|
||||
b.extend_from_slice(buf.as_ref());
|
||||
b.extend_from_slice(msg);
|
||||
b.extend_from_slice(b"\r\n");
|
||||
}
|
||||
Ok(*eof)
|
||||
}
|
||||
TransferEncodingKind::Length(ref mut remaining) => {
|
||||
if *remaining > 0 {
|
||||
if msg.is_empty() {
|
||||
return Ok(*remaining == 0);
|
||||
}
|
||||
let len = cmp::min(*remaining, msg.len() as u64);
|
||||
|
||||
self.buf
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.extend_from_slice(&msg[..len as usize]);
|
||||
|
||||
*remaining -= len as u64;
|
||||
Ok(*remaining == 0)
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode eof. Return `EOF` state of encoder
|
||||
#[inline]
|
||||
pub fn encode_eof(&mut self) -> bool {
|
||||
match self.kind {
|
||||
TransferEncodingKind::Eof => true,
|
||||
TransferEncodingKind::Length(rem) => rem == 0,
|
||||
TransferEncodingKind::Chunked(ref mut eof) => {
|
||||
if !*eof {
|
||||
*eof = true;
|
||||
self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n");
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for TransferEncoding {
|
||||
#[inline]
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
if self.buf.is_some() {
|
||||
self.encode(buf)?;
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct AcceptEncoding {
|
||||
encoding: ContentEncoding,
|
||||
quality: f64,
|
||||
}
|
||||
|
||||
impl Eq for AcceptEncoding {}
|
||||
|
||||
impl Ord for AcceptEncoding {
|
||||
fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering {
|
||||
if self.quality > other.quality {
|
||||
cmp::Ordering::Less
|
||||
} else if self.quality < other.quality {
|
||||
cmp::Ordering::Greater
|
||||
} else {
|
||||
cmp::Ordering::Equal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for AcceptEncoding {
|
||||
fn partial_cmp(&self, other: &AcceptEncoding) -> Option<cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AcceptEncoding {
|
||||
fn eq(&self, other: &AcceptEncoding) -> bool {
|
||||
self.quality == other.quality
|
||||
}
|
||||
}
|
||||
|
||||
impl AcceptEncoding {
|
||||
fn new(tag: &str) -> Option<AcceptEncoding> {
|
||||
let parts: Vec<&str> = tag.split(';').collect();
|
||||
let encoding = match parts.len() {
|
||||
0 => return None,
|
||||
_ => ContentEncoding::from(parts[0]),
|
||||
};
|
||||
let quality = match parts.len() {
|
||||
1 => encoding.quality(),
|
||||
_ => match f64::from_str(parts[1]) {
|
||||
Ok(q) => q,
|
||||
Err(_) => 0.0,
|
||||
},
|
||||
};
|
||||
Some(AcceptEncoding { encoding, quality })
|
||||
}
|
||||
|
||||
/// Parse a raw Accept-Encoding header value into an ordered list.
|
||||
pub fn parse(raw: &str) -> ContentEncoding {
|
||||
let mut encodings: Vec<_> = raw
|
||||
.replace(' ', "")
|
||||
.split(',')
|
||||
.map(|l| AcceptEncoding::new(l))
|
||||
.collect();
|
||||
encodings.sort();
|
||||
|
||||
for enc in encodings {
|
||||
if let Some(enc) = enc {
|
||||
return enc.encoding;
|
||||
}
|
||||
}
|
||||
ContentEncoding::Identity
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bytes::Bytes;
|
||||
|
||||
#[test]
|
||||
fn test_chunked_te() {
|
||||
let bytes = BytesMut::new();
|
||||
let mut enc = TransferEncoding::chunked(bytes);
|
||||
{
|
||||
assert!(!enc.encode(b"test").ok().unwrap());
|
||||
assert!(enc.encode(b"").ok().unwrap());
|
||||
}
|
||||
assert_eq!(
|
||||
enc.buf_mut().take().freeze(),
|
||||
Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
272
src/server/service.rs
Normal file
272
src/server/service.rs
Normal file
@@ -0,0 +1,272 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::time::Duration;
|
||||
|
||||
use actix_net::service::{NewService, Service};
|
||||
use futures::future::{ok, FutureResult};
|
||||
use futures::{Async, Poll};
|
||||
|
||||
use super::channel::{H1Channel, HttpChannel};
|
||||
use super::error::HttpDispatchError;
|
||||
use super::handler::HttpHandler;
|
||||
use super::settings::ServiceConfig;
|
||||
use super::IoStream;
|
||||
|
||||
/// `NewService` implementation for HTTP1/HTTP2 transports
|
||||
pub struct HttpService<H, Io>
|
||||
where
|
||||
H: HttpHandler,
|
||||
Io: IoStream,
|
||||
{
|
||||
settings: ServiceConfig<H>,
|
||||
_t: PhantomData<Io>,
|
||||
}
|
||||
|
||||
impl<H, Io> HttpService<H, Io>
|
||||
where
|
||||
H: HttpHandler,
|
||||
Io: IoStream,
|
||||
{
|
||||
/// Create new `HttpService` instance.
|
||||
pub fn new(settings: ServiceConfig<H>) -> Self {
|
||||
HttpService {
|
||||
settings,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, Io> NewService for HttpService<H, Io>
|
||||
where
|
||||
H: HttpHandler,
|
||||
Io: IoStream,
|
||||
{
|
||||
type Request = Io;
|
||||
type Response = ();
|
||||
type Error = HttpDispatchError;
|
||||
type InitError = ();
|
||||
type Service = HttpServiceHandler<H, Io>;
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self) -> Self::Future {
|
||||
ok(HttpServiceHandler::new(self.settings.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HttpServiceHandler<H, Io>
|
||||
where
|
||||
H: HttpHandler,
|
||||
Io: IoStream,
|
||||
{
|
||||
settings: ServiceConfig<H>,
|
||||
_t: PhantomData<Io>,
|
||||
}
|
||||
|
||||
impl<H, Io> HttpServiceHandler<H, Io>
|
||||
where
|
||||
H: HttpHandler,
|
||||
Io: IoStream,
|
||||
{
|
||||
fn new(settings: ServiceConfig<H>) -> HttpServiceHandler<H, Io> {
|
||||
HttpServiceHandler {
|
||||
settings,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, Io> Service for HttpServiceHandler<H, Io>
|
||||
where
|
||||
H: HttpHandler,
|
||||
Io: IoStream,
|
||||
{
|
||||
type Request = Io;
|
||||
type Response = ();
|
||||
type Error = HttpDispatchError;
|
||||
type Future = HttpChannel<Io, H>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||
HttpChannel::new(self.settings.clone(), req)
|
||||
}
|
||||
}
|
||||
|
||||
/// `NewService` implementation for HTTP1 transport
|
||||
pub struct H1Service<H, Io>
|
||||
where
|
||||
H: HttpHandler,
|
||||
Io: IoStream,
|
||||
{
|
||||
settings: ServiceConfig<H>,
|
||||
_t: PhantomData<Io>,
|
||||
}
|
||||
|
||||
impl<H, Io> H1Service<H, Io>
|
||||
where
|
||||
H: HttpHandler,
|
||||
Io: IoStream,
|
||||
{
|
||||
/// Create new `HttpService` instance.
|
||||
pub fn new(settings: ServiceConfig<H>) -> Self {
|
||||
H1Service {
|
||||
settings,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, Io> NewService for H1Service<H, Io>
|
||||
where
|
||||
H: HttpHandler,
|
||||
Io: IoStream,
|
||||
{
|
||||
type Request = Io;
|
||||
type Response = ();
|
||||
type Error = HttpDispatchError;
|
||||
type InitError = ();
|
||||
type Service = H1ServiceHandler<H, Io>;
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self) -> Self::Future {
|
||||
ok(H1ServiceHandler::new(self.settings.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
/// `Service` implementation for HTTP1 transport
|
||||
pub struct H1ServiceHandler<H, Io>
|
||||
where
|
||||
H: HttpHandler,
|
||||
Io: IoStream,
|
||||
{
|
||||
settings: ServiceConfig<H>,
|
||||
_t: PhantomData<Io>,
|
||||
}
|
||||
|
||||
impl<H, Io> H1ServiceHandler<H, Io>
|
||||
where
|
||||
H: HttpHandler,
|
||||
Io: IoStream,
|
||||
{
|
||||
fn new(settings: ServiceConfig<H>) -> H1ServiceHandler<H, Io> {
|
||||
H1ServiceHandler {
|
||||
settings,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, Io> Service for H1ServiceHandler<H, Io>
|
||||
where
|
||||
H: HttpHandler,
|
||||
Io: IoStream,
|
||||
{
|
||||
type Request = Io;
|
||||
type Response = ();
|
||||
type Error = HttpDispatchError;
|
||||
type Future = H1Channel<Io, H>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||
H1Channel::new(self.settings.clone(), req)
|
||||
}
|
||||
}
|
||||
|
||||
/// `NewService` implementation for stream configuration service
|
||||
///
|
||||
/// Stream configuration service allows to change some socket level
|
||||
/// parameters. for example `tcp nodelay` or `tcp keep-alive`.
|
||||
pub struct StreamConfiguration<T, E> {
|
||||
no_delay: Option<bool>,
|
||||
tcp_ka: Option<Option<Duration>>,
|
||||
_t: PhantomData<(T, E)>,
|
||||
}
|
||||
|
||||
impl<T, E> Default for StreamConfiguration<T, E> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> StreamConfiguration<T, E> {
|
||||
/// Create new `StreamConfigurationService` instance.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
no_delay: None,
|
||||
tcp_ka: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the value of the `TCP_NODELAY` option on this socket.
|
||||
pub fn nodelay(mut self, nodelay: bool) -> Self {
|
||||
self.no_delay = Some(nodelay);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether keepalive messages are enabled to be sent on this socket.
|
||||
pub fn tcp_keepalive(mut self, keepalive: Option<Duration>) -> Self {
|
||||
self.tcp_ka = Some(keepalive);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IoStream, E> NewService for StreamConfiguration<T, E> {
|
||||
type Request = T;
|
||||
type Response = T;
|
||||
type Error = E;
|
||||
type InitError = ();
|
||||
type Service = StreamConfigurationService<T, E>;
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self) -> Self::Future {
|
||||
ok(StreamConfigurationService {
|
||||
no_delay: self.no_delay,
|
||||
tcp_ka: self.tcp_ka,
|
||||
_t: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Stream configuration service
|
||||
///
|
||||
/// Stream configuration service allows to change some socket level
|
||||
/// parameters. for example `tcp nodelay` or `tcp keep-alive`.
|
||||
pub struct StreamConfigurationService<T, E> {
|
||||
no_delay: Option<bool>,
|
||||
tcp_ka: Option<Option<Duration>>,
|
||||
_t: PhantomData<(T, E)>,
|
||||
}
|
||||
|
||||
impl<T, E> Service for StreamConfigurationService<T, E>
|
||||
where
|
||||
T: IoStream,
|
||||
{
|
||||
type Request = T;
|
||||
type Response = T;
|
||||
type Error = E;
|
||||
type Future = FutureResult<T, E>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: Self::Request) -> Self::Future {
|
||||
if let Some(no_delay) = self.no_delay {
|
||||
if req.set_nodelay(no_delay).is_err() {
|
||||
error!("Can not set socket no-delay option");
|
||||
}
|
||||
}
|
||||
if let Some(keepalive) = self.tcp_ka {
|
||||
if req.set_keepalive(keepalive).is_err() {
|
||||
error!("Can not set socket keep-alive option");
|
||||
}
|
||||
}
|
||||
|
||||
ok(req)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,53 @@
|
||||
use bytes::BytesMut;
|
||||
use futures_cpupool::{Builder, CpuPool};
|
||||
use http::StatusCode;
|
||||
use std::cell::{Cell, RefCell, RefMut, UnsafeCell};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Write;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, mem, net};
|
||||
use time;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{env, fmt, net};
|
||||
|
||||
use super::channel::Node;
|
||||
use super::helpers;
|
||||
use super::shared::{SharedBytes, SharedBytesPool};
|
||||
use bytes::BytesMut;
|
||||
use futures::{future, Future};
|
||||
use futures_cpupool::CpuPool;
|
||||
use http::StatusCode;
|
||||
use lazycell::LazyCell;
|
||||
use parking_lot::Mutex;
|
||||
use time;
|
||||
use tokio_current_thread::spawn;
|
||||
use tokio_timer::{sleep, Delay};
|
||||
|
||||
use super::message::{Request, RequestPool};
|
||||
use super::KeepAlive;
|
||||
use body::Body;
|
||||
use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool};
|
||||
|
||||
/// Various server settings
|
||||
pub struct ServerSettings {
|
||||
addr: Option<net::SocketAddr>,
|
||||
secure: bool,
|
||||
host: String,
|
||||
cpu_pool: Arc<InnerCpuPool>,
|
||||
responses: Rc<UnsafeCell<HttpResponsePool>>,
|
||||
/// Env variable for default cpu pool size
|
||||
const ENV_CPU_POOL_VAR: &str = "ACTIX_CPU_POOL";
|
||||
|
||||
lazy_static! {
|
||||
pub(crate) static ref DEFAULT_CPUPOOL: Mutex<CpuPool> = {
|
||||
let default = match env::var(ENV_CPU_POOL_VAR) {
|
||||
Ok(val) => {
|
||||
if let Ok(val) = val.parse() {
|
||||
val
|
||||
} else {
|
||||
error!("Can not parse ACTIX_CPU_POOL value");
|
||||
20
|
||||
}
|
||||
}
|
||||
Err(_) => 20,
|
||||
};
|
||||
Mutex::new(CpuPool::new(default))
|
||||
};
|
||||
}
|
||||
|
||||
unsafe impl Sync for ServerSettings {}
|
||||
unsafe impl Send for ServerSettings {}
|
||||
/// Various server settings
|
||||
pub struct ServerSettings {
|
||||
addr: net::SocketAddr,
|
||||
secure: bool,
|
||||
host: String,
|
||||
cpu_pool: LazyCell<CpuPool>,
|
||||
responses: &'static HttpResponsePool,
|
||||
}
|
||||
|
||||
impl Clone for ServerSettings {
|
||||
fn clone(&self) -> Self {
|
||||
@@ -33,49 +55,20 @@ impl Clone for ServerSettings {
|
||||
addr: self.addr,
|
||||
secure: self.secure,
|
||||
host: self.host.clone(),
|
||||
cpu_pool: self.cpu_pool.clone(),
|
||||
responses: HttpResponsePool::pool(),
|
||||
cpu_pool: LazyCell::new(),
|
||||
responses: HttpResponsePool::get_pool(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InnerCpuPool {
|
||||
cpu_pool: UnsafeCell<Option<CpuPool>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for InnerCpuPool {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "CpuPool")
|
||||
}
|
||||
}
|
||||
|
||||
impl InnerCpuPool {
|
||||
fn new() -> Self {
|
||||
InnerCpuPool {
|
||||
cpu_pool: UnsafeCell::new(None),
|
||||
}
|
||||
}
|
||||
fn cpu_pool(&self) -> &CpuPool {
|
||||
unsafe {
|
||||
let val = &mut *self.cpu_pool.get();
|
||||
if val.is_none() {
|
||||
*val = Some(Builder::new().create());
|
||||
}
|
||||
val.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for InnerCpuPool {}
|
||||
|
||||
impl Default for ServerSettings {
|
||||
fn default() -> Self {
|
||||
ServerSettings {
|
||||
addr: None,
|
||||
addr: "127.0.0.1:8080".parse().unwrap(),
|
||||
secure: false,
|
||||
host: "localhost:8080".to_owned(),
|
||||
responses: HttpResponsePool::pool(),
|
||||
cpu_pool: Arc::new(InnerCpuPool::new()),
|
||||
responses: HttpResponsePool::get_pool(),
|
||||
cpu_pool: LazyCell::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,17 +76,11 @@ impl Default for ServerSettings {
|
||||
impl ServerSettings {
|
||||
/// Crate server settings instance
|
||||
pub(crate) fn new(
|
||||
addr: Option<net::SocketAddr>, host: &Option<String>, secure: bool,
|
||||
addr: net::SocketAddr, host: &str, secure: bool,
|
||||
) -> ServerSettings {
|
||||
let host = if let Some(ref host) = *host {
|
||||
host.clone()
|
||||
} else if let Some(ref addr) = addr {
|
||||
format!("{}", addr)
|
||||
} else {
|
||||
"localhost".to_owned()
|
||||
};
|
||||
let cpu_pool = Arc::new(InnerCpuPool::new());
|
||||
let responses = HttpResponsePool::pool();
|
||||
let host = host.to_owned();
|
||||
let cpu_pool = LazyCell::new();
|
||||
let responses = HttpResponsePool::get_pool();
|
||||
ServerSettings {
|
||||
addr,
|
||||
secure,
|
||||
@@ -104,7 +91,7 @@ impl ServerSettings {
|
||||
}
|
||||
|
||||
/// Returns the socket address of the local half of this TCP connection
|
||||
pub fn local_addr(&self) -> Option<net::SocketAddr> {
|
||||
pub fn local_addr(&self) -> net::SocketAddr {
|
||||
self.addr
|
||||
}
|
||||
|
||||
@@ -120,7 +107,7 @@ impl ServerSettings {
|
||||
|
||||
/// Returns default `CpuPool` for server
|
||||
pub fn cpu_pool(&self) -> &CpuPool {
|
||||
self.cpu_pool.cpu_pool()
|
||||
self.cpu_pool.borrow_with(|| DEFAULT_CPUPOOL.lock().clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -139,99 +126,294 @@ impl ServerSettings {
|
||||
// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
||||
const DATE_VALUE_LENGTH: usize = 29;
|
||||
|
||||
pub(crate) struct WorkerSettings<H> {
|
||||
h: RefCell<Vec<H>>,
|
||||
keep_alive: u64,
|
||||
/// Http service configuration
|
||||
pub struct ServiceConfig<H>(Rc<Inner<H>>);
|
||||
|
||||
struct Inner<H> {
|
||||
handler: H,
|
||||
keep_alive: Option<Duration>,
|
||||
client_timeout: u64,
|
||||
client_shutdown: u64,
|
||||
ka_enabled: bool,
|
||||
bytes: Rc<SharedBytesPool>,
|
||||
messages: Rc<helpers::SharedMessagePool>,
|
||||
channels: Cell<usize>,
|
||||
node: Box<Node<()>>,
|
||||
date: UnsafeCell<Date>,
|
||||
messages: &'static RequestPool,
|
||||
date: Cell<Option<Date>>,
|
||||
}
|
||||
|
||||
impl<H> WorkerSettings<H> {
|
||||
pub(crate) fn new(h: Vec<H>, keep_alive: KeepAlive) -> WorkerSettings<H> {
|
||||
impl<H> Clone for ServiceConfig<H> {
|
||||
fn clone(&self) -> Self {
|
||||
ServiceConfig(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> ServiceConfig<H> {
|
||||
/// Create instance of `ServiceConfig`
|
||||
pub(crate) fn new(
|
||||
handler: H, keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64,
|
||||
settings: ServerSettings,
|
||||
) -> ServiceConfig<H> {
|
||||
let (keep_alive, ka_enabled) = match keep_alive {
|
||||
KeepAlive::Timeout(val) => (val as u64, true),
|
||||
KeepAlive::Os | KeepAlive::Tcp(_) => (0, true),
|
||||
KeepAlive::Disabled => (0, false),
|
||||
};
|
||||
let keep_alive = if ka_enabled && keep_alive > 0 {
|
||||
Some(Duration::from_secs(keep_alive))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
WorkerSettings {
|
||||
ServiceConfig(Rc::new(Inner {
|
||||
handler,
|
||||
keep_alive,
|
||||
ka_enabled,
|
||||
h: RefCell::new(h),
|
||||
client_timeout,
|
||||
client_shutdown,
|
||||
bytes: Rc::new(SharedBytesPool::new()),
|
||||
messages: Rc::new(helpers::SharedMessagePool::new()),
|
||||
channels: Cell::new(0),
|
||||
node: Box::new(Node::head()),
|
||||
date: UnsafeCell::new(Date::new()),
|
||||
}
|
||||
messages: RequestPool::pool(settings),
|
||||
date: Cell::new(None),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn num_channels(&self) -> usize {
|
||||
self.channels.get()
|
||||
/// Create worker settings builder.
|
||||
pub fn build(handler: H) -> ServiceConfigBuilder<H> {
|
||||
ServiceConfigBuilder::new(handler)
|
||||
}
|
||||
|
||||
pub fn head(&self) -> &Node<()> {
|
||||
&self.node
|
||||
pub(crate) fn handler(&self) -> &H {
|
||||
&self.0.handler
|
||||
}
|
||||
|
||||
pub fn handlers(&self) -> RefMut<Vec<H>> {
|
||||
self.h.borrow_mut()
|
||||
}
|
||||
|
||||
pub fn keep_alive(&self) -> u64 {
|
||||
self.keep_alive
|
||||
#[inline]
|
||||
/// Keep alive duration if configured.
|
||||
pub fn keep_alive(&self) -> Option<Duration> {
|
||||
self.0.keep_alive
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Return state of connection keep-alive funcitonality
|
||||
pub fn keep_alive_enabled(&self) -> bool {
|
||||
self.ka_enabled
|
||||
self.0.ka_enabled
|
||||
}
|
||||
|
||||
pub fn get_shared_bytes(&self) -> SharedBytes {
|
||||
SharedBytes::new(self.bytes.get_bytes(), Rc::clone(&self.bytes))
|
||||
pub(crate) fn get_bytes(&self) -> BytesMut {
|
||||
self.0.bytes.get_bytes()
|
||||
}
|
||||
|
||||
pub fn get_http_message(&self) -> helpers::SharedHttpInnerMessage {
|
||||
helpers::SharedHttpInnerMessage::new(
|
||||
self.messages.get(),
|
||||
Rc::clone(&self.messages),
|
||||
)
|
||||
pub(crate) fn release_bytes(&self, bytes: BytesMut) {
|
||||
self.0.bytes.release_bytes(bytes)
|
||||
}
|
||||
|
||||
pub fn add_channel(&self) {
|
||||
self.channels.set(self.channels.get() + 1);
|
||||
}
|
||||
|
||||
pub fn remove_channel(&self) {
|
||||
let num = self.channels.get();
|
||||
if num > 0 {
|
||||
self.channels.set(num - 1);
|
||||
} else {
|
||||
error!("Number of removed channels is bigger than added channel. Bug in actix-web");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_date(&self) {
|
||||
unsafe { &mut *self.date.get() }.update();
|
||||
}
|
||||
|
||||
pub fn set_date(&self, dst: &mut BytesMut) {
|
||||
let mut buf: [u8; 39] = unsafe { mem::uninitialized() };
|
||||
buf[..6].copy_from_slice(b"date: ");
|
||||
buf[6..35].copy_from_slice(&(unsafe { &*self.date.get() }.bytes));
|
||||
buf[35..].copy_from_slice(b"\r\n\r\n");
|
||||
dst.extend_from_slice(&buf);
|
||||
}
|
||||
|
||||
pub fn set_date_simple(&self, dst: &mut BytesMut) {
|
||||
dst.extend_from_slice(&(unsafe { &*self.date.get() }.bytes));
|
||||
pub(crate) fn get_request(&self) -> Request {
|
||||
RequestPool::get(self.0.messages)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: 'static> ServiceConfig<H> {
|
||||
#[inline]
|
||||
/// Client timeout for first request.
|
||||
pub fn client_timer(&self) -> Option<Delay> {
|
||||
let delay = self.0.client_timeout;
|
||||
if delay != 0 {
|
||||
Some(Delay::new(self.now() + Duration::from_millis(delay)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Client timeout for first request.
|
||||
pub fn client_timer_expire(&self) -> Option<Instant> {
|
||||
let delay = self.0.client_timeout;
|
||||
if delay != 0 {
|
||||
Some(self.now() + Duration::from_millis(delay))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Client shutdown timer
|
||||
pub fn client_shutdown_timer(&self) -> Option<Instant> {
|
||||
let delay = self.0.client_shutdown;
|
||||
if delay != 0 {
|
||||
Some(self.now() + Duration::from_millis(delay))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Return keep-alive timer delay is configured.
|
||||
pub fn keep_alive_timer(&self) -> Option<Delay> {
|
||||
if let Some(ka) = self.0.keep_alive {
|
||||
Some(Delay::new(self.now() + ka))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Keep-alive expire time
|
||||
pub fn keep_alive_expire(&self) -> Option<Instant> {
|
||||
if let Some(ka) = self.0.keep_alive {
|
||||
Some(self.now() + ka)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn check_date(&self) {
|
||||
if unsafe { &*self.0.date.as_ptr() }.is_none() {
|
||||
self.0.date.set(Some(Date::new()));
|
||||
|
||||
// periodic date update
|
||||
let s = self.clone();
|
||||
spawn(sleep(Duration::from_millis(500)).then(move |_| {
|
||||
s.0.date.set(None);
|
||||
future::ok(())
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_date(&self, dst: &mut BytesMut, full: bool) {
|
||||
self.check_date();
|
||||
|
||||
let date = &unsafe { &*self.0.date.as_ptr() }.as_ref().unwrap().bytes;
|
||||
|
||||
if full {
|
||||
let mut buf: [u8; 39] = [0; 39];
|
||||
buf[..6].copy_from_slice(b"date: ");
|
||||
buf[6..35].copy_from_slice(date);
|
||||
buf[35..].copy_from_slice(b"\r\n\r\n");
|
||||
dst.extend_from_slice(&buf);
|
||||
} else {
|
||||
dst.extend_from_slice(date);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn now(&self) -> Instant {
|
||||
self.check_date();
|
||||
unsafe { &*self.0.date.as_ptr() }.as_ref().unwrap().current
|
||||
}
|
||||
}
|
||||
|
||||
/// A service config builder
|
||||
///
|
||||
/// This type can be used to construct an instance of `ServiceConfig` through a
|
||||
/// builder-like pattern.
|
||||
pub struct ServiceConfigBuilder<H> {
|
||||
handler: H,
|
||||
keep_alive: KeepAlive,
|
||||
client_timeout: u64,
|
||||
client_shutdown: u64,
|
||||
host: String,
|
||||
addr: net::SocketAddr,
|
||||
secure: bool,
|
||||
}
|
||||
|
||||
impl<H> ServiceConfigBuilder<H> {
|
||||
/// Create instance of `ServiceConfigBuilder`
|
||||
pub fn new(handler: H) -> ServiceConfigBuilder<H> {
|
||||
ServiceConfigBuilder {
|
||||
handler,
|
||||
keep_alive: KeepAlive::Timeout(5),
|
||||
client_timeout: 5000,
|
||||
client_shutdown: 5000,
|
||||
secure: false,
|
||||
host: "localhost".to_owned(),
|
||||
addr: "127.0.0.1:8080".parse().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable secure flag for current server.
|
||||
///
|
||||
/// By default this flag is set to false.
|
||||
pub fn secure(mut self) -> Self {
|
||||
self.secure = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set server keep-alive setting.
|
||||
///
|
||||
/// By default keep alive is set to a 5 seconds.
|
||||
pub fn keep_alive<T: Into<KeepAlive>>(mut self, val: T) -> Self {
|
||||
self.keep_alive = val.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set server client timeout in milliseconds for first request.
|
||||
///
|
||||
/// Defines a timeout for reading client request header. If a client does not transmit
|
||||
/// the entire set headers within this time, the request is terminated with
|
||||
/// the 408 (Request Time-out) error.
|
||||
///
|
||||
/// To disable timeout set value to 0.
|
||||
///
|
||||
/// By default client timeout is set to 5000 milliseconds.
|
||||
pub fn client_timeout(mut self, val: u64) -> Self {
|
||||
self.client_timeout = val;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set server connection shutdown timeout in milliseconds.
|
||||
///
|
||||
/// Defines a timeout for shutdown connection. If a shutdown procedure does not complete
|
||||
/// within this time, the request is dropped. This timeout affects only secure connections.
|
||||
///
|
||||
/// To disable timeout set value to 0.
|
||||
///
|
||||
/// By default client timeout is set to 5000 milliseconds.
|
||||
pub fn client_shutdown(mut self, val: u64) -> Self {
|
||||
self.client_shutdown = val;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set server host name.
|
||||
///
|
||||
/// Host name is used by application router aa a hostname for url
|
||||
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
|
||||
/// html#method.host) documentation for more information.
|
||||
///
|
||||
/// By default host name is set to a "localhost" value.
|
||||
pub fn server_hostname(mut self, val: &str) -> Self {
|
||||
self.host = val.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set server ip address.
|
||||
///
|
||||
/// Host name is used by application router aa a hostname for url
|
||||
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
|
||||
/// html#method.host) documentation for more information.
|
||||
///
|
||||
/// By default server address is set to a "127.0.0.1:8080"
|
||||
pub fn server_address<S: net::ToSocketAddrs>(mut self, addr: S) -> Self {
|
||||
match addr.to_socket_addrs() {
|
||||
Err(err) => error!("Can not convert to SocketAddr: {}", err),
|
||||
Ok(mut addrs) => if let Some(addr) = addrs.next() {
|
||||
self.addr = addr;
|
||||
},
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Finish service configuration and create `ServiceConfig` object.
|
||||
pub fn finish(self) -> ServiceConfig<H> {
|
||||
let settings = ServerSettings::new(self.addr, &self.host, self.secure);
|
||||
let client_shutdown = if self.secure { self.client_shutdown } else { 0 };
|
||||
|
||||
ServiceConfig::new(
|
||||
self.handler,
|
||||
self.keep_alive,
|
||||
self.client_timeout,
|
||||
client_shutdown,
|
||||
settings,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct Date {
|
||||
current: Instant,
|
||||
bytes: [u8; DATE_VALUE_LENGTH],
|
||||
pos: usize,
|
||||
}
|
||||
@@ -239,6 +421,7 @@ struct Date {
|
||||
impl Date {
|
||||
fn new() -> Date {
|
||||
let mut date = Date {
|
||||
current: Instant::now(),
|
||||
bytes: [0; DATE_VALUE_LENGTH],
|
||||
pos: 0,
|
||||
};
|
||||
@@ -247,6 +430,7 @@ impl Date {
|
||||
}
|
||||
fn update(&mut self) {
|
||||
self.pos = 0;
|
||||
self.current = Instant::now();
|
||||
write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -260,9 +444,36 @@ impl fmt::Write for Date {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SharedBytesPool(RefCell<VecDeque<BytesMut>>);
|
||||
|
||||
impl SharedBytesPool {
|
||||
pub fn new() -> SharedBytesPool {
|
||||
SharedBytesPool(RefCell::new(VecDeque::with_capacity(128)))
|
||||
}
|
||||
|
||||
pub fn get_bytes(&self) -> BytesMut {
|
||||
if let Some(bytes) = self.0.borrow_mut().pop_front() {
|
||||
bytes
|
||||
} else {
|
||||
BytesMut::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn release_bytes(&self, mut bytes: BytesMut) {
|
||||
let v = &mut self.0.borrow_mut();
|
||||
if v.len() < 128 {
|
||||
bytes.clear();
|
||||
v.push_front(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures::future;
|
||||
use tokio::runtime::current_thread;
|
||||
|
||||
#[test]
|
||||
fn test_date_len() {
|
||||
@@ -271,11 +482,22 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_date() {
|
||||
let settings = WorkerSettings::<()>::new(Vec::new(), KeepAlive::Os);
|
||||
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
||||
settings.set_date(&mut buf1);
|
||||
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
||||
settings.set_date(&mut buf2);
|
||||
assert_eq!(buf1, buf2);
|
||||
let mut rt = current_thread::Runtime::new().unwrap();
|
||||
|
||||
let _ = rt.block_on(future::lazy(|| {
|
||||
let settings = ServiceConfig::<()>::new(
|
||||
(),
|
||||
KeepAlive::Os,
|
||||
0,
|
||||
0,
|
||||
ServerSettings::default(),
|
||||
);
|
||||
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
||||
settings.set_date(&mut buf1, true);
|
||||
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
||||
settings.set_date(&mut buf2, true);
|
||||
assert_eq!(buf1, buf2);
|
||||
future::ok::<_, ()>(())
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::io;
|
||||
use std::rc::Rc;
|
||||
|
||||
use body::Binary;
|
||||
|
||||
/// Internal use only! unsafe
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SharedBytesPool(RefCell<VecDeque<Rc<BytesMut>>>);
|
||||
|
||||
impl SharedBytesPool {
|
||||
pub fn new() -> SharedBytesPool {
|
||||
SharedBytesPool(RefCell::new(VecDeque::with_capacity(128)))
|
||||
}
|
||||
|
||||
pub fn get_bytes(&self) -> Rc<BytesMut> {
|
||||
if let Some(bytes) = self.0.borrow_mut().pop_front() {
|
||||
bytes
|
||||
} else {
|
||||
Rc::new(BytesMut::new())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn release_bytes(&self, mut bytes: Rc<BytesMut>) {
|
||||
let v = &mut self.0.borrow_mut();
|
||||
if v.len() < 128 {
|
||||
Rc::get_mut(&mut bytes).unwrap().clear();
|
||||
v.push_front(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SharedBytes(Option<Rc<BytesMut>>, Option<Rc<SharedBytesPool>>);
|
||||
|
||||
impl Drop for SharedBytes {
|
||||
fn drop(&mut self) {
|
||||
if let Some(pool) = self.1.take() {
|
||||
if let Some(bytes) = self.0.take() {
|
||||
if Rc::strong_count(&bytes) == 1 {
|
||||
pool.release_bytes(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SharedBytes {
|
||||
pub fn empty() -> Self {
|
||||
SharedBytes(None, None)
|
||||
}
|
||||
|
||||
pub fn new(bytes: Rc<BytesMut>, pool: Rc<SharedBytesPool>) -> SharedBytes {
|
||||
SharedBytes(Some(bytes), Some(pool))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(mutable_transmutes)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
|
||||
pub(crate) fn get_mut(&self) -> &mut BytesMut {
|
||||
let r: &BytesMut = self.0.as_ref().unwrap().as_ref();
|
||||
unsafe { &mut *(r as *const _ as *mut _) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.as_ref().unwrap().len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.as_ref().unwrap().is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref().unwrap().as_ref()
|
||||
}
|
||||
|
||||
pub fn split_to(&self, n: usize) -> BytesMut {
|
||||
self.get_mut().split_to(n)
|
||||
}
|
||||
|
||||
pub fn take(&self) -> BytesMut {
|
||||
self.get_mut().take()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reserve(&self, cnt: usize) {
|
||||
self.get_mut().reserve(cnt)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
|
||||
pub fn extend(&self, data: Binary) {
|
||||
let buf = self.get_mut();
|
||||
let data = data.as_ref();
|
||||
buf.reserve(data.len());
|
||||
SharedBytes::put_slice(buf, data);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn extend_from_slice(&self, data: &[u8]) {
|
||||
let buf = self.get_mut();
|
||||
buf.reserve(data.len());
|
||||
SharedBytes::put_slice(buf, data);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn put_slice(buf: &mut BytesMut, src: &[u8]) {
|
||||
let len = src.len();
|
||||
unsafe {
|
||||
buf.bytes_mut()[..len].copy_from_slice(src);
|
||||
buf.advance_mut(len);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn extend_from_slice_(buf: &mut BytesMut, data: &[u8]) {
|
||||
buf.reserve(data.len());
|
||||
SharedBytes::put_slice(buf, data);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SharedBytes {
|
||||
fn default() -> Self {
|
||||
SharedBytes(Some(Rc::new(BytesMut::new())), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for SharedBytes {
|
||||
fn clone(&self) -> SharedBytes {
|
||||
SharedBytes(self.0.clone(), self.1.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for SharedBytes {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,974 +0,0 @@
|
||||
use std::rc::Rc;
|
||||
use std::sync::{mpsc as sync_mpsc, Arc};
|
||||
use std::time::Duration;
|
||||
use std::{io, net, thread};
|
||||
|
||||
use actix::actors::signal;
|
||||
use actix::prelude::*;
|
||||
use futures::sync::mpsc;
|
||||
use futures::{Future, Sink, Stream};
|
||||
use mio;
|
||||
use net2::TcpBuilder;
|
||||
use num_cpus;
|
||||
use slab::Slab;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
use native_tls::TlsAcceptor;
|
||||
|
||||
#[cfg(feature = "alpn")]
|
||||
use openssl::ssl::{AlpnError, SslAcceptorBuilder};
|
||||
|
||||
use super::channel::{HttpChannel, WrapperStream};
|
||||
use super::settings::{ServerSettings, WorkerSettings};
|
||||
use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker};
|
||||
use super::{IntoHttpHandler, IoStream, KeepAlive};
|
||||
use super::{PauseServer, ResumeServer, StopServer};
|
||||
|
||||
#[cfg(feature = "alpn")]
|
||||
fn configure_alpn(builder: &mut SslAcceptorBuilder) -> io::Result<()> {
|
||||
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
|
||||
builder.set_alpn_select_callback(|_, protos| {
|
||||
const H2: &[u8] = b"\x02h2";
|
||||
if protos.windows(3).any(|window| window == H2) {
|
||||
Ok(b"h2")
|
||||
} else {
|
||||
Err(AlpnError::NOACK)
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// An HTTP Server
|
||||
pub struct HttpServer<H>
|
||||
where
|
||||
H: IntoHttpHandler + 'static,
|
||||
{
|
||||
h: Option<Rc<WorkerSettings<H::Handler>>>,
|
||||
threads: usize,
|
||||
backlog: i32,
|
||||
host: Option<String>,
|
||||
keep_alive: KeepAlive,
|
||||
factory: Arc<Fn() -> Vec<H> + Send + Sync>,
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
|
||||
workers: Vec<(usize, Addr<Syn, Worker<H::Handler>>)>,
|
||||
sockets: Vec<Socket>,
|
||||
accept: Vec<(mio::SetReadiness, sync_mpsc::Sender<Command>)>,
|
||||
exit: bool,
|
||||
shutdown_timeout: u16,
|
||||
signals: Option<Addr<Syn, signal::ProcessSignals>>,
|
||||
no_http2: bool,
|
||||
no_signals: bool,
|
||||
}
|
||||
|
||||
unsafe impl<H> Sync for HttpServer<H>
|
||||
where
|
||||
H: IntoHttpHandler,
|
||||
{
|
||||
}
|
||||
unsafe impl<H> Send for HttpServer<H>
|
||||
where
|
||||
H: IntoHttpHandler,
|
||||
{
|
||||
}
|
||||
|
||||
enum ServerCommand {
|
||||
WorkerDied(usize, Slab<SocketInfo>),
|
||||
}
|
||||
|
||||
impl<H> Actor for HttpServer<H>
|
||||
where
|
||||
H: IntoHttpHandler,
|
||||
{
|
||||
type Context = Context<Self>;
|
||||
}
|
||||
|
||||
struct Socket {
|
||||
lst: net::TcpListener,
|
||||
addr: net::SocketAddr,
|
||||
tp: StreamHandlerType,
|
||||
}
|
||||
|
||||
impl<H> HttpServer<H>
|
||||
where
|
||||
H: IntoHttpHandler + 'static,
|
||||
{
|
||||
/// Create new http server with application factory
|
||||
pub fn new<F, U>(factory: F) -> Self
|
||||
where
|
||||
F: Fn() -> U + Sync + Send + 'static,
|
||||
U: IntoIterator<Item = H> + 'static,
|
||||
{
|
||||
let f = move || (factory)().into_iter().collect();
|
||||
|
||||
HttpServer {
|
||||
h: None,
|
||||
threads: num_cpus::get(),
|
||||
backlog: 2048,
|
||||
host: None,
|
||||
keep_alive: KeepAlive::Os,
|
||||
factory: Arc::new(f),
|
||||
workers: Vec::new(),
|
||||
sockets: Vec::new(),
|
||||
accept: Vec::new(),
|
||||
exit: false,
|
||||
shutdown_timeout: 30,
|
||||
signals: None,
|
||||
no_http2: false,
|
||||
no_signals: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set number of workers to start.
|
||||
///
|
||||
/// By default http server uses number of available logical cpu as threads
|
||||
/// count.
|
||||
pub fn workers(mut self, num: usize) -> Self {
|
||||
self.threads = num;
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "0.6.0", note = "please use `HttpServer::workers()` instead")]
|
||||
pub fn threads(self, num: usize) -> Self {
|
||||
self.workers(num)
|
||||
}
|
||||
|
||||
/// Set the maximum number of pending connections.
|
||||
///
|
||||
/// This refers to the number of clients that can be waiting to be served.
|
||||
/// Exceeding this number results in the client getting an error when
|
||||
/// attempting to connect. It should only affect servers under significant
|
||||
/// load.
|
||||
///
|
||||
/// Generally set in the 64-2048 range. Default value is 2048.
|
||||
///
|
||||
/// This method should be called before `bind()` method call.
|
||||
pub fn backlog(mut self, num: i32) -> Self {
|
||||
self.backlog = num;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set server keep-alive setting.
|
||||
///
|
||||
/// By default keep alive is set to a `Os`.
|
||||
pub fn keep_alive<T: Into<KeepAlive>>(mut self, val: T) -> Self {
|
||||
self.keep_alive = val.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set server host name.
|
||||
///
|
||||
/// Host name is used by application router aa a hostname for url
|
||||
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
|
||||
/// html#method.host) documentation for more information.
|
||||
pub fn server_hostname(mut self, val: String) -> Self {
|
||||
self.host = Some(val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Send `SystemExit` message to actix system
|
||||
///
|
||||
/// `SystemExit` message stops currently running system arbiter and all
|
||||
/// nested arbiters.
|
||||
pub fn system_exit(mut self) -> Self {
|
||||
self.exit = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set alternative address for `ProcessSignals` actor.
|
||||
pub fn signals(mut self, addr: Addr<Syn, signal::ProcessSignals>) -> Self {
|
||||
self.signals = Some(addr);
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable signal handling
|
||||
pub fn disable_signals(mut self) -> Self {
|
||||
self.no_signals = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Timeout for graceful workers shutdown.
|
||||
///
|
||||
/// After receiving a stop signal, workers have this much time to finish
|
||||
/// serving requests. Workers still alive after the timeout are force
|
||||
/// dropped.
|
||||
///
|
||||
/// By default shutdown timeout sets to 30 seconds.
|
||||
pub fn shutdown_timeout(mut self, sec: u16) -> Self {
|
||||
self.shutdown_timeout = sec;
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable `HTTP/2` support
|
||||
pub fn no_http2(mut self) -> Self {
|
||||
self.no_http2 = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Get addresses of bound sockets.
|
||||
pub fn addrs(&self) -> Vec<net::SocketAddr> {
|
||||
self.sockets.iter().map(|s| s.addr).collect()
|
||||
}
|
||||
|
||||
/// Get addresses of bound sockets and the scheme for it.
|
||||
///
|
||||
/// This is useful when the server is bound from different sources
|
||||
/// with some sockets listening on http and some listening on https
|
||||
/// and the user should be presented with an enumeration of which
|
||||
/// socket requires which protocol.
|
||||
pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> {
|
||||
self.sockets
|
||||
.iter()
|
||||
.map(|s| (s.addr, s.tp.scheme()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Use listener for accepting incoming connection requests
|
||||
///
|
||||
/// HttpServer does not change any configuration for TcpListener,
|
||||
/// it needs to be configured before passing it to listen() method.
|
||||
pub fn listen(mut self, lst: net::TcpListener) -> Self {
|
||||
let addr = lst.local_addr().unwrap();
|
||||
self.sockets.push(Socket {
|
||||
addr,
|
||||
lst,
|
||||
tp: StreamHandlerType::Normal,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
/// Use listener for accepting incoming tls connection requests
|
||||
///
|
||||
/// HttpServer does not change any configuration for TcpListener,
|
||||
/// it needs to be configured before passing it to listen() method.
|
||||
pub fn listen_tls(mut self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self {
|
||||
let addr = lst.local_addr().unwrap();
|
||||
self.sockets.push(Socket {
|
||||
addr,
|
||||
lst,
|
||||
tp: StreamHandlerType::Tls(acceptor.clone()),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "alpn")]
|
||||
/// Use listener for accepting incoming tls connection requests
|
||||
///
|
||||
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||
pub fn listen_ssl(
|
||||
mut self, lst: net::TcpListener, mut builder: SslAcceptorBuilder,
|
||||
) -> io::Result<Self> {
|
||||
// alpn support
|
||||
if !self.no_http2 {
|
||||
configure_alpn(&mut builder)?;
|
||||
}
|
||||
let acceptor = builder.build();
|
||||
let addr = lst.local_addr().unwrap();
|
||||
self.sockets.push(Socket {
|
||||
addr,
|
||||
lst,
|
||||
tp: StreamHandlerType::Alpn(acceptor.clone()),
|
||||
});
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn bind2<S: net::ToSocketAddrs>(&mut self, addr: S) -> io::Result<Vec<Socket>> {
|
||||
let mut err = None;
|
||||
let mut succ = false;
|
||||
let mut sockets = Vec::new();
|
||||
for addr in addr.to_socket_addrs()? {
|
||||
match create_tcp_listener(addr, self.backlog) {
|
||||
Ok(lst) => {
|
||||
succ = true;
|
||||
let addr = lst.local_addr().unwrap();
|
||||
sockets.push(Socket {
|
||||
lst,
|
||||
addr,
|
||||
tp: StreamHandlerType::Normal,
|
||||
});
|
||||
}
|
||||
Err(e) => err = Some(e),
|
||||
}
|
||||
}
|
||||
|
||||
if !succ {
|
||||
if let Some(e) = err.take() {
|
||||
Err(e)
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Can not bind to address.",
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(sockets)
|
||||
}
|
||||
}
|
||||
|
||||
/// The socket address to bind
|
||||
///
|
||||
/// To bind multiple addresses this method can be called multiple times.
|
||||
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> {
|
||||
let sockets = self.bind2(addr)?;
|
||||
self.sockets.extend(sockets);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
/// The ssl socket address to bind
|
||||
///
|
||||
/// To bind multiple addresses this method can be called multiple times.
|
||||
pub fn bind_tls<S: net::ToSocketAddrs>(
|
||||
mut self, addr: S, acceptor: TlsAcceptor,
|
||||
) -> io::Result<Self> {
|
||||
let sockets = self.bind2(addr)?;
|
||||
self.sockets.extend(sockets.into_iter().map(|mut s| {
|
||||
s.tp = StreamHandlerType::Tls(acceptor.clone());
|
||||
s
|
||||
}));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "alpn")]
|
||||
/// Start listening for incoming tls connections.
|
||||
///
|
||||
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||
pub fn bind_ssl<S: net::ToSocketAddrs>(
|
||||
mut self, addr: S, mut builder: SslAcceptorBuilder,
|
||||
) -> io::Result<Self> {
|
||||
// alpn support
|
||||
if !self.no_http2 {
|
||||
configure_alpn(&mut builder)?;
|
||||
}
|
||||
|
||||
let acceptor = builder.build();
|
||||
let sockets = self.bind2(addr)?;
|
||||
self.sockets.extend(sockets.into_iter().map(|mut s| {
|
||||
s.tp = StreamHandlerType::Alpn(acceptor.clone());
|
||||
s
|
||||
}));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn start_workers(
|
||||
&mut self, settings: &ServerSettings, sockets: &Slab<SocketInfo>,
|
||||
) -> Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)> {
|
||||
// start workers
|
||||
let mut workers = Vec::new();
|
||||
for idx in 0..self.threads {
|
||||
let s = settings.clone();
|
||||
let (tx, rx) = mpsc::unbounded::<Conn<net::TcpStream>>();
|
||||
|
||||
let ka = self.keep_alive;
|
||||
let socks = sockets.clone();
|
||||
let factory = Arc::clone(&self.factory);
|
||||
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
|
||||
let apps: Vec<_> = (*factory)()
|
||||
.into_iter()
|
||||
.map(|h| h.into_handler(s.clone()))
|
||||
.collect();
|
||||
ctx.add_message_stream(rx);
|
||||
Worker::new(apps, socks, ka)
|
||||
});
|
||||
workers.push((idx, tx));
|
||||
self.workers.push((idx, addr));
|
||||
}
|
||||
info!("Starting {} http workers", self.threads);
|
||||
workers
|
||||
}
|
||||
|
||||
// subscribe to os signals
|
||||
fn subscribe_to_signals(&self) -> Option<Addr<Syn, signal::ProcessSignals>> {
|
||||
if !self.no_signals {
|
||||
if let Some(ref signals) = self.signals {
|
||||
Some(signals.clone())
|
||||
} else {
|
||||
Some(Arbiter::system_registry().get::<signal::ProcessSignals>())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: IntoHttpHandler> HttpServer<H> {
|
||||
/// Start listening for incoming connections.
|
||||
///
|
||||
/// This method starts number of http handler workers in separate threads.
|
||||
/// For each address this method starts separate thread which does
|
||||
/// `accept()` in a loop.
|
||||
///
|
||||
/// This methods panics if no socket addresses get bound.
|
||||
///
|
||||
/// This method requires to run within properly configured `Actix` system.
|
||||
///
|
||||
/// ```rust
|
||||
/// extern crate actix;
|
||||
/// extern crate actix_web;
|
||||
/// use actix_web::{server, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let sys = actix::System::new("example"); // <- create Actix system
|
||||
///
|
||||
/// server::new(
|
||||
/// || App::new()
|
||||
/// .resource("/", |r| r.h(|_| HttpResponse::Ok())))
|
||||
/// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0")
|
||||
/// .start();
|
||||
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
///
|
||||
/// let _ = sys.run(); // <- Run actix system, this method actually starts all async processes
|
||||
/// }
|
||||
/// ```
|
||||
pub fn start(mut self) -> Addr<Syn, Self> {
|
||||
if self.sockets.is_empty() {
|
||||
panic!("HttpServer::bind() has to be called before start()");
|
||||
} else {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
|
||||
let mut socks = Slab::new();
|
||||
let mut addrs: Vec<(usize, Socket)> = Vec::new();
|
||||
|
||||
for socket in self.sockets.drain(..) {
|
||||
let entry = socks.vacant_entry();
|
||||
let token = entry.key();
|
||||
entry.insert(SocketInfo {
|
||||
addr: socket.addr,
|
||||
htype: socket.tp.clone(),
|
||||
});
|
||||
addrs.push((token, socket));
|
||||
}
|
||||
|
||||
let settings = ServerSettings::new(Some(addrs[0].1.addr), &self.host, false);
|
||||
let workers = self.start_workers(&settings, &socks);
|
||||
|
||||
// start acceptors threads
|
||||
for (token, sock) in addrs {
|
||||
info!("Starting server on http://{}", sock.addr);
|
||||
self.accept.push(start_accept_thread(
|
||||
token,
|
||||
sock,
|
||||
tx.clone(),
|
||||
socks.clone(),
|
||||
workers.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
// start http server actor
|
||||
let signals = self.subscribe_to_signals();
|
||||
let addr: Addr<Syn, _> = Actor::create(move |ctx| {
|
||||
ctx.add_stream(rx);
|
||||
self
|
||||
});
|
||||
if let Some(signals) = signals {
|
||||
signals.do_send(signal::Subscribe(addr.clone().recipient()))
|
||||
}
|
||||
addr
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn new thread and start listening for incoming connections.
|
||||
///
|
||||
/// This method spawns new thread and starts new actix system. Other than
|
||||
/// that it is similar to `start()` method. This method blocks.
|
||||
///
|
||||
/// This methods panics if no socket addresses get bound.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # extern crate futures;
|
||||
/// # extern crate actix;
|
||||
/// # extern crate actix_web;
|
||||
/// # use futures::Future;
|
||||
/// use actix_web::*;
|
||||
///
|
||||
/// fn main() {
|
||||
/// HttpServer::new(
|
||||
/// || App::new()
|
||||
/// .resource("/", |r| r.h(|_| HttpResponse::Ok())))
|
||||
/// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0")
|
||||
/// .run();
|
||||
/// }
|
||||
/// ```
|
||||
pub fn run(mut self) {
|
||||
self.exit = true;
|
||||
self.no_signals = false;
|
||||
|
||||
let _ = thread::spawn(move || {
|
||||
let sys = System::new("http-server");
|
||||
self.start();
|
||||
let _ = sys.run();
|
||||
}).join();
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "tls")]
|
||||
#[deprecated(
|
||||
since = "0.6.0", note = "please use `actix_web::HttpServer::bind_tls` instead"
|
||||
)]
|
||||
impl<H: IntoHttpHandler> HttpServer<H> {
|
||||
/// Start listening for incoming tls connections.
|
||||
pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result<Addr<Syn, Self>> {
|
||||
for sock in &mut self.sockets {
|
||||
match sock.tp {
|
||||
StreamHandlerType::Normal => (),
|
||||
_ => continue,
|
||||
}
|
||||
sock.tp = StreamHandlerType::Tls(acceptor.clone());
|
||||
}
|
||||
Ok(self.start())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "alpn")]
|
||||
#[deprecated(
|
||||
since = "0.6.0", note = "please use `actix_web::HttpServer::bind_ssl` instead"
|
||||
)]
|
||||
impl<H: IntoHttpHandler> HttpServer<H> {
|
||||
/// Start listening for incoming tls connections.
|
||||
///
|
||||
/// This method sets alpn protocols to "h2" and "http/1.1"
|
||||
pub fn start_ssl(
|
||||
mut self, mut builder: SslAcceptorBuilder,
|
||||
) -> io::Result<Addr<Syn, Self>> {
|
||||
// alpn support
|
||||
if !self.no_http2 {
|
||||
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
|
||||
builder.set_alpn_select_callback(|_, protos| {
|
||||
const H2: &[u8] = b"\x02h2";
|
||||
if protos.windows(3).any(|window| window == H2) {
|
||||
Ok(b"h2")
|
||||
} else {
|
||||
Err(AlpnError::NOACK)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let acceptor = builder.build();
|
||||
for sock in &mut self.sockets {
|
||||
match sock.tp {
|
||||
StreamHandlerType::Normal => (),
|
||||
_ => continue,
|
||||
}
|
||||
sock.tp = StreamHandlerType::Alpn(acceptor.clone());
|
||||
}
|
||||
Ok(self.start())
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: IntoHttpHandler> HttpServer<H> {
|
||||
/// Start listening for incoming connections from a stream.
|
||||
///
|
||||
/// This method uses only one thread for handling incoming connections.
|
||||
pub fn start_incoming<T, A, S>(mut self, stream: S, secure: bool) -> Addr<Syn, Self>
|
||||
where
|
||||
S: Stream<Item = (T, A), Error = io::Error> + 'static,
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
A: 'static,
|
||||
{
|
||||
// set server settings
|
||||
let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
|
||||
let settings = ServerSettings::new(Some(addr), &self.host, secure);
|
||||
let apps: Vec<_> = (*self.factory)()
|
||||
.into_iter()
|
||||
.map(|h| h.into_handler(settings.clone()))
|
||||
.collect();
|
||||
self.h = Some(Rc::new(WorkerSettings::new(apps, self.keep_alive)));
|
||||
|
||||
// start server
|
||||
let signals = self.subscribe_to_signals();
|
||||
let addr: Addr<Syn, _> = HttpServer::create(move |ctx| {
|
||||
ctx.add_message_stream(stream.map_err(|_| ()).map(move |(t, _)| Conn {
|
||||
io: WrapperStream::new(t),
|
||||
token: 0,
|
||||
peer: None,
|
||||
http2: false,
|
||||
}));
|
||||
self
|
||||
});
|
||||
if let Some(signals) = signals {
|
||||
signals.do_send(signal::Subscribe(addr.clone().recipient()))
|
||||
}
|
||||
addr
|
||||
}
|
||||
}
|
||||
|
||||
/// Signals support
|
||||
/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and send `SystemExit(0)`
|
||||
/// message to `System` actor.
|
||||
impl<H: IntoHttpHandler> Handler<signal::Signal> for HttpServer<H> {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: signal::Signal, ctx: &mut Context<Self>) {
|
||||
match msg.0 {
|
||||
signal::SignalType::Int => {
|
||||
info!("SIGINT received, exiting");
|
||||
self.exit = true;
|
||||
Handler::<StopServer>::handle(self, StopServer { graceful: false }, ctx);
|
||||
}
|
||||
signal::SignalType::Term => {
|
||||
info!("SIGTERM received, stopping");
|
||||
self.exit = true;
|
||||
Handler::<StopServer>::handle(self, StopServer { graceful: true }, ctx);
|
||||
}
|
||||
signal::SignalType::Quit => {
|
||||
info!("SIGQUIT received, exiting");
|
||||
self.exit = true;
|
||||
Handler::<StopServer>::handle(self, StopServer { graceful: false }, ctx);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Commands from accept threads
|
||||
impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
|
||||
fn finished(&mut self, _: &mut Context<Self>) {}
|
||||
fn handle(&mut self, msg: ServerCommand, _: &mut Context<Self>) {
|
||||
match msg {
|
||||
ServerCommand::WorkerDied(idx, socks) => {
|
||||
let mut found = false;
|
||||
for i in 0..self.workers.len() {
|
||||
if self.workers[i].0 == idx {
|
||||
self.workers.swap_remove(i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
error!("Worker has died {:?}, restarting", idx);
|
||||
let (tx, rx) = mpsc::unbounded::<Conn<net::TcpStream>>();
|
||||
|
||||
let mut new_idx = self.workers.len();
|
||||
'found: loop {
|
||||
for i in 0..self.workers.len() {
|
||||
if self.workers[i].0 == new_idx {
|
||||
new_idx += 1;
|
||||
continue 'found;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let ka = self.keep_alive;
|
||||
let factory = Arc::clone(&self.factory);
|
||||
let settings =
|
||||
ServerSettings::new(Some(socks[0].addr), &self.host, false);
|
||||
|
||||
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
|
||||
let apps: Vec<_> = (*factory)()
|
||||
.into_iter()
|
||||
.map(|h| h.into_handler(settings.clone()))
|
||||
.collect();
|
||||
ctx.add_message_stream(rx);
|
||||
Worker::new(apps, socks, ka)
|
||||
});
|
||||
for item in &self.accept {
|
||||
let _ = item.1.send(Command::Worker(new_idx, tx.clone()));
|
||||
let _ = item.0.set_readiness(mio::Ready::readable());
|
||||
}
|
||||
|
||||
self.workers.push((new_idx, addr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, H> Handler<Conn<T>> for HttpServer<H>
|
||||
where
|
||||
T: IoStream,
|
||||
H: IntoHttpHandler,
|
||||
{
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: Conn<T>, _: &mut Context<Self>) -> Self::Result {
|
||||
Arbiter::handle().spawn(HttpChannel::new(
|
||||
Rc::clone(self.h.as_ref().unwrap()),
|
||||
msg.io,
|
||||
msg.peer,
|
||||
msg.http2,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: IntoHttpHandler> Handler<PauseServer> for HttpServer<H> {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, _: PauseServer, _: &mut Context<Self>) {
|
||||
for item in &self.accept {
|
||||
let _ = item.1.send(Command::Pause);
|
||||
let _ = item.0.set_readiness(mio::Ready::readable());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: IntoHttpHandler> Handler<ResumeServer> for HttpServer<H> {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, _: ResumeServer, _: &mut Context<Self>) {
|
||||
for item in &self.accept {
|
||||
let _ = item.1.send(Command::Resume);
|
||||
let _ = item.0.set_readiness(mio::Ready::readable());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: IntoHttpHandler> Handler<StopServer> for HttpServer<H> {
|
||||
type Result = actix::Response<(), ()>;
|
||||
|
||||
fn handle(&mut self, msg: StopServer, ctx: &mut Context<Self>) -> Self::Result {
|
||||
// stop accept threads
|
||||
for item in &self.accept {
|
||||
let _ = item.1.send(Command::Stop);
|
||||
let _ = item.0.set_readiness(mio::Ready::readable());
|
||||
}
|
||||
|
||||
// stop workers
|
||||
let (tx, rx) = mpsc::channel(1);
|
||||
|
||||
let dur = if msg.graceful {
|
||||
Some(Duration::new(u64::from(self.shutdown_timeout), 0))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
for worker in &self.workers {
|
||||
let tx2 = tx.clone();
|
||||
worker
|
||||
.1
|
||||
.send(StopWorker { graceful: dur })
|
||||
.into_actor(self)
|
||||
.then(move |_, slf, ctx| {
|
||||
slf.workers.pop();
|
||||
if slf.workers.is_empty() {
|
||||
let _ = tx2.send(());
|
||||
|
||||
// we need to stop system if server was spawned
|
||||
if slf.exit {
|
||||
ctx.run_later(Duration::from_millis(300), |_, _| {
|
||||
Arbiter::system().do_send(actix::msgs::SystemExit(0))
|
||||
});
|
||||
}
|
||||
}
|
||||
actix::fut::ok(())
|
||||
})
|
||||
.spawn(ctx);
|
||||
}
|
||||
|
||||
if !self.workers.is_empty() {
|
||||
Response::async(rx.into_future().map(|_| ()).map_err(|_| ()))
|
||||
} else {
|
||||
// we need to stop system if server was spawned
|
||||
if self.exit {
|
||||
ctx.run_later(Duration::from_millis(300), |_, _| {
|
||||
Arbiter::system().do_send(actix::msgs::SystemExit(0))
|
||||
});
|
||||
}
|
||||
Response::reply(Ok(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Command {
|
||||
Pause,
|
||||
Resume,
|
||||
Stop,
|
||||
Worker(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>),
|
||||
}
|
||||
|
||||
fn start_accept_thread(
|
||||
token: usize, sock: Socket, srv: mpsc::UnboundedSender<ServerCommand>,
|
||||
socks: Slab<SocketInfo>,
|
||||
mut workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>,
|
||||
) -> (mio::SetReadiness, sync_mpsc::Sender<Command>) {
|
||||
let (tx, rx) = sync_mpsc::channel();
|
||||
let (reg, readiness) = mio::Registration::new2();
|
||||
|
||||
// start accept thread
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
|
||||
let _ = thread::Builder::new()
|
||||
.name(format!("Accept on {}", sock.addr))
|
||||
.spawn(move || {
|
||||
const SRV: mio::Token = mio::Token(0);
|
||||
const CMD: mio::Token = mio::Token(1);
|
||||
|
||||
let addr = sock.addr;
|
||||
let mut server = Some(
|
||||
mio::net::TcpListener::from_std(sock.lst)
|
||||
.expect("Can not create mio::net::TcpListener"),
|
||||
);
|
||||
|
||||
// Create a poll instance
|
||||
let poll = match mio::Poll::new() {
|
||||
Ok(poll) => poll,
|
||||
Err(err) => panic!("Can not create mio::Poll: {}", err),
|
||||
};
|
||||
|
||||
// Start listening for incoming connections
|
||||
if let Some(ref srv) = server {
|
||||
if let Err(err) =
|
||||
poll.register(srv, SRV, mio::Ready::readable(), mio::PollOpt::edge())
|
||||
{
|
||||
panic!("Can not register io: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Start listening for incoming commands
|
||||
if let Err(err) =
|
||||
poll.register(®, CMD, mio::Ready::readable(), mio::PollOpt::edge())
|
||||
{
|
||||
panic!("Can not register Registration: {}", err);
|
||||
}
|
||||
|
||||
// Create storage for events
|
||||
let mut events = mio::Events::with_capacity(128);
|
||||
|
||||
// Sleep on error
|
||||
let sleep = Duration::from_millis(100);
|
||||
|
||||
let mut next = 0;
|
||||
loop {
|
||||
if let Err(err) = poll.poll(&mut events, None) {
|
||||
panic!("Poll error: {}", err);
|
||||
}
|
||||
|
||||
for event in events.iter() {
|
||||
match event.token() {
|
||||
SRV => if let Some(ref server) = server {
|
||||
loop {
|
||||
match server.accept_std() {
|
||||
Ok((io, addr)) => {
|
||||
let mut msg = Conn {
|
||||
io,
|
||||
token,
|
||||
peer: Some(addr),
|
||||
http2: false,
|
||||
};
|
||||
while !workers.is_empty() {
|
||||
match workers[next].1.unbounded_send(msg) {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
let _ = srv.unbounded_send(
|
||||
ServerCommand::WorkerDied(
|
||||
workers[next].0,
|
||||
socks.clone(),
|
||||
),
|
||||
);
|
||||
msg = err.into_inner();
|
||||
workers.swap_remove(next);
|
||||
if workers.is_empty() {
|
||||
error!("No workers");
|
||||
thread::sleep(sleep);
|
||||
break;
|
||||
} else if workers.len() <= next {
|
||||
next = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
next = (next + 1) % workers.len();
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(ref e)
|
||||
if e.kind() == io::ErrorKind::WouldBlock =>
|
||||
{
|
||||
break
|
||||
}
|
||||
Err(ref e) if connection_error(e) => continue,
|
||||
Err(e) => {
|
||||
error!("Error accepting connection: {}", e);
|
||||
// sleep after error
|
||||
thread::sleep(sleep);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
CMD => match rx.try_recv() {
|
||||
Ok(cmd) => match cmd {
|
||||
Command::Pause => if let Some(ref server) = server {
|
||||
if let Err(err) = poll.deregister(server) {
|
||||
error!(
|
||||
"Can not deregister server socket {}",
|
||||
err
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
"Paused accepting connections on {}",
|
||||
addr
|
||||
);
|
||||
}
|
||||
},
|
||||
Command::Resume => {
|
||||
if let Some(ref server) = server {
|
||||
if let Err(err) = poll.register(
|
||||
server,
|
||||
SRV,
|
||||
mio::Ready::readable(),
|
||||
mio::PollOpt::edge(),
|
||||
) {
|
||||
error!("Can not resume socket accept process: {}", err);
|
||||
} else {
|
||||
info!("Accepting connections on {} has been resumed",
|
||||
addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Stop => {
|
||||
if let Some(server) = server.take() {
|
||||
let _ = poll.deregister(&server);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Command::Worker(idx, addr) => {
|
||||
workers.push((idx, addr));
|
||||
}
|
||||
},
|
||||
Err(err) => match err {
|
||||
sync_mpsc::TryRecvError::Empty => (),
|
||||
sync_mpsc::TryRecvError::Disconnected => {
|
||||
if let Some(server) = server.take() {
|
||||
let _ = poll.deregister(&server);
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
(readiness, tx)
|
||||
}
|
||||
|
||||
fn create_tcp_listener(
|
||||
addr: net::SocketAddr, backlog: i32,
|
||||
) -> io::Result<net::TcpListener> {
|
||||
let builder = match addr {
|
||||
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,
|
||||
net::SocketAddr::V6(_) => TcpBuilder::new_v6()?,
|
||||
};
|
||||
builder.reuse_address(true)?;
|
||||
builder.bind(addr)?;
|
||||
Ok(builder.listen(backlog)?)
|
||||
}
|
||||
|
||||
/// This function defines errors that are per-connection. Which basically
|
||||
/// means that if we get this error from `accept()` system call it means
|
||||
/// next connection might be ready to be accepted.
|
||||
///
|
||||
/// All other errors will incur a timeout before next `accept()` is performed.
|
||||
/// The timeout is useful to handle resource exhaustion errors like ENFILE
|
||||
/// and EMFILE. Otherwise, could enter into tight loop.
|
||||
fn connection_error(e: &io::Error) -> bool {
|
||||
e.kind() == io::ErrorKind::ConnectionRefused
|
||||
|| e.kind() == io::ErrorKind::ConnectionAborted
|
||||
|| e.kind() == io::ErrorKind::ConnectionReset
|
||||
}
|
||||
12
src/server/ssl/mod.rs
Normal file
12
src/server/ssl/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
||||
mod openssl;
|
||||
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
||||
pub use self::openssl::{openssl_acceptor_with_flags, OpensslAcceptor};
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
mod nativetls;
|
||||
|
||||
#[cfg(feature = "rust-tls")]
|
||||
mod rustls;
|
||||
#[cfg(feature = "rust-tls")]
|
||||
pub use self::rustls::RustlsAcceptor;
|
||||
34
src/server/ssl/nativetls.rs
Normal file
34
src/server/ssl/nativetls.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
use std::{io, time};
|
||||
|
||||
use actix_net::ssl::TlsStream;
|
||||
|
||||
use server::IoStream;
|
||||
|
||||
impl<Io: IoStream> IoStream for TlsStream<Io> {
|
||||
#[inline]
|
||||
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
|
||||
let _ = self.get_mut().shutdown();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn peer_addr(&self) -> Option<SocketAddr> {
|
||||
self.get_ref().get_ref().peer_addr()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||
self.get_mut().get_mut().set_nodelay(nodelay)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().get_mut().set_linger(dur)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().get_mut().set_keepalive(dur)
|
||||
}
|
||||
}
|
||||
87
src/server/ssl/openssl.rs
Normal file
87
src/server/ssl/openssl.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
use std::{io, time};
|
||||
|
||||
use actix_net::ssl;
|
||||
use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio_openssl::SslStream;
|
||||
|
||||
use server::{IoStream, ServerFlags};
|
||||
|
||||
/// Support `SSL` connections via openssl package
|
||||
///
|
||||
/// `ssl` feature enables `OpensslAcceptor` type
|
||||
pub struct OpensslAcceptor<T> {
|
||||
_t: ssl::OpensslAcceptor<T>,
|
||||
}
|
||||
|
||||
impl<T: AsyncRead + AsyncWrite> OpensslAcceptor<T> {
|
||||
/// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support.
|
||||
pub fn new(builder: SslAcceptorBuilder) -> io::Result<ssl::OpensslAcceptor<T>> {
|
||||
OpensslAcceptor::with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2)
|
||||
}
|
||||
|
||||
/// Create `OpensslAcceptor` with custom server flags.
|
||||
pub fn with_flags(
|
||||
builder: SslAcceptorBuilder, flags: ServerFlags,
|
||||
) -> io::Result<ssl::OpensslAcceptor<T>> {
|
||||
let acceptor = openssl_acceptor_with_flags(builder, flags)?;
|
||||
|
||||
Ok(ssl::OpensslAcceptor::new(acceptor))
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure `SslAcceptorBuilder` with custom server flags.
|
||||
pub fn openssl_acceptor_with_flags(
|
||||
mut builder: SslAcceptorBuilder, flags: ServerFlags,
|
||||
) -> io::Result<SslAcceptor> {
|
||||
let mut protos = Vec::new();
|
||||
if flags.contains(ServerFlags::HTTP1) {
|
||||
protos.extend(b"\x08http/1.1");
|
||||
}
|
||||
if flags.contains(ServerFlags::HTTP2) {
|
||||
protos.extend(b"\x02h2");
|
||||
builder.set_alpn_select_callback(|_, protos| {
|
||||
const H2: &[u8] = b"\x02h2";
|
||||
if protos.windows(3).any(|window| window == H2) {
|
||||
Ok(b"h2")
|
||||
} else {
|
||||
Err(AlpnError::NOACK)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if !protos.is_empty() {
|
||||
builder.set_alpn_protos(&protos)?;
|
||||
}
|
||||
|
||||
Ok(builder.build())
|
||||
}
|
||||
|
||||
impl<T: IoStream> IoStream for SslStream<T> {
|
||||
#[inline]
|
||||
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
|
||||
let _ = self.get_mut().shutdown();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn peer_addr(&self) -> Option<SocketAddr> {
|
||||
self.get_ref().get_ref().peer_addr()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||
self.get_mut().get_mut().set_nodelay(nodelay)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().get_mut().set_linger(dur)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().get_mut().set_keepalive(dur)
|
||||
}
|
||||
}
|
||||
87
src/server/ssl/rustls.rs
Normal file
87
src/server/ssl/rustls.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
use std::{io, time};
|
||||
|
||||
use actix_net::ssl; //::RustlsAcceptor;
|
||||
use rustls::{ClientSession, ServerConfig, ServerSession};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio_rustls::TlsStream;
|
||||
|
||||
use server::{IoStream, ServerFlags};
|
||||
|
||||
/// Support `SSL` connections via rustls package
|
||||
///
|
||||
/// `rust-tls` feature enables `RustlsAcceptor` type
|
||||
pub struct RustlsAcceptor<T> {
|
||||
_t: ssl::RustlsAcceptor<T>,
|
||||
}
|
||||
|
||||
impl<T: AsyncRead + AsyncWrite> RustlsAcceptor<T> {
|
||||
/// Create `RustlsAcceptor` with custom server flags.
|
||||
pub fn with_flags(
|
||||
mut config: ServerConfig, flags: ServerFlags,
|
||||
) -> ssl::RustlsAcceptor<T> {
|
||||
let mut protos = Vec::new();
|
||||
if flags.contains(ServerFlags::HTTP2) {
|
||||
protos.push("h2".to_string());
|
||||
}
|
||||
if flags.contains(ServerFlags::HTTP1) {
|
||||
protos.push("http/1.1".to_string());
|
||||
}
|
||||
if !protos.is_empty() {
|
||||
config.set_protocols(&protos);
|
||||
}
|
||||
|
||||
ssl::RustlsAcceptor::new(config)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io: IoStream> IoStream for TlsStream<Io, ClientSession> {
|
||||
#[inline]
|
||||
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
|
||||
let _ = <Self as AsyncWrite>::shutdown(self);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||
self.get_mut().0.set_nodelay(nodelay)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().0.set_linger(dur)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().0.set_keepalive(dur)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io: IoStream> IoStream for TlsStream<Io, ServerSession> {
|
||||
#[inline]
|
||||
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
|
||||
let _ = <Self as AsyncWrite>::shutdown(self);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn peer_addr(&self) -> Option<SocketAddr> {
|
||||
self.get_ref().0.peer_addr()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||
self.get_mut().0.set_nodelay(nodelay)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().0.set_linger(dur)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().0.set_keepalive(dur)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures::{Async, Poll};
|
||||
use std::io;
|
||||
|
||||
use super::IoStream;
|
||||
|
||||
const LW_BUFFER_SIZE: usize = 4096;
|
||||
const HW_BUFFER_SIZE: usize = 32_768;
|
||||
|
||||
pub fn read_from_io<T: IoStream>(
|
||||
io: &mut T, buf: &mut BytesMut,
|
||||
) -> Poll<usize, io::Error> {
|
||||
unsafe {
|
||||
if buf.remaining_mut() < LW_BUFFER_SIZE {
|
||||
buf.reserve(HW_BUFFER_SIZE);
|
||||
}
|
||||
match io.read(buf.bytes_mut()) {
|
||||
Ok(n) => {
|
||||
buf.advance_mut(n);
|
||||
Ok(Async::Ready(n))
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::WouldBlock {
|
||||
Ok(Async::NotReady)
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
use futures::unsync::oneshot;
|
||||
use futures::Future;
|
||||
use net2::TcpStreamExt;
|
||||
use slab::Slab;
|
||||
use std::rc::Rc;
|
||||
use std::{net, time};
|
||||
use tokio_core::net::TcpStream;
|
||||
use tokio_core::reactor::Handle;
|
||||
|
||||
#[cfg(any(feature = "tls", feature = "alpn"))]
|
||||
use futures::future;
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
use native_tls::TlsAcceptor;
|
||||
#[cfg(feature = "tls")]
|
||||
use tokio_tls::TlsAcceptorExt;
|
||||
|
||||
#[cfg(feature = "alpn")]
|
||||
use openssl::ssl::SslAcceptor;
|
||||
#[cfg(feature = "alpn")]
|
||||
use tokio_openssl::SslAcceptorExt;
|
||||
|
||||
use actix::msgs::StopArbiter;
|
||||
use actix::*;
|
||||
|
||||
use server::channel::HttpChannel;
|
||||
use server::settings::WorkerSettings;
|
||||
use server::{HttpHandler, KeepAlive};
|
||||
|
||||
#[derive(Message)]
|
||||
pub(crate) struct Conn<T> {
|
||||
pub io: T,
|
||||
pub token: usize,
|
||||
pub peer: Option<net::SocketAddr>,
|
||||
pub http2: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SocketInfo {
|
||||
pub addr: net::SocketAddr,
|
||||
pub htype: StreamHandlerType,
|
||||
}
|
||||
|
||||
/// Stop worker message. Returns `true` on successful shutdown
|
||||
/// and `false` if some connections still alive.
|
||||
pub(crate) struct StopWorker {
|
||||
pub graceful: Option<time::Duration>,
|
||||
}
|
||||
|
||||
impl Message for StopWorker {
|
||||
type Result = Result<bool, ()>;
|
||||
}
|
||||
|
||||
/// Http worker
|
||||
///
|
||||
/// Worker accepts Socket objects via unbounded channel and start requests
|
||||
/// processing.
|
||||
pub(crate) struct Worker<H>
|
||||
where
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
settings: Rc<WorkerSettings<H>>,
|
||||
hnd: Handle,
|
||||
socks: Slab<SocketInfo>,
|
||||
tcp_ka: Option<time::Duration>,
|
||||
}
|
||||
|
||||
impl<H: HttpHandler + 'static> Worker<H> {
|
||||
pub(crate) fn new(
|
||||
h: Vec<H>, socks: Slab<SocketInfo>, keep_alive: KeepAlive,
|
||||
) -> Worker<H> {
|
||||
let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive {
|
||||
Some(time::Duration::new(val as u64, 0))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Worker {
|
||||
settings: Rc::new(WorkerSettings::new(h, keep_alive)),
|
||||
hnd: Arbiter::handle().clone(),
|
||||
socks,
|
||||
tcp_ka,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_time(&self, ctx: &mut Context<Self>) {
|
||||
self.settings.update_date();
|
||||
ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_time(ctx));
|
||||
}
|
||||
|
||||
fn shutdown_timeout(
|
||||
&self, ctx: &mut Context<Self>, tx: oneshot::Sender<bool>, dur: time::Duration,
|
||||
) {
|
||||
// sleep for 1 second and then check again
|
||||
ctx.run_later(time::Duration::new(1, 0), move |slf, ctx| {
|
||||
let num = slf.settings.num_channels();
|
||||
if num == 0 {
|
||||
let _ = tx.send(true);
|
||||
Arbiter::arbiter().do_send(StopArbiter(0));
|
||||
} else if let Some(d) = dur.checked_sub(time::Duration::new(1, 0)) {
|
||||
slf.shutdown_timeout(ctx, tx, d);
|
||||
} else {
|
||||
info!("Force shutdown http worker, {} connections", num);
|
||||
slf.settings.head().traverse::<TcpStream, H>();
|
||||
let _ = tx.send(false);
|
||||
Arbiter::arbiter().do_send(StopArbiter(0));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: 'static> Actor for Worker<H>
|
||||
where
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
type Context = Context<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
self.update_time(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> Handler<Conn<net::TcpStream>> for Worker<H>
|
||||
where
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: Conn<net::TcpStream>, _: &mut Context<Self>) {
|
||||
if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() {
|
||||
error!("Can not set socket keep-alive option");
|
||||
}
|
||||
self.socks.get_mut(msg.token).unwrap().htype.handle(
|
||||
Rc::clone(&self.settings),
|
||||
&self.hnd,
|
||||
msg,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// `StopWorker` message handler
|
||||
impl<H> Handler<StopWorker> for Worker<H>
|
||||
where
|
||||
H: HttpHandler + 'static,
|
||||
{
|
||||
type Result = Response<bool, ()>;
|
||||
|
||||
fn handle(&mut self, msg: StopWorker, ctx: &mut Context<Self>) -> Self::Result {
|
||||
let num = self.settings.num_channels();
|
||||
if num == 0 {
|
||||
info!("Shutting down http worker, 0 connections");
|
||||
Response::reply(Ok(true))
|
||||
} else if let Some(dur) = msg.graceful {
|
||||
info!("Graceful http worker shutdown, {} connections", num);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.shutdown_timeout(ctx, tx, dur);
|
||||
Response::async(rx.map_err(|_| ()))
|
||||
} else {
|
||||
info!("Force shutdown http worker, {} connections", num);
|
||||
self.settings.head().traverse::<TcpStream, H>();
|
||||
Response::reply(Ok(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum StreamHandlerType {
|
||||
Normal,
|
||||
#[cfg(feature = "tls")]
|
||||
Tls(TlsAcceptor),
|
||||
#[cfg(feature = "alpn")]
|
||||
Alpn(SslAcceptor),
|
||||
}
|
||||
|
||||
impl StreamHandlerType {
|
||||
fn handle<H: HttpHandler>(
|
||||
&mut self, h: Rc<WorkerSettings<H>>, hnd: &Handle, msg: Conn<net::TcpStream>,
|
||||
) {
|
||||
match *self {
|
||||
StreamHandlerType::Normal => {
|
||||
let _ = msg.io.set_nodelay(true);
|
||||
let io = TcpStream::from_stream(msg.io, hnd)
|
||||
.expect("failed to associate TCP stream");
|
||||
|
||||
hnd.spawn(HttpChannel::new(h, io, msg.peer, msg.http2));
|
||||
}
|
||||
#[cfg(feature = "tls")]
|
||||
StreamHandlerType::Tls(ref acceptor) => {
|
||||
let Conn {
|
||||
io, peer, http2, ..
|
||||
} = msg;
|
||||
let _ = io.set_nodelay(true);
|
||||
let io = TcpStream::from_stream(io, hnd)
|
||||
.expect("failed to associate TCP stream");
|
||||
|
||||
hnd.spawn(TlsAcceptorExt::accept_async(acceptor, io).then(move |res| {
|
||||
match res {
|
||||
Ok(io) => {
|
||||
Arbiter::handle().spawn(HttpChannel::new(h, io, peer, http2))
|
||||
}
|
||||
Err(err) => {
|
||||
trace!("Error during handling tls connection: {}", err)
|
||||
}
|
||||
};
|
||||
future::result(Ok(()))
|
||||
}));
|
||||
}
|
||||
#[cfg(feature = "alpn")]
|
||||
StreamHandlerType::Alpn(ref acceptor) => {
|
||||
let Conn { io, peer, .. } = msg;
|
||||
let _ = io.set_nodelay(true);
|
||||
let io = TcpStream::from_stream(io, hnd)
|
||||
.expect("failed to associate TCP stream");
|
||||
|
||||
hnd.spawn(SslAcceptorExt::accept_async(acceptor, io).then(move |res| {
|
||||
match res {
|
||||
Ok(io) => {
|
||||
let http2 = if let Some(p) =
|
||||
io.get_ref().ssl().selected_alpn_protocol()
|
||||
{
|
||||
p.len() == 2 && &p == b"h2"
|
||||
} else {
|
||||
false
|
||||
};
|
||||
Arbiter::handle()
|
||||
.spawn(HttpChannel::new(h, io, peer, http2));
|
||||
}
|
||||
Err(err) => {
|
||||
trace!("Error during handling tls connection: {}", err)
|
||||
}
|
||||
};
|
||||
future::result(Ok(()))
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn scheme(&self) -> &'static str {
|
||||
match *self {
|
||||
StreamHandlerType::Normal => "http",
|
||||
#[cfg(feature = "tls")]
|
||||
StreamHandlerType::Tls(_) => "https",
|
||||
#[cfg(feature = "alpn")]
|
||||
StreamHandlerType::Alpn(_) => "https",
|
||||
}
|
||||
}
|
||||
}
|
||||
407
src/test.rs
407
src/test.rs
@@ -1,36 +1,39 @@
|
||||
//! Various helpers for Actix applications to use during testing.
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use std::sync::mpsc;
|
||||
use std::{net, thread};
|
||||
|
||||
use actix::{msgs, Actor, Addr, Arbiter, Syn, System, SystemRunner, Unsync};
|
||||
use actix::{Actor, Addr, System};
|
||||
|
||||
use cookie::Cookie;
|
||||
use futures::Future;
|
||||
use http::header::HeaderName;
|
||||
use http::{HeaderMap, HttpTryFrom, Method, Uri, Version};
|
||||
use net2::TcpBuilder;
|
||||
use tokio_core::net::TcpListener;
|
||||
use tokio_core::reactor::Core;
|
||||
use tokio::runtime::current_thread::Runtime;
|
||||
|
||||
#[cfg(feature = "alpn")]
|
||||
use openssl::ssl::SslAcceptor;
|
||||
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
||||
use openssl::ssl::SslAcceptorBuilder;
|
||||
#[cfg(feature = "rust-tls")]
|
||||
use rustls::ServerConfig;
|
||||
|
||||
use application::{App, HttpApplication};
|
||||
use body::Binary;
|
||||
use client::{ClientConnector, ClientRequest, ClientRequestBuilder};
|
||||
use error::Error;
|
||||
use handler::{AsyncResultItem, Handler, Responder};
|
||||
use handler::{AsyncResult, AsyncResultItem, Handler, Responder};
|
||||
use header::{Header, IntoHeaderValue};
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use middleware::Middleware;
|
||||
use param::Params;
|
||||
use payload::Payload;
|
||||
use resource::ResourceHandler;
|
||||
use resource::Resource;
|
||||
use router::Router;
|
||||
use server::message::{Request, RequestPool};
|
||||
use server::{HttpServer, IntoHttpHandler, ServerSettings};
|
||||
use uri::Url as InnerUrl;
|
||||
use ws;
|
||||
|
||||
/// The `TestServer` type.
|
||||
@@ -41,11 +44,10 @@ use ws;
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix;
|
||||
/// # extern crate actix_web;
|
||||
/// # use actix_web::*;
|
||||
/// #
|
||||
/// # fn my_handler(req: HttpRequest) -> HttpResponse {
|
||||
/// # fn my_handler(req: &HttpRequest) -> HttpResponse {
|
||||
/// # HttpResponse::Ok().into()
|
||||
/// # }
|
||||
/// #
|
||||
@@ -61,11 +63,9 @@ use ws;
|
||||
/// ```
|
||||
pub struct TestServer {
|
||||
addr: net::SocketAddr,
|
||||
thread: Option<thread::JoinHandle<()>>,
|
||||
system: SystemRunner,
|
||||
server_sys: Addr<Syn, System>,
|
||||
ssl: bool,
|
||||
conn: Addr<Unsync, ClientConnector>,
|
||||
conn: Addr<ClientConnector>,
|
||||
rt: Runtime,
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
@@ -75,13 +75,13 @@ impl TestServer {
|
||||
/// middlewares or set handlers for test application.
|
||||
pub fn new<F>(config: F) -> Self
|
||||
where
|
||||
F: Sync + Send + 'static + Fn(&mut TestApp<()>),
|
||||
F: Clone + Send + 'static + Fn(&mut TestApp<()>),
|
||||
{
|
||||
TestServerBuilder::new(|| ()).start(config)
|
||||
}
|
||||
|
||||
/// Create test server builder
|
||||
pub fn build() -> TestServerBuilder<()> {
|
||||
pub fn build() -> TestServerBuilder<(), impl Fn() -> () + Clone + Send + 'static> {
|
||||
TestServerBuilder::new(|| ())
|
||||
}
|
||||
|
||||
@@ -90,53 +90,51 @@ impl TestServer {
|
||||
/// This method can be used for constructing application state.
|
||||
/// Also it can be used for external dependency initialization,
|
||||
/// like creating sync actors for diesel integration.
|
||||
pub fn build_with_state<F, S>(state: F) -> TestServerBuilder<S>
|
||||
pub fn build_with_state<S, F>(state: F) -> TestServerBuilder<S, F>
|
||||
where
|
||||
F: Fn() -> S + Sync + Send + 'static,
|
||||
F: Fn() -> S + Clone + Send + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
TestServerBuilder::new(state)
|
||||
}
|
||||
|
||||
/// Start new test server with application factory
|
||||
pub fn with_factory<F, U, H>(factory: F) -> Self
|
||||
pub fn with_factory<F, H>(factory: F) -> Self
|
||||
where
|
||||
F: Fn() -> U + Sync + Send + 'static,
|
||||
U: IntoIterator<Item = H> + 'static,
|
||||
F: Fn() -> H + Send + Clone + 'static,
|
||||
H: IntoHttpHandler + 'static,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
// run server in separate thread
|
||||
let join = thread::spawn(move || {
|
||||
thread::spawn(move || {
|
||||
let sys = System::new("actix-test-server");
|
||||
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let local_addr = tcp.local_addr().unwrap();
|
||||
let tcp =
|
||||
TcpListener::from_listener(tcp, &local_addr, Arbiter::handle()).unwrap();
|
||||
|
||||
HttpServer::new(factory)
|
||||
let _ = HttpServer::new(factory)
|
||||
.disable_signals()
|
||||
.start_incoming(tcp.incoming(), false);
|
||||
.listen(tcp)
|
||||
.keep_alive(5)
|
||||
.start();
|
||||
|
||||
tx.send((Arbiter::system(), local_addr)).unwrap();
|
||||
let _ = sys.run();
|
||||
tx.send((System::current(), local_addr, TestServer::get_conn()))
|
||||
.unwrap();
|
||||
sys.run();
|
||||
});
|
||||
|
||||
let sys = System::new("actix-test");
|
||||
let (server_sys, addr) = rx.recv().unwrap();
|
||||
let (system, addr, conn) = rx.recv().unwrap();
|
||||
System::set_current(system);
|
||||
TestServer {
|
||||
addr,
|
||||
server_sys,
|
||||
conn,
|
||||
ssl: false,
|
||||
conn: TestServer::get_conn(),
|
||||
thread: Some(join),
|
||||
system: sys,
|
||||
rt: Runtime::new().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_conn() -> Addr<Unsync, ClientConnector> {
|
||||
#[cfg(feature = "alpn")]
|
||||
fn get_conn() -> Addr<ClientConnector> {
|
||||
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
||||
{
|
||||
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
|
||||
|
||||
@@ -144,7 +142,20 @@ impl TestServer {
|
||||
builder.set_verify(SslVerifyMode::NONE);
|
||||
ClientConnector::with_connector(builder.build()).start()
|
||||
}
|
||||
#[cfg(not(feature = "alpn"))]
|
||||
#[cfg(all(
|
||||
feature = "rust-tls",
|
||||
not(any(feature = "alpn", feature = "ssl"))
|
||||
))]
|
||||
{
|
||||
use rustls::ClientConfig;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
let mut config = ClientConfig::new();
|
||||
let pem_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap());
|
||||
config.root_store.add_pem_file(pem_file).unwrap();
|
||||
ClientConnector::with_connector(config).start()
|
||||
}
|
||||
#[cfg(not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")))]
|
||||
{
|
||||
ClientConnector::default().start()
|
||||
}
|
||||
@@ -169,16 +180,16 @@ impl TestServer {
|
||||
pub fn url(&self, uri: &str) -> String {
|
||||
if uri.starts_with('/') {
|
||||
format!(
|
||||
"{}://{}{}",
|
||||
"{}://localhost:{}{}",
|
||||
if self.ssl { "https" } else { "http" },
|
||||
self.addr,
|
||||
self.addr.port(),
|
||||
uri
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{}://{}/{}",
|
||||
"{}://localhost:{}/{}",
|
||||
if self.ssl { "https" } else { "http" },
|
||||
self.addr,
|
||||
self.addr.port(),
|
||||
uri
|
||||
)
|
||||
}
|
||||
@@ -186,10 +197,7 @@ impl TestServer {
|
||||
|
||||
/// Stop http server
|
||||
fn stop(&mut self) {
|
||||
if let Some(handle) = self.thread.take() {
|
||||
self.server_sys.do_send(msgs::SystemExit(0));
|
||||
let _ = handle.join();
|
||||
}
|
||||
System::current().stop();
|
||||
}
|
||||
|
||||
/// Execute future on current core
|
||||
@@ -197,17 +205,23 @@ impl TestServer {
|
||||
where
|
||||
F: Future<Item = I, Error = E>,
|
||||
{
|
||||
self.system.run_until_complete(fut)
|
||||
self.rt.block_on(fut)
|
||||
}
|
||||
|
||||
/// Connect to websocket server
|
||||
/// Connect to websocket server at a given path
|
||||
pub fn ws_at(
|
||||
&mut self, path: &str,
|
||||
) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
|
||||
let url = self.url(path);
|
||||
self.rt
|
||||
.block_on(ws::Client::with_connector(url, self.conn.clone()).connect())
|
||||
}
|
||||
|
||||
/// Connect to a websocket server
|
||||
pub fn ws(
|
||||
&mut self,
|
||||
) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
|
||||
let url = self.url("/");
|
||||
self.system.run_until_complete(
|
||||
ws::Client::with_connector(url, self.conn.clone()).connect(),
|
||||
)
|
||||
self.ws_at("/")
|
||||
}
|
||||
|
||||
/// Create `GET` request
|
||||
@@ -245,99 +259,115 @@ impl Drop for TestServer {
|
||||
///
|
||||
/// This type can be used to construct an instance of `TestServer` through a
|
||||
/// builder-like pattern.
|
||||
pub struct TestServerBuilder<S> {
|
||||
state: Box<Fn() -> S + Sync + Send + 'static>,
|
||||
#[cfg(feature = "alpn")]
|
||||
ssl: Option<SslAcceptor>,
|
||||
pub struct TestServerBuilder<S, F>
|
||||
where
|
||||
F: Fn() -> S + Send + Clone + 'static,
|
||||
{
|
||||
state: F,
|
||||
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
||||
ssl: Option<SslAcceptorBuilder>,
|
||||
#[cfg(feature = "rust-tls")]
|
||||
rust_ssl: Option<ServerConfig>,
|
||||
}
|
||||
|
||||
impl<S: 'static> TestServerBuilder<S> {
|
||||
pub fn new<F>(state: F) -> TestServerBuilder<S>
|
||||
where
|
||||
F: Fn() -> S + Sync + Send + 'static,
|
||||
{
|
||||
impl<S: 'static, F> TestServerBuilder<S, F>
|
||||
where
|
||||
F: Fn() -> S + Send + Clone + 'static,
|
||||
{
|
||||
/// Create a new test server
|
||||
pub fn new(state: F) -> TestServerBuilder<S, F> {
|
||||
TestServerBuilder {
|
||||
state: Box::new(state),
|
||||
#[cfg(feature = "alpn")]
|
||||
state,
|
||||
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
||||
ssl: None,
|
||||
#[cfg(feature = "rust-tls")]
|
||||
rust_ssl: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alpn")]
|
||||
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
||||
/// Create ssl server
|
||||
pub fn ssl(mut self, ssl: SslAcceptor) -> Self {
|
||||
pub fn ssl(mut self, ssl: SslAcceptorBuilder) -> Self {
|
||||
self.ssl = Some(ssl);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "rust-tls")]
|
||||
/// Create rust tls server
|
||||
pub fn rustls(mut self, ssl: ServerConfig) -> Self {
|
||||
self.rust_ssl = Some(ssl);
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
/// Configure test application and run test server
|
||||
pub fn start<F>(mut self, config: F) -> TestServer
|
||||
pub fn start<C>(mut self, config: C) -> TestServer
|
||||
where
|
||||
F: Sync + Send + 'static + Fn(&mut TestApp<S>),
|
||||
C: Fn(&mut TestApp<S>) + Clone + Send + 'static,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
#[cfg(feature = "alpn")]
|
||||
let ssl = self.ssl.is_some();
|
||||
#[cfg(not(feature = "alpn"))]
|
||||
let ssl = false;
|
||||
let mut has_ssl = false;
|
||||
|
||||
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
||||
{
|
||||
has_ssl = has_ssl || self.ssl.is_some();
|
||||
}
|
||||
|
||||
#[cfg(feature = "rust-tls")]
|
||||
{
|
||||
has_ssl = has_ssl || self.rust_ssl.is_some();
|
||||
}
|
||||
|
||||
// run server in separate thread
|
||||
let join = thread::spawn(move || {
|
||||
thread::spawn(move || {
|
||||
let addr = TestServer::unused_addr();
|
||||
|
||||
let sys = System::new("actix-test-server");
|
||||
|
||||
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let local_addr = tcp.local_addr().unwrap();
|
||||
let tcp =
|
||||
TcpListener::from_listener(tcp, &local_addr, Arbiter::handle()).unwrap();
|
||||
|
||||
let state = self.state;
|
||||
|
||||
let srv = HttpServer::new(move || {
|
||||
let mut srv = HttpServer::new(move || {
|
||||
let mut app = TestApp::new(state());
|
||||
config(&mut app);
|
||||
vec![app]
|
||||
}).disable_signals();
|
||||
app
|
||||
}).workers(1)
|
||||
.keep_alive(5)
|
||||
.disable_signals();
|
||||
|
||||
#[cfg(feature = "alpn")]
|
||||
tx.send((System::current(), addr, TestServer::get_conn()))
|
||||
.unwrap();
|
||||
|
||||
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
||||
{
|
||||
use futures::Stream;
|
||||
use std::io;
|
||||
use tokio_openssl::SslAcceptorExt;
|
||||
|
||||
let ssl = self.ssl.take();
|
||||
if let Some(ssl) = ssl {
|
||||
srv.start_incoming(
|
||||
tcp.incoming().and_then(move |(sock, addr)| {
|
||||
ssl.accept_async(sock)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
.map(move |s| (s, addr))
|
||||
}),
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
srv.start_incoming(tcp.incoming(), false);
|
||||
let tcp = net::TcpListener::bind(addr).unwrap();
|
||||
srv = srv.listen_ssl(tcp, ssl).unwrap();
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "alpn"))]
|
||||
#[cfg(feature = "rust-tls")]
|
||||
{
|
||||
srv.start_incoming(tcp.incoming(), false);
|
||||
let ssl = self.rust_ssl.take();
|
||||
if let Some(ssl) = ssl {
|
||||
let tcp = net::TcpListener::bind(addr).unwrap();
|
||||
srv = srv.listen_rustls(tcp, ssl);
|
||||
}
|
||||
}
|
||||
if !has_ssl {
|
||||
let tcp = net::TcpListener::bind(addr).unwrap();
|
||||
srv = srv.listen(tcp);
|
||||
}
|
||||
srv.start();
|
||||
|
||||
tx.send((Arbiter::system(), local_addr)).unwrap();
|
||||
let _ = sys.run();
|
||||
sys.run();
|
||||
});
|
||||
|
||||
let system = System::new("actix-test");
|
||||
let (server_sys, addr) = rx.recv().unwrap();
|
||||
let (system, addr, conn) = rx.recv().unwrap();
|
||||
System::set_current(system);
|
||||
TestServer {
|
||||
addr,
|
||||
server_sys,
|
||||
ssl,
|
||||
system,
|
||||
conn: TestServer::get_conn(),
|
||||
thread: Some(join),
|
||||
conn,
|
||||
ssl: has_ssl,
|
||||
rt: Runtime::new().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,8 +384,12 @@ impl<S: 'static> TestApp<S> {
|
||||
}
|
||||
|
||||
/// Register handler for "/"
|
||||
pub fn handler<H: Handler<S>>(&mut self, handler: H) {
|
||||
self.app = Some(self.app.take().unwrap().resource("/", |r| r.h(handler)));
|
||||
pub fn handler<F, R>(&mut self, handler: F)
|
||||
where
|
||||
F: Fn(&HttpRequest<S>) -> R + 'static,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
self.app = Some(self.app.take().unwrap().resource("/", |r| r.f(handler)));
|
||||
}
|
||||
|
||||
/// Register middleware
|
||||
@@ -371,7 +405,7 @@ impl<S: 'static> TestApp<S> {
|
||||
/// to `App::resource()` method.
|
||||
pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut TestApp<S>
|
||||
where
|
||||
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static,
|
||||
F: FnOnce(&mut Resource<S>) -> R + 'static,
|
||||
{
|
||||
self.app = Some(self.app.take().unwrap().resource(path, f));
|
||||
self
|
||||
@@ -381,8 +415,8 @@ impl<S: 'static> TestApp<S> {
|
||||
impl<S: 'static> IntoHttpHandler for TestApp<S> {
|
||||
type Handler = HttpApplication<S>;
|
||||
|
||||
fn into_handler(mut self, settings: ServerSettings) -> HttpApplication<S> {
|
||||
self.app.take().unwrap().into_handler(settings)
|
||||
fn into_handler(mut self) -> HttpApplication<S> {
|
||||
self.app.take().unwrap().into_handler()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,7 +442,7 @@ impl<S: 'static> Iterator for TestApp<S> {
|
||||
/// # use actix_web::*;
|
||||
/// use actix_web::test::TestRequest;
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> HttpResponse {
|
||||
/// fn index(req: &HttpRequest) -> HttpResponse {
|
||||
/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
|
||||
/// HttpResponse::Ok().into()
|
||||
/// } else {
|
||||
@@ -418,11 +452,11 @@ impl<S: 'static> Iterator for TestApp<S> {
|
||||
///
|
||||
/// fn main() {
|
||||
/// let resp = TestRequest::with_header("content-type", "text/plain")
|
||||
/// .run(index).unwrap();
|
||||
/// .run(&index)
|
||||
/// .unwrap();
|
||||
/// assert_eq!(resp.status(), StatusCode::OK);
|
||||
///
|
||||
/// let resp = TestRequest::default()
|
||||
/// .run(index).unwrap();
|
||||
/// let resp = TestRequest::default().run(&index).unwrap();
|
||||
/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
/// }
|
||||
/// ```
|
||||
@@ -432,9 +466,10 @@ pub struct TestRequest<S> {
|
||||
method: Method,
|
||||
uri: Uri,
|
||||
headers: HeaderMap,
|
||||
params: Params<'static>,
|
||||
params: Params,
|
||||
cookies: Option<Vec<Cookie<'static>>>,
|
||||
payload: Option<Payload>,
|
||||
prefix: u16,
|
||||
}
|
||||
|
||||
impl Default for TestRequest<()> {
|
||||
@@ -448,6 +483,7 @@ impl Default for TestRequest<()> {
|
||||
params: Params::new(),
|
||||
cookies: None,
|
||||
payload: None,
|
||||
prefix: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -485,6 +521,7 @@ impl<S: 'static> TestRequest<S> {
|
||||
params: Params::new(),
|
||||
cookies: None,
|
||||
payload: None,
|
||||
prefix: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,7 +569,7 @@ impl<S: 'static> TestRequest<S> {
|
||||
|
||||
/// Set request path pattern parameter
|
||||
pub fn param(mut self, name: &'static str, value: &'static str) -> Self {
|
||||
self.params.add(name, value);
|
||||
self.params.add_static(name, value);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -545,6 +582,12 @@ impl<S: 'static> TestRequest<S> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set request's prefix
|
||||
pub fn prefix(mut self, prefix: u16) -> Self {
|
||||
self.prefix = prefix;
|
||||
self
|
||||
}
|
||||
|
||||
/// Complete request creation and generate `HttpRequest` instance
|
||||
pub fn finish(self) -> HttpRequest<S> {
|
||||
let TestRequest {
|
||||
@@ -553,50 +596,103 @@ impl<S: 'static> TestRequest<S> {
|
||||
uri,
|
||||
version,
|
||||
headers,
|
||||
params,
|
||||
mut params,
|
||||
cookies,
|
||||
payload,
|
||||
prefix,
|
||||
} = self;
|
||||
let mut req = HttpRequest::new(method, uri, version, headers, payload);
|
||||
let router = Router::<()>::default();
|
||||
|
||||
let pool = RequestPool::pool(ServerSettings::default());
|
||||
let mut req = RequestPool::get(pool);
|
||||
{
|
||||
let inner = req.inner_mut();
|
||||
inner.method = method;
|
||||
inner.url = InnerUrl::new(uri);
|
||||
inner.version = version;
|
||||
inner.headers = headers;
|
||||
*inner.payload.borrow_mut() = payload;
|
||||
}
|
||||
params.set_url(req.url().clone());
|
||||
let mut info = router.route_info_params(0, params);
|
||||
info.set_prefix(prefix);
|
||||
|
||||
let mut req = HttpRequest::new(req, Rc::new(state), info);
|
||||
req.set_cookies(cookies);
|
||||
req.as_mut().params = params;
|
||||
let (router, _) = Router::new::<S>("/", ServerSettings::default(), Vec::new());
|
||||
req.with_state(Rc::new(state), router)
|
||||
req
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Complete request creation and generate `HttpRequest` instance
|
||||
pub(crate) fn finish_with_router(self, router: Router) -> HttpRequest<S> {
|
||||
pub(crate) fn finish_with_router(self, router: Router<S>) -> HttpRequest<S> {
|
||||
let TestRequest {
|
||||
state,
|
||||
method,
|
||||
uri,
|
||||
version,
|
||||
headers,
|
||||
params,
|
||||
mut params,
|
||||
cookies,
|
||||
payload,
|
||||
prefix,
|
||||
} = self;
|
||||
|
||||
let mut req = HttpRequest::new(method, uri, version, headers, payload);
|
||||
let pool = RequestPool::pool(ServerSettings::default());
|
||||
let mut req = RequestPool::get(pool);
|
||||
{
|
||||
let inner = req.inner_mut();
|
||||
inner.method = method;
|
||||
inner.url = InnerUrl::new(uri);
|
||||
inner.version = version;
|
||||
inner.headers = headers;
|
||||
*inner.payload.borrow_mut() = payload;
|
||||
}
|
||||
params.set_url(req.url().clone());
|
||||
let mut info = router.route_info_params(0, params);
|
||||
info.set_prefix(prefix);
|
||||
let mut req = HttpRequest::new(req, Rc::new(state), info);
|
||||
req.set_cookies(cookies);
|
||||
req.as_mut().params = params;
|
||||
req.with_state(Rc::new(state), router)
|
||||
req
|
||||
}
|
||||
|
||||
/// Complete request creation and generate server `Request` instance
|
||||
pub fn request(self) -> Request {
|
||||
let TestRequest {
|
||||
method,
|
||||
uri,
|
||||
version,
|
||||
headers,
|
||||
payload,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let pool = RequestPool::pool(ServerSettings::default());
|
||||
let mut req = RequestPool::get(pool);
|
||||
{
|
||||
let inner = req.inner_mut();
|
||||
inner.method = method;
|
||||
inner.url = InnerUrl::new(uri);
|
||||
inner.version = version;
|
||||
inner.headers = headers;
|
||||
*inner.payload.borrow_mut() = payload;
|
||||
}
|
||||
req
|
||||
}
|
||||
|
||||
/// This method generates `HttpRequest` instance and runs handler
|
||||
/// with generated request.
|
||||
///
|
||||
/// This method panics is handler returns actor or async result.
|
||||
pub fn run<H: Handler<S>>(self, mut h: H) -> Result<HttpResponse, Error> {
|
||||
pub fn run<H: Handler<S>>(self, h: &H) -> Result<HttpResponse, Error> {
|
||||
let req = self.finish();
|
||||
let resp = h.handle(req.clone());
|
||||
let resp = h.handle(&req);
|
||||
|
||||
match resp.respond_to(&req) {
|
||||
Ok(resp) => match resp.into().into() {
|
||||
AsyncResultItem::Ok(resp) => Ok(resp),
|
||||
AsyncResultItem::Err(err) => Err(err),
|
||||
AsyncResultItem::Future(_) => panic!("Async handler is not supported."),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
let mut sys = System::new("test");
|
||||
sys.block_on(fut)
|
||||
}
|
||||
},
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
@@ -616,8 +712,8 @@ impl<S: 'static> TestRequest<S> {
|
||||
let req = self.finish();
|
||||
let fut = h(req.clone());
|
||||
|
||||
let mut core = Core::new().unwrap();
|
||||
match core.run(fut) {
|
||||
let mut sys = System::new("test");
|
||||
match sys.block_on(fut) {
|
||||
Ok(r) => match r.respond_to(&req) {
|
||||
Ok(reply) => match reply.into().into() {
|
||||
AsyncResultItem::Ok(resp) => Ok(resp),
|
||||
@@ -628,4 +724,45 @@ impl<S: 'static> TestRequest<S> {
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// This method generates `HttpRequest` instance and executes handler
|
||||
pub fn run_async_result<F, R, I, E>(self, f: F) -> Result<I, E>
|
||||
where
|
||||
F: FnOnce(&HttpRequest<S>) -> R,
|
||||
R: Into<AsyncResult<I, E>>,
|
||||
{
|
||||
let req = self.finish();
|
||||
let res = f(&req);
|
||||
|
||||
match res.into().into() {
|
||||
AsyncResultItem::Ok(resp) => Ok(resp),
|
||||
AsyncResultItem::Err(err) => Err(err),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
let mut sys = System::new("test");
|
||||
sys.block_on(fut)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This method generates `HttpRequest` instance and executes handler
|
||||
pub fn execute<F, R>(self, f: F) -> Result<HttpResponse, Error>
|
||||
where
|
||||
F: FnOnce(&HttpRequest<S>) -> R,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
let req = self.finish();
|
||||
let resp = f(&req);
|
||||
|
||||
match resp.respond_to(&req) {
|
||||
Ok(resp) => match resp.into().into() {
|
||||
AsyncResultItem::Ok(resp) => Ok(resp),
|
||||
AsyncResultItem::Err(err) => Err(err),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
let mut sys = System::new("test");
|
||||
sys.block_on(fut)
|
||||
}
|
||||
},
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
106
src/uri.rs
106
src/uri.rs
@@ -1,24 +1,12 @@
|
||||
use http::Uri;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[allow(dead_code)]
|
||||
const GEN_DELIMS: &[u8] = b":/?#[]@";
|
||||
#[allow(dead_code)]
|
||||
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
|
||||
#[allow(dead_code)]
|
||||
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
|
||||
#[allow(dead_code)]
|
||||
const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
|
||||
#[allow(dead_code)]
|
||||
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
1234567890
|
||||
-._~";
|
||||
const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
1234567890
|
||||
-._~
|
||||
!$'()*,";
|
||||
const QS: &[u8] = b"+&=;b";
|
||||
// https://tools.ietf.org/html/rfc3986#section-2.2
|
||||
const RESERVED_PLUS_EXTRA: &[u8] = b":/?#[]@!$&'()*,+?;=%^ <>\"\\`{}|";
|
||||
|
||||
// https://tools.ietf.org/html/rfc3986#section-2.3
|
||||
const UNRESERVED: &[u8] =
|
||||
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-._~";
|
||||
|
||||
#[inline]
|
||||
fn bit_at(array: &[u8], ch: u8) -> bool {
|
||||
@@ -31,18 +19,19 @@ fn set_bit(array: &mut [u8], ch: u8) {
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") };
|
||||
static ref UNRESERVED_QUOTER: Quoter = { Quoter::new(UNRESERVED) };
|
||||
pub(crate) static ref RESERVED_QUOTER: Quoter = { Quoter::new(RESERVED_PLUS_EXTRA) };
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub(crate) struct Url {
|
||||
uri: Uri,
|
||||
path: Option<String>,
|
||||
path: Option<Rc<String>>,
|
||||
}
|
||||
|
||||
impl Url {
|
||||
pub fn new(uri: Uri) -> Url {
|
||||
let path = DEFAULT_QUOTER.requote(uri.path().as_bytes());
|
||||
let path = UNRESERVED_QUOTER.requote(uri.path().as_bytes());
|
||||
|
||||
Url { uri, path }
|
||||
}
|
||||
@@ -62,40 +51,23 @@ impl Url {
|
||||
|
||||
pub(crate) struct Quoter {
|
||||
safe_table: [u8; 16],
|
||||
protected_table: [u8; 16],
|
||||
}
|
||||
|
||||
impl Quoter {
|
||||
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
|
||||
pub fn new(safe: &[u8]) -> Quoter {
|
||||
let mut q = Quoter {
|
||||
safe_table: [0; 16],
|
||||
protected_table: [0; 16],
|
||||
};
|
||||
|
||||
// prepare safe table
|
||||
for i in 0..128 {
|
||||
if ALLOWED.contains(&i) {
|
||||
set_bit(&mut q.safe_table, i);
|
||||
}
|
||||
if QS.contains(&i) {
|
||||
set_bit(&mut q.safe_table, i);
|
||||
}
|
||||
}
|
||||
|
||||
for ch in safe {
|
||||
set_bit(&mut q.safe_table, *ch)
|
||||
}
|
||||
|
||||
// prepare protected table
|
||||
for ch in protected {
|
||||
set_bit(&mut q.safe_table, *ch);
|
||||
set_bit(&mut q.protected_table, *ch);
|
||||
}
|
||||
|
||||
q
|
||||
}
|
||||
|
||||
pub fn requote(&self, val: &[u8]) -> Option<String> {
|
||||
pub fn requote(&self, val: &[u8]) -> Option<Rc<String>> {
|
||||
let mut has_pct = 0;
|
||||
let mut pct = [b'%', 0, 0];
|
||||
let mut idx = 0;
|
||||
@@ -114,19 +86,17 @@ impl Quoter {
|
||||
|
||||
if let Some(ch) = restore_ch(pct[1], pct[2]) {
|
||||
if ch < 128 {
|
||||
if bit_at(&self.protected_table, ch) {
|
||||
buf.extend_from_slice(&pct);
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if bit_at(&self.safe_table, ch) {
|
||||
buf.push(ch);
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
buf.extend_from_slice(&pct);
|
||||
} else {
|
||||
// Not ASCII, decode it
|
||||
buf.push(ch);
|
||||
}
|
||||
buf.push(ch);
|
||||
} else {
|
||||
buf.extend_from_slice(&pct[..]);
|
||||
}
|
||||
@@ -145,7 +115,9 @@ impl Quoter {
|
||||
}
|
||||
|
||||
if let Some(data) = cloned {
|
||||
Some(unsafe { String::from_utf8_unchecked(data) })
|
||||
// Unsafe: we get data from http::Uri, which does utf-8 checks already
|
||||
// this code only decodes valid pct encoded values
|
||||
Some(Rc::new(unsafe { String::from_utf8_unchecked(data) }))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -169,3 +141,37 @@ fn from_hex(v: u8) -> Option<u8> {
|
||||
fn restore_ch(d1: u8, d2: u8) -> Option<u8> {
|
||||
from_hex(d1).and_then(|d1| from_hex(d2).and_then(move |d2| Some(d1 << 4 | d2)))
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn decode_path() {
|
||||
assert_eq!(UNRESERVED_QUOTER.requote(b"https://localhost:80/foo"), None);
|
||||
|
||||
assert_eq!(
|
||||
Rc::try_unwrap(UNRESERVED_QUOTER.requote(
|
||||
b"https://localhost:80/foo%25"
|
||||
).unwrap()).unwrap(),
|
||||
"https://localhost:80/foo%25".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Rc::try_unwrap(UNRESERVED_QUOTER.requote(
|
||||
b"http://cache-service/http%3A%2F%2Flocalhost%3A80%2Ffoo"
|
||||
).unwrap()).unwrap(),
|
||||
"http://cache-service/http%3A%2F%2Flocalhost%3A80%2Ffoo".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Rc::try_unwrap(UNRESERVED_QUOTER.requote(
|
||||
b"http://cache/http%3A%2F%2Flocal%3A80%2Ffile%2F%252Fvar%252Flog%0A"
|
||||
).unwrap()).unwrap(),
|
||||
"http://cache/http%3A%2F%2Flocal%3A80%2Ffile%2F%252Fvar%252Flog%0A".to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
726
src/with.rs
726
src/with.rs
@@ -1,7 +1,5 @@
|
||||
use futures::{Async, Future, Poll};
|
||||
use std::cell::UnsafeCell;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::rc::Rc;
|
||||
|
||||
use error::Error;
|
||||
@@ -9,140 +7,76 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
/// Extractor configuration
|
||||
///
|
||||
/// `Route::with()` and `Route::with_async()` returns instance
|
||||
/// of the `ExtractorConfig` type. It could be used for extractor configuration.
|
||||
///
|
||||
/// In this example `Form<FormData>` configured.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Form, Result, http};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct FormData {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// fn index(form: Form<FormData>) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}!", form.username))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/index.html", |r| {
|
||||
/// r.method(http::Method::GET)
|
||||
/// .with(index)
|
||||
/// .limit(4096);} // <- change form extractor configuration
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Same could be donce with multiple extractors
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Form, Path, Result, http};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct FormData {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// fn index(data: (Path<(String,)>, Form<FormData>)) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}!", data.1.username))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/index.html", |r| {
|
||||
/// r.method(http::Method::GET)
|
||||
/// .with(index)
|
||||
/// .1.limit(4096);} // <- change form extractor configuration
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub struct ExtractorConfig<S: 'static, T: FromRequest<S>> {
|
||||
cfg: Rc<UnsafeCell<T::Config>>,
|
||||
trait FnWith<T, R>: 'static {
|
||||
fn call_with(self: &Self, T) -> R;
|
||||
}
|
||||
|
||||
impl<S: 'static, T: FromRequest<S>> Default for ExtractorConfig<S, T> {
|
||||
fn default() -> Self {
|
||||
ExtractorConfig {
|
||||
cfg: Rc::new(UnsafeCell::new(T::Config::default())),
|
||||
}
|
||||
impl<T, R, F: Fn(T) -> R + 'static> FnWith<T, R> for F {
|
||||
fn call_with(self: &Self, arg: T) -> R {
|
||||
(*self)(arg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static, T: FromRequest<S>> Clone for ExtractorConfig<S, T> {
|
||||
fn clone(&self) -> Self {
|
||||
ExtractorConfig {
|
||||
cfg: Rc::clone(&self.cfg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static, T: FromRequest<S>> AsRef<T::Config> for ExtractorConfig<S, T> {
|
||||
fn as_ref(&self) -> &T::Config {
|
||||
unsafe { &*self.cfg.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static, T: FromRequest<S>> Deref for ExtractorConfig<S, T> {
|
||||
type Target = T::Config;
|
||||
|
||||
fn deref(&self) -> &T::Config {
|
||||
unsafe { &*self.cfg.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static, T: FromRequest<S>> DerefMut for ExtractorConfig<S, T> {
|
||||
fn deref_mut(&mut self) -> &mut T::Config {
|
||||
unsafe { &mut *self.cfg.get() }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct With<T, S, F, R>
|
||||
#[doc(hidden)]
|
||||
pub trait WithFactory<T, S, R>: 'static
|
||||
where
|
||||
T: FromRequest<S>,
|
||||
R: Responder,
|
||||
{
|
||||
fn create(self) -> With<T, S, R>;
|
||||
|
||||
fn create_with_config(self, T::Config) -> With<T, S, R>;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait WithAsyncFactory<T, S, R, I, E>: 'static
|
||||
where
|
||||
T: FromRequest<S>,
|
||||
R: Future<Item = I, Error = E>,
|
||||
I: Responder,
|
||||
E: Into<Error>,
|
||||
{
|
||||
fn create(self) -> WithAsync<T, S, R, I, E>;
|
||||
|
||||
fn create_with_config(self, T::Config) -> WithAsync<T, S, R, I, E>;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct With<T, S, R>
|
||||
where
|
||||
F: Fn(T) -> R,
|
||||
T: FromRequest<S>,
|
||||
S: 'static,
|
||||
{
|
||||
hnd: Rc<UnsafeCell<F>>,
|
||||
cfg: ExtractorConfig<S, T>,
|
||||
hnd: Rc<FnWith<T, R>>,
|
||||
cfg: Rc<T::Config>,
|
||||
_s: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<T, S, F, R> With<T, S, F, R>
|
||||
impl<T, S, R> With<T, S, R>
|
||||
where
|
||||
F: Fn(T) -> R,
|
||||
T: FromRequest<S>,
|
||||
S: 'static,
|
||||
{
|
||||
pub fn new(f: F, cfg: ExtractorConfig<S, T>) -> Self {
|
||||
pub fn new<F: Fn(T) -> R + 'static>(f: F, cfg: T::Config) -> Self {
|
||||
With {
|
||||
cfg,
|
||||
hnd: Rc::new(UnsafeCell::new(f)),
|
||||
cfg: Rc::new(cfg),
|
||||
hnd: Rc::new(f),
|
||||
_s: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, F, R> Handler<S> for With<T, S, F, R>
|
||||
impl<T, S, R> Handler<S> for With<T, S, R>
|
||||
where
|
||||
F: Fn(T) -> R + 'static,
|
||||
R: Responder + 'static,
|
||||
T: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
type Result = AsyncResult<HttpResponse>;
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
|
||||
let mut fut = WithHandlerFut {
|
||||
req,
|
||||
req: req.clone(),
|
||||
started: false,
|
||||
hnd: Rc::clone(&self.hnd),
|
||||
cfg: self.cfg.clone(),
|
||||
@@ -152,30 +86,28 @@ where
|
||||
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
||||
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
|
||||
Ok(Async::NotReady) => AsyncResult::future(Box::new(fut)),
|
||||
Err(e) => AsyncResult::err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WithHandlerFut<T, S, F, R>
|
||||
struct WithHandlerFut<T, S, R>
|
||||
where
|
||||
F: Fn(T) -> R,
|
||||
R: Responder,
|
||||
T: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
started: bool,
|
||||
hnd: Rc<UnsafeCell<F>>,
|
||||
cfg: ExtractorConfig<S, T>,
|
||||
hnd: Rc<FnWith<T, R>>,
|
||||
cfg: Rc<T::Config>,
|
||||
req: HttpRequest<S>,
|
||||
fut1: Option<Box<Future<Item = T, Error = Error>>>,
|
||||
fut2: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
|
||||
}
|
||||
|
||||
impl<T, S, F, R> Future for WithHandlerFut<T, S, F, R>
|
||||
impl<T, S, R> Future for WithHandlerFut<T, S, R>
|
||||
where
|
||||
F: Fn(T) -> R,
|
||||
R: Responder + 'static,
|
||||
T: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
@@ -206,8 +138,7 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
let item = match (*hnd)(item).respond_to(&self.req) {
|
||||
let item = match self.hnd.as_ref().call_with(item).respond_to(&self.req) {
|
||||
Ok(item) => item.into(),
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
@@ -223,41 +154,39 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WithAsync<T, S, F, R, I, E>
|
||||
#[doc(hidden)]
|
||||
pub struct WithAsync<T, S, R, I, E>
|
||||
where
|
||||
F: Fn(T) -> R,
|
||||
R: Future<Item = I, Error = E>,
|
||||
I: Responder,
|
||||
E: Into<E>,
|
||||
T: FromRequest<S>,
|
||||
S: 'static,
|
||||
{
|
||||
hnd: Rc<UnsafeCell<F>>,
|
||||
cfg: ExtractorConfig<S, T>,
|
||||
hnd: Rc<FnWith<T, R>>,
|
||||
cfg: Rc<T::Config>,
|
||||
_s: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<T, S, F, R, I, E> WithAsync<T, S, F, R, I, E>
|
||||
impl<T, S, R, I, E> WithAsync<T, S, R, I, E>
|
||||
where
|
||||
F: Fn(T) -> R,
|
||||
R: Future<Item = I, Error = E>,
|
||||
I: Responder,
|
||||
E: Into<Error>,
|
||||
T: FromRequest<S>,
|
||||
S: 'static,
|
||||
{
|
||||
pub fn new(f: F, cfg: ExtractorConfig<S, T>) -> Self {
|
||||
pub fn new<F: Fn(T) -> R + 'static>(f: F, cfg: T::Config) -> Self {
|
||||
WithAsync {
|
||||
cfg,
|
||||
hnd: Rc::new(UnsafeCell::new(f)),
|
||||
cfg: Rc::new(cfg),
|
||||
hnd: Rc::new(f),
|
||||
_s: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, F, R, I, E> Handler<S> for WithAsync<T, S, F, R, I, E>
|
||||
impl<T, S, R, I, E> Handler<S> for WithAsync<T, S, R, I, E>
|
||||
where
|
||||
F: Fn(T) -> R + 'static,
|
||||
R: Future<Item = I, Error = E> + 'static,
|
||||
I: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
@@ -266,12 +195,12 @@ where
|
||||
{
|
||||
type Result = AsyncResult<HttpResponse>;
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
|
||||
let mut fut = WithAsyncHandlerFut {
|
||||
req,
|
||||
req: req.clone(),
|
||||
started: false,
|
||||
hnd: Rc::clone(&self.hnd),
|
||||
cfg: self.cfg.clone(),
|
||||
cfg: Rc::clone(&self.cfg),
|
||||
fut1: None,
|
||||
fut2: None,
|
||||
fut3: None,
|
||||
@@ -279,15 +208,14 @@ where
|
||||
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
||||
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
|
||||
Ok(Async::NotReady) => AsyncResult::future(Box::new(fut)),
|
||||
Err(e) => AsyncResult::err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WithAsyncHandlerFut<T, S, F, R, I, E>
|
||||
struct WithAsyncHandlerFut<T, S, R, I, E>
|
||||
where
|
||||
F: Fn(T) -> R,
|
||||
R: Future<Item = I, Error = E> + 'static,
|
||||
I: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
@@ -295,17 +223,16 @@ where
|
||||
S: 'static,
|
||||
{
|
||||
started: bool,
|
||||
hnd: Rc<UnsafeCell<F>>,
|
||||
cfg: ExtractorConfig<S, T>,
|
||||
hnd: Rc<FnWith<T, R>>,
|
||||
cfg: Rc<T::Config>,
|
||||
req: HttpRequest<S>,
|
||||
fut1: Option<Box<Future<Item = T, Error = Error>>>,
|
||||
fut2: Option<R>,
|
||||
fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
|
||||
}
|
||||
|
||||
impl<T, S, F, R, I, E> Future for WithAsyncHandlerFut<T, S, F, R, I, E>
|
||||
impl<T, S, R, I, E> Future for WithAsyncHandlerFut<T, S, R, I, E>
|
||||
where
|
||||
F: Fn(T) -> R,
|
||||
R: Future<Item = I, Error = E> + 'static,
|
||||
I: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
@@ -356,458 +283,101 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
self.fut2 = Some((*hnd)(item));
|
||||
self.fut2 = Some(self.hnd.as_ref().call_with(item));
|
||||
self.poll()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct With2<T1, T2, S, F, R>
|
||||
where
|
||||
F: Fn(T1, T2) -> R,
|
||||
T1: FromRequest<S> + 'static,
|
||||
T2: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
hnd: Rc<UnsafeCell<F>>,
|
||||
cfg1: ExtractorConfig<S, T1>,
|
||||
cfg2: ExtractorConfig<S, T2>,
|
||||
_s: PhantomData<S>,
|
||||
}
|
||||
macro_rules! with_factory_tuple ({$(($n:tt, $T:ident)),+} => {
|
||||
impl<$($T,)+ State, Func, Res> WithFactory<($($T,)+), State, Res> for Func
|
||||
where Func: Fn($($T,)+) -> Res + 'static,
|
||||
$($T: FromRequest<State> + 'static,)+
|
||||
Res: Responder + 'static,
|
||||
State: 'static,
|
||||
{
|
||||
fn create(self) -> With<($($T,)+), State, Res> {
|
||||
With::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+))
|
||||
}
|
||||
|
||||
impl<T1, T2, S, F, R> With2<T1, T2, S, F, R>
|
||||
where
|
||||
F: Fn(T1, T2) -> R,
|
||||
T1: FromRequest<S> + 'static,
|
||||
T2: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
pub fn new(
|
||||
f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2>,
|
||||
) -> Self {
|
||||
With2 {
|
||||
hnd: Rc::new(UnsafeCell::new(f)),
|
||||
cfg1,
|
||||
cfg2,
|
||||
_s: PhantomData,
|
||||
fn create_with_config(self, cfg: ($($T::Config,)+)) -> With<($($T,)+), State, Res> {
|
||||
With::new(move |($($n,)+)| (self)($($n,)+), cfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
impl<T1, T2, S, F, R> Handler<S> for With2<T1, T2, S, F, R>
|
||||
where
|
||||
F: Fn(T1, T2) -> R + 'static,
|
||||
R: Responder + 'static,
|
||||
T1: FromRequest<S> + 'static,
|
||||
T2: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
type Result = AsyncResult<HttpResponse>;
|
||||
macro_rules! with_async_factory_tuple ({$(($n:tt, $T:ident)),+} => {
|
||||
impl<$($T,)+ State, Func, Res, Item, Err> WithAsyncFactory<($($T,)+), State, Res, Item, Err> for Func
|
||||
where Func: Fn($($T,)+) -> Res + 'static,
|
||||
$($T: FromRequest<State> + 'static,)+
|
||||
Res: Future<Item=Item, Error=Err>,
|
||||
Item: Responder + 'static,
|
||||
Err: Into<Error>,
|
||||
State: 'static,
|
||||
{
|
||||
fn create(self) -> WithAsync<($($T,)+), State, Res, Item, Err> {
|
||||
WithAsync::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+))
|
||||
}
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
let mut fut = WithHandlerFut2 {
|
||||
req,
|
||||
started: false,
|
||||
hnd: Rc::clone(&self.hnd),
|
||||
cfg1: self.cfg1.clone(),
|
||||
cfg2: self.cfg2.clone(),
|
||||
item: None,
|
||||
fut1: None,
|
||||
fut2: None,
|
||||
fut3: None,
|
||||
};
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
||||
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
|
||||
Err(e) => AsyncResult::ok(e),
|
||||
fn create_with_config(self, cfg: ($($T::Config,)+)) -> WithAsync<($($T,)+), State, Res, Item, Err> {
|
||||
WithAsync::new(move |($($n,)+)| (self)($($n,)+), cfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
struct WithHandlerFut2<T1, T2, S, F, R>
|
||||
where
|
||||
F: Fn(T1, T2) -> R + 'static,
|
||||
R: Responder + 'static,
|
||||
T1: FromRequest<S> + 'static,
|
||||
T2: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
started: bool,
|
||||
hnd: Rc<UnsafeCell<F>>,
|
||||
cfg1: ExtractorConfig<S, T1>,
|
||||
cfg2: ExtractorConfig<S, T2>,
|
||||
req: HttpRequest<S>,
|
||||
item: Option<T1>,
|
||||
fut1: Option<Box<Future<Item = T1, Error = Error>>>,
|
||||
fut2: Option<Box<Future<Item = T2, Error = Error>>>,
|
||||
fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
|
||||
}
|
||||
with_factory_tuple!((a, A));
|
||||
with_factory_tuple!((a, A), (b, B));
|
||||
with_factory_tuple!((a, A), (b, B), (c, C));
|
||||
with_factory_tuple!((a, A), (b, B), (c, C), (d, D));
|
||||
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E));
|
||||
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F));
|
||||
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G));
|
||||
with_factory_tuple!(
|
||||
(a, A),
|
||||
(b, B),
|
||||
(c, C),
|
||||
(d, D),
|
||||
(e, E),
|
||||
(f, F),
|
||||
(g, G),
|
||||
(h, H)
|
||||
);
|
||||
with_factory_tuple!(
|
||||
(a, A),
|
||||
(b, B),
|
||||
(c, C),
|
||||
(d, D),
|
||||
(e, E),
|
||||
(f, F),
|
||||
(g, G),
|
||||
(h, H),
|
||||
(i, I)
|
||||
);
|
||||
|
||||
impl<T1, T2, S, F, R> Future for WithHandlerFut2<T1, T2, S, F, R>
|
||||
where
|
||||
F: Fn(T1, T2) -> R + 'static,
|
||||
R: Responder + 'static,
|
||||
T1: FromRequest<S> + 'static,
|
||||
T2: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(ref mut fut) = self.fut3 {
|
||||
return fut.poll();
|
||||
}
|
||||
|
||||
if !self.started {
|
||||
self.started = true;
|
||||
let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into();
|
||||
let item1 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut1 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
|
||||
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
|
||||
let item2 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.item = Some(item1);
|
||||
self.fut2 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
match (*hnd)(item1, item2).respond_to(&self.req) {
|
||||
Ok(item) => match item.into().into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut3 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
if self.fut1.is_some() {
|
||||
match self.fut1.as_mut().unwrap().poll()? {
|
||||
Async::Ready(item) => {
|
||||
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
|
||||
let item2 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.item = Some(item);
|
||||
self.fut2 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
match (*hnd)(item, item2).respond_to(&self.req) {
|
||||
Ok(item) => match item.into().into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut3 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
|
||||
let item = match self.fut2.as_mut().unwrap().poll()? {
|
||||
Async::Ready(item) => item,
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
};
|
||||
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
let item = match (*hnd)(self.item.take().unwrap(), item).respond_to(&self.req) {
|
||||
Ok(item) => item.into(),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
match item.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => self.fut3 = Some(fut),
|
||||
}
|
||||
|
||||
self.poll()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct With3<T1, T2, T3, S, F, R>
|
||||
where
|
||||
F: Fn(T1, T2, T3) -> R,
|
||||
T1: FromRequest<S> + 'static,
|
||||
T2: FromRequest<S> + 'static,
|
||||
T3: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
hnd: Rc<UnsafeCell<F>>,
|
||||
cfg1: ExtractorConfig<S, T1>,
|
||||
cfg2: ExtractorConfig<S, T2>,
|
||||
cfg3: ExtractorConfig<S, T3>,
|
||||
_s: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<T1, T2, T3, S, F, R> With3<T1, T2, T3, S, F, R>
|
||||
where
|
||||
F: Fn(T1, T2, T3) -> R,
|
||||
T1: FromRequest<S> + 'static,
|
||||
T2: FromRequest<S> + 'static,
|
||||
T3: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
pub fn new(
|
||||
f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2>,
|
||||
cfg3: ExtractorConfig<S, T3>,
|
||||
) -> Self {
|
||||
With3 {
|
||||
hnd: Rc::new(UnsafeCell::new(f)),
|
||||
cfg1,
|
||||
cfg2,
|
||||
cfg3,
|
||||
_s: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2, T3, S, F, R> Handler<S> for With3<T1, T2, T3, S, F, R>
|
||||
where
|
||||
F: Fn(T1, T2, T3) -> R + 'static,
|
||||
R: Responder + 'static,
|
||||
T1: FromRequest<S>,
|
||||
T2: FromRequest<S>,
|
||||
T3: FromRequest<S>,
|
||||
T1: 'static,
|
||||
T2: 'static,
|
||||
T3: 'static,
|
||||
S: 'static,
|
||||
{
|
||||
type Result = AsyncResult<HttpResponse>;
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
let mut fut = WithHandlerFut3 {
|
||||
req,
|
||||
hnd: Rc::clone(&self.hnd),
|
||||
cfg1: self.cfg1.clone(),
|
||||
cfg2: self.cfg2.clone(),
|
||||
cfg3: self.cfg3.clone(),
|
||||
started: false,
|
||||
item1: None,
|
||||
item2: None,
|
||||
fut1: None,
|
||||
fut2: None,
|
||||
fut3: None,
|
||||
fut4: None,
|
||||
};
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
||||
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
|
||||
Err(e) => AsyncResult::err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WithHandlerFut3<T1, T2, T3, S, F, R>
|
||||
where
|
||||
F: Fn(T1, T2, T3) -> R + 'static,
|
||||
R: Responder + 'static,
|
||||
T1: FromRequest<S> + 'static,
|
||||
T2: FromRequest<S> + 'static,
|
||||
T3: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
hnd: Rc<UnsafeCell<F>>,
|
||||
req: HttpRequest<S>,
|
||||
cfg1: ExtractorConfig<S, T1>,
|
||||
cfg2: ExtractorConfig<S, T2>,
|
||||
cfg3: ExtractorConfig<S, T3>,
|
||||
started: bool,
|
||||
item1: Option<T1>,
|
||||
item2: Option<T2>,
|
||||
fut1: Option<Box<Future<Item = T1, Error = Error>>>,
|
||||
fut2: Option<Box<Future<Item = T2, Error = Error>>>,
|
||||
fut3: Option<Box<Future<Item = T3, Error = Error>>>,
|
||||
fut4: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
|
||||
}
|
||||
|
||||
impl<T1, T2, T3, S, F, R> Future for WithHandlerFut3<T1, T2, T3, S, F, R>
|
||||
where
|
||||
F: Fn(T1, T2, T3) -> R + 'static,
|
||||
R: Responder + 'static,
|
||||
T1: FromRequest<S> + 'static,
|
||||
T2: FromRequest<S> + 'static,
|
||||
T3: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(ref mut fut) = self.fut4 {
|
||||
return fut.poll();
|
||||
}
|
||||
|
||||
if !self.started {
|
||||
self.started = true;
|
||||
let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into();
|
||||
let item1 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut1 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
|
||||
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
|
||||
let item2 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.item1 = Some(item1);
|
||||
self.fut2 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
|
||||
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
|
||||
let item3 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.item1 = Some(item1);
|
||||
self.item2 = Some(item2);
|
||||
self.fut3 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
match (*hnd)(item1, item2, item3).respond_to(&self.req) {
|
||||
Ok(item) => match item.into().into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut4 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
if self.fut1.is_some() {
|
||||
match self.fut1.as_mut().unwrap().poll()? {
|
||||
Async::Ready(item) => {
|
||||
self.item1 = Some(item);
|
||||
self.fut1.take();
|
||||
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
|
||||
let item2 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut2 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
|
||||
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
|
||||
let item3 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.item2 = Some(item2);
|
||||
self.fut3 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
match (*hnd)(self.item1.take().unwrap(), item2, item3)
|
||||
.respond_to(&self.req)
|
||||
{
|
||||
Ok(item) => match item.into().into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut4 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
|
||||
if self.fut2.is_some() {
|
||||
match self.fut2.as_mut().unwrap().poll()? {
|
||||
Async::Ready(item) => {
|
||||
self.fut2.take();
|
||||
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
|
||||
let item3 = match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.item2 = Some(item);
|
||||
self.fut3 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
};
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
match (*hnd)(self.item1.take().unwrap(), item, item3)
|
||||
.respond_to(&self.req)
|
||||
{
|
||||
Ok(item) => match item.into().into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut4 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
|
||||
let item = match self.fut3.as_mut().unwrap().poll()? {
|
||||
Async::Ready(item) => item,
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
};
|
||||
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
let item =
|
||||
match (*hnd)(self.item1.take().unwrap(), self.item2.take().unwrap(), item)
|
||||
.respond_to(&self.req)
|
||||
{
|
||||
Ok(item) => item.into(),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
match item.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => self.fut4 = Some(fut),
|
||||
}
|
||||
|
||||
self.poll()
|
||||
}
|
||||
}
|
||||
with_async_factory_tuple!((a, A));
|
||||
with_async_factory_tuple!((a, A), (b, B));
|
||||
with_async_factory_tuple!((a, A), (b, B), (c, C));
|
||||
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D));
|
||||
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E));
|
||||
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F));
|
||||
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G));
|
||||
with_async_factory_tuple!(
|
||||
(a, A),
|
||||
(b, B),
|
||||
(c, C),
|
||||
(d, D),
|
||||
(e, E),
|
||||
(f, F),
|
||||
(g, G),
|
||||
(h, H)
|
||||
);
|
||||
with_async_factory_tuple!(
|
||||
(a, A),
|
||||
(b, B),
|
||||
(c, C),
|
||||
(d, D),
|
||||
(e, E),
|
||||
(f, F),
|
||||
(g, G),
|
||||
(h, H),
|
||||
(i, I)
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Http client request
|
||||
use std::cell::UnsafeCell;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
use std::{fmt, io, str};
|
||||
@@ -7,59 +7,73 @@ use std::{fmt, io, str};
|
||||
use base64;
|
||||
use bytes::Bytes;
|
||||
use cookie::Cookie;
|
||||
use futures::unsync::mpsc::{unbounded, UnboundedSender};
|
||||
use futures::sync::mpsc::{unbounded, UnboundedSender};
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use http::header::{self, HeaderName, HeaderValue};
|
||||
use http::{Error as HttpError, HttpTryFrom, StatusCode};
|
||||
use rand;
|
||||
use sha1::Sha1;
|
||||
|
||||
use actix::prelude::*;
|
||||
use actix::{Addr, SystemService};
|
||||
|
||||
use body::{Binary, Body};
|
||||
use error::{Error, UrlParseError};
|
||||
use header::IntoHeaderValue;
|
||||
use httpmessage::HttpMessage;
|
||||
use payload::PayloadHelper;
|
||||
use payload::PayloadBuffer;
|
||||
|
||||
use client::{
|
||||
ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse,
|
||||
HttpResponseParserError, SendRequest, SendRequestError,
|
||||
ClientConnector, ClientRequest, ClientRequestBuilder, HttpResponseParserError,
|
||||
Pipeline, SendRequest, SendRequestError,
|
||||
};
|
||||
|
||||
use super::frame::Frame;
|
||||
use super::frame::{Frame, FramedMessage};
|
||||
use super::proto::{CloseReason, OpCode};
|
||||
use super::{Message, ProtocolError, WsWriter};
|
||||
|
||||
/// Websocket client error
|
||||
#[derive(Fail, Debug)]
|
||||
pub enum ClientError {
|
||||
/// Invalid url
|
||||
#[fail(display = "Invalid url")]
|
||||
InvalidUrl,
|
||||
/// Invalid response status
|
||||
#[fail(display = "Invalid response status")]
|
||||
InvalidResponseStatus(StatusCode),
|
||||
/// Invalid upgrade header
|
||||
#[fail(display = "Invalid upgrade header")]
|
||||
InvalidUpgradeHeader,
|
||||
/// Invalid connection header
|
||||
#[fail(display = "Invalid connection header")]
|
||||
InvalidConnectionHeader(HeaderValue),
|
||||
/// Missing CONNECTION header
|
||||
#[fail(display = "Missing CONNECTION header")]
|
||||
MissingConnectionHeader,
|
||||
/// Missing SEC-WEBSOCKET-ACCEPT header
|
||||
#[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")]
|
||||
MissingWebSocketAcceptHeader,
|
||||
/// Invalid challenge response
|
||||
#[fail(display = "Invalid challenge response")]
|
||||
InvalidChallengeResponse(String, HeaderValue),
|
||||
/// Http parsing error
|
||||
#[fail(display = "Http parsing error")]
|
||||
Http(Error),
|
||||
/// Url parsing error
|
||||
#[fail(display = "Url parsing error")]
|
||||
Url(UrlParseError),
|
||||
/// Response parsing error
|
||||
#[fail(display = "Response parsing error")]
|
||||
ResponseParseError(HttpResponseParserError),
|
||||
/// Send request error
|
||||
#[fail(display = "{}", _0)]
|
||||
SendRequest(SendRequestError),
|
||||
/// Protocol error
|
||||
#[fail(display = "{}", _0)]
|
||||
Protocol(#[cause] ProtocolError),
|
||||
/// IO Error
|
||||
#[fail(display = "{}", _0)]
|
||||
Io(io::Error),
|
||||
/// "Disconnected"
|
||||
#[fail(display = "Disconnected")]
|
||||
Disconnected,
|
||||
}
|
||||
@@ -104,15 +118,16 @@ impl From<HttpResponseParserError> for ClientError {
|
||||
///
|
||||
/// Example of `WebSocket` client usage is available in
|
||||
/// [websocket example](
|
||||
/// https://github.com/actix/actix-web/blob/master/examples/websocket/src/client.rs#L24)
|
||||
/// https://github.com/actix/examples/blob/master/websocket/src/client.rs#L24)
|
||||
pub struct Client {
|
||||
request: ClientRequestBuilder,
|
||||
err: Option<ClientError>,
|
||||
http_err: Option<HttpError>,
|
||||
origin: Option<HeaderValue>,
|
||||
protocols: Option<String>,
|
||||
conn: Addr<Unsync, ClientConnector>,
|
||||
conn: Addr<ClientConnector>,
|
||||
max_size: usize,
|
||||
no_masking: bool,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
@@ -122,9 +137,7 @@ impl Client {
|
||||
}
|
||||
|
||||
/// Create new websocket connection with custom `ClientConnector`
|
||||
pub fn with_connector<S: AsRef<str>>(
|
||||
uri: S, conn: Addr<Unsync, ClientConnector>,
|
||||
) -> Client {
|
||||
pub fn with_connector<S: AsRef<str>>(uri: S, conn: Addr<ClientConnector>) -> Client {
|
||||
let mut cl = Client {
|
||||
request: ClientRequest::build(),
|
||||
err: None,
|
||||
@@ -132,6 +145,7 @@ impl Client {
|
||||
origin: None,
|
||||
protocols: None,
|
||||
max_size: 65_536,
|
||||
no_masking: false,
|
||||
conn,
|
||||
};
|
||||
cl.request.uri(uri.as_ref());
|
||||
@@ -186,6 +200,12 @@ impl Client {
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable payload masking. By default ws client masks frame payload.
|
||||
pub fn no_masking(mut self) -> Self {
|
||||
self.no_masking = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set request header
|
||||
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
||||
where
|
||||
@@ -248,14 +268,14 @@ impl Client {
|
||||
}
|
||||
|
||||
// start handshake
|
||||
ClientHandshake::new(request, self.max_size)
|
||||
ClientHandshake::new(request, self.max_size, self.no_masking)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
tx: UnboundedSender<Bytes>,
|
||||
rx: PayloadHelper<ClientResponse>,
|
||||
rx: PayloadBuffer<Box<Pipeline>>,
|
||||
closed: bool,
|
||||
}
|
||||
|
||||
@@ -269,10 +289,13 @@ pub struct ClientHandshake {
|
||||
key: String,
|
||||
error: Option<ClientError>,
|
||||
max_size: usize,
|
||||
no_masking: bool,
|
||||
}
|
||||
|
||||
impl ClientHandshake {
|
||||
fn new(mut request: ClientRequest, max_size: usize) -> ClientHandshake {
|
||||
fn new(
|
||||
mut request: ClientRequest, max_size: usize, no_masking: bool,
|
||||
) -> ClientHandshake {
|
||||
// Generate a random key for the `Sec-WebSocket-Key` header.
|
||||
// a base64-encoded (see Section 4 of [RFC4648]) value that,
|
||||
// when decoded, is 16 bytes in length (RFC 6455)
|
||||
@@ -292,6 +315,7 @@ impl ClientHandshake {
|
||||
ClientHandshake {
|
||||
key,
|
||||
max_size,
|
||||
no_masking,
|
||||
request: Some(request.send()),
|
||||
tx: Some(tx),
|
||||
error: None,
|
||||
@@ -305,6 +329,7 @@ impl ClientHandshake {
|
||||
tx: None,
|
||||
error: Some(err),
|
||||
max_size: 0,
|
||||
no_masking: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,24 +431,27 @@ impl Future for ClientHandshake {
|
||||
|
||||
let inner = Inner {
|
||||
tx: self.tx.take().unwrap(),
|
||||
rx: PayloadHelper::new(resp),
|
||||
rx: PayloadBuffer::new(resp.payload()),
|
||||
closed: false,
|
||||
};
|
||||
|
||||
let inner = Rc::new(UnsafeCell::new(inner));
|
||||
let inner = Rc::new(RefCell::new(inner));
|
||||
Ok(Async::Ready((
|
||||
ClientReader {
|
||||
inner: Rc::clone(&inner),
|
||||
max_size: self.max_size,
|
||||
no_masking: self.no_masking,
|
||||
},
|
||||
ClientWriter { inner },
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Websocket reader client
|
||||
pub struct ClientReader {
|
||||
inner: Rc<UnsafeCell<Inner>>,
|
||||
inner: Rc<RefCell<Inner>>,
|
||||
max_size: usize,
|
||||
no_masking: bool,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ClientReader {
|
||||
@@ -432,26 +460,20 @@ impl fmt::Debug for ClientReader {
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientReader {
|
||||
#[inline]
|
||||
fn as_mut(&mut self) -> &mut Inner {
|
||||
unsafe { &mut *self.inner.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for ClientReader {
|
||||
type Item = Message;
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
let max_size = self.max_size;
|
||||
let inner = self.as_mut();
|
||||
let no_masking = self.no_masking;
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if inner.closed {
|
||||
return Ok(Async::Ready(None));
|
||||
}
|
||||
|
||||
// read
|
||||
match Frame::parse(&mut inner.rx, false, max_size) {
|
||||
match Frame::parse(&mut inner.rx, no_masking, max_size) {
|
||||
Ok(Async::Ready(Some(frame))) => {
|
||||
let (_finished, opcode, payload) = frame.unpack();
|
||||
|
||||
@@ -499,26 +521,23 @@ impl Stream for ClientReader {
|
||||
}
|
||||
}
|
||||
|
||||
/// Websocket writer client
|
||||
pub struct ClientWriter {
|
||||
inner: Rc<UnsafeCell<Inner>>,
|
||||
inner: Rc<RefCell<Inner>>,
|
||||
}
|
||||
|
||||
impl ClientWriter {
|
||||
/// Write payload
|
||||
#[inline]
|
||||
fn write(&mut self, mut data: Binary) {
|
||||
if !self.as_mut().closed {
|
||||
let _ = self.as_mut().tx.unbounded_send(data.take());
|
||||
fn write(&mut self, mut data: FramedMessage) {
|
||||
let inner = self.inner.borrow_mut();
|
||||
if !inner.closed {
|
||||
let _ = inner.tx.unbounded_send(data.0.take());
|
||||
} else {
|
||||
warn!("Trying to write to disconnected response");
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_mut(&mut self) -> &mut Inner {
|
||||
unsafe { &mut *self.inner.get() }
|
||||
}
|
||||
|
||||
/// Send text frame
|
||||
#[inline]
|
||||
pub fn text<T: Into<Binary>>(&mut self, text: T) {
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
use futures::sync::oneshot::Sender;
|
||||
use futures::unsync::oneshot;
|
||||
use futures::{Async, Poll};
|
||||
extern crate actix;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::sync::oneshot::{self, Sender};
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
|
||||
use actix::fut::ActorFuture;
|
||||
use actix::{
|
||||
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
|
||||
Syn, Unsync,
|
||||
use self::actix::dev::{
|
||||
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler,
|
||||
ToEnvelope,
|
||||
};
|
||||
use self::actix::fut::ActorFuture;
|
||||
use self::actix::{
|
||||
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler,
|
||||
Message as ActixMessage, SpawnHandle,
|
||||
};
|
||||
|
||||
use body::{Binary, Body};
|
||||
use context::{ActorHttpContext, Drain, Frame as ContextFrame};
|
||||
use error::{Error, ErrorInternalServerError};
|
||||
use error::{Error, ErrorInternalServerError, PayloadError};
|
||||
use httprequest::HttpRequest;
|
||||
|
||||
use ws::frame::Frame;
|
||||
use ws::frame::{Frame, FramedMessage};
|
||||
use ws::proto::{CloseReason, OpCode};
|
||||
use ws::WsWriter;
|
||||
use ws::{Message, ProtocolError, WsStream, WsWriter};
|
||||
|
||||
/// Execution context for `WebSockets` actors
|
||||
pub struct WebsocketContext<A, S = ()>
|
||||
where
|
||||
A: Actor<Context = WebsocketContext<A, S>>,
|
||||
{
|
||||
inner: ContextImpl<A>,
|
||||
inner: ContextParts<A>,
|
||||
stream: Option<SmallVec<[ContextFrame; 4]>>,
|
||||
request: HttpRequest<S>,
|
||||
disconnected: bool,
|
||||
@@ -75,16 +80,9 @@ where
|
||||
self.inner.cancel_future(handle)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
fn unsync_address(&mut self) -> Addr<Unsync, A> {
|
||||
self.inner.unsync_address()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
fn sync_address(&mut self) -> Addr<Syn, A> {
|
||||
self.inner.sync_address()
|
||||
fn address(&self) -> Addr<A> {
|
||||
self.inner.address()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,23 +91,39 @@ where
|
||||
A: Actor<Context = Self>,
|
||||
{
|
||||
#[inline]
|
||||
pub fn new(req: HttpRequest<S>, actor: A) -> WebsocketContext<A, S> {
|
||||
WebsocketContext::from_request(req).actor(actor)
|
||||
}
|
||||
|
||||
pub fn from_request(req: HttpRequest<S>) -> WebsocketContext<A, S> {
|
||||
WebsocketContext {
|
||||
inner: ContextImpl::new(None),
|
||||
/// Create a new Websocket context from a request and an actor
|
||||
pub fn create<P>(req: HttpRequest<S>, actor: A, stream: WsStream<P>) -> Body
|
||||
where
|
||||
A: StreamHandler<Message, ProtocolError>,
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
{
|
||||
let mb = Mailbox::default();
|
||||
let mut ctx = WebsocketContext {
|
||||
inner: ContextParts::new(mb.sender_producer()),
|
||||
stream: None,
|
||||
request: req,
|
||||
disconnected: false,
|
||||
}
|
||||
};
|
||||
ctx.add_stream(stream);
|
||||
|
||||
Body::Actor(Box::new(WebsocketContextFut::new(ctx, actor, mb)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn actor(mut self, actor: A) -> WebsocketContext<A, S> {
|
||||
self.inner.set_actor(actor);
|
||||
self
|
||||
/// Create a new Websocket context
|
||||
pub fn with_factory<F>(req: HttpRequest<S>, f: F) -> Body
|
||||
where
|
||||
F: FnOnce(&mut Self) -> A + 'static,
|
||||
{
|
||||
let mb = Mailbox::default();
|
||||
let mut ctx = WebsocketContext {
|
||||
inner: ContextParts::new(mb.sender_producer()),
|
||||
stream: None,
|
||||
request: req,
|
||||
disconnected: false,
|
||||
};
|
||||
|
||||
let act = f(&mut ctx);
|
||||
Body::Actor(Box::new(WebsocketContextFut::new(ctx, act, mb)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,15 +132,19 @@ where
|
||||
A: Actor<Context = Self>,
|
||||
{
|
||||
/// Write payload
|
||||
///
|
||||
/// This is a low-level function that accepts framed messages that should
|
||||
/// be created using `Frame::message()`. If you want to send text or binary
|
||||
/// data you should prefer the `text()` or `binary()` convenience functions
|
||||
/// that handle the framing for you.
|
||||
#[inline]
|
||||
fn write(&mut self, data: Binary) {
|
||||
pub fn write_raw(&mut self, data: FramedMessage) {
|
||||
if !self.disconnected {
|
||||
if self.stream.is_none() {
|
||||
self.stream = Some(SmallVec::new());
|
||||
}
|
||||
let stream = self.stream.as_mut().unwrap();
|
||||
stream.push(ContextFrame::Chunk(Some(data)));
|
||||
self.inner.modify();
|
||||
stream.push(ContextFrame::Chunk(Some(data.0)));
|
||||
} else {
|
||||
warn!("Trying to write to disconnected response");
|
||||
}
|
||||
@@ -147,7 +165,6 @@ where
|
||||
/// Returns drain future
|
||||
pub fn drain(&mut self) -> Drain<A> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.inner.modify();
|
||||
self.add_frame(ContextFrame::Drain(tx));
|
||||
Drain::new(rx)
|
||||
}
|
||||
@@ -155,19 +172,19 @@ where
|
||||
/// Send text frame
|
||||
#[inline]
|
||||
pub fn text<T: Into<Binary>>(&mut self, text: T) {
|
||||
self.write(Frame::message(text.into(), OpCode::Text, true, false));
|
||||
self.write_raw(Frame::message(text.into(), OpCode::Text, true, false));
|
||||
}
|
||||
|
||||
/// Send binary frame
|
||||
#[inline]
|
||||
pub fn binary<B: Into<Binary>>(&mut self, data: B) {
|
||||
self.write(Frame::message(data, OpCode::Binary, true, false));
|
||||
self.write_raw(Frame::message(data, OpCode::Binary, true, false));
|
||||
}
|
||||
|
||||
/// Send ping frame
|
||||
#[inline]
|
||||
pub fn ping(&mut self, message: &str) {
|
||||
self.write(Frame::message(
|
||||
self.write_raw(Frame::message(
|
||||
Vec::from(message),
|
||||
OpCode::Ping,
|
||||
true,
|
||||
@@ -178,7 +195,7 @@ where
|
||||
/// Send pong frame
|
||||
#[inline]
|
||||
pub fn pong(&mut self, message: &str) {
|
||||
self.write(Frame::message(
|
||||
self.write_raw(Frame::message(
|
||||
Vec::from(message),
|
||||
OpCode::Pong,
|
||||
true,
|
||||
@@ -189,7 +206,7 @@ where
|
||||
/// Send close frame
|
||||
#[inline]
|
||||
pub fn close(&mut self, reason: Option<CloseReason>) {
|
||||
self.write(Frame::close(reason, false));
|
||||
self.write_raw(Frame::close(reason, false));
|
||||
}
|
||||
|
||||
/// Check if connection still open
|
||||
@@ -206,7 +223,6 @@ where
|
||||
if let Some(s) = self.stream.as_mut() {
|
||||
s.push(frame)
|
||||
}
|
||||
self.inner.modify();
|
||||
}
|
||||
|
||||
/// Handle of the running future
|
||||
@@ -215,6 +231,13 @@ where
|
||||
pub fn handle(&self) -> SpawnHandle {
|
||||
self.inner.curr_handle()
|
||||
}
|
||||
|
||||
/// Set mailbox capacity
|
||||
///
|
||||
/// By default mailbox capacity is 16 messages.
|
||||
pub fn set_mailbox_capacity(&mut self, cap: usize) {
|
||||
self.inner.set_mailbox_capacity(cap)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> WsWriter for WebsocketContext<A, S>
|
||||
@@ -253,28 +276,52 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> ActorHttpContext for WebsocketContext<A, S>
|
||||
impl<A, S> AsyncContextParts<A> for WebsocketContext<A, S>
|
||||
where
|
||||
A: Actor<Context = Self>,
|
||||
{
|
||||
fn parts(&mut self) -> &mut ContextParts<A> {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
struct WebsocketContextFut<A, S>
|
||||
where
|
||||
A: Actor<Context = WebsocketContext<A, S>>,
|
||||
{
|
||||
fut: ContextFut<A, WebsocketContext<A, S>>,
|
||||
}
|
||||
|
||||
impl<A, S> WebsocketContextFut<A, S>
|
||||
where
|
||||
A: Actor<Context = WebsocketContext<A, S>>,
|
||||
{
|
||||
fn new(ctx: WebsocketContext<A, S>, act: A, mailbox: Mailbox<A>) -> Self {
|
||||
let fut = ContextFut::new(ctx, act, mailbox);
|
||||
WebsocketContextFut { fut }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> ActorHttpContext for WebsocketContextFut<A, S>
|
||||
where
|
||||
A: Actor<Context = WebsocketContext<A, S>>,
|
||||
S: 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn disconnected(&mut self) {
|
||||
self.disconnected = true;
|
||||
self.stop();
|
||||
self.fut.ctx().disconnected = true;
|
||||
self.fut.ctx().stop();
|
||||
}
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<SmallVec<[ContextFrame; 4]>>, Error> {
|
||||
let ctx: &mut WebsocketContext<A, S> = unsafe { &mut *(self as *mut _) };
|
||||
|
||||
if self.inner.alive() && self.inner.poll(ctx).is_err() {
|
||||
if self.fut.alive() && self.fut.poll().is_err() {
|
||||
return Err(ErrorInternalServerError("error"));
|
||||
}
|
||||
|
||||
// frames
|
||||
if let Some(data) = self.stream.take() {
|
||||
if let Some(data) = self.fut.ctx().stream.take() {
|
||||
Ok(Async::Ready(Some(data)))
|
||||
} else if self.inner.alive() {
|
||||
} else if self.fut.alive() {
|
||||
Ok(Async::NotReady)
|
||||
} else {
|
||||
Ok(Async::Ready(None))
|
||||
@@ -282,23 +329,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, M, S> ToEnvelope<Syn, A, M> for WebsocketContext<A, S>
|
||||
impl<A, M, S> ToEnvelope<A, M> for WebsocketContext<A, S>
|
||||
where
|
||||
A: Actor<Context = WebsocketContext<A, S>> + Handler<M>,
|
||||
M: Message + Send + 'static,
|
||||
M: ActixMessage + Send + 'static,
|
||||
M::Result: Send,
|
||||
{
|
||||
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> SyncEnvelope<A> {
|
||||
SyncEnvelope::new(msg, tx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> From<WebsocketContext<A, S>> for Body
|
||||
where
|
||||
A: Actor<Context = WebsocketContext<A, S>>,
|
||||
S: 'static,
|
||||
{
|
||||
fn from(ctx: WebsocketContext<A, S>) -> Body {
|
||||
Body::Actor(Box::new(ctx))
|
||||
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> Envelope<A> {
|
||||
Envelope::new(msg, tx)
|
||||
}
|
||||
}
|
||||
|
||||
117
src/ws/frame.rs
117
src/ws/frame.rs
@@ -1,13 +1,12 @@
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
|
||||
use byteorder::{BigEndian, ByteOrder, NetworkEndian};
|
||||
use byteorder::{ByteOrder, LittleEndian, NetworkEndian};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use futures::{Async, Poll, Stream};
|
||||
use rand;
|
||||
use std::{fmt, ptr};
|
||||
use std::fmt;
|
||||
|
||||
use body::Binary;
|
||||
use error::PayloadError;
|
||||
use payload::PayloadHelper;
|
||||
use payload::PayloadBuffer;
|
||||
|
||||
use ws::mask::apply_mask;
|
||||
use ws::proto::{CloseCode, CloseReason, OpCode};
|
||||
@@ -29,7 +28,7 @@ impl Frame {
|
||||
|
||||
/// Create a new Close control frame.
|
||||
#[inline]
|
||||
pub fn close(reason: Option<CloseReason>, genmask: bool) -> Binary {
|
||||
pub fn close(reason: Option<CloseReason>, genmask: bool) -> FramedMessage {
|
||||
let payload = match reason {
|
||||
None => Vec::new(),
|
||||
Some(reason) => {
|
||||
@@ -49,7 +48,7 @@ impl Frame {
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
|
||||
fn read_copy_md<S>(
|
||||
pl: &mut PayloadHelper<S>, server: bool, max_size: usize,
|
||||
pl: &mut PayloadBuffer<S>, server: bool, max_size: usize,
|
||||
) -> Poll<Option<(usize, bool, OpCode, usize, Option<u32>)>, ProtocolError>
|
||||
where
|
||||
S: Stream<Item = Bytes, Error = PayloadError>,
|
||||
@@ -95,9 +94,12 @@ impl Frame {
|
||||
Async::Ready(None) => return Ok(Async::Ready(None)),
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
};
|
||||
let len = NetworkEndian::read_uint(&buf[idx..], 8) as usize;
|
||||
let len = NetworkEndian::read_uint(&buf[idx..], 8);
|
||||
if len > max_size as u64 {
|
||||
return Err(ProtocolError::Overflow);
|
||||
}
|
||||
idx += 8;
|
||||
len
|
||||
len as usize
|
||||
} else {
|
||||
len as usize
|
||||
};
|
||||
@@ -115,8 +117,7 @@ impl Frame {
|
||||
};
|
||||
|
||||
let mask: &[u8] = &buf[idx..idx + 4];
|
||||
let mask_u32: u32 =
|
||||
unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) };
|
||||
let mask_u32 = LittleEndian::read_u32(mask);
|
||||
idx += 4;
|
||||
Some(mask_u32)
|
||||
} else {
|
||||
@@ -167,9 +168,12 @@ impl Frame {
|
||||
if chunk_len < 10 {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
let len = NetworkEndian::read_uint(&chunk[idx..], 8) as usize;
|
||||
let len = NetworkEndian::read_uint(&chunk[idx..], 8);
|
||||
if len > max_size as u64 {
|
||||
return Err(ProtocolError::Overflow);
|
||||
}
|
||||
idx += 8;
|
||||
len
|
||||
len as usize
|
||||
} else {
|
||||
len as usize
|
||||
};
|
||||
@@ -185,8 +189,7 @@ impl Frame {
|
||||
}
|
||||
|
||||
let mask: &[u8] = &chunk[idx..idx + 4];
|
||||
let mask_u32: u32 =
|
||||
unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) };
|
||||
let mask_u32 = LittleEndian::read_u32(mask);
|
||||
idx += 4;
|
||||
Some(mask_u32)
|
||||
} else {
|
||||
@@ -198,7 +201,7 @@ impl Frame {
|
||||
|
||||
/// Parse the input stream into a frame.
|
||||
pub fn parse<S>(
|
||||
pl: &mut PayloadHelper<S>, server: bool, max_size: usize,
|
||||
pl: &mut PayloadBuffer<S>, server: bool, max_size: usize,
|
||||
) -> Poll<Option<Frame>, ProtocolError>
|
||||
where
|
||||
S: Stream<Item = Bytes, Error = PayloadError>,
|
||||
@@ -227,7 +230,7 @@ impl Frame {
|
||||
}
|
||||
|
||||
// remove prefix
|
||||
pl.drop_payload(idx);
|
||||
pl.drop_bytes(idx);
|
||||
|
||||
// no need for body
|
||||
if length == 0 {
|
||||
@@ -257,13 +260,14 @@ impl Frame {
|
||||
}
|
||||
|
||||
// unmask
|
||||
if let Some(mask) = mask {
|
||||
let p: &mut [u8] = unsafe {
|
||||
let ptr: &[u8] = &data;
|
||||
&mut *(ptr as *const _ as *mut _)
|
||||
};
|
||||
apply_mask(p, mask);
|
||||
}
|
||||
let data = if let Some(mask) = mask {
|
||||
let mut buf = BytesMut::new();
|
||||
buf.extend_from_slice(&data);
|
||||
apply_mask(&mut buf, mask);
|
||||
buf.freeze()
|
||||
} else {
|
||||
data
|
||||
};
|
||||
|
||||
Ok(Async::Ready(Some(Frame {
|
||||
finished,
|
||||
@@ -275,7 +279,7 @@ impl Frame {
|
||||
/// Parse the payload of a close frame.
|
||||
pub fn parse_close_payload(payload: &Binary) -> Option<CloseReason> {
|
||||
if payload.len() >= 2 {
|
||||
let raw_code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16;
|
||||
let raw_code = NetworkEndian::read_u16(payload.as_ref());
|
||||
let code = CloseCode::from(raw_code);
|
||||
let description = if payload.len() > 2 {
|
||||
Some(String::from_utf8_lossy(&payload.as_ref()[2..]).into())
|
||||
@@ -291,7 +295,7 @@ impl Frame {
|
||||
/// Generate binary representation
|
||||
pub fn message<B: Into<Binary>>(
|
||||
data: B, code: OpCode, finished: bool, genmask: bool,
|
||||
) -> Binary {
|
||||
) -> FramedMessage {
|
||||
let payload = data.into();
|
||||
let one: u8 = if finished {
|
||||
0x80 | Into::<u8>::into(code)
|
||||
@@ -312,39 +316,28 @@ impl Frame {
|
||||
} else if payload_len <= 65_535 {
|
||||
let mut buf = BytesMut::with_capacity(p_len + 4);
|
||||
buf.put_slice(&[one, two | 126]);
|
||||
{
|
||||
let buf_mut = unsafe { buf.bytes_mut() };
|
||||
BigEndian::write_u16(&mut buf_mut[..2], payload_len as u16);
|
||||
}
|
||||
unsafe { buf.advance_mut(2) };
|
||||
buf.put_u16_be(payload_len as u16);
|
||||
buf
|
||||
} else {
|
||||
let mut buf = BytesMut::with_capacity(p_len + 10);
|
||||
buf.put_slice(&[one, two | 127]);
|
||||
{
|
||||
let buf_mut = unsafe { buf.bytes_mut() };
|
||||
BigEndian::write_u64(&mut buf_mut[..8], payload_len as u64);
|
||||
}
|
||||
unsafe { buf.advance_mut(8) };
|
||||
buf.put_u64_be(payload_len as u64);
|
||||
buf
|
||||
};
|
||||
|
||||
if genmask {
|
||||
let binary = if genmask {
|
||||
let mask = rand::random::<u32>();
|
||||
unsafe {
|
||||
{
|
||||
let buf_mut = buf.bytes_mut();
|
||||
*(buf_mut as *mut _ as *mut u32) = mask;
|
||||
buf_mut[4..payload_len + 4].copy_from_slice(payload.as_ref());
|
||||
apply_mask(&mut buf_mut[4..], mask);
|
||||
}
|
||||
buf.advance_mut(payload_len + 4);
|
||||
}
|
||||
buf.put_u32_le(mask);
|
||||
buf.extend_from_slice(payload.as_ref());
|
||||
let pos = buf.len() - payload_len;
|
||||
apply_mask(&mut buf[pos..], mask);
|
||||
buf.into()
|
||||
} else {
|
||||
buf.put_slice(payload.as_ref());
|
||||
buf.into()
|
||||
}
|
||||
};
|
||||
|
||||
FramedMessage(binary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,6 +374,10 @@ impl fmt::Display for Frame {
|
||||
}
|
||||
}
|
||||
|
||||
/// `WebSocket` message with framing.
|
||||
#[derive(Debug)]
|
||||
pub struct FramedMessage(pub(crate) Binary);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -402,14 +399,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
let mut buf = PayloadHelper::new(once(Ok(BytesMut::from(
|
||||
let mut buf = PayloadBuffer::new(once(Ok(BytesMut::from(
|
||||
&[0b0000_0001u8, 0b0000_0001u8][..],
|
||||
).freeze())));
|
||||
assert!(is_none(&Frame::parse(&mut buf, false, 1024)));
|
||||
|
||||
let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
|
||||
buf.extend(b"1");
|
||||
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
||||
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
|
||||
|
||||
let frame = extract(Frame::parse(&mut buf, false, 1024));
|
||||
assert!(!frame.finished);
|
||||
@@ -420,7 +417,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_parse_length0() {
|
||||
let buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]);
|
||||
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
||||
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
|
||||
|
||||
let frame = extract(Frame::parse(&mut buf, false, 1024));
|
||||
assert!(!frame.finished);
|
||||
@@ -431,13 +428,13 @@ mod tests {
|
||||
#[test]
|
||||
fn test_parse_length2() {
|
||||
let buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]);
|
||||
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
||||
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
|
||||
assert!(is_none(&Frame::parse(&mut buf, false, 1024)));
|
||||
|
||||
let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]);
|
||||
buf.extend(&[0u8, 4u8][..]);
|
||||
buf.extend(b"1234");
|
||||
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
||||
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
|
||||
|
||||
let frame = extract(Frame::parse(&mut buf, false, 1024));
|
||||
assert!(!frame.finished);
|
||||
@@ -448,13 +445,13 @@ mod tests {
|
||||
#[test]
|
||||
fn test_parse_length4() {
|
||||
let buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]);
|
||||
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
||||
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
|
||||
assert!(is_none(&Frame::parse(&mut buf, false, 1024)));
|
||||
|
||||
let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]);
|
||||
buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]);
|
||||
buf.extend(b"1234");
|
||||
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
||||
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
|
||||
|
||||
let frame = extract(Frame::parse(&mut buf, false, 1024));
|
||||
assert!(!frame.finished);
|
||||
@@ -467,7 +464,7 @@ mod tests {
|
||||
let mut buf = BytesMut::from(&[0b0000_0001u8, 0b1000_0001u8][..]);
|
||||
buf.extend(b"0001");
|
||||
buf.extend(b"1");
|
||||
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
||||
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
|
||||
|
||||
assert!(Frame::parse(&mut buf, false, 1024).is_err());
|
||||
|
||||
@@ -481,7 +478,7 @@ mod tests {
|
||||
fn test_parse_frame_no_mask() {
|
||||
let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
|
||||
buf.extend(&[1u8]);
|
||||
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
||||
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
|
||||
|
||||
assert!(Frame::parse(&mut buf, true, 1024).is_err());
|
||||
|
||||
@@ -495,7 +492,7 @@ mod tests {
|
||||
fn test_parse_frame_max_size() {
|
||||
let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]);
|
||||
buf.extend(&[1u8, 1u8]);
|
||||
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
||||
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
|
||||
|
||||
assert!(Frame::parse(&mut buf, true, 1).is_err());
|
||||
|
||||
@@ -511,7 +508,7 @@ mod tests {
|
||||
|
||||
let mut v = vec![137u8, 4u8];
|
||||
v.extend(b"data");
|
||||
assert_eq!(frame, v.into());
|
||||
assert_eq!(frame.0, v.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -520,7 +517,7 @@ mod tests {
|
||||
|
||||
let mut v = vec![138u8, 4u8];
|
||||
v.extend(b"data");
|
||||
assert_eq!(frame, v.into());
|
||||
assert_eq!(frame.0, v.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -530,12 +527,12 @@ mod tests {
|
||||
|
||||
let mut v = vec![136u8, 6u8, 3u8, 232u8];
|
||||
v.extend(b"data");
|
||||
assert_eq!(frame, v.into());
|
||||
assert_eq!(frame.0, v.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_close_frame() {
|
||||
let frame = Frame::close(None, false);
|
||||
assert_eq!(frame, vec![0x88, 0x00].into());
|
||||
assert_eq!(frame.0, vec![0x88, 0x00].into());
|
||||
}
|
||||
}
|
||||
|
||||
185
src/ws/mask.rs
185
src/ws/mask.rs
@@ -1,119 +1,126 @@
|
||||
//! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs)
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
|
||||
use std::cmp::min;
|
||||
use std::mem::uninitialized;
|
||||
use std::ptr::copy_nonoverlapping;
|
||||
use std::slice;
|
||||
|
||||
/// Mask/unmask a frame.
|
||||
#[inline]
|
||||
pub fn apply_mask(buf: &mut [u8], mask: u32) {
|
||||
apply_mask_fast32(buf, mask)
|
||||
}
|
||||
// Holds a slice guaranteed to be shorter than 8 bytes
|
||||
struct ShortSlice<'a>(&'a mut [u8]);
|
||||
|
||||
/// A safe unoptimized mask application.
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) {
|
||||
for (i, byte) in buf.iter_mut().enumerate() {
|
||||
*byte ^= mask[i & 3];
|
||||
impl<'a> ShortSlice<'a> {
|
||||
unsafe fn new(slice: &'a mut [u8]) -> Self {
|
||||
// Sanity check for debug builds
|
||||
debug_assert!(slice.len() < 8);
|
||||
ShortSlice(slice)
|
||||
}
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Faster version of `apply_mask()` which operates on 8-byte blocks.
|
||||
#[inline]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))]
|
||||
fn apply_mask_fast32(buf: &mut [u8], mask_u32: u32) {
|
||||
let mut ptr = buf.as_mut_ptr();
|
||||
let mut len = buf.len();
|
||||
pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) {
|
||||
// Extend the mask to 64 bits
|
||||
let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64);
|
||||
// Split the buffer into three segments
|
||||
let (head, mid, tail) = align_buf(buf);
|
||||
|
||||
// Possible first unaligned block.
|
||||
let head = min(len, (8 - (ptr as usize & 0x7)) & 0x3);
|
||||
let mask_u32 = if head > 0 {
|
||||
let n = if head > 4 { head - 4 } else { head };
|
||||
|
||||
let mask_u32 = if n > 0 {
|
||||
unsafe {
|
||||
xor_mem(ptr, mask_u32, n);
|
||||
ptr = ptr.offset(head as isize);
|
||||
}
|
||||
len -= n;
|
||||
if cfg!(target_endian = "big") {
|
||||
mask_u32.rotate_left(8 * n as u32)
|
||||
} else {
|
||||
mask_u32.rotate_right(8 * n as u32)
|
||||
}
|
||||
// Initial unaligned segment
|
||||
let head_len = head.len();
|
||||
if head_len > 0 {
|
||||
xor_short(head, mask_u64);
|
||||
if cfg!(target_endian = "big") {
|
||||
mask_u64 = mask_u64.rotate_left(8 * head_len as u32);
|
||||
} else {
|
||||
mask_u32
|
||||
};
|
||||
|
||||
if head > 4 {
|
||||
unsafe {
|
||||
*(ptr as *mut u32) ^= mask_u32;
|
||||
ptr = ptr.offset(4);
|
||||
len -= 4;
|
||||
}
|
||||
}
|
||||
mask_u32
|
||||
} else {
|
||||
mask_u32
|
||||
};
|
||||
|
||||
if len > 0 {
|
||||
debug_assert_eq!(ptr as usize % 4, 0);
|
||||
}
|
||||
|
||||
// Properly aligned middle of the data.
|
||||
if len >= 8 {
|
||||
let mut mask_u64 = mask_u32 as u64;
|
||||
mask_u64 = mask_u64 << 32 | mask_u32 as u64;
|
||||
|
||||
while len >= 8 {
|
||||
unsafe {
|
||||
*(ptr as *mut u64) ^= mask_u64;
|
||||
ptr = ptr.offset(8);
|
||||
len -= 8;
|
||||
}
|
||||
mask_u64 = mask_u64.rotate_right(8 * head_len as u32);
|
||||
}
|
||||
}
|
||||
|
||||
while len >= 4 {
|
||||
unsafe {
|
||||
*(ptr as *mut u32) ^= mask_u32;
|
||||
ptr = ptr.offset(4);
|
||||
len -= 4;
|
||||
}
|
||||
// Aligned segment
|
||||
for v in mid {
|
||||
*v ^= mask_u64;
|
||||
}
|
||||
|
||||
// Possible last block.
|
||||
if len > 0 {
|
||||
unsafe {
|
||||
xor_mem(ptr, mask_u32, len);
|
||||
}
|
||||
// Final unaligned segment
|
||||
if tail.len() > 0 {
|
||||
xor_short(tail, mask_u64);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
// TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so
|
||||
// inefficient, it could be done better. The compiler does not see that len is
|
||||
// limited to 3.
|
||||
unsafe fn xor_mem(ptr: *mut u8, mask: u32, len: usize) {
|
||||
let mut b: u32 = uninitialized();
|
||||
#[allow(trivial_casts)]
|
||||
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len);
|
||||
b ^= mask;
|
||||
#[allow(trivial_casts)]
|
||||
copy_nonoverlapping(&b as *const _ as *const u8, ptr, len);
|
||||
// inefficient, it could be done better. The compiler does not understand that
|
||||
// a `ShortSlice` must be smaller than a u64.
|
||||
#[cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
allow(needless_pass_by_value)
|
||||
)]
|
||||
fn xor_short(buf: ShortSlice, mask: u64) {
|
||||
// Unsafe: we know that a `ShortSlice` fits in a u64
|
||||
unsafe {
|
||||
let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len());
|
||||
let mut b: u64 = 0;
|
||||
#[allow(trivial_casts)]
|
||||
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len);
|
||||
b ^= mask;
|
||||
#[allow(trivial_casts)]
|
||||
copy_nonoverlapping(&b as *const _ as *const u8, ptr, len);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
// Unsafe: caller must ensure the buffer has the correct size and alignment
|
||||
unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] {
|
||||
// Assert correct size and alignment in debug builds
|
||||
debug_assert!(buf.len().trailing_zeros() >= 3);
|
||||
debug_assert!((buf.as_ptr() as usize).trailing_zeros() >= 3);
|
||||
|
||||
slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
// Splits a slice into three parts: an unaligned short head and tail, plus an aligned
|
||||
// u64 mid section.
|
||||
fn align_buf(buf: &mut [u8]) -> (ShortSlice, &mut [u64], ShortSlice) {
|
||||
let start_ptr = buf.as_ptr() as usize;
|
||||
let end_ptr = start_ptr + buf.len();
|
||||
|
||||
// Round *up* to next aligned boundary for start
|
||||
let start_aligned = (start_ptr + 7) & !0x7;
|
||||
// Round *down* to last aligned boundary for end
|
||||
let end_aligned = end_ptr & !0x7;
|
||||
|
||||
if end_aligned >= start_aligned {
|
||||
// We have our three segments (head, mid, tail)
|
||||
let (tmp, tail) = buf.split_at_mut(end_aligned - start_ptr);
|
||||
let (head, mid) = tmp.split_at_mut(start_aligned - start_ptr);
|
||||
|
||||
// Unsafe: we know the middle section is correctly aligned, and the outer
|
||||
// sections are smaller than 8 bytes
|
||||
unsafe { (ShortSlice::new(head), cast_slice(mid), ShortSlice(tail)) }
|
||||
} else {
|
||||
// We didn't cross even one aligned boundary!
|
||||
|
||||
// Unsafe: The outer sections are smaller than 8 bytes
|
||||
unsafe { (ShortSlice::new(buf), &mut [], ShortSlice::new(&mut [])) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{apply_mask_fallback, apply_mask_fast32};
|
||||
use std::ptr;
|
||||
use super::apply_mask;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
|
||||
/// A safe unoptimized mask application.
|
||||
fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) {
|
||||
for (i, byte) in buf.iter_mut().enumerate() {
|
||||
*byte ^= mask[i & 3];
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_mask() {
|
||||
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
||||
let mask_u32: u32 = unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) };
|
||||
let mask_u32: u32 = LittleEndian::read_u32(&mask);
|
||||
|
||||
let unmasked = vec![
|
||||
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17,
|
||||
@@ -126,7 +133,7 @@ mod tests {
|
||||
apply_mask_fallback(&mut masked, &mask);
|
||||
|
||||
let mut masked_fast = unmasked.clone();
|
||||
apply_mask_fast32(&mut masked_fast, mask_u32);
|
||||
apply_mask(&mut masked_fast, mask_u32);
|
||||
|
||||
assert_eq!(masked, masked_fast);
|
||||
}
|
||||
@@ -137,7 +144,7 @@ mod tests {
|
||||
apply_mask_fallback(&mut masked[1..], &mask);
|
||||
|
||||
let mut masked_fast = unmasked.clone();
|
||||
apply_mask_fast32(&mut masked_fast[1..], mask_u32);
|
||||
apply_mask(&mut masked_fast[1..], mask_u32);
|
||||
|
||||
assert_eq!(masked, masked_fast);
|
||||
}
|
||||
|
||||
207
src/ws/mod.rs
207
src/ws/mod.rs
@@ -7,14 +7,14 @@
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate actix;
|
||||
//! # extern crate actix_web;
|
||||
//! # use actix::*;
|
||||
//! # extern crate actix;
|
||||
//! # use actix::prelude::*;
|
||||
//! # use actix_web::*;
|
||||
//! use actix_web::{ws, HttpRequest, HttpResponse};
|
||||
//!
|
||||
//! // do websocket handshake and start actor
|
||||
//! fn ws_index(req: HttpRequest) -> Result<HttpResponse> {
|
||||
//! fn ws_index(req: &HttpRequest) -> Result<HttpResponse> {
|
||||
//! ws::start(req, Ws)
|
||||
//! }
|
||||
//!
|
||||
@@ -26,7 +26,6 @@
|
||||
//!
|
||||
//! // Handler for ws::Message messages
|
||||
//! impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
|
||||
//!
|
||||
//! fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||
//! match msg {
|
||||
//! ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||
@@ -47,14 +46,14 @@ use bytes::Bytes;
|
||||
use futures::{Async, Poll, Stream};
|
||||
use http::{header, Method, StatusCode};
|
||||
|
||||
use actix::{Actor, AsyncContext, StreamHandler};
|
||||
use super::actix::{Actor, StreamHandler};
|
||||
|
||||
use body::Binary;
|
||||
use error::{Error, PayloadError, ResponseError};
|
||||
use httpmessage::HttpMessage;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder};
|
||||
use payload::PayloadHelper;
|
||||
use payload::PayloadBuffer;
|
||||
|
||||
mod client;
|
||||
mod context;
|
||||
@@ -66,7 +65,7 @@ pub use self::client::{
|
||||
Client, ClientError, ClientHandshake, ClientReader, ClientWriter,
|
||||
};
|
||||
pub use self::context::WebsocketContext;
|
||||
pub use self::frame::Frame;
|
||||
pub use self::frame::{Frame, FramedMessage};
|
||||
pub use self::proto::{CloseCode, CloseReason, OpCode};
|
||||
|
||||
/// Websocket protocol errors
|
||||
@@ -160,26 +159,29 @@ impl ResponseError for HandshakeError {
|
||||
/// `WebSocket` Message
|
||||
#[derive(Debug, PartialEq, Message)]
|
||||
pub enum Message {
|
||||
/// Text message
|
||||
Text(String),
|
||||
/// Binary message
|
||||
Binary(Binary),
|
||||
/// Ping message
|
||||
Ping(String),
|
||||
/// Pong message
|
||||
Pong(String),
|
||||
/// Close message with optional reason
|
||||
Close(Option<CloseReason>),
|
||||
}
|
||||
|
||||
/// Do websocket handshake and start actor
|
||||
pub fn start<A, S>(req: HttpRequest<S>, actor: A) -> Result<HttpResponse, Error>
|
||||
pub fn start<A, S>(req: &HttpRequest<S>, actor: A) -> Result<HttpResponse, Error>
|
||||
where
|
||||
A: Actor<Context = WebsocketContext<A, S>> + StreamHandler<Message, ProtocolError>,
|
||||
S: 'static,
|
||||
{
|
||||
let mut resp = handshake(&req)?;
|
||||
let stream = WsStream::new(req.clone());
|
||||
let mut resp = handshake(req)?;
|
||||
let stream = WsStream::new(req.payload());
|
||||
|
||||
let mut ctx = WebsocketContext::new(req, actor);
|
||||
ctx.add_stream(stream);
|
||||
|
||||
Ok(resp.body(ctx))
|
||||
let body = WebsocketContext::create(req.clone(), actor, stream);
|
||||
Ok(resp.body(body))
|
||||
}
|
||||
|
||||
/// Prepare `WebSocket` handshake response.
|
||||
@@ -251,7 +253,7 @@ pub fn handshake<S>(
|
||||
|
||||
/// Maps `Payload` stream into stream of `ws::Message` items
|
||||
pub struct WsStream<S> {
|
||||
rx: PayloadHelper<S>,
|
||||
rx: PayloadBuffer<S>,
|
||||
closed: bool,
|
||||
max_size: usize,
|
||||
}
|
||||
@@ -263,7 +265,7 @@ where
|
||||
/// Create new websocket frames stream
|
||||
pub fn new(stream: S) -> WsStream<S> {
|
||||
WsStream {
|
||||
rx: PayloadHelper::new(stream),
|
||||
rx: PayloadBuffer::new(stream),
|
||||
closed: false,
|
||||
max_size: 65_536,
|
||||
}
|
||||
@@ -357,161 +359,100 @@ pub trait WsWriter {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::{header, HeaderMap, Method, Uri, Version};
|
||||
use std::str::FromStr;
|
||||
use http::{header, Method};
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_handshake() {
|
||||
let req = HttpRequest::new(
|
||||
Method::POST,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
HeaderMap::new(),
|
||||
None,
|
||||
);
|
||||
let req = TestRequest::default().method(Method::POST).finish();
|
||||
assert_eq!(
|
||||
HandshakeError::GetMethodRequired,
|
||||
handshake(&req).err().unwrap()
|
||||
);
|
||||
|
||||
let req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
HeaderMap::new(),
|
||||
None,
|
||||
);
|
||||
let req = TestRequest::default().finish();
|
||||
assert_eq!(
|
||||
HandshakeError::NoWebsocketUpgrade,
|
||||
handshake(&req).err().unwrap()
|
||||
);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(header::UPGRADE, header::HeaderValue::from_static("test"));
|
||||
let req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
headers,
|
||||
None,
|
||||
);
|
||||
let req = TestRequest::default()
|
||||
.header(header::UPGRADE, header::HeaderValue::from_static("test"))
|
||||
.finish();
|
||||
assert_eq!(
|
||||
HandshakeError::NoWebsocketUpgrade,
|
||||
handshake(&req).err().unwrap()
|
||||
);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"),
|
||||
);
|
||||
let req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
headers,
|
||||
None,
|
||||
);
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"),
|
||||
).finish();
|
||||
assert_eq!(
|
||||
HandshakeError::NoConnectionUpgrade,
|
||||
handshake(&req).err().unwrap()
|
||||
);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"),
|
||||
);
|
||||
headers.insert(
|
||||
header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"),
|
||||
);
|
||||
let req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
headers,
|
||||
None,
|
||||
);
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"),
|
||||
).header(
|
||||
header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"),
|
||||
).finish();
|
||||
assert_eq!(
|
||||
HandshakeError::NoVersionHeader,
|
||||
handshake(&req).err().unwrap()
|
||||
);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"),
|
||||
);
|
||||
headers.insert(
|
||||
header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"),
|
||||
);
|
||||
headers.insert(
|
||||
header::SEC_WEBSOCKET_VERSION,
|
||||
header::HeaderValue::from_static("5"),
|
||||
);
|
||||
let req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
headers,
|
||||
None,
|
||||
);
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"),
|
||||
).header(
|
||||
header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"),
|
||||
).header(
|
||||
header::SEC_WEBSOCKET_VERSION,
|
||||
header::HeaderValue::from_static("5"),
|
||||
).finish();
|
||||
assert_eq!(
|
||||
HandshakeError::UnsupportedVersion,
|
||||
handshake(&req).err().unwrap()
|
||||
);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"),
|
||||
);
|
||||
headers.insert(
|
||||
header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"),
|
||||
);
|
||||
headers.insert(
|
||||
header::SEC_WEBSOCKET_VERSION,
|
||||
header::HeaderValue::from_static("13"),
|
||||
);
|
||||
let req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
headers,
|
||||
None,
|
||||
);
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"),
|
||||
).header(
|
||||
header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"),
|
||||
).header(
|
||||
header::SEC_WEBSOCKET_VERSION,
|
||||
header::HeaderValue::from_static("13"),
|
||||
).finish();
|
||||
assert_eq!(
|
||||
HandshakeError::BadWebsocketKey,
|
||||
handshake(&req).err().unwrap()
|
||||
);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"),
|
||||
);
|
||||
headers.insert(
|
||||
header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"),
|
||||
);
|
||||
headers.insert(
|
||||
header::SEC_WEBSOCKET_VERSION,
|
||||
header::HeaderValue::from_static("13"),
|
||||
);
|
||||
headers.insert(
|
||||
header::SEC_WEBSOCKET_KEY,
|
||||
header::HeaderValue::from_static("13"),
|
||||
);
|
||||
let req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11,
|
||||
headers,
|
||||
None,
|
||||
);
|
||||
let req = TestRequest::default()
|
||||
.header(
|
||||
header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"),
|
||||
).header(
|
||||
header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"),
|
||||
).header(
|
||||
header::SEC_WEBSOCKET_VERSION,
|
||||
header::HeaderValue::from_static("13"),
|
||||
).header(
|
||||
header::SEC_WEBSOCKET_KEY,
|
||||
header::HeaderValue::from_static("13"),
|
||||
).finish();
|
||||
assert_eq!(
|
||||
StatusCode::SWITCHING_PROTOCOLS,
|
||||
handshake(&req).unwrap().finish().status()
|
||||
|
||||
@@ -180,8 +180,11 @@ impl From<u16> for CloseCode {
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
/// Reason for closing the connection
|
||||
pub struct CloseReason {
|
||||
/// Exit code
|
||||
pub code: CloseCode,
|
||||
/// Optional description of the exit code
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFPjCCAyYCCQDvLYiYD+jqeTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV
|
||||
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww
|
||||
CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xODAxMjUx
|
||||
NzQ2MDFaFw0xOTAxMjUxNzQ2MDFaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD
|
||||
QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY
|
||||
MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
|
||||
MIICCgKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEPn8k1
|
||||
sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+MIK5U
|
||||
NLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM54jXy
|
||||
voLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZWLWr
|
||||
odGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAkoqND
|
||||
xdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNliJDmA
|
||||
CRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6/stI
|
||||
yFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuDYX2U
|
||||
UuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nPwPTO
|
||||
vRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA69un
|
||||
CEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEAATAN
|
||||
BgkqhkiG9w0BAQsFAAOCAgEApavsgsn7SpPHfhDSN5iZs1ILZQRewJg0Bty0xPfk
|
||||
3tynSW6bNH3nSaKbpsdmxxomthNSQgD2heOq1By9YzeOoNR+7Pk3s4FkASnf3ToI
|
||||
JNTUasBFFfaCG96s4Yvs8KiWS/k84yaWuU8c3Wb1jXs5Rv1qE1Uvuwat1DSGXSoD
|
||||
JNluuIkCsC4kWkyq5pWCGQrabWPRTWsHwC3PTcwSRBaFgYLJaR72SloHB1ot02zL
|
||||
d2age9dmFRFLLCBzP+D7RojBvL37qS/HR+rQ4SoQwiVc/JzaeqSe7ZbvEH9sZYEu
|
||||
ALowJzgbwro7oZflwTWunSeSGDSltkqKjvWvZI61pwfHKDahUTmZ5h2y67FuGEaC
|
||||
CIOUI8dSVSPKITxaq3JL4ze2e9/0Lt7hj19YK2uUmtMAW5Tirz4Yx5lyGH9U8Wur
|
||||
y/X8VPxTc4A9TMlJgkyz0hqvhbPOT/zSWB10zXh0glKAsSBryAOEDxV1UygmSir7
|
||||
YV8Qaq+oyKUTMc1MFq5vZ07M51EPaietn85t8V2Y+k/8XYltRp32NxsypxAJuyxh
|
||||
g/ko6RVTrWa1sMvz/F9LFqAdKiK5eM96lh9IU4xiLg4ob8aS/GRAA8oIFkZFhLrt
|
||||
tOwjIUPmEPyHWFi8dLpNuQKYalLYhuwZftG/9xV+wqhKGZO9iPrpHSYBRTap8w2y
|
||||
1QU=
|
||||
MIIFXTCCA0WgAwIBAgIJAJ3tqfd0MLLNMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV
|
||||
BAYTAlVTMQswCQYDVQQIDAJDRjELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBh
|
||||
bnkxDDAKBgNVBAsMA09yZzEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMB4XDTE4
|
||||
MDcyOTE4MDgzNFoXDTE5MDcyOTE4MDgzNFowYTELMAkGA1UEBhMCVVMxCzAJBgNV
|
||||
BAgMAkNGMQswCQYDVQQHDAJTRjEQMA4GA1UECgwHQ29tcGFueTEMMAoGA1UECwwD
|
||||
T3JnMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUA
|
||||
A4ICDwAwggIKAoICAQDZbMgDYilVH1Nv0QWEhOXG6ETmtjZrdLqrNg3NBWBIWCDF
|
||||
cQ+fyTWxARx6vkF8A/3zpJyTcfQW8HgG38jw/A61QKaHBxzwq0HlNwY9Hh+Neeuk
|
||||
L4wgrlQ0uTC7IEMrOJjNN0GPyRQVfVbGa8QcSCpOg85l8GCxLvVwkBH/M5atoMtJ
|
||||
EzniNfK+gtk3hOL2tBqBCu9NDjhXPnJwNDLtTG1tQaHUJW/r281Wvv9I46H83DkU
|
||||
05lYtauh0bKh5znCH2KpFmBGqJNRzou3tXZFZzZfaCPBJPZR8j5TjoinehpDtkPh
|
||||
4CSio0PF2eIFkDKRUbdz/327HgEARJMXx+w1yHpS2JwHFgy5O76i68/Smx8j3DDA
|
||||
2WIkOYAJFRMH0CBHKdsvUDOGpCgN+xv3whl+N806nCfC4vCkwA+FuB3ko11logng
|
||||
dvr+y0jIUSU4THF3dMDEXYayF3+WrUlw0cBnUNJdXky85ZP81aBfBsjNSBDx4iL4
|
||||
e4NhfZRS5oHpHy1t3nYfuttS/oet+Ke5KUpaqNJguSIoeTBSmgzDzL1TJxFLOzUT
|
||||
2c/A9M69FdvSY0JB4EJX0W9K01Vd0JRNPwsY+/zvFIPama3suKOUTqYcsbwxx9xa
|
||||
TMDr26cIQcgUAUOKZO43sQGWNzXX3FYVNwczKhkB8UX6hOrBJsEYiau4LGdokQID
|
||||
AQABoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIB
|
||||
AIX+Qb4QRBxHl5X2UjRyLfWVkimtGlwI8P+eJZL3DrHBH/TpqAaCvTf0EbRC32nm
|
||||
ASDMwIghaMvyrW40QN6V/CWRRi25cXUfsIZr1iHAHK0eZJV8SWooYtt4iNrcUs3g
|
||||
4OTvDxhNmDyNwV9AXhJsBKf80dCW6/84jItqVAj20/OO4Rkd2tEeI8NomiYBc6a1
|
||||
hgwvv02myYF5hG/xZ9YSqeroBCZHwGYoJJnSpMPqJsxbCVnx2/U9FzGwcRmNHFCe
|
||||
0g7EJZd3//8Plza6nkTBjJ/V7JnLqMU+ltx4mAgZO8rfzIr84qZdt0YN33VJQhYq
|
||||
seuMySxrsuaAoxAmm8IoK9cW4IPzx1JveBQiroNlq5YJGf2UW7BTc3gz6c2tINZi
|
||||
7ailBVdhlMnDXAf3/9xiiVlRAHOxgZh/7sRrKU7kDEHM4fGoc0YyZBTQKndPYMwO
|
||||
3Bd82rlQ4sd46XYutTrB+mBYClVrJs+OzbNedTsR61DVNKKsRG4mNPyKSAIgOfM5
|
||||
XmSvCMPN5JK9U0DsNIV2/SnVsmcklQczT35FLTxl9ntx8ys7ZYK+SppD7XuLfWMq
|
||||
GT9YMWhlpw0aRDg/aayeeOcnsNBhzAFMcOpQj1t6Fgv4+zbS9BM2bT0hbX86xjkr
|
||||
E6wWgkuCslMgQlEJ+TM5RhYrI5/rVZQhvmgcob/9gPZv
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
BIN
tests/identity.pfx
Normal file
BIN
tests/identity.pfx
Normal file
Binary file not shown.
@@ -5,8 +5,11 @@ extern crate bytes;
|
||||
extern crate flate2;
|
||||
extern crate futures;
|
||||
extern crate rand;
|
||||
#[cfg(all(unix, feature = "uds"))]
|
||||
extern crate tokio_uds;
|
||||
|
||||
use std::io::Read;
|
||||
use std::io::{Read, Write};
|
||||
use std::{net, thread};
|
||||
|
||||
use bytes::Bytes;
|
||||
use flate2::read::GzDecoder;
|
||||
@@ -64,10 +67,20 @@ fn test_simple() {
|
||||
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connection_close() {
|
||||
let mut srv =
|
||||
test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
|
||||
|
||||
let request = srv.get().header("Connection", "close").finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_query_parameter() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.handler(|req: HttpRequest| match req.query().get("qp") {
|
||||
app.handler(|req: &HttpRequest| match req.query().get("qp") {
|
||||
Some(_) => HttpResponse::Ok().finish(),
|
||||
None => HttpResponse::BadRequest().finish(),
|
||||
})
|
||||
@@ -110,14 +123,13 @@ fn test_no_decompress() {
|
||||
#[test]
|
||||
fn test_client_gzip_encoding() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.handler(|req: HttpRequest| {
|
||||
app.handler(|req: &HttpRequest| {
|
||||
req.body()
|
||||
.and_then(|bytes: Bytes| {
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_encoding(http::ContentEncoding::Deflate)
|
||||
.body(bytes))
|
||||
})
|
||||
.responder()
|
||||
}).responder()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -140,14 +152,13 @@ fn test_client_gzip_encoding_large() {
|
||||
let data = STR.repeat(10);
|
||||
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.handler(|req: HttpRequest| {
|
||||
app.handler(|req: &HttpRequest| {
|
||||
req.body()
|
||||
.and_then(|bytes: Bytes| {
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_encoding(http::ContentEncoding::Deflate)
|
||||
.body(bytes))
|
||||
})
|
||||
.responder()
|
||||
}).responder()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -168,19 +179,18 @@ fn test_client_gzip_encoding_large() {
|
||||
#[test]
|
||||
fn test_client_gzip_encoding_large_random() {
|
||||
let data = rand::thread_rng()
|
||||
.gen_ascii_chars()
|
||||
.sample_iter(&rand::distributions::Alphanumeric)
|
||||
.take(100_000)
|
||||
.collect::<String>();
|
||||
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.handler(|req: HttpRequest| {
|
||||
app.handler(|req: &HttpRequest| {
|
||||
req.body()
|
||||
.and_then(|bytes: Bytes| {
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_encoding(http::ContentEncoding::Deflate)
|
||||
.body(bytes))
|
||||
})
|
||||
.responder()
|
||||
}).responder()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -198,18 +208,24 @@ fn test_client_gzip_encoding_large_random() {
|
||||
assert_eq!(bytes, Bytes::from(data));
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "uds"))]
|
||||
#[test]
|
||||
fn test_compatible_with_unix_socket_stream() {
|
||||
let (stream, _) = tokio_uds::UnixStream::pair().unwrap();
|
||||
let _ = client::Connection::from_stream(stream);
|
||||
}
|
||||
|
||||
#[cfg(feature = "brotli")]
|
||||
#[test]
|
||||
fn test_client_brotli_encoding() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.handler(|req: HttpRequest| {
|
||||
app.handler(|req: &HttpRequest| {
|
||||
req.body()
|
||||
.and_then(|bytes: Bytes| {
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_encoding(http::ContentEncoding::Gzip)
|
||||
.body(bytes))
|
||||
})
|
||||
.responder()
|
||||
}).responder()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -231,19 +247,18 @@ fn test_client_brotli_encoding() {
|
||||
#[test]
|
||||
fn test_client_brotli_encoding_large_random() {
|
||||
let data = rand::thread_rng()
|
||||
.gen_ascii_chars()
|
||||
.sample_iter(&rand::distributions::Alphanumeric)
|
||||
.take(70_000)
|
||||
.collect::<String>();
|
||||
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.handler(|req: HttpRequest| {
|
||||
app.handler(|req: &HttpRequest| {
|
||||
req.body()
|
||||
.and_then(move |bytes: Bytes| {
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_encoding(http::ContentEncoding::Gzip)
|
||||
.body(bytes))
|
||||
})
|
||||
.responder()
|
||||
}).responder()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -266,14 +281,13 @@ fn test_client_brotli_encoding_large_random() {
|
||||
#[test]
|
||||
fn test_client_deflate_encoding() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.handler(|req: HttpRequest| {
|
||||
app.handler(|req: &HttpRequest| {
|
||||
req.body()
|
||||
.and_then(|bytes: Bytes| {
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_encoding(http::ContentEncoding::Br)
|
||||
.body(bytes))
|
||||
})
|
||||
.responder()
|
||||
}).responder()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -295,19 +309,18 @@ fn test_client_deflate_encoding() {
|
||||
#[test]
|
||||
fn test_client_deflate_encoding_large_random() {
|
||||
let data = rand::thread_rng()
|
||||
.gen_ascii_chars()
|
||||
.sample_iter(&rand::distributions::Alphanumeric)
|
||||
.take(70_000)
|
||||
.collect::<String>();
|
||||
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.handler(|req: HttpRequest| {
|
||||
app.handler(|req: &HttpRequest| {
|
||||
req.body()
|
||||
.and_then(|bytes: Bytes| {
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_encoding(http::ContentEncoding::Br)
|
||||
.body(bytes))
|
||||
})
|
||||
.responder()
|
||||
}).responder()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -328,7 +341,7 @@ fn test_client_deflate_encoding_large_random() {
|
||||
#[test]
|
||||
fn test_client_streaming_explicit() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.handler(|req: HttpRequest| {
|
||||
app.handler(|req: &HttpRequest| {
|
||||
req.body()
|
||||
.map_err(Error::from)
|
||||
.and_then(|body| {
|
||||
@@ -336,8 +349,7 @@ fn test_client_streaming_explicit() {
|
||||
.chunked()
|
||||
.content_encoding(http::ContentEncoding::Identity)
|
||||
.body(body))
|
||||
})
|
||||
.responder()
|
||||
}).responder()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -393,26 +405,31 @@ fn test_client_cookie_handling() {
|
||||
let mut srv = test::TestServer::new(move |app| {
|
||||
let cookie1 = cookie1b.clone();
|
||||
let cookie2 = cookie2b.clone();
|
||||
app.handler(move |req: HttpRequest| {
|
||||
app.handler(move |req: &HttpRequest| {
|
||||
// Check cookies were sent correctly
|
||||
req.cookie("cookie1").ok_or_else(err)
|
||||
.and_then(|c1| if c1.value() == "value1" {
|
||||
req.cookie("cookie1")
|
||||
.ok_or_else(err)
|
||||
.and_then(|c1| {
|
||||
if c1.value() == "value1" {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(err())
|
||||
})
|
||||
.and_then(|()| req.cookie("cookie2").ok_or_else(err))
|
||||
.and_then(|c2| if c2.value() == "value2" {
|
||||
}
|
||||
}).and_then(|()| req.cookie("cookie2").ok_or_else(err))
|
||||
.and_then(|c2| {
|
||||
if c2.value() == "value2" {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(err())
|
||||
})
|
||||
// Send some cookies back
|
||||
.map(|_| HttpResponse::Ok()
|
||||
.cookie(cookie1.clone())
|
||||
.cookie(cookie2.clone())
|
||||
.finish()
|
||||
)
|
||||
}
|
||||
})
|
||||
// Send some cookies back
|
||||
.map(|_| {
|
||||
HttpResponse::Ok()
|
||||
.cookie(cookie1.clone())
|
||||
.cookie(cookie2.clone())
|
||||
.finish()
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
@@ -425,7 +442,67 @@ fn test_client_cookie_handling() {
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
let c1 = response.cookie("cookie1").expect("Missing cookie1");
|
||||
assert_eq!(c1, &cookie1);
|
||||
assert_eq!(c1, cookie1);
|
||||
let c2 = response.cookie("cookie2").expect("Missing cookie2");
|
||||
assert_eq!(c2, &cookie2);
|
||||
assert_eq!(c2, cookie2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_headers() {
|
||||
let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
let repr = format!("{:?}", request);
|
||||
assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\""));
|
||||
assert!(repr.contains(concat!(
|
||||
"\"user-agent\": \"actix-web/",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
"\""
|
||||
)));
|
||||
|
||||
let request_override = srv
|
||||
.get()
|
||||
.header("User-Agent", "test")
|
||||
.header("Accept-Encoding", "over_test")
|
||||
.finish()
|
||||
.unwrap();
|
||||
let repr_override = format!("{:?}", request_override);
|
||||
assert!(repr_override.contains("\"user-agent\": \"test\""));
|
||||
assert!(repr_override.contains("\"accept-encoding\": \"over_test\""));
|
||||
assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\""));
|
||||
assert!(!repr_override.contains(concat!(
|
||||
"\"user-agent\": \"Actix-web/",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
"\""
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_read_until_eof() {
|
||||
let addr = test::TestServer::unused_addr();
|
||||
|
||||
thread::spawn(move || {
|
||||
let lst = net::TcpListener::bind(addr).unwrap();
|
||||
|
||||
for stream in lst.incoming() {
|
||||
let mut stream = stream.unwrap();
|
||||
let mut b = [0; 1000];
|
||||
let _ = stream.read(&mut b).unwrap();
|
||||
let _ = stream
|
||||
.write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!");
|
||||
}
|
||||
});
|
||||
|
||||
let mut sys = actix::System::new("test");
|
||||
|
||||
// client request
|
||||
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = sys.block_on(req.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
// read response
|
||||
let bytes = sys.block_on(response.body()).unwrap();
|
||||
assert_eq!(bytes, Bytes::from_static(b"welcome!"));
|
||||
}
|
||||
|
||||
81
tests/test_custom_pipeline.rs
Normal file
81
tests/test_custom_pipeline.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
extern crate actix;
|
||||
extern crate actix_net;
|
||||
extern crate actix_web;
|
||||
|
||||
use std::{thread, time};
|
||||
|
||||
use actix::System;
|
||||
use actix_net::server::Server;
|
||||
use actix_net::service::NewServiceExt;
|
||||
use actix_web::server::{HttpService, KeepAlive, ServiceConfig, StreamConfiguration};
|
||||
use actix_web::{client, http, test, App, HttpRequest};
|
||||
|
||||
#[test]
|
||||
fn test_custom_pipeline() {
|
||||
let addr = test::TestServer::unused_addr();
|
||||
|
||||
thread::spawn(move || {
|
||||
Server::new()
|
||||
.bind("test", addr, move || {
|
||||
let app = App::new()
|
||||
.route("/", http::Method::GET, |_: HttpRequest| "OK")
|
||||
.finish();
|
||||
let settings = ServiceConfig::build(app)
|
||||
.keep_alive(KeepAlive::Disabled)
|
||||
.client_timeout(1000)
|
||||
.client_shutdown(1000)
|
||||
.server_hostname("localhost")
|
||||
.server_address(addr)
|
||||
.finish();
|
||||
|
||||
StreamConfiguration::new()
|
||||
.nodelay(true)
|
||||
.tcp_keepalive(Some(time::Duration::from_secs(10)))
|
||||
.and_then(HttpService::new(settings))
|
||||
}).unwrap()
|
||||
.run();
|
||||
});
|
||||
|
||||
let mut sys = System::new("test");
|
||||
{
|
||||
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = sys.block_on(req.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_h1() {
|
||||
use actix_web::server::H1Service;
|
||||
|
||||
let addr = test::TestServer::unused_addr();
|
||||
thread::spawn(move || {
|
||||
Server::new()
|
||||
.bind("test", addr, move || {
|
||||
let app = App::new()
|
||||
.route("/", http::Method::GET, |_: HttpRequest| "OK")
|
||||
.finish();
|
||||
let settings = ServiceConfig::build(app)
|
||||
.keep_alive(KeepAlive::Disabled)
|
||||
.client_timeout(1000)
|
||||
.client_shutdown(1000)
|
||||
.server_hostname("localhost")
|
||||
.server_address(addr)
|
||||
.finish();
|
||||
|
||||
H1Service::new(settings)
|
||||
}).unwrap()
|
||||
.run();
|
||||
});
|
||||
|
||||
let mut sys = System::new("test");
|
||||
{
|
||||
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = sys.block_on(req.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
}
|
||||
@@ -4,21 +4,20 @@ extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate h2;
|
||||
extern crate http;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio_timer;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::io;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use actix::*;
|
||||
use actix_web::*;
|
||||
use bytes::Bytes;
|
||||
use futures::Future;
|
||||
use http::StatusCode;
|
||||
use serde_json::Value;
|
||||
use tokio_core::reactor::Timeout;
|
||||
use tokio_timer::Delay;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PParam {
|
||||
@@ -43,6 +42,28 @@ fn test_path_extractor() {
|
||||
assert_eq!(bytes, Bytes::from_static(b"Welcome test!"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_handler() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with(|p: Path<PParam>| {
|
||||
Delay::new(Instant::now() + Duration::from_millis(10))
|
||||
.and_then(move |_| Ok(format!("Welcome {}!", p.username)))
|
||||
.responder()
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
// read response
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(bytes, Bytes::from_static(b"Welcome test!"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_extractor() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
@@ -70,13 +91,65 @@ fn test_query_extractor() {
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub enum ResponseType {
|
||||
Token,
|
||||
Code,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AuthRequest {
|
||||
id: u64,
|
||||
response_type: ResponseType,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_enum_extractor() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/index.html", |r| {
|
||||
r.with(|p: Query<AuthRequest>| format!("{:?}", p.into_inner()))
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/index.html?id=64&response_type=Code"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
// read response
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(
|
||||
bytes,
|
||||
Bytes::from_static(b"AuthRequest { id: 64, response_type: Code }")
|
||||
);
|
||||
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/index.html?id=64&response_type=Co"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/index.html?response_type=Code"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_extractor_async() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with(|data: Json<Value>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
Delay::new(Instant::now() + Duration::from_millis(10))
|
||||
.and_then(move |_| Ok(format!("{}", data.0)))
|
||||
.responder()
|
||||
})
|
||||
@@ -98,11 +171,69 @@ fn test_async_extractor_async() {
|
||||
assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}"));
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct FormData {
|
||||
username: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_form_extractor() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route()
|
||||
.with(|form: Form<FormData>| format!("{}", form.username))
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv
|
||||
.post()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.form(FormData {
|
||||
username: "test".to_string(),
|
||||
}).unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
// read response
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(bytes, Bytes::from_static(b"test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_form_extractor2() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with_config(
|
||||
|form: Form<FormData>| format!("{}", form.username),
|
||||
|cfg| {
|
||||
cfg.0.error_handler(|err, _| {
|
||||
error::InternalError::from_response(
|
||||
err,
|
||||
HttpResponse::Conflict().finish(),
|
||||
).into()
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv
|
||||
.post()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.header("content-type", "application/x-www-form-urlencoded")
|
||||
.body("918237129hdk:D:D:D:D:D:DjASHDKJhaswkjeq")
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_client_error());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_and_query_extractor() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with2(|p: Path<PParam>, q: Query<PParam>| {
|
||||
r.route().with(|(p, q): (Path<PParam>, Query<PParam>)| {
|
||||
format!("Welcome {} - {}!", p.username, q.username)
|
||||
})
|
||||
});
|
||||
@@ -136,7 +267,7 @@ fn test_path_and_query_extractor2() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route()
|
||||
.with3(|_: HttpRequest, p: Path<PParam>, q: Query<PParam>| {
|
||||
.with(|(_r, p, q): (HttpRequest, Path<PParam>, Query<PParam>)| {
|
||||
format!("Welcome {} - {}!", p.username, q.username)
|
||||
})
|
||||
});
|
||||
@@ -169,15 +300,14 @@ fn test_path_and_query_extractor2() {
|
||||
fn test_path_and_query_extractor2_async() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route()
|
||||
.with3(|p: Path<PParam>, _: Query<PParam>, data: Json<Value>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
r.route().with(
|
||||
|(p, _q, data): (Path<PParam>, Query<PParam>, Json<Value>)| {
|
||||
Delay::new(Instant::now() + Duration::from_millis(10))
|
||||
.and_then(move |_| {
|
||||
Ok(format!("Welcome {} - {}!", p.username, data.0))
|
||||
})
|
||||
.responder()
|
||||
})
|
||||
}).responder()
|
||||
},
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -200,13 +330,11 @@ fn test_path_and_query_extractor2_async() {
|
||||
fn test_path_and_query_extractor3_async() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with2(|p: Path<PParam>, data: Json<Value>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
r.route().with(|(p, data): (Path<PParam>, Json<Value>)| {
|
||||
Delay::new(Instant::now() + Duration::from_millis(10))
|
||||
.and_then(move |_| {
|
||||
Ok(format!("Welcome {} - {}!", p.username, data.0))
|
||||
})
|
||||
.responder()
|
||||
}).responder()
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -226,13 +354,11 @@ fn test_path_and_query_extractor3_async() {
|
||||
fn test_path_and_query_extractor4_async() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with2(|data: Json<Value>, p: Path<PParam>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
r.route().with(|(data, p): (Json<Value>, Path<PParam>)| {
|
||||
Delay::new(Instant::now() + Duration::from_millis(10))
|
||||
.and_then(move |_| {
|
||||
Ok(format!("Welcome {} - {}!", p.username, data.0))
|
||||
})
|
||||
.responder()
|
||||
}).responder()
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -252,15 +378,14 @@ fn test_path_and_query_extractor4_async() {
|
||||
fn test_path_and_query_extractor2_async2() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route()
|
||||
.with3(|p: Path<PParam>, data: Json<Value>, _: Query<PParam>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
r.route().with(
|
||||
|(p, data, _q): (Path<PParam>, Json<Value>, Query<PParam>)| {
|
||||
Delay::new(Instant::now() + Duration::from_millis(10))
|
||||
.and_then(move |_| {
|
||||
Ok(format!("Welcome {} - {}!", p.username, data.0))
|
||||
})
|
||||
.responder()
|
||||
})
|
||||
}).responder()
|
||||
},
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -293,13 +418,11 @@ fn test_path_and_query_extractor2_async3() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route()
|
||||
.with3(|data: Json<Value>, p: Path<PParam>, _: Query<PParam>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.with(|data: Json<Value>, p: Path<PParam>, _: Query<PParam>| {
|
||||
Delay::new(Instant::now() + Duration::from_millis(10))
|
||||
.and_then(move |_| {
|
||||
Ok(format!("Welcome {} - {}!", p.username, data.0))
|
||||
})
|
||||
.responder()
|
||||
}).responder()
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -334,12 +457,10 @@ fn test_path_and_query_extractor2_async4() {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route()
|
||||
.with(|data: (Json<Value>, Path<PParam>, Query<PParam>)| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
Delay::new(Instant::now() + Duration::from_millis(10))
|
||||
.and_then(move |_| {
|
||||
Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0))
|
||||
})
|
||||
.responder()
|
||||
}).responder()
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -443,8 +564,8 @@ fn test_nested_scope_and_path_extractor() {
|
||||
fn test_impl_trait(
|
||||
data: (Json<Value>, Path<PParam>, Query<PParam>),
|
||||
) -> impl Future<Item = String, Error = io::Error> {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
Delay::new(Instant::now() + Duration::from_millis(10))
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout"))
|
||||
.and_then(move |_| Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)))
|
||||
}
|
||||
|
||||
@@ -452,8 +573,8 @@ fn test_impl_trait(
|
||||
fn test_impl_trait_err(
|
||||
_data: (Json<Value>, Path<PParam>, Query<PParam>),
|
||||
) -> impl Future<Item = String, Error = io::Error> {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
Delay::new(Instant::now() + Duration::from_millis(10))
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout"))
|
||||
.and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other")))
|
||||
}
|
||||
|
||||
@@ -551,6 +672,6 @@ fn test_unsafe_path_route() {
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(
|
||||
bytes,
|
||||
Bytes::from_static(b"success: http:%2F%2Fexample.com")
|
||||
Bytes::from_static(b"success: http%3A%2F%2Fexample.com")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
extern crate actix;
|
||||
extern crate actix_web;
|
||||
extern crate futures;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio_timer;
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use actix::*;
|
||||
use actix_web::error::{Error, ErrorInternalServerError};
|
||||
use actix_web::*;
|
||||
use futures::{future, Future};
|
||||
use tokio_core::reactor::Timeout;
|
||||
use tokio_timer::Delay;
|
||||
|
||||
struct MiddlewareTest {
|
||||
start: Arc<AtomicUsize>,
|
||||
@@ -21,21 +20,21 @@ struct MiddlewareTest {
|
||||
}
|
||||
|
||||
impl<S> middleware::Middleware<S> for MiddlewareTest {
|
||||
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
|
||||
fn start(&self, _: &HttpRequest<S>) -> Result<middleware::Started> {
|
||||
self.start
|
||||
.store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
||||
Ok(middleware::Started::Done)
|
||||
}
|
||||
|
||||
fn response(
|
||||
&self, _: &mut HttpRequest<S>, resp: HttpResponse,
|
||||
&self, _: &HttpRequest<S>, resp: HttpResponse,
|
||||
) -> Result<middleware::Response> {
|
||||
self.response
|
||||
.store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
||||
Ok(middleware::Response::Done(resp))
|
||||
}
|
||||
|
||||
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
|
||||
fn finish(&self, _: &HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
|
||||
self.finish
|
||||
.store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
||||
middleware::Finished::Done
|
||||
@@ -85,11 +84,10 @@ fn test_middleware_multiple() {
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
}).middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.handler(|_| HttpResponse::Ok())
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
}).handler(|_| HttpResponse::Ok())
|
||||
});
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
@@ -144,11 +142,10 @@ fn test_resource_middleware_multiple() {
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
}).middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.handler(|_| HttpResponse::Ok())
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
}).handler(|_| HttpResponse::Ok())
|
||||
});
|
||||
|
||||
let request = srv.get().finish().unwrap();
|
||||
@@ -177,8 +174,7 @@ fn test_scope_middleware() {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
})
|
||||
});
|
||||
|
||||
@@ -208,13 +204,11 @@ fn test_scope_middleware_multiple() {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.middleware(MiddlewareTest {
|
||||
}).middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
})
|
||||
});
|
||||
|
||||
@@ -243,11 +237,9 @@ fn test_middleware_async_handler() {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/", |r| {
|
||||
}).resource("/", |r| {
|
||||
r.route().a(|_| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
Delay::new(Instant::now() + Duration::from_millis(10))
|
||||
.and_then(|_| Ok(HttpResponse::Ok()))
|
||||
})
|
||||
})
|
||||
@@ -282,8 +274,7 @@ fn test_resource_middleware_async_handler() {
|
||||
App::new().resource("/test", |r| {
|
||||
r.middleware(mw);
|
||||
r.route().a(|_| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
Delay::new(Instant::now() + Duration::from_millis(10))
|
||||
.and_then(|_| Ok(HttpResponse::Ok()))
|
||||
})
|
||||
})
|
||||
@@ -315,11 +306,9 @@ fn test_scope_middleware_async_handler() {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/test", |r| {
|
||||
}).resource("/test", |r| {
|
||||
r.route().a(|_| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
Delay::new(Instant::now() + Duration::from_millis(10))
|
||||
.and_then(|_| Ok(HttpResponse::Ok()))
|
||||
})
|
||||
})
|
||||
@@ -335,7 +324,7 @@ fn test_scope_middleware_async_handler() {
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
fn index_test_middleware_async_error(_: HttpRequest) -> FutureResponse<HttpResponse> {
|
||||
fn index_test_middleware_async_error(_: &HttpRequest) -> FutureResponse<HttpResponse> {
|
||||
future::result(Err(error::ErrorBadRequest("TEST"))).responder()
|
||||
}
|
||||
|
||||
@@ -383,8 +372,7 @@ fn test_scope_middleware_async_error() {
|
||||
start: Arc::clone(&act_req),
|
||||
response: Arc::clone(&act_resp),
|
||||
finish: Arc::clone(&act_fin),
|
||||
})
|
||||
.resource("/test", |r| r.f(index_test_middleware_async_error))
|
||||
}).resource("/test", |r| r.f(index_test_middleware_async_error))
|
||||
})
|
||||
});
|
||||
|
||||
@@ -416,7 +404,7 @@ fn test_resource_middleware_async_error() {
|
||||
|
||||
App::new().resource("/test", move |r| {
|
||||
r.middleware(mw);
|
||||
r.h(index_test_middleware_async_error);
|
||||
r.f(index_test_middleware_async_error);
|
||||
})
|
||||
});
|
||||
|
||||
@@ -436,8 +424,8 @@ struct MiddlewareAsyncTest {
|
||||
}
|
||||
|
||||
impl<S> middleware::Middleware<S> for MiddlewareAsyncTest {
|
||||
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
|
||||
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
|
||||
fn start(&self, _: &HttpRequest<S>) -> Result<middleware::Started> {
|
||||
let to = Delay::new(Instant::now() + Duration::from_millis(10));
|
||||
|
||||
let start = Arc::clone(&self.start);
|
||||
Ok(middleware::Started::Future(Box::new(
|
||||
@@ -449,9 +437,9 @@ impl<S> middleware::Middleware<S> for MiddlewareAsyncTest {
|
||||
}
|
||||
|
||||
fn response(
|
||||
&self, _: &mut HttpRequest<S>, resp: HttpResponse,
|
||||
&self, _: &HttpRequest<S>, resp: HttpResponse,
|
||||
) -> Result<middleware::Response> {
|
||||
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
|
||||
let to = Delay::new(Instant::now() + Duration::from_millis(10));
|
||||
|
||||
let response = Arc::clone(&self.response);
|
||||
Ok(middleware::Response::Future(Box::new(
|
||||
@@ -462,8 +450,8 @@ impl<S> middleware::Middleware<S> for MiddlewareAsyncTest {
|
||||
)))
|
||||
}
|
||||
|
||||
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
|
||||
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
|
||||
fn finish(&self, _: &HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
|
||||
let to = Delay::new(Instant::now() + Duration::from_millis(10));
|
||||
|
||||
let finish = Arc::clone(&self.finish);
|
||||
middleware::Finished::Future(Box::new(to.from_err().and_then(move |_| {
|
||||
@@ -518,13 +506,11 @@ fn test_async_middleware_multiple() {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.middleware(MiddlewareAsyncTest {
|
||||
}).middleware(MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
||||
@@ -554,13 +540,11 @@ fn test_async_sync_middleware_multiple() {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.middleware(MiddlewareTest {
|
||||
}).middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
||||
@@ -591,8 +575,7 @@ fn test_async_scope_middleware() {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
})
|
||||
});
|
||||
|
||||
@@ -624,13 +607,11 @@ fn test_async_scope_middleware_multiple() {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.middleware(MiddlewareAsyncTest {
|
||||
}).middleware(MiddlewareAsyncTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
})
|
||||
});
|
||||
|
||||
@@ -662,13 +643,11 @@ fn test_async_async_scope_middleware_multiple() {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.middleware(MiddlewareTest {
|
||||
}).middleware(MiddlewareTest {
|
||||
start: Arc::clone(&act_num1),
|
||||
response: Arc::clone(&act_num2),
|
||||
finish: Arc::clone(&act_num3),
|
||||
})
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
})
|
||||
});
|
||||
|
||||
@@ -701,7 +680,7 @@ fn test_async_resource_middleware() {
|
||||
};
|
||||
App::new().resource("/test", move |r| {
|
||||
r.middleware(mw);
|
||||
r.h(|_| HttpResponse::Ok());
|
||||
r.f(|_| HttpResponse::Ok());
|
||||
})
|
||||
});
|
||||
|
||||
@@ -740,7 +719,7 @@ fn test_async_resource_middleware_multiple() {
|
||||
App::new().resource("/test", move |r| {
|
||||
r.middleware(mw1);
|
||||
r.middleware(mw2);
|
||||
r.h(|_| HttpResponse::Ok());
|
||||
r.f(|_| HttpResponse::Ok());
|
||||
})
|
||||
});
|
||||
|
||||
@@ -779,7 +758,7 @@ fn test_async_sync_resource_middleware_multiple() {
|
||||
App::new().resource("/test", move |r| {
|
||||
r.middleware(mw1);
|
||||
r.middleware(mw2);
|
||||
r.h(|_| HttpResponse::Ok());
|
||||
r.f(|_| HttpResponse::Ok());
|
||||
})
|
||||
});
|
||||
|
||||
@@ -797,7 +776,7 @@ fn test_async_sync_resource_middleware_multiple() {
|
||||
struct MiddlewareWithErr;
|
||||
|
||||
impl<S> middleware::Middleware<S> for MiddlewareWithErr {
|
||||
fn start(&self, _req: &mut HttpRequest<S>) -> Result<middleware::Started, Error> {
|
||||
fn start(&self, _: &HttpRequest<S>) -> Result<middleware::Started, Error> {
|
||||
Err(ErrorInternalServerError("middleware error"))
|
||||
}
|
||||
}
|
||||
@@ -805,7 +784,7 @@ impl<S> middleware::Middleware<S> for MiddlewareWithErr {
|
||||
struct MiddlewareAsyncWithErr;
|
||||
|
||||
impl<S> middleware::Middleware<S> for MiddlewareAsyncWithErr {
|
||||
fn start(&self, _req: &mut HttpRequest<S>) -> Result<middleware::Started, Error> {
|
||||
fn start(&self, _: &HttpRequest<S>) -> Result<middleware::Started, Error> {
|
||||
Ok(middleware::Started::Future(Box::new(future::err(
|
||||
ErrorInternalServerError("middleware error"),
|
||||
))))
|
||||
@@ -831,11 +810,11 @@ fn test_middleware_chain_with_error() {
|
||||
App::new()
|
||||
.middleware(mw1)
|
||||
.middleware(MiddlewareWithErr)
|
||||
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
srv.execute(request.send()).unwrap();
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
@@ -861,11 +840,11 @@ fn test_middleware_async_chain_with_error() {
|
||||
App::new()
|
||||
.middleware(mw1)
|
||||
.middleware(MiddlewareAsyncWithErr)
|
||||
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
srv.execute(request.send()).unwrap();
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
@@ -892,12 +871,12 @@ fn test_scope_middleware_chain_with_error() {
|
||||
scope
|
||||
.middleware(mw1)
|
||||
.middleware(MiddlewareWithErr)
|
||||
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
srv.execute(request.send()).unwrap();
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
@@ -924,12 +903,12 @@ fn test_scope_middleware_async_chain_with_error() {
|
||||
scope
|
||||
.middleware(mw1)
|
||||
.middleware(MiddlewareAsyncWithErr)
|
||||
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
|
||||
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
srv.execute(request.send()).unwrap();
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
@@ -955,12 +934,12 @@ fn test_resource_middleware_chain_with_error() {
|
||||
App::new().resource("/test", move |r| {
|
||||
r.middleware(mw1);
|
||||
r.middleware(MiddlewareWithErr);
|
||||
r.h(|_| HttpResponse::Ok());
|
||||
r.f(|_| HttpResponse::Ok());
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
srv.execute(request.send()).unwrap();
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
@@ -986,14 +965,91 @@ fn test_resource_middleware_async_chain_with_error() {
|
||||
App::new().resource("/test", move |r| {
|
||||
r.middleware(mw1);
|
||||
r.middleware(MiddlewareAsyncWithErr);
|
||||
r.h(|_| HttpResponse::Ok());
|
||||
r.f(|_| HttpResponse::Ok());
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
srv.execute(request.send()).unwrap();
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[cfg(feature = "session")]
|
||||
#[test]
|
||||
fn test_session_storage_middleware() {
|
||||
use actix_web::middleware::session::{
|
||||
CookieSessionBackend, RequestSession, SessionStorage,
|
||||
};
|
||||
|
||||
const SIMPLE_NAME: &'static str = "simple";
|
||||
const SIMPLE_PAYLOAD: &'static str = "kantan";
|
||||
const COMPLEX_NAME: &'static str = "test";
|
||||
const COMPLEX_PAYLOAD: &'static str = "url=https://test.com&generate_204";
|
||||
//TODO: investigate how to handle below input
|
||||
//const COMPLEX_PAYLOAD: &'static str = "FJc%26continue_url%3Dhttp%253A%252F%252Fconnectivitycheck.gstatic.com%252Fgenerate_204";
|
||||
|
||||
let mut srv = test::TestServer::with_factory(move || {
|
||||
App::new()
|
||||
.middleware(SessionStorage::new(
|
||||
CookieSessionBackend::signed(&[0; 32]).secure(false),
|
||||
)).resource("/index", move |r| {
|
||||
r.f(|req| {
|
||||
let res = req.session().set(COMPLEX_NAME, COMPLEX_PAYLOAD);
|
||||
assert!(res.is_ok());
|
||||
let value = req.session().get::<String>(COMPLEX_NAME);
|
||||
assert!(value.is_ok());
|
||||
let value = value.unwrap();
|
||||
assert!(value.is_some());
|
||||
assert_eq!(value.unwrap(), COMPLEX_PAYLOAD);
|
||||
|
||||
let res = req.session().set(SIMPLE_NAME, SIMPLE_PAYLOAD);
|
||||
assert!(res.is_ok());
|
||||
let value = req.session().get::<String>(SIMPLE_NAME);
|
||||
assert!(value.is_ok());
|
||||
let value = value.unwrap();
|
||||
assert!(value.is_some());
|
||||
assert_eq!(value.unwrap(), SIMPLE_PAYLOAD);
|
||||
|
||||
HttpResponse::Ok()
|
||||
})
|
||||
}).resource("/expect_cookie", move |r| {
|
||||
r.f(|req| {
|
||||
let _cookies = req.cookies().expect("To get cookies");
|
||||
|
||||
let value = req.session().get::<String>(SIMPLE_NAME);
|
||||
assert!(value.is_ok());
|
||||
let value = value.unwrap();
|
||||
assert!(value.is_some());
|
||||
assert_eq!(value.unwrap(), SIMPLE_PAYLOAD);
|
||||
|
||||
let value = req.session().get::<String>(COMPLEX_NAME);
|
||||
assert!(value.is_ok());
|
||||
let value = value.unwrap();
|
||||
assert!(value.is_some());
|
||||
assert_eq!(value.unwrap(), COMPLEX_PAYLOAD);
|
||||
|
||||
HttpResponse::Ok()
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get().uri(srv.url("/index")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
|
||||
assert!(response.headers().contains_key("set-cookie"));
|
||||
let set_cookie = response.headers().get("set-cookie");
|
||||
assert!(set_cookie.is_some());
|
||||
let set_cookie = set_cookie.unwrap().to_str().expect("Convert to str");
|
||||
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/expect_cookie"))
|
||||
.header("cookie", set_cookie.split(';').next().unwrap())
|
||||
.finish()
|
||||
.unwrap();
|
||||
|
||||
srv.execute(request.send()).unwrap();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user