1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-03 09:36:36 +02:00

Compare commits

...

2652 Commits

Author SHA1 Message Date
cb379c0e0c prepare actix-files release 0.6.0 2022-02-25 20:36:16 +00:00
d4a5d450de prepare actix-web release 4.0.1 2022-02-25 20:31:46 +00:00
542200cbc2 update readme 2022-02-25 19:11:46 +00:00
d0c08dbb7d prepare releases: actix-http 3.0.0 and actix-web 4.0.0 (#2663) 2022-02-25 18:46:35 +00:00
d0b5fb18d2 update migration guide on middleware 2022-02-22 17:40:38 +00:00
12fb3412a5 remove concurrency groups 2022-02-22 12:52:07 +00:00
2665357a0c fix ci groups 2022-02-22 12:47:57 +00:00
693271e571 add CI job concurrency groups 2022-02-22 12:41:08 +00:00
10ef9b0751 remove useless doctest main fns 2022-02-22 12:32:06 +00:00
ce00c88963 fix changelog typo 2022-02-22 11:46:51 +00:00
75e6ffb057 prepare actix-router release 0.5.0 (#2658) 2022-02-22 11:38:25 +00:00
ad38973767 move blocking error to web (#2660) 2022-02-22 08:45:28 +00:00
1c1d6477ef remove legacy ws test 2022-02-22 07:11:16 +00:00
53509a5361 ignore all http1 connection headers in h2 2022-02-22 07:07:12 +00:00
a6f27baff1 flesh out Responder docs 2022-02-22 07:07:12 +00:00
218e34ee17 fix http error debug impl 2022-02-22 07:07:12 +00:00
11bfa84926 rename simple_service to status_service (#2659) 2022-02-22 07:06:36 +00:00
5aa6f713c7 update errorhandlers migration guide 2022-02-22 06:23:01 +00:00
151a15da74 prepare actix-http release 3.0.0-rc.4 2022-02-22 00:21:49 +00:00
1ce58ecb30 fix dispatcher panic on pending flush
fixes thread panic in actix-http-3.0.0-rc.3 #2655
2022-02-22 00:19:48 +00:00
f940653981 Edits to the migration notes (#2654) 2022-02-19 17:05:54 +00:00
b291e29882 fix links 2022-02-18 03:41:10 +00:00
f843776f36 Fix links in README (#2653) 2022-02-18 03:34:12 +00:00
52f7d96358 tweak migration document 2022-02-17 19:13:03 +00:00
51e573b888 prepare actix-test release 0.1.0-beta.13 2022-02-16 03:13:41 +00:00
38e015432b prepare actix-http-test release 3.0.0-beta.13 2022-02-16 03:13:22 +00:00
f5895d5eff prepare actix-web-actors release 4.0.0-beta.12 2022-02-16 03:11:22 +00:00
a0c4bf8d1b prepare awc release 3.0.0-beta.21 2022-02-16 03:10:01 +00:00
594e3a6ef1 prepare actix-http release 3.0.0-rc.3 2022-02-16 03:07:12 +00:00
a808a26d8c bump actix-codec to 0.5 2022-02-15 20:49:10 +00:00
de62e8b025 add nextest to post-merge ci 2022-02-15 14:40:26 +00:00
3486edabcf update migrations guide re tokio v1 2022-02-15 00:54:12 +00:00
4c59a34513 Remove clone implementation for Path (#2639) 2022-02-10 10:29:00 +00:00
1b706b3069 update body type migration guide 2022-02-09 16:12:39 +00:00
a9f445875a update migration guide 2022-02-09 12:31:06 +00:00
e0f02c1d9e update migration guide 2022-02-08 16:53:09 +00:00
092dbba5b9 update migration guide 2022-02-08 15:24:35 +00:00
ff4b2d251f fix impl assertions 2022-02-08 14:32:57 +00:00
98faa61afe fix impl assertions 2022-02-08 13:37:01 +00:00
3f2db9e75c fix doc tests 2022-02-08 12:25:13 +00:00
074d18209d better document relationship with tokio 2022-02-08 10:21:47 +00:00
593fbde46a prepare actix-web release 4.0.0-rc.3 2022-02-08 09:31:48 +00:00
161861997c prepare actix-http release 3.0.0-rc.2 2022-02-08 09:31:20 +00:00
3d621677a5 clippy 2022-02-08 08:00:47 +00:00
0c144054cb make Condition generic over body type (#2635)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-02-08 07:50:05 +00:00
b0fbe0dfd8 fix workers doc 2022-02-08 06:58:33 +00:00
b653bf557f added note to v4 migration guide about worker thread update (#2634) 2022-02-07 19:04:03 +00:00
1d1a65282f RC refinements (#2625) 2022-02-04 20:37:33 +00:00
b0a363a7ae add migration note about fromrequest::configure 2022-02-04 18:48:22 +00:00
b4d3c2394d clean up migration guide 2022-02-04 18:22:38 +00:00
5ca42df89a fix stuck connection when handler doesn't read payload (#2624) 2022-02-03 07:03:39 +00:00
fc5ecdc30b fix changelog 2022-02-02 03:55:43 +00:00
7fe800c3ff prepare actix-web release 4.0.0-rc.2 2022-02-02 03:54:26 +00:00
075df88a07 update 4.0 migration guide 2022-02-02 03:42:07 +00:00
391d8a744a update 4.0 migratio guide 2022-02-02 03:13:11 +00:00
5b6cb681b9 update 4.0 migration guide 2022-02-02 03:09:33 +00:00
0957ec40b4 split migration file 2022-02-02 02:46:37 +00:00
ccf430d74a disable coverage job 2022-02-01 15:24:35 +00:00
c84c1f0f15 simplify macros feature 2022-02-01 14:39:49 +00:00
e9279dfbb8 Fix deprecated notice about client_shutdown (#2621) 2022-02-01 13:44:56 +00:00
a68239adaa bump zstd to 0.10 2022-02-01 13:35:32 +00:00
40a4b1ccd5 add macro feature (#2619)
Co-authored-by: Ibraheem Ahmed <ibrah1440@gmail.com>
2022-02-01 02:35:05 +00:00
7f5a8c0851 fix vmanifest 2022-02-01 00:33:41 +00:00
bcdde1d4ea move actix-web to own dir 2022-02-01 00:30:41 +00:00
30aa64ea32 update dep graphs 2022-02-01 00:23:58 +00:00
5469b02638 prepare actix-web-codegen release 0.5.0-rc.2 2022-02-01 00:12:42 +00:00
a66cd38ec5 prepare actix-web-actors release 4.0.0-beta.11 2022-01-31 22:35:18 +00:00
20609e93fd prepare actix-test release 0.1.0-beta.12 2022-01-31 22:34:59 +00:00
bf282472ab prepare actix-http-test release 3.0.0-beta.12 2022-01-31 22:33:38 +00:00
7f4b44c258 prepare actix-multipart release 0.4.0-beta.13 2022-01-31 22:33:11 +00:00
66243717b3 prepare actix-files release 0.6.0-beta.16 2022-01-31 22:32:52 +00:00
102720d398 prepare awc release 3.0.0-beta.20 2022-01-31 22:32:09 +00:00
c3c7eb8df9 prepare actix-web release 4.0.0-rc.1 2022-01-31 22:23:33 +00:00
21f57caf4a prepare actix-http release 3.0.0-rc.1 2022-01-31 22:22:40 +00:00
47f5faf26e prepare actix-router release 0.5.0-rc.3 2022-01-31 22:21:30 +00:00
9777653dc0 prep readme for rc release 2022-01-31 22:20:53 +00:00
9fde5b30db tweak and document router (#2612)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-01-31 22:12:48 +00:00
fd412a8223 Quoter::requote returns Vec<u8> (#2613) 2022-01-31 21:26:34 +00:00
cd511affd5 add ws and http2 feature flags (#2618) 2022-01-31 21:22:23 +00:00
3200de3f34 fix request head timeout (#2611) 2022-01-31 17:30:34 +00:00
b3e84b5c4b tweak default_service docs 2022-01-28 20:53:51 +00:00
a3416112a5 Improve the documentation for default_service (#2614) 2022-01-28 20:31:54 +00:00
21a08ca796 tweak set_content_encoding docs 2022-01-28 20:27:16 +00:00
a9f497d05f Guard against broken intra-doc links in CI (#2616) 2022-01-28 17:28:16 +00:00
cc9ba162f7 add late request dispatcher test 2022-01-27 17:00:07 +00:00
37799df978 add basic dispatcher test 2022-01-27 06:42:54 +00:00
0d93a8c273 add examples readme 2022-01-27 06:32:28 +00:00
3ae4f0a629 add keep-alive dispatcher tests 2022-01-27 06:29:46 +00:00
14a4f325d3 move dispatcher tests to own file 2022-01-27 06:06:55 +00:00
1bd2076b35 prevent drive traversal in windows 2022-01-25 16:44:05 +00:00
5454699bab propagate response error in all necessary places 2022-01-24 11:56:23 +00:00
d7c5c966d2 remove impl Future for HttpResponse (#2601) 2022-01-24 11:56:01 +00:00
50894e392e document new body map types 2022-01-23 23:26:35 +00:00
008753f07a improve body docs 2022-01-23 03:57:08 +00:00
c92aa31f91 document full percent-decoding of web::Path 2022-01-22 16:17:46 +00:00
c25dd23820 move path impls to derives 2022-01-22 04:02:34 +00:00
acacb90b2e add actix-http 2.2.2 changelog 2022-01-21 21:24:09 +00:00
8459f566a8 fix brotli encoding buffer size 2022-01-21 21:17:07 +00:00
232a14dc8b prepare actix-files release 0.6.0-beta.15 2022-01-21 20:27:29 +00:00
6e9f5fba24 prepare awc release 3.0.0-beta.19 2022-01-21 20:25:46 +00:00
c5d6df0078 prepare actix-web release 4.0.0-beta.21 2022-01-21 20:23:29 +00:00
8865540f3b prepare actix-http release 3.0.0-beta.19 2022-01-21 20:21:49 +00:00
141790b200 use camel case in special headers
fixes #2595
2022-01-21 20:15:43 +00:00
9668a2396f prepare actix-router release 0.5.0-rc.2 2022-01-21 17:21:46 +00:00
cb7347216c add Logger::log_target (#2594) 2022-01-21 17:19:17 +00:00
ae7f71e317 remove ambiguous HttpResponseBuilder::del_cookie (#2591) 2022-01-21 17:18:07 +00:00
bc89f0bfc2 s/example/examples 2022-01-21 16:56:33 +00:00
c959916346 fmt codegen 2022-01-20 01:54:57 +00:00
f227e880d7 refactor route codegen to be cleaner 2022-01-20 01:53:02 +00:00
68ad81f989 remove debug logs 2022-01-20 01:30:33 +00:00
f2e736719a add url_for test for conflicting named resources 2022-01-20 01:30:33 +00:00
81ef12a0fd add warn log to from_parts if given request is cloned
closes #2562
2022-01-19 22:23:53 +00:00
1bc1538118 use tokio::main in client example 2022-01-19 21:36:14 +00:00
1cc3e7b24c deprecate Path::path (#2590) 2022-01-19 20:26:33 +00:00
3dd98c308c document Path::unprocessed panic 2022-01-19 18:33:23 +00:00
cb5d9a7e64 bump deps to stable actix-server v2 2022-01-19 16:58:11 +00:00
5ee555462f add HttpResponse::add_removal_cookie (#2586) 2022-01-19 16:36:11 +00:00
ad159f5219 fix ClientResponse::body doc
fixes #2589
2022-01-19 15:52:16 +00:00
2ffc21dd4f move response extensions out of head (#2585) 2022-01-19 02:09:25 +00:00
7b8a392ef5 allow camel case response headers (#2587) 2022-01-16 03:16:26 +00:00
3c7ccf5521 update http changelog 2022-01-15 15:43:18 +00:00
e7cae5a95b migrate to brotli crate (#2538) 2022-01-15 14:03:16 +00:00
455d5c460d prepare actix-files release 0.6.0-beta.14 2022-01-14 20:01:11 +00:00
8faca783fa prepare actix-web release 4.0.0-beta.20 2022-01-14 20:00:26 +00:00
edbb9b047e prepare actix-router release 0.5.0-rc.1 2022-01-14 19:59:36 +00:00
32742d0715 support opaque app in test helpers (#2584) 2022-01-14 19:45:32 +00:00
d90c1a2331 convert error in Result extractor (#2581)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-01-12 18:59:22 +00:00
2a12b41456 fix support for 12 extractors (#2582)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-01-12 18:31:48 +00:00
6c97d448b7 update tokio-uring to 0.2 (#2583) 2022-01-12 17:53:36 +00:00
c3ce33df05 unify generics across App, Scope and Resource (#2572)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-01-05 15:02:28 +00:00
4431c8da65 fix bench 2022-01-05 14:10:38 +00:00
2d11ab5977 Add ServiceConfig::configure (#1988)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-01-05 12:31:39 +00:00
4ebf16890d add GuardContext::header (#2569) 2022-01-05 11:47:14 +00:00
fe0bbfb3da optimize PathDeserializer (#2570) 2022-01-05 10:48:20 +00:00
2462b6dd5d generalize impl Responder for HttpResponse (#2567)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-01-05 04:42:52 +00:00
49cfabeaf5 simplify Resource trait (#2568)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-01-05 04:34:13 +00:00
0f7292c69a remove readme msrv link 2022-01-05 04:24:40 +00:00
8bbf2b5052 prepare actix-test release 0.1.0-beta.11 2022-01-04 15:37:48 +00:00
8c975bcc1f prepare actix-http-test release 3.0.0-beta.11 2022-01-04 15:37:33 +00:00
742ad56d30 prepare actix-web-actors release 4.0.0-beta.10 2022-01-04 15:37:14 +00:00
bcc8d5c441 prepare actix-multipart release 0.4.0-beta.12 2022-01-04 15:36:56 +00:00
f659098d21 prepare awc release 3.0.0-beta.18 2022-01-04 15:35:21 +00:00
8621ae12f8 prepare actix-web release 4.0.0-beta.19 2022-01-04 15:35:08 +00:00
b338eb8473 prepare actix-http release 3.0.0-beta.18 2022-01-04 15:34:52 +00:00
5abd1c2c2c prepare actix-web-codegen release 0.5.0-rc.1 2022-01-04 15:34:16 +00:00
05336269f9 prepare actix-router release 0.5.0-beta.4 2022-01-04 15:33:44 +00:00
86df295ee2 fully percent decode path segments when capturing (#2566) 2022-01-04 15:19:29 +00:00
85c9b1a263 move quoter 2022-01-04 12:58:40 +00:00
577597a80a rename on-connect example 2022-01-04 12:54:20 +00:00
374dc9bfc9 files: percent-decode url path (#2398)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-01-04 12:54:11 +00:00
93754f307f try path config from Data as well 2022-01-04 04:08:46 +00:00
c7639bc3be document quoter 2022-01-04 03:48:12 +00:00
0bc4ae9158 remove BodyEncoding trait (#2565) 2022-01-03 18:46:04 +00:00
19a46e3925 fix doc test 2022-01-03 15:35:47 +00:00
68cd853aa2 improve docs for Compress 2022-01-03 14:59:01 +00:00
25fe1bbaa5 add double compress layer test 2022-01-03 14:05:08 +00:00
e890307091 Fix AcceptEncoding header (#2501) 2022-01-03 13:17:57 +00:00
b708924590 only run nightly checks on master ci 2021-12-31 08:38:58 +00:00
5dcb250237 fix doc test 2021-12-31 07:53:53 +00:00
b4ff6addfe use match name if possible in data debug log 2021-12-30 07:15:57 +00:00
231a24ef8d improve application data docs 2021-12-30 07:11:35 +00:00
6df4974234 prepare awc release 3.0.0-beta.17 2021-12-29 10:17:28 +00:00
a80e93d6db prepare actix-web release 4.0.0-beta.18 2021-12-29 10:17:11 +00:00
542c92c9a7 tweak changelogs 2021-12-29 10:06:36 +00:00
74738c63a7 Upgrade time dependency (via cookie) (#2555) 2021-12-29 10:03:25 +00:00
a87e01f0d1 bump msrv to 1.54 2021-12-29 08:59:15 +00:00
9779010a5a prepare actix-files release 0.6.0-beta.12 2021-12-29 07:08:10 +00:00
11d50d792b prepare actix-web release 4.0.0-beta.17 2021-12-29 07:07:51 +00:00
798e9911e9 prepare awc release 3.0.0-beta.16 2021-12-29 07:07:46 +00:00
2b2de29800 never return port in realip_remote_addr (#2554) 2021-12-28 14:52:43 +00:00
0f5c876c6b tweak guard docs 2021-12-28 14:50:48 +00:00
96a4dc9dec use modern signatures for awc send_* and header methods (#2553) 2021-12-28 03:22:22 +00:00
4616ca8ee6 rework Guard trait (#2552) 2021-12-28 02:37:13 +00:00
36193b0a50 specify tokio dep to avoid RUSTSEC-2021-0124 warning 2021-12-27 18:54:10 +00:00
76684a786e update server dep to rc2 (#2550) 2021-12-27 18:45:31 +00:00
2308f8afa4 use const header values where possible 2021-12-27 16:15:33 +00:00
554ae7a868 rework Handler trait (#2549) 2021-12-27 00:44:30 +00:00
ac0c4eb684 update actix-tls references to stable 3.0.0 2021-12-26 21:24:03 +00:00
2e493cf791 remove crate level clippy allows 2021-12-25 04:53:51 +00:00
5860fe5381 expose Handler trait 2021-12-25 04:43:59 +00:00
adf9935841 improve scope documentation
closes #2389
2021-12-25 03:44:09 +00:00
34e5c7c799 Improve module docs for error handler middleware (#2543) 2021-12-25 02:35:19 +00:00
01cbfc5724 reduce -http re-exports in awc 2021-12-25 02:34:35 +00:00
3756dfc2ce move client to own module 2021-12-25 02:34:31 +00:00
d2590fd46c ClientRequest::send_body takes impl MessageBody (#2546) 2021-12-25 02:33:37 +00:00
1296e07c48 relax unpin bounds on payload types (#2545) 2021-12-24 17:47:47 +00:00
7b1512d863 allow any body type in Scope (#2523) 2021-12-22 15:48:59 +00:00
cd025f5c0b allow any body type in Resource (#2526) 2021-12-22 15:00:32 +00:00
1769812d0b bump outdated deps 2021-12-22 08:43:38 +00:00
324eba7e0b tighten tokio version range to prevent RUSTSEC-2021-0124 2021-12-22 08:41:44 +00:00
b3ac918d70 update itoa to v1 2021-12-22 08:34:48 +00:00
de20d21703 use dash hyphenation in markdown 2021-12-22 08:21:30 +00:00
212c6926f9 Revert "use dash hyphenation in changelogs"
This reverts commit 1ea619f2a1.
2021-12-22 08:18:44 +00:00
1ea619f2a1 use dash hyphenation in changelogs 2021-12-22 08:17:35 +00:00
40a0162074 add tests to scope and resource for returning from fns 2021-12-22 07:58:37 +00:00
f8488aff1e upstream changelog for v3.3.3 2021-12-22 07:20:53 +00:00
64c2e5e1cd remove crate level clippy lint 2021-12-22 07:16:07 +00:00
17f636a183 split request and response modules (#2530) 2021-12-19 17:05:27 +00:00
2e00776d5e Update FUNDING.yml 2021-12-19 04:18:57 +00:00
7d507a41ee Create FUNDING.yml 2021-12-19 03:58:29 +00:00
fb036264cc only build SslConnectorBuilder once (#2503) 2021-12-18 16:44:30 +00:00
d2b9724010 update bump script to detect prerelease versions 2021-12-18 03:27:32 +00:00
5c53db1e4d remove hidden anybody 2021-12-18 01:48:16 +00:00
84ea9e7e88 http: Replace header::map::GetAll with std::slice::Iter (#2527) 2021-12-18 00:05:12 +00:00
0bd5ccc432 update changelog 2021-12-17 21:39:15 +00:00
9cd8526085 prepare actix-router release 0.5.0-beta.3 2021-12-17 21:24:34 +00:00
73bbe56971 prepare actix-test release 0.1.0-beta.9 2021-12-17 21:00:15 +00:00
8340b63b7b prepare awc release 3.0.0-beta.14 2021-12-17 20:59:59 +00:00
6c2c7b68e2 prepare actix-web release 4.0.0-beta.15 2021-12-17 20:59:01 +00:00
7bf47967cc prepare actix-http release 3.0.0-beta.16 2021-12-17 20:57:51 +00:00
ae47d96fc6 use body::None in encoder body 2021-12-17 20:56:54 +00:00
5842a3279d update messagebody documentation 2021-12-17 19:35:08 +00:00
1d6f5ba6d6 improve codegen on BoxBody poll_next 2021-12-17 19:19:21 +00:00
aa31086af5 improve BoxBody Debug impl 2021-12-17 19:16:42 +00:00
57ea322ce5 simplify MessageBody::complete_body interface (#2522) 2021-12-17 19:09:08 +00:00
2cf27863cb remove direct dep on pin-project in -http (#2524) 2021-12-17 14:13:54 +00:00
5359fa56c2 include source for dispatch body errors 2021-12-17 01:29:41 +00:00
a2467718ac passthrough StreamLog error type 2021-12-17 01:27:27 +00:00
3c0d059d92 MessageBody::boxed (#2520)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-12-17 00:43:40 +00:00
44b7302845 minimize futures-util dep in actix-http 2021-12-16 22:26:45 +00:00
a6d5776481 various fixes to MessageBody::complete_body (#2519) 2021-12-16 22:25:10 +00:00
156cc20ac8 refactor testing utils (#2518) 2021-12-15 01:44:51 +00:00
dd4a372613 allow error handler middleware to return different body type (#2515) 2021-12-14 21:17:50 +00:00
05255c7f7c remove either crate conversions (#2516) 2021-12-14 19:57:18 +00:00
fb091b2b88 split up router pattern and resource_path modules 2021-12-14 18:59:59 +00:00
11ee8ec3ab align remaining header map terminology (#2510) 2021-12-13 16:08:08 +00:00
551a0d973c doc tweaks 2021-12-13 02:58:19 +00:00
cea44be670 add test for returning App from function 2021-12-11 16:18:28 +00:00
b41b346c00 inline trivial body methods 2021-12-11 16:05:08 +00:00
5b0a50249b prepare actix-multipart release 0.4.0-beta.10 2021-12-11 00:35:26 +00:00
60b030ff53 prepare actix-web-actors release 4.0.0-beta.8 2021-12-11 00:34:23 +00:00
fc4e9ff96b prepare actix-web-codegen release 0.5.0-beta.6 2021-12-11 00:33:31 +00:00
6481a5fb73 prepare actix-test release 0.1.0-beta.8 2021-12-11 00:32:26 +00:00
0cd7c17682 prepare actix-http-test release 3.0.0-beta.9 2021-12-11 00:32:00 +00:00
ed2f5b40b9 prepare actix-files release 0.6.0-beta.10 2021-12-11 00:31:41 +00:00
cc37be9700 prepare actix-web release 4.0.0-beta.14 2021-12-11 00:30:12 +00:00
e1cdabe5cb prepare awc release 3.0.0-beta.13 2021-12-11 00:28:38 +00:00
d0f4c809ca prepare actix-http release 3.0.0-beta.15 2021-12-11 00:22:09 +00:00
65dd5dfa7b bump script updates referenced crate versions 2021-12-11 00:21:30 +00:00
f62383a975 unpin h2 2021-12-10 22:13:12 +00:00
f9348d7129 add ServiceResponse::into_parts (#2499) 2021-12-09 14:57:27 +00:00
774ac7fec4 provide optimisation path for single-chunk body types (#2497) 2021-12-09 13:52:35 +00:00
69fa17f66f clean future h2 dispatcher 2021-12-09 11:27:29 +00:00
816d68dee8 pin h2 temporarily 2021-12-09 00:46:28 +00:00
7dc034f0fb Remove extensions from head (#2487) 2021-12-08 22:58:50 +00:00
07f2fe385b standardize crate level lints 2021-12-08 06:09:56 +00:00
406f694095 standardize rustfmt max_width 2021-12-08 06:01:11 +00:00
e49e559f47 fix some docs 2021-12-08 05:43:50 +00:00
d35b7644dc add connection level data container (#2491) 2021-12-07 17:23:34 +00:00
069cf2da07 enable scope middleware with generic res body. (#2492)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-12-07 16:26:28 +00:00
6460e67f84 remove generic body type in App. (#2493) 2021-12-07 15:53:04 +00:00
9587261c20 add fakeshadow's actix-web in actix-http example 2021-12-07 15:31:15 +00:00
606a371ec3 improve Data docs 2021-12-06 17:14:56 +00:00
bed72d9bb7 fix examples 2021-12-05 23:23:36 +00:00
c596f573a6 bump actix-server to rc.1 2021-12-05 21:25:15 +00:00
627c0dc22f workaround rustdoc bug for Error (#2489) 2021-12-05 16:19:08 +00:00
2d053b7036 remove actix_http::http module (#2488) 2021-12-05 14:37:20 +00:00
59be0c65c6 disallow query or fragements in url_for constructions (#2430)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-12-05 04:39:18 +00:00
e1a2d9c606 Quality / QualityItem improvements (#2486) 2021-12-05 03:38:08 +00:00
d89c706cd6 re-instate Range typed header (#2485)
Co-authored-by: RideWindX <ridewindx@gmail.com>
2021-12-05 00:02:25 +00:00
4c9ca7196d Add WsResponseBuilder to build web socket session response (#1920)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-12-04 22:32:44 +00:00
fa7f3e6908 undeprecate App::data_factory (#2484) 2021-12-04 19:41:15 +00:00
c7c02ef99d body ergonomics v3 (#2468) 2021-12-04 19:40:47 +00:00
a2d5c5a058 Use cilent time out for h2 handshake timeout. (#2483) 2021-12-02 18:16:34 +00:00
deece8d519 re-instate accept-encoding typed header (#2482) 2021-12-02 17:04:40 +00:00
2a72bdae09 improve typed header macro (#2481) 2021-12-02 15:25:39 +00:00
075d871e63 wrap LanguageTags type in new AnyOrSome type to support wildcards (#2480) 2021-12-02 13:59:25 +00:00
c4b20df56a convert all remaining IETF RFC links to new format 2021-12-02 03:45:04 +00:00
0df275c478 update all IETF RFC links to new URL format 2021-12-01 19:42:02 +00:00
697238fadc prepare actix-multipart release 0.4.0-beta.9 2021-12-01 00:26:07 +00:00
e045418038 prepare for actix-tls rc.1 (#2474) 2021-11-30 14:12:04 +00:00
a978b417f3 use actix ready future in remaining return types 2021-11-30 13:11:41 +00:00
fa82b698b7 remove pin-project from actix-web. (#2471) 2021-11-30 11:16:53 +00:00
fc4cdf81eb expose header::map module (#2470) 2021-11-29 02:22:47 +00:00
654dc64a09 don't hang after dropping mutipart (#2463) 2021-11-29 02:00:24 +00:00
cf54388534 re-work from request macro. (#2469) 2021-11-29 01:23:27 +00:00
39243095b5 guarantee ordering of header map get_all (#2467) 2021-11-28 19:23:29 +00:00
89c6d62656 clean up multipart and field stream trait impl (#2462) 2021-11-25 00:10:53 +00:00
52bbbd1d73 Mnior cleanup of multipart API. (#2461) 2021-11-24 20:53:11 +00:00
3e6e9779dc fix big5 charset parsing 2021-11-24 20:16:15 +00:00
9bdd334bb4 add test for duplicate dynamic segent name 2021-11-23 15:57:18 +00:00
bcbbc115aa fix awc changelog 2021-11-23 15:12:55 +00:00
ab5eb7c1aa prepare actix-multipart release 0.4.0-beta.8 2021-11-22 18:48:14 +00:00
18b8ef0765 prepare actix-test release 0.1.0-beta.7 2021-11-22 18:47:43 +00:00
b806b4773c prepare actix-http-test release 3.0.0-beta.7 2021-11-22 18:46:58 +00:00
0062d99b6f prepare actix-files release 0.6.0-beta.9 2021-11-22 18:46:19 +00:00
99e6a9c26d prepare awc release 3.0.0-beta.11 2021-11-22 18:41:43 +00:00
5f5bd2184e prepare actix-web release 4.0.0-beta.12 2021-11-22 18:20:55 +00:00
88e074879d prepare actix-http release 3.0.0-beta.13 2021-11-22 18:19:09 +00:00
e7987e7429 awc: support http2 over plain tcp with feature flag (#2439)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-11-22 18:16:56 +00:00
a172f5968d prepare for actix-tls v3 beta 9 (#2456) 2021-11-22 15:37:23 +00:00
a2a42ec152 use anybody in doc test 2021-11-22 01:35:33 +00:00
dd347e0bd0 implement io-uring for actix-files (#2408)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-11-22 01:19:09 +00:00
194a691537 files: 304 Not Modified responses omit Content-Length header (#2453) 2021-11-19 14:04:12 +00:00
56ee97f722 add files path traversal tests 2021-11-18 18:14:34 +00:00
66620a1012 simplify handler.rs (#2450) 2021-11-17 20:11:35 +00:00
e33618ed6d ensure content disposition header in multipart (#2451)
Co-authored-by: Craig Pastro <craig.pastro@gmail.com>
2021-11-17 17:44:50 +00:00
1fe309bcc6 increase ci test timeout 2021-11-17 15:32:42 +00:00
168a7284d3 fix actix_http::Error conversion. (#2449) 2021-11-17 13:13:05 +00:00
68a3acb9c2 bump zstd dep 2021-11-16 23:22:29 +00:00
84c6d25fd3 bump env logger dep 2021-11-16 23:07:08 +00:00
0a135c7dc9 bump actix-codec to 0.4.1 2021-11-16 22:41:24 +00:00
668a33c793 remove internal usage of Body 2021-11-16 22:10:30 +00:00
d8cbb879dd make AnyBody generic on Body type (#2448) 2021-11-16 21:41:35 +00:00
13cf5a9e44 remove chunked encoding header for websockets 2021-11-16 16:55:45 +00:00
4df1cd78b7 simplify AnyBody and BodySize (#2446) 2021-11-16 09:21:10 +00:00
e8a0e16863 run tarpaulin on workspace 2021-11-15 18:11:51 +00:00
a2f59c02f7 bump actix-server to beta 9 (#2442) 2021-11-15 04:03:33 +00:00
2754608f3c fix codegen tests 2021-11-08 02:46:43 +00:00
c020cedb63 Log internal server errors (#2387) 2021-11-07 17:02:23 +00:00
5e554dca35 fix awc clippy warning (#2431) 2021-11-04 15:57:55 +00:00
6ec2d7b909 add keep alive to h2 through ping pong (#2433) 2021-11-04 15:15:23 +00:00
ec6d284a8e improve "data no configured" message (#2429) 2021-10-31 13:19:21 +00:00
be9530eb72 avoid building actix-tls with no-default-features (#2426) 2021-10-26 13:16:48 +01:00
855e260fdb Add html_utf8 content type. (#2423) 2021-10-26 09:24:38 +01:00
d13854505f move actix_http::client module to awc (#2425) 2021-10-26 00:37:40 +01:00
d40b6748bc remove dead dep (#2420) 2021-10-22 00:22:58 +01:00
c79b9a0df3 prepare actix-files release 0.6.0-beta.8 2021-10-20 23:32:46 +01:00
4af414064b prepare actix-multipart release 0.4.0-beta.7 2021-10-20 23:31:46 +01:00
9abe166d52 actix-web beta 10 releases (#2417) 2021-10-20 22:32:05 +01:00
c09ec6af4c split off coverage ci job 2021-10-20 02:27:30 +01:00
37f2bf5625 clippy 2021-10-20 02:06:51 +01:00
4f6f0b0137 chore: Bump rustls to 0.20.0 (#2416)
Co-authored-by: Kirill Mironov <vetrokm@gmail.com>
2021-10-20 02:00:11 +01:00
591abc37c3 add test runtime macro (#2409) 2021-10-19 17:30:32 +01:00
ad22cc4e7f bump msrv to 1.52.1 2021-10-19 01:59:28 +01:00
efdf3ab1c3 clippy 2021-10-19 01:32:58 +01:00
6b3ea4fc61 copy original route macro input with compile errors (#2410) 2021-10-14 18:06:31 +01:00
99985fc4ec web: implement into_inner for Data<T: ?Sized> (#2407) 2021-10-12 18:35:33 +01:00
a6707fb7ee Remove checked_expr (#2401) 2021-10-11 18:28:09 +01:00
a3806cde19 fix changelog 2021-09-12 22:41:08 +01:00
efefa0d0ce web: add option to not require content type header for Json (#2362)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-09-11 17:27:50 +01:00
450ff5fa1d improve extract docs (#2384) 2021-09-11 16:48:47 +01:00
8ae278cb68 Remove FromRequest::Config (#2233)
Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
Co-authored-by: Igor Aleksanov <popzxc@yandex.ru>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-09-11 01:11:16 +01:00
46699e3429 remove time dep from actix-http (#2383) 2021-09-11 00:01:01 +01:00
ba88d3b4bf prepare actix-web beta.9 releases (#2381)
* prepare actix-router release 0.5.0-beta.2

* prepare actix-web-codegen release 0.5.0-beta.4

* prepare actix-http release 3.0.0-beta.10

* prepare awc release 3.0.0-beta.8

* prepare actix-web release 4.0.0-beta.9

* prepare actix-http-test release 3.0.0-beta.6

* prepare actix-test release 0.1.0-beta.4

* prepare actix-files release 0.6.0-beta.7

* prepare actix-multipart release 0.4.0-beta.6

* prepare actix-web-actors release 4.0.0-beta.7

* fix http test version

* re-add patch

* update router repo url

* fix http test readme version
2021-09-09 01:35:41 +01:00
8dd30611fa accept owned strings in TestRequest::param (#2172)
* accept owned strings in TestRequest::param

* bump actix-router to 0.4.0

* update changelog

Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-09-09 00:42:40 +01:00
1383c7d701 speed up ci 2021-09-08 17:42:14 +01:00
d8a0f46f26 refactor web module (#2379) 2021-09-03 18:00:43 +01:00
53ec66caf4 Send headers within the redirect requests. (#2310) 2021-09-01 20:16:41 +01:00
93112644d3 non exhaustive content encoding (#2377) 2021-09-01 09:53:26 +01:00
ddc8c16cb3 Fix quality parse error in Accept-Encoding HTTP header (#2344) 2021-09-01 09:08:29 +01:00
373b3f91df rework ResourceMap internals (#2337) 2021-09-01 04:48:43 +01:00
7d01ece355 ResourceDef: support multiple-patterns as prefix (#2356)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-08-31 14:15:22 +01:00
c50eef6166 "deprecate" calls to NormalizePath::default 2021-08-31 04:19:02 +01:00
dade818eba add middleware composition tests (#2375) 2021-08-31 04:18:54 +01:00
ae35e69382 use rust 1.51 features 2021-08-31 02:52:29 +01:00
5128b1bdfc bump msrv to 1.51 2021-08-30 23:19:03 +01:00
168b2f227d compile time validation of path (#2350)
* compile time validation of path

* added trybuild err message

* Update Cargo.toml

* add changelog entry

* test more cases of path validation

* fmt

Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-08-30 21:50:40 +01:00
4bb32fb19b [fix] Bump actix-http dependency to 3.0.0-beta.9, up from 3.0.0-beta.8 (#2360)
Fixes https://rustsec.org/advisories/RUSTSEC-2021-0081
2021-08-30 20:07:12 +01:00
f9da6e48e0 ResourceDef: define behavior for prefix with trailing slash (#2355)
* ResourceDef: define behavior

* fix tests

* add scope test

* revert firestorm bump

* update changelog

* fmt

Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-08-30 20:05:49 +01:00
ff07816b65 update httparse for uninit header parsing (#2374) 2021-08-29 01:42:22 +01:00
5f412c67db clippy 2021-08-13 18:49:58 +01:00
a0c0bff944 Don't create a slice to potential uninit data on h1 encoder (#2364)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-08-13 18:41:19 +01:00
384164cc14 update graphs 2021-08-12 21:04:26 +01:00
e965d8298f HRS security fixes (#2363) 2021-08-12 20:18:09 +01:00
f6e69919ed update to router 0.5.0 beta (#2339) 2021-08-06 22:42:31 +01:00
293c52c3ef re-export ServiceFactory (#2325) 2021-07-12 16:55:41 +01:00
5a14ffeef2 clippy fixes (#2296) 2021-07-12 16:55:24 +01:00
7ae132cb68 Update MIGRATION.md (#2315)
Minor edit
2021-07-12 02:02:19 +01:00
d8deed0475 fix tests with tokio 1.8.1 (#2317) 2021-07-09 23:57:21 +01:00
2504c2ecb0 Move dev module to separate file, update description (#2293) 2021-06-27 07:44:56 +01:00
604be5495f prepare beta.8 releases (#2292) 2021-06-26 16:33:36 +01:00
262c6bc828 Various refactorings (#2281)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-06-26 15:33:43 +01:00
5eba95b731 simplify ConnectionInfo::new (#2282) 2021-06-26 00:39:06 +01:00
09afd033fc files: file path filtering closure (#2274)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-06-25 14:21:57 +01:00
539697292a fix scope and resource middleware data access (#2288) 2021-06-25 13:19:42 +01:00
5a480d1d78 re-add serde error impls 2021-06-25 12:28:04 +01:00
9a26393375 Remove duplicated step from CI workflow (#2289) 2021-06-25 12:27:22 +01:00
2eacb735a4 Don't leak internal macros (#2290) 2021-06-25 12:25:50 +01:00
767e4efe22 Remove downcast macro from actix-http (#2291) 2021-06-25 10:53:53 +01:00
e559a197cc remove comment 2021-06-24 15:30:11 +01:00
93aa86e30b clippy 2021-06-24 15:11:01 +01:00
2d8d2f5ab0 app data doc improvements 2021-06-24 15:10:51 +01:00
083ee05d50 Route::service (#2262)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-06-23 21:30:06 +01:00
ed0516d724 try to fix doc test failures (#2284) 2021-06-23 20:47:17 +01:00
7535a1ade8 Note that Form cannot require data ordering (#2283) 2021-06-23 16:54:25 +01:00
8846808804 ServiceRequest::parts_mut (#2177) 2021-06-23 00:42:00 +01:00
3b6333e65f Propagate error cause to middlewares (#2280) 2021-06-22 22:22:33 +01:00
b1148fd735 Implement FromRequest for request parts (#2263)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-06-22 17:32:03 +01:00
12f7720309 deprecate App::data and App::data_factory (#2271) 2021-06-22 15:50:58 +01:00
2d8530feb3 chore: bump actix to 0.12.0 in actix-web-actors (#2277)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-06-22 14:00:28 +01:00
7faeffc5ab prepare actix-test release 0.1.0-beta.3 2021-06-20 19:47:42 +01:00
f81d4bdae7 remove unused private hidden methods 2021-06-19 23:40:30 +01:00
6893773280 files: allow show_files_listing() with index_file() (#2228) 2021-06-19 21:00:31 +01:00
73a655544e tweak compress feature docs 2021-06-19 20:23:06 +01:00
baa5a663c4 Select compression algorithm using features flags (#2250)
Add compress-* feature flags in actix-http / actix-web / awc.
This allow enable / disable not wanted compression algorithm.
2021-06-19 20:21:13 +01:00
c260fb1c48 beta.7 releases (#2266) 2021-06-19 11:51:20 +01:00
532f7b9923 refined error model (#2253) 2021-06-17 17:57:58 +01:00
bb0331ae28 fix cargo cache on msrv 2021-06-17 16:49:31 +01:00
8d124713fc files: inline disposition for common web app file types (#2257) 2021-06-16 20:33:22 +01:00
fb2b362b60 Adjust JSON limit to 2MB and report on sizes (#2162)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-06-16 15:52:49 +01:00
75f65fea4f Extends Rustls ALPN protocols instead of replacing them when creating Rustls based services (#2226) 2021-06-10 16:25:21 +01:00
812269d656 clarify docs for BodyEncoding::encoding() (#2258) 2021-06-10 15:38:35 +01:00
e46cda5228 Deduplicate rt::main macro logic (#2255) 2021-06-08 22:44:56 +01:00
2e1d761854 add Seal argument to sealed AsHeaderName methods (#2252) 2021-06-08 12:57:19 +01:00
b1e841f168 Don't normalize URIs with no valid path (#2246) 2021-06-05 17:19:45 +01:00
0bb035cfa7 Add information about Actix discord server (#2247) 2021-06-04 02:54:40 +01:00
3479293416 Add zstd ContentEncoding support (#2244)
Co-authored-by: Igor Aleksanov <popzxc@yandex.ru>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-06-03 21:32:52 +01:00
136dac1352 Additional test coverage and tidyup (middleware::normalize) (#2243) 2021-06-03 03:28:09 +01:00
e5b713b04a files: Fix redirect_to_slash_directory() when used with show_files_listing() (#2225) 2021-05-26 10:42:29 +01:00
3847429d00 Response::from_error take impl Into<Error> (#2214) 2021-05-26 13:41:48 +09:00
bb7d33c9d4 refactor h2 dispatcher to async/await.reduce duplicate code (#2211) 2021-05-25 03:21:20 +01:00
4598a7c0cc Only run UI tests on MSRV (#2232) 2021-05-25 00:09:38 +09:00
b1de196509 Fix clippy warnings (#2217) 2021-05-15 01:13:33 +01:00
2a8c650f2c move internalerror to actix web (#2215) 2021-05-14 16:40:00 +01:00
f277b128b6 cleanup ws test (#2213) 2021-05-13 12:24:32 +01:00
4903950b22 update changelog 2021-05-09 20:15:49 +01:00
f55e8d7a11 remove error field from response 2021-05-09 20:15:48 +01:00
900c9e270e remove responsebody indirection from response (#2201) 2021-05-09 20:12:48 +01:00
a9dc1586a0 remove rogue eprintln 2021-05-07 10:14:25 +01:00
947caa3599 examples use info log level by default 2021-05-06 20:24:18 +01:00
7d1d5c8acd Expose SererBuilder::worker_max_blocking_threads (#2200) 2021-05-06 18:35:04 +01:00
ddaf8c3e43 add associated error type to MessageBody (#2183) 2021-05-05 18:36:02 +01:00
dd1a3e7675 Fix loophole in soundness of __private_get_type_id__ (#2199) 2021-05-05 11:16:12 +01:00
c17662fe39 Reduce the level of the emitted log line from error to debug. (#2196)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-05-03 00:58:14 +01:00
3a0fb3f89e Static either extract future (#2184) 2021-05-01 03:02:56 +01:00
1fcf92e11f Update dependency "language-tags" (#2188) 2021-04-28 01:23:12 +01:00
6a29a50f25 files doc wording 2021-04-22 18:37:45 +01:00
75867bd073 clean up files service docs and rename method
follow on from #2046
2021-04-22 18:31:21 +01:00
f44a0bc159 add support of filtering guards in Files of actix-files (#2046)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-04-22 18:13:13 +01:00
07036b5640 static form extract future (#2181) 2021-04-22 13:54:29 +01:00
a7cd4e85cf use stable codec 0.4.0 2021-04-21 11:14:22 +01:00
6a9c4f1026 update awc docs link, formatting (#2180) 2021-04-20 19:57:27 +01:00
427fe6bd82 improve responseerror trait docs 2021-04-19 23:16:04 +01:00
2aa674c1fd Fix perf drop in HttpResponseBuilder (#2174) 2021-04-19 23:15:57 +01:00
52bb2b5daf hide downcast macros 2021-04-19 03:42:53 +01:00
db97974dc1 make some http re-exports more accessible (#2171) 2021-04-19 03:29:38 +01:00
b9dbc58e20 content disposition methods take impl AsRef<str> 2021-04-19 02:31:11 +01:00
35f8188410 restore cookie methods on ServiceRequest 2021-04-19 02:24:20 +01:00
8ffb1f2011 update files changelog 2021-04-19 02:11:07 +01:00
26e9c80626 Named file service (#2135) 2021-04-18 23:34:51 +01:00
f462aaa7b6 prepare actix-test release 0.1.0-beta.2 2021-04-17 15:53:54 +01:00
5a162932f3 prepare awc release 3.0.0-beta.5 2021-04-17 15:30:31 +01:00
b2d6b6a70c prepare web release 4.0.0-beta.6 2021-04-17 15:28:13 +01:00
f743e885a3 prepare http release 3.0.0-beta.6 2021-04-17 15:24:18 +01:00
5747f84736 bump utils to stable v3 2021-04-17 02:07:33 +01:00
879a4cbcd8 re-export ready boilerplate macros in dev 2021-04-16 23:21:02 +01:00
2449f2555c missed one pipeline_factory 2021-04-16 20:48:37 +01:00
d8f56eee3e bump service to stable v2 2021-04-16 20:28:21 +01:00
8d88a0a9af rename header generator macros 2021-04-16 19:15:10 +01:00
845c02cb86 Add responder impl for Cow<str> (#2164) 2021-04-16 00:54:51 +01:00
64bed506c2 chore: update benchmaks to round 20 (#2163) 2021-04-15 19:11:30 +01:00
ff65f1d006 non exhaustive http errors (#2161) 2021-04-14 06:07:59 +01:00
a9f26286f9 reduce branches in h1 dispatcher poll_keepalive (#2089) 2021-04-14 05:20:45 +01:00
037ac80a32 document messagebody trait items 2021-04-14 03:23:15 +01:00
1bfdfd1f41 implement parts as assoc method 2021-04-14 02:57:28 +01:00
5202bf03c1 add some doc examples to response builder 2021-04-14 02:45:58 +01:00
387c229f28 move response builder code to own file 2021-04-14 02:12:47 +01:00
23e0c9b6e0 remove http-codes builders from actix-http (#2159) 2021-04-14 02:00:14 +01:00
02ced426fd add body to_bytes helper (#2158) 2021-04-13 13:34:22 +01:00
4442535a45 clippy 2021-04-13 12:44:38 +01:00
edd9f14752 remove unpin from body types (#2152) 2021-04-13 11:16:12 +01:00
ce50cc9523 files: Don't use canonical path when serving file (#2156) 2021-04-13 05:28:30 +01:00
981c54432c remove json and url encoded form support from -http (#2148) 2021-04-12 10:30:28 +01:00
44c55dd036 remove cookie support from -http (#2065) 2021-04-09 18:07:10 +01:00
c72d77065d derive debug where possible (#2142) 2021-04-09 03:22:51 +01:00
44a2d2214c update year in MIT license (#2143) 2021-04-09 01:28:35 +01:00
3f5a73793a make module/crate re-exports doc inline (#2141) 2021-04-08 20:51:16 +01:00
e0b2246c68 prepare test release 0.1.0-beta.1 2021-04-02 10:03:01 +01:00
e0ae8e59bf prepare actors release 4.0.0-beta.4 2021-04-02 09:55:35 +01:00
a9641e475a prepare http-test release 3.0.0-beta.4 2021-04-02 09:54:35 +01:00
05c7505563 prepare multipart release 0.4.0-beta.4 2021-04-02 09:45:31 +01:00
8561263545 prepare files release 0.6.0-beta.4 2021-04-02 09:43:51 +01:00
a32151525c prepare awc release 3.0.0-beta.4 2021-04-02 09:40:36 +01:00
546e7c5da4 prepare web release 4.0.0-beta.5 2021-04-02 09:37:51 +01:00
6fb06a720a prepare http release 3.0.0-beta.5 2021-04-02 09:27:11 +01:00
c54a0713de migrate integration testing to new crate (#2112) 2021-04-02 08:26:59 +01:00
50dc13f280 move typed headers and implement FromRequest (#2094)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-04-01 16:42:18 +01:00
c8ed8dd1a4 migrate to -utils beta 4 (#2127) 2021-04-01 15:26:13 +01:00
a807d33600 added TestServer::client_headers (#2097)
Co-authored-by: fakeshadow <24548779@qq.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-04-01 06:40:10 +01:00
1f1be6fd3d add Client::headers (#2114) 2021-03-31 11:43:56 +01:00
c49fe79207 Simplify lifetime annotation in HttpServiceBuilder. Simplify PlStream (#2129) 2021-03-30 15:46:09 +01:00
f66774e30b remove From<OffsetDateTime> impl from HttpDate
fully removes time crate from public api of -http
2021-03-30 03:32:22 +01:00
1281a748d0 merge H1ServiceHandler requests into HttpServiceHandler (#2126) 2021-03-30 03:06:16 +01:00
222acfd070 Fix build for next actix-tls-beta release (#2122) 2021-03-29 13:45:48 +01:00
980ecc5f07 fix openssl windows ci 2021-03-29 13:01:37 +01:00
e8ce73b496 update dep docs 2021-03-29 11:52:59 +01:00
f954a30c34 Fix typo in CHANGES.md (#2124) 2021-03-29 10:18:05 +01:00
60f9cfbb2a Refactor actix_http::h2::service module. Reduce loc. (#2118) 2021-03-26 18:24:51 +00:00
6822bf2f58 Refactor actix_http::h1::service (#2117) 2021-03-26 16:15:04 +00:00
2f7f1fa97a fix broken pipe for h2 when client is instantly dropped (#2113) 2021-03-26 00:05:31 +00:00
8c2ce2dedb fix awc compress feature (#2116) 2021-03-25 22:47:37 +00:00
3188ef5731 don't use rust annotation on code doc blocks 2021-03-25 08:45:52 +00:00
9704beddf8 Relax MessageBody limit to 2048kb (#2110)
* relax MessageBody limit to 2048kb

* fix clippy

* Update awc/src/response.rs

Co-authored-by: Rob Ede <robjtede@icloud.com>

* fix default body limit

Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-03-24 04:44:03 -07:00
1be54efbeb Simplify service factory macro (#2108) 2021-03-23 13:42:46 +00:00
746d983849 handle header error with CustomResponder (#2093) 2021-03-20 05:18:06 +00:00
8d9de76826 Simplify handler factory macro (#2086) 2021-03-19 16:30:53 +00:00
9488757c29 Update to socket2 v0.4 (#2092) 2021-03-19 12:17:06 +00:00
351286486c fix clippy warning on nightly (#2088)
* fix clippy warning on nightly
2021-03-19 19:25:35 +08:00
78fcd0237a Format extract macro (#2087) 2021-03-19 04:08:23 +00:00
81942d31d6 fix new dyn trait lint 2021-03-19 02:03:09 +00:00
b75b5114c3 refactor actix_http connection types and connector services (#2081) 2021-03-18 17:53:22 +00:00
abcb444dd9 fix routes in Path documentation (#2084) 2021-03-18 13:21:44 +00:00
983b6904a7 unvendor openssl 2021-03-17 00:38:54 +00:00
3dc2d145ef import some traits as _ 2021-03-17 00:38:54 +00:00
c8f6d37290 rename client io trait. reduce duplicate code (#2079) 2021-03-16 16:31:14 +00:00
69dd1a9bd6 Remove ConnectionLifetime trait. Simplify Acquired handling (#2072) 2021-03-16 02:56:23 +00:00
d93314a683 fix awc readme example (#2076) 2021-03-15 10:59:42 +00:00
a55e87faaa refactor actix_http::helpers to generic over bufmut trait (#2069) 2021-03-15 02:33:51 +00:00
515d0e3fb4 change behavior of default upgrade handler (#2071) 2021-03-13 22:20:18 +00:00
22dcc31193 Fix logger middleware properly escape %% (#2067) 2021-03-11 14:12:42 +00:00
909ef0344b document client mod removal
closes #2064
2021-03-11 00:43:03 +00:00
a2b0e86632 simplify connector generic type (#2063) 2021-03-10 23:57:32 +00:00
d0c1f1a84c remove actix_http::client::pool::Protocol (#2061) 2021-03-10 01:31:50 +00:00
b62da7e86b prepare actix-web-actors release 4.0.0-beta.3 2021-03-09 23:44:26 +00:00
5e9a3eb6ae prepare actix-multipart release 0.4.0-beta.3 2021-03-09 23:40:50 +00:00
3451d6874f prepare actix-files release 0.6.0-beta.3 2021-03-09 23:39:40 +00:00
18c3783a1c prepare actix-http-test release 3.0.0-beta.3 2021-03-09 23:35:42 +00:00
4b46351d36 prepare actix-web release 4.0.0-beta.4 2021-03-09 23:31:44 +00:00
b7c406637d prepare actix-web-codegen release 0.5.0-beta.2 2021-03-09 23:27:38 +00:00
c4e5651215 update docs 2021-03-08 23:49:12 +00:00
23b0e64199 prepare awc release 3.0.0-beta.3 2021-03-08 23:15:53 +00:00
fc31b091e4 prepare http release 3.0.0-beta.4 2021-03-08 23:07:40 +00:00
effacf8fc8 fix ssl test 2021-03-08 20:51:50 +00:00
95130fcfd0 address clippy warnings 2021-03-08 20:32:19 +00:00
5e81105317 remove ka timer from h2 dispatcher (#2057) 2021-03-08 20:00:20 +00:00
5b4105e1e6 Refactor/client builder (#2053) 2021-03-07 23:57:32 +00:00
2d3a0d6038 json method receives plain serialize (#2052) 2021-03-07 22:11:39 +00:00
fe0b3f459f remove localwaker from h1::payload (#2051) 2021-03-07 21:23:42 +00:00
ca69b6577e use iota for more content-length insertions (#2050) 2021-03-07 19:29:02 +00:00
880b863f95 fix h1 client for handling expect header request (#2049) 2021-03-07 18:33:16 +00:00
78384c3ff5 make actix_http::ws::Codec::new const (#2043) 2021-03-04 19:19:01 +00:00
c1c4400c4a fix h2 tests (#2034) 2021-03-04 13:27:54 +00:00
fc6f974617 Add "name" attribute to route macro (#1934) 2021-03-04 12:38:47 +00:00
14b249b804 update to actix-0.11.0-beta.3 for actix-web-actors (#2042) 2021-03-04 11:39:29 +00:00
0195824794 Update codecov.yml 2021-03-02 13:18:16 +00:00
fb019f15b4 test(files): Fix test and remove outdated case (#2037)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-02-28 23:01:59 +00:00
abc7fd374b update example links (#2036)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-02-28 21:41:07 +00:00
cd652dca75 refactor websocket key hashing (#2035) 2021-02-28 19:55:34 +00:00
c836de44af add client middleware (#2013) 2021-02-28 18:17:08 +00:00
badae2f8fd add local_address bind for client builder (#2024) 2021-02-27 22:31:14 +00:00
1f34718ecd Use once_cell instead of lazy_static (#2029) 2021-02-27 21:55:50 +00:00
ebda60fd6b refactor boxed route (#2033) 2021-02-27 21:00:36 +00:00
d242f57758 fix tests for codecov 2021-02-27 20:58:44 +00:00
b95e1dda34 pin h2 to 0.3.0 2021-02-27 19:57:09 +00:00
8f2a97c6e3 Update README example links (#2027)
The examples repo went through a folder restructuring. More info: https://github.com/actix/examples/pull/411
2021-02-26 01:21:56 +00:00
ebaf25d55a Fix typos in CHANGES.md (#2025)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-02-24 16:19:40 +00:00
42711c23d7 Port over doc comments in route macros. (#2022)
Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-02-24 12:26:56 +00:00
f6393728c7 remove usage of actix_utils::mpsc (#2023) 2021-02-24 09:08:56 +00:00
d92ab7e8e0 add msrv to clippy config (#1862) 2021-02-22 15:39:31 +00:00
5845b3965c actix-http-test: minimize features of dependencies (#2019) 2021-02-22 12:00:08 +00:00
aacec30ad1 reduce duplicate code (#2020) 2021-02-22 11:15:12 +00:00
2dbdf61c37 Inner field of web::Query is public again (#2016) (#2017)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-02-20 17:59:09 +00:00
83365058ce Fix HTTP client link (#2011) 2021-02-18 21:56:24 +00:00
3b93c62e23 Fix Json extractor to be 32kB by default (#2010) 2021-02-18 15:20:20 +00:00
946cccaa1a refactor awc::ClientBuilder (#2008) 2021-02-18 12:30:09 +00:00
1838d9cd0f remove unused method. reduce leaf future type (#2009) 2021-02-18 11:24:10 +00:00
f62a982a51 simplify the match on h1 message type (#2006) 2021-02-18 10:38:27 +00:00
dfd9dc40ea remove awc::connect::connect trait. (#2004) 2021-02-17 17:10:46 +00:00
5efea652e3 add ClientResponse::timeout (#1931) 2021-02-17 11:55:11 +00:00
dfa795ff9d return poll in poll_flush (#2005) 2021-02-17 11:18:31 +00:00
2cc6b47fcf Use http-range library for HttpRange (#2003) 2021-02-16 18:48:16 +00:00
117025a96b simplify client::connection::Connection trait (#1998) 2021-02-16 14:10:22 +00:00
3e0a9b99ff update rust-cache action 2021-02-16 09:28:14 +00:00
17b3e7e225 pool doc nits (#1999) 2021-02-16 09:08:30 +00:00
c065729468 rework client connection pool (#1994) 2021-02-16 08:27:14 +00:00
55db3ec65c split up http body module 2021-02-15 12:20:43 +00:00
0404b78b54 improve body size docs 2021-02-15 11:24:46 +00:00
68d1bd88b1 remove unused flag upgrade (#1992) 2021-02-14 18:13:05 +00:00
308b70b039 fix potential over read (#1991) 2021-02-14 17:36:18 +00:00
7fa6333a0c use rcgen for tls key generation (#1989) 2021-02-13 17:16:36 +00:00
3279070f9f optional cookies features (#1981) 2021-02-13 15:08:43 +00:00
b37669cb3b fix notify on drop (#1987) 2021-02-13 04:23:37 +00:00
1e538bf73e rework ci (#1982) 2021-02-12 21:53:21 +00:00
366c032c36 refactor DateService (#1983) 2021-02-12 21:52:58 +00:00
95113ad12f do not self wake up when have a payload (#1984) 2021-02-12 20:33:13 +00:00
ce9b2770e2 remove unused Dispatcher::new_timeout (#1985) 2021-02-12 10:37:28 +00:00
4fc7d76759 s/websocket/WebSocket in docs 2021-02-12 00:27:20 +00:00
81bef93e5e add time parser year shift tests 2021-02-12 00:15:25 +00:00
31d9ed81c5 change rustfmt line width to 96 2021-02-11 23:03:17 +00:00
c1af5089b9 add 431 and 451 status codes 2021-02-11 22:58:40 +00:00
77efc09362 hide httpmessage mod 2021-02-11 22:58:40 +00:00
871ca5e4ae stop claiming actor support 2021-02-11 22:58:40 +00:00
ceace26ed4 remove unused flag POLLED (#1980) 2021-02-11 14:19:14 -08:00
75a9a72e78 clean up poll_response. add comments (#1978) 2021-02-11 14:54:42 +00:00
d9d0d1d1a2 reduce unsafe (#1972) 2021-02-10 23:11:12 +00:00
ea5ce3befb prepare actix-http 3.0.0-beta.3 release 2021-02-10 18:36:14 +00:00
e18464b274 bump actix web versions in deps 2021-02-10 12:57:13 +00:00
bd26083f33 prepare codegen 0.5.0-beta.1 release 2021-02-10 12:45:46 +00:00
991363a104 consistent case s/web/Web 2021-02-10 12:12:03 +00:00
a290e58982 prepare beta 2 release set (#1975) 2021-02-10 12:10:03 +00:00
dcad9724bc ensure poll_flush on h1 connection disconnect (#1974) 2021-02-10 10:11:53 +00:00
949d14ae2b clean up header map (#1964) 2021-02-09 22:59:17 +00:00
a6ed4aee84 add poll_flush after a non blocked write to h1 dispatcher (#1971) 2021-02-09 22:32:46 +00:00
519d7f2b8a add trust-dns optional feature for actix-http and awc (#1969) 2021-02-09 10:41:20 +00:00
dddb623a11 add services register for tuple and vec of services (#1933) 2021-02-07 23:47:51 +00:00
266cf0622c reduce branch.remove deadcode for h1 dispatcher (#1962) 2021-02-07 22:48:27 +00:00
9604e249c9 use stable clippy (#1963) 2021-02-07 20:33:53 +00:00
dbc47c9122 optimize actix-http messages (#1914) 2021-02-07 20:19:10 +00:00
4c243cbf89 simplify methods of awc::connect::Connect trait (#1941) 2021-02-07 18:56:39 +00:00
deafb7c8b8 Improve impl ResponseError documentation (#1939)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-02-07 04:54:41 +00:00
50309aa295 Use askama-escape for html escaping (#1953) 2021-02-07 04:50:23 +00:00
9eaea6a2fd tweak feature flags 2021-02-07 03:54:58 +00:00
830fb2cdb2 properly drop h2 connection (#1926) 2021-02-07 03:51:36 +00:00
7cfed73be8 fix memory usage for h1 and read bug on buffer size. (#1929) 2021-02-07 03:20:35 +00:00
41bc04b1c4 Use immutable reference of service state. Update awc dns resolver. (#1905) 2021-02-07 01:00:40 +00:00
20cf0094e5 fix master branch build. change web::block output type. (#1957) 2021-02-06 16:23:59 +00:00
83fb4978ad fix awc test_client test (#1960) 2021-02-06 16:05:33 +00:00
51e54dac8b fix limit not working on HttpMessageBody::limit (#1938) 2021-01-27 10:49:57 +00:00
c201c15f8c Improve documentation for PayloadConfig (#1923) 2021-01-24 00:32:10 +00:00
0c8196f8b0 Remove HttpResponseBuilder::json2() (#1903)
It's not necessary to keep both json() and json2() around since the
former reduces the ownership of its parameter to a borrow only to pass
the reference to the latter. Users can instead borrow themselves when
passing an owned value: there doesn't need to be two separate functions.

This change also makes HttpResponseBuilder::json() take T: Deref so it
can accept both references and web extractors like web::Json.
2021-01-18 12:14:29 +00:00
ee10148444 revive commented out tests (#1912) 2021-01-17 05:19:32 +00:00
1c95fc2654 Refactor poll_keepalive for readability (#1901) 2021-01-16 00:15:06 +00:00
da69bb4d12 implement App::data as App::app_data(Data::new(T))) (#1906) 2021-01-15 23:37:33 +00:00
0a506bf2e9 cleanup top level doc comments 2021-01-15 05:38:50 +00:00
b2a9ba2ee4 Update PULL_REQUEST_TEMPLATE.md 2021-01-15 04:54:23 +00:00
f976150b67 return option item from Extensions::insert (#1904) 2021-01-15 04:22:42 +00:00
b1dd8d28bc response header rework (#1869) 2021-01-15 02:11:10 +00:00
4edeb5ce47 optimize ErrorHandler middleware (#1902) 2021-01-14 01:43:44 +00:00
d34a8689e5 Refactor h1 encoder (#1900) 2021-01-12 14:38:53 +00:00
a919d2de56 actix-files: Fix If-(Un)Modified to not consider sub-seconds (#1887) 2021-01-11 18:18:23 +00:00
46a8f28b74 fix actix-files doc about thread pool (#1898) 2021-01-11 17:27:33 +00:00
57398c6df1 Refactor/service request (#1893) 2021-01-11 01:29:16 +00:00
7affc6878e simplify h1 dispatcher (#1899)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-01-11 00:13:56 +00:00
46b2f7eaaf use a non leak pool for HttpRequestInner (#1889)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-01-10 22:59:44 +00:00
9e401b6ef7 refactor Scope (#1895) 2021-01-09 18:06:49 +00:00
fe392abeb4 remove actix-threadpool.use actix_rt::task::spawn_blocking (#1878) 2021-01-09 16:04:19 +00:00
f6cc829758 remove leaked box in REQUEST_POOL and RESPONSE_POOL (#1896) 2021-01-09 15:40:20 +00:00
6575ee93f2 big clean up and docs improvmenet of types mod (#1894) 2021-01-09 13:17:19 +00:00
530d03791d refactor Resource (#1883) 2021-01-09 03:36:58 +00:00
d40ae8c8ca use sync method on Responder trait (#1891) 2021-01-08 22:17:19 +00:00
2204614134 don't run awc doctests that rely on external public endpoints (#1888) 2021-01-08 12:00:58 +00:00
188ee44f81 remove copyless dependency (#1884) 2021-01-07 21:55:00 +00:00
a4c9aaf337 fix extra branch in h1 dispatcher timer (#1882) 2021-01-07 20:42:09 +00:00
c09186a2c0 prepare v4 beta releases (#1881) 2021-01-07 20:02:08 +00:00
d3c476b8c2 use env_logger builders in examples 2021-01-07 02:41:05 +00:00
dc23559f23 address clippy lints 2021-01-07 02:04:26 +00:00
6d710629af fix bug where upgrade future is not reset properly (#1880) 2021-01-07 00:57:34 +00:00
85753130d9 fmt 2021-01-07 00:35:19 +00:00
00ba8d5549 add http3 variant to protocol enum 2021-01-06 18:58:24 +00:00
51e9e1500b add docs to recent additions 2021-01-06 18:52:06 +00:00
a03dbe2dcf replace cloneable service with httpflow abstraction (#1876) 2021-01-06 18:43:52 +00:00
57a3722146 More refactor of app_service (#1879) 2021-01-06 18:11:20 +00:00
57da1d3c0f refactor app_service (#1877) 2021-01-06 11:35:30 +00:00
68117543ea major cleanup of middleware module (#1875)
* major cleanup of middleware module

* update changelog
2021-01-05 09:51:58 +00:00
4f5971d79e add Compat middleware (#1865) 2021-01-05 00:22:57 +00:00
93161df141 clean up body type (#1872) 2021-01-04 23:47:38 +00:00
e567873326 optimize message pool release (#1871) 2021-01-04 13:03:46 +00:00
7d632d0b7b use ByteString as container for websocket text message (#1864) 2021-01-04 11:27:32 +00:00
36aee18c64 fmt 2021-01-04 04:33:15 +00:00
007a145988 use ahash for internal hashmaps 2021-01-04 04:29:07 +00:00
2d4a174420 fmt 2021-01-04 01:01:35 +00:00
21f6c9d7a5 improve code readability 2021-01-04 00:49:02 +00:00
e1683313ec optimize ServiceRequest methods (#1870)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-01-04 00:32:41 +00:00
32de9f8840 Tokio 1.0 (#1813)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-01-03 23:47:04 +00:00
1f202d40e4 optimize write_camel_case in h1 encoder (#1868) 2021-01-03 16:53:01 +00:00
ad608aa64e optimize Resource and Scope service call (#1867) 2021-01-02 19:40:31 +00:00
a1b00b2cd0 change unreleased year 2021-01-02 00:12:18 +00:00
3beb4cf2da replace tinyvec with smallvec (#1866) 2021-01-01 23:18:25 +00:00
522c9a5ea6 update CoC text 2020-12-31 03:24:18 +00:00
102bb8f9ab update dot dep graphs 2020-12-29 00:22:28 +00:00
20b46cdaf9 format factory_tuple macro invocations (#1859) 2020-12-28 21:04:02 +00:00
2a2a20c3e7 bump msrv to 1.46 (#1858) 2020-12-28 00:44:15 +00:00
093d3a6c59 remove deprecated on_connect methods (#1857) 2020-12-27 23:23:30 +00:00
8c9ea43e23 address clippy warnings 2020-12-27 20:54:04 +00:00
f9fcf56d5c reduce branch in actix_http::h1::codec (#1854) 2020-12-27 20:37:53 +00:00
cbda928a33 Rename factory to handler (#1852) 2020-12-26 21:46:19 +00:00
1032f04ded remove unused actix_http::h1::OneRequest (#1853) 2020-12-26 12:46:36 +00:00
b373e1370d prepare files 0.5.0 release 2020-12-26 04:05:45 +00:00
404b5a7709 Add optional support for hidden files/directories (#1811) 2020-12-26 03:36:15 +00:00
ecf08d5156 Remove boxed future from h1 Dispatcher (#1836) 2020-12-24 19:15:17 +00:00
87655b3028 reduce one clone on Arc. (#1850) 2020-12-23 23:58:25 +00:00
3a192400a6 Simplify handler (#1843) 2020-12-23 15:47:07 +00:00
2a7f2c1d59 dispatcher internals testing (#1840) 2020-12-23 01:28:17 +00:00
05f104c240 improve NormalizePath docs (#1839) 2020-12-23 00:19:20 +00:00
4dccd092f3 Bump rand from 0.7.x to 0.8.x (#1845) 2020-12-22 23:45:31 +00:00
95ccf1c9bc replace actix_utils::oneshot with futures_channle::oneshot (#1844) 2020-12-21 16:42:20 +00:00
6cbf27508a simplify ExtractService's return type (#1842) 2020-12-20 02:20:29 +00:00
79de04d862 optimise Extract service (#1841) 2020-12-19 16:33:34 +00:00
a4dbaa8ed1 remove boxed future in DefaultHeaders middleware (#1838) 2020-12-18 23:08:59 +00:00
c7b4c6edfa Disable PR comment from codecov 2020-12-17 21:38:52 +09:00
2a5215c1d6 Remove boxed future from HttpMessage (#1834) 2020-12-17 11:40:49 +00:00
97f615c245 remove boxed futures on Json extract type (#1832) 2020-12-16 23:34:33 +00:00
1a361273e7 optimize bytes and string payload extractors (#1831) 2020-12-16 22:40:26 +00:00
d7ce648445 remove boxed future for Option<T> and Result<T, E> extract type (#1829)
* remove boxed future for Option<T> and Result<T, E> extract type

* use ready macro

* fix fmt
2020-12-16 18:34:10 +00:00
fabc68659b Intradoc links conversion (#1827)
* switching to nightly for intra-doc links

* actix-files intra-doc conversion

* more specific Result

* intradoc conversion complete

* rm blank comments and readme doc link fixes

* macros and broken links
2020-12-13 13:28:39 +00:00
542db82282 Simplify wake up of task (#1826) 2020-12-12 20:07:06 +00:00
ae63eb8bb2 fix clippy warnings (#1806)
* fix clippy warnings

* prevent CI fail status caused by codecov
2020-12-09 11:22:19 +00:00
7a3776b770 remove two unused generics on BoxedRouteFuture types. (#1820) 2020-12-09 10:47:59 +00:00
ff79c33fd4 remove a box (#1814) 2020-12-06 11:42:15 +00:00
b75a9b7a20 add error to message in test helper func (#1812) 2020-12-05 04:57:56 +09:00
d0c6ca7671 test-server => actix-http-test (#1807) 2020-12-02 17:23:30 +00:00
24d525d978 prepare web 3.3.2 release 2020-12-01 22:22:46 +00:00
1f70ef155d Fix match_pattern() returning None for scope with resource of empty path (#1798)
* fix match_pattern function not returning pattern where scope has resource of path ""

* remove print in test

* make comparison on existing else if block

* add fix to changelog
2020-12-01 13:39:41 +00:00
7981e0068a Remove a panic in normalize middleware (#1762)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-12-01 10:22:15 +09:00
32d59ca904 Upgrade socket2 dependency (#1803)
Upgrades to a version not making invalid assumptions about
the memory layout of std::net::SocketAddr
2020-12-01 04:18:02 +09:00
ea8bf36104 update web and awc changelogs 2020-11-29 16:35:35 +00:00
0b5b463cfa prepare web and awc releases
closes #1799
2020-11-29 16:33:45 +00:00
fe6ad816cc update dotgraphs 2020-11-25 00:54:00 +00:00
e72b787ba7 prepare actix-web and actix-http-test releases 2020-11-25 00:53:48 +00:00
efc317d3b0 prepare actix-http and awc releases 2020-11-25 00:07:56 +00:00
31057becca prepare actix-files release 0.4.1 2020-11-24 20:33:23 +00:00
f1a9b45437 improve docs for Files::new 2020-11-24 20:23:09 +00:00
5af46775b8 refactor quality and use TryFrom instead of custom trait (#1797) 2020-11-24 11:37:05 +00:00
70f4747a23 add method for getting accept type preference (#1793) 2020-11-24 10:08:57 +00:00
2f11ef089b fix rustdoc uploads 2020-11-24 00:29:13 +00:00
4100c50c70 add either extractor (#1788) 2020-11-20 18:02:41 +00:00
a929209967 actix-files intra-doc migration (#1785) 2020-11-10 23:54:38 +00:00
49e945c88f switching to nightly for intra-doc links (#1783) 2020-11-09 14:01:36 +00:00
9b42333fac Fix typo in Query extractor docs (#1777) 2020-11-06 13:34:42 +00:00
e5b86d189c Fix typo in request_data.rs (#1774) 2020-11-05 17:46:17 +00:00
4bfd5c2781 Upgrade serde_urlencoded to 0.7 (#1773) 2020-11-06 01:36:15 +09:00
9b6a089b36 fix awc doc example (#1772)
* fix awc readme example

Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-11-05 06:20:01 +08:00
ceac97bb8d Update config.yml 2020-11-04 15:08:12 +00:00
61b65aa64a add common 1xx http response builders (#1768)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-11-02 18:23:18 +09:00
5468c3c410 Drop content length headers from 101 responses (#1767)
Co-authored-by: Sebastian Mayr <smayr@atlassian.com>
2020-11-02 17:44:14 +09:00
b6385c2b4e Remove CoC on actix-http as duplicated 2020-10-31 12:12:19 +09:00
5135c1e3a0 Update CoC contact information 2020-10-31 12:06:51 +09:00
22b451cf2d fix deps.rs badge 2020-10-31 02:39:54 +00:00
42f51eb962 prepare web release 3.2.0 2020-10-30 03:15:22 +00:00
156c97cef2 prepare awc release 2.0.1 2020-10-30 02:50:53 +00:00
798d744eef prepare http release 2.1.0 2020-10-30 02:19:56 +00:00
4cb833616a deprecate builder if-x methods (#1760) 2020-10-30 02:10:05 +00:00
9963a5ef54 expose on_connect v2 (#1754)
Co-authored-by: Mikail Bagishov <bagishov.mikail@yandex.ru>
2020-10-30 02:03:26 +00:00
4519db36b2 register fns for custom request-derived logging units (#1749)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-10-29 18:38:49 +00:00
7030bf5fe8 Adding app_data to ServiceConfig (#1758)
Co-authored-by: Rob Ede <robjtede@icloud.com>
Co-authored-by: Augusto <augusto@flowciety.de>
2020-10-26 17:02:45 +00:00
20078fe603 Bump pin-project to 1.0 (#1733) 2020-10-25 19:41:44 +09:00
06e5042b94 use idenity encoding on client if no compression features are enabled (#1737)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-10-24 21:15:01 +01:00
41e7cec72f Re-export bytes::Buf and bytes::BufMut as well (#1750)
Co-authored-by: Daniel Egger <daniel.egger@axiros.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-10-24 20:31:23 +01:00
d45a1aa6b6 Add web::ReqData<T> extractor (#1748)
Co-authored-by: Jonas Platte <jonas@lumeo.com>
2020-10-24 18:49:50 +01:00
98243db9f1 Print unconfigured Data<T> type when attempting extraction (#1743)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-10-20 17:35:34 +01:00
f92742bdac Bump base64 to 0.13 (#1744) 2020-10-19 18:24:22 +01:00
e563025b16 always construct shortslice using debug checked new constructor (#1741) 2020-10-19 12:51:30 +01:00
cfd5b381f1 Implement Logger middleware regex exclude pattern (#1723)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-10-19 07:18:16 +01:00
2f84914146 Skip some tests that cause ICE on nightly (#1740) 2020-10-19 11:52:05 +09:00
d765e9099d Fix clippy::rc_buffer (#1728) 2020-10-10 09:26:05 +09:00
34b23f31c9 prepare files release 0.4.0 2020-10-06 22:08:33 +01:00
26c1a901d9 add files preference for utf8 text responses (#1714) 2020-10-06 21:56:28 +01:00
c2c71cc626 Fix/suppress clippy warnings (#1720) 2020-10-01 18:19:09 +09:00
aa11231ee5 prepare web release 3.1.0 (#1716) 2020-09-30 11:07:35 +01:00
b5812b15f0 Remove Sized Bound for web::Data (#1712) 2020-09-29 22:44:12 +01:00
b4e02fe29a Fix cyclic references in ResourceMap (#1708) 2020-09-25 17:42:49 +01:00
37c76a39ab Fix Multipart consuming payload before header checks (#1704)
* Fix Multipart consuming payload before header checks

What
--
Split up logic in the constructor into two functions:

- **from_boundary:** build Multipart from boundary and stream
- **from_error:** build Multipart for MultipartError

Also we make the `boundary`, `from_boundary`, `from_error`  methods public within the crate so that we can use them in the extractor.

The extractor is then able to perform header checks and only consume the
payload if the checks pass.

* Add tests

* Add payload consumption test

Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-09-25 14:50:37 +01:00
60e7e52276 Add TrailingSlash::MergeOnly behavior (#1695)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-09-25 12:50:59 +01:00
c53e9468bc prepare codegen 0.4.0 release (#1702) 2020-09-24 23:54:01 +01:00
162121bf8d Unify route macros (#1705) 2020-09-22 22:42:51 +01:00
f7bcad9567 split up files lib (#1685) 2020-09-20 23:18:25 +01:00
f9e3f78e45 eemove non-relevant comment from actix-http README.md (#1701) 2020-09-20 17:21:53 +01:00
1596893ef7 update actix-http dev-dependencies (#1696)
Co-authored-by: luojinming <luojm@hxsmart.com>
2020-09-19 23:20:34 +09:00
2a2474ca09 Update tinyvec to 1.0 (#1689) 2020-09-17 18:09:42 +01:00
509b2e6eec Provide attribute macro for multiple HTTP methods (#1674)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-09-16 22:37:41 +01:00
d707704556 prepare web release 3.0.2 (#1681) 2020-09-15 13:14:14 +01:00
a429ee6646 Add possibility to set address for test_server (#1645) 2020-09-15 12:09:16 +01:00
7f8073233a fix trimming to inaccessible root path (#1678) 2020-09-15 11:32:31 +01:00
4b4c9d1b93 update migration guide
closes #1680
2020-09-14 22:26:03 +01:00
3fde3be3d8 add trybuild tests to routing codegen (#1677) 2020-09-13 16:31:08 +01:00
f861508789 prepare web release 3.0.1 (#1676) 2020-09-13 03:24:44 +01:00
a4546f02d2 make TrailingSlash enum accessible (#1673)
Co-authored-by: Damian Lesiuk <lesiuk@sabre.com>
2020-09-13 00:55:39 +01:00
64a2c13cdf the big three point oh (#1668) 2020-09-11 13:50:10 +01:00
bf53fe5a22 bump actix dependency to v0.10 (#1666) 2020-09-11 12:09:52 +01:00
cf5138e740 fix clippy async_yields_async lints (#1667) 2020-09-11 11:29:17 +01:00
121075c1ef awc: Rename Client::build to Client::builder (#1665) 2020-09-11 09:24:39 +01:00
22089aff87 Improve json, form and query extractor config docs (#1661) 2020-09-10 15:40:20 +01:00
7787638f26 fix CI clippy warnings (#1664) 2020-09-10 14:46:35 +01:00
2f6e9738c4 prepare multipart and actors releases (#1663) 2020-09-10 12:54:27 +01:00
e39d166a17 Fix examples hyperlink in README (#1660) 2020-09-10 00:12:50 +01:00
059d1671d7 prepare release beta 4 (#1659) 2020-09-09 22:14:11 +01:00
3a27580ebe awc: improve module documentation (#1656)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-09-09 14:24:12 +01:00
9d0534999d bump connect and tls versions (#1655) 2020-09-09 09:20:54 +01:00
c54d73e0bb Improve awc websocket docs (#1654)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-09-07 12:04:54 +01:00
9a9d4b182e document all remaining unsafe usages (#1642)
adds some debug assertions where appropriate
2020-09-03 10:00:24 +01:00
4e321595bc extract more config types from Data<T> as well (#1641) 2020-09-02 22:12:07 +01:00
01cbef700f Fix a small typo in a doc comment. (#1649) 2020-08-28 22:16:41 +01:00
8497b5f490 integrate with updated actix-{codec, utils} (#1634) 2020-08-24 10:13:35 +01:00
LJ
75d86a6beb Configurable trailing slash behaviour for NormalizePath (#1639)
Co-authored-by: ljoonal <ljoona@ljoonal.xyz>
2020-08-19 12:21:52 +01:00
3892a95c11 Fix actix-web version to publish 2020-08-18 01:16:18 +09:00
5802eb797f awc,web: Bump up to next beta releases (#1638) 2020-08-18 01:08:40 +09:00
ff2ca0f420 Update rustls to 0.18 (#1637) 2020-08-18 00:28:39 +09:00
59ad1738e9 web: Bump up to 3.0.0-beta.2 (#1636) 2020-08-17 11:32:38 +01:00
aa2bd6fbfb http: Bump up to 2.0.0-beta.3 (#1630) 2020-08-14 19:42:14 +09:00
5aad8e24c7 Re-export all error types from awc (#1621) 2020-08-14 01:24:35 +01:00
6e97bc09f8 Use action to upload docs 2020-08-13 16:04:50 +09:00
160995b8d4 fix awc pool leak (#1626) 2020-08-09 21:49:43 +01:00
187646b2f9 match HttpRequest app_data behavior in ServiceRequest (#1618) 2020-08-09 15:51:38 +01:00
46627be36f add dep graph dot graphs (#1601) 2020-08-09 13:54:35 +01:00
a78380739e require rustls feature for client example (#1625) 2020-08-09 13:32:37 +01:00
cf1c8abe62 prepare release http & awc (#1617) 2020-07-22 01:13:10 +01:00
92b5bcd13f Check format and tweak CI config (#1619) 2020-07-22 00:28:33 +01:00
701bdacfa2 Fix illegal chunked encoding (#1615)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-07-21 17:24:56 +01:00
6dc47c4093 fix soundness concern in h1 decoder (#1614)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-07-21 16:25:33 +01:00
0ec335a39c bump MSRV to 1.42 (#1616) 2020-07-21 16:40:30 +09:00
f8d5ad6b53 Make web::Path a tuple struct with a public inner value (#1594)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-07-21 00:54:26 +01:00
43c362779d also try extracting payload config as Data<T> (#1610) 2020-07-20 17:40:58 +01:00
971ba3eee1 fix continous growth of app data in pooled requests (#1609)
fixes #1606
fixes #1607
2020-07-18 16:17:00 +01:00
2fd96c03e5 prepare beta.1 release for multipart/files/actors (#1605) 2020-07-16 11:38:57 +01:00
ad7c6d2633 prepare actix-web v3.0.0-beta.1 release (#1600) 2020-07-15 00:44:44 +01:00
3362a3d61b Merge pull request #1603 from JohnTitor/license
Avoid using deprecated `/` in license field
2020-07-14 15:25:48 +09:00
769ea6bd5b Merge pull request #1602 from actix/release/awc-beta-1
prepare awc v2.0.0-beta.1 release
2020-07-14 13:33:05 +09:00
1382094c15 Avoid using deprecated / in license field 2020-07-14 11:19:56 +09:00
78594a72bd prepare awc v2.0.0-beta.1 release 2020-07-14 03:16:26 +01:00
327e472e44 prepare http-2.0.0-beta.1 release (#1596) 2020-07-13 15:35:30 +01:00
e10eb648d9 Fix leaks with actix_http's client (#1580) 2020-07-10 22:35:22 +01:00
a2662b928b Update PULL_REQUEST_TEMPLATE.md 2020-07-10 14:55:56 +01:00
84583799be Merge pull request #1592 from JohnTitor/fix-tarpaulin
Use tarpaulin 0.13 to avoid failure
2020-07-07 05:11:09 +09:00
08f9a34075 Use tarpaulin 0.13 to avoid failure 2020-07-07 04:00:18 +09:00
056803d534 revamp readme and root doc page (#1590) 2020-07-05 01:16:53 +01:00
deab634247 actix-http: Update sha-1 to 0.9 (#1586) 2020-07-03 01:08:24 +01:00
0b641a2db2 Merge pull request #1585 from JohnTitor/v-htmlescape
Update `v_htmlescape` to 0.10
2020-07-03 07:46:26 +09:00
f2d641b772 Update v_htmlescape to 0.10 2020-07-02 17:52:42 +09:00
23c8191cca add method to extract matched resource name (#1577)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-06-27 16:22:16 +01:00
487f90be5b move pull request template (#1578) 2020-06-23 14:26:07 +09:00
fa28175a74 add method to extract matched resource pattern (#1566) 2020-06-23 00:58:20 +01:00
a70e599ff5 re-export rt in web and add main macro (#1575) 2020-06-22 20:09:48 +01:00
c11052f826 add PR template for bugs and features (#1570)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-06-22 18:28:06 +09:00
73ec01e83b Merge pull request #1576 from b4skyx/patch-1
Update benchmark url in README.md
2020-06-22 16:48:49 +09:00
a7c8533291 Update benchmark url in README.md
Updated benchmark url from r18 to the latest r19.
2020-06-22 04:14:48 +00:00
eb0eda69c6 migrate cookie handling to cookie crate (#1558) 2020-06-19 14:34:14 +01:00
dc74db1f2f re-export actix_rt::main macro (#1559) 2020-06-18 15:45:30 +01:00
0ba73fc44c exclude default -web features on -actors (#1562) 2020-06-18 11:23:36 +01:00
9af07d66ae Fix NormalizePath trailing slash behavior (#1548) 2020-06-17 10:54:20 +01:00
e72ee28232 Enforce HW_BUFFER_SIZE inside h1::dispatcher (#1550) 2020-06-17 08:58:23 +01:00
4f9a1ac3b7 Merge pull request #1553 from taiki-e/pin-project
Remove uses of pin_project::project attribute
2020-06-07 02:00:01 +09:00
6c5c4ea230 Remove uses of pin_project::project attribute
pin-project will deprecate the project attribute due to some unfixable
limitations.

Refs: https://github.com/taiki-e/pin-project/issues/225
2020-06-06 06:44:14 +09:00
a79450482c Merge pull request #1547 from JohnTitor/appveyor
Remove AppVeyor config
2020-06-03 10:43:01 +09:00
5286b8aed7 Remove AppVeyor config 2020-06-03 03:39:35 +09:00
621ebec01a Fix typo in timeout error display (#1552) 2020-06-02 18:04:49 +01:00
5e5c8b1c83 Merge pull request #1544 from JohnTitor/remove-framed
Remove actix-framed from workspace
2020-05-31 16:10:46 +09:00
322e7c15d1 Remove actix-framed from workspace 2020-05-31 05:50:32 +09:00
7aa757ad5a Merge pull request #1540 from JohnTitor/next-multipart
multipart: Bump up to 0.3.0-alpha.1
2020-05-25 22:04:07 +09:00
482f74e409 multipart: Bump up to 0.3.0-alpha.1 2020-05-25 19:12:20 +09:00
19967c41cc Merge pull request #1538 from JohnTitor/codegen-meta
Tweak codegen metadata
2020-05-25 17:37:58 +09:00
8a106c07b4 Tweak codegen metadata 2020-05-25 16:45:34 +09:00
11a9fdad95 Merge pull request #1535 from JohnTitor/next-files
files: Bump up to 0.3.0-alpha.1
2020-05-24 11:52:19 +09:00
75a34dc8bc files: Bump up to 0.3.0-alpha.1 2020-05-23 18:47:08 +09:00
5b60ee8cfd Merge pull request #1534 from JohnTitor/next-codegen
codegen: Bump up to 0.2.2
2020-05-23 18:08:36 +09:00
bb89d04080 codegen: Bump up to 0.2.2 2020-05-23 17:22:30 +09:00
f57b5659da Merge pull request #1533 from JohnTitor/next-test-server
http-test: Bump up to 2.0.0-alpha.1
2020-05-23 15:22:40 +09:00
4a955c425d Update actix-http-test dependency to 2.0.0-alpha.1 2020-05-23 12:14:17 +09:00
905f86b540 http-test: Bump up to 2.0.0-alpha.1 2020-05-23 12:13:43 +09:00
0348114ad8 Merge pull request #1532 from JohnTitor/issue-template
Add links to Gitter on the issue template
2020-05-23 12:11:59 +09:00
9c5a2d6580 Add links to Gitter on the issue template 2020-05-22 13:14:01 +09:00
2550f00702 Merge pull request #1530 from NickKolpinskiy/itoa
Use `itoa` in the content-length helper
2020-05-22 13:03:50 +09:00
7d8fb631a0 Use itoa in the content-length helper 2020-05-21 22:25:34 +03:00
b9e268e95f Merge pull request #1529 from JohnTitor/next-web
web: Bump up to 3.0.0-alpha.3
2020-05-21 19:32:24 +09:00
6dd78d9355 Run rustfmt 2020-05-21 17:56:53 +09:00
fe89ba7027 Update actix-web dependency to 3.0.0-alpha.3 2020-05-21 17:32:36 +09:00
5d39110470 web: Bump up to 3.0.0-alpha.3 2020-05-21 17:31:22 +09:00
5bde4e2529 Merge pull request #1528 from JohnTitor/next-awc
awc: Bump up to 2.0.0-alpha.2
2020-05-21 17:25:37 +09:00
9b72d33b79 Update awc to 2.0.0-alpha.2 2020-05-21 16:48:20 +09:00
0f826fd11a awc: Bump up to 2.0.0-alpha.2 2020-05-21 16:47:16 +09:00
cf92cfa777 Merge pull request #1527 from JohnTitor/next-http
http: Bump up to 2.0.0-alpha.4
2020-05-21 16:35:34 +09:00
9cfb32c780 Update actix-http to 2.0.0-alpha.4 2020-05-21 15:22:42 +09:00
48fa78e182 http: Bump up to 2.0.0-alpha.4 2020-05-21 15:22:07 +09:00
184683a698 Merge pull request #1525 from JohnTitor/deps
Update dependencies
2020-05-21 11:58:59 +09:00
6c78f57a70 test-server: Update dependencies 2020-05-21 09:52:15 +09:00
603973dede awc: Update base64 to 0.12 2020-05-21 09:51:58 +09:00
8391427905 http: Update base64 to 0.12 2020-05-21 09:51:32 +09:00
2314dc30b5 Merge pull request #1519 from JohnTitor/sample-size
bench: Reduce sample count to remove warning
2020-05-21 01:49:17 +09:00
864fc489ce CI: Reduce sample size 2020-05-20 22:33:25 +09:00
c48af0c822 Merge pull request #1522 from actix/only-server-bench
CI: Only run the server benchmark to avoid SIGKILL
2020-05-20 15:22:13 +09:00
50adbdecbe CI: Only run the server benchmark to avoid SIGKILL 2020-05-20 13:12:08 +09:00
f8f5a82f40 Merge pull request #1518 from JohnTitor/replace-net2
Replace deprecated `net2` crate with `socket2`
2020-05-19 10:25:22 +09:00
9a7f93610a web: Replace net2 crate with socket2 2020-05-19 09:34:37 +09:00
2dac9afc4e test-server: Replace net2 crate with socket2 2020-05-19 09:25:51 +09:00
74491dca59 Merge pull request #1515 from JohnTitor/minimize-futures
Minimize `futures` dependencies
2020-05-19 09:18:07 +09:00
81b0c32062 test-server: Minimize futures dependencies 2020-05-19 08:29:12 +09:00
a98e53ecb8 web: Minimize futures dependencies 2020-05-19 08:29:12 +09:00
d7abbff3b0 awc: Minimize futures dependencies 2020-05-19 08:29:12 +09:00
24372467d9 codegen: Minimize futures dependencies 2020-05-19 08:29:11 +09:00
fc8e07b947 actors: Minimize futures dependencies 2020-05-19 08:29:11 +09:00
ab4d8704f1 multipart: Minimize futures dependencies 2020-05-19 08:29:11 +09:00
292af145cb http: Minimize futures dependencies 2020-05-19 08:29:11 +09:00
9bd6407730 framed: Minimize futures dependencies 2020-05-19 08:24:34 +09:00
245dd471dd files: Minimize futures dependencies 2020-05-19 08:24:34 +09:00
32a37b7282 Remove content_length from ResponseBuilder (#1491)
* Remove content_length since it'll be overwritten by the response body. FIXES #1439

* Add setting of Content-Length to the no_chunking function

* Add changes and migration documentations

* Update MIGRATION.md

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>

Co-authored-by: Rob Ede <robjtede@icloud.com>
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-05-19 07:46:31 +09:00
426a9b5d4d Merge pull request #1514 from actix/remove/sized64
remove needless BodySize::Sized64 variant
2020-05-18 10:40:01 +09:00
7e8ea44d5c remove needless BodySize::Sized64 variant 2020-05-18 00:42:51 +01:00
b0866a8a0f Actix-files will always send SizedStream (#1496)
* Fixes #1384

* There is no need to set no_chunking

* Test content-length for static files

* Update the tests

* Add Changelog

* Try to simply fix Windows test issues!

Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-05-18 06:54:42 +09:00
7fe426f626 Merge pull request #1512 from JohnTitor/remove-outdated-members
Remove outdated members
2020-05-17 12:30:03 +09:00
433a4563cf Remove outdated members 2020-05-17 10:56:06 +09:00
f3b0233477 use mem::take where possible (#1507) 2020-05-17 10:54:42 +09:00
201090d7a2 Provide impl<T> From<Arc<T>> for Data<T> (#1509) 2020-05-16 00:27:03 +01:00
4fc99d4a6f Fix audit issue logging by default peer address (#1485)
* Fix audit issue logging by default peer address

By default log format include remote address that is taken from headers.
This is very easy to replace making log untrusted.

Changing default log format value `%a` to peer address we are getting
this trusted data always. Also, remote address option is maintianed and
relegated to `%{r}a` value.

Related  kanidm/kanidm#191.

* Rename peer/remote to remote_addr/realip_remote_addr

Change names to avoid naming confusions. I choose this accord to Nginx
variables and
[ngx_http_realip_module](https://nginx.org/en/docs/http/ngx_http_realip_module.html).

Add more specific documentation about security concerns of using Real IP
in logger.

* Rename security advertise header in doc

* Add fix audit issue logging by default peer adress to changelog

Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-05-15 09:07:27 +09:00
92ce975d87 Merge pull request #1506 from actix/chore/bump-140
bump msrv to 1.40
2020-05-13 23:46:13 +09:00
996f1d7eae bump msrv in ci and readme 2020-05-13 01:57:37 +01:00
63864ecf9e support parsing of SameSite=None (#1503) 2020-05-12 17:48:35 +01:00
bbd4d19830 Merge pull request #1486 from actix/feat/data-cascade
allow parent data containers to be accessed from child scopes
2020-05-09 09:40:25 +09:00
879cad9422 allow parent data containers to be accessed from child scopes 2020-05-09 00:31:26 +01:00
6e8ff5c905 Merge pull request #1495 from JohnTitor/new-web
actix-web: Bump up to 3.0.0-alpha.2
2020-05-08 07:28:18 +09:00
b66c3083a5 Update the actix-web dependency to 3.0.0-alpha.2 2020-05-08 06:46:42 +09:00
b6b3481c6f web: Bump up to 3.0.0-alpha.2 2020-05-08 06:46:13 +09:00
574714d156 Merge pull request #1494 from JohnTitor/new-actors
actors: Bump up to 3.0.0-alpha.1
2020-05-08 06:17:15 +09:00
54abf356d4 actors: Bump up to 3.0.0-alpha.1 2020-05-08 03:33:29 +09:00
9cb3b0ef58 Merge pull request #1493 from JohnTitor/http-next
http: Bump up to 2.0.0-alpha.3
2020-05-08 03:09:52 +09:00
9d0c80b6ce Update actix-http deps 2020-05-08 02:35:45 +09:00
0bc4a5e703 http: Bump up to 2.0.0-alpha.3 2020-05-08 02:35:45 +09:00
9d94fb91b2 correct spelling of ConnectError::Unresolved (#1487)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-05-08 02:26:48 +09:00
9164ed1f0c add resource middleware on actix-web-codegen (#1467)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-05-07 18:31:12 +09:00
b521e9b221 conditional test compilation [range, charset] (#1483)
* conditionally compile range and charset tests

* remove deprecated try macros

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-05-03 22:33:29 +09:00
f37cb6dd0b refactor h1 status line helper to remove unsafe usage (#1484)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-05-03 17:37:40 +09:00
d5ceae2074 Replace deprecated now with now_utc (#1481)
* Replace deprecated now with now_utc

* Update doctest
2020-05-02 10:14:50 +01:00
c27d3fad8e clarify resource/scope app data overriding (#1476)
* relocate FnDataFactory

* clarify app data overriding in Scope and Resource

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-04-30 02:20:47 +09:00
bb17280f51 simplify data factory future polling (#1473)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-04-29 15:38:53 +09:00
b047413b39 Small ws codec fix (#1465)
* Small ws codec fix

* Update actix-http/Changes.md

Co-authored-by: Huston Bokinsky <huston@deepgram.com>
2020-04-29 11:13:09 +09:00
Huy
ce24630d31 Fix typos in MIGRATION.md (#1470)
* Fix typos in MIGRATION.md

Those are `crate` not `create`

* Update MIGRATION.md
2020-04-23 03:39:09 +09:00
751253f23e Merge pull request #1463 from actix/fix/doc-typos
fix spelling errors in doc comments
2020-04-21 13:47:03 +09:00
5b0f7fff69 fix spelling errors in doc comments 2020-04-21 04:09:35 +01:00
54619cb680 actix-http: Remove failure support (#1449) 2020-04-16 06:54:34 +09:00
5b36381cb0 Merge pull request #1452 from actix/fix/default-service-data
set data container on default service calls
2020-04-16 06:01:56 +09:00
45e2e40140 set data container on default service calls
closes #1450
2020-04-14 02:33:19 +01:00
df3f722589 Merge pull request #1451 from actix/cache
Remove cache config from GHA workflows
2020-04-13 06:06:45 +09:00
e7ba871bbf Remove cache config from GHA workflows 2020-04-13 03:42:44 +09:00
ebc2e67015 Merge pull request #1442 from JohnTitor/workspace-doc
Deploy all the workspace crates' docs
2020-04-09 00:48:08 +09:00
74ddc852c8 Tweak README 2020-04-08 04:48:01 +09:00
dfaa330a94 Deploy all the workspace crates' docs 2020-04-08 04:42:38 +09:00
0ad02ee0e0 Add convenience functions for testing (#1401)
* Add convenience functions for testing

* Fix remarks from PR and add tests

* Add unpin to read_json_body

* Update changelog
2020-04-06 04:12:44 +09:00
aaff68bf05 Change NormalizePath to append trailing slash (#1433)
* Change NormalizePath to append trailing slash

* add tests

* Update CHANGES.md

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-04-05 03:26:40 +09:00
fcb1dec235 Merge pull request #1431 from OSSystems/topic/explicit-features-requirements
Add explicit feature requirements for examples and tests
2020-03-28 10:58:00 +09:00
7b7daa75a4 Add explicit feature requirements for examples and tests
This allow us to build 'actix-web' without default features and run all
tests.

Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
2020-03-25 23:49:24 -03:00
2067331884 Refactor actix-codegen duplicate code (#1423)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-03-20 04:40:42 +09:00
bf630d9475 Merge pull request #1422 from OSSystems/topic/impl-error-more-types
Implement `std::error::Error` for our custom errors
2020-03-19 05:05:57 +09:00
146ae4da18 Implement std::error::Error for our custom errors
For allowing a more ergonomic use and better integration on the
ecosystem, this adds the `std::error::Error` `impl` for our custom
errors.

We intent to drop this hand made code once `derive_more` finishes the
addition of the Error derive support[1]. Until that is available, we
need to live with that.

1. https://github.com/JelteF/derive_more/issues/92

Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
2020-03-18 00:22:18 -03:00
52c5755d56 Merge pull request #1421 from actix/JohnTitor-patch-1
Upload coverage on PRs
2020-03-18 06:16:41 +09:00
5548c57a09 Upload coverage on PRs 2020-03-18 05:04:30 +09:00
0d958fabd7 📝 Improve the code example for JsonConfig (#1418)
* 📝 Improve the code example for JsonConfig

* Remove a redundant comment
2020-03-17 08:23:54 +09:00
c67e4c1fe8 Refactored macros (#1333)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-03-15 07:23:28 +09:00
4875dfbec7 Merge pull request #1416 from JohnTitor/fix-doc
Fix `read_body` doc
2020-03-13 07:06:57 +09:00
d602a7e386 Fix read_body doc 2020-03-13 05:52:58 +09:00
9f196fe5a5 Merge pull request #1413 from OSSystems/topic/fix-warnings
Fix clippy warnings
2020-03-12 16:24:44 +09:00
e4adcd1935 Merge pull request #1411 from JohnTitor/patch
Clean-up metadata
2020-03-12 16:21:34 +09:00
7e0d898d5a Fix clippy warnings
Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
2020-03-12 00:52:21 -03:00
51518721e5 Add notes to READMEs 2020-03-12 07:57:38 +09:00
c02d3e205b Clean-up metadata 2020-03-12 07:57:20 +09:00
a253d7d3ce Merge pull request #1410 from JohnTitor/new-web
Release actix-web 3.0.0-alpha.1
2020-03-12 05:12:54 +09:00
0152cedc5d Update changelog 2020-03-12 03:24:15 +09:00
a6a47b95ee Exclude actix-cors 2020-03-12 03:04:31 +09:00
1b28a5d48b Update actix-web dependency to 3.0.0-alpha.1 2020-03-12 03:03:50 +09:00
c147b94832 Bump up to 3.0.0-alpha.1 2020-03-12 03:03:22 +09:00
95f9a12a5e dev-deps: Update env_logger to 0.7 2020-03-12 02:58:22 +09:00
73eeab0e90 Merge pull request #1391 from JohnTitor/new-awc
Release awc v2.0.0-alpha.1
2020-03-11 21:15:48 +09:00
ce1e996b41 Update release date 2020-03-11 20:42:45 +09:00
e718f65121 Update tests 2020-03-08 16:42:45 +09:00
a9a475d555 Make test_server async fn 2020-03-08 16:42:26 +09:00
b93e1555ec Update actix-connect to 2.0.0-alpha.2 2020-03-08 15:27:40 +09:00
6f33b7ea42 Update awc dependency 2020-03-08 15:27:40 +09:00
294523a32f Bump up to 2.0.0-alpha.1 2020-03-08 15:27:39 +09:00
6b626c7d77 dev-deps: Update env_logger to 0.7 2020-03-08 03:07:53 +09:00
5da9e277a2 Merge pull request #1399 from JohnTitor/new-http
Release actix-http 2.0.0-alpha.2
2020-03-08 01:47:40 +09:00
0d5646a8b6 Run rustfmt 2020-03-08 00:52:39 +09:00
7941594f94 Update actix-http dependency 2020-03-08 00:50:20 +09:00
6f63acaf01 Bump up to 2.0.0-alpha.2 2020-03-08 00:48:45 +09:00
7172885beb Update changelog 2020-03-08 00:43:17 +09:00
cf721c5fff Update README example 2020-03-08 00:43:01 +09:00
10e3e72595 Http2 client configuration to improve performance (#1394)
* add defaults for http2 client configuration

* fix spaces

* Add changes text for extended H2 defaults buffers

* client: configurable H2 window sizes and max_http_version

* add H2 window size configuration and max_http_version to awc::ClientBuilder

* add awc::ClientBuilder H2 window sizes and max_http_version

* add test for H2 window size settings

* cleanup comment

* Apply code review fixes

* Code review fix for awc ClientBuilder

* Remove unnecessary comments on code review

* pin quote version to resolve build issue

* max_http_version to accept http::Version

* revert fix for quote broken build
2020-03-07 11:09:31 +09:00
a7d805aab7 Merge pull request #1396 from Aaron1011/fix/reapply-dispatcher
Re-apply commit 2cf7b3ad20
2020-03-05 02:48:20 +09:00
e90950fee1 Re-apply commit 2cf7b3ad20
This ended up getting reverted by #1367, which re-introduced an unsound
use of `Pin::new_unchecked`

See my original PR #1374 for the reasoning behind this change.
2020-03-04 11:27:58 -05:00
c8f0672ef7 Merge pull request #1395 from JohnTitor/rustls
Update `rustls` to 0.17
2020-03-04 15:56:27 +09:00
9d661dc4f3 Update changelog 2020-03-04 15:20:14 +09:00
687dc609dd Update rustls to 0.17 2020-03-04 15:11:31 +09:00
b9b52079e0 Update actix-tls to 2.0.0-alpha.1 2020-03-04 15:10:23 +09:00
117d28f7ba Update actix-connect to 2.0.0-alpha.1 2020-03-04 15:09:31 +09:00
795a575fc5 Merge pull request #1386 from JohnTitor/deny-to-warn
Demote lint level to warn
2020-02-28 14:17:11 +09:00
b4d63667df Demote lint level to warn 2020-02-27 22:39:11 +09:00
3dc859af58 Fix missing std::error::Error implement for MultipartError. (#1382)
* Fix missing `std::error::Error` implement for `MultipartError`.

* Update actix-multipart CHANGES.md.
2020-02-27 22:34:06 +09:00
1fa02b5f1c Merge pull request #1385 from JohnTitor/http-2-alpha
Release actix-http 2.0.0-alpha.1
2020-02-27 14:47:32 +09:00
c9fdcc596d Update actix to 0.10.0-alpha.1 2020-02-27 12:46:29 +09:00
6cc83dbb67 Allow clippy lint for compatibility 2020-02-27 12:45:11 +09:00
3b675c9125 Update actix-http to 2.0.0-alpha.1 2020-02-27 12:39:04 +09:00
15a2587887 Bump up to 2.0.0-alpha.1 2020-02-27 12:39:04 +09:00
0173f99726 Update changelog 2020-02-27 12:39:04 +09:00
f27dd19093 Fix Clippy warnings 2020-02-27 12:39:04 +09:00
7ba14fd113 Run rustfmt 2020-02-27 11:10:55 +09:00
903ae47baa dev-deps: Update env_logger to 0.7 2020-02-27 11:08:45 +09:00
95c18dbdf3 Merge pull request #1367 from actix/msg-body
Merge `MessageBody` improvements
2020-02-27 10:42:14 +09:00
d3ccf46e92 Clean-up metadata 2020-02-27 09:53:27 +09:00
cd1765035c Avoid re-definition 2020-02-27 09:42:32 +09:00
ea28219d0f reenable actix-http test-ws 2020-02-27 09:42:32 +09:00
77058ef779 adopt MessageBody Pin changes to actix-web root 2020-02-27 09:42:32 +09:00
e5f2feec45 reenable actix-http from local path 2020-02-27 09:42:32 +09:00
0a86907dd2 use mem::replace instead of mem::take rust 1.40+ 2020-02-27 09:37:05 +09:00
78749a4b7e rollback actix-http version change 2020-02-27 09:37:05 +09:00
de815dd99c Fixed condition for finishing transfer of response 2020-02-27 09:37:05 +09:00
e6078bf792 Fix EncoderBody enum to align with Body::Message 2020-02-27 09:37:05 +09:00
a84b37199a Add Unpin to Body to get rid of unsafe in MessageBody 2020-02-27 09:37:05 +09:00
c05f9475c5 refactor dispatcher to avoid possible UB with DispatcherState Pin 2020-02-27 09:37:05 +09:00
69dab0063c Get rid of one more unsafe 2020-02-27 09:37:05 +09:00
ec5c779732 unlink MessageBody from Unpin 2020-02-27 09:37:05 +09:00
2e2ea7ab80 remove extra whitespaces and Unpins 2020-02-27 09:37:05 +09:00
eeebc653fd change actix-http version to alpha 2020-02-27 09:37:05 +09:00
835a00599c rollback missed dependencies and CHANGES in crates except actix-http 2020-02-27 09:37:05 +09:00
d9c415e540 disable weird poll test until actix-web based on actix-http:2 2020-02-27 09:37:05 +09:00
09a391a3ca rollback changes to actix-web, awc and test-server for now 2020-02-27 09:37:05 +09:00
62aba424e2 Rollback actix-http-test dependency to show the issue 2020-02-27 09:37:05 +09:00
9d04b250f9 This is a squashed commit:
- Convert MessageBody to accept Pin in poll_next

- add CHANGES and increase versions aligned to semver

- update crates to accomodate MessageBody Pin change

- fix tests and dependencies
2020-02-27 09:37:05 +09:00
a4148de226 add test crashing with segfault according to #1321 2020-02-27 09:36:30 +09:00
48ef4d7a26 Add actix-http support for actix error messages (#1379)
* Moved actix-http for actix from actix crate

* remove resolver feature

* renamed actix feature to actor

* fixed doc attr for actors, add documentation
2020-02-27 09:34:49 +09:00
71c4bd1b30 Remove uses of Pin::new_unchecked in h1 Dispatcher (#1374)
This removes the last uses of unsafe `Pin` functions in actix-web.

This PR adds a `Pin<Box<_>>` wrapper to `DispatcherState::Upgrade`,
`State::ExpectCall`, and `State::ServiceCall`.

The previous uses of the futures `State::ExpectCall` and `State::ServiceCall`
were Undefined Behavior - a future was obtained from `self.expect.call`
or `self.service.call`, pinned on the stack, and then immediately
returned from `handle_request`. The only alternative to using `Box::pin`
would be to refactor `handle_request` to write the futures directly into
their final location, or avoid polling them before they are returned.

The previous use of `DispatcherState::Upgrade` doesn't seem to be
unsound. However, having data pinned inside an enum that we
`std::mem::replace` would require some careful `unsafe` code to ensure
that we never call `std::mem::replace` when the active variant contains
pinned data. By using `Box::pin`, we any possibility of future
refactoring accidentally introducing undefined behavior.

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-02-26 08:21:05 +09:00
de1d6ad5cb Merge pull request #1344 from actix/replace-unsafe-content-length-helper
Replace unsafe content length helper
2020-02-25 17:02:22 +09:00
2a72e8d119 Merge branch 'master' into replace-unsafe-content-length-helper 2020-02-25 14:30:04 +09:00
2a8e5fdc73 Merge pull request #1370 from mattgathu/feat/helper-function-for-trace-method
Create helper function for HTTP Trace Method
2020-02-25 14:24:09 +09:00
b213c07799 Merge branch 'master' into feat/helper-function-for-trace-method 2020-02-25 12:36:20 +09:00
3d6b8686ad Merge pull request #1373 from JohnTitor/new-codegen
Release `actix-web-codegen` v0.2.1
2020-02-25 09:32:48 +09:00
a4f87a53da Update CHANGES.md 2020-02-25 08:42:39 +09:00
08f172a0aa Merge branch 'master' into new-codegen 2020-02-25 08:29:31 +09:00
7792eaa16e Merge pull request #1378 from JohnTitor/fix-doc
Fix doc comment
2020-02-25 08:29:14 +09:00
845ce3cf34 Fix doc comment 2020-02-25 07:46:03 +09:00
7daef22e24 Merge branch 'master' into new-codegen 2020-02-25 06:58:49 +09:00
1249262c35 Merge pull request #1372 from JohnTitor/time-0.2.7
Update `time` to 0.2.7
2020-02-25 06:58:33 +09:00
94da08f506 increase content-length fast path to responses up to 1MB 2020-02-24 20:58:41 +00:00
d143c44130 Update the ChangeLog 2020-02-23 09:33:28 +01:00
8ec8ccf4fb Create helper function for HTTP Trace Method
Create *route* with `TRACE` method guard.
2020-02-23 09:25:55 +01:00
c8ccc69b93 actix-http: update time to 0.2.7 2020-02-23 07:09:00 +09:00
f9f9fb4c84 actix-http-test: update time to 0.2.7 2020-02-23 07:08:50 +09:00
1b77963aac actix-web: update time to 0.2.7 2020-02-23 07:08:22 +09:00
036ffd43f9 Prepare for new release 2020-02-23 06:40:02 +09:00
bdccccd536 Merge pull request #1368 from mattgathu/add-missing-docs-attr-to-codegen-structs
Add`#[allow(missing_docs)]` attribute to generated structs
2020-02-23 06:26:42 +09:00
060c392c67 Add missing_docs attribute to generated structs 2020-02-22 10:32:12 +01:00
245f96868a impl downcast_ref for MessageBody (#1287)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-02-21 13:31:51 +09:00
b3f1071aaf Merge pull request #1361 from Aaron1011/fix/connector-pool-support
Use #[pin_project] with `ConnectorPoolSupport`
2020-02-21 06:01:26 +09:00
e6811e8818 Use #[pin_project] with ConnectorPoolSupport
This removes a use of `Pin::get_unchecked_mut`
2020-02-19 21:42:53 -05:00
809930d36e Add dependencies to docs example (#1343)
* Add dependencies to docs example

* Change codeblock type to toml

* Clarify the need for actix-rt

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-02-20 05:13:10 +09:00
f266b44cb0 replace unsafe blocks in write_usize helper 2020-02-16 15:20:25 +00:00
31a3515e90 add safe vs unsafe benchmarks 2020-02-16 14:31:06 +00:00
82b2786d6b replace unsafe content length implementation 2020-02-16 14:31:05 +00:00
6ab7cfa2be Remove descriptions about undefined uds feature from docs (#1356) 2020-02-16 04:18:31 +09:00
9b3f7248a8 Merge pull request #1354 from actix/JohnTitor-patch-1
Disable coverage for PRs
2020-02-14 08:18:09 +09:00
a1835d6510 Disable coverage for PRs 2020-02-14 07:31:29 +09:00
4484b3f66e Merge pull request #1347 from actix/bye-travis
Use Actions fully
2020-02-12 05:54:32 +09:00
cde3ae5f61 Remove Travis config 2020-02-08 05:36:11 +09:00
7d40b66300 Add some Actions workflows 2020-02-08 04:28:34 +09:00
63730c1f73 Merge pull request #1345 from JohnTitor/fix-warnings
Fix warnings
2020-02-08 04:17:04 +09:00
53ff3ad099 More ignore test causes timeout 2020-02-08 02:20:01 +09:00
6406f56ca2 Fix/suppress warnings 2020-02-08 02:20:01 +09:00
728b944360 Extensions module improvement and tests. (#1297)
* replace get.is_some to contains_key

* Add tests

* remove unnecessary box cast

* fix missing uints

* asserts fix

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-02-07 16:08:25 +09:00
3851a377df Fix minor grammatical errors (#1341) 2020-02-07 03:00:22 +09:00
fe13789345 Use Pin<Box<S>> in BodyStream and SizedStream (#1328)
Fixes #1321

A better fix would be to change `MessageBody` to take a `Pin<&mut
Self>`, rather than a `Pin<&mut Self>`. This will avoid requiring the
use of `Box` for all consumers by allowing the caller to determine how
to pin the `MessageBody` implementation (e.g. via stack pinning).

However, doing so is a breaking change that will affect every user of
`MessageBody`. By pinning the inner stream ourselves, we can fix the
undefined behavior without breaking the API.

I've included @sebzim4500's reproduction case as a new test case.
However, due to the nature of undefined behavior, this could pass (and
not segfault) even if underlying issue were to regress.

Unfortunately, until rust-lang/unsafe-code-guidelines#148 is resolved,
it's not even possible to write a Miri test that will pass when the bug
is fixed.

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-31 09:39:34 +09:00
3033f187d2 Enforce safety of downcast_ref at compile time. (#1326)
* Enforce safety of `downcast_ref` at compile time.

The safety of `downcast_ref` requires that `__private_get_type_id__` not
be overriden by callers, since the returned `TypeId` is used to check if
the cast is safe. However, all trait methods in Rust are public, so
users can override `__private_get_type_id__` despite it being
`#[doc(hidden)]`.

This commit makes `__private_get_type_id__` return a type with a private
constructor, ensuring that the only possible implementation is the
default implementation. A more detailed explanation is provided in the
comments added to the file.

Note that the standard library was affected by this type of issue with
the `Error::type_id` function: see https://blog.rust-lang.org/2019/05/14/Rust-1.34.2.html#whats-in-1.34.2-stable

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-30 23:43:35 +09:00
276a5a3ee4 Replace UnsafeCell with Cell in DateServiceInner (#1325)
* Replace `UnsafeCell` with `Cell` in `DateServiceInner`

This ensures that it's impossible to cause undefined behavior by
accidentally violating Rust's aliasing rules (e.g. passing a closure to
`set_date` which ends up invoking `reset` or `update` on the inner
`DateServiceInner`).

There might be a tiny amount of overhead from copying the `Option<(Date,
Instant)>` rather than taking a reference, but it shouldn't be
measurable.

Since the wrapped type is `Copy`, a `Cell` can be used, avoiding the
runtime overhead of a `RefCell`.

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-29 21:05:08 +09:00
664f9a8b2d Long lasting auto-prolonged session (#1292)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-29 10:26:39 +09:00
c73c2dc12c Don't use cache in Windows CI (#1327) 2020-01-29 09:00:04 +09:00
e634e64847 Upgrade time to 0.2.5 (#1254)
* Use `OffsetDateTime` instead of `PrimitiveDateTime`

* Parse time strings with `PrimitiveDateTime::parse` instead of `OffsetDateTime::parse`

* Remove unused `time` dependency from actix-multipart

* Fix a few errors with time related tests from the `time` upgrade

* Implement logic to convert a RFC 850 two-digit year into a full length year, and organize time parsing related functions

* Upgrade `time` to 0.2.2

* Correctly parse C's asctime time format using time 0.2's new format patterns

* Update CHANGES.md

* Use `time` without any of its deprecated functions

* Enforce a UTC time offset when converting an `OffsetDateTime` into a Header value

* Use the more readable version of `Duration::seconds(0)`, `Duration::zero()`

* Remove unneeded conversion of time::Duration to std::time::Duration

* Use `OffsetDateTime::as_seconds_f64` instead of manually calculating the amount of seconds from nanoseconds

* Replace a few additional instances of `Duration::seconds(0)` with `Duration::zero()`

* Truncate any nanoseconds from a supplied `Duration` within `Cookie::set_max_age` to ensure two Cookies with the same amount whole seconds equate to one another

* Fix the actix-http:🍪:do_not_panic_on_large_max_ages test

* Convert `Cookie::max_age` and `Cookie::expires` examples to `time` 0.2

Mainly minor  changes. Type inference can be used alongside the new
`time::parse` method, such that the type doesn't need to be specified.
This will be useful if a refactoring takes place that changes the type.
There are also new macros, which are used where possible.

One change that is not immediately obvious, in `HttpDate`, there was an
unnecessary conditional. As the time crate allows for negative durations
(and can perform arithmetic with such), the if/else can be removed
entirely.

Time v0.2.3 also has some bug fixes, which is why I am not using a more
general v0.2 in Cargo.toml.

v0.2.3 has been yanked, as it was backwards imcompatible. This version
reverts the breaking change, while still supporting rustc back to
1.34.0.

* Add missing `time::offset` macro import

* Fix type confusion when using `time::parse` followed by `using_offset`

* Update `time` to 0.2.5

* Update CHANGES.md

Co-authored-by: Jacob Pratt <the.z.cuber@gmail.com>
2020-01-28 20:44:22 +09:00
cdba30d45f Skip empty chucks for BodyStream and SizedStream (#1308)
* Skip empty chucks for BodyStream and SizedStream when streaming response (#1267)

* Fix tests to fail on previous implementation

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-28 18:28:09 +09:00
74dcc7366d Remove several uses of Pin::new_unchecked (#1294)
Most of the relevant struct already had a `#[pin_project]` attribute,
but it wasn't being used.

The remaining uses of `Pin::new_unchecked` all involve going from a
`&mut T` to a `Pin<&mut T>`, without directly observing a `Pin<&mut T>`
first. As such, they cannot be replaced by `pin_project`

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-28 12:35:51 +09:00
d137a8635b Replace Pin::new_unchecked with #[pin_project] in tuple_from_req! (#1293)
Using some module trickery, we can generate a tuple struct for each
invocation of the macro. This allows us to use `pin_project` to project
through to the tuple fields, removing the need to use
`Pin::new_unchecked`

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-28 10:45:25 +09:00
a2d4ff157e Update call_service documentation (#1302)
Co-authored-by: Christian Battaglia <christian.d.battaglia@gmail.com>
2020-01-28 08:09:46 +09:00
71d11644a7 Add ability to name a handler function as 'config' (#1290)
* eliminate handler naming restrictions #1277

* Update actix-web-codegen/CHANGES.md

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-26 07:22:40 +09:00
8888520d83 Add benchmark for full stack request lifecycle (#1298)
* add benchmark for full stack request lifecycle

* add direct service benchmarks

* fix newline

* add cloneable service benchmarks

* remove cloneable bench experiments + cargo fmt

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-25 08:05:25 +09:00
cf3577550c Tweak caches (#1319)
* Try to use `cargo-cache`

* Tweak issue template
2020-01-25 02:27:13 +09:00
58844874a0 Fixing #1295 convert UnsafeCell to RefCell in CloneableService (#1303)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-24 14:51:38 +09:00
78f24dda03 Initial Issue template (#1311)
* Initial Issue template

* First round of changes for the bug report

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-24 07:32:34 +09:00
e17b3accb9 Remove codecoverage for tests and examples (#1299)
* Ignore Tests & Examples for CodeCoverage

Ignore Tests & Examples for CodeCoverage
2020-01-24 05:10:02 +09:00
c6fa007e72 Fix vcpkg cache (#1312) 2020-01-23 11:27:34 +09:00
a3287948d1 allow explicit SameSite=None cookies (#1282)
fixes #1035
2020-01-23 10:08:23 +09:00
2e9ab0625e Tweak actions (#1305)
* Add benchmark action

* Fix Windows build
2020-01-23 06:23:53 +09:00
3a5b62b550 Add dependencies instruction (#1281) 2020-01-16 23:17:17 +09:00
412e54ce10 Fixed documentation typo for actix-files (#1278) 2020-01-15 11:09:58 -08:00
bca41f8d40 Changes to Cors builder (#1266)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-11 04:53:17 +09:00
7c974ee668 Update doc comment for HttpRequest::app_data (#1265)
* update doc comment for `HttpRequest::app_data`

* add `no_run` to doc comment

* add `ignore` to doc comment

* Update src/request.rs

Co-Authored-By: Yuki Okushi <huyuumi.dev@gmail.com>

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-11 03:55:20 +09:00
abb462ef85 Replace sha1 dependency with sha-1 (#1258)
* Replace sha1 dependency with sha-1

This other crate is being maintained, and it offers better performances
when using the `asm` feature (especially [on
AArch64](https://github.com/RustCrypto/hashes/pull/97)).

* Update CHANGES.md with the sha-1 migration

* Add a test for hash_key()
2020-01-11 02:34:31 +09:00
e66312b664 add extra constraints 2020-01-10 11:36:59 +06:00
39f4b2b39e Merge branch 'master' of github.com:actix/actix-web 2020-01-10 11:28:58 +06:00
f6ff056b8a Fix panic with already borrowed: BorrowMutError #1263 2020-01-10 11:26:54 +06:00
51ab4fb73d Tweak actions to use cache and not to be stuck on the way (#1264) 2020-01-10 03:30:45 +09:00
f5fd6bc49f Fix actix-http examples (#1259)
Fix actix-http examples
2020-01-07 00:15:04 +09:00
2803fcbe22 Small grammaritical update to lib.rs (#1248) 2020-01-03 08:45:17 +06:00
67793c5d92 add ssl feature migration 2019-12-30 21:22:04 +06:00
bcb5086c91 Added 2.0.0 rustls feature name change (#1244) 2019-12-30 21:16:04 +06:00
7bd2270290 Fix link to example in readme.md (#1236)
* Fix link to example in readme.md

* Add links to openssl and rustls examples

* Rustls should be uppercase
2019-12-26 19:42:07 +09:00
a4ad5e6b69 update timeouts for test server 2019-12-25 20:52:20 +04:00
6db909a3e7 update migration 2019-12-25 20:27:30 +04:00
642ae161c0 prep actix-web release 2019-12-25 20:21:00 +04:00
7b3c99b933 prep actix-framed release 2019-12-25 20:17:22 +04:00
f86ce0390e allow to specify multi pattern for resources 2019-12-25 20:14:44 +04:00
7882f545e5 Allow to gracefully stop test server via TestServer::stop() 2019-12-25 12:10:48 +04:00
1c75e6876b update migration 2019-12-22 17:16:07 +04:00
6a0cd2dced Rename HttpServer::start() to HttpServer::run() 2019-12-22 17:12:22 +04:00
c7f3915779 update actix-service dep 2019-12-22 16:39:25 +04:00
f45db1f909 Enable GitHub Actions and fix file URL behavior (#1232)
* Use GitHub Actions

* Fix unused imports on Windows

* Fix test for Windows

* Stop to run CI for i686-pc-windows-msvc for now

* Use `/` instead of `\` on Windows

* Add entry to changelog

* Prepare actix-files release
2019-12-22 16:43:41 +09:00
3751a4018e fixed test::init_service api docs (missing await) (#1230) 2019-12-21 08:47:18 +06:00
0cb1b0642f add test server data test 2019-12-20 23:18:59 +06:00
48476362a3 update changes 2019-12-20 17:59:34 +06:00
2b4256baab add links to configs 2019-12-20 17:49:05 +06:00
e5a50f423d Make web::Data deref to Arc<T> #1214 2019-12-20 17:45:35 +06:00
8b8a9a995d bump ver 2019-12-20 17:36:48 +06:00
74fa4060c2 fix awc tests 2019-12-20 17:27:32 +06:00
c877840c07 rename App::register_data to App::app_data and HttpRequest::app_data returns Option<&T> instead of Option<&Data<T>> 2019-12-20 17:13:09 +06:00
20248daeda Allow to set peer_addr for TestRequest #1074 2019-12-20 16:11:51 +06:00
a08d8dab70 AppConfig::secure() is always false. #1202 2019-12-20 16:04:51 +06:00
fbbb4a86e9 feat: add access to the session also from immutable references (#1225) 2019-12-20 13:59:07 +06:00
1d12ba9d5f Replace brotli with brotli2 #1224 2019-12-20 13:50:07 +06:00
8c54054844 Use .advance() intead of .split_to() 2019-12-19 09:56:14 +06:00
1732ae8c79 fix Bodyencoding trait usage 2019-12-18 09:30:14 +06:00
3b860ebdc7 Fix poll_ready call for WebSockets upgrade (#1219)
* Fix poll_ready call for WebSockets upgrade

* Poll upgrade service from H1ServiceHandler too
2019-12-17 13:34:25 +06:00
29ac6463e1 Merge branch 'master' of github.com:actix/actix-web 2019-12-16 17:22:49 +06:00
01613f334b Move BodyEncoding to dev module #1220 2019-12-16 17:22:26 +06:00
30dcaf9da0 fix deprecated Error::description (#1218) 2019-12-16 07:43:19 +06:00
b0aa9395da prep actix-web alpha.6 release 2019-12-15 22:51:14 +06:00
a153374b61 migrate actix-web-actors 2019-12-15 22:45:38 +06:00
a791aab418 prep awc release 2019-12-15 13:36:05 +06:00
cb705317b8 compile with default-features off 2019-12-15 13:28:54 +06:00
e8e0f98f96 fix docs.rs features list 2019-12-13 12:41:48 +06:00
c878f66d05 fix docs.rs features list 2019-12-13 12:40:22 +06:00
fac6dec3c9 update deps 2019-12-13 12:36:15 +06:00
232f71b3b5 update changes 2019-12-13 12:18:30 +06:00
8881c13e60 update changes 2019-12-13 12:16:43 +06:00
d006a7b31f update changes 2019-12-13 12:10:45 +06:00
3d64d565d9 fix warnings 2019-12-13 11:46:02 +06:00
c1deaaeb2f cleanup imports 2019-12-13 11:24:57 +06:00
b81417c2fa fix warnings 2019-12-13 10:59:02 +06:00
4937c9f9c2 refactor http-test server 2019-12-12 23:08:38 +06:00
db1d6b7963 refactor test server impl 2019-12-12 22:28:47 +06:00
fa07415721 Replace flate2-xxx features with compress 2019-12-12 15:08:08 +06:00
b4b3350b3e Add websockets continuation frame support 2019-12-12 14:06:54 +06:00
4a1695f719 fixes missing import in example (#1210) 2019-12-12 07:06:22 +06:00
1b8d747937 Fix extra line feed (#1209) 2019-12-12 07:05:39 +06:00
a43a005f59 Log path if it is not a directory (#1208) 2019-12-12 07:04:53 +06:00
a612b74aeb actix-multipart: Fix multipart boundary reading (#1205)
* actix-multipart: Fix multipart boundary reading

If we're not ready to read the first line after the multipart field
(which should be a "\r\n" line) then return Pending instead of Ready(None)
so that we will get called again to read that line.

Without this I was getting MultipartError::Boundary from read_boundary()
because it got the "\r\n" line instead of the boundary.

Also tweaks the test_stream test to test partial reads.

This is a forward port of #1189 from 1.0

* actix-multipart: Update changes for boundary fix
2019-12-12 07:03:44 +06:00
131c897099 upgrade to actix-net release 2019-12-11 19:20:20 +06:00
ef3a33b9d6 use std mutext instead of parking_lot 2019-12-10 09:00:51 +06:00
5132257b0d Fix buffer remaining capacity calcualtion 2019-12-09 21:55:22 +06:00
0c1f5f9edc Check Upgrade service readiness before calling it 2019-12-09 17:40:15 +06:00
e4382e4fc1 Fix broken docs (#1204)
Fixed un escaped brackets in lib.rs, and reflowed links to ConnectionInfo in app, config, and server.rs
2019-12-09 10:02:43 +06:00
a3ce371312 ws ping and pong uses bytes #1049 2019-12-09 07:01:22 +06:00
42258ee289 deps 2019-12-08 20:22:39 +06:00
b92eafb839 prepare actix-http release 2019-12-08 20:15:51 +06:00
3b2e78db47 add link to chat 2019-12-08 19:27:06 +06:00
63da1a5560 Merge branch 'master' of github.com:actix/actix-web 2019-12-08 19:26:12 +06:00
1f3ffe38e8 update actix-service dep 2019-12-08 19:25:24 +06:00
c23b6b3879 Merge pull request #1192 from krircc/master
Add rich project metadata
2019-12-08 16:03:39 +08:00
909c7c8b5b Merge branch 'master' into master 2019-12-08 16:26:35 +09:00
4a8a9ef405 update tests and clippy warnings 2019-12-08 12:31:16 +06:00
6c9f9fff73 clippy warnings 2019-12-08 00:46:51 +06:00
8df33f7a81 remove HttpServer::run() as it is not useful with async/await 2019-12-08 00:06:04 +06:00
7ec5ca88a1 update changes 2019-12-07 22:01:55 +06:00
e5f3d88a4e Switch brotli compressor to rust. (#1197)
* Switch to a rustified version of brotli.

* Some memory optimizations.

* Make brotli not optional anymore.
2019-12-07 21:55:41 +06:00
0ba125444a Add impl ResponseBuilder for Error 2019-12-07 21:41:34 +06:00
6c226e47bd prepare actix-web-actors release 2019-12-07 20:10:36 +06:00
8c3f58db9d Allow comma-separated websocket subprotocols without spaces (#1172)
* Allow comma-separated websocket subprotocols without spaces

* [CHANGES] Added an entry to CHANGES.md
2019-12-07 20:08:06 +06:00
4921243add Fix rustls build. (#1195) 2019-12-07 16:14:09 +06:00
91b3fcf85c Fix dependency features. (#1196) 2019-12-07 16:13:26 +06:00
1729a52f8b prepare alpha.3 release 2019-12-07 13:00:03 +06:00
ed2f3fe80d use actix-net alpha.3 release 2019-12-07 12:28:26 +06:00
f2ba389496 Merge branch 'master' into master 2019-12-06 16:57:42 +09:00
439f02b6b1 Update README.md 2019-12-06 14:59:11 +08:00
e32da08a26 Update README.md 2019-12-06 14:34:14 +08:00
82110e0927 Update README.md 2019-12-06 14:29:10 +08:00
7b3354a9ad Update README.md 2019-12-06 14:26:23 +08:00
5243e8baca Update README.md 2019-12-06 14:23:28 +08:00
98903028c7 Update README.md 2019-12-06 14:22:29 +08:00
7dd676439c update changes for actix-session 2019-12-06 11:24:25 +06:00
fbead137f0 feat: add access to UserSession from RequestHead (#1164)
* feat: add access to UserSession from RequestHead

* add test case for session from RequestHead and changes entry for the new feature
2019-12-06 11:21:43 +06:00
205a964d8f upgrade to tokio 0.2 2019-12-05 23:35:43 +06:00
b45c6cd66b replace hashbrown with std hashmap 2019-12-04 18:33:43 +06:00
0015a204aa update version 2019-12-03 19:03:53 +06:00
c7ed6d3428 update version 2019-12-03 16:35:31 +06:00
cf30eafb49 update md 2019-12-03 00:49:12 +06:00
14075ebf7f use released versions of actix-net 2019-12-02 23:33:39 +06:00
068f047dd5 update service factory config 2019-12-02 21:37:13 +06:00
f4c01384ec update to latest actix-net 2019-12-02 17:33:11 +06:00
b7d44d6c4c Merge pull request #1 from actix/master
git pull
2019-12-01 16:56:42 +08:00
33574403b5 Remove rustls from package.metadata.docs.rs (#1182) 2019-11-28 06:25:21 +06:00
dcc6efa3e6 Merge branch 'master' of github.com:actix/actix-web 2019-11-27 21:08:13 +06:00
56b9f11c98 disable rustls 2019-11-27 21:07:49 +06:00
f43a706364 Set name for each generated resource 2019-11-26 19:25:28 +06:00
f2b3dc5625 update examples 2019-11-26 17:16:33 +06:00
f73f97353b refactor ResponseError trait 2019-11-26 16:07:39 +06:00
4dc31aac93 use actix_rt::test for test setup 2019-11-26 11:25:50 +06:00
c1c44a7dd6 upgrade derive_more 2019-11-25 17:59:14 +06:00
c5907747ad Remove implementation of Responder for (). Fixes #1108.
Rationale:

- In Rust, one can omit a semicolon after a function's final expression to make
  its value the function's return value. It's common for people to include a
  semicolon after the last expression by mistake - common enough that the Rust
  compiler suggests removing the semicolon when there's a type mismatch between
  the function's signature and body. By implementing Responder for (), Actix makes
  this common mistake a silent error in handler functions.

- Functions returning an empty body should return HTTP status 204 ("No Content"),
  so the current Responder impl for (), which returns status 200 ("OK"), is not
  really what one wants anyway.

- It's not much of a burden to ask handlers to explicitly return
  `HttpResponse::Ok()` if that is what they want; all the examples in the
  documentation do this already.
2019-11-23 21:10:02 +06:00
525c22de15 fix typos from updating to futures 0.3 2019-11-22 13:25:55 +06:00
57981ca04a update tests to async handlers 2019-11-22 11:49:35 +06:00
e668acc596 update travis config 2019-11-22 10:13:32 +06:00
512dd2be63 disable rustls support 2019-11-22 07:01:05 +06:00
8683ba8bb0 rename .to_async() to .to() 2019-11-21 21:36:35 +06:00
0b9e3d381b add test with custom connector 2019-11-21 17:36:18 +06:00
1f0577f8d5 cleanup api doc examples 2019-11-21 16:02:17 +06:00
53c5151692 use response instead of result for asyn c handlers 2019-11-21 16:02:17 +06:00
55698f2524 migrade rest of middlewares 2019-11-21 16:02:17 +06:00
471f82f0e0 migrate actix-multipart 2019-11-21 16:02:17 +06:00
60ada97b3d migrate actix-session 2019-11-21 16:02:17 +06:00
0de101bc4d update actix-web-codegen tests 2019-11-21 16:02:17 +06:00
95e2a0ef2e migrate actix-framed 2019-11-21 16:02:17 +06:00
69cadcdedb migrate actix-files 2019-11-21 16:02:17 +06:00
6ac4ac66b9 migrate actix-cors 2019-11-21 16:02:17 +06:00
3646725cf6 migrate actix-identity 2019-11-21 16:02:17 +06:00
ff62facc0d disable unmigrated crates 2019-11-21 16:02:17 +06:00
b510527a9f update awc tests 2019-11-21 16:02:17 +06:00
3127dd4db6 migrate actix-web to std::future 2019-11-21 16:02:17 +06:00
d081e57316 fix h2 client send body 2019-11-21 16:02:17 +06:00
1ffa7d18d3 drop unpin constraint 2019-11-21 16:02:17 +06:00
687884fb94 update test-server tests 2019-11-21 16:02:17 +06:00
5ab29b2e62 migrate awc and test-server to std::future 2019-11-21 16:02:17 +06:00
a6a2d2f444 update ssl impls 2019-11-21 16:02:17 +06:00
9e95efcc16 migrate client to std::future 2019-11-21 16:02:17 +06:00
8cba1170e6 make actix-http compile with std::future 2019-11-21 16:02:17 +06:00
5cb2d500d1 update actix-web-actors 2019-11-14 08:58:24 +06:00
0212c618c6 prepare actix-web release 2019-11-14 08:55:37 +06:00
88110ed268 Add security note to ConnectionInfo::remote() (#1158) 2019-11-14 08:32:47 +06:00
fba02fdd8c prep awc release 2019-11-06 11:33:25 -08:00
b2934ad8d2 prep actix-file release 2019-11-06 11:25:26 -08:00
f7f410d033 fix test order dep 2019-11-06 11:20:47 -08:00
885ff7396e prepare actox-http release 2019-11-06 10:35:13 -08:00
61b38e8d0d Increase timeouts in test-server (#1153) 2019-11-06 06:09:22 -08:00
edcde67076 Fix escaping/encoding problems in Content-Disposition header (#1151)
* Fix filename encoding in Content-Disposition of acitx_files::NamedFile

* Add more comments on how to use Content-Disposition header properly & Fix some trivial problems

* Improve Content-Disposition filename(*) parameters of actix_files::NamedFile

* Tweak Content-Disposition parse to accept empty param value in quoted-string

* Fix typos in comments in .../content_disposition.rs (pointed out by @JohnTitor)

* Update CHANGES.md

* Update CHANGES.md again
2019-11-06 06:08:37 -08:00
f0612f7570 awc: Add support for setting query from Serialize type for client request (#1130)
Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2019-10-26 08:27:14 +03:00
ace98e3a1e support Host guards when Host header is unset (#1129) 2019-10-15 05:05:54 +06:00
1ca9d87f0a prep actix-web-codegen release 2019-10-14 21:35:53 +06:00
967f965405 Update syn & quote to 1.0 (#1133)
* chore(actix-web-codegen): Upgrade syn and quote to 1.0

* feat(actix-web-codegen): Generate better error message

* doc(actix-web-codegen): Update CHANGES.md

* fix: Build with stable rust
2019-10-14 21:34:17 +06:00
062e51e8ce prep actix-file release 2019-10-14 21:26:26 +06:00
a48e616def feat(files): add possibility to redirect to slash-ended path (#1134)
When accessing to a folder without a final slash, the index file will be loaded ok, but if it has
references (like a css or an image in an html file) these resources won't be loaded correctly if
they are using relative paths. In order to solve this, this PR adds the possibility to detect
folders without a final slash and make a 302 redirect to mitigate this issue. The behavior is off by
default. We're adding a new method called `redirect_to_slash_directory` which can be used to enable
this behavior.
2019-10-14 21:23:15 +06:00
effa96f5e4 Removed httpcode 'MovedPermanenty'. (#1128) 2019-10-12 06:45:12 +06:00
cc0b4be5b7 Fix typo in response.rs body() comment (#1126)
Fixes https://github.com/actix/actix-web/issues/1125
2019-10-09 19:11:55 +06:00
a464ffc23d prepare actix-files release 2019-10-08 10:13:16 +06:00
4de2e8a898 [actix-files] Allow user defined guards for NamedFile (actix#1113) (#1115)
* [actix-files] remove request method checks from NamedFile

* [actix-files] added custom guard checks to FilesService

* [actix-files] modify method check tests (NamedFile -> Files)

* [actix-files] add test for custom guards in Files

* [actix-files] update changelog
2019-10-08 10:09:40 +06:00
0f09415469 Convert documentation examples to Rust 2018 edition (#1120)
* Convert types::query examples to rust-2018 edition

* Convert types::json examples to rust-2018 edition

* Convert types::path examples to rust-2018 edition

* Convert types::form examples to rust-2018 edition

* Convert rest of the examples to rust-2018 edition.
2019-10-07 11:29:11 +06:00
f089cf185b Let ResponseError render w/ 'text/plain; charset=utf-8' header (#1118) (#1119)
* Let ResponseError render w/ 'text/plain; charset=utf-8' header (#1118)

Trait ResponseError originally render Error messages with header
`text/plain` , which causes browsers (i.e. Firefox 70.0) with
Non-English locale unable to render UTF-8 responses with non-English
characters correctly. i.e. emoji.

This fix solved this problem by specifying the charset of `text/plain`
as utf-8, which is the default charset in rust.

Before actix-web consider to support other charsets, this hotfix is
 enough.

Test case:

fn test() -> Result<String, actix_web::Error> {
    Err(actix_web::error::ErrorForbidden("😋test"))
}

* Update actix-http/CHANGES.md for #1118
2019-10-07 10:56:24 +06:00
15d3c1ae81 Update docs of guard.rs (#1116)
* Update guard.rs
2019-10-07 12:05:17 +09:00
fba31d4e0a Expose ContentDisposition in actix-multipart to fix broken doc link (#1114)
* Expose ContentDisposition in actix-multipart to fix broken doc link

* Revert "Expose ContentDisposition in actix-multipart to fix broken doc link"

This reverts commit e90d71d16c.

* Unhide actix-http::header::common docs

These types are used in other exported documented interfaces and create
broken links if not documented.
See `actix_multipart::Field.content_disposition`
2019-10-02 09:48:25 +06:00
f81ae37677 Add From<Payload> for crate::dev::Payload (#1110)
* Add From<Payload> for crate::dev::Payload

* Make dev::Payload field of Payload public and add into_inner method

* Add changelog entry
2019-10-01 14:05:38 +06:00
5169d306ae update ConnectionInfo.remote() doc string 2019-09-27 07:03:12 +06:00
4f3e97fff8 prepare actix-web release 2019-09-25 15:39:09 +06:00
3ff01a9fc4 Add changelog entry for #1101 (#1102) 2019-09-25 15:35:28 +06:00
3d4e45a0e5 prepare release 2019-09-25 15:30:20 +06:00
c659c33919 Feature uds: Add listen_uds to ServerBuilder (#1085)
Allows using an existing Unix Listener instead of binding to a path.
Useful for when running as a daemon under systemd.

Change-Id: I54a0e78c321d8b7a9ded381083217af590e9a7fa
2019-09-25 15:16:51 +06:00
959f7754b2 Merge pull request #1101 from actix/add-awc-get-head-methods
Add remaining getter methods from private head field
2019-09-25 10:23:23 +02:00
23f04c4f38 Add remaining getter methods from private head field 2019-09-25 08:50:45 +02:00
d9af8f66ba Use actix-testing for testing utils 2019-09-25 10:28:41 +06:00
aa39b8ca6f Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() (#1096)
* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body()

* Update actix-http/CHANGES.md
2019-09-25 09:33:52 +06:00
58c7065f08 Implement register_data method on Resource and Scope. (#1094)
* Implement `register_data` method on `Resource` and `Scope`.

* Split Scope::register_data tests out from Scope::data tests.

* CHANGES.md: Mention {Scope,Resource}::register_data.
2019-09-18 06:36:39 +06:00
b3783b403e Merge branch 'master' of github.com:actix/actix-web 2019-09-17 21:46:45 +06:00
e4503046de Do not override current System 2019-09-17 21:45:06 +06:00
32a1c36597 Make UrlencodedError::Overflow more informative (#1089) 2019-09-17 06:58:04 +06:00
7c9f9afc46 Add ability to use Infallible as HttpResponse error type (#1093)
* Add `std::convert::Infallible` implementantion for `ResponseError`

* Add from `std::convert::Infallible` to `Error`

* Remove `ResponseError` implementantion for `Infallible`

* Remove useless docs

* Better comment

* Update changelog

* Update actix_http::changelog
2019-09-17 06:57:38 +06:00
c1f99e0775 Remove mem::uninitialized() (#1090) 2019-09-16 07:52:23 +09:00
a32573bb58 Allow to re-construct ServiceRequest from HttpRequest and Payload #1088 2019-09-13 11:56:24 +06:00
e35d930ef9 prepare releases 2019-09-12 21:58:08 +06:00
60b7aebd0a fmt & clippy 2019-09-12 21:52:46 +06:00
45d2fd4299 export frozen request related types; refactor code layout 2019-09-12 10:40:56 +06:00
71f8577713 prepare awc release 2019-09-11 20:13:28 +06:00
043f763c51 prepare actix-http release 2019-09-11 20:07:39 +06:00
8873e9b39e Added FrozenClientRequest for easier retrying HTTP calls (#1064)
* Initial commit

* Added extra_headers

* Added freeze() method to ClientRequest which produces a 'read-only' copy of a request suitable for retrying the send operation

* Additional methods for FrozenClientRequest

* Fix

* Increased crates versions

* Fixed a unit test. Added one more unit test.

* Added RequestHeaderWrapper

* Small fixes

* Renamed RequestHeadWrapper->RequestHeadType

* Updated CHANGES.md files

* Small fix

* Small changes

* Removed *_extra methods from Connection trait

* Added FrozenSendBuilder

* Added FrozenSendBuilder

* Minor fix

* Replaced impl Future with concrete Future implementation

* Small renaming

* Renamed Send->SendBody
2019-09-10 10:29:32 +06:00
5e8f1c338c fix h2 not using error response (#1080)
* fix h2 not using error response

* add fix change log

* fix h2 service error tests
2019-09-09 16:24:57 +06:00
1d96ae9bc3 actix-multipart: Correctly parse multipart body which does not end in CRLF (#1042)
* Correctly parse multipart body which does not end in CRLF

* Add in an eof guard for extra safety
2019-09-09 13:58:00 +06:00
8d61fe0925 Ensure that awc::ws::WebsocketsRequest sets the Host header (#1070)
* Ensure that awc::ws::WebsocketsRequest sets the Host header before connecting.

* Make sure to check if headers already have a HOST value before setting

* Update CHANGES.md to reflect WebSocket client update.
2019-09-09 12:27:13 +06:00
8a9fcddb3c Condition middleware (#1075)
* add condition middleware

* write tests

* update changes

* Update src/middleware/condition.rs

Co-Authored-By: Yuki Okushi <huyuumi.dev@gmail.com>

* Update src/middleware/condition.rs

Co-Authored-By: Yuki Okushi <huyuumi.dev@gmail.com>

* Update src/middleware/condition.rs

Co-Authored-By: Yuki Okushi <huyuumi.dev@gmail.com>

* Update src/middleware/condition.rs

Co-Authored-By: Yuki Okushi <huyuumi.dev@gmail.com>
2019-09-09 12:26:38 +06:00
c9400456f6 update actix-connect ver 2019-09-02 15:20:28 -07:00
63ddd30ee4 on_connect result isnt added to request extensions for http2 requests #1009 2019-09-01 13:15:02 +06:00
bae29897d6 prep actix-web release 2019-08-29 09:36:16 +06:00
616981ecf9 clear extensions before reclaiming HttpRequests in their pool (#1063)
Issue #1062
2019-08-29 09:35:05 +06:00
98bf8ab098 enable rust-tls feature for actix_web::client #1045 2019-08-28 21:40:24 +06:00
c193137905 actix_web::test::TestRequest::set_form (#1058) 2019-08-28 21:32:17 +06:00
a07cdd6533 Data::into_inner 2019-08-27 17:25:25 +01:00
61e492e7e3 Prepare actix-multipart 0.1.3 release 2019-08-18 10:39:22 +09:00
23d768a77b Add explicit dyns (#1041)
* Add explicit `dyn`s

* Remove unnecessary lines
2019-08-17 02:45:44 +09:00
87b7162473 chore(readme): fix copy paste error (#1040)
Fix actix-cors README
2019-08-16 09:21:30 +09:00
979c4d44f4 update awc dep 2019-08-13 12:41:26 -07:00
5d248cad89 prep release 2019-08-13 12:28:05 -07:00
b1cb72d088 update url crate 2019-08-13 11:03:24 -07:00
55179d6ab2 update dependencies 2019-08-13 10:48:11 -07:00
192dfff680 prepare actix-http 0.2.9 release 2019-08-13 15:20:29 +02:00
915010e733 Fixes a bug in OpenWaitingConnection where the h2 flow would panic a future (#1031) 2019-08-13 14:55:04 +02:00
dbe4c9ffb5 Replace deprecated methods in actix_files (#1027)
* Bump up mime_guess to 2.0.1

* Replace deprecated methods

* Update CHANGE.md
2019-08-12 05:43:29 +09:00
0ee69671ba Update nightly to 2019-08-10 (#1028) 2019-08-12 04:00:13 +09:00
80e1d16ab8 Merge pull request #1023 from lukaslueg/byteorder_removed
Remove byteorder-dependency
2019-08-07 12:28:23 -03:00
b70de5b991 Update CHANGES.md 2019-08-07 16:43:03 +02:00
0b9e692298 Remove byteorder-dependency 2019-08-06 18:32:36 +02:00
cf1a60cb3a prepare awc release 2019-08-01 15:41:14 -07:00
0d15861e23 prepare actix-http release 2019-08-01 15:26:30 -07:00
cb19ebfe0c add rustls support for actix-http and awc (#998)
* add rustls support for actix-http and awc

* fix features conflict

* remove unnecessary duplication

* test server with rust-tls

* fix

* test rustls

* awc rustls test

* format

* tests

* fix dependencies

* fixes and add changes

* remove test-server and Cargo.toml dev-dependencies changes

* cargo fmt
2019-07-31 13:02:56 -07:00
0d9ea41047 update min rust version 2019-07-31 06:49:46 -07:00
e9b4aa205f Merge branch 'master' of github.com:actix/actix-web 2019-07-30 08:00:57 -07:00
7674f1173c fix awc client panic #1016 2019-07-30 08:00:46 -07:00
511026cab0 Allow HeaderMap to be cloned (#1014)
* Allow HeaderMap to be cloned

* Add entry to changelog
2019-07-29 08:11:23 +04:00
81ab37f235 Fix two dyn warnings (#1015) 2019-07-29 08:10:33 +04:00
6f2049ba9b Fix typo 2019-07-25 12:54:59 +01:00
52372fcbea actix-files: "Specified path is not a directory" error now includes the path (#1004) 2019-07-23 06:41:58 +06:00
f3751d83f8 Modify response body only if encoder is not None #997 2019-07-22 11:35:00 +06:00
b0b462581b update CHANGES.md for Form impl Responder 2019-07-20 14:46:46 +01:00
8f48ed2597 impl Responder for Form 2019-07-20 14:46:46 +01:00
c96068e78d bump version 2019-07-20 11:46:21 +06:00
7bca1f7d8d Allow to disable Content-Disposition header #686 2019-07-20 11:43:49 +06:00
3618a84164 update changes 2019-07-20 11:27:21 +06:00
03ca408e94 add support for specifying protocols on websocket handshake (#835)
* add support for specifying protocols on websocket handshake

* separated the handshake function with and without protocols
changed protocols type from Vec<&str> to [&str]
2019-07-20 11:22:06 +06:00
e53e9c8ba3 Add the start_with_addr() function, to obtain an addr to the target websocket actor (#988) 2019-07-20 11:17:58 +06:00
941241c5f0 Remove unneeded actix-utils dependency 2019-07-20 10:50:36 +06:00
f8320fedd8 add note about Query decoding (#992) 2019-07-19 17:37:49 +06:00
c808364c07 make Query payload public (#991) 2019-07-19 15:47:44 +06:00
cccd829656 update changes 2019-07-19 11:07:52 +06:00
3650f6d7b8 Re-implement Host predicate (#989)
* update HostGuard implementation

* update/add tests for new HostGuard implementation
2019-07-19 10:28:43 +06:00
6b7df6b242 prep actix-web release 2019-07-18 17:51:51 +06:00
b6ff786ed3 update dependencies 2019-07-18 17:50:10 +06:00
9c3789cbd0 revert DateServiceInner changes 2019-07-18 17:37:41 +06:00
29098f8397 Add support for downcasting response errors (#986)
* Add support for downcasting response errors

* Added test for error casting
2019-07-18 17:25:50 +06:00
fbdda8acb1 Unix domain sockets (HttpServer::bind_uds) #92 2019-07-18 17:24:12 +06:00
d03296237e Log error results in Logger middleware (closes #938) (#984)
* Log error results in Logger middleware (closes #938)

* Log internal server errors with an ERROR log level

* Logger middleware: don't log 500 internal server errors, as Actix now logs them always

* Changelog
2019-07-18 14:31:18 +06:00
b36fdc46db Remove several usages of 'unsafe' (#968)
* Replace UnsafeCell in DateServiceInner with Cell

The previous API was extremely dangerous - calling `get_ref()`
followed by `reset()` would trigger instant UB, without requiring
any `unsafe` blocks in the caller.

By making DateInner `Copy`, we can use a normal `Cell` instead
of an `UnsafeCell`. This makes it impossible to cause UB (or even panic)
with the API.

* Split unsafe block HttpServiceHandlerResponse

Also add explanation of the safety of the usage of `unsafe`

* Replace UnsafeCell with RefCell in PayloadRef

This ensures that a mistake in the usage of 'get_mut' will cause
a panic, not undefined behavior.
2019-07-18 04:45:17 +06:00
2a2d7f5768 nightly clippy warnings 2019-07-17 15:53:51 +06:00
4092c7f326 clippy warnings 2019-07-17 15:08:30 +06:00
ef3e1037a8 bump version 2019-07-17 14:18:26 +06:00
baaa7b3fbb Replace ClonableService with local copy 2019-07-17 13:55:44 +06:00
32718b7e31 Expose factory traits and some clippy fixes (#983) 2019-07-17 12:58:42 +06:00
c01611d8b5 prepare actix-web release 2019-07-17 12:07:12 +06:00
7b1dcaffda cleanup deprecation warning for Box<dyn> 2019-07-17 11:44:39 +06:00
c65dbaf88e expose app's ResourceMap via resource_map method 2019-07-17 11:33:05 +06:00
c45728ac01 prep test server release 2019-07-16 10:21:52 +06:00
6f71409355 Add DELETE, PATCH, OPTIONS methods to TestServerRunner (#973) 2019-07-16 10:19:28 +06:00
8d17c8651f update bench link 2019-07-11 14:45:58 +06:00
b1143168e5 Impl Responder for (T, StatusCode) where T: Responder (#954) 2019-07-11 14:42:58 +06:00
69456991f6 update api doc example for client and add panic info for connection_info 2019-07-11 14:40:37 +06:00
f410f3330f prepare actix-session release 2019-07-08 23:25:51 +06:00
e1fcd203f8 Update the copyless version to 0.1.4 (#956)
< 0.1.4 failed to check for null when doing allocations which could lead to null dereferences.
2019-07-08 15:48:20 +06:00
0d8a4304a9 Drop a duplicated word (#958) 2019-07-05 20:46:55 +06:00
14cc5a5d6b Merge pull request #912 from Dowwie/master
updated actix-session to support login and logout functionality
2019-07-03 21:07:07 -04:00
287c2b1d18 Merge branch 'master' into master 2019-07-03 18:50:19 -04:00
7596ab69e0 reverted actix-web/CHANGES.md 2019-07-03 08:55:29 -04:00
1fdd77bffa reworded session info in CHANGES 2019-07-03 07:56:50 -04:00
2d424957fb updated version in Cargo to 0.2 2019-07-03 07:50:45 -04:00
dabc4fe00b updated actix-session/CHANGES with info 2019-07-03 07:50:11 -04:00
5bf5b0acd2 updated CHANGES with info about actix-session update 2019-07-03 07:46:46 -04:00
099a8ff7d8 updated session cookie to support login, logout, changes 2019-07-01 15:26:19 -04:00
a28b7139e6 prepare awc release 2019-07-01 11:34:57 +06:00
a0a469fe85 disable travis cargo cache 2019-07-01 11:33:11 +06:00
dbab55dd6b Bump rand crate version to 0.7 (#951) 2019-07-01 09:37:03 +06:00
d2eb1edac3 Actix-web client: Always append a colon after username in basic auth (#949)
* Always append a colon after username in basic auth

* Update CHANGES.md
2019-07-01 09:34:42 +06:00
5901dfee1a Fix link to actix-cors (#950) 2019-06-30 21:30:04 +06:00
0e05b37082 updated cookie session to update on change 2019-06-29 14:24:02 -04:00
37f4ce8604 Fixes typo in docs. (#948)
Small typo in docs.
2019-06-29 10:38:16 +06:00
12b5174850 update deps 2019-06-28 14:46:26 +06:00
b77ed193f7 prepare actix-web release 2019-06-28 14:41:56 +06:00
d286ccb4f5 Add on-connect callback #946 2019-06-28 14:34:26 +06:00
cac162aed7 update actix-http changes 2019-06-28 12:34:43 +06:00
a3a78ac6fb Do not set Content-Length header, let actix-http set it #930 2019-06-28 11:42:20 +06:00
596483ff55 prepare actix-web-actors release 2019-06-28 10:54:23 +06:00
768859513a Expose the max limit for payload sizes in Websocket Actors. #925 (#933)
* Expose the max limit for payload sizes in Websocket Actors.

* Revert to previous not-formatted code.

* Implement WebsocketContext::with_codec and make Codec Copy and Clone.

* Fix formatting.

* Fix formatting.
2019-06-28 10:49:03 +06:00
44bb79cd07 Call req.path() on Json extractor error only (#945)
* Call req.path() on Json extractor error only

* Cleanup len parse code
2019-06-28 10:44:53 +06:00
af9fb5d190 Support asynchronous data factories #850 2019-06-28 10:43:52 +06:00
50a9d9e2c5 Merge branch 'master' into master 2019-06-27 06:38:13 -04:00
c0c71f82c0 Fixes typo. (#940)
Small typo fix.
2019-06-25 23:23:36 +06:00
93855b889a Merge branch 'master' into master 2019-06-24 18:41:48 -04:00
fa7e0fe6df updated cookie.rs req to get_changes 2019-06-24 18:40:14 -04:00
b948f74b54 Extractor configuration Migration (#937)
added guide for Extractor configuration in MIGRATION.md
2019-06-24 07:16:04 +06:00
1a24ff8717 Add builder function for HTTP 429 Too Many Requests status (#931) 2019-06-21 13:06:29 +06:00
47fab0e393 Bump derive_more crate version to 0.15.0 in actix-cors (#927) 2019-06-19 16:41:42 +06:00
313ac48765 Use encoding_rs crate instead of unmaintained encoding crate (#922)
* Use encoding_rs crate instead of unmaintained encoding crate

* Update changelog
2019-06-18 12:43:25 +06:00
d7780d53c9 Fix typo in actix_web::web::Data::get_ref docstring (#921) 2019-06-18 07:27:23 +06:00
ad0e6f73b3 update version 2019-06-17 12:35:00 +06:00
546a8a58db remove cors and identity middlewares 2019-06-17 12:33:00 +06:00
acda1c075a prepare actix-web release 2019-06-17 12:23:30 +06:00
686e5f1595 update deps 2019-06-16 22:10:22 +06:00
d2b6502c7a prepare actix-http release 2019-06-16 21:59:22 +06:00
7c0f570845 Do not compress NoContent (204) responses #918 2019-06-16 21:54:17 +06:00
382d4ca216 Merge branch 'master' into master 2019-06-15 22:21:39 +06:00
eaa371db8b update migration 2019-06-15 22:20:46 +06:00
d293ae2a69 fix nested resource map registration #915 2019-06-15 22:12:20 +06:00
d7ec241fd0 re-export identity and cors middleware 2019-06-15 21:47:06 +06:00
cd323f2ff1 Move cors middleware to actix-cors crate 2019-06-15 09:34:16 +06:00
32a66a99bf reverting change to get_session due to side effects 2019-06-13 09:19:03 -04:00
73ae801a13 Merge branch 'master' of https://github.com/Dowwie/actix-web 2019-06-13 09:00:45 -04:00
ca4ed0932e made Session::get_session public 2019-06-13 08:59:59 -04:00
bf48798bce Content-Length is 0 for NamedFile HEAD request #914 2019-06-13 15:27:21 +06:00
9fc7c8b1af Merge branch 'master' into master 2019-06-12 23:53:36 +06:00
c8118e8411 fix path doc tests 2019-06-12 20:12:15 +06:00
65732197b8 modified so as to consider unanticipated state changes 2019-06-12 10:11:38 -04:00
959eef05ae updated actix-session to support login and logout functionality (renew and purge) 2019-06-12 08:03:27 -04:00
e7ba67e1a8 rename PathPayloadError and test for path config 2019-06-12 17:02:45 +06:00
13e618b128 Added initial support for PathConfig, allows setting custom error handler. (#903) 2019-06-12 16:49:56 +06:00
36e6f0cb4b add "put" and "sput" methods for test server (#909) 2019-06-12 16:47:00 +06:00
7450ae37a7 Re-apply patch from #637 #894 2019-06-12 16:45:05 +06:00
2ffda29f9b Allow to test an app that uses async actors #897 2019-06-12 16:15:06 +06:00
ff724e239d move identity service separate crate 2019-06-12 15:52:48 +06:00
Bob
ee769832cf get_identity from HttpMessage (#908)
* get_identity from HttpMessage

* more doc for RequestIdentity
2019-06-12 09:26:46 +06:00
c4b7980b4f Upgraded actix-web dependency and set default-features to false (#895) 2019-06-07 09:34:56 +06:00
bfbac4f875 Upgraded actix-web dependency and set default-features to false (#900) 2019-06-07 09:34:30 +06:00
53e2f8090f Mark default enabled package features in the docs (#890) 2019-06-06 11:14:56 +06:00
e399e01a22 update readme 2019-06-05 09:02:44 +06:00
d9a62c4bbf add App::register_data() 2019-06-05 08:43:39 +06:00
a548b69679 fmt 2019-06-05 08:43:13 +06:00
ae64475d98 test-server release 2019-06-05 08:27:25 +06:00
a342b1289d prep awc release 2019-06-05 08:14:00 +06:00
38f04b75a7 update deps 2019-06-04 22:36:10 +06:00
a771540b16 prepare actix-web-codegen release 2019-06-04 22:33:43 +06:00
cf217d35a8 Added HEAD, CONNECT, OPTIONS and TRACE to the codegen (#886)
* Added HEAD, CONNECT, OPTIONS and TRACE to the codegen

* Add new macros to use statement

* Add patch to supported codegen http methods

* Update CHANGES.md

Added head, options, trace, connect and patch codegen changes to CHANGES.md
2019-06-04 22:30:43 +06:00
0e138e111f add external resource support on scope level 2019-06-03 23:41:32 +06:00
1fce4876f3 Scope configuration (#880)
* WIP: Scope configuarion

* Extensions: Fix into_iter()

* Scope: Fix tests

* Add ScopeConfig to web

Committing from mobile, if this doesn't look good it's because I haven't tested it...

* Scope Config: Use ServiceConfig instead

* Scope: Switch to ServiceConfig in doc

* ScopeConfig: Remove unnecessary changes, handle the case when data is empty

* ScopeConfig: Remove changes from actix-http
2019-06-03 23:12:37 +06:00
4a179d1ae1 prepare actix-session release 2019-06-03 10:52:43 +06:00
a780ea10e9 Guard cookie mod by cookie-session feature (#883)
Signed-off-by: Igor Gnatenko <i.gnatenko.brain@gmail.com>
2019-06-03 10:30:30 +06:00
6d2e190c8e prepare actix-files release 2019-06-02 13:09:21 +06:00
b1cfbdcf7a prepare actix-http release 2019-06-02 13:05:22 +06:00
24180f9014 Fix boundary parsing #876 2019-06-02 12:58:37 +06:00
15cdc680f6 Static files are incorrectly served as both chunked and with length #812 2019-06-01 17:57:40 +06:00
666756bfbe body helpers 2019-06-01 17:57:25 +06:00
a1b40f4314 add license files 2019-06-01 17:25:29 +06:00
29a0fe76d5 prepare actix-web-codegen release 2019-06-01 17:21:22 +06:00
7753b9da6d web-codegen: Add extra-traits to syn features (#879)
```rust
error[E0277]: `syn::attr::NestedMeta` doesn't implement `std::fmt::Debug`
   --> src/route.rs:149:57
    |
149 |                 attr => panic!("Unknown attribute{:?}", attr),
    |                                                         ^^^^ `syn::attr::NestedMeta` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
    |
    = help: the trait `std::fmt::Debug` is not implemented for `syn::attr::NestedMeta`
    = note: required because of the requirements on the impl of `std::fmt::Debug` for `&syn::attr::NestedMeta`
    = note: required by `std::fmt::Debug::fmt`
```
2019-06-01 14:13:45 +06:00
f1764bba43 Fix Logger time format (use rfc3339) (#867)
* Fix Logger time format (use rfc3339)

* Update change log
2019-05-31 12:09:21 +04:00
c2d7db7e06 prepare actix-web-actors release 2019-05-29 16:22:57 -07:00
21418c7414 prep actix-http release 2019-05-29 16:15:12 -07:00
fe781345d5 Add Migration steps for Custom Error (#869)
Adds migration steps for custom error in 1.0
2019-05-29 20:47:04 +04:00
a614be7cb5 Don't DISCONNECT from stream when reader is empty (#870)
* Don't DISCONNECT from stream when reader is empty

* Fix chunked transfer: poll_request before closing stream + Test
2019-05-29 20:37:42 +04:00
1eb89b8375 remove debug prints 2019-05-25 03:16:53 -07:00
aa626a1e72 handle disconnects 2019-05-25 03:16:46 -07:00
7f12b754e9 Handle socket read disconnect 2019-05-25 03:07:40 -07:00
3f196f469d update version 2019-05-25 02:13:04 -07:00
35eb378585 prepare actix-files release 2019-05-25 02:02:28 -07:00
6db625f55b Update actix-web dep to 1.0.0-rc (#864) 2019-05-25 01:52:23 -07:00
801cc2ed5d Cleaned unnecessary Option<_> around ServerBuilder in server.rs/HttpServer (#863) 2019-05-23 05:21:02 -07:00
ded1e86e7e Add ServiceRequest::set_payload() method 2019-05-22 21:25:51 -07:00
babf48c550 fix NamedFile last-modified check #820 2019-05-22 21:21:12 -07:00
d3e807f6e9 move Payload to inner http request 2019-05-22 11:49:27 -07:00
7746e785c1 re-export Service and Transform traits 2019-05-22 11:20:37 -07:00
4e141d7f5d Merge branch 'master' of github.com:actix/actix-web 2019-05-22 11:18:42 -07:00
12842871fe Clear http requests pool on app service drop #860 2019-05-22 11:18:33 -07:00
fc85ae4014 small documentation fix (#856) 2019-05-21 10:43:18 -07:00
5826f39dbe Add set_json method to TestRequest (#851)
- Takes a type which implements serde::Serialize, serializes it to JSON,
and sets it as the payload. The content-type is also set to JSON.
2019-05-18 19:36:28 -07:00
8ff56d7cd5 prepare actix-session release 2019-05-18 11:20:09 -07:00
0843bce7ba prepare actix-multipart 2019-05-18 11:15:58 -07:00
dea0e0a721 update actix-server dep 2019-05-18 11:00:33 -07:00
e857ab1f81 HttpServer::shutdown_timeout u16 to u64 (#849)
Increase maximum graceful shutdown time from 18 hours.

For issue #848.
2019-05-18 10:50:35 -07:00
0dda4b06ea prepare release 2019-05-18 10:49:59 -07:00
cbe0226177 update changes 2019-05-18 10:47:08 -07:00
e8c8626878 update deps 2019-05-18 09:54:23 -07:00
4b215e0839 Support Query<T>::from_query() (#846) 2019-05-17 13:10:46 -07:00
e1ff3bf8fa fix resource match with params #841 2019-05-15 10:31:40 -07:00
80f4ef9aac When using codegen with paths that have parameters then only the first endpoint resolves (#842) 2019-05-15 09:21:07 -07:00
bba90d7f22 Query config (#839)
* add QueryConfig

* expose QueryConfig in web module

* fmt

* use associated type for QueryConfig

* update CHANGES.md
2019-05-14 13:54:30 -07:00
f8af3b86e5 export set_date 2019-05-14 08:48:11 -07:00
6c3d8b8738 Make JsonConfig send (#830)
* replace Rc with Arc

* add Send trait requirement for Fn in JsonConfig error handler

* add Sync trait requirement for Fn in JsonConfig error handler

* use associated type inside JsonConfig

* fix lint: members in the impl has the same order in the trait

* Update CHANGES.md
2019-05-12 20:04:08 -07:00
5a90e33bcc update deps 2019-05-12 12:01:24 -07:00
86b569e320 version 2019-05-12 11:56:01 -07:00
2350a2dc68 Handle cancellation of uploads #834 #736 2019-05-12 11:43:05 -07:00
36d017dcc6 update deps 2019-05-12 11:41:43 -07:00
3bb081852c prep actix-session release 2019-05-12 10:53:21 -07:00
1ca58e876b prepare beta4 release 2019-05-12 10:49:21 -07:00
e9cbcbaf03 update dependencies 2019-05-12 10:18:02 -07:00
07c9eec803 prepare awc release 2019-05-12 10:04:38 -07:00
beae9ca0f7 update changes 2019-05-12 09:57:16 -07:00
07b9707ca1 prepare actix-http release 2019-05-12 09:56:55 -07:00
45c05978b0 Allow to set/override app data on scope level 2019-05-12 09:42:05 -07:00
df08baf67f update actix-net dependencies 2019-05-12 08:34:51 -07:00
4066375737 Update CHANGES.md 2019-05-10 14:45:30 -07:00
a77b0b054a Make App::configure take an FnOnce (#825) 2019-05-10 14:44:49 -07:00
a17ff492a1 fix formatting 2019-05-04 22:18:59 -07:00
33b4c05557 add payload stream migration entry 2019-05-04 22:18:02 -07:00
005c055a7f prepare actix-web release 2019-05-04 20:05:20 -07:00
3d1af19080 prepare actix-http release 2019-05-04 19:51:13 -07:00
fa78da8156 unify route and app data, it allows to provide global extractor config #775 2019-05-04 19:43:49 -07:00
01cfcf3b75 update changes 2019-05-04 08:42:27 -07:00
7ef4f5ac0b Make request headers optional in CORS preflight (#816) 2019-05-04 08:41:37 -07:00
fc19ce41c4 Clean up response extensions in response pool #817 2019-05-03 15:26:34 -07:00
6e00eef63a awc: Fix typo on ResponseError documentation (#815)
* awc: Fix typo on ResponseError documentation

Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>

* http: Fix typo on ResponseError documentation

Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>

* http: Expand type names for openssl related errors documentation

Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
2019-05-03 14:30:00 -07:00
337c2febe3 add more tests 2019-05-02 09:49:10 -07:00
f27beab016 fix case for transfer-encoding header name 2019-05-02 09:30:00 -07:00
4f1c6d1bb7 Update MIGRATION.md (#811) 2019-05-02 09:26:51 -07:00
6b34909537 Fix NormalizePath middleware impl #806 2019-05-01 12:40:56 -07:00
87284f0951 Add doctest to verify NormalizePath middleware (#809) 2019-05-01 11:47:51 -07:00
24bd5b1344 update readmes 2019-04-29 20:47:21 -07:00
94a0d1a6bc remove old api doc refs 2019-04-29 18:42:21 -07:00
f4e1205cbb fix reactor drop panic 2019-04-29 10:14:08 -07:00
d2c1791067 add async handler test with blocking call 2019-04-29 09:45:37 -07:00
f4b4875cb1 Add helper function for executing futures test::block_fn() 2019-04-29 09:34:14 -07:00
29a841529f Allow to construct Data instances to avoid double Arc for Send + Sync types. 2019-04-29 09:26:12 -07:00
b51b5b763c added clarification to docs regarding middleware processing sequence, added delete method to TestRequest (#799)
* added clarification to docs regarding middleware processing sequnce

* added delete method to TestRequest, doc, and test
2019-04-29 09:14:36 -07:00
8db6b48a76 update version 2019-04-28 09:09:18 -07:00
ffd2c04cd3 Add helper trait UserSession which allows to get session for ServiceRequest and HttpRequest 2019-04-28 09:08:51 -07:00
70a4c36496 use Error explicitly 2019-04-25 11:14:32 -07:00
cba78e06ae update changes 2019-04-24 15:42:34 -07:00
3b3dbb4f40 add raw services support 2019-04-24 15:29:15 -07:00
7300002226 grammar fixes (#796) 2019-04-24 13:21:42 -07:00
5426413cb6 update dependencies 2019-04-24 13:00:30 -07:00
2bc937f6c3 prepare release 2019-04-24 12:50:44 -07:00
60fa0d5427 Store visit and login timestamp in the identity cookie (#502)
This allows to verify time of login or last visit and therfore limiting
the danger of leaked cookies.
2019-04-24 12:49:56 -07:00
f429d3319f Read until eof for http/1.0 responses #771 2019-04-24 11:57:40 -07:00
2e19f572ee add tests for camel case headers rendering 2019-04-24 11:27:57 -07:00
64f603b076 Support to set header names of ClientRequest as Camel-Case (#713)
* Support to set header names of `ClientRequest` as Camel-Case

This is the case for supporting to request for servers which don't
perfectly implement the `RFC 7230`. It is important for an app
which uses `ClientRequest` as core part.

* Add field `upper_camel_case_headers` to `ClientRequest`.

* Add function `set_upper_camel_case_headers` to `ClientRequest`
  and `ClientRequestBuilder` to set field `upper_camel_case_headers`.

* Add trait `client::writer::UpperCamelCaseHeader` for
  `http::header::HeaderName`, let it can be converted to Camel-Case
  then writed to buffer.

* Add test `test_client::test_upper_camel_case_headers`.

* Support upper Camel-Case headers

* [actix-http] Add field `upper_camel_case_headers` for `RequestHead`
* [actix-http] Add code for `MessageType` to support upper camel case
* [awc] Add functions for `ClientRequest` to set upper camel case

* Use `Flags::CAMEL_CASE` for upper camel case of headers
2019-04-24 10:48:49 -07:00
679d1cd513 allow to override responder's status code and headers 2019-04-24 10:25:46 -07:00
42644dac3f prepare actix-http-test release 2019-04-24 07:31:33 -07:00
898ef57080 Fix async web::Data factory handling 2019-04-23 21:21:49 -07:00
9702b2d88e add client h2 reuse test 2019-04-23 15:06:30 -07:00
d2b0afd859 Fix http client pool and wait queue management 2019-04-23 14:57:03 -07:00
5f6a1a8249 update version 2019-04-23 09:45:39 -07:00
5d531989e7 Fix BorrowMutError panic in client connector #793 2019-04-23 09:42:19 -07:00
3532602299 Added support for remainder match (i.e /path/{tail}*) 2019-04-22 21:22:17 -07:00
48bee55087 .to_async() handler can return Responder type #792 2019-04-22 14:22:08 -07:00
d00c9bb844 do not consume boundary 2019-04-21 16:14:09 -07:00
895e409d57 Optimize multipart handling #634, #769 2019-04-21 15:41:01 -07:00
f0789aad05 update dep versions 2019-04-21 09:03:46 -07:00
7e480ab2f7 beta.1 release 2019-04-20 21:16:51 -07:00
891f857547 update changes 2019-04-20 11:18:04 -07:00
01b1350dcc update versions 2019-04-19 18:16:01 -07:00
5e4e95fb0a update create version 2019-04-19 18:13:05 -07:00
9f421b81b8 fix non-ssl connector 2019-04-19 18:10:53 -07:00
6decfdda1f update deps 2019-04-19 18:06:34 -07:00
fc9b14a933 allow to specify server address for http and ws requests 2019-04-19 18:03:44 -07:00
7292d0b696 drop chrono and use i64 for max age 2019-04-19 17:23:17 -07:00
a3844c1bfd update version 2019-04-19 13:55:36 -07:00
791f22bbc8 replate time::Duration with chrono::Duration and add max_age_time method (#789)
* feat: replate time::Duration with chrono::Duration

* feat: rename max_age method which accepts `Duration` to max_age_time and add new max_age method accepting isize of seconds

* feat: replace `time:Duration` with `chrono:Duration` in repo `actix-http`
2019-04-19 13:54:44 -07:00
1e7f97a111 Add Normalization middleware for in place (#783) 2019-04-19 13:53:49 -07:00
bc40f5ae40 Merge pull request #788 from Dowwie/master
added put and patch to TestRequest, docs, and test
2019-04-19 06:55:44 -04:00
3504a8fc0a Merge branch 'master' into master 2019-04-19 04:38:41 -04:00
bfe0df5ab0 update tests 2019-04-18 21:28:23 -07:00
ed94df189f Merge branch 'master' into master 2019-04-18 19:03:48 -04:00
aa255298ef make ServiceRequest::from_parts private, as it is not safe to create from parts 2019-04-18 16:03:13 -07:00
da86b6e062 added put and patch to TestRequest, docs, and test 2019-04-18 18:06:32 -04:00
75e340137d use local version of http-test 2019-04-18 12:23:56 -07:00
e659e09e29 update tests 2019-04-18 11:01:04 -07:00
163ca89cf4 more tests 2019-04-17 17:48:25 -07:00
85b598a614 add cookie session test 2019-04-17 11:02:03 -07:00
b64851c5ec enable runtime for test:: methods 2019-04-17 10:28:27 -07:00
cc8420377e pass request ownership to closure instead of ref 2019-04-16 15:43:55 -07:00
5740f1e63a prepare actix-framed release 2019-04-16 11:18:47 -07:00
c943e95812 update dependencies 2019-04-16 11:17:29 -07:00
4c0ebd55d3 prepare actix-http-test release 2019-04-16 11:02:26 -07:00
e7ec77aa81 update readme 2019-04-16 10:50:37 -07:00
ddfd7523f7 prepare awc release 2019-04-16 10:49:38 -07:00
2986077a28 no need for feature 2019-04-16 10:32:48 -07:00
3744957804 actix_http::encoding always available 2019-04-16 10:27:58 -07:00
420d3064c5 Add .peer_addr() #744 2019-04-16 10:11:38 -07:00
eb4f6b74fb Merge branch 'master' of github.com:actix/actix-web 2019-04-16 09:58:07 -07:00
a116c4c2c7 Expose peer addr via Request::peer_addr() and RequestHead::peer_addr 2019-04-16 09:54:02 -07:00
7f674febb1 add 422 to httpcodes.rs (#782) 2019-04-15 16:55:06 -07:00
14252f5ef2 use test::call_service 2019-04-15 09:09:21 -07:00
7a28b32f6d Rename test::call_success to test::call_service 2019-04-15 07:44:07 -07:00
09cdf1e302 Rename RouterConfig to ServiceConfig 2019-04-15 07:32:49 -07:00
1eebd47072 fix warnings 2019-04-14 21:00:16 -07:00
002c41a7ca update trust-dns 2019-04-14 20:45:44 -07:00
ab4fda6084 update tests 2019-04-14 20:20:33 -07:00
f9078d41cd add test::read_response; fix TestRequest::app_data() 2019-04-14 19:52:12 -07:00
4cc2b38059 added read_response_json for testing (#776)
* added read_response_json for testing

* cleaned up

* modied docs for read_response_json

* typo in doc

* test code in doc should compile now

* use type coercion in doc

* removed generic R, replaced with Request
2019-04-14 16:25:45 -07:00
d7040dc303 alpha.6 release 2019-04-14 08:09:32 -07:00
6bc1a0c76b Do not set default headers for websocket request 2019-04-14 07:43:53 -07:00
5bd5651faa Allow to use any service as default service 2019-04-13 22:25:00 -07:00
32ac159ba2 update migration 2019-04-13 16:51:41 -07:00
ee33f52736 make extractor config type explicit 2019-04-13 16:35:25 -07:00
4f30fa9d46 Remove generic type for request payload, always use default 2019-04-13 14:50:54 -07:00
043f6e77ae remove nested multipart support 2019-04-13 10:11:07 -07:00
48518df883 do not generate all docs; use docs.rs for 1.0 docs 2019-04-13 09:35:23 -07:00
1f2b15397d prepare alpha5 release 2019-04-12 14:00:45 -07:00
87167f6581 update actix-connect 2019-04-12 12:33:11 -07:00
b4768a8f81 add TestRequest::run(), allows to run async functions 2019-04-12 11:28:57 -07:00
3fb7343e73 provide during test request construction 2019-04-12 11:22:18 -07:00
5cfba5ff16 add FramedRequest builder for testing 2019-04-12 11:15:58 -07:00
67c34a5937 Add Debug impl for BoxedSocket 2019-04-11 16:01:54 -07:00
94d7a7f873 custom future for SendError service 2019-04-11 15:12:23 -07:00
d86567fbdc revert SendResponse::Error type 2019-04-11 14:18:58 -07:00
d115b3b3ed ws verifyciation takes RequestHead; add SendError utility service 2019-04-11 14:00:32 -07:00
6420a2fe1f update client example 2019-04-10 20:57:18 -07:00
0eed9e5257 add more migration 2019-04-10 20:51:57 -07:00
7801fcb993 update migration 2019-04-10 20:47:28 -07:00
e55be4dba6 add FramedRequest helper methods 2019-04-10 19:57:34 -07:00
12e1dad42e export TestBuffer 2019-04-10 19:43:09 -07:00
7cd59c38d3 rename framed App 2019-04-10 18:08:28 -07:00
8dc4a88aa6 add actix-framed 2019-04-10 15:06:27 -07:00
52aebb3bca fmt 2019-04-10 15:05:03 -07:00
6b42b2aaee remove framed for now 2019-04-10 12:55:56 -07:00
6ab9838977 added some error logging for extractors: Data, Json, Query, and Path (#765)
* added some error logging for extractors

* changed log::error to log::debug and fixed position of log for path

* added request path to debug logs
2019-04-10 12:45:13 -07:00
9d82d4dfb9 Fix body propagation in Response::from_error. #760 2019-04-10 12:43:31 -07:00
9bb40c249f add h1::SendResponse future; renamed to MessageBody::size 2019-04-10 12:24:17 -07:00
046b7a1425 Expand codegen to allow specify guards and async 2019-04-10 15:43:18 +03:00
c22a3a71f2 fix test 2019-04-08 19:07:11 -07:00
9c9940d88d update readme 2019-04-08 17:53:19 -07:00
561f83d044 add upgrade service support to h1 dispatcher 2019-04-08 17:51:14 -07:00
43d325a139 allow to specify upgrade service 2019-04-08 14:51:16 -07:00
0a6dd0efdf fix compression tests 2019-04-08 12:48:39 -07:00
b921abf18f set host header for http1 connections 2019-04-08 12:48:26 -07:00
9bcd5d6664 updated legacy code in call_success example (#762) 2019-04-08 11:20:46 -07:00
bc58dbb2f5 add async expect service test 2019-04-08 11:19:56 -07:00
b1547bbbb6 do not set default headers 2019-04-08 11:09:57 -07:00
a7fdac1043 fix expect service registration and tests 2019-04-08 10:31:29 -07:00
53da55aa3c alpha4 release 2019-04-07 23:42:05 -07:00
aa78565453 use objects pool for HttpRequest; optimize nested services call 2019-04-07 23:06:21 -07:00
75b213a6f0 refactor FromRequest trait 2019-04-07 14:43:07 -07:00
3c650ca194 remove buffer capacity for payload 2019-04-07 10:40:45 -07:00
219baf3323 remove PayloadWriter trait 2019-04-07 10:29:26 -07:00
ec09d6fbe6 optimize encode headers and body split 2019-04-07 10:03:38 -07:00
68d2203dd6 run travis with stable rust only 2019-04-07 08:17:29 -07:00
748289f0ff use custom headers map; more optimizations 2019-04-06 15:02:02 -07:00
4ef46e26f9 Merge branch 'master' of github.com:actix/actix-web 2019-04-06 08:13:14 -07:00
3872d3ba5a refactor h1 dispatcher 2019-04-06 08:12:58 -07:00
b1523ab78c started 1.0 migration guide (#758) 2019-04-06 07:39:20 -07:00
fbedaec661 add expect: 100-continue support #141 2019-04-05 16:46:44 -07:00
02fcaca3da add backward compatibility 2019-04-05 11:36:26 -07:00
18593d8476 updated Connector docs and renamed service() to finish() (#757)
* added Connector to actix-web::client namespace

* updated Connector, renaming service() to finish() and adding docs

* added doc for finish method on Connector
2019-04-05 11:34:27 -07:00
b6dacaa23a remove SendError and SendResponse services 2019-04-05 11:29:42 -07:00
f89321fd01 fix import 2019-04-05 10:50:11 -07:00
0d4a8e1b1c update actix-connect 2019-04-05 10:35:14 -07:00
162cd3eecd added Connector to actix-web::client namespace (#756) 2019-04-05 07:37:00 -07:00
a655bdac52 Fix clippy warning (#755) 2019-04-05 12:34:24 +03:00
309c480782 encoder sent uncompressed data before compressed 2019-04-04 15:03:40 -07:00
9c205f9f1d update tests for content-encoding 2019-04-04 14:00:56 -07:00
1f5c0f50f9 Add minimal std::error::Error impl for Error 2019-04-04 13:23:38 -07:00
d8bc66a18e Use thread pool for response body comression 2019-04-04 13:17:55 -07:00
bc834f6a03 remove some static contraints 2019-04-04 10:59:34 -07:00
dc7c3d37a1 upgrade router 2019-04-03 21:45:30 -07:00
1e2bd68e83 Render error and return as response body 2019-04-03 19:55:19 -07:00
954fe21751 set response error body 2019-04-03 19:07:25 -07:00
7d6085ddbd Add %U (URLPath) for logger (#752)
* Add %R (Route) for logger

* Requested Updates (Route => URLPath, %R => %U)
2019-04-03 17:41:42 -07:00
cef3dc3586 added app_data() method 2019-04-03 15:25:52 -07:00
237bfba1ed add App::configure() - allow to offload app configuration to different methods 2019-04-03 15:09:31 -07:00
dfa0abf5a5 Export IntoHeaderValue 2019-04-03 12:44:47 -07:00
e738361e09 move multipart support to separate crate 2019-04-03 12:28:58 -07:00
f56072954b remove PayloadBuffer 2019-04-03 03:20:20 -07:00
2a89b995aa do not cleanup travis build 2019-04-02 21:56:38 -07:00
442f5057dd alpha.3 release 2019-04-02 21:49:31 -07:00
19eef36f8f Merge branch 'tarpaulin' 2019-04-02 21:11:03 -07:00
51d5006ccf Detect socket disconnection during protocol selection 2019-04-02 20:50:25 -07:00
3aebe09e5c travis 2019-04-02 19:21:22 -07:00
4227cddd30 fix dev dependencies 2019-04-02 15:00:10 -07:00
db1f7651a3 more patch cratesio 2019-04-02 14:47:59 -07:00
00000fb316 mut obj 2019-04-02 14:27:54 -07:00
f100976ef0 rename close_connection to force_close 2019-04-02 14:08:30 -07:00
deac983bc7 fix test-server workspace setup 2019-04-02 14:04:28 -07:00
bca31eb7ad remove Deref 2019-04-02 13:35:01 -07:00
e282ef7925 return back consuming builder 2019-04-02 12:51:16 -07:00
49a499ce74 properly allocate read buffer 2019-04-02 11:11:32 -07:00
d067b1d5f1 do not use static 2019-04-02 10:53:44 -07:00
c27fbdc35f Preallocate read buffer for h1 codec, #749 2019-04-02 10:19:56 -07:00
1bd0995d7a remove unneded & 2019-04-01 18:00:38 -07:00
2d43489278 ClientRequest::json() accepts reference instead of object 2019-04-01 17:53:30 -07:00
89a0a50e14 Merge branch 'master' of github.com:actix/actix-web 2019-04-01 15:20:04 -07:00
38afc93304 Use non-consuming builder pattern for ClientRequest 2019-04-01 15:19:34 -07:00
03c84be1f2 Merge pull request #750 from Dowwie/master
added docs for wrap and wrap_fn
2019-04-01 17:37:04 -04:00
6d169f4c9c Merge branch 'master' of https://github.com/Dowwie/actix-web 2019-04-01 15:10:49 -04:00
3dd3f7bc92 updated scope wrap doc 2019-04-01 15:10:28 -04:00
e6936d9f73 Merge branch 'master' into master 2019-04-01 14:53:23 -04:00
03dfbdfcdd updated wrap and wrap fn descriptions, still requiring viable examples 2019-04-01 14:52:05 -04:00
5c4e4edda4 add ClientResponse::json() 2019-04-01 11:51:18 -07:00
c5fa6c1abe do not consume response 2019-04-01 11:29:50 -07:00
6c195d8521 add Derev<Target=RequestHead> for ClientRequest 2019-04-01 10:26:25 -07:00
96fd61f3d5 rust 1.31.0 compatibility 2019-04-01 10:26:09 -07:00
8800b8ef13 mentioned re-use in wrap doc 2019-04-01 09:59:21 -04:00
220c04b7b3 added docs for wrap and wrap_fn 2019-04-01 09:30:11 -04:00
34695f4bce rename test methods; update tests 2019-03-31 20:43:00 -07:00
15c5a3bcfb fix test 2019-03-31 18:57:54 -07:00
ab45974e35 add default handler 2019-03-31 18:19:18 -07:00
e4b3f79458 allocate enough space 2019-03-31 17:05:02 -07:00
ce8294740e fix tests with disabled features 2019-03-31 17:04:34 -07:00
ddf5089bff Warn when an unsealed private cookie isn't valid UTF-8 (#746) 2019-03-31 16:26:56 +03:00
7596d0b7cb fix fn_guard doc string 2019-03-30 20:48:00 -07:00
1a871d708e update guard doc test 2019-03-30 12:13:21 -07:00
351df84cca update stable release api doc link 2019-03-30 11:37:56 -07:00
6fcbe4bcda add fn_guard 2019-03-30 11:33:31 -07:00
457b75c995 update api docs; move web to submodule 2019-03-30 10:04:38 -07:00
724e9c2efb replace deprecated fn 2019-03-30 07:56:09 -07:00
2e159d1eb9 test-server: Request functions should accept path (#743) 2019-03-30 07:53:45 -07:00
a20b9fd354 prepare aplha2 release 2019-03-29 22:06:14 -07:00
d846328f36 fork cookie crate 2019-03-29 21:13:39 -07:00
193f8fb2d9 update tests 2019-03-29 18:51:07 -07:00
3a954298d7 Merge branch 'master' of github.com:actix/actix-web 2019-03-29 18:23:07 -07:00
3220777ff9 Added ws::Message::Nop, no-op websockets message 2019-03-29 18:22:49 -07:00
00526f60dc Impl BodyEncoding for Response (#740) 2019-03-29 16:29:11 -07:00
c126713f40 add rustls support to HttpServer 2019-03-29 16:28:32 -07:00
e9bbde6832 allow to override request's uri 2019-03-29 16:28:28 -07:00
5eb3f1154e revert 2019-03-29 14:27:22 -07:00
aebeb511cd explicit impl traits for ws connect 2019-03-29 14:26:11 -07:00
744d82431d add per request timeout 2019-03-29 14:07:37 -07:00
058b1d56e6 Export ws sub-module with websockets related types 2019-03-29 13:49:21 -07:00
709475b2bb multipart::Field renamed to MultipartField 2019-03-29 11:59:38 -07:00
19a0b8046b remove actix reference 2019-03-29 11:13:36 -07:00
1e7096a63a add request timeout 2019-03-28 22:33:41 -07:00
ea4d98d669 Session wide headers, basic and bearer auth 2019-03-28 21:48:35 -07:00
3b897da8e2 Do not use thread pool for decomression if chunk size is smaller than 2048 2019-03-28 21:15:26 -07:00
10b166404e Merge branch 'master' of github.com:actix/actix-web 2019-03-28 20:27:59 -07:00
80ff7d40a1 enable awc/ssl if ssl features is enabled 2019-03-28 20:27:47 -07:00
c4a8bbe47b fix the example in README.md (#739) 2019-03-28 20:03:17 -07:00
9710e9b01f Re-export actix_http::client::Connector 2019-03-28 14:46:33 -07:00
1d79f16529 update release api docs link 2019-03-28 14:30:38 -07:00
670a457013 fix docs.rs feature list 2019-03-28 14:28:59 -07:00
878f32c495 fix tests for no-default-features 2019-03-28 14:27:07 -07:00
a2c9ff3a33 back to development 2019-03-28 14:10:03 -07:00
9c198a0d29 alpha.1 release 2019-03-28 13:46:26 -07:00
9cca86e60d prepear actix-http release 2019-03-28 12:45:41 -07:00
605ce05127 App::enable_encoding() allows to enable compression and decompression 2019-03-28 12:32:59 -07:00
5795850bbb decompress payload in cpu threadpool 2019-03-28 11:08:24 -07:00
e84c95968f reuse PayloadBuffer from actix-http 2019-03-28 05:34:33 -07:00
6e0fe7db2d use actix-threadpool for blocking calls 2019-03-28 05:16:43 -07:00
4309d9b88c port multipart support 2019-03-28 05:04:39 -07:00
c59937784e add client websockets support 2019-03-27 18:53:19 -07:00
e254fe4f9c allow to override response body encoding 2019-03-27 11:29:31 -07:00
3edc515bac refactor RequestHead/ResponseHead 2019-03-27 10:38:01 -07:00
fb9c94c3e0 remove Backtrace from error 2019-03-27 09:31:07 -07:00
faa3ea8e5b rename BodyLength to BodySize 2019-03-27 09:24:55 -07:00
b6b37d3ea3 Add Client::request_from 2019-03-26 23:25:24 -07:00
b7570b2476 remove unused code 2019-03-26 22:33:01 -07:00
959aebb24f simplify TestResponse builder 2019-03-26 22:03:00 -07:00
d49a8ba53b add client TestResponse 2019-03-26 21:57:04 -07:00
5703bd8160 fix client cookies parsing 2019-03-26 21:31:18 -07:00
ab597dd98a Added HTTP Authentication for Client #540 2019-03-26 20:57:06 -07:00
1cca25c276 add client decompression support 2019-03-26 20:45:00 -07:00
2629699b62 rename flate2-c feature to flate2-zlib 2019-03-26 18:46:06 -07:00
1904b01fc0 add content-encoding decompression 2019-03-26 15:14:32 -07:00
9451ba71f4 update cargo files 2019-03-26 12:50:51 -07:00
50c0ddb3cd update tests 2019-03-26 12:31:51 -07:00
c7ad677804 Merge actix-http project 2019-03-26 11:54:35 -07:00
2c7da28ef9 move high level client code to awc crate 2019-03-26 11:43:22 -07:00
b254113d9f move high level client code from actix-http 2019-03-26 11:41:38 -07:00
999fa65efa Merge branch 'master' of github.com:actix/actix-web 2019-03-26 09:22:51 -07:00
cc24c77acc add Client::new() 2019-03-26 09:11:27 -07:00
254b61e800 Fix copy/paste mistake in error message (#733) 2019-03-26 09:07:19 -07:00
83d4447349 add http client 2019-03-25 21:58:01 -07:00
9037473e0f update client error 2019-03-25 21:52:45 -07:00
8d1195d8ac add async handler tests 2019-03-25 14:33:34 -07:00
e18227cc3d add wrap_fn to App and Scope 2019-03-25 13:43:02 -07:00
d30027ac5b Remove StaticFilesConfig (#731)
* Remove StaticFilesConfig

* Applying comments

* Impl Clone for Files<S>
2019-03-25 13:02:37 -07:00
86a21c956c rename .middleware to .wrap 2019-03-25 13:02:10 -07:00
939d2e745c rename Resource::middleware to Resource::wrap and add wrap_fn for fn middlewares 2019-03-25 12:47:58 -07:00
1970c99522 add session test 2019-03-24 20:21:20 -07:00
51e4dcf3b3 update test doc string 2019-03-24 17:13:17 -07:00
e37e81af0b simplify Payload extractor 2019-03-24 17:00:59 -07:00
ed322c175e update tests 2019-03-24 16:28:16 -07:00
b95e99a09e update changes 2019-03-24 16:17:59 -07:00
ffb3324129 do not use default resource from app, return 405 if no matching route found 2019-03-24 16:15:34 -07:00
9932a342ef export Scope 2019-03-24 11:59:35 -07:00
c1e8d8363c fix errhandlers doc string 2019-03-24 11:49:26 -07:00
913155d34c update doc strings 2019-03-24 11:47:23 -07:00
ede32c8b3f export errhandlers module 2019-03-24 11:32:30 -07:00
307b2e5b0e fix compress features 2019-03-24 11:29:35 -07:00
bc01d39d4d add error response test for cors 2019-03-23 22:03:40 -07:00
548f6f89bf allow to get app data via HttpRequest 2019-03-23 21:39:02 -07:00
5b06f2bee5 port cors middleware 2019-03-23 21:29:16 -07:00
60050307bd session feature is renamed to cookies 2019-03-23 11:18:31 -07:00
1e069bb843 Merge branch '1.0' 2019-03-23 10:16:32 -07:00
535b407ac0 make cookies optional 2019-03-23 10:06:54 -07:00
c5c7b244be cookie is optional 2019-03-23 09:40:20 -07:00
00b7dc7887 handle socket shutdown for h1 connections 2019-03-18 09:44:48 -07:00
8872f3b590 fix ws upgrade 2019-03-18 05:30:18 -07:00
fd86d73a03 fix response upgrade type 2019-03-18 05:26:12 -07:00
efe3025395 add handshake test 2019-03-17 22:57:27 -07:00
3301a46264 proper connection upgrade check 2019-03-17 22:56:13 -07:00
b0343eb22d simplify ws stream interface 2019-03-17 22:31:10 -07:00
6ab7665868 export ws module 2019-03-17 22:11:50 -07:00
fd3e351c31 add websockets context 2019-03-17 22:02:03 -07:00
f26d4b6a23 do not chunk websocket stream 2019-03-17 21:57:53 -07:00
85c2887b30 export ws::hash_key 2019-03-17 21:09:50 -07:00
88152740c6 move macros tests to codegen crate 2019-03-17 20:20:10 -07:00
a07ea00cc4 add basic test for proc macro 2019-03-17 13:55:03 -07:00
6b66681827 add basic actors integration 2019-03-17 13:47:20 -07:00
9bd0f29ca3 add tests for error and some responders 2019-03-17 10:11:10 -07:00
c14c66d2b0 add json extractor tests 2019-03-17 09:52:41 -07:00
2b5f9f0511 temp fix for tarpaulin 2019-03-17 08:52:03 -07:00
7435c5e9bf temp fix for tarpaulin 2019-03-17 01:49:00 -07:00
b550f9ecf4 update imports 2019-03-17 01:08:56 -07:00
fa66a07ec5 move httpmessage futures to actix-web 2019-03-17 01:02:51 -07:00
9012c46fe1 move payload futures from actix-http 2019-03-17 00:48:40 -07:00
fd141ef9b1 move json to actix-web 2019-03-16 22:10:15 -07:00
c80884904c move JsonBody from actix-http 2019-03-16 22:04:09 -07:00
725ee3d396 rename extract to types 2019-03-16 21:43:48 -07:00
4a4826b23a cleanup doc strings and clippy warnings 2019-03-16 21:35:02 -07:00
e396c90c9e update api doc 2019-03-16 21:13:16 -07:00
60386f1791 introduce RouteData extractor 2019-03-16 21:09:11 -07:00
6afcecdb5f Merge branch '1.0' of github.com:actix/actix-web into 1.0 2019-03-16 20:17:48 -07:00
b1e267bce4 rename State to a Data 2019-03-16 20:17:27 -07:00
d93fe157b9 use better name Route::data instead of Route::config 2019-03-16 11:58:01 -07:00
414614e1b5 change marco import (#727) 2019-03-15 21:08:39 -07:00
ce4a2629f3 update actix-connect 2019-03-14 22:56:06 -07:00
15ba40d3ab fix non ssl connector 2019-03-14 13:08:05 -07:00
76bb30dc3a fix names 2019-03-14 13:06:29 -07:00
1f9467e880 update tests 2019-03-14 12:01:35 -07:00
b8bfd29d2c use Uri as client connect message 2019-03-14 11:52:52 -07:00
bf8262196f feat: enable use of patch as request method (#718) 2019-03-14 11:36:10 +03:00
d2c755bb47 update client connector 2019-03-13 22:57:28 -07:00
3a24a75d13 update dep 2019-03-13 16:56:11 -07:00
033a8d890c update actix connect 2019-03-13 15:57:33 -07:00
1941aa0217 use actix-connect crate 2019-03-13 14:41:40 -07:00
17ecdd63d2 httpresponse: add constructor for HttpResponseBuilder (#697) 2019-03-13 17:20:18 +03:00
86405cfe7a more tests 2019-03-12 22:57:09 -07:00
28f01beaec update deps 2019-03-12 17:06:08 -07:00
f627d01055 update actix-server 2019-03-12 17:04:08 -07:00
e271d4e47a Merge branch 'master' of github.com:actix/actix-http 2019-03-12 16:55:38 -07:00
402a40ab27 update actix-server dep 2019-03-12 16:55:16 -07:00
7242d96701 map BlockingError 2019-03-11 23:19:05 -07:00
a2c4639074 move blocking code to actix-rt 2019-03-11 23:11:51 -07:00
00d47acedc Update README.md 2019-03-11 17:56:48 -07:00
409888fcd5 remove debug print, remove unused flags 2019-03-11 16:47:12 -07:00
e15e4f18fd update tests 2019-03-11 16:42:33 -07:00
eae48f9612 use server backlog 2019-03-11 15:26:05 -07:00
ad43ca735b update server service requirenments 2019-03-11 15:09:42 -07:00
cc7f6b5eef Fix preflight CORS header compliance; refactor previous patch. (#717) 2019-03-11 07:26:54 +03:00
9680423025 Add more tests for route 2019-03-10 18:35:15 -07:00
b8829bbf22 add identity middleware tests 2019-03-10 17:16:32 -07:00
0f0d6b65ca update service request/response location 2019-03-10 17:16:28 -07:00
6436004194 set test cookie if it is not empty 2019-03-10 17:06:43 -07:00
50a0cb5653 do no move self 2019-03-10 17:02:14 -07:00
615fbb49bd support cookies in TestRequest 2019-03-10 17:00:03 -07:00
4d96abb639 use actix_web::Error for middleware errors 2019-03-10 16:35:38 -07:00
d755772039 add From impls for ResponseBuilder 2019-03-10 15:30:31 -07:00
79875ea039 update deps 2019-03-10 14:22:53 -07:00
039efc5703 move tests to different mods 2019-03-10 11:04:50 -07:00
b6c1135798 hide blocking mod 2019-03-10 10:56:53 -07:00
16c42be4a2 simplify extractor configuration, config is optional now 2019-03-10 10:53:56 -07:00
ee8725b581 move extractors to separate submod 2019-03-10 10:01:24 -07:00
49d65fb07a move extract to submodule 2019-03-10 09:34:25 -07:00
9b8812423c reexport Server controller form actix-server 2019-03-10 09:20:58 -07:00
3a2035a121 fix doc tests 2019-03-09 21:15:26 -08:00
be9031c55e update doc api 2019-03-09 20:48:05 -08:00
12f0c78091 port identity middleware 2019-03-09 20:40:09 -08:00
134863d5c8 move middlewares 2019-03-09 18:05:50 -08:00
513ce0b08d add json and form client request's method 2019-03-09 17:42:35 -08:00
85664cc6f7 update deps 2019-03-09 14:56:18 -08:00
6c4be45787 update deps 2019-03-09 14:33:33 -08:00
d2dba028f6 fix dependency link 2019-03-09 14:07:43 -08:00
54678308d0 propogate app config with http request; add tests for url_for 2019-03-09 14:06:24 -08:00
9c7056e9b8 fix connector 2019-03-09 13:38:56 -08:00
c0ce7f0bae update http service usage; add app host 2019-03-09 10:53:00 -08:00
d026821924 unify service builders 2019-03-09 10:39:06 -08:00
fde55ffa14 revert generic request parameter for service; support ServerConfig as new factory config 2019-03-09 09:49:11 -08:00
aadcdaa3d6 add resource map, it allow to check if router has resource and it allows to generate urls for named resources 2019-03-09 07:39:34 -08:00
ca73f178c9 revert generic service request; add ServerConfig to service factories 2019-03-09 07:37:23 -08:00
e324522389 listen method has different signature 2019-03-08 22:47:49 -08:00
2f6df11183 do not execute blocking fn if result is not required 2019-03-07 19:31:17 -08:00
eef687ec80 remove unneeded methods 2019-03-07 15:51:24 -08:00
88e5059910 add doc string to guards 2019-03-07 15:37:39 -08:00
b6b2eadb3a rename blocking fn 2019-03-07 14:41:43 -08:00
c2a350b33f export blocking via web module 2019-03-07 14:40:20 -08:00
0e57b4ad61 export extractor configs via web module 2019-03-07 14:01:52 -08:00
b211966c28 Payload extractor 2019-03-07 13:33:40 -08:00
d77954d19e fix files test 2019-03-07 12:32:40 -08:00
ceb6d45bf2 reexpost extractors in web module 2019-03-07 11:43:46 -08:00
22708e78a9 added proc-macros for route registration 2019-03-07 11:09:42 -08:00
1151b5bf7c fix crate name 2019-03-06 23:43:47 -08:00
e56691bcf2 rename to Files 2019-03-06 23:39:08 -08:00
6e638129c5 use generic HttpService 2019-03-06 23:06:14 -08:00
6d639ae3df allow to create http services with config 2019-03-06 22:59:56 -08:00
3b069e0568 added combined http1/2 service 2019-03-06 22:56:34 -08:00
e25483a0d5 fix warnings 2019-03-06 21:12:35 -08:00
60c048c8cd fix nested resources 2019-03-06 19:27:18 -08:00
244fff9e0a added Logger middleware 2019-03-06 19:19:27 -08:00
561a89b8b3 copy logger 2019-03-06 17:33:12 -08:00
fe22e83144 refactor service registration process; unify services and resources 2019-03-06 15:47:15 -08:00
b689bb9260 add failure support 2019-03-06 11:45:33 -08:00
5cde4dc479 update actix-rt 2019-03-06 10:41:07 -08:00
ad08e856d7 update actix-rt 2019-03-06 10:30:17 -08:00
db39a604ae implement ResponseError trait for BlockingError 2019-03-06 10:03:37 -08:00
db566a634c make State type Send compatible 2019-03-06 10:03:18 -08:00
3fc28c5d07 simplify StaticFile constructor, move HttpRange to separate module 2019-03-06 09:27:02 -08:00
ceca96da28 Added HTTP Authentication for Client (#540) 2019-03-06 12:56:12 +03:00
6efc3438b8 refactor and enable some tests for staticfiles 2019-03-05 22:10:08 -08:00
889d67a356 add Stream impl for ResponseBody 2019-03-05 21:19:12 -08:00
34c8b95a35 allow to extract body from response 2019-03-05 21:15:18 -08:00
1a80b70868 add Responder impl for InternalError 2019-03-05 19:41:50 -08:00
0de47211b2 tune App::default_resource signature 2019-03-05 19:30:44 -08:00
f71354783e update HttpMessage impls 2019-03-05 19:10:45 -08:00
d85468f7e1 do not expose headers_mut via HttpMessage 2019-03-05 19:07:07 -08:00
81273f71ef update tests 2019-03-05 19:03:59 -08:00
0cf73f1a04 move session to different folder 2019-03-05 18:52:29 -08:00
143ef87b66 add session and cookie session backend 2019-03-05 18:47:18 -08:00
496ee8d039 remove more MessageBody constraints from Response 2019-03-05 18:14:30 -08:00
0d2116156a Messagebody constraint is not required from Response::into_body 2019-03-05 17:24:24 -08:00
200cae19a9 add HttpMessage impl &mut T 2019-03-05 14:39:06 -08:00
96477d42cb extend HttpMessage trait, add api to work with requests cookies 2019-03-05 13:16:26 -08:00
01329af1c2 fix non ssl code 2019-03-05 10:18:46 -08:00
6457996cf1 move session to separate crate 2019-03-05 10:13:33 -08:00
03248028a9 update actix-service 2019-03-05 10:08:08 -08:00
ce0b172598 update actix-service 2019-03-05 09:30:11 -08:00
b6fe1dacf2 update middleware impl 2019-03-04 21:42:51 -08:00
42f030d3f4 Ensure that Content-Length zero is specified in empty request 2019-03-05 08:37:15 +03:00
3a456ec148 update actix-service dependency 2019-03-04 20:46:33 -08:00
65a313c78b update utils dep 2019-03-04 19:51:09 -08:00
2e79562c9d add HttpServer type 2019-03-04 16:29:03 -08:00
a88b3b090d allow to specify service config for h1 service 2019-03-04 15:58:39 -08:00
bd4124587a provide block_on function for testing purpose 2019-03-04 13:25:35 -08:00
e442ddb167 allow scope level guards 2019-03-04 11:47:53 -08:00
5c61321565 fix state factory support, tests for state and state factory 2019-03-03 21:40:03 -08:00
34171fa7f5 add scopes 2019-03-03 21:02:01 -08:00
8502c32a3c re-enable extractor tests 2019-03-03 15:32:47 -08:00
360082f99f update api docs 2019-03-03 14:45:56 -08:00
e50d4c5e0e rename extractor module to extract, re-enable doc tests 2019-03-03 13:53:31 -08:00
237677be15 rename filter to guard 2019-03-03 12:09:38 -08:00
b81ae899f6 better naming 2019-03-03 08:24:09 -08:00
015364edf8 fix travis config 2019-03-03 08:00:12 -08:00
f90ca868ca update tests 2019-03-03 01:12:06 -08:00
a8f3dec527 use tarpaulin from cache 2019-03-03 01:03:28 -08:00
6df85e32df added extractor configuration system 2019-03-03 00:57:48 -08:00
08fcb6891e use specific nightly version for travis 2019-03-02 22:33:46 -08:00
b320dc127a remove unused code 2019-03-02 22:22:45 -08:00
115b30d9cc add state example 2019-03-02 22:11:24 -08:00
d5c54a1867 update extractor tests 2019-03-02 22:03:45 -08:00
352e7b7a75 update tests for defaultheaders middleware 2019-03-02 21:35:31 -08:00
b535adf637 add IntoFuture impl for Response and ResponseBuilder 2019-03-02 21:22:01 -08:00
8103d33270 use custom request for FromRequest trait 2019-03-02 19:19:56 -08:00
2d0495093c add Payload::take method 2019-03-02 18:37:09 -08:00
e4198a037a add TestServiceRequest builder 2019-03-02 16:24:14 -08:00
00ea195601 TestRequest::take public 2019-03-02 16:04:43 -08:00
0081b9d446 improve ergomonics of TestRequest 2019-03-02 15:59:05 -08:00
de9b38295f update deps 2019-03-02 15:08:10 -08:00
9394a4e2a5 cleanup dependencies 2019-03-02 14:07:21 -08:00
3454812b68 rename actix-web-fs crate 2019-03-02 13:59:12 -08:00
75fbb97480 update new transform trait 2019-03-02 13:57:00 -08:00
cc20fee628 add request chain services 2019-03-02 11:53:05 -08:00
fdf3011837 add responder for unit type 2019-03-02 09:05:07 -08:00
bc3c29c398 update version 2019-03-02 00:04:39 -08:00
e6d04d24cc move fs to separate crate 2019-03-01 23:59:44 -08:00
2d7293aaf8 copy actix-web2 2019-03-01 22:51:32 -08:00
5fff07402e downgrade tarpaulin 2019-03-01 21:36:37 -08:00
650474ca39 choose openssl version for travis 2019-03-01 21:02:56 -08:00
38c86d4683 update tarpaulin travis config 2019-03-01 20:33:31 -08:00
6d11ee683f fixing little typo in docs (#711) 2019-03-01 11:34:58 +03:00
80d4cbe301 Add change notes for new HttpResponseBuilder 2019-02-27 21:37:20 +03:00
69d710dbce Add insert and remove() to response builder (#707) 2019-02-27 15:52:42 +03:00
b80ee71785 use new new service api 2019-02-22 14:21:35 -08:00
2f89b12f4f remove more response containts 2019-02-20 21:05:37 -08:00
60a8da5c05 remove Response constraint 2019-02-20 21:02:23 -08:00
7f749ac9cc add missing end of line 2019-02-18 22:34:22 -08:00
781f1a3fef do not skip content length is no chunking is selected 2019-02-18 22:20:00 -08:00
c8713d045c poll payload again if framed object get flushed during same iteration 2019-02-18 21:41:38 -08:00
842da939dc fix chunked transfer encoding handling 2019-02-18 20:24:50 -08:00
d180b2a1e3 update tests 2019-02-18 18:46:30 -08:00
037c3da172 enable ssl for connector 2019-02-18 18:40:40 -08:00
e6e83ea57e add Response::map_body 2019-02-18 17:01:35 -08:00
118606262b refactor payload handling 2019-02-13 13:52:11 -08:00
0059a55dfb Fix typo 2019-02-13 14:31:28 +03:00
8d4ce0c956 export PayloadStream 2019-02-12 11:09:58 -08:00
a41459bf69 make payload generic 2019-02-12 11:07:42 -08:00
32021532c3 export Payload type 2019-02-12 09:55:29 -08:00
f9724fa0ec add ErrorResponse impl for TimeoutError 2019-02-11 09:54:41 -08:00
e178db7f74 fix test 2019-02-09 21:32:44 -08:00
1af149b9e6 remove Clone constraint from handler service 2019-02-09 20:27:39 -08:00
a66d8589c2 add Extensions::contains method 2019-02-09 10:45:35 -08:00
6a343fae06 simplify Message type 2019-02-09 10:33:49 -08:00
f3ed1b601e Change service response to Into<Response> 2019-02-09 08:44:22 -08:00
c695358bcb Ignored the If-Modified-Since if If-None-Match is specified (#680) (#692) 2019-02-09 00:33:00 +03:00
ed7ca7fe07 make Message clonable and expose as public 2019-02-07 21:50:20 -08:00
b0e36fdcf9 simplify Message api 2019-02-07 21:19:10 -08:00
a7a2d4cf5c fix warns 2019-02-07 19:53:48 -08:00
2a6e4dc7ab use non mutable self for HttpMessage::payload() for ergonomic reasons 2019-02-07 19:26:12 -08:00
5575ee7d2d use same payload type for h1 and h2 2019-02-07 13:41:50 -08:00
7d49a07f91 add h1/h2 payload 2019-02-07 13:39:15 -08:00
c4596b0bd6 add headers from actix-web 2019-02-07 13:24:24 -08:00
cd83553db7 simplify payload api; add missing http error helper functions 2019-02-07 11:37:33 -08:00
b018e4abaf Fixes TestRequest::with_cookie panic 2019-02-07 07:55:27 +03:00
fcace161c7 fix manifest features 2019-02-06 12:22:40 -08:00
55a29d3778 add h2 server support 2019-02-06 11:44:15 -08:00
346d85a884 Serve static file directly instead of redirecting (#676) 2019-02-04 13:20:46 +03:00
ef5b54a481 use released service crate 2019-02-03 14:05:44 -08:00
c9bb2116fe update actix-utils 2019-02-03 10:50:29 -08:00
e70c7f2a5d upgrade derive-more 2019-02-01 20:22:43 -08:00
3269e35722 migrate to actix-service 0.2 2019-02-01 20:18:44 -08:00
76866f054f move service to submodule; update travis config 2019-01-30 10:29:15 -08:00
3e6bdbd9ee rename trait 2019-01-29 10:34:27 -08:00
9a4eb5a848 update readme 2019-01-29 10:17:38 -08:00
4217894d48 cleaup warnings 2019-01-29 10:14:00 -08:00
4a388d7ad9 add client http/2 support 2019-01-28 20:41:09 -08:00
9968afe4a6 Use NamedFile with an existing File (#670) 2019-01-28 08:07:28 +03:00
12fb94204f use hashbrown instead of std HashMap 2019-01-27 11:42:41 -08:00
c3d3e8b465 move TestServer to separate crate 2019-01-27 11:07:48 -08:00
42277c5c8f update deps 2019-01-26 22:09:26 -08:00
f5bec968c7 Bump v_htmlescape version to 0.4 2019-01-25 11:31:42 +03:00
a534fdd125 Add io handling for ECONNRESET when data has already been received 2019-01-20 08:45:33 +03:00
3431fff4d7 Fixed example in client documentation. This closes #665. 2019-01-14 07:44:30 +03:00
d6df2e3399 Fix HttpResponse doc spelling "os" to "of" 2019-01-11 08:45:15 +03:00
1fbb52ad3b 0.7.18 Bump 2019-01-10 17:05:18 +03:00
e5cdd22720 Fix test server listener thread leak (#655) 2019-01-08 10:42:22 -08:00
4f2e970732 Tidy up CHANGES.md 2019-01-08 10:49:03 +03:00
4d45313f9d Decode special characters when handling static files 2019-01-08 10:46:58 +03:00
55a2a59906 Improve change askama_escape in favor of v_htmlescape (#651) 2019-01-03 22:34:18 +03:00
61883042c2 Add with-cookie init-method for TestRequest (#647) 2019-01-02 13:24:08 +03:00
799c6eb719 0.7.17 Bump 2018-12-25 16:28:36 +03:00
037a1c6a24 Bump min version of rustc
Due to actix & trust-dns requirement
2018-12-24 21:17:09 +03:00
bfdf762062 Only return a single Origin value (#644)
Only return a single origin if matched.
2018-12-24 21:16:07 +03:00
477bf0d8ae Send HTTP/1.1 100 Continue if request contains expect: continue header #634 2018-12-23 10:19:12 -08:00
e9fe3879df Support custom content types in JsonConfig 2018-12-23 08:27:47 +03:00
f2251b8059 Merge branch 'master' of github.com:fafhrd91/actix-http 2018-12-19 18:35:09 -08:00
cc74435b01 drop failure crate 2018-12-19 18:34:56 -08:00
3bd5167ac2 Merge pull request #10 from DoumanAsh/http1_case
H1 decoder should ignore headers case
2018-12-16 07:44:40 -08:00
67df9399df H1 decoder should ignore headers case 2018-12-16 18:43:11 +03:00
1a940d4c18 H1 decoded should ignore header cases 2018-12-16 18:34:32 +03:00
e8bdcb1c08 Update min version of http
Closes #630
2018-12-15 09:26:56 +03:00
b1001b80b7 upgrade actix-service dependency 2018-12-12 18:39:01 -08:00
46db09428c Prepare release 0.7.16 2018-12-11 21:04:05 +03:00
1c60992723 use released crates 2018-12-11 09:29:12 -08:00
90eef31cc0 impl ResponseError for SendError when possible (#619) 2018-12-11 19:37:52 +03:00
aaae368ed9 use new actix crates 2018-12-10 18:08:33 -08:00
86af02156b add impl FromRequest for Either<A,B> (#618) 2018-12-10 19:02:05 +03:00
9f4d48f7a1 update tests 2018-12-06 15:03:01 -08:00
e9121025b7 convert to 2018 edition 2018-12-06 14:32:52 -08:00
ac9fc662c6 Bump version to 0.7.15 2018-12-05 18:27:06 +03:00
0745a1a9f8 Remove usage of upcoming keyword async
AsyncResult::async is replaced with AsyncResult::future
2018-12-05 18:23:04 +03:00
b1635bc0e6 Update some dependencies (#612)
* Update rand to 0.6

* Update parking_lot to 0.7

* Update env_logger to 0.6
2018-12-04 09:58:22 +03:00
08c7743bb8 Add set_mailbox_capacity() function 2018-12-02 08:40:09 +03:00
68c5d6e6d6 impl From<Cow<'static, [u8]>> for Binary (#611)
impl `From` for `Cow<'static, [u8]>`  and `From<Cow<'static, str>>` for `Binary`
2018-12-02 08:32:55 +03:00
c0f8bc9e90 fix ssl support 2018-11-30 16:04:33 -08:00
5003c00efb use new Service and NewService traits 2018-11-30 11:57:57 -08:00
d269904fbf add cause for nested errors 2018-11-28 09:10:13 -10:00
06387fc778 display parse error for ws client errors 2018-11-28 09:02:31 -10:00
617b8557e1 Merge pull request #9 from alishir/new-example
echo example with `impl Future`
2018-11-27 20:17:32 -10:00
4028f6f6fd http crate removed, cargo fmt 2018-11-28 09:42:04 +03:30
397804a786 echo example with impl Future 2018-11-28 09:15:08 +03:30
756bf0af58 Merge pull request #5 from alishir/echo-example
Two other simple examples.
2018-11-25 20:50:17 -10:00
f1bfdc7395 Merge branch 'master' of https://github.com/fafhrd91/actix-http 2018-11-25 20:14:58 -10:00
9c038ee189 allow to use Uri for client request 2018-11-25 20:14:42 -10:00
ca1b460924 comments aligned. 2018-11-25 05:48:33 +03:30
c386353337 decode reserved characters when extracting path with configuration (#577)
* decode reserved characters when extracting path with configuration

* remove useless clone

* add a method to get decoded parameter by name
2018-11-24 16:54:11 +03:00
7a97de3a1e update readme. 2018-11-24 17:17:34 +03:30
d5b2640342 add framed_hello.rs 2018-11-24 17:08:17 +03:30
c3c2286e3a An other hello word example and update sample in README.md 2018-11-24 17:07:30 +03:30
d5ca6e21e2 simple echo server. 2018-11-24 11:29:14 +03:30
9aab382ea8 Allow user to provide addr to custom resolver
We basically swaps Addr with Recipient to enable user to use custom resolver
2018-11-23 15:36:12 +03:00
7ef6eab9af Merge pull request #3 from alishir/examples
hello-world example added.
2018-11-22 19:24:50 -10:00
41d68c87d9 hello-world example added. 2018-11-23 07:42:40 +03:30
1a322966ff handle response errors 2018-11-21 07:49:24 -08:00
389cb13cd6 Export PathConfig and QueryConfig
Closes #597
2018-11-20 23:06:38 +03:00
ab3e12f2b4 set server response version 2018-11-20 11:23:05 -08:00
186d3d727a add kee-alive tests 2018-11-20 10:55:50 -08:00
6a93178479 Complete error helper functions. 2018-11-20 08:07:46 +03:00
e1fc6dea84 restore execute method 2018-11-19 16:39:40 -08:00
6b60c9e230 add debug impl for H1ServiceResult 2018-11-19 16:11:58 -08:00
3901239128 unify requedt/response encoder 2018-11-19 14:57:12 -08:00
1ca6b44bae add TestServer 2018-11-18 21:48:20 -08:00
18fcddfd63 remove backtrace dep 2018-11-18 20:25:59 -08:00
7d66430324 move url module to different crate 2018-11-18 20:08:43 -08:00
22d4523c93 update actix-net 2018-11-18 18:31:44 -08:00
7d3adaa6a8 replace message flags with ConnectionType 2018-11-18 18:17:38 -08:00
adad203314 refactor encoder/decoder impl 2018-11-18 17:52:56 -08:00
8fea1367c7 re-introduce Body type, use Body as default body type for Response 2018-11-18 13:48:42 -08:00
7fed50bcae refactor response body management 2018-11-17 20:21:28 -08:00
e73a97884a do not allow to set server response version 2018-11-17 09:03:35 -08:00
f0bd4d868e simplify server response type 2018-11-17 08:56:40 -08:00
3a4b16a6d5 use BodyLength for request and response body 2018-11-16 21:30:37 -08:00
aa20e2670d refactor h1 dispatcher 2018-11-16 21:09:33 -08:00
625469f0f4 refactor decoder 2018-11-16 19:28:07 -08:00
3b7bc41418 use RequestHead for Request 2018-11-15 22:34:29 -08:00
6d9733cdf7 define generic client Connection trait 2018-11-15 11:10:23 -08:00
acd42f92d8 remove debug print 2018-11-14 19:08:52 -08:00
6e7560e287 SendResponse service sends body as well 2018-11-14 18:57:58 -08:00
cd9901c928 prepare release 2018-11-14 16:24:01 -08:00
03ad9a3105 simplify client decoder 2018-11-14 10:52:40 -08:00
6297fe0d41 refactor client response payload handling 2018-11-14 09:38:16 -08:00
550c5f55b6 add simple http client 2018-11-13 22:59:00 -08:00
537144f0b9 add http client connector service 2018-11-11 23:12:54 -08:00
1ef0eed0bd do not stop on keep-alive timer if sink is not completly flushed 2018-11-08 20:46:13 -08:00
b25b083866 do not stop on keep-alive timer if sink is not completly flushed 2018-11-08 20:45:48 -08:00
dea39030bc properly handle upgrade header if content-length header is set 2018-11-08 20:38:40 -08:00
61b1030882 Fix websockets connection drop if request contains content-length header #567 2018-11-08 20:35:47 -08:00
7065c540e1 set nodelay on socket #560 2018-11-08 16:29:43 -08:00
aed3933ae8 Merge branch 'master' of github.com:actix/actix-web 2018-11-08 16:15:45 -08:00
5b7740dee3 hide ChunkedReadFile 2018-11-08 16:12:16 -08:00
1a0bf32ec7 Fix unnecessary owned string and change htmlescape in favor of askama_escape (#584) 2018-11-08 16:08:06 -08:00
9ab586e24e update actix-net dep 2018-11-08 16:06:23 -08:00
6a1d560f22 fix keep-alive timer reset 2018-11-08 09:30:53 -08:00
62f1c90c8d update base64 dep 2018-11-07 21:18:40 -08:00
2677d325a7 fix keep-alive timer reset 2018-11-07 21:09:33 -08:00
8e354021d4 Add SameSite option to identity middleware cookie (#581) 2018-11-07 23:24:06 +03:00
f1587243c2 fix body decoding 2018-11-05 19:32:03 -08:00
3b536ee96c Use old clippy attributes syntax (#562) 2018-11-01 11:14:48 +03:00
da82e24954 render error message as body 2018-10-30 11:55:17 -07:00
79bcbb8a10 use error message 2018-10-30 11:50:30 -07:00
148cf73003 allow to create response with error message 2018-10-30 11:46:44 -07:00
c2540cc59b clippy warnings 2018-10-29 16:39:46 -07:00
cfd9a56ff7 Add async/await ref 2018-10-28 09:24:19 -07:00
5f91f5eda6 Correct IoStream::set_keepalive for UDS (#564)
Enable uds feature in tests
2018-10-26 10:59:06 +03:00
540ad18432 add Debug impl 2018-10-24 16:48:45 -07:00
cd0223e8b7 update Connector usage 2018-10-23 22:41:30 -07:00
bc6e62349c update deps; export api 2018-10-23 21:44:20 -07:00
4260692034 add DefaultClient type alias 2018-10-22 18:52:40 -07:00
09c94cb06b add client http codec; websockets client 2018-10-22 18:18:05 -07:00
9b94eaa6a8 ws services 2018-10-22 09:59:20 -07:00
42d5d48e71 add a way to configure error treatment for Query and Path extractors (#550)
* add a way to configure error treatment for Query extractor

* allow error handler to be customized for Path extractor
2018-10-20 06:43:43 +03:00
960274ada8 Refactoring of server output to not exclude HTTP_10 (#552) 2018-10-19 07:52:10 +03:00
f383f618b5 Fix typo in error message (#554) 2018-10-18 21:27:31 +03:00
20c693b39c rename service 2018-10-15 16:46:13 -07:00
3c402a55da added H1SimpleService 2018-10-15 15:56:47 -07:00
c04b4678f1 bump version 2018-10-14 08:10:41 -07:00
dd948f836e HttpServer not sending streamed request body on HTTP/2 requests #544 2018-10-14 08:08:12 -07:00
d39c018c93 do not handle upgrade and connect requests 2018-10-13 23:57:31 -07:00
63a443fce0 Correct build script 2018-10-13 10:05:21 +03:00
d145136e56 Add individual check for TLS features 2018-10-13 09:54:03 +03:00
b960b5827c export Uri 2018-10-11 20:15:10 -07:00
32145cf6c3 fix after update tokio-rustls (#542) 2018-10-11 11:05:07 +03:00
06addd5523 update deps 2018-10-10 13:23:25 -07:00
47b47af01a refactor ws codec 2018-10-10 13:20:00 -07:00
ec8aef6b43 update dep versions 2018-10-10 08:36:16 -07:00
f45038bbfe remove unused code 2018-10-09 13:23:37 -07:00
c63838bb71 fix 204 support for http/2 2018-10-09 13:12:49 -07:00
4d17a9afcc update version 2018-10-09 11:42:52 -07:00
65e9201b4d Fixed panic during graceful shutdown 2018-10-09 11:35:57 -07:00
4a167dc89e update readme example 2018-10-09 10:47:41 -07:00
1407bf4f7f simplify h1 codec messages 2018-10-09 10:39:55 -07:00
c3ad516f56 disable shutdown atm 2018-10-09 09:45:24 -07:00
cb78d9d41a use actix-net release 2018-10-08 22:04:53 -07:00
93b1c5fd46 update deps 2018-10-08 21:58:37 -07:00
fd5da5945e update appveyor config 2018-10-08 21:23:52 -07:00
2b4870e65b fix tests on stable 2018-10-08 16:10:07 -07:00
f99a723643 add Default impl for ServiceConfig 2018-10-08 15:52:12 -07:00
3984ad45df separate ResponseLength::Zero is not needed 2018-10-08 15:33:38 -07:00
4e7fac08b9 do not override content-length header 2018-10-08 15:30:59 -07:00
805e7a4cd0 impl response body support 2018-10-08 15:24:51 -07:00
07f6ca4b71 Merge branch 'master' of github.com:actix/actix-web 2018-10-08 13:06:49 -07:00
03d988b898 refactor date rendering 2018-10-08 10:16:19 -07:00
431e33acb2 add Date header to response 2018-10-08 10:14:29 -07:00
30db78c19c use TakeItem instead of TakeRequest 2018-10-08 07:55:01 -07:00
cfad5bf1f3 enable slow request timeout for h2 dispatcher 2018-10-08 07:47:42 -07:00
8acf9eb98a better keep-alive handling 2018-10-07 10:09:48 -07:00
13193a0721 refactor http/1 dispatcher 2018-10-07 09:59:40 -07:00
9c4a55c95c simplify H1Service configuration 2018-10-07 08:28:38 -07:00
8d85c45c1d simplify error handling 2018-10-07 00:04:38 -07:00
b0ca6220f0 refactor te encoding 2018-10-06 22:36:57 -07:00
dda5b399ca add content-length test 2018-10-06 21:32:01 -07:00
25af82c45a cleanup dependencies 2018-10-06 21:17:27 -07:00
87b83a3403 update tests, remove unused deps 2018-10-06 21:07:32 -07:00
c368abdf5f remove Json type 2018-10-06 20:34:19 -07:00
ee62814216 split request decoder and payload decoder 2018-10-06 20:31:22 -07:00
10678a22af test content length (#532) 2018-10-06 08:17:20 +03:00
lzx
7ae5a43877 httpresponse.rs doc fix (#534) 2018-10-06 08:16:12 +03:00
c0699a070e add TakeRequest service; update ws test case 2018-10-05 15:40:56 -07:00
7e135b798b add websocket transport and test 2018-10-05 14:30:40 -07:00
5c0a2066cc refactor ws to a websocket codec 2018-10-05 12:47:22 -07:00
8c2244dd88 rename HttpResponse 2018-10-05 11:04:59 -07:00
d53f3d7187 re-enable websockets 2018-10-05 10:20:49 -07:00
2e27d77740 fix connection keepalive support 2018-10-05 10:03:10 -07:00
fbf67544e5 remove unused code 2018-10-05 08:03:25 -07:00
c24a8f4c2d remove high level apis 2018-10-05 07:02:09 -07:00
c99f9eaa63 Update test_h1v2.rs 2018-10-05 05:59:02 -07:00
caa5a54b8f fix test and remove unused code 2018-10-04 23:46:43 -07:00
7fdc18f9b9 calculate response parameters 2018-10-04 23:39:11 -07:00
e78014c65a fix travis link in readme 2018-10-04 21:19:43 -07:00
df50e636f1 update readme 2018-10-04 21:18:36 -07:00
99a915e668 disable gh-pages update 2018-10-04 21:15:24 -07:00
829dbae609 cleanups and tests 2018-10-04 21:14:18 -07:00
4ca711909b refactor types 2018-10-04 20:02:10 -07:00
b15b2dda22 remove ServerSettings 2018-10-04 17:34:57 -07:00
6aa2de7b8d remove actix-web artifacts 2018-10-04 17:00:27 -07:00
13b0ee7355 stopping point 2018-10-04 16:22:00 -07:00
1e1a4f846e use actix-net cell features 2018-10-02 22:23:51 -07:00
49eea3bf76 travis config 2018-10-02 20:22:51 -07:00
b0677aa029 fix stable compatibility 2018-10-02 19:42:24 -07:00
401ea574c0 make AcceptorTimeout::new public 2018-10-02 19:31:30 -07:00
bbcd618304 export AcceptorTimeout 2018-10-02 19:12:08 -07:00
1f68ce8541 fix tests 2018-10-02 19:05:58 -07:00
2710f70e39 add H1 transport 2018-10-02 17:30:29 -07:00
ae5c4dfb78 refactor http channels list; rename WorkerSettings 2018-10-02 15:25:32 -07:00
d7379bd10b update server ssl tests; upgrade rustls 2018-10-02 13:41:33 -07:00
b59712c439 add ssl handshake timeout tests 2018-10-02 11:32:43 -07:00
724668910b fix ssh handshake timeout 2018-10-02 11:18:59 -07:00
61c7534e03 fix stream flushing 2018-10-02 10:43:23 -07:00
f8b176de9e Fix no_http2 flag in HttpServer (#526) 2018-10-02 20:09:31 +03:00
c8505bb53f content-length bug fix (#525)
* content-length bug fix

* changes.md is updated

* typo
2018-10-02 09:15:48 -07:00
eed377e773 uneeded dep 2018-10-02 00:20:27 -07:00
f3ce6574e4 fix client timer and add slow request tests 2018-10-02 00:19:28 -07:00
f007860a16 cleanup warnings 2018-10-01 22:48:11 -07:00
fdfadb52e1 fix doc test for State 2018-10-01 22:29:30 -07:00
368f73513a set tcp-keepalive for test as well 2018-10-01 22:25:53 -07:00
c674ea9126 add StreamConfiguration service 2018-10-01 22:23:02 -07:00
7c78797d9b proper stop for test_ws_stopped test 2018-10-01 21:30:00 -07:00
84edc57fd9 increase sleep time 2018-10-01 21:19:27 -07:00
127af92541 clippy warnings 2018-10-01 21:16:56 -07:00
e4686f6c8d set socket linger to 0 on timeout 2018-10-01 20:53:22 -07:00
1bac65de4c add websocket stopped test 2018-10-01 20:15:26 -07:00
16945a554a add client shutdown timeout 2018-10-01 20:04:16 -07:00
91af3ca148 simplify h1 dispatcher 2018-10-01 19:18:24 -07:00
2217a152cb expose app error by http service 2018-10-01 15:19:49 -07:00
c1e0b4f322 expose internal http server types and allow to create custom http pipelines 2018-10-01 14:43:06 -07:00
5966ee6192 add HttpServer::register() function, allows to register services in actix net server 2018-09-28 16:03:53 -07:00
4aac3d6a92 refactor keep-alive timer 2018-09-28 15:04:59 -07:00
e95babf8d3 log acctor init errors 2018-09-28 12:37:20 -07:00
f2d42e5e77 refactor acceptor error handling 2018-09-28 11:50:47 -07:00
0f1c80ccc6 deprecate start_incoming 2018-09-28 08:45:49 -07:00
fc5088b55e fix tarpaulin args 2018-09-28 00:08:23 -07:00
bec37fdbd5 update travis config 2018-09-27 22:23:29 -07:00
4b59ae2476 fix ssl config for client connector 2018-09-27 22:15:38 -07:00
d0fc9d7b99 simplify listen_ and bind_ methods 2018-09-27 21:55:44 -07:00
1ff86e5ac4 restore rust-tls support 2018-09-27 21:24:21 -07:00
ecfda64f6d add native-tls support 2018-09-27 20:40:34 -07:00
0bca21ec6d fix ssl tests 2018-09-27 19:57:40 -07:00
3173c9fa83 diesable client timeout for tcp stream acceptor 2018-09-27 19:34:07 -07:00
85445ea809 rename and simplify ServiceFactory trait 2018-09-27 18:33:29 -07:00
d57579d700 refactor acceptor pipeline add client timeout 2018-09-27 18:33:29 -07:00
b6a1cfa6ad update openssl support 2018-09-27 18:33:29 -07:00
9f1417af30 refactor http service builder 2018-09-27 18:33:29 -07:00
0aa0f326f7 fix changes from master 2018-09-27 18:33:29 -07:00
dbb4fab4f7 separate mod for HttpHandler; add HttpHandler impl for Vec<H> 2018-09-27 18:33:29 -07:00
6f3e70a92a simplify application factory 2018-09-27 18:33:29 -07:00
a63d3f9a7a cleanup ServerFactory trait 2018-09-27 18:33:29 -07:00
a3cfc24232 refactor acceptor service 2018-09-27 18:33:29 -07:00
6a61138bf8 enable ssl feature 2018-09-27 18:33:29 -07:00
7cf9af9b55 disable ssl for travis 2018-09-27 18:33:29 -07:00
c9a52e3197 refactor date generatioin 2018-09-27 18:33:29 -07:00
1907102685 switch to actix-net server 2018-09-27 18:33:29 -07:00
52195bbf16 update version 2018-09-27 18:17:58 -07:00
59deb4b40d Try to separate HTTP/1 read & write disconnect handling, to fix #511. (#514) 2018-09-27 18:15:02 -07:00
782eeb5ded Reduced unsafe converage (#520) 2018-09-26 11:56:34 +03:00
1b298142e3 Correct composing of multiple origins in cors (#518) 2018-09-21 08:45:22 +03:00
0dc96658f2 Send response to inform client of error (#515) 2018-09-21 07:24:10 +03:00
f40153fca4 fix node::insert() method, missing next element 2018-09-17 11:39:03 -07:00
764103566d update changes 2018-09-17 10:48:37 -07:00
bfb2f2e9e1 fix node.remove(), update next node pointer 2018-09-17 10:25:45 -07:00
599e6b3385 refactor channel node remove operation 2018-09-17 05:29:07 -07:00
03e318f446 update changes 2018-09-15 17:10:53 -07:00
7449884ce3 fix wrong error message for path deserialize for i32 #510 2018-09-15 17:09:07 -07:00
bbe69e5b8d update version 2018-09-15 10:00:54 -07:00
9d1eefc38f use 5 seconds keep-alive timer by default 2018-09-15 09:57:54 -07:00
d65c72b44d use server keep-alive timer as slow request timer 2018-09-15 09:55:38 -07:00
c3f8b5cf22 clippy warnings 2018-09-11 11:25:32 -07:00
70a3f317d3 fix failing requests to test server #508 2018-09-11 11:24:05 -07:00
513c8ec1ce Merge pull request #505 from Neopallium/master
Fix issue with HttpChannel linked list.
2018-09-11 11:18:33 -07:00
04608b2ea6 Update changes. 2018-09-12 00:27:15 +08:00
70b45659e2 Make Node's traverse method take a closure instead of calling shutdown on each HttpChannel. 2018-09-12 00:27:15 +08:00
e0ae6b10cd Fix bug with HttpChannel linked list. 2018-09-12 00:27:15 +08:00
003b05b095 Don't ignore errors in std::fmt::Debug implementations (#506) 2018-09-11 14:57:55 +03:00
cdb57b840e prepare release 2018-09-07 20:47:54 -07:00
002bb24b26 unhide SessionBackend and SessionImpl traits and cleanup warnings 2018-09-07 20:46:43 -07:00
51982b3fec Merge pull request #503 from uzytkownik/route-regex
Refactor resource route parsing to allow repetition in the regexes
2018-09-07 20:19:31 -07:00
4251b0bc10 Refactor resource route parsing to allow repetition in the regexes 2018-09-06 08:51:55 +02:00
42f3773bec update changes 2018-09-05 09:03:58 -07:00
86fdbb47a5 Fix system_exit in HttpServer (#501) 2018-09-05 10:41:23 +02:00
4ca9fd2ad1 remove debug print 2018-09-03 22:09:12 -07:00
f0f67072ae Read client response until eof if connection header set to close #464 2018-09-03 21:35:59 -07:00
24d1228943 simplify handler path processing 2018-09-03 11:28:47 -07:00
b7a73e0a4f fix Scope::handler doc test 2018-09-02 08:51:26 -07:00
968c81e267 Handling scoped paths without leading slashes #460 2018-09-02 08:14:54 -07:00
d5957a8466 Merge branch 'master' of https://github.com/actix/actix-web 2018-09-02 07:47:45 -07:00
f2f05e7715 allow to register handlers on scope level #465 2018-09-02 07:47:19 -07:00
3439f55288 doc: Add example for using custom nativetls connector (#497) 2018-09-01 18:13:52 +03:00
0425e2776f Fix Issue #490 (#498)
* Add failing testcase for HTTP 404 response with no reason text.

* Include canonical reason test for HTTP error responses.

* Don't send a reason for unknown status codes.
2018-09-01 12:00:32 +03:00
6464f96f8b Merge branch 'master' of https://github.com/actix/actix-web 2018-08-31 18:56:53 -07:00
a2b170fec9 fmt 2018-08-31 18:56:21 -07:00
0b42cae082 update tests 2018-08-31 18:54:19 -07:00
c313c003a4 Fix typo 2018-08-31 17:45:29 -07:00
3fa23f5e10 update version 2018-08-31 17:25:15 -07:00
2d51831899 handle socket read disconnect 2018-08-31 17:24:13 -07:00
e59abfd716 Merge pull request #496 from Neopallium/master
Fix issue with 'Connection: close' in ClientRequest
2018-08-31 17:17:39 -07:00
66881d7dd1 If buffer is empty, read more data before calling parser. 2018-09-01 02:25:05 +08:00
a42a8a2321 Add some comments to clarify logic. 2018-09-01 02:15:36 +08:00
2341656173 Simplify buffer reading logic. Remove duplicate code. 2018-09-01 01:41:38 +08:00
487519acec Add client test for 'Connection: close' as reported in issue #495 2018-09-01 00:34:19 +08:00
af6caa92c8 Merge branch 'master' into master 2018-09-01 00:17:34 +08:00
3ccbce6bc8 Fix issue with 'Connection: close' in ClientRequest 2018-09-01 00:08:53 +08:00
797b52ecbf Update CHANGES.md 2018-08-29 20:58:23 +02:00
4bab50c861 Add ability to pass a custom TlsConnector (#491) 2018-08-29 20:53:31 +02:00
5906971b6d Merge pull request #483 from Neopallium/master
Fix bug with client disconnect immediately after receiving http request.
2018-08-26 10:15:25 -07:00
8393d09a0f Fix tests. 2018-08-27 00:31:31 +08:00
c3ae9997fc Fix bug with http1 client disconnects. 2018-08-26 22:21:05 +08:00
d39dcc58cd Merge pull request #482 from 0x1793d1/master
Fix server startup log message
2018-08-24 20:53:45 -07:00
471a3e9806 Fix server startup log message 2018-08-24 23:21:32 +02:00
48ef18ffa9 update changes 2018-08-23 12:54:59 -07:00
9ef7a9c182 hide AcceptorService 2018-08-23 11:30:49 -07:00
3dafe6c251 hide token and server flags 2018-08-23 11:30:07 -07:00
8dfc34e785 fix tokio-tls IoStream impl 2018-08-23 10:27:32 -07:00
810995ade0 fix tokio-tls dependency #480 2018-08-23 10:10:13 -07:00
1716380f08 clippy fmt 2018-08-23 09:48:01 -07:00
e9c139bdea clippy warnings 2018-08-23 09:47:32 -07:00
cf54be2f17 hide new server api 2018-08-23 09:39:11 -07:00
f39b520a2d Merge pull request #478 from fzgregor/master
Made extensions constructor public
2018-08-23 09:34:47 -07:00
89f414477c Merge branch 'master' into master 2018-08-23 09:34:34 -07:00
986f19af86 Revert back to serde_urlencoded dependecy (#479) 2018-08-21 22:23:17 +03:00
e680541e10 Made extensions constructor public 2018-08-18 19:32:28 +02:00
56bc900a82 Set minimum rustls version that fixes corruption (#474) 2018-08-17 19:53:16 +03:00
bdc9a8bb07 Optionally support tokio-uds's UnixStream as IoStream (#472) 2018-08-17 19:04:15 +03:00
8fe30a5b66 Merge pull request #473 from kornelski/usetest
Fix tests on Unix
2018-08-17 07:20:47 -07:00
a8405d0686 Fix tests on Unix 2018-08-17 13:13:48 +01:00
eb1e9a785f allow to use fn with multiple arguments with .with()/.with_async() 2018-08-16 20:29:06 -07:00
248bd388ca Improve HTTP server docs (#470) 2018-08-16 16:11:15 +03:00
9f5641c85b Add mention of reworked Content-Disposition 2018-08-13 17:37:00 +03:00
d9c7cd96a6 Rework Content-Disposition parsing totally (#461) 2018-08-13 17:34:05 +03:00
bf7779a9a3 add TestRequest::run_async_result helper method 2018-08-09 18:58:14 -07:00
cc3fbd27e0 better ergonomics 2018-08-09 17:25:23 -07:00
26629aafa5 explicit use 2018-08-09 13:41:13 -07:00
2ab7dbadce better ergonomics for Server::service() method 2018-08-09 13:38:10 -07:00
2e8d67e2ae upgrade native-tls package 2018-08-09 13:08:59 -07:00
43b6828ab5 Merge branch 'master' of https://github.com/actix/actix-web 2018-08-09 11:52:45 -07:00
e4ce6dfbdf refactor workers management 2018-08-09 11:52:32 -07:00
6b9fa2c3d9 Merge pull request #458 from davidMcneil/master
Add json2 HttpResponseBuilder method
2018-08-09 02:10:14 -07:00
5713d93158 Merge branch 'master' into master 2018-08-09 08:13:22 +03:00
cfe4829a56 add TestRequest::execute() helper method 2018-08-08 16:13:45 -07:00
b69774db61 fix attr name 2018-08-08 14:23:16 -07:00
542782f28a add HttpRequest::drop_state() 2018-08-08 13:57:13 -07:00
7c8dc4c201 Add json2 tests 2018-08-08 12:17:19 -06:00
7a11c2eac1 Add json2 HttpResponseBuilder method 2018-08-08 11:11:15 -06:00
8eb9eb4247 flush io on complete 2018-08-08 09:12:32 -07:00
992f7a11b3 remove debug println 2018-08-07 22:40:09 -07:00
30769e3072 fix http/2 error handling 2018-08-07 20:48:25 -07:00
57f991280c fix protocol order for rustls acceptor 2018-08-07 13:53:24 -07:00
85acc3f8df deprecate HttpServer::no_http2(), update changes 2018-08-07 12:49:40 -07:00
5bd82d4f03 update changes 2018-08-07 12:00:51 -07:00
58a079bd10 include content-length to error response 2018-08-07 11:56:39 -07:00
16546a707f Merge pull request #453 from DoumanAsh/reserve_status_line_for_server_error
Reserve enough space for ServerError task to write status line
2018-08-07 11:48:55 -07:00
86a5afb5ca Reserve enough space for ServerError task to write status line 2018-08-07 17:34:24 +03:00
9c80d3aa77 Write non-80 port in HOST of client's request (#451) 2018-08-07 10:01:29 +03:00
954f1a0b0f impl FromRequest for () (#449) 2018-08-06 10:44:08 +03:00
f4fba5f481 Merge pull request #447 from DoumanAsh/multiple_set_cookies
Correct setting cookies in HTTP2 writer
2018-08-04 08:58:12 -07:00
995f819eae Merge branch 'master' into multiple_set_cookies 2018-08-04 08:58:00 -07:00
85e7548088 fix adding multiple response headers for http/2 #446 2018-08-04 08:56:33 -07:00
900fd5a98e Correct settings headers for HTTP2
Add test to verify number of Set-Cookies
2018-08-04 18:05:41 +03:00
84b27db218 fix no_http2 flag 2018-08-03 19:40:43 -07:00
ac9180ac46 simplify channel impl 2018-08-03 19:32:46 -07:00
e34b5c08ba allow to pass extra information from acceptor to application level 2018-08-03 19:24:53 -07:00
f3f1e04853 refactor ssl support 2018-08-03 16:09:46 -07:00
036cf5e867 update changes 2018-08-03 08:20:59 -07:00
e61ef7dee4 Use zlib instead of deflate for content encoding (#442) 2018-08-03 14:56:26 +02:00
9a10d8aa7a Fixed headers' formating for CORS Middleware Access-Control-Expose-Headers header value to HTTP/1.1 & HTTP/2 spec-compliant format (#436) 2018-08-03 15:03:11 +03:00
f8e5d7c6c1 Fixed broken build on wrong variable usage (#440) 2018-08-03 14:11:51 +03:00
8c89c90c50 add accept backpressure #250 2018-08-02 23:17:10 -07:00
e9c1889df4 test timing 2018-08-01 16:41:24 -07:00
0da3fdcb09 do not use Arc for rustls config 2018-08-01 10:59:00 -07:00
a5f80a25ff update changes 2018-08-01 10:51:47 -07:00
6d9a1cadad Merge pull request #433 from jrconlin/feat/432
feature: allow TestServer to open a websocket on any URL
2018-08-01 10:45:55 -07:00
97ada3d3d0 Merge branch 'feat/432' of github.com:jrconlin/actix-web into feat/432 2018-08-01 10:27:48 -07:00
115f59dd14 Merge branch 'master' of https://github.com/actix/actix-web into feat/432 2018-08-01 09:59:36 -07:00
972b008a6e remove unsafe error transmute, upgrade failure to 0.1.2 #434 2018-08-01 09:42:12 -07:00
246eafb8d2 Merge branch 'master' of https://github.com/actix/actix-web into feat/432 2018-08-01 09:36:08 -07:00
dca4c110dd feature: allow TestServer to open a websocket on any URL
* added `TestServer::ws_at(uri_str)`
* modified `TestServer::ws()` to call `self.ws_at("/")` to preserve
behavior

Closes #432
2018-08-01 09:30:27 -07:00
58230b15b9 use one thread for accept loop; refactor rust-tls support 2018-07-31 19:51:26 -07:00
aa1e75f071 feature: allow TestServer to open a websocket on any URL
* added `TestServer::ws_at(uri_str)`
* modified `TestServer::ws()` to call `self.ws_at("/")` to preserve
behavior

Closes #432
2018-07-31 16:21:18 -07:00
2071ea0532 HttpRequest::url_for is not working with scopes #429 2018-07-31 15:40:52 -07:00
3bd43090fb use new gzdecoder, fixes gz streaming #228 2018-07-31 09:06:05 -07:00
4dba531bf9 do not override HOST header for client request #428 2018-07-31 08:51:24 -07:00
2072c933ba handle error during request creation 2018-07-30 15:04:52 -07:00
7bc0ace52d move server accept impl to seprate module 2018-07-30 13:42:42 -07:00
4c4d0d2745 update changes 2018-07-30 10:23:28 -07:00
28a855214b Merge pull request #427 from jeizsm/feature/rustls
add rustls
2018-07-30 10:21:37 -07:00
196da6d570 add rustls 2018-07-30 08:21:12 +03:00
b4ed564e5d update changes 2018-07-26 09:11:50 -07:00
80fbc2e9ec Fix stream draining for http/2 connections #290 2018-07-25 15:38:02 -07:00
f58065082e fix missing content-encoding header for h2 connections #421 2018-07-25 10:30:55 -07:00
6048817ba7 Correct flate feature names in documentation 2018-07-25 20:22:18 +03:00
e408b68744 Update cookie dependency (#422) 2018-07-25 18:01:22 +03:00
b878613e10 fix warning 2018-07-24 15:49:46 -07:00
85b275bb2b fix warnings 2018-07-24 15:09:30 -07:00
d6abd2fe22 allow to handle empty path for application with prefix 2018-07-24 14:51:48 -07:00
b79a9aaec7 fix changelog 2018-07-24 14:18:04 -07:00
b9586b3f71 Merge pull request #412 from gdamjan/master
remove the timestamp from the default logger middleware
2018-07-24 14:07:10 -07:00
d3b12d885e Merge branch 'master' into master 2018-07-24 14:07:03 -07:00
f21386708a Merge pull request #416 from axos88/master
Add FromRequest<S> implementation for Option<T> and Result<T> where T: FromRequest<S>
2018-07-24 14:06:08 -07:00
b48a2d4d7b add changes to CHANGES.md 2018-07-24 22:25:48 +02:00
35b754a3ab pr fixes 2018-07-24 09:42:46 +02:00
1079c5c562 Add FromRequest<S> implementation for Result<T> and Option<T> where T:FromRequest<S> 2018-07-24 09:42:46 +02:00
f4bb7efa89 add partialeq, eq, partialord and ord dervie to Path, Form and Query 2018-07-24 09:42:46 +02:00
0099091e96 remove unnecessary use 2018-07-24 09:42:46 +02:00
c352a69d54 fix dead links 2018-07-23 13:22:16 -07:00
f5347ec897 Merge pull request #415 from DenisKolodin/cookie-http-only
Add http_only flag to CookieSessionBackend
2018-07-23 02:54:23 -07:00
b367f07d56 Add http_only flag to CookieSessionBackend 2018-07-23 12:49:59 +03:00
6a75a3d683 document the change in the default logger 2018-07-21 16:01:42 +02:00
56b924e155 remove the timestamp from the default logger middleware
env_logger and other logging systems will (or should) already add their
own timestamp.
2018-07-21 15:15:28 +02:00
4862227df9 fix not implemented panic #410 2018-07-21 05:58:08 -07:00
f6499d9ba5 publish stable docs on actix.rs site 2018-07-21 04:19:02 -07:00
7138bb2f29 update migration 2018-07-21 01:00:50 -07:00
8cb510293d update changes 2018-07-20 14:10:41 -07:00
040d9d2755 Merge branch 'master' of github.com:actix/actix-web 2018-07-20 12:43:44 -07:00
2043bb5ece do not reallocate waiters 2018-07-20 10:20:41 -07:00
a751df2589 Initial config for static files (#405) 2018-07-20 07:49:25 +03:00
f6e35a04f0 Just a bit of sanity check for short paths (#409) 2018-07-20 07:48:57 +03:00
0925a7691a ws/context: Increase write() visibility to public (#402)
This type is introduced to avoid confusion between the `.binary()` and `.write_raw()` methods on WebSocket contexts
2018-07-19 20:04:13 +03:00
2988a84e5f Expose leaked private ContentDisposition (#406) 2018-07-19 20:03:45 +03:00
6b10e1eff6 rename PayloadHelper 2018-07-18 10:01:28 +06:00
85672d1379 fix client connector wait queue 2018-07-18 01:23:56 +06:00
373f2e5028 add release stat 2018-07-17 17:38:16 +06:00
f9f259e718 Merge branch 'master' of github.com:actix/actix-web 2018-07-17 17:23:23 +06:00
d43902ee7c proper handling for client connection release 2018-07-17 17:23:03 +06:00
a7ca5fa5d8 Add few missing entries to changelog 2018-07-17 11:10:04 +03:00
29a275b0f5 Session should write percent encoded cookies and add cookie middleware test (#393)
* Should write percent encoded cookies to HTTP response

* Add cookie middleware test
2018-07-17 08:38:18 +03:00
1af5aa3a3e calculate client request timeout 2018-07-17 02:30:21 +06:00
bccd7c7671 add wait queue size stat to client connector 2018-07-17 01:57:57 +06:00
2a8c2fb55e export Payload 2018-07-16 12:14:24 +06:00
2dd57a48d6 checks nested scopes in has_resource() 2018-07-16 11:33:29 +06:00
22385505a3 clippy warnings and fmt 2018-07-16 11:17:45 +06:00
5888f01317 use has_prefixed_route for NormalizePath helper 2018-07-16 11:13:41 +06:00
b7a3fce17b simplify has_prefixed_route() 2018-07-16 11:10:51 +06:00
bce05e4fcb Merge pull request #381 from OtaK/fix/has_route_prefixes
Add prefix aware RouteInfo::has_prefixed_route()
2018-07-16 10:58:50 +06:00
3373847a14 allocate buffer for request payload extractors 2018-07-16 00:40:22 +06:00
8f64508887 Added RouteInfo::has_prefixed_route() method for route matching with prefix awareness 2018-07-15 19:37:20 +02:00
30c84786b7 Merge pull request #399 from actix/router-refactor
Router refactoring
2018-07-15 19:16:07 +06:00
2e5f627050 do not force install tarpaulin 2018-07-15 19:15:36 +06:00
2214492792 use assert and restore test case 2018-07-15 18:53:02 +06:00
c43b6e3577 cargo tarpaulin 2018-07-15 16:39:15 +06:00
42d3e86941 calculate prefix dynamicly 2018-07-15 16:25:56 +06:00
b759dddf5a simplify application prefix impl 2018-07-15 16:25:56 +06:00
9570c1cccd rename RouteInfo 2018-07-15 16:25:56 +06:00
da915972c0 refactor router 2018-07-15 16:25:56 +06:00
cf976d296f Merge pull request #397 from actix/Turbo87-patch-1
error: Fix documentation typo
2018-07-14 09:38:43 +06:00
9012cf43fe error: Fix documentation typo 2018-07-14 00:05:07 +02:00
7d753eeb8c Private serde fork (#390)
* Fork serde_urlencoded

* Apply enum PR https://github.com/nox/serde_urlencoded/pull/30

* Add test to verify enum in query

* Docs are updated to show example of how to use enum.
2018-07-13 09:59:09 +03:00
4395add1c7 update travis config 2018-07-13 00:05:01 +06:00
35911b832a Merge branch 'master' of github.com:actix/actix-web 2018-07-12 23:59:10 +06:00
b8b90d9ec9 rename ResourceHandler to Resource 2018-07-12 15:30:01 +06:00
422a870cd7 Merge pull request #387 from actix/fix-missing-content-length
fix missing content length
2018-07-12 16:18:55 +10:00
db005af1af clippy warnings 2018-07-12 10:41:49 +06:00
8e462c5944 use write instead format 2018-07-12 10:35:09 +06:00
86e44de787 pin failure crate 2018-07-12 10:29:37 +06:00
d9988f3ab6 fix missing content length
fix missing content length when no compression is used
2018-07-11 21:21:32 +10:00
696152f763 Merge pull request #377 from Diggsey/apply-mask
Refactor `apply_mask` implementation, removing dead code paths and re…
2018-07-11 13:36:08 +06:00
f38a370b94 update changes 2018-07-11 13:34:40 +06:00
28b36c650a fix h2 compatibility 2018-07-11 13:25:07 +06:00
b22132d3d6 Merge branch 'master' into apply-mask 2018-07-11 13:15:35 +06:00
19ae5e9489 Merge branch 'master' of github.com:actix/actix-web 2018-07-11 12:56:53 +06:00
9aef34e768 remove & to &mut transmute #385 2018-07-11 12:56:35 +06:00
bed961fe35 Lessen numbers of jobs for AppVeyor 2018-07-11 09:23:17 +03:00
87824a9cf6 Refactor apply_mask implementation, removing dead code paths and reducing scope of unsafety 2018-07-08 13:56:43 +01:00
82920e1ac1 Do not override user settings on signals and stop handling (#375) 2018-07-08 09:01:44 +03:00
110605f50b stop actor context on error #311 2018-07-08 09:41:55 +06:00
00c97504b6 Merge pull request #368 from Diggsey/master
Remove reimplementation of `LazyCell`
2018-07-07 09:46:44 +06:00
85012f947a Remove reimplementation of LazyCell 2018-07-06 22:28:08 +01:00
62ba01fc15 update changes 2018-07-06 15:00:14 +06:00
5b7aed101a remove unsafe 2018-07-06 13:54:43 +06:00
1c3b32169e remove stream from WebsocketsContext::with_factory 2018-07-06 12:11:40 +06:00
cfa470db50 close conneciton for head requests 2018-07-06 09:21:24 +06:00
a5f7a67b4d clippy warnings 2018-07-06 08:24:44 +06:00
185e710dc8 do not drop content-encoding header in case of identity #363 2018-07-06 08:24:36 +06:00
9070d59ea8 do not read head payload 2018-07-06 08:11:36 +06:00
2a25caf2c5 Merge branch 'master' of github.com:actix/actix-web 2018-07-06 07:49:50 +06:00
7d96b92aa3 add check for usize cast 2018-07-06 07:46:47 +06:00
67e4cad281 Introduce method to set header if it is missing only (#364)
Also let default headers use it.

Closes #320
2018-07-05 19:27:18 +03:00
080f232a0f Use StaticFile default handler when file is inaccessible (#357)
* Use Staticfile default handler on all error paths

* Return an error from StaticFiles::new() if directory doesn't exist
2018-07-05 12:34:13 +03:00
ac3a76cd32 update httparse version 2018-07-05 13:21:33 +06:00
8058d15624 clippy warnings 2018-07-05 13:16:16 +06:00
05a43a855e remove unsafe 2018-07-05 13:00:46 +06:00
80339147b9 call disconnect on write error 2018-07-05 12:50:54 +06:00
6af2f5d642 re-enable start_incoming support 2018-07-05 12:14:10 +06:00
d7762297da update actix dependency 2018-07-05 12:02:32 +06:00
d5606625a2 remove public Clone for Request 2018-07-04 22:57:40 +06:00
5d79114239 optimize Request handling 2018-07-04 22:52:49 +06:00
f559f23e1c Merge branch 'master' of github.com:actix/actix-web 2018-07-04 21:02:40 +06:00
6fd686ef98 cleanup warnings 2018-07-04 21:01:27 +06:00
4c5a63965e use new actix context api 2018-07-04 17:04:23 +06:00
09aabc7b3b plain/text -> text/plain in comment (#362) 2018-07-04 11:17:44 +03:00
b6d26c9faf Merge pull request #348 from actix/request-mutability
Request mutability
2018-07-02 23:52:42 +06:00
fec6047ddc refactor HttpRequest mutability 2018-07-02 23:35:32 +06:00
445ea043dd remove unsafes 2018-07-02 23:32:29 +06:00
0be5448597 Properly escape special characters in fs/directory_listing. (#355) 2018-06-30 15:01:48 +03:00
0f27389e72 set length of vector to max_bytes (closes #345) (#346) 2018-06-26 08:09:12 +03:00
a9425a866b Fix duplicate tail of StaticFiles with index_file
Map from 0.6 to master
2018-06-25 19:59:55 +03:00
800c404c72 explicit response release 2018-06-25 10:10:02 +06:00
32212bad1f simplify http response pool 2018-06-25 09:08:28 +06:00
d1b73e30e0 update comments 2018-06-24 22:27:30 +06:00
c0cdc39ba9 do not store cookies on client response 2018-06-24 22:21:04 +06:00
8e8a68f90b add empty output stream 2018-06-24 22:05:44 +06:00
989cd61236 handle empty te 2018-06-24 10:59:01 +06:00
33260c7b35 split encoding module 2018-06-24 10:42:20 +06:00
40ca9ba9c5 simplify write buffer 2018-06-24 10:30:58 +06:00
45682c04a8 refactor content encoder 2018-06-24 08:54:01 +06:00
348491b18c fix alpn connector 2018-06-23 17:59:45 +06:00
3d2226aa9e Merge branch 'master' of github.com:actix/actix-web 2018-06-23 12:40:45 +06:00
cf38183dcb refactor client connector waiters maintenance 2018-06-23 12:40:21 +06:00
e3dc6f0ca8 refactor h1decoder 2018-06-23 12:28:55 +06:00
a5369aed8b Changes a leaked box into an Rc<String> and makes resource() return an Option (#343) 2018-06-23 08:16:52 +02:00
ff0ab733e4 remove unsafe from mask 2018-06-23 11:51:02 +06:00
d1318a35a0 remove unnecessary unsafes 2018-06-23 10:29:23 +06:00
756227896b update set_date impl 2018-06-23 10:13:09 +06:00
4fadff63f4 Use Box::leak for dynamic param names 2018-06-23 09:57:03 +06:00
7bc7b4839b Switch from fnv to a identity hasher in extensions (#342) 2018-06-22 11:32:32 +02:00
dda6ee95df Changes the router to use atoms internally (#341) 2018-06-22 09:33:32 +02:00
765c38e7b9 remove libc dependency 2018-06-22 11:47:33 +06:00
6c44575923 transmute names once 2018-06-22 11:44:38 +06:00
fc7238baee refactor read_from_io 2018-06-22 11:30:40 +06:00
edd22bb279 refactor read_from_io 2018-06-22 09:01:20 +06:00
17c033030b Revert "remove unnecessary use of unsafe in read_from_io"
This reverts commit da237611cb.
2018-06-22 08:55:19 +06:00
3afdf3fa7e Merge pull request #335 from gnzlbg/fix_unsafe
remove unnecessary use of unsafe in read_from_io
2018-06-22 07:23:14 +06:00
50fbef88fc cleanup srver pipeline 2018-06-21 23:51:25 +06:00
c9069e9a3c remove unneeded UnsafeCell 2018-06-21 23:21:28 +06:00
65ca563579 use read only self for Middleware 2018-06-21 23:06:23 +06:00
3de9284592 Handler::handle uses &self instead of mutabble reference 2018-06-21 17:07:54 +06:00
5a9992736f Merge pull request #339 from joshleeb/propogate-scope-default-resource
Propagate scope default resource
2018-06-21 15:40:02 +06:00
0338767264 Update CHANGES for default scope propagation 2018-06-21 19:37:34 +10:00
c5e8c1b710 Propagate default resources to underlying scopes 2018-06-21 18:17:27 +10:00
b5594ae2a5 Fix doc api example 2018-06-21 14:11:00 +06:00
58d1f4a4aa switch to actix master 2018-06-21 13:34:36 +06:00
b7d813eeba update tests 2018-06-21 12:04:00 +06:00
8e160ebda7 clippy warning 2018-06-21 11:49:36 +06:00
0093b7ea5a refactor extractor configuration #331 2018-06-21 11:47:01 +06:00
75eec8bd4f fix condition 2018-06-21 11:23:21 +06:00
ebc59cf7b9 add unsafe checks #331 2018-06-21 11:20:21 +06:00
c2c4a5ba3f fix failure Send+Sync compatibility 2018-06-21 10:45:24 +06:00
dbd093075d Merge pull request #338 from tbroadley/fix-typos
Fix typos
2018-06-21 10:13:30 +06:00
1be27e17f8 convert timer error to io error 2018-06-21 10:05:20 +06:00
8b0fbb85d1 SendRequest execution fails with the entered unreachable code #329 2018-06-21 09:52:18 +06:00
cfe6725eb4 Allow to disable masking for websockets client 2018-06-21 09:49:33 +06:00
f815c1c096 Add test for default_resource scope propagation 2018-06-21 13:10:40 +10:00
280eae4335 Merge pull request #334 from Vurich/master
Fix some unsoundness
2018-06-21 07:15:33 +06:00
bd8cbfff35 docs: fix typos 2018-06-20 21:05:26 -04:00
da237611cb remove unnecessary use of unsafe in read_from_io 2018-06-20 13:14:53 +02:00
Jef
234c60d473 Fix some unsoundness
This improves the sound implementation of `fn route`.
Previously this function would iterate twice but we
can reduce the overhead without using `unsafe`.
2018-06-20 10:53:18 +02:00
2f917f3700 various cleanups and comments 2018-06-20 01:27:41 +06:00
311f0b23a9 cleanup more code 2018-06-20 00:36:32 +06:00
a69c1e3de5 remove unsafe from scope impl 2018-06-19 23:46:58 +06:00
c427fd1241 Merge pull request #328 from xfix/remove-some-uses-of-unsafe-from-frame-message
Remove some uses of unsafe from Frame::message
2018-06-19 21:52:41 +06:00
adcb4e1492 Merge pull request #327 from xfix/remove-use-of-unsafe-from-pipeline-poll
Remove use of unsafe from Pipeline#poll
2018-06-19 19:58:15 +06:00
3b1124c56c Merge branch 'master' into remove-some-uses-of-unsafe-from-frame-message 2018-06-19 19:20:40 +06:00
cafde76361 Merge branch 'master' into remove-use-of-unsafe-from-pipeline-poll 2018-06-19 19:20:25 +06:00
bfb93cae66 Update connector.rs 2018-06-19 19:19:31 +06:00
b5c1e42feb Merge branch 'master' into remove-use-of-unsafe-from-pipeline-poll 2018-06-19 18:30:37 +06:00
e884e7e84e Remove some uses of unsafe from Frame::message 2018-06-19 14:11:53 +02:00
877e177b60 Remove use of unsafe from Pipeline#poll 2018-06-19 13:42:44 +02:00
27b6af2800 refactor route matching 2018-06-19 16:45:26 +06:00
5c42b0902f better doc api examples 2018-06-19 12:07:07 +06:00
247e8727cb ClientBody is not needed 2018-06-19 10:15:16 +06:00
362b14c2f7 remove unsafe cell from ws client 2018-06-19 09:36:17 +06:00
261ad31b9a remove some unsafe code 2018-06-19 07:44:01 +06:00
68cd5bdf68 use actix 0.6 2018-06-18 09:18:03 +06:00
26f37ec2e3 refactor HttpHandlerTask trait 2018-06-18 05:45:54 +06:00
ef15646bd7 refactor edfault cpu pool 2018-06-18 04:56:18 +06:00
a5bbc455c0 cleanup mut transform 2018-06-18 04:41:41 +06:00
6ec8352612 method only for tests 2018-06-18 01:05:02 +06:00
f0f19c14d2 remove wsclient 2018-06-18 01:03:47 +06:00
daed502ee5 make mut api private 2018-06-18 01:03:07 +06:00
9d114d785e remove Clone from ExtractorConfig 2018-06-18 00:19:07 +06:00
ea118edf56 do not use references in ConnectionInfo 2018-06-18 00:01:41 +06:00
e1db47d550 refactor server settings 2018-06-17 23:51:20 +06:00
38fe8bebec fix doc string 2018-06-17 08:57:51 +06:00
c3f295182f use HashMap for HttpRequest::query() 2018-06-17 08:54:30 +06:00
b6ed778775 remove HttpMessage::range() 2018-06-17 08:48:50 +06:00
0f2aac1a27 remove unneed Send and Sync 2018-06-17 08:32:22 +06:00
70244c29e0 update doc api examples 2018-06-17 04:09:07 +06:00
a7a062fb68 clippy warnings 2018-06-17 03:26:34 +06:00
f3a73d7dde update changelog 2018-06-17 03:24:08 +06:00
879b2b5bde port Extensions from http crate #315 2018-06-17 03:22:08 +06:00
33050f55a3 remove Context::actor() method 2018-06-17 03:10:44 +06:00
e4443226f6 update actix usage 2018-06-17 02:58:56 +06:00
342a194605 fix handling ServerCommand #316 2018-06-16 22:56:27 +06:00
566b16c1f7 Merge branch 'master' of github.com:actix/actix-web 2018-06-14 11:42:27 +02:00
8261cf437d update actix api 2018-06-13 23:37:19 -07:00
8a8e6add08 Merge pull request #314 from DJMcNab/app-cleanup
remove duplication of `App::with_state` in `App::new`
2018-06-14 01:19:56 +03:00
b79307cab1 Merge branch 'master' into app-cleanup 2018-06-14 01:01:11 +03:00
4c646962a9 Merge pull request #312 from eddomuke/master
Add HttpMessage::readlines()
2018-06-14 00:40:29 +03:00
cb77f7e688 Add HttpMessage::readlines() 2018-06-14 00:19:48 +03:00
1bee528018 move ReadlinesError to error module 2018-06-13 22:59:36 +03:00
ad9aacf521 change poll method of Readlines 2018-06-13 22:41:35 +03:00
f8854f951c remove duplication of App::with_state in App::new 2018-06-13 20:31:20 +01:00
6d95e34552 add HttpMessage::readlines() 2018-06-13 20:45:31 +03:00
6c765739d0 add HttpMessage::readlines() 2018-06-13 20:43:03 +03:00
c8528e8920 Merge pull request #308 from eddomuke/master
Allow to override Form extractor error
2018-06-13 01:53:32 +03:00
0a080d9fb4 add test for form extractor 2018-06-13 01:33:28 +03:00
45b408526c Merge branch 'master' into master 2018-06-13 00:53:46 +03:00
1a91854270 Merge branch 'master' of github.com:actix/actix-web 2018-06-12 14:50:41 -07:00
99092fdf06 http/2 end-of-frame is not set if body is empty bytes #307 2018-06-12 14:50:21 -07:00
748ff389e4 Allow to override Form extractor error 2018-06-13 00:47:47 +03:00
b679b4cabc Merge pull request #306 from eddomuke/master
add ClientRequestBuilder::form()
2018-06-12 13:33:16 -07:00
ed7cbaa772 fix form_extractor test 2018-06-12 23:04:54 +03:00
e6bbda0efc add serialize 2018-06-12 22:42:15 +03:00
94283a73c2 make into_string, to_string 2018-06-12 22:31:33 +03:00
ffca416463 add test for ClientRequestBuilder::form() 2018-06-12 22:16:20 +03:00
9cc7651c22 add change to CHANGES.md 2018-06-12 20:32:16 +03:00
8af082d873 remove FormPayloadError 2018-06-12 20:26:09 +03:00
d4d3add17d add ClientRequestBuilder::form() 2018-06-12 19:30:00 +03:00
ce6f9e848b Merge pull request #305 from axon-q/response-cookies
Add HttpResponse methods to retrieve, add, and delete cookies
2018-06-12 14:39:06 +00:00
d8e1fd102d add cookie methods to HttpResponse 2018-06-12 13:56:53 +00:00
e414a52b51 content_disposition: remove unnecessary allocations 2018-06-12 13:48:23 +00:00
4d69e6d0b4 fs: minor cleanups to content_disposition 2018-06-12 13:47:49 +00:00
6f38d769a8 Merge pull request #304 from kazcw/master
fix url in example
2018-06-12 03:58:48 -07:00
48f77578ea fix url in example 2018-06-11 21:55:05 -07:00
9b012b3304 do not allow stream or actor responses for internal error #301 2018-06-11 19:45:17 -07:00
a0344eebeb InternalError can trigger memory unsafety #301 2018-06-11 18:54:36 -07:00
b9f6c313d4 Merge branch 'master' of github.com:actix/actix-web 2018-06-11 12:56:33 -07:00
ef420a8bdf fix docs.rs 2018-06-11 12:21:09 -07:00
0d54b6f38e Implement Responder for Option #294 (#297) 2018-06-11 14:05:41 +03:00
9afc3b6737 api docs link 2018-06-10 10:31:19 -07:00
ef88fc78d0 Merge branch 'master' of github.com:actix/actix-web 2018-06-10 10:25:05 -07:00
9dd66dfc22 better name for error 2018-06-10 10:24:34 -07:00
87a822e093 fix deprecated warnings 2018-06-10 10:14:13 -07:00
3788887c92 Merge pull request #293 from axon-q/static-file-updates
Better Content-Type and Content-Disposition handling for static files
2018-06-09 08:51:42 -07:00
785d0e24f0 Merge branch 'master' into static-file-updates 2018-06-09 08:21:34 -07:00
818d0bc187 new StreamHandler impl 2018-06-09 07:53:46 -07:00
aee24d4af0 minor syntax changes 2018-06-09 14:47:06 +00:00
fee203b402 update changelog 2018-06-09 14:02:05 +00:00
8681a346c6 fs: refactor Content-Type and Content-Disposition handling 2018-06-09 13:56:01 +00:00
1fdf6d13be content_disposition: add doc example 2018-06-09 13:38:21 +00:00
3751656722 expose fs::file_extension_to_mime() function 2018-06-09 11:20:06 +00:00
9151d61eda allow to use custom resolver for ClientConnector 2018-06-08 16:33:57 -07:00
4fe2f6b763 Merge pull request #284 from axon-q/multipart-content-disposition
multipart: parse and validate Content-Disposition
2018-06-07 21:20:18 -07:00
5a7902ff9a Merge branch 'master' into multipart-content-disposition 2018-06-07 21:20:11 -07:00
172b514fef Merge pull request #288 from memoryruins/patch-1
Update TechEmpower benchmarks to round 16
2018-06-07 21:09:49 -07:00
efb5d13280 readme: link to TechEmpower r16 benchmarks 2018-06-07 23:55:08 -04:00
f9f2ed04ab fix doc test 2018-06-07 20:22:23 -07:00
ce40ab307b update changes 2018-06-07 20:09:08 -07:00
f7ef8ae5a5 add Host predicate 2018-06-07 20:00:54 -07:00
60d40df545 fix clippy warning 2018-06-07 19:46:46 -07:00
f7bd6eeedc add application filters 2018-06-07 19:46:38 -07:00
a11f3c112f fix doc test 2018-06-07 21:18:51 +00:00
e9f59bc7d6 Merge branch 'master' into multipart-content-disposition 2018-06-07 11:02:53 -07:00
e970846167 update changelog 2018-06-07 17:59:35 +00:00
56e0dc06c1 defer parsing until user method call 2018-06-07 17:29:46 +00:00
789af0bbf2 Added improved failure interoperability with downcasting (#285)
Deprecates Error::cause and introduces failure interoperability functions and downcasting.
2018-06-07 18:53:27 +02:00
97b5410aad remove Option from ContentDisposition::from_raw() argument 2018-06-07 12:55:35 +00:00
a6e07c06b6 move CD parsing to Content-Type parsing location 2018-06-07 12:35:10 +00:00
31a301c9a6 fix multipart test 2018-06-07 11:38:35 +00:00
5a37a8b813 restore hyper tests 2018-06-07 10:55:36 +00:00
c0c1817b5c remove unicase dependency 2018-06-07 10:33:00 +00:00
82c888df22 fix test 2018-06-07 09:10:46 +00:00
936ba2a368 multipart: parse and validate Content-Disposition 2018-06-06 14:06:01 +00:00
2d0b609c68 travis config 2018-06-05 10:08:42 -07:00
6467d34a32 update release date 2018-06-05 09:45:07 -07:00
2b616808c7 metadata for docs.rs 2018-06-05 09:00:21 -07:00
e5f7e4e481 update changelog 2018-06-05 08:55:28 -07:00
d1da227ac5 fix multipart boundary parsing #282 2018-06-05 08:53:51 -07:00
960a8c425d update changelog 2018-06-05 07:40:11 -07:00
f94fd9ebee CORS: Do not validate Origin header on non-OPTION requests #271 2018-06-05 07:39:47 -07:00
67ee24f9a0 Merge pull request #274 from mockersf/user-agent
add default value for header User-Agent in requests
2018-06-04 14:04:52 -07:00
5004821cda Merge branch 'master' into user-agent 2018-06-04 14:04:45 -07:00
ae7a0e993d update changelog 2018-06-04 13:43:52 -07:00
984791187a Middleware::response is not invoked if error result was returned by another Middleware::start #255 2018-06-04 13:42:47 -07:00
b07c50860a update changelog 2018-06-04 22:34:07 +02:00
eb0909b3a8 Merge branch 'master' into user-agent 2018-06-04 10:20:53 -07:00
ca3fb11f8b add actix-web version in header 2018-06-04 08:15:04 +02:00
47eb4e3d3d Merge pull request #278 from mbrobbel/patch-2
Fix typo
2018-06-03 16:28:51 -07:00
268c5d9238 Fix typo 2018-06-03 20:28:08 +02:00
86be54df71 add default value for header User-Agent in requests 2018-06-03 15:48:00 +02:00
ea018e0ad6 better examle in doc string 2018-06-02 16:03:23 -07:00
b799677532 better error messages for overflow errors 2018-06-02 15:10:48 -07:00
8c7182f6e6 Merge pull request #270 from DoumanAsh/payload_err
Specialize ResponseError for PayloadError
2018-06-02 15:06:55 -07:00
7298c7aabf Merge branch 'master' into payload_err 2018-06-02 15:04:22 -07:00
7e0706a942 implement Debug for Form, Query, Path extractors 2018-06-02 15:00:11 -07:00
698f0a1849 update changelog 2018-06-02 15:00:11 -07:00
8b8a3ac01d Support chunked encoding for UrlEncoded body #262 2018-06-02 15:00:06 -07:00
7ab23d082d fix doc test 2018-06-02 13:45:29 -07:00
913dce0a72 Merge branch 'master' into payload_err 2018-06-02 23:10:06 +03:00
2a9b57f489 Correct docstring 2018-06-02 22:27:43 +03:00
fce8dd275a Specialize ResponseError for PayloadError
Closes #257
2018-06-02 22:20:22 +03:00
3c472a2f66 remove debug prints 2018-06-02 11:57:49 -07:00
dcb561584d remove debug print 2018-06-02 11:55:50 -07:00
593a66324f update changelog 2018-06-02 11:45:37 -07:00
4a39216aa7 fixed HttpRequest::url_for for a named route with no variables #265 2018-06-02 11:44:09 -07:00
8d905c8504 add links to migration 2018-06-02 09:28:32 -07:00
33326ea41b fix layout 2018-06-02 09:25:11 -07:00
0457fe4d61 add System changes to migration guide 2018-06-02 09:19:13 -07:00
cede817915 update changelog 2018-06-02 09:15:44 -07:00
3bfed36fcc do not re-export actix_inner 2018-06-02 09:14:47 -07:00
0ff5f5f448 update migration 2018-06-02 09:01:51 -07:00
2f476021d8 Merge pull request #267 from joshleeb/trait-middleware-mut-self
Update Middleware Trait to Use `&mut self`
2018-06-02 08:54:30 -07:00
a61a1b0efe Merge branch 'master' into trait-middleware-mut-self 2018-06-02 08:54:00 -07:00
e041e9d3b7 Merge pull request #268 from killercup/docs/no-more-missing-docs
No more missing docs
2018-06-02 08:52:14 -07:00
890a7e70d6 Add missing API docs
These were written without much knowledge of the actix-web internals!
Please review carefully!
2018-06-02 15:52:50 +02:00
47b7be4fd3 Add warning for missing API docs 2018-06-02 15:50:45 +02:00
9c9eb62031 Update Middleware trait to use &mut self 2018-06-02 16:47:18 +10:00
8d73c30dae Merge pull request #266 from killercup/docs/fix-typos-and-run-more-code
Fix some ResourceHandler docs
2018-06-01 16:37:34 -07:00
d912bf8771 Add more docs to ResourceHandler API 2018-06-02 00:57:24 +02:00
f414a491dd Fix some ResourceHandler docs
Re-enables code blocks as doc tests to prevent them failing in the
future.
2018-06-02 00:57:07 +02:00
8f42fec9b2 stable compat 2018-06-01 12:17:13 -07:00
8452c7a044 fix doc api example 2018-06-01 11:22:40 -07:00
009ee4b3db update changelog 2018-06-01 10:55:54 -07:00
3e0a71101c drop with2 and with3 2018-06-01 10:54:23 -07:00
c8930b7b6b fix rustfmt formatting 2018-06-01 10:27:23 -07:00
3f5a39a5b7 cargo fmt 2018-06-01 09:37:14 -07:00
154cd3c5de better actix mod re-exports 2018-06-01 09:36:16 -07:00
80965d7a9a Re-export actix dependency. Closes #260 (#264)
- Re-export actix's prelude into actix namespace
- Removing implicit dependency on root's actix module
2018-05-31 20:43:14 +03:00
77becb9bc0 fix doc string 2018-05-29 18:48:39 -07:00
dde266b9ef fix doc string 2018-05-29 18:31:39 -07:00
34fd9f8148 travis config 2018-05-29 18:18:05 -07:00
a64205e502 refactor TransferEncoding; allow to use client api with threaded tokio runtime 2018-05-29 16:32:39 -07:00
844be8d9dd fix ssl test server 2018-05-29 10:59:24 -07:00
dffb7936fb Merge branch 'master' of github.com:actix/actix-web 2018-05-29 10:31:43 -07:00
ecd05662c0 use new actix system api 2018-05-29 10:31:37 -07:00
6eee3d1083 Merge pull request #258 from mbrobbel/patch-1
Fix typo in httpresponse.rs
2018-05-29 09:15:39 -07:00
6b43fc7068 Fix typo in httpresponse.rs 2018-05-29 18:11:10 +02:00
fb582a6bca fix connector 2018-05-27 05:18:37 -07:00
be2ceb7c66 update actix Addr; make ClientConnector thread safe 2018-05-27 05:02:49 -07:00
7c71171602 Merge pull request #248 from bbigras/same-site
Add same-site to CookieSessionBackend
2018-05-26 08:02:12 -07:00
4dcecd907b Add same-site to CookieSessionBackend
closes #247
2018-05-25 19:18:16 -04:00
255cd4917d fix doc test 2018-05-24 22:04:14 -07:00
f48702042b min rustc version 2018-05-24 21:09:20 -07:00
690169db89 migrate to tokio 2018-05-24 21:03:16 -07:00
565bcfb561 Merge pull request #245 from svartalf/response-builder-cookies-doc
Updating docs for HttpResponseBuilder::del_cookie
2018-05-24 12:42:08 -07:00
36f933ce1d Updating docs for HttpResponseBuilder::del_cookie 2018-05-24 21:53:35 +03:00
111b6835fa fix comment 2018-05-24 11:06:15 -07:00
bf63be3bcd bump version 2018-05-24 09:24:04 -07:00
9f9e0b98ad change homepage link 2018-05-24 08:55:10 -07:00
556646aaec update changelog 2018-05-24 07:56:51 -07:00
174fb0b5f4 Merge pull request #239 from max-frai/master
Add ability to set encoding for exact NamedFile.
2018-05-24 07:46:53 -07:00
836706653b Merge branch 'master' into master 2018-05-24 07:46:46 -07:00
17f1a2b92a more scope tests 2018-05-23 14:11:01 -07:00
3b08b16c11 bump version 2018-05-23 13:21:54 -07:00
68eb2f26c9 Allow to use path without traling slashes for scope registration #241 2018-05-23 13:21:29 -07:00
72757887c9 update doc links 2018-05-23 11:20:12 -07:00
eb5dbd43ae update changelog 2018-05-23 10:37:17 -07:00
1f1dfac3f9 Merge pull request #240 from ivanovaleksey/patch-2
Fix TestServer::post
2018-05-23 09:50:40 -07:00
2479b14aba Fix TestServer::post 2018-05-23 19:07:42 +03:00
ac24703512 Add ability to set encoding for exact NamedFile. 2018-05-23 09:12:23 +03:00
456 changed files with 73991 additions and 38382 deletions

View File

@ -1,62 +0,0 @@
environment:
global:
PROJECT_NAME: actix
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
CHANNEL: nightly
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
# Install Rust and Cargo
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
install:
- ps: >-
If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') {
$Env:PATH += ';C:\msys64\mingw64\bin'
} ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') {
$Env:PATH += ';C:\MinGW\bin'
}
- curl -sSf -o rustup-init.exe https://win.rustup.rs
- rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -Vv
- cargo -V
# 'cargo test' takes care of building for us, so disable Appveyor's build stage.
build: false
# Equivalent to Travis' `script` phase
test_script:
- cargo test --no-default-features --features="flate2-rust"

17
.cargo/config.toml Normal file
View File

@ -0,0 +1,17 @@
[alias]
lint = "clippy --workspace --tests --examples --bins -- -Dclippy::todo"
lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo"
# lib checking
ci-check-min = "hack --workspace check --no-default-features"
ci-check-default = "hack --workspace check"
ci-check-default-tests = "check --workspace --tests"
ci-check-all-feature-powerset="hack --workspace --feature-powerset --skip=__compress,experimental-io-uring check"
ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=__compress check"
# testing
ci-doctest-default = "test --workspace --doc --no-fail-fast -- --nocapture"
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
# compile docs as docs.rs would
# RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [robjtede]

37
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,37 @@
---
name: Bug Report
about: Create a bug report.
---
Your issue may already be reported!
Please search on the [Actix Web issue tracker](https://github.com/actix/actix-web/issues) before creating one.
## Expected Behavior
<!--- If you're describing a bug, tell us what should happen -->
<!--- If you're suggesting a change/improvement, tell us how it should work -->
## Current Behavior
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
## Possible Solution
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
<!--- or ideas how to implement the addition or change -->
## Steps to Reproduce (for bugs)
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
1.
2.
3.
4.
## Context
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
## Your Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->
- Rust Version (I.e, output of `rustc -V`):
- Actix Web Version:

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Actix Discord
url: https://discord.gg/NWpN5mmg3x
about: Actix developer discussion and community chat
- name: GitHub Discussions
url: https://github.com/actix/actix-web/discussions
about: Actix Web Q&A

27
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,27 @@
<!-- Thanks for considering contributing actix! -->
<!-- Please fill out the following to get your PR reviewed quicker. -->
## PR Type
<!-- What kind of change does this PR make? -->
<!-- Bug Fix / Feature / Refactor / Code Style / Other -->
PR_TYPE
## PR Checklist
<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] A changelog entry has been made for the appropriate packages.
- [ ] Format code with the latest stable rustfmt.
- [ ] (Team) Label with affected crates and semver status.
## Overview
<!-- Describe the current and new behavior. -->
<!-- Emphasize any breaking changes. -->
<!-- If this PR fixes or closes an issue, reference it here. -->
<!-- Closes #000 -->

26
.github/workflows/bench.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: Benchmark
on:
push:
branches:
- master
jobs:
check_benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true
- name: Check benchmark
uses: actions-rs/cargo@v1
with:
command: bench
args: --bench=server -- --sample-size=15

185
.github/workflows/ci-post-merge.yml vendored Normal file
View File

@ -0,0 +1,185 @@
name: CI (post-merge)
on:
push:
branches: [master]
jobs:
build_and_test_nightly:
strategy:
fail-fast: false
matrix:
target:
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
- { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc }
version:
- nightly
name: ${{ matrix.target.name }} / ${{ matrix.version }}
runs-on: ${{ matrix.target.os }}
env:
CI: 1
CARGO_INCREMENTAL: 0
VCPKGRS_DYNAMIC: 1
steps:
- uses: actions/checkout@v2
# install OpenSSL on Windows
# TODO: GitHub actions docs state that OpenSSL is
# already installed on these Windows machines somewhere
- name: Set vcpkg root
if: matrix.target.triple == 'x86_64-pc-windows-msvc'
run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Install OpenSSL
if: matrix.target.triple == 'x86_64-pc-windows-msvc'
run: vcpkg install openssl:x64-windows
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
- name: Install cargo-hack
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-hack
- name: check minimal
uses: actions-rs/cargo@v1
with: { command: ci-check-min }
- name: check default
uses: actions-rs/cargo@v1
with: { command: ci-check-default }
- name: tests
timeout-minutes: 60
run: |
cargo test --lib --tests -p=actix-router --all-features
cargo test --lib --tests -p=actix-http --all-features
cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web-codegen --all-features
cargo test --lib --tests -p=awc --all-features
cargo test --lib --tests -p=actix-http-test --all-features
cargo test --lib --tests -p=actix-test --all-features
cargo test --lib --tests -p=actix-files
cargo test --lib --tests -p=actix-multipart --all-features
cargo test --lib --tests -p=actix-web-actors --all-features
- name: tests (io-uring)
if: matrix.target.os == 'ubuntu-latest'
timeout-minutes: 60
run: >
sudo bash -c "ulimit -Sl 512
&& ulimit -Hl 512
&& PATH=$PATH:/usr/share/rust/.cargo/bin
&& RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo test --lib --tests -p=actix-files --all-features"
- name: Clear the cargo caches
run: |
cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean
cargo-cache
ci_feature_powerset_check:
name: Verify Feature Combinations
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable-x86_64-unknown-linux-gnu
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
- name: Install cargo-hack
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-hack
- name: check feature combinations
uses: actions-rs/cargo@v1
with: { command: ci-check-all-feature-powerset }
- name: check feature combinations
uses: actions-rs/cargo@v1
with: { command: ci-check-all-feature-powerset-linux }
# job currently (1st Feb 2022) segfaults
# coverage:
# name: coverage
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
# - name: Install stable
# uses: actions-rs/toolchain@v1
# with:
# toolchain: stable-x86_64-unknown-linux-gnu
# profile: minimal
# override: true
# - name: Generate Cargo.lock
# uses: actions-rs/cargo@v1
# with: { command: generate-lockfile }
# - name: Cache Dependencies
# uses: Swatinem/rust-cache@v1.2.0
# - name: Generate coverage file
# run: |
# cargo install cargo-tarpaulin --vers "^0.13"
# cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose
# - name: Upload to Codecov
# uses: codecov/codecov-action@v1
# with: { file: cobertura.xml }
nextest:
name: nextest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.3.0
- name: Install cargo-nextest
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-nextest
- name: Test with cargo-nextest
uses: actions-rs/cargo@v1
with:
command: nextest
args: run

120
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,120 @@
name: CI
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches: [master]
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
target:
- { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
- { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc }
version:
- 1.54.0 # MSRV
- stable
name: ${{ matrix.target.name }} / ${{ matrix.version }}
runs-on: ${{ matrix.target.os }}
env:
CI: 1
CARGO_INCREMENTAL: 0
VCPKGRS_DYNAMIC: 1
steps:
- uses: actions/checkout@v2
# install OpenSSL on Windows
# TODO: GitHub actions docs state that OpenSSL is
# already installed on these Windows machines somewhere
- name: Set vcpkg root
if: matrix.target.triple == 'x86_64-pc-windows-msvc'
run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Install OpenSSL
if: matrix.target.triple == 'x86_64-pc-windows-msvc'
run: vcpkg install openssl:x64-windows
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
- name: Install cargo-hack
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-hack
- name: check minimal
uses: actions-rs/cargo@v1
with: { command: ci-check-min }
- name: check default
uses: actions-rs/cargo@v1
with: { command: ci-check-default }
- name: tests
timeout-minutes: 60
run: |
cargo test --lib --tests -p=actix-router --all-features
cargo test --lib --tests -p=actix-http --all-features
cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web-codegen --all-features
cargo test --lib --tests -p=awc --all-features
cargo test --lib --tests -p=actix-http-test --all-features
cargo test --lib --tests -p=actix-test --all-features
cargo test --lib --tests -p=actix-files
cargo test --lib --tests -p=actix-multipart --all-features
cargo test --lib --tests -p=actix-web-actors --all-features
- name: tests (io-uring)
if: matrix.target.os == 'ubuntu-latest'
timeout-minutes: 60
run: >
sudo bash -c "ulimit -Sl 512
&& ulimit -Hl 512
&& PATH=$PATH:/usr/share/rust/.cargo/bin
&& RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo test --lib --tests -p=actix-files --all-features"
- name: Clear the cargo caches
run: |
cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean
cargo-cache
rustdoc:
name: doc tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust (nightly)
uses: actions-rs/toolchain@v1
with:
toolchain: nightly-x86_64-unknown-linux-gnu
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.3.0
- name: doc tests
uses: actions-rs/cargo@v1
timeout-minutes: 60
with: { command: ci-doctest }

66
.github/workflows/clippy-fmt.yml vendored Normal file
View File

@ -0,0 +1,66 @@
name: Lint
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
fmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: rustfmt
- name: Check with rustfmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: clippy
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
- name: Check with Clippy
uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace --tests --examples --all-features
lint-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: rust-docs
- name: Check for broken intra-doc links
uses: actions-rs/cargo@v1
env:
RUSTDOCFLAGS: "-D warnings"
with:
command: doc
args: --no-deps --all-features --workspace

35
.github/workflows/upload-doc.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: Upload Documentation
on:
push:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly-x86_64-unknown-linux-gnu
profile: minimal
override: true
- name: Build Docs
uses: actions-rs/cargo@v1
with:
command: doc
args: --workspace --all-features --no-deps
- name: Tweak HTML
run: echo '<meta http-equiv="refresh" content="0;url=actix_web/index.html">' > target/doc/index.html
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@3.7.1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages
FOLDER: target/doc

7
.gitignore vendored
View File

@ -9,6 +9,13 @@ guide/build/
*.pid
*.sock
*~
.DS_Store
# These are backup files generated by rustfmt
**/*.rs.bk
# Configuration directory generated by CLion
.idea
# Configuration directory generated by VSCode
.vscode

3
.prettierrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"proseWrap": "never"
}

View File

@ -1,55 +0,0 @@
language: rust
sudo: false
dist: trusty
cache:
cargo: true
apt: true
matrix:
include:
- rust: 1.24.0
- rust: stable
- rust: beta
- rust: nightly
allow_failures:
- rust: nightly
env:
global:
# - RUSTFLAGS="-C link-dead-code"
- OPENSSL_VERSION=openssl-1.0.2
before_install:
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
- sudo apt-get update -qq
- sudo apt-get install -qq libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
# Add clippy
before_script:
- export PATH=$PATH:~/.cargo/bin
script:
- |
if [[ "$TRAVIS_RUST_VERSION" != "1.24.0" ]]; then
cargo clean
cargo test --features="alpn,tls" -- --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
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"
fi
# Upload docs
after_success:
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cargo doc --features "alpn, tls, session" --no-deps &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
git clone https://github.com/davisp/ghp-import.git &&
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
echo "Uploaded documentation"
fi

View File

@ -1,388 +1,5 @@
# Changes
# Changelog
## 0.6.9 (2018-05-22)
Changelogs are kept separately for each crate in this repo.
* Drop connection if request's payload is not fully consumed #236
* Fix streaming response with body compression
## 0.6.8 (2018-05-20)
* Fix scope resource path extractor #234
* Re-use tcp listener on pause/resume
## 0.6.7 (2018-05-17)
* Fix compilation with --no-default-features
## 0.6.6 (2018-05-17)
* Panic during middleware execution #226
* Add support for listen_tls/listen_ssl #224
* Implement extractor for `Session`
* Ranges header support for NamedFile #60
## 0.6.5 (2018-05-15)
* Fix error handling during request decoding #222
## 0.6.4 (2018-05-11)
* Fix segfault in ServerSettings::get_response_builder()
## 0.6.3 (2018-05-10)
* Add `Router::with_async()` method for async handler registration.
* Added error response functions for 501,502,503,504
* Fix client request timeout handling
## 0.6.2 (2018-05-09)
* WsWriter trait is optional.
## 0.6.1 (2018-05-08)
* Fix http/2 payload streaming #215
* Fix connector's default `keep-alive` and `lifetime` settings #212
* Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214
* Allow to exclude certain endpoints from logging #211
## 0.6.0 (2018-05-08)
* Add route scopes #202
* Allow to use ssl and non-ssl connections at the same time #206
* Websocket CloseCode Empty/Status is ambiguous #193
* Add Content-Disposition to NamedFile #204
* Allow to access Error's backtrace object
* Allow to override files listing renderer for `StaticFiles` #203
* Various extractor usability improvements #207
## 0.5.6 (2018-04-24)
* Make flate2 crate optional #200
## 0.5.5 (2018-04-24)
* Fix panic when Websocket is closed with no error code #191
* Allow to use rust backend for flate2 crate #199
## 0.5.4 (2018-04-19)
* Add identity service middleware
* Middleware response() is not invoked if there was an error in async handler #187
* Use Display formatting for InternalError Display implementation #188
## 0.5.3 (2018-04-18)
* Impossible to quote slashes in path parameters #182
## 0.5.2 (2018-04-16)
* Allow to configure StaticFiles's CpuPool, via static method or env variable
* Add support for custom handling of Json extractor errors #181
* Fix StaticFiles does not support percent encoded paths #177
* Fix Client Request with custom Body Stream halting on certain size requests #176
## 0.5.1 (2018-04-12)
* Client connector provides stats, `ClientConnector::stats()`
* Fix end-of-stream handling in parse_payload #173
* Fix StaticFiles generate a lot of threads #174
## 0.5.0 (2018-04-10)
* Type-safe path/query/form parameter handling, using serde #70
* HttpResponse builder's methods `.body()`, `.finish()`, `.json()`
return `HttpResponse` instead of `Result`
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()`
* Added `signed` and `private` `CookieSessionBackend`s
* Added `HttpRequest::resource()`, returns current matched resource
* Added `ErrorHandlers` middleware
* Fix router cannot parse Non-ASCII characters in URL #137
* Fix client connection pooling
* Fix long client urls #129
* Fix panic on invalid URL characters #130
* Fix logger request duration calculation #152
* Fix prefix and static file serving #168
## 0.4.10 (2018-03-20)
* Use `Error` instead of `InternalError` for `error::ErrorXXXX` methods
* Allow to set client request timeout
* Allow to set client websocket handshake timeout
* Refactor `TestServer` configuration
* Fix server websockets big payloads support
* Fix http/2 date header generation
## 0.4.9 (2018-03-16)
* Allow to disable http/2 support
* Wake payload reading task when data is available
* Fix server keep-alive handling
* Send Query Parameters in client requests #120
* Move brotli encoding to a feature
* Add option of default handler for `StaticFiles` handler #57
* Add basic client connection pooling
## 0.4.8 (2018-03-12)
* Allow to set read buffer capacity for server request
* Handle WouldBlock error for socket accept call
## 0.4.7 (2018-03-11)
* Fix panic on unknown content encoding
* Fix connection get closed too early
* Fix streaming response handling for http/2
* Better sleep on error support
## 0.4.6 (2018-03-10)
* Fix client cookie handling
* Fix json content type detection
* Fix CORS middleware #117
* Optimize websockets stream support
## 0.4.5 (2018-03-07)
* Fix compression #103 and #104
* Fix client cookie handling #111
* Non-blocking processing of a `NamedFile`
* Enable compression support for `NamedFile`
* Better support for `NamedFile` type
* Add `ResponseError` impl for `SendRequestError`. This improves ergonomics of the client.
* Add native-tls support for client
* Allow client connection timeout to be set #108
* Allow to use std::net::TcpListener for HttpServer
* Handle panics in worker threads
## 0.4.4 (2018-03-04)
* Allow to use Arc<Vec<u8>> as response/request body
* Fix handling of requests with an encoded body with a length > 8192 #93
## 0.4.3 (2018-03-03)
* Fix request body read bug
* Fix segmentation fault #79
* Set reuse address before bind #90
## 0.4.2 (2018-03-02)
* Better naming for websockets implementation
* Add `Pattern::with_prefix()`, make it more usable outside of actix
* Add csrf middleware for filter for cross-site request forgery #89
* Fix disconnect on idle connections
## 0.4.1 (2018-03-01)
* Rename `Route::p()` to `Route::filter()`
* Better naming for http codes
* Fix payload parse in situation when socket data is not ready.
* Fix Session mutable borrow lifetime #87
## 0.4.0 (2018-02-28)
* Actix 0.5 compatibility
* Fix request json/urlencoded loaders
* Simplify HttpServer type definition
* Added HttpRequest::encoding() method
* Added HttpRequest::mime_type() method
* Added HttpRequest::uri_mut(), allows to modify request uri
* Added StaticFiles::index_file()
* Added http client
* Added websocket client
* Added TestServer::ws(), test websockets client
* Added TestServer http client support
* Allow to override content encoding on application level
## 0.3.3 (2018-01-25)
* Stop processing any events after context stop
* Re-enable write back-pressure for h1 connections
* Refactor HttpServer::start_ssl() method
* Upgrade openssl to 0.10
## 0.3.2 (2018-01-21)
* Fix HEAD requests handling
* Log request processing errors
* Always enable content encoding if encoding explicitly selected
* Allow multiple Applications on a single server with different state #49
* CORS middleware: allowed_headers is defaulting to None #50
## 0.3.1 (2018-01-13)
* Fix directory entry path #47
* Do not enable chunked encoding for HTTP/1.0
* Allow explicitly disable chunked encoding
## 0.3.0 (2018-01-12)
* HTTP/2 Support
* Refactor streaming responses
* Refactor error handling
* Asynchronous middlewares
* Refactor logger middleware
* Content compression/decompression (br, gzip, deflate)
* Server multi-threading
* Gracefull shutdown support
## 0.2.1 (2017-11-03)
* Allow to start tls server with `HttpServer::serve_tls`
* Export `Frame` enum
* Add conversion impl from `HttpResponse` and `BinaryBody` to a `Frame`
## 0.2.0 (2017-10-30)
* Do not use `http::Uri` as it can not parse some valid paths
* Refactor response `Body`
* Refactor `RouteRecognizer` usability
* Refactor `HttpContext::write`
* Refactor `Payload` stream
* Re-use `BinaryBody` for `Frame::Payload`
* Stop http actor on `write_eof`
* Fix disconnection handling.
## 0.1.0 (2017-10-23)
* First release
Actix Web changelog [is now here &rarr;](./actix-web/CHANGES.md).

View File

@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
@ -34,10 +34,13 @@ This Code of Conduct applies both within project spaces and in public spaces whe
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at robjtede@icloud.com ([@robjtede]) or huyuumi@neet.club ([@JohnTitor]). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
[@robjtede]: https://github.com/robjtede
[@JohnTitor]: https://github.com/JohnTitor
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]

View File

@ -1,116 +1,44 @@
[package]
name = "actix-web"
version = "0.6.9"
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://github.com/actix/actix-web"
repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-web/"
categories = ["network-programming", "asynchronous",
"web-programming::http-server",
"web-programming::http-client",
"web-programming::websocket"]
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
build = "build.rs"
[workspace]
resolver = "2"
members = [
"actix-files",
"actix-http-test",
"actix-http",
"actix-multipart",
"actix-router",
"actix-test",
"actix-web-actors",
"actix-web-codegen",
"actix-web",
"awc",
]
[badges]
travis-ci = { repository = "actix/actix-web", branch = "master" }
appveyor = { repository = "fafhrd91/actix-web-hdy9d" }
codecov = { repository = "actix/actix-web", branch = "master", service = "github" }
[lib]
name = "actix_web"
path = "src/lib.rs"
[features]
default = ["session", "brotli", "flate2-c"]
# tls
tls = ["native-tls", "tokio-tls"]
# openssl
alpn = ["openssl", "tokio-openssl"]
# sessions feature, session require "ring" crate and c compiler
session = ["cookie/secure"]
# brotli encoding, requires c compiler
brotli = ["brotli2"]
# miniz-sys backend for flate2 crate
flate2-c = ["flate2/miniz-sys"]
# rust backend for flate2 crate
flate2-rust = ["flate2/rust_backend"]
[dependencies]
actix = "^0.5.5"
base64 = "0.9"
bitflags = "1.0"
failure = "0.1.1"
h2 = "0.1"
http = "^0.1.5"
httparse = "1.2"
http-range = "0.1"
libc = "0.2"
log = "0.4"
mime = "0.3"
mime_guess = "2.0.0-alpha"
num_cpus = "1.0"
percent-encoding = "1.0"
rand = "0.4"
regex = "1.0"
serde = "1.0"
serde_json = "1.0"
serde_urlencoded = "0.5"
sha1 = "0.6"
smallvec = "0.6"
time = "0.1"
encoding = "0.2"
language-tags = "0.2"
lazy_static = "1.0"
url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.10", features=["percent-encode"] }
brotli2 = { version="^0.3.2", optional = true }
flate2 = { version="1.0", optional = true, default-features = false }
# io
mio = "^0.6.13"
net2 = "0.2"
bytes = "0.4"
byteorder = "1"
futures = "0.1"
futures-cpupool = "0.1"
slab = "0.4"
tokio-io = "0.1"
tokio-core = "0.1"
# native-tls
native-tls = { version="0.1", optional = true }
tokio-tls = { version="0.1", optional = true }
# openssl
openssl = { version="0.10", optional = true }
tokio-openssl = { version="0.2", optional = true }
[dev-dependencies]
env_logger = "0.5"
serde_derive = "1.0"
[build-dependencies]
version_check = "0.1"
[profile.dev]
# Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much.
debug = 0
[profile.release]
lto = true
opt-level = 3
codegen-units = 1
[workspace]
members = [
"./",
"tools/wsload/",
]
[patch.crates-io]
actix-files = { path = "actix-files" }
actix-http = { path = "actix-http" }
actix-http-test = { path = "actix-http-test" }
actix-multipart = { path = "actix-multipart" }
actix-router = { path = "actix-router" }
actix-test = { path = "actix-test" }
actix-web = { path = "actix-web" }
actix-web-actors = { path = "actix-web-actors" }
actix-web-codegen = { path = "actix-web-codegen" }
awc = { path = "awc" }
# uncomment for quick testing against local actix-net repo
# actix-service = { path = "../actix-net/actix-service" }
# actix-macros = { path = "../actix-net/actix-macros" }
# actix-rt = { path = "../actix-net/actix-rt" }
# actix-codec = { path = "../actix-net/actix-codec" }
# actix-utils = { path = "../actix-net/actix-utils" }
# actix-tls = { path = "../actix-net/actix-tls" }
# actix-server = { path = "../actix-net/actix-server" }

View File

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017-NOW Nikolay Kim
Copyright 2017-NOW Actix Team
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,4 +1,4 @@
Copyright (c) 2017 Nikolay Kim
Copyright (c) 2017-NOW Actix Team
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated

View File

@ -1,82 +0,0 @@
## Migration from 0.5 to 0.6
* `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
* `ws::Message::Close` now includes optional close reason.
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
* `HttpServer::threads()` renamed to `HttpServer::workers()`.
* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated.
Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead.
* `HttpRequest::extensions()` returns read only reference to the request's Extension
`HttpRequest::extensions_mut()` returns mutable reference.
* Instead of
`use actix_web::middleware::{
CookieSessionBackend, CookieSessionError, RequestSession,
Session, SessionBackend, SessionImpl, SessionStorage};`
use `actix_web::middleware::session`
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError,
RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
* `FromRequest::from_request()` accepts mutable reference to a request
* `FromRequest::Result` has to implement `Into<Reply<Self>>`
* [`Responder::respond_to()`](
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
is generic over `S`
* Use `Query` extractor instead of HttpRequest::query()`.
```rust
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
...
}
```
or
```rust
let q = Query::<HashMap<String, String>>::extract(req);
```
* Websocket operations are implemented as `WsWriter` trait.
you need to use `use actix_web::ws::WsWriter`
## Migration from 0.4 to 0.5
* `HttpResponseBuilder::body()`, `.finish()`, `.json()`
methods return `HttpResponse` instead of `Result<HttpResponse>`
* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version`
moved to `actix_web::http` module
* `actix_web::header` moved to `actix_web::http::header`
* `NormalizePath` moved to `actix_web::http` module
* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function,
shortcut for `actix_web::server::HttpServer::new()`
* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself
* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead.
* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type
* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()`
functions should be used instead
* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>`
instead of `Result<_, http::Error>`
* `Application` renamed to a `App`
* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev`

View File

@ -1,14 +0,0 @@
.PHONY: default build test doc book clean
CARGO_FLAGS := --features "$(FEATURES) alpn"
default: test
build:
cargo build $(CARGO_FLAGS)
test: build clippy
cargo test $(CARGO_FLAGS)
doc: build
cargo doc --no-deps $(CARGO_FLAGS)

View File

@ -1,89 +0,0 @@
# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/book/actix-web/sec-12-http2.html) protocols
* Streaming and pipelining
* Keep-alive and slow requests handling
* Client/server [WebSockets](https://actix.rs/book/actix-web/sec-11-websockets.html) support
* Transparent content compression/decompression (br, gzip, deflate)
* Configurable [request routing](https://actix.rs/book/actix-web/sec-6-url-dispatch.html)
* 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))
* Includes an asynchronous [HTTP client](https://github.com/actix/actix-web/blob/master/src/client/mod.rs)
* Built on top of [Actix actor framework](https://github.com/actix/actix)
## Documentation & community resources
* [User Guide](https://actix.rs/book/actix-web/)
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
* [API Documentation (Releases)](https://docs.rs/actix-web/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
* Minimum supported Rust version: 1.24 or later
## Example
```rust
extern crate actix_web;
use actix_web::{http, server, App, Path};
fn index(info: Path<(u32, String)>) -> String {
format!("Hello {}! id:{}", info.1, info.0)
}
fn main() {
server::new(
|| App::new()
.route("/{id}/{name}/index.html", http::Method::GET, index))
.bind("127.0.0.1:8080").unwrap()
.run();
}
```
### More examples
* [Basics](https://github.com/actix/examples/tree/master/basics/)
* [Stateful](https://github.com/actix/examples/tree/master/state/)
* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/)
* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/)
* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/)
* [Tera](https://github.com/actix/examples/tree/master/template_tera/) /
[Askama](https://github.com/actix/examples/tree/master/template_askama/) templates
* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/)
* [r2d2](https://github.com/actix/examples/tree/master/r2d2/)
* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/)
* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/)
* [Json](https://github.com/actix/examples/tree/master/json/)
You may consider checking out
[this directory](https://github.com/actix/examples/tree/master/) for more examples.
## Benchmarks
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext)
* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks).
## License
This project is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
at your option.
## Code of Conduct
Contribution to the actix-web crate is organized under the terms of the
Contributor Covenant, the maintainer of actix-web, @fafhrd91, promises to
intervene to uphold that code of conduct.

1
README.md Symbolic link
View File

@ -0,0 +1 @@
actix-web/README.md

211
actix-files/CHANGES.md Normal file
View File

@ -0,0 +1,211 @@
# Changes
## Unreleased - 2021-xx-xx
## 0.6.0 - 2022-02-25
- No significant changes since `0.6.0-beta.16`.
## 0.6.0-beta.16 - 2022-01-31
- No significant changes since `0.6.0-beta.15`.
## 0.6.0-beta.15 - 2022-01-21
- No significant changes since `0.6.0-beta.14`.
## 0.6.0-beta.14 - 2022-01-14
- The `prefer_utf8` option introduced in `0.4.0` is now true by default. [#2583]
[#2583]: https://github.com/actix/actix-web/pull/2583
## 0.6.0-beta.13 - 2022-01-04
- The `Files` service now rejects requests with URL paths that include `%2F` (decoded: `/`). [#2398]
- The `Files` service now correctly decodes `%25` in the URL path to `%` for the file path. [#2398]
- Minimum supported Rust version (MSRV) is now 1.54.
[#2398]: https://github.com/actix/actix-web/pull/2398
## 0.6.0-beta.12 - 2021-12-29
- No significant changes since `0.6.0-beta.11`.
## 0.6.0-beta.11 - 2021-12-27
- No significant changes since `0.6.0-beta.10`.
## 0.6.0-beta.10 - 2021-12-11
- No significant changes since `0.6.0-beta.9`.
## 0.6.0-beta.9 - 2021-11-22
- Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408]
- Add `NamedFile::open_async`. [#2408]
- Fix 304 Not Modified responses to omit the Content-Length header, as per the spec. [#2453]
- The `Responder` impl for `NamedFile` now has a boxed future associated type. [#2408]
- The `Service` impl for `NamedFileService` now has a boxed future associated type. [#2408]
- Add `impl Clone` for `FilesService`. [#2408]
[#2408]: https://github.com/actix/actix-web/pull/2408
[#2453]: https://github.com/actix/actix-web/pull/2453
## 0.6.0-beta.8 - 2021-10-20
- Minimum supported Rust version (MSRV) is now 1.52.
## 0.6.0-beta.7 - 2021-09-09
- Minimum supported Rust version (MSRV) is now 1.51.
## 0.6.0-beta.6 - 2021-06-26
- Added `Files::path_filter()`. [#2274]
- `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228]
[#2274]: https://github.com/actix/actix-web/pull/2274
[#2228]: https://github.com/actix/actix-web/pull/2228
## 0.6.0-beta.5 - 2021-06-17
- `NamedFile` now implements `ServiceFactory` and `HttpServiceFactory` making it much more useful in routing. For example, it can be used directly as a default service. [#2135]
- For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156]
- `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225]
- `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257]
[#2135]: https://github.com/actix/actix-web/pull/2135
[#2156]: https://github.com/actix/actix-web/pull/2156
[#2225]: https://github.com/actix/actix-web/pull/2225
[#2257]: https://github.com/actix/actix-web/pull/2257
## 0.6.0-beta.4 - 2021-04-02
- Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046]
[#2046]: https://github.com/actix/actix-web/pull/2046
## 0.6.0-beta.3 - 2021-03-09
- No notable changes.
## 0.6.0-beta.2 - 2021-02-10
- Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887]
- Replace `v_htmlescape` with `askama_escape`. [#1953]
[#1887]: https://github.com/actix/actix-web/pull/1887
[#1953]: https://github.com/actix/actix-web/pull/1953
## 0.6.0-beta.1 - 2021-01-07
- `HttpRange::parse` now has its own error type.
- Update `bytes` to `1.0`. [#1813]
[#1813]: https://github.com/actix/actix-web/pull/1813
## 0.5.0 - 2020-12-26
- Optionally support hidden files/directories. [#1811]
[#1811]: https://github.com/actix/actix-web/pull/1811
## 0.4.1 - 2020-11-24
- Clarify order of parameters in `Files::new` and improve docs.
## 0.4.0 - 2020-10-06
- Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714]
[#1714]: https://github.com/actix/actix-web/pull/1714
## 0.3.0 - 2020-09-11
- No significant changes from 0.3.0-beta.1.
## 0.3.0-beta.1 - 2020-07-15
- Update `v_htmlescape` to 0.10
- Update `actix-web` and `actix-http` dependencies to beta.1
## 0.3.0-alpha.1 - 2020-05-23
- Update `actix-web` and `actix-http` dependencies to alpha
- Fix some typos in the docs
- Bump minimum supported Rust version to 1.40
- Support sending Content-Length when Content-Range is specified [#1384]
[#1384]: https://github.com/actix/actix-web/pull/1384
## 0.2.1 - 2019-12-22
- Use the same format for file URLs regardless of platforms
## 0.2.0 - 2019-12-20
- Fix BodyEncoding trait import #1220
## 0.2.0-alpha.1 - 2019-12-07
- Migrate to `std::future`
## 0.1.7 - 2019-11-06
- Add an additional `filename*` param in the `Content-Disposition` header of
`actix_files::NamedFile` to be more compatible. (#1151)
## 0.1.6 - 2019-10-14
- Add option to redirect to a slash-ended path `Files` #1132
## 0.1.5 - 2019-10-08
- Bump up `mime_guess` crate version to 2.0.1
- Bump up `percent-encoding` crate version to 2.1
- Allow user defined request guards for `Files` #1113
## 0.1.4 - 2019-07-20
- Allow to disable `Content-Disposition` header #686
## 0.1.3 - 2019-06-28
- Do not set `Content-Length` header, let actix-http set it #930
## 0.1.2 - 2019-06-13
- Content-Length is 0 for NamedFile HEAD request #914
- Fix ring dependency from actix-web default features for #741
## 0.1.1 - 2019-06-01
- Static files are incorrectly served as both chunked and with length #812
## 0.1.0 - 2019-05-25
- NamedFile last-modified check always fails due to nano-seconds in file modified date #820
## 0.1.0-beta.4 - 2019-05-12
- Update actix-web to beta.4
## 0.1.0-beta.1 - 2019-04-20
- Update actix-web to beta.1
## 0.1.0-alpha.6 - 2019-04-14
- Update actix-web to alpha6
## 0.1.0-alpha.4 - 2019-04-08
- Update actix-web to alpha4
## 0.1.0-alpha.2 - 2019-04-02
- Add default handler support
## 0.1.0-alpha.1 - 2019-03-28
- Initial impl

48
actix-files/Cargo.toml Normal file
View File

@ -0,0 +1,48 @@
[package]
name = "actix-files"
version = "0.6.0"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>",
"Rob Ede <robjtede@icloud.com>",
]
description = "Static file serving for Actix Web"
keywords = ["actix", "http", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web"
categories = ["asynchronous", "web-programming::http-server"]
license = "MIT OR Apache-2.0"
edition = "2018"
[lib]
name = "actix_files"
path = "src/lib.rs"
[features]
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
[dependencies]
actix-http = "3"
actix-service = "2"
actix-utils = "3"
actix-web = { version = "4", default-features = false }
askama_escape = "0.10"
bitflags = "1"
bytes = "1"
derive_more = "0.99.5"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
http-range = "0.1.4"
log = "0.4"
mime = "0.3"
mime_guess = "2.0.1"
percent-encoding = "2.1"
pin-project-lite = "0.2.7"
tokio-uring = { version = "0.2", optional = true, features = ["bytes"] }
[dev-dependencies]
actix-rt = "2.2"
actix-test = "0.1.0-beta.13"
actix-web = "4.0.0"
tempfile = "3.2"

1
actix-files/LICENSE-APACHE Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-APACHE

1
actix-files/LICENSE-MIT Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-MIT

18
actix-files/README.md Normal file
View File

@ -0,0 +1,18 @@
# actix-files
> Static file serving for Actix Web
[![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files)
[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0)](https://docs.rs/actix-files/0.6.0)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
![License](https://img.shields.io/crates/l/actix-files.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-files/0.6.0/status.svg)](https://deps.rs/crate/actix-files/0.6.0)
[![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-files)
- [Example Project](https://github.com/actix/examples/tree/master/basics/static-files)
- Minimum Supported Rust Version (MSRV): 1.54

218
actix-files/src/chunked.rs Normal file
View File

@ -0,0 +1,218 @@
use std::{
cmp, fmt,
future::Future,
io,
pin::Pin,
task::{Context, Poll},
};
use actix_web::{error::Error, web::Bytes};
use futures_core::{ready, Stream};
use pin_project_lite::pin_project;
#[cfg(feature = "experimental-io-uring")]
use bytes::BytesMut;
use super::named::File;
pin_project! {
/// Adapter to read a `std::file::File` in chunks.
#[doc(hidden)]
pub struct ChunkedReadFile<F, Fut> {
size: u64,
offset: u64,
#[pin]
state: ChunkedReadFileState<Fut>,
counter: u64,
callback: F,
}
}
#[cfg(not(feature = "experimental-io-uring"))]
pin_project! {
#[project = ChunkedReadFileStateProj]
#[project_replace = ChunkedReadFileStateProjReplace]
enum ChunkedReadFileState<Fut> {
File { file: Option<File>, },
Future { #[pin] fut: Fut },
}
}
#[cfg(feature = "experimental-io-uring")]
pin_project! {
#[project = ChunkedReadFileStateProj]
#[project_replace = ChunkedReadFileStateProjReplace]
enum ChunkedReadFileState<Fut> {
File { file: Option<(File, BytesMut)> },
Future { #[pin] fut: Fut },
}
}
impl<F, Fut> fmt::Debug for ChunkedReadFile<F, Fut> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ChunkedReadFile")
}
}
pub(crate) fn new_chunked_read(
size: u64,
offset: u64,
file: File,
) -> impl Stream<Item = Result<Bytes, Error>> {
ChunkedReadFile {
size,
offset,
#[cfg(not(feature = "experimental-io-uring"))]
state: ChunkedReadFileState::File { file: Some(file) },
#[cfg(feature = "experimental-io-uring")]
state: ChunkedReadFileState::File {
file: Some((file, BytesMut::new())),
},
counter: 0,
callback: chunked_read_file_callback,
}
}
#[cfg(not(feature = "experimental-io-uring"))]
async fn chunked_read_file_callback(
mut file: File,
offset: u64,
max_bytes: usize,
) -> Result<(File, Bytes), Error> {
use io::{Read as _, Seek as _};
let res = actix_web::web::block(move || {
let mut buf = Vec::with_capacity(max_bytes);
file.seek(io::SeekFrom::Start(offset))?;
let n_bytes = file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?;
if n_bytes == 0 {
Err(io::Error::from(io::ErrorKind::UnexpectedEof))
} else {
Ok((file, Bytes::from(buf)))
}
})
.await??;
Ok(res)
}
#[cfg(feature = "experimental-io-uring")]
async fn chunked_read_file_callback(
file: File,
offset: u64,
max_bytes: usize,
mut bytes_mut: BytesMut,
) -> io::Result<(File, Bytes, BytesMut)> {
bytes_mut.reserve(max_bytes);
let (res, mut bytes_mut) = file.read_at(bytes_mut, offset).await;
let n_bytes = res?;
if n_bytes == 0 {
return Err(io::ErrorKind::UnexpectedEof.into());
}
let bytes = bytes_mut.split_to(n_bytes).freeze();
Ok((file, bytes, bytes_mut))
}
#[cfg(feature = "experimental-io-uring")]
impl<F, Fut> Stream for ChunkedReadFile<F, Fut>
where
F: Fn(File, u64, usize, BytesMut) -> Fut,
Fut: Future<Output = io::Result<(File, Bytes, BytesMut)>>,
{
type Item = Result<Bytes, Error>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut this = self.as_mut().project();
match this.state.as_mut().project() {
ChunkedReadFileStateProj::File { file } => {
let size = *this.size;
let offset = *this.offset;
let counter = *this.counter;
if size == counter {
Poll::Ready(None)
} else {
let max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize;
let (file, bytes_mut) = file
.take()
.expect("ChunkedReadFile polled after completion");
let fut = (this.callback)(file, offset, max_bytes, bytes_mut);
this.state
.project_replace(ChunkedReadFileState::Future { fut });
self.poll_next(cx)
}
}
ChunkedReadFileStateProj::Future { fut } => {
let (file, bytes, bytes_mut) = ready!(fut.poll(cx))?;
this.state.project_replace(ChunkedReadFileState::File {
file: Some((file, bytes_mut)),
});
*this.offset += bytes.len() as u64;
*this.counter += bytes.len() as u64;
Poll::Ready(Some(Ok(bytes)))
}
}
}
}
#[cfg(not(feature = "experimental-io-uring"))]
impl<F, Fut> Stream for ChunkedReadFile<F, Fut>
where
F: Fn(File, u64, usize) -> Fut,
Fut: Future<Output = Result<(File, Bytes), Error>>,
{
type Item = Result<Bytes, Error>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut this = self.as_mut().project();
match this.state.as_mut().project() {
ChunkedReadFileStateProj::File { file } => {
let size = *this.size;
let offset = *this.offset;
let counter = *this.counter;
if size == counter {
Poll::Ready(None)
} else {
let max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize;
let file = file
.take()
.expect("ChunkedReadFile polled after completion");
let fut = (this.callback)(file, offset, max_bytes);
this.state
.project_replace(ChunkedReadFileState::Future { fut });
self.poll_next(cx)
}
}
ChunkedReadFileStateProj::Future { fut } => {
let (file, bytes) = ready!(fut.poll(cx))?;
this.state
.project_replace(ChunkedReadFileState::File { file: Some(file) });
*this.offset += bytes.len() as u64;
*this.counter += bytes.len() as u64;
Poll::Ready(Some(Ok(bytes)))
}
}
}
}

View File

@ -0,0 +1,121 @@
use std::{fmt::Write, fs::DirEntry, io, path::Path, path::PathBuf};
use actix_web::{dev::ServiceResponse, HttpRequest, HttpResponse};
use askama_escape::{escape as escape_html_entity, Html};
use percent_encoding::{utf8_percent_encode, CONTROLS};
/// A directory; responds with the generated directory listing.
#[derive(Debug)]
pub struct Directory {
/// Base directory.
pub base: PathBuf,
/// Path of subdirectory to generate listing for.
pub path: PathBuf,
}
impl Directory {
/// Create a new directory
pub fn new(base: PathBuf, path: PathBuf) -> Directory {
Directory { base, path }
}
/// Is this entry visible from this directory?
pub fn is_visible(&self, entry: &io::Result<DirEntry>) -> bool {
if let Ok(ref entry) = *entry {
if let Some(name) = entry.file_name().to_str() {
if name.starts_with('.') {
return false;
}
}
if let Ok(ref md) = entry.metadata() {
let ft = md.file_type();
return ft.is_dir() || ft.is_file() || ft.is_symlink();
}
}
false
}
}
pub(crate) type DirectoryRenderer =
dyn Fn(&Directory, &HttpRequest) -> Result<ServiceResponse, io::Error>;
/// Returns percent encoded file URL path.
macro_rules! encode_file_url {
($path:ident) => {
utf8_percent_encode(&$path, CONTROLS)
};
}
/// Returns HTML entity encoded formatter.
///
/// ```plain
/// " => &quot;
/// & => &amp;
/// ' => &#x27;
/// < => &lt;
/// > => &gt;
/// / => &#x2f;
/// ```
macro_rules! encode_file_name {
($entry:ident) => {
escape_html_entity(&$entry.file_name().to_string_lossy(), Html)
};
}
pub(crate) fn directory_listing(
dir: &Directory,
req: &HttpRequest,
) -> Result<ServiceResponse, io::Error> {
let index_of = format!("Index of {}", req.path());
let mut body = String::new();
let base = Path::new(req.path());
for entry in dir.path.read_dir()? {
if dir.is_visible(&entry) {
let entry = entry.unwrap();
let p = match entry.path().strip_prefix(&dir.path) {
Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace('\\', "/"),
Ok(p) => base.join(p).to_string_lossy().into_owned(),
Err(_) => continue,
};
// if file is a directory, add '/' to the end of the name
if let Ok(metadata) = entry.metadata() {
if metadata.is_dir() {
let _ = write!(
body,
"<li><a href=\"{}\">{}/</a></li>",
encode_file_url!(p),
encode_file_name!(entry),
);
} else {
let _ = write!(
body,
"<li><a href=\"{}\">{}</a></li>",
encode_file_url!(p),
encode_file_name!(entry),
);
}
} else {
continue;
}
}
}
let html = format!(
"<html>\
<head><title>{}</title></head>\
<body><h1>{}</h1>\
<ul>\
{}\
</ul></body>\n</html>",
index_of, index_of, body
);
Ok(ServiceResponse::new(
req.clone(),
HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(html),
))
}

View File

@ -0,0 +1,52 @@
use mime::Mime;
/// Transforms MIME `text/*` types into their UTF-8 equivalent, if supported.
///
/// MIME types that are converted
/// - application/javascript
/// - text/html
/// - text/css
/// - text/plain
/// - text/csv
/// - text/tab-separated-values
pub(crate) fn equiv_utf8_text(ct: Mime) -> Mime {
// use (roughly) order of file-type popularity for a web server
if ct == mime::APPLICATION_JAVASCRIPT {
return mime::APPLICATION_JAVASCRIPT_UTF_8;
}
if ct == mime::TEXT_HTML {
return mime::TEXT_HTML_UTF_8;
}
if ct == mime::TEXT_CSS {
return mime::TEXT_CSS_UTF_8;
}
if ct == mime::TEXT_PLAIN {
return mime::TEXT_PLAIN_UTF_8;
}
if ct == mime::TEXT_CSV {
return mime::TEXT_CSV_UTF_8;
}
if ct == mime::TEXT_TAB_SEPARATED_VALUES {
return mime::TEXT_TAB_SEPARATED_VALUES_UTF_8;
}
ct
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_equiv_utf8_text() {
assert_eq!(equiv_utf8_text(mime::TEXT_PLAIN), mime::TEXT_PLAIN_UTF_8);
assert_eq!(equiv_utf8_text(mime::TEXT_XML), mime::TEXT_XML);
assert_eq!(equiv_utf8_text(mime::IMAGE_PNG), mime::IMAGE_PNG);
}
}

50
actix-files/src/error.rs Normal file
View File

@ -0,0 +1,50 @@
use actix_web::{http::StatusCode, ResponseError};
use derive_more::Display;
/// Errors which can occur when serving static files.
#[derive(Display, Debug, PartialEq)]
pub enum FilesError {
/// Path is not a directory
#[allow(dead_code)]
#[display(fmt = "Path is not a directory. Unable to serve static files")]
IsNotDirectory,
/// Cannot render directory
#[display(fmt = "Unable to render directory without index file")]
IsDirectory,
}
/// Return `NotFound` for `FilesError`
impl ResponseError for FilesError {
fn status_code(&self) -> StatusCode {
StatusCode::NOT_FOUND
}
}
#[allow(clippy::enum_variant_names)]
#[derive(Display, Debug, PartialEq)]
#[non_exhaustive]
pub enum UriSegmentError {
/// The segment started with the wrapped invalid character.
#[display(fmt = "The segment started with the wrapped invalid character")]
BadStart(char),
/// The segment contained the wrapped invalid character.
#[display(fmt = "The segment contained the wrapped invalid character")]
BadChar(char),
/// The segment ended with the wrapped invalid character.
#[display(fmt = "The segment ended with the wrapped invalid character")]
BadEnd(char),
/// The path is not a valid UTF-8 string after doing percent decoding.
#[display(fmt = "The path is not a valid UTF-8 string after percent-decoding")]
NotValidUtf8,
}
/// Return `BadRequest` for `UriSegmentError`
impl ResponseError for UriSegmentError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
}

392
actix-files/src/files.rs Normal file
View File

@ -0,0 +1,392 @@
use std::{
cell::RefCell,
fmt, io,
path::{Path, PathBuf},
rc::Rc,
};
use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt};
use actix_web::{
dev::{
AppService, HttpServiceFactory, RequestHead, ResourceDef, ServiceRequest,
ServiceResponse,
},
error::Error,
guard::Guard,
http::header::DispositionType,
HttpRequest,
};
use futures_core::future::LocalBoxFuture;
use crate::{
directory_listing, named,
service::{FilesService, FilesServiceInner},
Directory, DirectoryRenderer, HttpNewService, MimeOverride, PathFilter,
};
/// Static files handling service.
///
/// `Files` service must be registered with `App::service()` method.
///
/// # Examples
/// ```
/// use actix_web::App;
/// use actix_files::Files;
///
/// let app = App::new()
/// .service(Files::new("/static", "."));
/// ```
pub struct Files {
mount_path: String,
directory: PathBuf,
index: Option<String>,
show_index: bool,
redirect_to_slash: bool,
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>,
path_filter: Option<Rc<PathFilter>>,
file_flags: named::Flags,
use_guards: Option<Rc<dyn Guard>>,
guards: Vec<Rc<dyn Guard>>,
hidden_files: bool,
}
impl fmt::Debug for Files {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Files")
}
}
impl Clone for Files {
fn clone(&self) -> Self {
Self {
directory: self.directory.clone(),
index: self.index.clone(),
show_index: self.show_index,
redirect_to_slash: self.redirect_to_slash,
default: self.default.clone(),
renderer: self.renderer.clone(),
file_flags: self.file_flags,
mount_path: self.mount_path.clone(),
mime_override: self.mime_override.clone(),
path_filter: self.path_filter.clone(),
use_guards: self.use_guards.clone(),
guards: self.guards.clone(),
hidden_files: self.hidden_files,
}
}
}
impl Files {
/// Create new `Files` instance for a specified base directory.
///
/// # Argument Order
/// The first argument (`mount_path`) is the root URL at which the static files are served.
/// For example, `/assets` will serve files at `example.com/assets/...`.
///
/// The second argument (`serve_from`) is the location on disk at which files are loaded.
/// This can be a relative path. For example, `./` would serve files from the current
/// working directory.
///
/// # Implementation Notes
/// If the mount path is set as the root path `/`, services registered after this one will
/// be inaccessible. Register more specific handlers and services first.
///
/// `Files` utilizes the existing Tokio thread-pool for blocking filesystem operations.
/// The number of running threads is adjusted over time as needed, up to a maximum of 512 times
/// the number of server [workers](actix_web::HttpServer::workers), by default.
pub fn new<T: Into<PathBuf>>(mount_path: &str, serve_from: T) -> Files {
let orig_dir = serve_from.into();
let dir = match orig_dir.canonicalize() {
Ok(canon_dir) => canon_dir,
Err(_) => {
log::error!("Specified path is not a directory: {:?}", orig_dir);
PathBuf::new()
}
};
Files {
mount_path: mount_path.trim_end_matches('/').to_owned(),
directory: dir,
index: None,
show_index: false,
redirect_to_slash: false,
default: Rc::new(RefCell::new(None)),
renderer: Rc::new(directory_listing),
mime_override: None,
path_filter: None,
file_flags: named::Flags::default(),
use_guards: None,
guards: Vec::new(),
hidden_files: false,
}
}
/// Show files listing for directories.
///
/// By default show files listing is disabled.
///
/// When used with [`Files::index_file()`], files listing is shown as a fallback
/// when the index file is not found.
pub fn show_files_listing(mut self) -> Self {
self.show_index = true;
self
}
/// Redirects to a slash-ended path when browsing a directory.
///
/// By default never redirect.
pub fn redirect_to_slash_directory(mut self) -> Self {
self.redirect_to_slash = true;
self
}
/// Set custom directory renderer
pub fn files_listing_renderer<F>(mut self, f: F) -> Self
where
for<'r, 's> F:
Fn(&'r Directory, &'s HttpRequest) -> Result<ServiceResponse, io::Error> + 'static,
{
self.renderer = Rc::new(f);
self
}
/// Specifies mime override callback
pub fn mime_override<F>(mut self, f: F) -> Self
where
F: Fn(&mime::Name<'_>) -> DispositionType + 'static,
{
self.mime_override = Some(Rc::new(f));
self
}
/// Sets path filtering closure.
///
/// The path provided to the closure is relative to `serve_from` path.
/// You can safely join this path with the `serve_from` path to get the real path.
/// However, the real path may not exist since the filter is called before checking path existence.
///
/// When a path doesn't pass the filter, [`Files::default_handler`] is called if set, otherwise,
/// `404 Not Found` is returned.
///
/// # Examples
/// ```
/// use std::path::Path;
/// use actix_files::Files;
///
/// // prevent searching subdirectories and following symlinks
/// let files_service = Files::new("/", "./static").path_filter(|path, _| {
/// path.components().count() == 1
/// && Path::new("./static")
/// .join(path)
/// .symlink_metadata()
/// .map(|m| !m.file_type().is_symlink())
/// .unwrap_or(false)
/// });
/// ```
pub fn path_filter<F>(mut self, f: F) -> Self
where
F: Fn(&Path, &RequestHead) -> bool + 'static,
{
self.path_filter = Some(Rc::new(f));
self
}
/// Set index file
///
/// Shows specific index file for directories instead of
/// showing files listing.
///
/// If the index file is not found, files listing is shown as a fallback if
/// [`Files::show_files_listing()`] is set.
pub fn index_file<T: Into<String>>(mut self, index: T) -> Self {
self.index = Some(index.into());
self
}
/// Specifies whether to use ETag or not.
///
/// Default is true.
pub fn use_etag(mut self, value: bool) -> Self {
self.file_flags.set(named::Flags::ETAG, value);
self
}
/// Specifies whether to use Last-Modified or not.
///
/// Default is true.
pub fn use_last_modified(mut self, value: bool) -> Self {
self.file_flags.set(named::Flags::LAST_MD, value);
self
}
/// Specifies whether text responses should signal a UTF-8 encoding.
///
/// Default is false (but will default to true in a future version).
pub fn prefer_utf8(mut self, value: bool) -> Self {
self.file_flags.set(named::Flags::PREFER_UTF8, value);
self
}
/// Adds a routing guard.
///
/// Use this to allow multiple chained file services that respond to strictly different
/// properties of a request. Due to the way routing works, if a guard check returns true and the
/// request starts being handled by the file service, it will not be able to back-out and try
/// the next service, you will simply get a 404 (or 405) error response.
///
/// To allow `POST` requests to retrieve files, see [`Files::use_guards`].
///
/// # Examples
/// ```
/// use actix_web::{guard::Header, App};
/// use actix_files::Files;
///
/// App::new().service(
/// Files::new("/","/my/site/files")
/// .guard(Header("Host", "example.com"))
/// );
/// ```
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
self.guards.push(Rc::new(guard));
self
}
/// Specifies guard to check before fetching directory listings or files.
///
/// Note that this guard has no effect on routing; it's main use is to guard on the request's
/// method just before serving the file, only allowing `GET` and `HEAD` requests by default.
/// See [`Files::guard`] for routing guards.
pub fn method_guard<G: Guard + 'static>(mut self, guard: G) -> Self {
self.use_guards = Some(Rc::new(guard));
self
}
/// See [`Files::method_guard`].
#[doc(hidden)]
#[deprecated(since = "0.6.0", note = "Renamed to `method_guard`.")]
pub fn use_guards<G: Guard + 'static>(self, guard: G) -> Self {
self.method_guard(guard)
}
/// Disable `Content-Disposition` header.
///
/// By default Content-Disposition` header is enabled.
pub fn disable_content_disposition(mut self) -> Self {
self.file_flags.remove(named::Flags::CONTENT_DISPOSITION);
self
}
/// Sets default handler which is used when no matched file could be found.
///
/// # Examples
/// Setting a fallback static file handler:
/// ```
/// use actix_files::{Files, NamedFile};
/// use actix_web::dev::{ServiceRequest, ServiceResponse, fn_service};
///
/// # fn run() -> Result<(), actix_web::Error> {
/// let files = Files::new("/", "./static")
/// .index_file("index.html")
/// .default_handler(fn_service(|req: ServiceRequest| async {
/// let (req, _) = req.into_parts();
/// let file = NamedFile::open_async("./static/404.html").await?;
/// let res = file.into_response(&req);
/// Ok(ServiceResponse::new(req, res))
/// }));
/// # Ok(())
/// # }
/// ```
pub fn default_handler<F, U>(mut self, f: F) -> Self
where
F: IntoServiceFactory<U, ServiceRequest>,
U: ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse,
Error = Error,
> + 'static,
{
// create and configure default resource
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory(
f.into_factory().map_init_err(|_| ()),
)))));
self
}
/// Enables serving hidden files and directories, allowing a leading dots in url fragments.
pub fn use_hidden_files(mut self) -> Self {
self.hidden_files = true;
self
}
}
impl HttpServiceFactory for Files {
fn register(mut self, config: &mut AppService) {
let guards = if self.guards.is_empty() {
None
} else {
let guards = std::mem::take(&mut self.guards);
Some(
guards
.into_iter()
.map(|guard| -> Box<dyn Guard> { Box::new(guard) })
.collect::<Vec<_>>(),
)
};
if self.default.borrow().is_none() {
*self.default.borrow_mut() = Some(config.default_service());
}
let rdef = if config.is_root() {
ResourceDef::root_prefix(&self.mount_path)
} else {
ResourceDef::prefix(&self.mount_path)
};
config.register_service(rdef, guards, self, None)
}
}
impl ServiceFactory<ServiceRequest> for Files {
type Response = ServiceResponse;
type Error = Error;
type Config = ();
type Service = FilesService;
type InitError = ();
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future {
let mut inner = FilesServiceInner {
directory: self.directory.clone(),
index: self.index.clone(),
show_index: self.show_index,
redirect_to_slash: self.redirect_to_slash,
default: None,
renderer: self.renderer.clone(),
mime_override: self.mime_override.clone(),
path_filter: self.path_filter.clone(),
file_flags: self.file_flags,
guards: self.use_guards.clone(),
hidden_files: self.hidden_files,
};
if let Some(ref default) = *self.default.borrow() {
let fut = default.new_service(());
Box::pin(async {
match fut.await {
Ok(default) => {
inner.default = Some(default);
Ok(FilesService(Rc::new(inner)))
}
Err(_) => Err(()),
}
})
} else {
Box::pin(async move { Ok(FilesService(Rc::new(inner))) })
}
}
}

989
actix-files/src/lib.rs Normal file
View File

@ -0,0 +1,989 @@
//! Static file serving for Actix Web.
//!
//! Provides a non-blocking service for serving static files from disk.
//!
//! # Examples
//! ```
//! use actix_web::App;
//! use actix_files::Files;
//!
//! let app = App::new()
//! .service(Files::new("/static", ".").prefer_utf8(true));
//! ```
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
use actix_service::boxed::{BoxService, BoxServiceFactory};
use actix_web::{
dev::{RequestHead, ServiceRequest, ServiceResponse},
error::Error,
http::header::DispositionType,
};
use mime_guess::from_ext;
use std::path::Path;
mod chunked;
mod directory;
mod encoding;
mod error;
mod files;
mod named;
mod path_buf;
mod range;
mod service;
pub use self::chunked::ChunkedReadFile;
pub use self::directory::Directory;
pub use self::files::Files;
pub use self::named::NamedFile;
pub use self::range::HttpRange;
pub use self::service::FilesService;
use self::directory::{directory_listing, DirectoryRenderer};
use self::error::FilesError;
use self::path_buf::PathBufWrap;
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
/// Return the MIME type associated with a filename extension (case-insensitive).
/// If `ext` is empty or no associated type for the extension was found, returns
/// the type `application/octet-stream`.
#[inline]
pub fn file_extension_to_mime(ext: &str) -> mime::Mime {
from_ext(ext).first_or_octet_stream()
}
type MimeOverride = dyn Fn(&mime::Name<'_>) -> DispositionType;
type PathFilter = dyn Fn(&Path, &RequestHead) -> bool;
#[cfg(test)]
mod tests {
use std::{
fs::{self},
ops::Add,
time::{Duration, SystemTime},
};
use actix_web::{
dev::ServiceFactory,
guard,
http::{
header::{self, ContentDisposition, DispositionParam, DispositionType},
Method, StatusCode,
},
middleware::Compress,
test::{self, TestRequest},
web::{self, Bytes},
App, HttpResponse, Responder,
};
use super::*;
use crate::named::File;
#[actix_web::test]
async fn test_file_extension_to_mime() {
let m = file_extension_to_mime("");
assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
let m = file_extension_to_mime("jpg");
assert_eq!(m, mime::IMAGE_JPEG);
let m = file_extension_to_mime("invalid extension!!");
assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
let m = file_extension_to_mime("");
assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
}
#[actix_rt::test]
async fn test_if_modified_since_without_if_none_match() {
let file = NamedFile::open_async("Cargo.toml").await.unwrap();
let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
let req = TestRequest::default()
.insert_header((header::IF_MODIFIED_SINCE, since))
.to_http_request();
let resp = file.respond_to(&req);
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
}
#[actix_rt::test]
async fn test_if_modified_since_without_if_none_match_same() {
let file = NamedFile::open_async("Cargo.toml").await.unwrap();
let since = file.last_modified().unwrap();
let req = TestRequest::default()
.insert_header((header::IF_MODIFIED_SINCE, since))
.to_http_request();
let resp = file.respond_to(&req);
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
}
#[actix_rt::test]
async fn test_if_modified_since_with_if_none_match() {
let file = NamedFile::open_async("Cargo.toml").await.unwrap();
let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
let req = TestRequest::default()
.insert_header((header::IF_NONE_MATCH, "miss_etag"))
.insert_header((header::IF_MODIFIED_SINCE, since))
.to_http_request();
let resp = file.respond_to(&req);
assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
}
#[actix_rt::test]
async fn test_if_unmodified_since() {
let file = NamedFile::open_async("Cargo.toml").await.unwrap();
let since = file.last_modified().unwrap();
let req = TestRequest::default()
.insert_header((header::IF_UNMODIFIED_SINCE, since))
.to_http_request();
let resp = file.respond_to(&req);
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn test_if_unmodified_since_failed() {
let file = NamedFile::open_async("Cargo.toml").await.unwrap();
let since = header::HttpDate::from(SystemTime::UNIX_EPOCH);
let req = TestRequest::default()
.insert_header((header::IF_UNMODIFIED_SINCE, since))
.to_http_request();
let resp = file.respond_to(&req);
assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED);
}
#[actix_rt::test]
async fn test_named_file_text() {
assert!(NamedFile::open_async("test--").await.is_err());
let mut file = NamedFile::open_async("Cargo.toml").await.unwrap();
{
file.file();
let _f: &File = &file;
}
{
let _f: &mut File = &mut file;
}
let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/x-toml"
);
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"Cargo.toml\""
);
}
#[actix_rt::test]
async fn test_named_file_content_disposition() {
assert!(NamedFile::open_async("test--").await.is_err());
let mut file = NamedFile::open_async("Cargo.toml").await.unwrap();
{
file.file();
let _f: &File = &file;
}
{
let _f: &mut File = &mut file;
}
let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req);
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"Cargo.toml\""
);
let file = NamedFile::open_async("Cargo.toml")
.await
.unwrap()
.disable_content_disposition();
let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req);
assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none());
}
#[actix_rt::test]
async fn test_named_file_non_ascii_file_name() {
let file = {
#[cfg(feature = "experimental-io-uring")]
{
crate::named::File::open("Cargo.toml").await.unwrap()
}
#[cfg(not(feature = "experimental-io-uring"))]
{
crate::named::File::open("Cargo.toml").unwrap()
}
};
let mut file = NamedFile::from_file(file, "貨物.toml").unwrap();
{
file.file();
let _f: &File = &file;
}
{
let _f: &mut File = &mut file;
}
let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/x-toml"
);
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"貨物.toml\"; filename*=UTF-8''%E8%B2%A8%E7%89%A9.toml"
);
}
#[actix_rt::test]
async fn test_named_file_set_content_type() {
let mut file = NamedFile::open_async("Cargo.toml")
.await
.unwrap()
.set_content_type(mime::TEXT_XML);
{
file.file();
let _f: &File = &file;
}
{
let _f: &mut File = &mut file;
}
let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/xml"
);
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"Cargo.toml\""
);
}
#[actix_rt::test]
async fn test_named_file_image() {
let mut file = NamedFile::open_async("tests/test.png").await.unwrap();
{
file.file();
let _f: &File = &file;
}
{
let _f: &mut File = &mut file;
}
let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"image/png"
);
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"test.png\""
);
}
#[actix_rt::test]
async fn test_named_file_javascript() {
let file = NamedFile::open_async("tests/test.js").await.unwrap();
let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"application/javascript; charset=utf-8"
);
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"test.js\""
);
}
#[actix_rt::test]
async fn test_named_file_image_attachment() {
let cd = ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![DispositionParam::Filename(String::from("test.png"))],
};
let mut file = NamedFile::open_async("tests/test.png")
.await
.unwrap()
.set_content_disposition(cd);
{
file.file();
let _f: &File = &file;
}
{
let _f: &mut File = &mut file;
}
let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"image/png"
);
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"attachment; filename=\"test.png\""
);
}
#[actix_rt::test]
async fn test_named_file_binary() {
let mut file = NamedFile::open_async("tests/test.binary").await.unwrap();
{
file.file();
let _f: &File = &file;
}
{
let _f: &mut File = &mut file;
}
let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"application/octet-stream"
);
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"attachment; filename=\"test.binary\""
);
}
#[actix_rt::test]
async fn test_named_file_status_code_text() {
let mut file = NamedFile::open_async("Cargo.toml")
.await
.unwrap()
.set_status_code(StatusCode::NOT_FOUND);
{
file.file();
let _f: &File = &file;
}
{
let _f: &mut File = &mut file;
}
let req = TestRequest::default().to_http_request();
let resp = file.respond_to(&req);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/x-toml"
);
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"Cargo.toml\""
);
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
#[actix_rt::test]
async fn test_mime_override() {
fn all_attachment(_: &mime::Name<'_>) -> DispositionType {
DispositionType::Attachment
}
let srv = test::init_service(
App::new().service(
Files::new("/", ".")
.mime_override(all_attachment)
.index_file("Cargo.toml"),
),
)
.await;
let request = TestRequest::get().uri("/").to_request();
let response = test::call_service(&srv, request).await;
assert_eq!(response.status(), StatusCode::OK);
let content_disposition = response
.headers()
.get(header::CONTENT_DISPOSITION)
.expect("To have CONTENT_DISPOSITION");
let content_disposition = content_disposition
.to_str()
.expect("Convert CONTENT_DISPOSITION to str");
assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\"");
}
#[actix_rt::test]
async fn test_named_file_ranges_status_code() {
let srv = test::init_service(
App::new().service(Files::new("/test", ".").index_file("Cargo.toml")),
)
.await;
// Valid range header
let request = TestRequest::get()
.uri("/t%65st/Cargo.toml")
.insert_header((header::RANGE, "bytes=10-20"))
.to_request();
let response = test::call_service(&srv, request).await;
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
// Invalid range header
let request = TestRequest::get()
.uri("/t%65st/Cargo.toml")
.insert_header((header::RANGE, "bytes=1-0"))
.to_request();
let response = test::call_service(&srv, request).await;
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
}
#[actix_rt::test]
async fn test_named_file_content_range_headers() {
let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
// Valid range header
let response = srv
.get("/tests/test.binary")
.insert_header((header::RANGE, "bytes=10-20"))
.send()
.await
.unwrap();
let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
assert_eq!(content_range.to_str().unwrap(), "bytes 10-20/100");
// Invalid range header
let response = srv
.get("/tests/test.binary")
.insert_header((header::RANGE, "bytes=10-5"))
.send()
.await
.unwrap();
let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
assert_eq!(content_range.to_str().unwrap(), "bytes */100");
}
#[actix_rt::test]
async fn test_named_file_content_length_headers() {
let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
// Valid range header
let response = srv
.get("/tests/test.binary")
.insert_header((header::RANGE, "bytes=10-20"))
.send()
.await
.unwrap();
let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
assert_eq!(content_length.to_str().unwrap(), "11");
// Valid range header, starting from 0
let response = srv
.get("/tests/test.binary")
.insert_header((header::RANGE, "bytes=0-20"))
.send()
.await
.unwrap();
let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
assert_eq!(content_length.to_str().unwrap(), "21");
// Without range header
let mut response = srv.get("/tests/test.binary").send().await.unwrap();
let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
assert_eq!(content_length.to_str().unwrap(), "100");
// Should be no transfer-encoding
let transfer_encoding = response.headers().get(header::TRANSFER_ENCODING);
assert!(transfer_encoding.is_none());
// Check file contents
let bytes = response.body().await.unwrap();
let data = web::Bytes::from(fs::read("tests/test.binary").unwrap());
assert_eq!(bytes, data);
}
#[actix_rt::test]
async fn test_head_content_length_headers() {
let srv = actix_test::start(|| App::new().service(Files::new("/", ".")));
let response = srv.head("/tests/test.binary").send().await.unwrap();
let content_length = response
.headers()
.get(header::CONTENT_LENGTH)
.unwrap()
.to_str()
.unwrap();
assert_eq!(content_length, "100");
}
#[actix_rt::test]
async fn test_static_files_with_spaces() {
let srv = test::init_service(
App::new().service(Files::new("/", ".").index_file("Cargo.toml")),
)
.await;
let request = TestRequest::get()
.uri("/tests/test%20space.binary")
.to_request();
let response = test::call_service(&srv, request).await;
assert_eq!(response.status(), StatusCode::OK);
let bytes = test::read_body(response).await;
let data = web::Bytes::from(fs::read("tests/test space.binary").unwrap());
assert_eq!(bytes, data);
}
#[actix_rt::test]
async fn test_files_not_allowed() {
let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
let req = TestRequest::default()
.uri("/Cargo.toml")
.method(Method::POST)
.to_request();
let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
let req = TestRequest::default()
.method(Method::PUT)
.uri("/Cargo.toml")
.to_request();
let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
}
#[actix_rt::test]
async fn test_files_guards() {
let srv = test::init_service(
App::new().service(Files::new("/", ".").method_guard(guard::Post())),
)
.await;
let req = TestRequest::default()
.uri("/Cargo.toml")
.method(Method::POST)
.to_request();
let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn test_named_file_content_encoding() {
let srv = test::init_service(App::new().wrap(Compress::default()).service(
web::resource("/").to(|| async {
NamedFile::open_async("Cargo.toml")
.await
.unwrap()
.set_content_encoding(header::ContentEncoding::Identity)
}),
))
.await;
let request = TestRequest::get()
.uri("/")
.insert_header((header::ACCEPT_ENCODING, "gzip"))
.to_request();
let res = test::call_service(&srv, request).await;
assert_eq!(res.status(), StatusCode::OK);
assert!(res.headers().contains_key(header::CONTENT_ENCODING));
assert!(!test::read_body(res).await.is_empty());
}
#[actix_rt::test]
async fn test_named_file_content_encoding_gzip() {
let srv = test::init_service(App::new().wrap(Compress::default()).service(
web::resource("/").to(|| async {
NamedFile::open_async("Cargo.toml")
.await
.unwrap()
.set_content_encoding(header::ContentEncoding::Gzip)
}),
))
.await;
let request = TestRequest::get()
.uri("/")
.insert_header((header::ACCEPT_ENCODING, "gzip"))
.to_request();
let res = test::call_service(&srv, request).await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers()
.get(header::CONTENT_ENCODING)
.unwrap()
.to_str()
.unwrap(),
"gzip"
);
}
#[actix_rt::test]
async fn test_named_file_allowed_method() {
let req = TestRequest::default().method(Method::GET).to_http_request();
let file = NamedFile::open_async("Cargo.toml").await.unwrap();
let resp = file.respond_to(&req);
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn test_static_files() {
let srv =
test::init_service(App::new().service(Files::new("/", ".").show_files_listing()))
.await;
let req = TestRequest::with_uri("/missing").to_request();
let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
let req = TestRequest::default().to_request();
let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let srv =
test::init_service(App::new().service(Files::new("/", ".").show_files_listing()))
.await;
let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&srv, req).await;
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/html; charset=utf-8"
);
let bytes = test::read_body(resp).await;
assert!(format!("{:?}", bytes).contains("/tests/test.png"));
}
#[actix_rt::test]
async fn test_redirect_to_slash_directory() {
// should not redirect if no index and files listing is disabled
let srv = test::init_service(
App::new().service(Files::new("/", ".").redirect_to_slash_directory()),
)
.await;
let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
// should redirect if index present
let srv = test::init_service(
App::new().service(
Files::new("/", ".")
.index_file("test.png")
.redirect_to_slash_directory(),
),
)
.await;
let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::FOUND);
// should redirect if files listing is enabled
let srv = test::init_service(
App::new().service(
Files::new("/", ".")
.show_files_listing()
.redirect_to_slash_directory(),
),
)
.await;
let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::FOUND);
// should not redirect if the path is wrong
let req = TestRequest::with_uri("/not_existing").to_request();
let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
#[actix_rt::test]
async fn test_static_files_bad_directory() {
let service = Files::new("/", "./missing").new_service(()).await.unwrap();
let req = TestRequest::with_uri("/").to_srv_request();
let resp = test::call_service(&service, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
#[actix_rt::test]
async fn test_default_handler_file_missing() {
let st = Files::new("/", ".")
.default_handler(|req: ServiceRequest| async {
Ok(req.into_response(HttpResponse::Ok().body("default content")))
})
.new_service(())
.await
.unwrap();
let req = TestRequest::with_uri("/missing").to_srv_request();
let resp = test::call_service(&st, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let bytes = test::read_body(resp).await;
assert_eq!(bytes, web::Bytes::from_static(b"default content"));
}
#[actix_rt::test]
async fn test_serve_index_nested() {
let service = Files::new(".", ".")
.index_file("lib.rs")
.new_service(())
.await
.unwrap();
let req = TestRequest::default().uri("/src").to_srv_request();
let resp = test::call_service(&service, req).await;
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/x-rust"
);
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"lib.rs\""
);
}
#[actix_rt::test]
async fn integration_serve_index() {
let srv = test::init_service(
App::new().service(Files::new("test", ".").index_file("Cargo.toml")),
)
.await;
let req = TestRequest::get().uri("/test").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
let bytes = test::read_body(res).await;
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
assert_eq!(bytes, data);
let req = TestRequest::get().uri("/test/").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
let bytes = test::read_body(res).await;
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
assert_eq!(bytes, data);
// nonexistent index file
let req = TestRequest::get().uri("/test/unknown").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let req = TestRequest::get().uri("/test/unknown/").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[actix_rt::test]
async fn integration_percent_encoded() {
let srv = test::init_service(
App::new().service(Files::new("test", ".").index_file("Cargo.toml")),
)
.await;
let req = TestRequest::get().uri("/test/%43argo.toml").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
// `%2F` == `/`
let req = TestRequest::get().uri("/test%2Ftest.binary").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let req = TestRequest::get().uri("/test/Cargo.toml%00").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[actix_rt::test]
async fn test_percent_encoding_2() {
let tmpdir = tempfile::tempdir().unwrap();
let filename = match cfg!(unix) {
true => "ض:?#[]{}<>()@!$&'`|*+,;= %20.test",
false => "ض#[]{}()@!$&'`+,;= %20.test",
};
let filename_encoded = filename
.as_bytes()
.iter()
.map(|c| format!("%{:02X}", c))
.collect::<String>();
std::fs::File::create(tmpdir.path().join(filename)).unwrap();
let srv = test::init_service(App::new().service(Files::new("", tmpdir.path()))).await;
let req = TestRequest::get()
.uri(&format!("/{}", filename_encoded))
.to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn test_serve_named_file() {
let factory = NamedFile::open_async("Cargo.toml").await.unwrap();
let srv = test::init_service(App::new().service(factory)).await;
let req = TestRequest::get().uri("/Cargo.toml").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
let bytes = test::read_body(res).await;
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
assert_eq!(bytes, data);
let req = TestRequest::get().uri("/test/unknown").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[actix_rt::test]
async fn test_serve_named_file_prefix() {
let factory = NamedFile::open_async("Cargo.toml").await.unwrap();
let srv =
test::init_service(App::new().service(web::scope("/test").service(factory))).await;
let req = TestRequest::get().uri("/test/Cargo.toml").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
let bytes = test::read_body(res).await;
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
assert_eq!(bytes, data);
let req = TestRequest::get().uri("/Cargo.toml").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[actix_rt::test]
async fn test_named_file_default_service() {
let factory = NamedFile::open_async("Cargo.toml").await.unwrap();
let srv = test::init_service(App::new().default_service(factory)).await;
for route in ["/foobar", "/baz", "/"].iter() {
let req = TestRequest::get().uri(route).to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
let bytes = test::read_body(res).await;
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
assert_eq!(bytes, data);
}
}
#[actix_rt::test]
async fn test_default_handler_named_file() {
let factory = NamedFile::open_async("Cargo.toml").await.unwrap();
let st = Files::new("/", ".")
.default_handler(factory)
.new_service(())
.await
.unwrap();
let req = TestRequest::with_uri("/missing").to_srv_request();
let resp = test::call_service(&st, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let bytes = test::read_body(resp).await;
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
assert_eq!(bytes, data);
}
#[actix_rt::test]
async fn test_symlinks() {
let srv = test::init_service(App::new().service(Files::new("test", "."))).await;
let req = TestRequest::get()
.uri("/test/tests/symlink-test.png")
.to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"symlink-test.png\""
);
}
#[actix_rt::test]
async fn test_index_with_show_files_listing() {
let service = Files::new(".", ".")
.index_file("lib.rs")
.show_files_listing()
.new_service(())
.await
.unwrap();
// Serve the index if exists
let req = TestRequest::default().uri("/src").to_srv_request();
let resp = test::call_service(&service, req).await;
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/x-rust"
);
// Show files listing, otherwise.
let req = TestRequest::default().uri("/tests").to_srv_request();
let resp = test::call_service(&service, req).await;
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/html; charset=utf-8"
);
let bytes = test::read_body(resp).await;
assert!(format!("{:?}", bytes).contains("/tests/test.png"));
}
#[actix_rt::test]
async fn test_path_filter() {
// prevent searching subdirectories
let st = Files::new("/", ".")
.path_filter(|path, _| path.components().count() == 1)
.new_service(())
.await
.unwrap();
let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
let resp = test::call_service(&st, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/src/lib.rs").to_srv_request();
let resp = test::call_service(&st, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
#[actix_rt::test]
async fn test_default_handler_filter() {
let st = Files::new("/", ".")
.default_handler(|req: ServiceRequest| async {
Ok(req.into_response(HttpResponse::Ok().body("default content")))
})
.path_filter(|path, _| path.extension() == Some("png".as_ref()))
.new_service(())
.await
.unwrap();
let req = TestRequest::with_uri("/Cargo.toml").to_srv_request();
let resp = test::call_service(&st, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let bytes = test::read_body(resp).await;
assert_eq!(bytes, web::Bytes::from_static(b"default content"));
}
}

631
actix-files/src/named.rs Normal file
View File

@ -0,0 +1,631 @@
use std::{
fs::Metadata,
io,
path::{Path, PathBuf},
time::{SystemTime, UNIX_EPOCH},
};
use actix_web::{
body::{self, BoxBody, SizedStream},
dev::{
self, AppService, HttpServiceFactory, ResourceDef, Service, ServiceFactory,
ServiceRequest, ServiceResponse,
},
http::{
header::{
self, Charset, ContentDisposition, ContentEncoding, DispositionParam,
DispositionType, ExtendedValue, HeaderValue,
},
StatusCode,
},
Error, HttpMessage, HttpRequest, HttpResponse, Responder,
};
use bitflags::bitflags;
use derive_more::{Deref, DerefMut};
use futures_core::future::LocalBoxFuture;
use mime_guess::from_path;
use crate::{encoding::equiv_utf8_text, range::HttpRange};
bitflags! {
pub(crate) struct Flags: u8 {
const ETAG = 0b0000_0001;
const LAST_MD = 0b0000_0010;
const CONTENT_DISPOSITION = 0b0000_0100;
const PREFER_UTF8 = 0b0000_1000;
}
}
impl Default for Flags {
fn default() -> Self {
Flags::from_bits_truncate(0b0000_1111)
}
}
/// A file with an associated name.
///
/// `NamedFile` can be registered as services:
/// ```
/// use actix_web::App;
/// use actix_files::NamedFile;
///
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let file = NamedFile::open_async("./static/index.html").await?;
/// let app = App::new().service(file);
/// # Ok(())
/// # }
/// ```
///
/// They can also be returned from handlers:
/// ```
/// use actix_web::{Responder, get};
/// use actix_files::NamedFile;
///
/// #[get("/")]
/// async fn index() -> impl Responder {
/// NamedFile::open_async("./static/index.html").await
/// }
/// ```
#[derive(Debug, Deref, DerefMut)]
pub struct NamedFile {
#[deref]
#[deref_mut]
file: File,
path: PathBuf,
modified: Option<SystemTime>,
pub(crate) md: Metadata,
pub(crate) flags: Flags,
pub(crate) status_code: StatusCode,
pub(crate) content_type: mime::Mime,
pub(crate) content_disposition: header::ContentDisposition,
pub(crate) encoding: Option<ContentEncoding>,
}
#[cfg(not(feature = "experimental-io-uring"))]
pub(crate) use std::fs::File;
#[cfg(feature = "experimental-io-uring")]
pub(crate) use tokio_uring::fs::File;
use super::chunked;
impl NamedFile {
/// Creates an instance from a previously opened file.
///
/// The given `path` need not exist and is only used to determine the `ContentType` and
/// `ContentDisposition` headers.
///
/// # Examples
/// ```ignore
/// use std::{
/// io::{self, Write as _},
/// env,
/// fs::File
/// };
/// use actix_files::NamedFile;
///
/// let mut file = File::create("foo.txt")?;
/// file.write_all(b"Hello, world!")?;
/// let named_file = NamedFile::from_file(file, "bar.txt")?;
/// # std::fs::remove_file("foo.txt");
/// Ok(())
/// ```
pub fn from_file<P: AsRef<Path>>(file: File, path: P) -> io::Result<NamedFile> {
let path = path.as_ref().to_path_buf();
// Get the name of the file and use it to construct default Content-Type
// and Content-Disposition values
let (content_type, content_disposition) = {
let filename = match path.file_name() {
Some(name) => name.to_string_lossy(),
None => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Provided path has no filename",
));
}
};
let ct = from_path(&path).first_or_octet_stream();
let disposition = match ct.type_() {
mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
mime::APPLICATION => match ct.subtype() {
mime::JAVASCRIPT | mime::JSON => DispositionType::Inline,
name if name == "wasm" => DispositionType::Inline,
_ => DispositionType::Attachment,
},
_ => DispositionType::Attachment,
};
let mut parameters =
vec![DispositionParam::Filename(String::from(filename.as_ref()))];
if !filename.is_ascii() {
parameters.push(DispositionParam::FilenameExt(ExtendedValue {
charset: Charset::Ext(String::from("UTF-8")),
language_tag: None,
value: filename.into_owned().into_bytes(),
}))
}
let cd = ContentDisposition {
disposition,
parameters,
};
(ct, cd)
};
let md = {
#[cfg(not(feature = "experimental-io-uring"))]
{
file.metadata()?
}
#[cfg(feature = "experimental-io-uring")]
{
use std::os::unix::prelude::{AsRawFd, FromRawFd};
let fd = file.as_raw_fd();
// SAFETY: fd is borrowed and lives longer than the unsafe block
unsafe {
let file = std::fs::File::from_raw_fd(fd);
let md = file.metadata();
// SAFETY: forget the fd before exiting block in success or error case but don't
// run destructor (that would close file handle)
std::mem::forget(file);
md?
}
}
};
let modified = md.modified().ok();
let encoding = None;
Ok(NamedFile {
path,
file,
content_type,
content_disposition,
md,
modified,
encoding,
status_code: StatusCode::OK,
flags: Flags::default(),
})
}
/// Attempts to open a file in read-only mode.
///
/// # Examples
/// ```
/// use actix_files::NamedFile;
/// let file = NamedFile::open("foo.txt");
/// ```
#[cfg(not(feature = "experimental-io-uring"))]
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> {
let file = File::open(&path)?;
Self::from_file(file, path)
}
#[allow(rustdoc::broken_intra_doc_links)]
/// Attempts to open a file asynchronously in read-only mode.
///
/// When the `experimental-io-uring` crate feature is enabled, this will be async.
/// Otherwise, it will be just like [`open`][Self::open].
///
/// # Examples
/// ```
/// use actix_files::NamedFile;
/// # async fn open() {
/// let file = NamedFile::open_async("foo.txt").await.unwrap();
/// # }
/// ```
pub async fn open_async<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> {
let file = {
#[cfg(not(feature = "experimental-io-uring"))]
{
File::open(&path)?
}
#[cfg(feature = "experimental-io-uring")]
{
File::open(&path).await?
}
};
Self::from_file(file, path)
}
/// Returns reference to the underlying `File` object.
#[inline]
pub fn file(&self) -> &File {
&self.file
}
/// Retrieve the path of this file.
///
/// # Examples
/// ```
/// # use std::io;
/// use actix_files::NamedFile;
///
/// # async fn path() -> io::Result<()> {
/// let file = NamedFile::open_async("test.txt").await?;
/// assert_eq!(file.path().as_os_str(), "foo.txt");
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn path(&self) -> &Path {
self.path.as_path()
}
/// Set response **Status Code**
pub fn set_status_code(mut self, status: StatusCode) -> Self {
self.status_code = status;
self
}
/// Set the MIME Content-Type for serving this file. By default the Content-Type is inferred
/// from the filename extension.
#[inline]
pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self {
self.content_type = mime_type;
self
}
/// Set the Content-Disposition for serving this file. This allows changing the
/// `inline/attachment` disposition as well as the filename sent to the peer.
///
/// By default the disposition is `inline` for `text/*`, `image/*`, `video/*` and
/// `application/{javascript, json, wasm}` mime types, and `attachment` otherwise, and the
/// filename is taken from the path provided in the `open` method after converting it to UTF-8
/// (using `to_string_lossy`).
#[inline]
pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self {
self.content_disposition = cd;
self.flags.insert(Flags::CONTENT_DISPOSITION);
self
}
/// Disable `Content-Disposition` header.
///
/// By default Content-Disposition` header is enabled.
#[inline]
pub fn disable_content_disposition(mut self) -> Self {
self.flags.remove(Flags::CONTENT_DISPOSITION);
self
}
/// Sets content encoding for this file.
///
/// This prevents the `Compress` middleware from modifying the file contents and signals to
/// browsers/clients how to decode it. For example, if serving a compressed HTML file (e.g.,
/// `index.html.gz`) then use `.set_content_encoding(ContentEncoding::Gzip)`.
#[inline]
pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self {
self.encoding = Some(enc);
self
}
/// Specifies whether to return `ETag` header in response.
///
/// Default is true.
#[inline]
pub fn use_etag(mut self, value: bool) -> Self {
self.flags.set(Flags::ETAG, value);
self
}
/// Specifies whether to return `Last-Modified` header in response.
///
/// Default is true.
#[inline]
pub fn use_last_modified(mut self, value: bool) -> Self {
self.flags.set(Flags::LAST_MD, value);
self
}
/// Specifies whether text responses should signal a UTF-8 encoding.
///
/// Default is false (but will default to true in a future version).
#[inline]
pub fn prefer_utf8(mut self, value: bool) -> Self {
self.flags.set(Flags::PREFER_UTF8, value);
self
}
/// Creates an `ETag` in a format is similar to Apache's.
pub(crate) fn etag(&self) -> Option<header::EntityTag> {
self.modified.as_ref().map(|mtime| {
let ino = {
#[cfg(unix)]
{
#[cfg(unix)]
use std::os::unix::fs::MetadataExt as _;
self.md.ino()
}
#[cfg(not(unix))]
{
0
}
};
let dur = mtime
.duration_since(UNIX_EPOCH)
.expect("modification time must be after epoch");
header::EntityTag::new_strong(format!(
"{:x}:{:x}:{:x}:{:x}",
ino,
self.md.len(),
dur.as_secs(),
dur.subsec_nanos()
))
})
}
pub(crate) fn last_modified(&self) -> Option<header::HttpDate> {
self.modified.map(|mtime| mtime.into())
}
/// Creates an `HttpResponse` with file as a streaming body.
pub fn into_response(self, req: &HttpRequest) -> HttpResponse<BoxBody> {
if self.status_code != StatusCode::OK {
let mut res = HttpResponse::build(self.status_code);
let ct = if self.flags.contains(Flags::PREFER_UTF8) {
equiv_utf8_text(self.content_type.clone())
} else {
self.content_type
};
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
res.insert_header((
header::CONTENT_DISPOSITION,
self.content_disposition.to_string(),
));
}
if let Some(current_encoding) = self.encoding {
res.insert_header((header::CONTENT_ENCODING, current_encoding.as_str()));
}
let reader = chunked::new_chunked_read(self.md.len(), 0, self.file);
return res.streaming(reader);
}
let etag = if self.flags.contains(Flags::ETAG) {
self.etag()
} else {
None
};
let last_modified = if self.flags.contains(Flags::LAST_MD) {
self.last_modified()
} else {
None
};
// check preconditions
let precondition_failed = if !any_match(etag.as_ref(), req) {
true
} else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) =
(last_modified, req.get_header())
{
let t1: SystemTime = (*m).into();
let t2: SystemTime = (*since).into();
match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) {
(Ok(t1), Ok(t2)) => t1.as_secs() > t2.as_secs(),
_ => false,
}
} else {
false
};
// check last modified
let not_modified = if !none_match(etag.as_ref(), req) {
true
} else if req.headers().contains_key(header::IF_NONE_MATCH) {
false
} else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) =
(last_modified, req.get_header())
{
let t1: SystemTime = (*m).into();
let t2: SystemTime = (*since).into();
match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) {
(Ok(t1), Ok(t2)) => t1.as_secs() <= t2.as_secs(),
_ => false,
}
} else {
false
};
let mut res = HttpResponse::build(self.status_code);
let ct = if self.flags.contains(Flags::PREFER_UTF8) {
equiv_utf8_text(self.content_type.clone())
} else {
self.content_type
};
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
res.insert_header((
header::CONTENT_DISPOSITION,
self.content_disposition.to_string(),
));
}
if let Some(current_encoding) = self.encoding {
res.insert_header((header::CONTENT_ENCODING, current_encoding.as_str()));
}
if let Some(lm) = last_modified {
res.insert_header((header::LAST_MODIFIED, lm.to_string()));
}
if let Some(etag) = etag {
res.insert_header((header::ETAG, etag.to_string()));
}
res.insert_header((header::ACCEPT_RANGES, "bytes"));
let mut length = self.md.len();
let mut offset = 0;
// check for range header
if let Some(ranges) = req.headers().get(header::RANGE) {
if let Ok(ranges_header) = ranges.to_str() {
if let Ok(ranges) = HttpRange::parse(ranges_header, length) {
length = ranges[0].length;
offset = ranges[0].start;
// don't allow compression middleware to modify partial content
res.insert_header((
header::CONTENT_ENCODING,
HeaderValue::from_static("identity"),
));
res.insert_header((
header::CONTENT_RANGE,
format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()),
));
} else {
res.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length)));
return res.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
};
} else {
return res.status(StatusCode::BAD_REQUEST).finish();
};
};
if precondition_failed {
return res.status(StatusCode::PRECONDITION_FAILED).finish();
} else if not_modified {
return res
.status(StatusCode::NOT_MODIFIED)
.body(body::None::new())
.map_into_boxed_body();
}
let reader = chunked::new_chunked_read(length, offset, self.file);
if offset != 0 || length != self.md.len() {
res.status(StatusCode::PARTIAL_CONTENT);
}
res.body(SizedStream::new(length, reader))
}
}
/// Returns true if `req` has no `If-Match` header or one which matches `etag`.
fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
match req.get_header::<header::IfMatch>() {
None | Some(header::IfMatch::Any) => true,
Some(header::IfMatch::Items(ref items)) => {
if let Some(some_etag) = etag {
for item in items {
if item.strong_eq(some_etag) {
return true;
}
}
}
false
}
}
}
/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`.
fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
match req.get_header::<header::IfNoneMatch>() {
Some(header::IfNoneMatch::Any) => false,
Some(header::IfNoneMatch::Items(ref items)) => {
if let Some(some_etag) = etag {
for item in items {
if item.weak_eq(some_etag) {
return false;
}
}
}
true
}
None => true,
}
}
impl Responder for NamedFile {
type Body = BoxBody;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
self.into_response(req)
}
}
impl ServiceFactory<ServiceRequest> for NamedFile {
type Response = ServiceResponse;
type Error = Error;
type Config = ();
type Service = NamedFileService;
type InitError = ();
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future {
let service = NamedFileService {
path: self.path.clone(),
};
Box::pin(async move { Ok(service) })
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct NamedFileService {
path: PathBuf,
}
impl Service<ServiceRequest> for NamedFileService {
type Response = ServiceResponse;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
dev::always_ready!();
fn call(&self, req: ServiceRequest) -> Self::Future {
let (req, _) = req.into_parts();
let path = self.path.clone();
Box::pin(async move {
let file = NamedFile::open_async(path).await?;
let res = file.into_response(&req);
Ok(ServiceResponse::new(req, res))
})
}
}
impl HttpServiceFactory for NamedFile {
fn register(self, config: &mut AppService) {
config.register_service(
ResourceDef::root_prefix(self.path.to_string_lossy().as_ref()),
None,
self,
None,
)
}
}

190
actix-files/src/path_buf.rs Normal file
View File

@ -0,0 +1,190 @@
use std::{
path::{Component, Path, PathBuf},
str::FromStr,
};
use actix_utils::future::{ready, Ready};
use actix_web::{dev::Payload, FromRequest, HttpRequest};
use crate::error::UriSegmentError;
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct PathBufWrap(PathBuf);
impl FromStr for PathBufWrap {
type Err = UriSegmentError;
fn from_str(path: &str) -> Result<Self, Self::Err> {
Self::parse_path(path, false)
}
}
impl PathBufWrap {
/// Parse a path, giving the choice of allowing hidden files to be considered valid segments.
///
/// Path traversal is guarded by this method.
pub fn parse_path(path: &str, hidden_files: bool) -> Result<Self, UriSegmentError> {
let mut buf = PathBuf::new();
// equivalent to `path.split('/').count()`
let mut segment_count = path.matches('/').count() + 1;
// we can decode the whole path here (instead of per-segment decoding)
// because we will reject `%2F` in paths using `segement_count`.
let path = percent_encoding::percent_decode_str(path)
.decode_utf8()
.map_err(|_| UriSegmentError::NotValidUtf8)?;
// disallow decoding `%2F` into `/`
if segment_count != path.matches('/').count() + 1 {
return Err(UriSegmentError::BadChar('/'));
}
for segment in path.split('/') {
if segment == ".." {
segment_count -= 1;
buf.pop();
} else if !hidden_files && segment.starts_with('.') {
return Err(UriSegmentError::BadStart('.'));
} else if segment.starts_with('*') {
return Err(UriSegmentError::BadStart('*'));
} else if segment.ends_with(':') {
return Err(UriSegmentError::BadEnd(':'));
} else if segment.ends_with('>') {
return Err(UriSegmentError::BadEnd('>'));
} else if segment.ends_with('<') {
return Err(UriSegmentError::BadEnd('<'));
} else if segment.is_empty() {
segment_count -= 1;
continue;
} else if cfg!(windows) && segment.contains('\\') {
return Err(UriSegmentError::BadChar('\\'));
} else if cfg!(windows) && segment.contains(':') {
return Err(UriSegmentError::BadChar(':'));
} else {
buf.push(segment)
}
}
// make sure we agree with stdlib parser
for (i, component) in buf.components().enumerate() {
assert!(
matches!(component, Component::Normal(_)),
"component `{:?}` is not normal",
component
);
assert!(i < segment_count);
}
Ok(PathBufWrap(buf))
}
}
impl AsRef<Path> for PathBufWrap {
fn as_ref(&self) -> &Path {
self.0.as_ref()
}
}
impl FromRequest for PathBufWrap {
type Error = UriSegmentError;
type Future = Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
ready(req.match_info().unprocessed().parse())
}
}
#[cfg(test)]
mod tests {
use std::iter::FromIterator;
use super::*;
#[test]
fn test_path_buf() {
assert_eq!(
PathBufWrap::from_str("/test/.tt").map(|t| t.0),
Err(UriSegmentError::BadStart('.'))
);
assert_eq!(
PathBufWrap::from_str("/test/*tt").map(|t| t.0),
Err(UriSegmentError::BadStart('*'))
);
assert_eq!(
PathBufWrap::from_str("/test/tt:").map(|t| t.0),
Err(UriSegmentError::BadEnd(':'))
);
assert_eq!(
PathBufWrap::from_str("/test/tt<").map(|t| t.0),
Err(UriSegmentError::BadEnd('<'))
);
assert_eq!(
PathBufWrap::from_str("/test/tt>").map(|t| t.0),
Err(UriSegmentError::BadEnd('>'))
);
assert_eq!(
PathBufWrap::from_str("/seg1/seg2/").unwrap().0,
PathBuf::from_iter(vec!["seg1", "seg2"])
);
assert_eq!(
PathBufWrap::from_str("/seg1/../seg2/").unwrap().0,
PathBuf::from_iter(vec!["seg2"])
);
}
#[test]
fn test_parse_path() {
assert_eq!(
PathBufWrap::parse_path("/test/.tt", false).map(|t| t.0),
Err(UriSegmentError::BadStart('.'))
);
assert_eq!(
PathBufWrap::parse_path("/test/.tt", true).unwrap().0,
PathBuf::from_iter(vec!["test", ".tt"])
);
}
#[test]
fn path_traversal() {
assert_eq!(
PathBufWrap::parse_path("/../README.md", false).unwrap().0,
PathBuf::from_iter(vec!["README.md"])
);
assert_eq!(
PathBufWrap::parse_path("/../README.md", true).unwrap().0,
PathBuf::from_iter(vec!["README.md"])
);
assert_eq!(
PathBufWrap::parse_path("/../../../../../../../../../../etc/passwd", false)
.unwrap()
.0,
PathBuf::from_iter(vec!["etc/passwd"])
);
}
#[test]
#[cfg_attr(windows, should_panic)]
fn windows_drive_traversal() {
// detect issues in windows that could lead to path traversal
// see <https://github.com/SergioBenitez/Rocket/issues/1949
assert_eq!(
PathBufWrap::parse_path("C:test.txt", false).unwrap().0,
PathBuf::from_iter(vec!["C:test.txt"])
);
assert_eq!(
PathBufWrap::parse_path("C:../whatever", false).unwrap().0,
PathBuf::from_iter(vec!["C:../whatever"])
);
assert_eq!(
PathBufWrap::parse_path(":test.txt", false).unwrap().0,
PathBuf::from_iter(vec![":test.txt"])
);
}
}

310
actix-files/src/range.rs Normal file
View File

@ -0,0 +1,310 @@
use derive_more::{Display, Error};
/// HTTP Range header representation.
#[derive(Debug, Clone, Copy)]
pub struct HttpRange {
/// Start of range.
pub start: u64,
/// Length of range.
pub length: u64,
}
#[derive(Debug, Clone, Display, Error)]
#[display(fmt = "Parse HTTP Range failed")]
pub struct ParseRangeErr(#[error(not(source))] ());
impl HttpRange {
/// Parses Range HTTP header string as per RFC 2616.
///
/// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`).
/// `size` is full size of response (file).
pub fn parse(header: &str, size: u64) -> Result<Vec<HttpRange>, ParseRangeErr> {
match http_range::HttpRange::parse(header, size) {
Ok(ranges) => Ok(ranges
.iter()
.map(|range| HttpRange {
start: range.start,
length: range.length,
})
.collect()),
Err(_) => Err(ParseRangeErr(())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct T(&'static str, u64, Vec<HttpRange>);
#[test]
fn test_parse() {
let tests = vec![
T("", 0, vec![]),
T("", 1000, vec![]),
T("foo", 0, vec![]),
T("bytes=", 0, vec![]),
T("bytes=7", 10, vec![]),
T("bytes= 7 ", 10, vec![]),
T("bytes=1-", 0, vec![]),
T("bytes=5-4", 10, vec![]),
T("bytes=0-2,5-4", 10, vec![]),
T("bytes=2-5,4-3", 10, vec![]),
T("bytes=--5,4--3", 10, vec![]),
T("bytes=A-", 10, vec![]),
T("bytes=A- ", 10, vec![]),
T("bytes=A-Z", 10, vec![]),
T("bytes= -Z", 10, vec![]),
T("bytes=5-Z", 10, vec![]),
T("bytes=Ran-dom, garbage", 10, vec![]),
T("bytes=0x01-0x02", 10, vec![]),
T("bytes= ", 10, vec![]),
T("bytes= , , , ", 10, vec![]),
T(
"bytes=0-9",
10,
vec![HttpRange {
start: 0,
length: 10,
}],
),
T(
"bytes=0-",
10,
vec![HttpRange {
start: 0,
length: 10,
}],
),
T(
"bytes=5-",
10,
vec![HttpRange {
start: 5,
length: 5,
}],
),
T(
"bytes=0-20",
10,
vec![HttpRange {
start: 0,
length: 10,
}],
),
T(
"bytes=15-,0-5",
10,
vec![HttpRange {
start: 0,
length: 6,
}],
),
T(
"bytes=1-2,5-",
10,
vec![
HttpRange {
start: 1,
length: 2,
},
HttpRange {
start: 5,
length: 5,
},
],
),
T(
"bytes=-2 , 7-",
11,
vec![
HttpRange {
start: 9,
length: 2,
},
HttpRange {
start: 7,
length: 4,
},
],
),
T(
"bytes=0-0 ,2-2, 7-",
11,
vec![
HttpRange {
start: 0,
length: 1,
},
HttpRange {
start: 2,
length: 1,
},
HttpRange {
start: 7,
length: 4,
},
],
),
T(
"bytes=-5",
10,
vec![HttpRange {
start: 5,
length: 5,
}],
),
T(
"bytes=-15",
10,
vec![HttpRange {
start: 0,
length: 10,
}],
),
T(
"bytes=0-499",
10000,
vec![HttpRange {
start: 0,
length: 500,
}],
),
T(
"bytes=500-999",
10000,
vec![HttpRange {
start: 500,
length: 500,
}],
),
T(
"bytes=-500",
10000,
vec![HttpRange {
start: 9500,
length: 500,
}],
),
T(
"bytes=9500-",
10000,
vec![HttpRange {
start: 9500,
length: 500,
}],
),
T(
"bytes=0-0,-1",
10000,
vec![
HttpRange {
start: 0,
length: 1,
},
HttpRange {
start: 9999,
length: 1,
},
],
),
T(
"bytes=500-600,601-999",
10000,
vec![
HttpRange {
start: 500,
length: 101,
},
HttpRange {
start: 601,
length: 399,
},
],
),
T(
"bytes=500-700,601-999",
10000,
vec![
HttpRange {
start: 500,
length: 201,
},
HttpRange {
start: 601,
length: 399,
},
],
),
// Match Apache laxity:
T(
"bytes= 1 -2 , 4- 5, 7 - 8 , ,,",
11,
vec![
HttpRange {
start: 1,
length: 2,
},
HttpRange {
start: 4,
length: 2,
},
HttpRange {
start: 7,
length: 2,
},
],
),
];
for t in tests {
let header = t.0;
let size = t.1;
let expected = t.2;
let res = HttpRange::parse(header, size);
if res.is_err() {
if expected.is_empty() {
continue;
} else {
panic!(
"parse({}, {}) returned error {:?}",
header,
size,
res.unwrap_err()
);
}
}
let got = res.unwrap();
if got.len() != expected.len() {
panic!(
"len(parseRange({}, {})) = {}, want {}",
header,
size,
got.len(),
expected.len()
);
}
for i in 0..expected.len() {
if got[i].start != expected[i].start {
panic!(
"parseRange({}, {})[{}].start = {}, want {}",
header, size, i, got[i].start, expected[i].start
)
}
if got[i].length != expected[i].length {
panic!(
"parseRange({}, {})[{}].length = {}, want {}",
header, size, i, got[i].length, expected[i].length
)
}
}
}
}
}

195
actix-files/src/service.rs Normal file
View File

@ -0,0 +1,195 @@
use std::{fmt, io, ops::Deref, path::PathBuf, rc::Rc};
use actix_web::{
body::BoxBody,
dev::{self, Service, ServiceRequest, ServiceResponse},
error::Error,
guard::Guard,
http::{header, Method},
HttpResponse,
};
use futures_core::future::LocalBoxFuture;
use crate::{
named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride, NamedFile,
PathBufWrap, PathFilter,
};
/// Assembled file serving service.
#[derive(Clone)]
pub struct FilesService(pub(crate) Rc<FilesServiceInner>);
impl Deref for FilesService {
type Target = FilesServiceInner;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
pub struct FilesServiceInner {
pub(crate) directory: PathBuf,
pub(crate) index: Option<String>,
pub(crate) show_index: bool,
pub(crate) redirect_to_slash: bool,
pub(crate) default: Option<HttpService>,
pub(crate) renderer: Rc<DirectoryRenderer>,
pub(crate) mime_override: Option<Rc<MimeOverride>>,
pub(crate) path_filter: Option<Rc<PathFilter>>,
pub(crate) file_flags: named::Flags,
pub(crate) guards: Option<Rc<dyn Guard>>,
pub(crate) hidden_files: bool,
}
impl fmt::Debug for FilesServiceInner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("FilesServiceInner")
}
}
impl FilesService {
async fn handle_err(
&self,
err: io::Error,
req: ServiceRequest,
) -> Result<ServiceResponse, Error> {
log::debug!("error handling {}: {}", req.path(), err);
if let Some(ref default) = self.default {
default.call(req).await
} else {
Ok(req.error_response(err))
}
}
fn serve_named_file(
&self,
req: ServiceRequest,
mut named_file: NamedFile,
) -> ServiceResponse {
if let Some(ref mime_override) = self.mime_override {
let new_disposition = mime_override(&named_file.content_type.type_());
named_file.content_disposition.disposition = new_disposition;
}
named_file.flags = self.file_flags;
let (req, _) = req.into_parts();
let res = named_file.into_response(&req);
ServiceResponse::new(req, res)
}
fn show_index(&self, req: ServiceRequest, path: PathBuf) -> ServiceResponse {
let dir = Directory::new(self.directory.clone(), path);
let (req, _) = req.into_parts();
(self.renderer)(&dir, &req).unwrap_or_else(|e| ServiceResponse::from_err(e, req))
}
}
impl fmt::Debug for FilesService {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("FilesService")
}
}
impl Service<ServiceRequest> for FilesService {
type Response = ServiceResponse<BoxBody>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
dev::always_ready!();
fn call(&self, req: ServiceRequest) -> Self::Future {
let is_method_valid = if let Some(guard) = &self.guards {
// execute user defined guards
(**guard).check(&req.guard_ctx())
} else {
// default behavior
matches!(*req.method(), Method::HEAD | Method::GET)
};
let this = self.clone();
Box::pin(async move {
if !is_method_valid {
return Ok(req.into_response(
HttpResponse::MethodNotAllowed()
.insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8))
.body("Request did not meet this resource's requirements."),
));
}
let path_on_disk = match PathBufWrap::parse_path(
req.match_info().unprocessed(),
this.hidden_files,
) {
Ok(item) => item,
Err(err) => return Ok(req.error_response(err)),
};
if let Some(filter) = &this.path_filter {
if !filter(path_on_disk.as_ref(), req.head()) {
if let Some(ref default) = this.default {
return default.call(req).await;
} else {
return Ok(req.into_response(HttpResponse::NotFound().finish()));
}
}
}
// full file path
let path = this.directory.join(&path_on_disk);
if let Err(err) = path.canonicalize() {
return this.handle_err(err, req).await;
}
if path.is_dir() {
if this.redirect_to_slash
&& !req.path().ends_with('/')
&& (this.index.is_some() || this.show_index)
{
let redirect_to = format!("{}/", req.path());
return Ok(req.into_response(
HttpResponse::Found()
.insert_header((header::LOCATION, redirect_to))
.finish(),
));
}
match this.index {
Some(ref index) => {
let named_path = path.join(index);
match NamedFile::open_async(named_path).await {
Ok(named_file) => Ok(this.serve_named_file(req, named_file)),
Err(_) if this.show_index => Ok(this.show_index(req, path)),
Err(err) => this.handle_err(err, req).await,
}
}
None if this.show_index => Ok(this.show_index(req, path)),
None => Ok(ServiceResponse::from_err(
FilesError::IsDirectory,
req.into_parts().0,
)),
}
} else {
match NamedFile::open_async(&path).await {
Ok(mut named_file) => {
if let Some(ref mime_override) = this.mime_override {
let new_disposition =
mime_override(&named_file.content_type.type_());
named_file.content_disposition.disposition = new_disposition;
}
named_file.flags = this.file_flags;
let (req, _) = req.into_parts();
let res = named_file.into_response(&req);
Ok(ServiceResponse::new(req, res))
}
Err(err) => this.handle_err(err, req).await,
}
}
})
}
}

View File

@ -0,0 +1,38 @@
use actix_files::Files;
use actix_web::{
http::{
header::{self, HeaderValue},
StatusCode,
},
test::{self, TestRequest},
App,
};
#[actix_web::test]
async fn test_utf8_file_contents() {
// use default ISO-8859-1 encoding
let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await;
let req = TestRequest::with_uri("/utf8.txt").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers().get(header::CONTENT_TYPE),
Some(&HeaderValue::from_static("text/plain; charset=utf-8")),
);
// disable UTF-8 attribute
let srv =
test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(false)))
.await;
let req = TestRequest::with_uri("/utf8.txt").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers().get(header::CONTENT_TYPE),
Some(&HeaderValue::from_static("text/plain")),
);
}

View File

@ -0,0 +1 @@
first

View File

@ -0,0 +1 @@
second

View File

@ -0,0 +1,36 @@
use actix_files::Files;
use actix_web::{
guard::Host,
http::StatusCode,
test::{self, TestRequest},
App,
};
use bytes::Bytes;
#[actix_web::test]
async fn test_guard_filter() {
let srv = test::init_service(
App::new()
.service(Files::new("/", "./tests/fixtures/guards/first").guard(Host("first.com")))
.service(
Files::new("/", "./tests/fixtures/guards/second").guard(Host("second.com")),
),
)
.await;
let req = TestRequest::with_uri("/index.txt")
.append_header(("Host", "first.com"))
.to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(test::read_body(res).await, Bytes::from("first"));
let req = TestRequest::with_uri("/index.txt")
.append_header(("Host", "second.com"))
.to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(test::read_body(res).await, Bytes::from("second"));
}

View File

@ -0,0 +1 @@
test.png

View File

@ -0,0 +1 @@
<EFBFBD>TǑɂV<EFBFBD>2<EFBFBD>vI<EFBFBD><EFBFBD><EFBFBD>\<5C><52><CB99><EFBFBD>e<EFBFBD><04>vD<76>:藽<>RV<03>Yp<59><70>;<3B><>G<><47>p!2<7F>C<EFBFBD>.<2E> <0C><><EFBFBD><EFBFBD>pA !<21>ߦ<EFBFBD>x j+Uc<55><63><EFBFBD>X<13>c%<17>;<3B>"y<10><>AI

View File

@ -0,0 +1 @@
// this file is empty.

View File

Before

Width:  |  Height:  |  Size: 168 B

After

Width:  |  Height:  |  Size: 168 B

View File

@ -0,0 +1,27 @@
use actix_files::Files;
use actix_web::{
http::StatusCode,
test::{self, TestRequest},
App,
};
#[actix_rt::test]
async fn test_directory_traversal_prevention() {
let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await;
let req =
TestRequest::with_uri("/../../../../../../../../../../../etc/passwd").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri(
"/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd",
)
.to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/%00/etc/passwd%00").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}

View File

@ -0,0 +1,3 @@
中文内容显示正确。
English is OK.

146
actix-http-test/CHANGES.md Normal file
View File

@ -0,0 +1,146 @@
# Changes
## Unreleased - 2021-xx-xx
## 3.0.0-beta.13 - 2022-02-16
- No significant changes since `3.0.0-beta.12`.
## 3.0.0-beta.12 - 2022-01-31
- No significant changes since `3.0.0-beta.11`.
## 3.0.0-beta.11 - 2022-01-04
- Minimum supported Rust version (MSRV) is now 1.54.
## 3.0.0-beta.10 - 2021-12-27
- Update `actix-server` to `2.0.0-rc.2`. [#2550]
[#2550]: https://github.com/actix/actix-web/pull/2550
## 3.0.0-beta.9 - 2021-12-11
- No significant changes since `3.0.0-beta.8`.
## 3.0.0-beta.8 - 2021-11-30
- Update `actix-tls` to `3.0.0-rc.1`. [#2474]
[#2474]: https://github.com/actix/actix-web/pull/2474
## 3.0.0-beta.7 - 2021-11-22
- Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]
[#2408]: https://github.com/actix/actix-web/pull/2408
## 3.0.0-beta.6 - 2021-11-15
- `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442]
- Update `actix-server` to `2.0.0-beta.9`. [#2442]
- Minimum supported Rust version (MSRV) is now 1.52.
[#2442]: https://github.com/actix/actix-web/pull/2442
## 3.0.0-beta.5 - 2021-09-09
- Minimum supported Rust version (MSRV) is now 1.51.
## 3.0.0-beta.4 - 2021-04-02
- Added `TestServer::client_headers` method. [#2097]
[#2097]: https://github.com/actix/actix-web/pull/2097
## 3.0.0-beta.3 - 2021-03-09
- No notable changes.
## 3.0.0-beta.2 - 2021-02-10
- No notable changes.
## 3.0.0-beta.1 - 2021-01-07
- Update `bytes` to `1.0`. [#1813]
[#1813]: https://github.com/actix/actix-web/pull/1813
## 2.1.0 - 2020-11-25
- Add ability to set address for `TestServer`. [#1645]
- Upgrade `base64` to `0.13`.
- Upgrade `serde_urlencoded` to `0.7`. [#1773]
[#1773]: https://github.com/actix/actix-web/pull/1773
[#1645]: https://github.com/actix/actix-web/pull/1645
## 2.0.0 - 2020-09-11
- Update actix-codec and actix-utils dependencies.
## 2.0.0-alpha.1 - 2020-05-23
- Update the `time` dependency to 0.2.7
- Update `actix-connect` dependency to 2.0.0-alpha.2
- Make `test_server` `async` fn.
- Bump minimum supported Rust version to 1.40
- Replace deprecated `net2` crate with `socket2`
- Update `base64` dependency to 0.12
- Update `env_logger` dependency to 0.7
## 1.0.0 - 2019-12-13
- Replaced `TestServer::start()` with `test_server()`
## 1.0.0-alpha.3 - 2019-12-07
- Migrate to `std::future`
## 0.2.5 - 2019-09-17
- Update serde_urlencoded to "0.6.1"
- Increase TestServerRuntime timeouts from 500ms to 3000ms
- Do not override current `System`
## 0.2.4 - 2019-07-18
- Update actix-server to 0.6
## 0.2.3 - 2019-07-16
- Add `delete`, `options`, `patch` methods to `TestServerRunner`
## 0.2.2 - 2019-06-16
- Add .put() and .sput() methods
## 0.2.1 - 2019-06-05
- Add license files
## 0.2.0 - 2019-05-12
- Update awc and actix-http deps
## 0.1.1 - 2019-04-24
- Always make new connection for http client
## 0.1.0 - 2019-04-16
- No changes
## 0.1.0-alpha.3 - 2019-04-02
- Request functions accept path #743
## 0.1.0-alpha.2 - 2019-03-29
- Added TestServerRuntime::load_body() method
- Update actix-http and awc libraries
## 0.1.0-alpha.1 - 2019-03-28
- Initial impl

View File

@ -0,0 +1,55 @@
[package]
name = "actix-http-test"
version = "3.0.0-beta.13"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Various helpers for Actix applications to use during testing"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git"
categories = [
"network-programming",
"asynchronous",
"web-programming::http-server",
"web-programming::websocket",
]
license = "MIT OR Apache-2.0"
edition = "2018"
[package.metadata.docs.rs]
features = []
[lib]
name = "actix_http_test"
path = "src/lib.rs"
[features]
default = []
# openssl
openssl = ["tls-openssl", "awc/openssl"]
[dependencies]
actix-service = "2.0.0"
actix-codec = "0.5"
actix-tls = "3"
actix-utils = "3.0.0"
actix-rt = "2.2"
actix-server = "2"
awc = { version = "3.0.0-beta.21", default-features = false }
base64 = "0.13"
bytes = "1"
futures-core = { version = "0.3.7", default-features = false }
http = "0.2.5"
log = "0.4"
socket2 = "0.4"
serde = "1.0"
serde_json = "1.0"
slab = "0.4"
serde_urlencoded = "0.7"
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
tokio = { version = "1.8.4", features = ["sync"] }
[dev-dependencies]
actix-web = { version = "4.0.0", default-features = false, features = ["cookies"] }
actix-http = "3.0.0"

View File

@ -0,0 +1 @@
../LICENSE-APACHE

1
actix-http-test/LICENSE-MIT Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-MIT

17
actix-http-test/README.md Normal file
View File

@ -0,0 +1,17 @@
# actix-http-test
> Various helpers for Actix applications to use during testing.
[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.13)](https://docs.rs/actix-http-test/3.0.0-beta.13)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
<br>
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.13)
[![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http-test)
- Minimum Supported Rust Version (MSRV): 1.54

312
actix-http-test/src/lib.rs Normal file
View File

@ -0,0 +1,312 @@
//! Various helpers for Actix applications to use during testing.
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#[cfg(feature = "openssl")]
extern crate tls_openssl as openssl;
use std::{net, thread, time::Duration};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_rt::{net::TcpStream, System};
use actix_server::{Server, ServerServiceFactory};
use awc::{
error::PayloadError, http::header::HeaderMap, ws, Client, ClientRequest, ClientResponse,
Connector,
};
use bytes::Bytes;
use futures_core::stream::Stream;
use http::Method;
use socket2::{Domain, Protocol, Socket, Type};
use tokio::sync::mpsc;
/// Start test server.
///
/// `TestServer` is very simple test server that simplify process of writing integration tests cases
/// for HTTP applications.
///
/// # Examples
/// ```no_run
/// use actix_http::HttpService;
/// use actix_http_test::test_server;
/// use actix_web::{web, App, HttpResponse, Error};
///
/// async fn my_handler() -> Result<HttpResponse, Error> {
/// Ok(HttpResponse::Ok().into())
/// }
///
/// #[actix_web::test]
/// async fn test_example() {
/// let mut srv = TestServer::start(||
/// HttpService::new(
/// App::new().service(web::resource("/").to(my_handler))
/// )
/// );
///
/// let req = srv.get("/");
/// let response = req.send().await.unwrap();
/// assert!(response.status().is_success());
/// }
/// ```
pub async fn test_server<F: ServerServiceFactory<TcpStream>>(factory: F) -> TestServer {
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
test_server_with_addr(tcp, factory).await
}
/// Start [`test server`](test_server()) on an existing address binding.
pub async fn test_server_with_addr<F: ServerServiceFactory<TcpStream>>(
tcp: net::TcpListener,
factory: F,
) -> TestServer {
let (started_tx, started_rx) = std::sync::mpsc::channel();
let (thread_stop_tx, thread_stop_rx) = mpsc::channel(1);
// run server in separate thread
thread::spawn(move || {
System::new().block_on(async move {
let local_addr = tcp.local_addr().unwrap();
let srv = Server::build()
.workers(1)
.disable_signals()
.system_exit()
.listen("test", tcp, factory)
.expect("test server could not be created");
let srv = srv.run();
started_tx
.send((System::current(), srv.handle(), local_addr))
.unwrap();
// drive server loop
srv.await.unwrap();
});
// notify TestServer that server and system have shut down
// all thread managed resources should be dropped at this point
let _ = thread_stop_tx.send(());
});
let (system, server, addr) = started_rx.recv().unwrap();
let client = {
#[cfg(feature = "openssl")]
let connector = {
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_verify(SslVerifyMode::NONE);
let _ = builder
.set_alpn_protos(b"\x02h2\x08http/1.1")
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
Connector::new()
.conn_lifetime(Duration::from_secs(0))
.timeout(Duration::from_millis(30000))
.openssl(builder.build())
};
#[cfg(not(feature = "openssl"))]
let connector = {
Connector::new()
.conn_lifetime(Duration::from_secs(0))
.timeout(Duration::from_millis(30000))
};
Client::builder().connector(connector).finish()
};
TestServer {
server,
client,
system,
addr,
thread_stop_rx,
}
}
/// Test server controller
pub struct TestServer {
server: actix_server::ServerHandle,
client: awc::Client,
system: actix_rt::System,
addr: net::SocketAddr,
thread_stop_rx: mpsc::Receiver<()>,
}
impl TestServer {
/// Construct test server url
pub fn addr(&self) -> net::SocketAddr {
self.addr
}
/// Construct test server url
pub fn url(&self, uri: &str) -> String {
if uri.starts_with('/') {
format!("http://localhost:{}{}", self.addr.port(), uri)
} else {
format!("http://localhost:{}/{}", self.addr.port(), uri)
}
}
/// Construct test HTTPS server URL.
pub fn surl(&self, uri: &str) -> String {
if uri.starts_with('/') {
format!("https://localhost:{}{}", self.addr.port(), uri)
} else {
format!("https://localhost:{}/{}", self.addr.port(), uri)
}
}
/// Create `GET` request
pub fn get<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.get(self.url(path.as_ref()).as_str())
}
/// Create HTTPS `GET` request
pub fn sget<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.get(self.surl(path.as_ref()).as_str())
}
/// Create `POST` request
pub fn post<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.post(self.url(path.as_ref()).as_str())
}
/// Create HTTPS `POST` request
pub fn spost<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.post(self.surl(path.as_ref()).as_str())
}
/// Create `HEAD` request
pub fn head<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.head(self.url(path.as_ref()).as_str())
}
/// Create HTTPS `HEAD` request
pub fn shead<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.head(self.surl(path.as_ref()).as_str())
}
/// Create `PUT` request
pub fn put<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.put(self.url(path.as_ref()).as_str())
}
/// Create HTTPS `PUT` request
pub fn sput<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.put(self.surl(path.as_ref()).as_str())
}
/// Create `PATCH` request
pub fn patch<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.patch(self.url(path.as_ref()).as_str())
}
/// Create HTTPS `PATCH` request
pub fn spatch<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.patch(self.surl(path.as_ref()).as_str())
}
/// Create `DELETE` request
pub fn delete<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.delete(self.url(path.as_ref()).as_str())
}
/// Create HTTPS `DELETE` request
pub fn sdelete<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.delete(self.surl(path.as_ref()).as_str())
}
/// Create `OPTIONS` request
pub fn options<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.options(self.url(path.as_ref()).as_str())
}
/// Create HTTPS `OPTIONS` request
pub fn soptions<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.options(self.surl(path.as_ref()).as_str())
}
/// Connect to test HTTP server
pub fn request<S: AsRef<str>>(&self, method: Method, path: S) -> ClientRequest {
self.client.request(method, path.as_ref())
}
pub async fn load_body<S>(
&mut self,
mut response: ClientResponse<S>,
) -> Result<Bytes, PayloadError>
where
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin + 'static,
{
response.body().limit(10_485_760).await
}
/// Connect to WebSocket server at a given path.
pub async fn ws_at(
&mut self,
path: &str,
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError> {
let url = self.url(path);
let connect = self.client.ws(url).connect();
connect.await.map(|(_, framed)| framed)
}
/// Connect to a WebSocket server.
pub async fn ws(
&mut self,
) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError> {
self.ws_at("/").await
}
/// Get default HeaderMap of Client.
///
/// Returns Some(&mut HeaderMap) when Client object is unique
/// (No other clone of client exists at the same time).
pub fn client_headers(&mut self) -> Option<&mut HeaderMap> {
self.client.headers()
}
/// Stop HTTP server.
///
/// Waits for spawned `Server` and `System` to (force) shutdown.
pub async fn stop(&mut self) {
// signal server to stop
self.server.stop(false).await;
// also signal system to stop
// though this is handled by `ServerBuilder::exit_system` too
self.system.stop();
// wait for thread to be stopped but don't care about result
let _ = self.thread_stop_rx.recv().await;
}
}
impl Drop for TestServer {
fn drop(&mut self) {
// calls in this Drop impl should be enough to shut down the server, system, and thread
// without needing to await anything
// signal server to stop
let _ = self.server.stop(true);
// signal system to stop
self.system.stop();
}
}
/// Get a localhost socket address with random, unused port.
pub fn unused_addr() -> net::SocketAddr {
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap();
socket.bind(&addr.into()).unwrap();
socket.set_reuse_address(true).unwrap();
let tcp = net::TcpListener::from(socket);
tcp.local_addr().unwrap()
}

1121
actix-http/CHANGES.md Normal file

File diff suppressed because it is too large Load Diff

139
actix-http/Cargo.toml Normal file
View File

@ -0,0 +1,139 @@
[package]
name = "actix-http"
version = "3.0.0"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
]
description = "HTTP primitives for the Actix ecosystem"
keywords = ["actix", "http", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git"
categories = [
"network-programming",
"asynchronous",
"web-programming::http-server",
"web-programming::websocket",
]
license = "MIT OR Apache-2.0"
edition = "2018"
[package.metadata.docs.rs]
# features that docs.rs will build with
features = ["http2", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"]
[lib]
name = "actix_http"
path = "src/lib.rs"
[features]
default = []
# HTTP/2 protocol support
http2 = ["h2"]
# WebSocket protocol implementation
ws = [
"local-channel",
"base64",
"rand",
"sha-1",
]
# TLS via OpenSSL
openssl = ["actix-tls/accept", "actix-tls/openssl"]
# TLS via Rustls
rustls = ["actix-tls/accept", "actix-tls/rustls"]
# Compression codecs
compress-brotli = ["__compress", "brotli"]
compress-gzip = ["__compress", "flate2"]
compress-zstd = ["__compress", "zstd"]
# Internal (PRIVATE!) features used to aid testing and cheking feature status.
# Don't rely on these whatsoever. They are semver-exempt and may disappear at anytime.
__compress = []
[dependencies]
actix-service = "2"
actix-codec = "0.5"
actix-utils = "3"
actix-rt = { version = "2.2", default-features = false }
ahash = "0.7"
bitflags = "1.2"
bytes = "1"
bytestring = "1"
derive_more = "0.99.5"
encoding_rs = "0.8"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
http = "0.2.5"
httparse = "1.5.1"
httpdate = "1.0.1"
itoa = "1"
language-tags = "0.3"
log = "0.4"
mime = "0.3"
percent-encoding = "2.1"
pin-project-lite = "0.2"
smallvec = "1.6.1"
# http2
h2 = { version = "0.3.9", optional = true }
# websockets
local-channel = { version = "0.1", optional = true }
base64 = { version = "0.13", optional = true }
rand = { version = "0.8", optional = true }
sha-1 = { version = "0.10", optional = true }
# openssl/rustls
actix-tls = { version = "3", default-features = false, optional = true }
# compress-*
brotli = { version = "3.3.3", optional = true }
flate2 = { version = "1.0.13", optional = true }
zstd = { version = "0.10", optional = true }
[dev-dependencies]
actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] }
actix-server = "2"
actix-tls = { version = "3", features = ["openssl"] }
actix-web = "4.0.0"
async-stream = "0.3"
criterion = { version = "0.3", features = ["html_reports"] }
env_logger = "0.9"
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
memchr = "2.4"
once_cell = "1.9"
rcgen = "0.8"
regex = "1.3"
rustls-pemfile = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.9" }
tls-rustls = { package = "rustls", version = "0.20.0" }
tokio = { version = "1.8.4", features = ["net", "rt", "macros"] }
[[example]]
name = "ws"
required-features = ["rustls"]
[[bench]]
name = "write-camel-case"
harness = false
[[bench]]
name = "status-line"
harness = false
[[bench]]
name = "uninit-headers"
harness = false
[[bench]]
name = "quality-value"
harness = false

1
actix-http/LICENSE-APACHE Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-APACHE

1
actix-http/LICENSE-MIT Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-MIT

66
actix-http/README.md Normal file
View File

@ -0,0 +1,66 @@
# actix-http
> HTTP primitives for the Actix ecosystem.
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0)](https://docs.rs/actix-http/3.0.0)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-http/3.0.0/status.svg)](https://deps.rs/crate/actix-http/3.0.0)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http)
- Minimum Supported Rust Version (MSRV): 1.54
## Example
```rust
use std::{env, io};
use actix_http::{HttpService, Response};
use actix_server::Server;
use futures_util::future;
use http::header::HeaderValue;
use log::info;
#[actix_rt::main]
async fn main() -> io::Result<()> {
env::set_var("RUST_LOG", "hello_world=info");
env_logger::init();
Server::build()
.bind("hello-world", "127.0.0.1:8080", || {
HttpService::build()
.client_timeout(1000)
.client_disconnect(1000)
.finish(|_req| {
info!("{:?}", _req);
let mut res = Response::Ok();
res.header("x-head", HeaderValue::from_static("dummy value!"));
future::ok::<_, ()>(res.body("Hello world!"))
})
.tcp()
})?
.run()
.await
}
```
## License
This project is licensed under either of
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
at your option.
## Code of Conduct
Contribution to the actix-http crate is organized under the terms of the
Contributor Covenant, the maintainer of actix-http, @fafhrd91, promises to
intervene to uphold that code of conduct.

View File

@ -0,0 +1,95 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
const CODES: &[u16] = &[0, 1000, 201, 800, 550];
fn bench_quality_display_impls(c: &mut Criterion) {
let mut group = c.benchmark_group("quality value display impls");
for i in CODES.iter() {
group.bench_with_input(BenchmarkId::new("New (fast?)", i), i, |b, &i| {
b.iter(|| _new::Quality(i).to_string())
});
group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| {
b.iter(|| _naive::Quality(i).to_string())
});
}
group.finish();
}
criterion_group!(benches, bench_quality_display_impls);
criterion_main!(benches);
mod _new {
use std::fmt;
pub struct Quality(pub(crate) u16);
impl fmt::Display for Quality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
0 => f.write_str("0"),
1000 => f.write_str("1"),
// some number in the range 1999
x => {
f.write_str("0.")?;
// this implementation avoids string allocation otherwise required
// for `.trim_end_matches('0')`
if x < 10 {
f.write_str("00")?;
// 0 is handled so it's not possible to have a trailing 0, we can just return
itoa_fmt(f, x)
} else if x < 100 {
f.write_str("0")?;
if x % 10 == 0 {
// trailing 0, divide by 10 and write
itoa_fmt(f, x / 10)
} else {
itoa_fmt(f, x)
}
} else {
// x is in range 101999
if x % 100 == 0 {
// two trailing 0s, divide by 100 and write
itoa_fmt(f, x / 100)
} else if x % 10 == 0 {
// one trailing 0, divide by 10 and write
itoa_fmt(f, x / 10)
} else {
itoa_fmt(f, x)
}
}
}
}
}
}
pub fn itoa_fmt<W: fmt::Write, V: itoa::Integer>(mut wr: W, value: V) -> fmt::Result {
let mut buf = itoa::Buffer::new();
wr.write_str(buf.format(value))
}
}
mod _naive {
use std::fmt;
pub struct Quality(pub(crate) u16);
impl fmt::Display for Quality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
0 => f.write_str("0"),
1000 => f.write_str("1"),
x => {
write!(f, "{}", format!("{:03}", x).trim_end_matches('0'))
}
}
}
}
}

View File

@ -0,0 +1,214 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use bytes::BytesMut;
use http::Version;
const CODES: &[u16] = &[201, 303, 404, 515];
fn bench_write_status_line_11(c: &mut Criterion) {
let mut group = c.benchmark_group("write_status_line v1.1");
let version = Version::HTTP_11;
for i in CODES.iter() {
group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_original::write_status_line(version, i, &mut b);
})
});
group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_new::write_status_line(version, i, &mut b);
})
});
group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_naive::write_status_line(version, i, &mut b);
})
});
}
group.finish();
}
fn bench_write_status_line_10(c: &mut Criterion) {
let mut group = c.benchmark_group("write_status_line v1.0");
let version = Version::HTTP_10;
for i in CODES.iter() {
group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_original::write_status_line(version, i, &mut b);
})
});
group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_new::write_status_line(version, i, &mut b);
})
});
group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_naive::write_status_line(version, i, &mut b);
})
});
}
group.finish();
}
fn bench_write_status_line_09(c: &mut Criterion) {
let mut group = c.benchmark_group("write_status_line v0.9");
let version = Version::HTTP_09;
for i in CODES.iter() {
group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_original::write_status_line(version, i, &mut b);
})
});
group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_new::write_status_line(version, i, &mut b);
})
});
group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_naive::write_status_line(version, i, &mut b);
})
});
}
group.finish();
}
criterion_group!(
benches,
bench_write_status_line_11,
bench_write_status_line_10,
bench_write_status_line_09
);
criterion_main!(benches);
mod _naive {
use bytes::{BufMut, BytesMut};
use http::Version;
pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) {
match version {
Version::HTTP_11 => bytes.put_slice(b"HTTP/1.1 "),
Version::HTTP_10 => bytes.put_slice(b"HTTP/1.0 "),
Version::HTTP_09 => bytes.put_slice(b"HTTP/0.9 "),
_ => {
// other HTTP version handlers do not use this method
}
}
bytes.put_slice(n.to_string().as_bytes());
}
}
mod _new {
use bytes::{BufMut, BytesMut};
use http::Version;
const DIGITS_START: u8 = b'0';
pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) {
match version {
Version::HTTP_11 => bytes.put_slice(b"HTTP/1.1 "),
Version::HTTP_10 => bytes.put_slice(b"HTTP/1.0 "),
Version::HTTP_09 => bytes.put_slice(b"HTTP/0.9 "),
_ => {
// other HTTP version handlers do not use this method
}
}
let d100 = (n / 100) as u8;
let d10 = ((n / 10) % 10) as u8;
let d1 = (n % 10) as u8;
bytes.put_u8(DIGITS_START + d100);
bytes.put_u8(DIGITS_START + d10);
bytes.put_u8(DIGITS_START + d1);
bytes.put_u8(b' ');
}
}
mod _original {
use std::ptr;
use bytes::{BufMut, BytesMut};
use http::Version;
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; STATUS_LINE_BUF_SIZE] = *b"HTTP/1.1 ";
match version {
Version::HTTP_2 => buf[5] = b'2',
Version::HTTP_10 => buf[7] = b'0',
Version::HTTP_09 => {
buf[5] = b'0';
buf[7] = b'9';
}
_ => {}
}
let mut curr: isize = 12;
let buf_ptr = buf.as_mut_ptr();
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 {
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;
unsafe {
*buf_ptr.offset(curr) = (n as u8) + b'0';
}
} else {
let d1 = n << 1;
curr -= 2;
unsafe {
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2);
}
}
bytes.put_slice(&buf);
if four {
bytes.put_u8(b' ');
}
}
}

View File

@ -0,0 +1,134 @@
use criterion::{criterion_group, criterion_main, Criterion};
use bytes::BytesMut;
// A Miri run detects UB, seen on this playground:
// https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f5d9aa166aa48df8dca05fce2b6c3915
fn bench_header_parsing(c: &mut Criterion) {
c.bench_function("Original (Unsound) [short]", |b| {
b.iter(|| {
let mut buf = BytesMut::from(REQ_SHORT);
_original::parse_headers(&mut buf);
})
});
c.bench_function("New (safe) [short]", |b| {
b.iter(|| {
let mut buf = BytesMut::from(REQ_SHORT);
_new::parse_headers(&mut buf);
})
});
c.bench_function("Original (Unsound) [realistic]", |b| {
b.iter(|| {
let mut buf = BytesMut::from(REQ);
_original::parse_headers(&mut buf);
})
});
c.bench_function("New (safe) [realistic]", |b| {
b.iter(|| {
let mut buf = BytesMut::from(REQ);
_new::parse_headers(&mut buf);
})
});
}
criterion_group!(benches, bench_header_parsing);
criterion_main!(benches);
const MAX_HEADERS: usize = 96;
const EMPTY_HEADER_ARRAY: [httparse::Header<'static>; MAX_HEADERS] =
[httparse::EMPTY_HEADER; MAX_HEADERS];
#[derive(Clone, Copy)]
struct HeaderIndex {
name: (usize, usize),
value: (usize, usize),
}
const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex {
name: (0, 0),
value: (0, 0),
};
const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] = [EMPTY_HEADER_INDEX; MAX_HEADERS];
impl HeaderIndex {
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);
}
}
}
// test cases taken from:
// https://github.com/seanmonstar/httparse/blob/master/benches/parse.rs
const REQ_SHORT: &[u8] = b"\
GET / HTTP/1.0\r\n\
Host: example.com\r\n\
Cookie: session=60; user_id=1\r\n\r\n";
const REQ: &[u8] = b"\
GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n\
Host: www.kittyhell.com\r\n\
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 Pathtraq/0.9\r\n\
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\
Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n\
Accept-Encoding: gzip,deflate\r\n\
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n\
Keep-Alive: 115\r\n\
Connection: keep-alive\r\n\
Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; __utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; __utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral|padding=under256\r\n\r\n";
mod _new {
use super::*;
pub fn parse_headers(src: &mut BytesMut) -> usize {
let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = EMPTY_HEADER_ARRAY;
let mut req = httparse::Request::new(&mut parsed);
match req.parse(src).unwrap() {
httparse::Status::Complete(_len) => {
HeaderIndex::record(src, req.headers, &mut headers);
req.headers.len()
}
_ => unreachable!(),
}
}
}
mod _original {
use super::*;
use std::mem::MaybeUninit;
pub fn parse_headers(src: &mut BytesMut) -> usize {
#![allow(clippy::uninit_assumed_init)]
let mut headers: [HeaderIndex; MAX_HEADERS] =
unsafe { MaybeUninit::uninit().assume_init() };
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] =
unsafe { MaybeUninit::uninit().assume_init() };
let mut req = httparse::Request::new(&mut parsed);
match req.parse(src).unwrap() {
httparse::Status::Complete(_len) => {
HeaderIndex::record(src, req.headers, &mut headers);
req.headers.len()
}
_ => unreachable!(),
}
}
}

View File

@ -0,0 +1,93 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
fn bench_write_camel_case(c: &mut Criterion) {
let mut group = c.benchmark_group("write_camel_case");
let names = ["connection", "Transfer-Encoding", "transfer-encoding"];
for &i in &names {
let bts = i.as_bytes();
group.bench_with_input(BenchmarkId::new("Original", i), bts, |b, bts| {
b.iter(|| {
let mut buf = black_box([0; 24]);
_original::write_camel_case(black_box(bts), &mut buf)
});
});
group.bench_with_input(BenchmarkId::new("New", i), bts, |b, bts| {
b.iter(|| {
let mut buf = black_box([0; 24]);
let len = black_box(bts.len());
_new::write_camel_case(black_box(bts), buf.as_mut_ptr(), len)
});
});
}
group.finish();
}
criterion_group!(benches, bench_write_camel_case);
criterion_main!(benches);
mod _new {
pub fn write_camel_case(value: &[u8], buf: *mut u8, len: usize) {
// first copy entire (potentially wrong) slice to output
let buffer = unsafe {
std::ptr::copy_nonoverlapping(value.as_ptr(), buf, len);
std::slice::from_raw_parts_mut(buf, len)
};
let mut iter = value.iter();
// first character should be uppercase
if let Some(c @ b'a'..=b'z') = iter.next() {
buffer[0] = c & 0b1101_1111;
}
// track 1 ahead of the current position since that's the location being assigned to
let mut index = 2;
// remaining characters after hyphens should also be uppercase
while let Some(&c) = iter.next() {
if c == b'-' {
// advance iter by one and uppercase if needed
if let Some(c @ b'a'..=b'z') = iter.next() {
buffer[index] = c & 0b1101_1111;
}
}
index += 1;
}
}
}
mod _original {
pub fn write_camel_case(value: &[u8], buffer: &mut [u8]) {
let mut index = 0;
let key = value;
let mut key_iter = key.iter();
if let Some(c) = key_iter.next() {
if *c >= b'a' && *c <= b'z' {
buffer[index] = *c ^ b' ';
index += 1;
}
} else {
return;
}
while let Some(c) = key_iter.next() {
buffer[index] = *c;
index += 1;
if *c == b'-' {
if let Some(c) = key_iter.next() {
if *c >= b'a' && *c <= b'z' {
buffer[index] = *c ^ b' ';
index += 1;
}
}
}
}
}
}

View File

@ -0,0 +1,26 @@
use actix_http::HttpService;
use actix_server::Server;
use actix_service::map_config;
use actix_web::{dev::AppConfig, get, App};
#[get("/")]
async fn index() -> &'static str {
"Hello, world. From Actix Web!"
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> std::io::Result<()> {
Server::build()
.bind("hello-world", "127.0.0.1:8080", || {
// construct actix-web app
let app = App::new().service(index);
HttpService::build()
// pass the app to service builder
// map_config is used to map App's configuration to ServiceBuilder
.finish(map_config(app, |_| AppConfig::default()))
.tcp()
})?
.run()
.await
}

View File

@ -0,0 +1,27 @@
use std::{convert::Infallible, io, time::Duration};
use actix_http::{HttpService, Request, Response, StatusCode};
use actix_server::Server;
use once_cell::sync::Lazy;
static STR: Lazy<String> = Lazy::new(|| "HELLO WORLD ".repeat(20));
#[actix_rt::main]
async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
Server::build()
.bind("dispatcher-benchmark", ("127.0.0.1", 8080), || {
HttpService::build()
.client_request_timeout(Duration::from_secs(1))
.finish(|_: Request| async move {
let mut res = Response::build(StatusCode::OK);
Ok::<_, Infallible>(res.body(&**STR))
})
.tcp()
})?
// limiting number of workers so that bench client is not sharing as many resources
.workers(4)
.run()
.await
}

View File

@ -0,0 +1,38 @@
use std::{io, time::Duration};
use actix_http::{Error, HttpService, Request, Response, StatusCode};
use actix_server::Server;
use bytes::BytesMut;
use futures_util::StreamExt as _;
use http::header::HeaderValue;
#[actix_rt::main]
async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
Server::build()
.bind("echo", ("127.0.0.1", 8080), || {
HttpService::build()
.client_request_timeout(Duration::from_secs(1))
.client_disconnect_timeout(Duration::from_secs(1))
// handles HTTP/1.1 and HTTP/2
.finish(|mut req: Request| async move {
let mut body = BytesMut::new();
while let Some(item) = req.payload().next().await {
body.extend_from_slice(&item?);
}
log::info!("request body: {:?}", body);
let res = Response::build(StatusCode::OK)
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
.body(body);
Ok::<_, Error>(res)
})
// No TLS
.tcp()
})?
.run()
.await
}

View File

@ -0,0 +1,35 @@
use std::io;
use actix_http::{
body::{BodyStream, MessageBody},
header, Error, HttpMessage, HttpService, Request, Response, StatusCode,
};
async fn handle_request(mut req: Request) -> Result<Response<impl MessageBody>, Error> {
let mut res = Response::build(StatusCode::OK);
if let Some(ct) = req.headers().get(header::CONTENT_TYPE) {
res.insert_header((header::CONTENT_TYPE, ct));
}
// echo request payload stream as (chunked) response body
let res = res.message_body(BodyStream::new(req.payload().take()))?;
Ok(res)
}
#[actix_rt::main]
async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
actix_server::Server::build()
.bind("echo", ("127.0.0.1", 8080), || {
HttpService::build()
// handles HTTP/1.1 only
.h1(handle_request)
// No TLS
.tcp()
})?
.run()
.await
}

View File

@ -0,0 +1,25 @@
use std::{convert::Infallible, io};
use actix_http::{HttpService, Request, Response, StatusCode};
use actix_server::Server;
use once_cell::sync::Lazy;
static STR: Lazy<String> = Lazy::new(|| "HELLO WORLD ".repeat(100));
#[actix_rt::main]
async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
Server::build()
.bind("h2spec", ("127.0.0.1", 8080), || {
HttpService::build()
.h2(|_: Request| async move {
let mut res = Response::build(StatusCode::OK);
Ok::<_, Infallible>(res.body(&**STR))
})
.tcp()
})?
.workers(4)
.run()
.await
}

View File

@ -0,0 +1,38 @@
use std::{convert::Infallible, io, time::Duration};
use actix_http::{
header::HeaderValue, HttpMessage, HttpService, Request, Response, StatusCode,
};
use actix_server::Server;
#[actix_rt::main]
async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
Server::build()
.bind("hello-world", ("127.0.0.1", 8080), || {
HttpService::build()
.client_request_timeout(Duration::from_secs(1))
.client_disconnect_timeout(Duration::from_secs(1))
.on_connect_ext(|_, ext| {
ext.insert(42u32);
})
.finish(|req: Request| async move {
log::info!("{:?}", req);
let mut res = Response::build(StatusCode::OK);
res.insert_header(("x-head", HeaderValue::from_static("dummy value!")));
let forty_two = req.extensions().get::<u32>().unwrap().to_string();
res.insert_header((
"x-forty-two",
HeaderValue::from_str(&forty_two).unwrap(),
));
Ok::<_, Infallible>(res.body("Hello world!"))
})
.tcp()
})?
.run()
.await
}

View File

@ -0,0 +1,40 @@
//! Example showing response body (chunked) stream erroring.
//!
//! Test using `nc` or `curl`.
//! ```sh
//! $ curl -vN 127.0.0.1:8080
//! $ echo 'GET / HTTP/1.1\n\n' | nc 127.0.0.1 8080
//! ```
use std::{convert::Infallible, io, time::Duration};
use actix_http::{body::BodyStream, HttpService, Response};
use actix_server::Server;
use async_stream::stream;
use bytes::Bytes;
#[actix_rt::main]
async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
Server::build()
.bind("streaming-error", ("127.0.0.1", 8080), || {
HttpService::build()
.finish(|req| async move {
log::info!("{:?}", req);
let res = Response::ok();
Ok::<_, Infallible>(res.set_body(BodyStream::new(stream! {
yield Ok(Bytes::from("123"));
yield Ok(Bytes::from("456"));
actix_rt::time::sleep(Duration::from_millis(1000)).await;
yield Err(io::Error::new(io::ErrorKind::Other, ""));
})))
})
.tcp()
})?
.run()
.await
}

112
actix-http/examples/ws.rs Normal file
View File

@ -0,0 +1,112 @@
//! Sets up a WebSocket server over TCP and TLS.
//! Sends a heartbeat message every 4 seconds but does not respond to any incoming frames.
extern crate tls_rustls as rustls;
use std::{
io,
pin::Pin,
task::{Context, Poll},
time::Duration,
};
use actix_codec::Encoder;
use actix_http::{body::BodyStream, error::Error, ws, HttpService, Request, Response};
use actix_rt::time::{interval, Interval};
use actix_server::Server;
use bytes::{Bytes, BytesMut};
use bytestring::ByteString;
use futures_core::{ready, Stream};
#[actix_rt::main]
async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
Server::build()
.bind("tcp", ("127.0.0.1", 8080), || {
HttpService::build().h1(handler).tcp()
})?
.bind("tls", ("127.0.0.1", 8443), || {
HttpService::build().finish(handler).rustls(tls_config())
})?
.run()
.await
}
async fn handler(req: Request) -> Result<Response<BodyStream<Heartbeat>>, Error> {
log::info!("handshaking");
let mut res = ws::handshake(req.head())?;
// handshake will always fail under HTTP/2
log::info!("responding");
Ok(res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new())))?)
}
struct Heartbeat {
codec: ws::Codec,
interval: Interval,
}
impl Heartbeat {
fn new(codec: ws::Codec) -> Self {
Self {
codec,
interval: interval(Duration::from_secs(4)),
}
}
}
impl Stream for Heartbeat {
type Item = Result<Bytes, Error>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
log::trace!("poll");
ready!(self.as_mut().interval.poll_tick(cx));
let mut buffer = BytesMut::new();
self.as_mut()
.codec
.encode(
ws::Message::Text(ByteString::from_static("hello world")),
&mut buffer,
)
.unwrap();
Poll::Ready(Some(Ok(buffer.freeze())))
}
}
fn tls_config() -> rustls::ServerConfig {
use std::io::BufReader;
use rustls::{Certificate, PrivateKey};
use rustls_pemfile::{certs, pkcs8_private_keys};
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
let cert_file = cert.serialize_pem().unwrap();
let key_file = cert.serialize_private_key_pem();
let cert_file = &mut BufReader::new(cert_file.as_bytes());
let key_file = &mut BufReader::new(key_file.as_bytes());
let cert_chain = certs(cert_file)
.unwrap()
.into_iter()
.map(Certificate)
.collect();
let mut keys = pkcs8_private_keys(key_file).unwrap();
let mut config = rustls::ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
.unwrap();
config.alpn_protocols.push(b"http/1.1".to_vec());
config.alpn_protocols.push(b"h2".to_vec());
config
}

View File

@ -0,0 +1,215 @@
use std::{
error::Error as StdError,
pin::Pin,
task::{Context, Poll},
};
use bytes::Bytes;
use futures_core::{ready, Stream};
use pin_project_lite::pin_project;
use super::{BodySize, MessageBody};
pin_project! {
/// Streaming response wrapper.
///
/// Response does not contain `Content-Length` header and appropriate transfer encoding is used.
pub struct BodyStream<S> {
#[pin]
stream: S,
}
}
// TODO: from_infallible method
impl<S, E> BodyStream<S>
where
S: Stream<Item = Result<Bytes, E>>,
E: Into<Box<dyn StdError>> + 'static,
{
#[inline]
pub fn new(stream: S) -> Self {
BodyStream { stream }
}
}
impl<S, E> MessageBody for BodyStream<S>
where
S: Stream<Item = Result<Bytes, E>>,
E: Into<Box<dyn StdError>> + 'static,
{
type Error = E;
#[inline]
fn size(&self) -> BodySize {
BodySize::Stream
}
/// Attempts to pull out the next value of the underlying [`Stream`].
///
/// Empty values are skipped to prevent [`BodyStream`]'s transmission being
/// ended on a zero-length chunk, but rather proceed until the underlying
/// [`Stream`] ends.
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
loop {
let stream = self.as_mut().project().stream;
let chunk = match ready!(stream.poll_next(cx)) {
Some(Ok(ref bytes)) if bytes.is_empty() => continue,
opt => opt,
};
return Poll::Ready(chunk);
}
}
}
#[cfg(test)]
mod tests {
use std::{convert::Infallible, time::Duration};
use actix_rt::{
pin,
time::{sleep, Sleep},
};
use actix_utils::future::poll_fn;
use derive_more::{Display, Error};
use futures_core::ready;
use futures_util::{stream, FutureExt as _};
use pin_project_lite::pin_project;
use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::*;
use crate::body::to_bytes;
assert_impl_all!(BodyStream<stream::Empty<Result<Bytes, crate::Error>>>: MessageBody);
assert_impl_all!(BodyStream<stream::Empty<Result<Bytes, &'static str>>>: MessageBody);
assert_impl_all!(BodyStream<stream::Repeat<Result<Bytes, &'static str>>>: MessageBody);
assert_impl_all!(BodyStream<stream::Empty<Result<Bytes, Infallible>>>: MessageBody);
assert_impl_all!(BodyStream<stream::Repeat<Result<Bytes, Infallible>>>: MessageBody);
assert_not_impl_any!(BodyStream<stream::Empty<Bytes>>: MessageBody);
assert_not_impl_any!(BodyStream<stream::Repeat<Bytes>>: MessageBody);
// crate::Error is not Clone
assert_not_impl_any!(BodyStream<stream::Repeat<Result<Bytes, crate::Error>>>: MessageBody);
#[actix_rt::test]
async fn skips_empty_chunks() {
let body = BodyStream::new(stream::iter(
["1", "", "2"]
.iter()
.map(|&v| Ok::<_, Infallible>(Bytes::from(v))),
));
pin!(body);
assert_eq!(
poll_fn(|cx| body.as_mut().poll_next(cx))
.await
.unwrap()
.ok(),
Some(Bytes::from("1")),
);
assert_eq!(
poll_fn(|cx| body.as_mut().poll_next(cx))
.await
.unwrap()
.ok(),
Some(Bytes::from("2")),
);
}
#[actix_rt::test]
async fn read_to_bytes() {
let body = BodyStream::new(stream::iter(
["1", "", "2"]
.iter()
.map(|&v| Ok::<_, Infallible>(Bytes::from(v))),
));
assert_eq!(to_bytes(body).await.ok(), Some(Bytes::from("12")));
}
#[derive(Debug, Display, Error)]
#[display(fmt = "stream error")]
struct StreamErr;
#[actix_rt::test]
async fn stream_immediate_error() {
let body = BodyStream::new(stream::once(async { Err(StreamErr) }));
assert!(matches!(to_bytes(body).await, Err(StreamErr)));
}
#[actix_rt::test]
async fn stream_string_error() {
// `&'static str` does not impl `Error`
// but it does impl `Into<Box<dyn Error>>`
let body = BodyStream::new(stream::once(async { Err("stringy error") }));
assert!(matches!(to_bytes(body).await, Err("stringy error")));
}
#[actix_rt::test]
async fn stream_boxed_error() {
// `Box<dyn Error>` does not impl `Error`
// but it does impl `Into<Box<dyn Error>>`
let body = BodyStream::new(stream::once(async {
Err(Box::<dyn StdError>::from("stringy error"))
}));
assert_eq!(
to_bytes(body).await.unwrap_err().to_string(),
"stringy error"
);
}
#[actix_rt::test]
async fn stream_delayed_error() {
let body = BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)]));
assert!(matches!(to_bytes(body).await, Err(StreamErr)));
pin_project! {
#[derive(Debug)]
#[project = TimeDelayStreamProj]
enum TimeDelayStream {
Start,
Sleep { delay: Pin<Box<Sleep>> },
Done,
}
}
impl Stream for TimeDelayStream {
type Item = Result<Bytes, StreamErr>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
match self.as_mut().get_mut() {
TimeDelayStream::Start => {
let sleep = sleep(Duration::from_millis(1));
self.as_mut().set(TimeDelayStream::Sleep {
delay: Box::pin(sleep),
});
cx.waker().wake_by_ref();
Poll::Pending
}
TimeDelayStream::Sleep { ref mut delay } => {
ready!(delay.poll_unpin(cx));
self.set(TimeDelayStream::Done);
cx.waker().wake_by_ref();
Poll::Pending
}
TimeDelayStream::Done => Poll::Ready(Some(Err(StreamErr))),
}
}
}
let body = BodyStream::new(TimeDelayStream::Start);
assert!(matches!(to_bytes(body).await, Err(StreamErr)));
}
}

View File

@ -0,0 +1,126 @@
use std::{
error::Error as StdError,
fmt,
pin::Pin,
task::{Context, Poll},
};
use bytes::Bytes;
use super::{BodySize, MessageBody, MessageBodyMapErr};
use crate::body;
/// A boxed message body with boxed errors.
#[derive(Debug)]
pub struct BoxBody(BoxBodyInner);
enum BoxBodyInner {
None(body::None),
Bytes(Bytes),
Stream(Pin<Box<dyn MessageBody<Error = Box<dyn StdError>>>>),
}
impl fmt::Debug for BoxBodyInner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None(arg0) => f.debug_tuple("None").field(arg0).finish(),
Self::Bytes(arg0) => f.debug_tuple("Bytes").field(arg0).finish(),
Self::Stream(_) => f.debug_tuple("Stream").field(&"dyn MessageBody").finish(),
}
}
}
impl BoxBody {
/// Boxes body type, erasing type information.
///
/// If the body type to wrap is unknown or generic it is better to use [`MessageBody::boxed`] to
/// avoid double boxing.
#[inline]
pub fn new<B>(body: B) -> Self
where
B: MessageBody + 'static,
{
match body.size() {
BodySize::None => Self(BoxBodyInner::None(body::None)),
_ => match body.try_into_bytes() {
Ok(bytes) => Self(BoxBodyInner::Bytes(bytes)),
Err(body) => {
let body = MessageBodyMapErr::new(body, Into::into);
Self(BoxBodyInner::Stream(Box::pin(body)))
}
},
}
}
/// Returns a mutable pinned reference to the inner message body type.
#[inline]
pub fn as_pin_mut(&mut self) -> Pin<&mut Self> {
Pin::new(self)
}
}
impl MessageBody for BoxBody {
type Error = Box<dyn StdError>;
#[inline]
fn size(&self) -> BodySize {
match &self.0 {
BoxBodyInner::None(none) => none.size(),
BoxBodyInner::Bytes(bytes) => bytes.size(),
BoxBodyInner::Stream(stream) => stream.size(),
}
}
#[inline]
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
match &mut self.0 {
BoxBodyInner::None(body) => {
Pin::new(body).poll_next(cx).map_err(|err| match err {})
}
BoxBodyInner::Bytes(body) => {
Pin::new(body).poll_next(cx).map_err(|err| match err {})
}
BoxBodyInner::Stream(body) => Pin::new(body).poll_next(cx),
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
match self.0 {
BoxBodyInner::None(body) => Ok(body.try_into_bytes().unwrap()),
BoxBodyInner::Bytes(body) => Ok(body.try_into_bytes().unwrap()),
_ => Err(self),
}
}
#[inline]
fn boxed(self) -> BoxBody {
self
}
}
#[cfg(test)]
mod tests {
use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::*;
use crate::body::to_bytes;
assert_impl_all!(BoxBody: fmt::Debug, MessageBody, Unpin);
assert_not_impl_any!(BoxBody: Send, Sync);
#[actix_rt::test]
async fn nested_boxed_body() {
let body = Bytes::from_static(&[1, 2, 3]);
let boxed_body = BoxBody::new(BoxBody::new(body));
assert_eq!(
to_bytes(boxed_body).await.unwrap(),
Bytes::from(vec![1, 2, 3]),
);
}
}

View File

@ -0,0 +1,122 @@
use std::{
pin::Pin,
task::{Context, Poll},
};
use bytes::Bytes;
use pin_project_lite::pin_project;
use super::{BodySize, BoxBody, MessageBody};
use crate::Error;
pin_project! {
/// An "either" type specialized for body types.
///
/// It is common, in middleware especially, to conditionally return an inner service's unknown/
/// generic body `B` type or return early with a new response. This type's "right" variant
/// defaults to `BoxBody` since error responses are the common case.
///
/// For example, middleware will often have `type Response = ServiceResponse<EitherBody<B>>`.
/// This means that the inner service's response body type maps to the `Left` variant and the
/// middleware's own error responses use the default `Right` variant of `BoxBody`. Of course,
/// there's no reason it couldn't use `EitherBody<B, String>` instead if its alternative
/// responses have a known type.
#[project = EitherBodyProj]
#[derive(Debug, Clone)]
pub enum EitherBody<L, R = BoxBody> {
/// A body of type `L`.
Left { #[pin] body: L },
/// A body of type `R`.
Right { #[pin] body: R },
}
}
impl<L> EitherBody<L, BoxBody> {
/// Creates new `EitherBody` left variant with a boxed right variant.
///
/// If the expected `R` type will be inferred and is not `BoxBody` then use the
/// [`left`](Self::left) constructor instead.
#[inline]
pub fn new(body: L) -> Self {
Self::Left { body }
}
}
impl<L, R> EitherBody<L, R> {
/// Creates new `EitherBody` using left variant.
#[inline]
pub fn left(body: L) -> Self {
Self::Left { body }
}
/// Creates new `EitherBody` using right variant.
#[inline]
pub fn right(body: R) -> Self {
Self::Right { body }
}
}
impl<L, R> MessageBody for EitherBody<L, R>
where
L: MessageBody + 'static,
R: MessageBody + 'static,
{
type Error = Error;
#[inline]
fn size(&self) -> BodySize {
match self {
EitherBody::Left { body } => body.size(),
EitherBody::Right { body } => body.size(),
}
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
match self.project() {
EitherBodyProj::Left { body } => body
.poll_next(cx)
.map_err(|err| Error::new_body().with_cause(err)),
EitherBodyProj::Right { body } => body
.poll_next(cx)
.map_err(|err| Error::new_body().with_cause(err)),
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
match self {
EitherBody::Left { body } => body
.try_into_bytes()
.map_err(|body| EitherBody::Left { body }),
EitherBody::Right { body } => body
.try_into_bytes()
.map_err(|body| EitherBody::Right { body }),
}
}
#[inline]
fn boxed(self) -> BoxBody {
match self {
EitherBody::Left { body } => body.boxed(),
EitherBody::Right { body } => body.boxed(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn type_parameter_inference() {
let _body: EitherBody<(), _> = EitherBody::new(());
let _body: EitherBody<_, ()> = EitherBody::left(());
let _body: EitherBody<(), _> = EitherBody::right(());
}
}

View File

@ -0,0 +1,609 @@
//! [`MessageBody`] trait and foreign implementations.
use std::{
convert::Infallible,
error::Error as StdError,
mem,
pin::Pin,
task::{Context, Poll},
};
use bytes::{Bytes, BytesMut};
use futures_core::ready;
use pin_project_lite::pin_project;
use super::{BodySize, BoxBody};
/// An interface for types that can be used as a response body.
///
/// It is not usually necessary to create custom body types, this trait is already [implemented for
/// a large number of sensible body types](#foreign-impls) including:
/// - Empty body: `()`
/// - Text-based: `String`, `&'static str`, [`ByteString`](https://docs.rs/bytestring/1).
/// - Byte-based: `Bytes`, `BytesMut`, `Vec<u8>`, `&'static [u8]`;
/// - Streams: [`BodyStream`](super::BodyStream), [`SizedStream`](super::SizedStream)
///
/// # Examples
/// ```
/// # use std::convert::Infallible;
/// # use std::task::{Poll, Context};
/// # use std::pin::Pin;
/// # use bytes::Bytes;
/// # use actix_http::body::{BodySize, MessageBody};
/// struct Repeat {
/// chunk: String,
/// n_times: usize,
/// }
///
/// impl MessageBody for Repeat {
/// type Error = Infallible;
///
/// fn size(&self) -> BodySize {
/// BodySize::Sized((self.chunk.len() * self.n_times) as u64)
/// }
///
/// fn poll_next(
/// self: Pin<&mut Self>,
/// _cx: &mut Context<'_>,
/// ) -> Poll<Option<Result<Bytes, Self::Error>>> {
/// let payload_string = self.chunk.repeat(self.n_times);
/// let payload_bytes = Bytes::from(payload_string);
/// Poll::Ready(Some(Ok(payload_bytes)))
/// }
/// }
/// ```
pub trait MessageBody {
/// The type of error that will be returned if streaming body fails.
///
/// Since it is not appropriate to generate a response mid-stream, it only requires `Error` for
/// internal use and logging.
type Error: Into<Box<dyn StdError>>;
/// Body size hint.
///
/// If [`BodySize::None`] is returned, optimizations that skip reading the body are allowed.
fn size(&self) -> BodySize;
/// Attempt to pull out the next chunk of body bytes.
///
/// # Return Value
/// Similar to the `Stream` interface, there are several possible return values, each indicating
/// a distinct state:
/// - `Poll::Pending` means that this body's next chunk is not ready yet. Implementations must
/// ensure that the current task will be notified when the next chunk may be ready.
/// - `Poll::Ready(Some(val))` means that the body has successfully produced a chunk, `val`,
/// and may produce further values on subsequent `poll_next` calls.
/// - `Poll::Ready(None)` means that the body is complete, and `poll_next` should not be
/// invoked again.
///
/// # Panics
/// Once a body is complete (i.e., `poll_next` returned `Ready(None)`), calling its `poll_next`
/// method again may panic, block forever, or cause other kinds of problems; this trait places
/// no requirements on the effects of such a call. However, as the `poll_next` method is not
/// marked unsafe, Rusts usual rules apply: calls must never cause UB, regardless of its state.
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>>;
/// Try to convert into the complete chunk of body bytes.
///
/// Override this method if the complete body can be trivially extracted. This is useful for
/// optimizations where `poll_next` calls can be avoided.
///
/// Body types with [`BodySize::None`] are allowed to return empty `Bytes`. Although, if calling
/// this method, it is recommended to check `size` first and return early.
///
/// # Errors
/// The default implementation will error and return the original type back to the caller for
/// further use.
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self>
where
Self: Sized,
{
Err(self)
}
/// Wraps this body into a `BoxBody`.
///
/// No-op when called on a `BoxBody`, meaning there is no risk of double boxing when calling
/// this on a generic `MessageBody`. Prefer this over [`BoxBody::new`] when a boxed body
/// is required.
#[inline]
fn boxed(self) -> BoxBody
where
Self: Sized + 'static,
{
BoxBody::new(self)
}
}
mod foreign_impls {
use super::*;
impl MessageBody for Infallible {
type Error = Infallible;
fn size(&self) -> BodySize {
match *self {}
}
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
match *self {}
}
}
impl MessageBody for () {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(0)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
Poll::Ready(None)
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(Bytes::new())
}
}
impl<B> MessageBody for Box<B>
where
B: MessageBody + Unpin + ?Sized,
{
type Error = B::Error;
#[inline]
fn size(&self) -> BodySize {
self.as_ref().size()
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
Pin::new(self.get_mut().as_mut()).poll_next(cx)
}
}
impl<B> MessageBody for Pin<Box<B>>
where
B: MessageBody + ?Sized,
{
type Error = B::Error;
#[inline]
fn size(&self) -> BodySize {
self.as_ref().size()
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
self.get_mut().as_mut().poll_next(cx)
}
}
impl MessageBody for &'static [u8] {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(Bytes::from_static(mem::take(self.get_mut())))))
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(Bytes::from_static(self))
}
}
impl MessageBody for Bytes {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(mem::take(self.get_mut()))))
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(self)
}
}
impl MessageBody for BytesMut {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(mem::take(self.get_mut()).freeze())))
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(self.freeze())
}
}
impl MessageBody for Vec<u8> {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(mem::take(self.get_mut()).into())))
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(Bytes::from(self))
}
}
impl MessageBody for &'static str {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() {
Poll::Ready(None)
} else {
let string = mem::take(self.get_mut());
let bytes = Bytes::from_static(string.as_bytes());
Poll::Ready(Some(Ok(bytes)))
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(Bytes::from_static(self.as_bytes()))
}
}
impl MessageBody for String {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() {
Poll::Ready(None)
} else {
let string = mem::take(self.get_mut());
Poll::Ready(Some(Ok(Bytes::from(string))))
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(Bytes::from(self))
}
}
impl MessageBody for bytestring::ByteString {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
let string = mem::take(self.get_mut());
Poll::Ready(Some(Ok(string.into_bytes())))
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(self.into_bytes())
}
}
}
pin_project! {
pub(crate) struct MessageBodyMapErr<B, F> {
#[pin]
body: B,
mapper: Option<F>,
}
}
impl<B, F, E> MessageBodyMapErr<B, F>
where
B: MessageBody,
F: FnOnce(B::Error) -> E,
{
pub(crate) fn new(body: B, mapper: F) -> Self {
Self {
body,
mapper: Some(mapper),
}
}
}
impl<B, F, E> MessageBody for MessageBodyMapErr<B, F>
where
B: MessageBody,
F: FnOnce(B::Error) -> E,
E: Into<Box<dyn StdError>>,
{
type Error = E;
#[inline]
fn size(&self) -> BodySize {
self.body.size()
}
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
let this = self.as_mut().project();
match ready!(this.body.poll_next(cx)) {
Some(Err(err)) => {
let f = self.as_mut().project().mapper.take().unwrap();
let mapped_err = (f)(err);
Poll::Ready(Some(Err(mapped_err)))
}
Some(Ok(val)) => Poll::Ready(Some(Ok(val))),
None => Poll::Ready(None),
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
let Self { body, mapper } = self;
body.try_into_bytes().map_err(|body| Self { body, mapper })
}
}
#[cfg(test)]
mod tests {
use actix_rt::pin;
use actix_utils::future::poll_fn;
use bytes::{Bytes, BytesMut};
use super::*;
use crate::body::{self, EitherBody};
macro_rules! assert_poll_next {
($pin:expr, $exp:expr) => {
assert_eq!(
poll_fn(|cx| $pin.as_mut().poll_next(cx))
.await
.unwrap() // unwrap option
.unwrap(), // unwrap result
$exp
);
};
}
macro_rules! assert_poll_next_none {
($pin:expr) => {
assert!(poll_fn(|cx| $pin.as_mut().poll_next(cx)).await.is_none());
};
}
#[actix_rt::test]
async fn boxing_equivalence() {
assert_eq!(().size(), BodySize::Sized(0));
assert_eq!(().size(), Box::new(()).size());
assert_eq!(().size(), Box::pin(()).size());
let pl = Box::new(());
pin!(pl);
assert_poll_next_none!(pl);
let mut pl = Box::pin(());
assert_poll_next_none!(pl);
}
#[actix_rt::test]
async fn test_unit() {
let pl = ();
assert_eq!(pl.size(), BodySize::Sized(0));
pin!(pl);
assert_poll_next_none!(pl);
}
#[actix_rt::test]
async fn test_static_str() {
assert_eq!("".size(), BodySize::Sized(0));
assert_eq!("test".size(), BodySize::Sized(4));
let pl = "test";
pin!(pl);
assert_poll_next!(pl, Bytes::from("test"));
}
#[actix_rt::test]
async fn test_static_bytes() {
assert_eq!(b"".as_ref().size(), BodySize::Sized(0));
assert_eq!(b"test".as_ref().size(), BodySize::Sized(4));
let pl = b"test".as_ref();
pin!(pl);
assert_poll_next!(pl, Bytes::from("test"));
}
#[actix_rt::test]
async fn test_vec() {
assert_eq!(vec![0; 0].size(), BodySize::Sized(0));
assert_eq!(Vec::from("test").size(), BodySize::Sized(4));
let pl = Vec::from("test");
pin!(pl);
assert_poll_next!(pl, Bytes::from("test"));
}
#[actix_rt::test]
async fn test_bytes() {
assert_eq!(Bytes::new().size(), BodySize::Sized(0));
assert_eq!(Bytes::from_static(b"test").size(), BodySize::Sized(4));
let pl = Bytes::from_static(b"test");
pin!(pl);
assert_poll_next!(pl, Bytes::from("test"));
}
#[actix_rt::test]
async fn test_bytes_mut() {
assert_eq!(BytesMut::new().size(), BodySize::Sized(0));
assert_eq!(BytesMut::from(b"test".as_ref()).size(), BodySize::Sized(4));
let pl = BytesMut::from("test");
pin!(pl);
assert_poll_next!(pl, Bytes::from("test"));
}
#[actix_rt::test]
async fn test_string() {
assert_eq!(String::new().size(), BodySize::Sized(0));
assert_eq!("test".to_owned().size(), BodySize::Sized(4));
let pl = "test".to_owned();
pin!(pl);
assert_poll_next!(pl, Bytes::from("test"));
}
#[actix_rt::test]
async fn complete_body_combinators() {
let body = Bytes::from_static(b"test");
let body = BoxBody::new(body);
let body = EitherBody::<_, ()>::left(body);
let body = EitherBody::<(), _>::right(body);
// Do not support try_into_bytes:
// let body = Box::new(body);
// let body = Box::pin(body);
assert_eq!(body.try_into_bytes().unwrap(), Bytes::from("test"));
}
#[actix_rt::test]
async fn complete_body_combinators_poll() {
let body = Bytes::from_static(b"test");
let body = BoxBody::new(body);
let body = EitherBody::<_, ()>::left(body);
let body = EitherBody::<(), _>::right(body);
let mut body = body;
assert_eq!(body.size(), BodySize::Sized(4));
assert_poll_next!(Pin::new(&mut body), Bytes::from("test"));
assert_poll_next_none!(Pin::new(&mut body));
}
#[actix_rt::test]
async fn none_body_combinators() {
fn none_body() -> BoxBody {
let body = body::None;
let body = BoxBody::new(body);
let body = EitherBody::<_, ()>::left(body);
let body = EitherBody::<(), _>::right(body);
body.boxed()
}
assert_eq!(none_body().size(), BodySize::None);
assert_eq!(none_body().try_into_bytes().unwrap(), Bytes::new());
assert_poll_next_none!(Pin::new(&mut none_body()));
}
// down-casting used to be done with a method on MessageBody trait
// test is kept to demonstrate equivalence of Any trait
#[actix_rt::test]
async fn test_body_casting() {
let mut body = String::from("hello cast");
// let mut resp_body: &mut dyn MessageBody<Error = Error> = &mut body;
let resp_body: &mut dyn std::any::Any = &mut body;
let body = resp_body.downcast_ref::<String>().unwrap();
assert_eq!(body, "hello cast");
let body = &mut resp_body.downcast_mut::<String>().unwrap();
body.push('!');
let body = resp_body.downcast_ref::<String>().unwrap();
assert_eq!(body, "hello cast!");
let not_body = resp_body.downcast_ref::<()>();
assert!(not_body.is_none());
}
}

View File

@ -0,0 +1,25 @@
//! Traits and structures to aid consuming and writing HTTP payloads.
//!
//! "Body" and "payload" are used somewhat interchangeably in this documentation.
// Though the spec kinda reads like "payload" is the possibly-transfer-encoded part of the message
// and the "body" is the intended possibly-decoded version of that.
mod body_stream;
mod boxed;
mod either;
mod message_body;
mod none;
mod size;
mod sized_stream;
mod utils;
pub use self::body_stream::BodyStream;
pub use self::boxed::BoxBody;
pub use self::either::EitherBody;
pub use self::message_body::MessageBody;
pub(crate) use self::message_body::MessageBodyMapErr;
pub use self::none::None;
pub use self::size::BodySize;
pub use self::sized_stream::SizedStream;
pub use self::utils::to_bytes;

View File

@ -0,0 +1,51 @@
use std::{
convert::Infallible,
pin::Pin,
task::{Context, Poll},
};
use bytes::Bytes;
use super::{BodySize, MessageBody};
/// Body type for responses that forbid payloads.
///
/// This is distinct from an "empty" response which _would_ contain a `Content-Length` header.
/// For an "empty" body, use `()` or `Bytes::new()`.
///
/// For example, the HTTP spec forbids a payload to be sent with a `204 No Content` response.
/// In this case, the payload (or lack thereof) is implicit from the status code, so a
/// `Content-Length` header is not required.
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub struct None;
impl None {
/// Constructs new "none" body.
#[inline]
pub fn new() -> Self {
None
}
}
impl MessageBody for None {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::None
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
Poll::Ready(Option::None)
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(Bytes::new())
}
}

View File

@ -0,0 +1,41 @@
/// Body size hint.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BodySize {
/// Implicitly empty body.
///
/// Will omit the Content-Length header. Used for responses to certain methods (e.g., `HEAD`) or
/// with particular status codes (e.g., 204 No Content). Consumers that read this as a body size
/// hint are allowed to make optimizations that skip reading or writing the payload.
None,
/// Known size body.
///
/// Will write `Content-Length: N` header.
Sized(u64),
/// Unknown size body.
///
/// Will not write Content-Length header. Can be used with chunked Transfer-Encoding.
Stream,
}
impl BodySize {
/// Equivalent to `BodySize::Sized(0)`;
pub const ZERO: Self = Self::Sized(0);
/// Returns true if size hint indicates omitted or empty body.
///
/// Streams will return false because it cannot be known without reading the stream.
///
/// ```
/// # use actix_http::body::BodySize;
/// assert!(BodySize::None.is_eof());
/// assert!(BodySize::Sized(0).is_eof());
///
/// assert!(!BodySize::Sized(64).is_eof());
/// assert!(!BodySize::Stream.is_eof());
/// ```
pub fn is_eof(&self) -> bool {
matches!(self, BodySize::None | BodySize::Sized(0))
}
}

View File

@ -0,0 +1,171 @@
use std::{
error::Error as StdError,
pin::Pin,
task::{Context, Poll},
};
use bytes::Bytes;
use futures_core::{ready, Stream};
use pin_project_lite::pin_project;
use super::{BodySize, MessageBody};
pin_project! {
/// Known sized streaming response wrapper.
///
/// This body implementation should be used if total size of stream is known. Data is sent as-is
/// without using chunked transfer encoding.
pub struct SizedStream<S> {
size: u64,
#[pin]
stream: S,
}
}
impl<S, E> SizedStream<S>
where
S: Stream<Item = Result<Bytes, E>>,
E: Into<Box<dyn StdError>> + 'static,
{
#[inline]
pub fn new(size: u64, stream: S) -> Self {
SizedStream { size, stream }
}
}
// TODO: from_infallible method
impl<S, E> MessageBody for SizedStream<S>
where
S: Stream<Item = Result<Bytes, E>>,
E: Into<Box<dyn StdError>> + 'static,
{
type Error = E;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.size as u64)
}
/// Attempts to pull out the next value of the underlying [`Stream`].
///
/// Empty values are skipped to prevent [`SizedStream`]'s transmission being
/// ended on a zero-length chunk, but rather proceed until the underlying
/// [`Stream`] ends.
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
loop {
let stream = self.as_mut().project().stream;
let chunk = match ready!(stream.poll_next(cx)) {
Some(Ok(ref bytes)) if bytes.is_empty() => continue,
val => val,
};
return Poll::Ready(chunk);
}
}
}
#[cfg(test)]
mod tests {
use std::convert::Infallible;
use actix_rt::pin;
use actix_utils::future::poll_fn;
use futures_util::stream;
use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::*;
use crate::body::to_bytes;
assert_impl_all!(SizedStream<stream::Empty<Result<Bytes, crate::Error>>>: MessageBody);
assert_impl_all!(SizedStream<stream::Empty<Result<Bytes, &'static str>>>: MessageBody);
assert_impl_all!(SizedStream<stream::Repeat<Result<Bytes, &'static str>>>: MessageBody);
assert_impl_all!(SizedStream<stream::Empty<Result<Bytes, Infallible>>>: MessageBody);
assert_impl_all!(SizedStream<stream::Repeat<Result<Bytes, Infallible>>>: MessageBody);
assert_not_impl_any!(SizedStream<stream::Empty<Bytes>>: MessageBody);
assert_not_impl_any!(SizedStream<stream::Repeat<Bytes>>: MessageBody);
// crate::Error is not Clone
assert_not_impl_any!(SizedStream<stream::Repeat<Result<Bytes, crate::Error>>>: MessageBody);
#[actix_rt::test]
async fn skips_empty_chunks() {
let body = SizedStream::new(
2,
stream::iter(
["1", "", "2"]
.iter()
.map(|&v| Ok::<_, Infallible>(Bytes::from(v))),
),
);
pin!(body);
assert_eq!(
poll_fn(|cx| body.as_mut().poll_next(cx))
.await
.unwrap()
.ok(),
Some(Bytes::from("1")),
);
assert_eq!(
poll_fn(|cx| body.as_mut().poll_next(cx))
.await
.unwrap()
.ok(),
Some(Bytes::from("2")),
);
}
#[actix_rt::test]
async fn read_to_bytes() {
let body = SizedStream::new(
2,
stream::iter(
["1", "", "2"]
.iter()
.map(|&v| Ok::<_, Infallible>(Bytes::from(v))),
),
);
assert_eq!(to_bytes(body).await.ok(), Some(Bytes::from("12")));
}
#[actix_rt::test]
async fn stream_string_error() {
// `&'static str` does not impl `Error`
// but it does impl `Into<Box<dyn Error>>`
let body = SizedStream::new(0, stream::once(async { Err("stringy error") }));
assert_eq!(to_bytes(body).await, Ok(Bytes::new()));
let body = SizedStream::new(1, stream::once(async { Err("stringy error") }));
assert!(matches!(to_bytes(body).await, Err("stringy error")));
}
#[actix_rt::test]
async fn stream_boxed_error() {
// `Box<dyn Error>` does not impl `Error`
// but it does impl `Into<Box<dyn Error>>`
let body = SizedStream::new(
0,
stream::once(async { Err(Box::<dyn StdError>::from("stringy error")) }),
);
assert_eq!(to_bytes(body).await.unwrap(), Bytes::new());
let body = SizedStream::new(
1,
stream::once(async { Err(Box::<dyn StdError>::from("stringy error")) }),
);
assert_eq!(
to_bytes(body).await.unwrap_err().to_string(),
"stringy error"
);
}
}

View File

@ -0,0 +1,77 @@
use std::task::Poll;
use actix_rt::pin;
use actix_utils::future::poll_fn;
use bytes::{Bytes, BytesMut};
use futures_core::ready;
use super::{BodySize, MessageBody};
/// Collects the body produced by a `MessageBody` implementation into `Bytes`.
///
/// Any errors produced by the body stream are returned immediately.
///
/// # Examples
/// ```
/// use actix_http::body::{self, to_bytes};
/// use bytes::Bytes;
///
/// # async fn test_to_bytes() {
/// let body = body::None::new();
/// let bytes = to_bytes(body).await.unwrap();
/// assert!(bytes.is_empty());
///
/// let body = Bytes::from_static(b"123");
/// let bytes = to_bytes(body).await.unwrap();
/// assert_eq!(bytes, b"123"[..]);
/// # }
/// ```
pub async fn to_bytes<B: MessageBody>(body: B) -> Result<Bytes, B::Error> {
let cap = match body.size() {
BodySize::None | BodySize::Sized(0) => return Ok(Bytes::new()),
BodySize::Sized(size) => size as usize,
// good enough first guess for chunk size
BodySize::Stream => 32_768,
};
let mut buf = BytesMut::with_capacity(cap);
pin!(body);
poll_fn(|cx| loop {
let body = body.as_mut();
match ready!(body.poll_next(cx)) {
Some(Ok(bytes)) => buf.extend_from_slice(&*bytes),
None => return Poll::Ready(Ok(())),
Some(Err(err)) => return Poll::Ready(Err(err)),
}
})
.await?;
Ok(buf.freeze())
}
#[cfg(test)]
mod test {
use futures_util::{stream, StreamExt as _};
use super::*;
use crate::{body::BodyStream, Error};
#[actix_rt::test]
async fn test_to_bytes() {
let bytes = to_bytes(()).await.unwrap();
assert!(bytes.is_empty());
let body = Bytes::from_static(b"123");
let bytes = to_bytes(body).await.unwrap();
assert_eq!(bytes, b"123"[..]);
let stream = stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")])
.map(Ok::<_, Error>);
let body = BodyStream::new(stream);
let bytes = to_bytes(body).await.unwrap();
assert_eq!(bytes, b"123abc"[..]);
}
}

258
actix-http/src/builder.rs Normal file
View File

@ -0,0 +1,258 @@
use std::{fmt, marker::PhantomData, net, rc::Rc, time::Duration};
use actix_codec::Framed;
use actix_service::{IntoServiceFactory, Service, ServiceFactory};
use crate::{
body::{BoxBody, MessageBody},
h1::{self, ExpectHandler, H1Service, UpgradeHandler},
service::HttpService,
ConnectCallback, Extensions, KeepAlive, Request, Response, ServiceConfig,
};
/// An HTTP service builder.
///
/// This type can construct an instance of [`HttpService`] through a builder-like pattern.
pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
keep_alive: KeepAlive,
client_request_timeout: Duration,
client_disconnect_timeout: Duration,
secure: bool,
local_addr: Option<net::SocketAddr>,
expect: X,
upgrade: Option<U>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
_phantom: PhantomData<S>,
}
impl<T, S> Default for HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
<S::Service as Service<Request>>::Future: 'static,
{
fn default() -> Self {
HttpServiceBuilder {
// ServiceConfig parts (make sure defaults match)
keep_alive: KeepAlive::default(),
client_request_timeout: Duration::from_secs(5),
client_disconnect_timeout: Duration::ZERO,
secure: false,
local_addr: None,
// dispatcher parts
expect: ExpectHandler,
upgrade: None,
on_connect_ext: None,
_phantom: PhantomData,
}
}
}
impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
<S::Service as Service<Request>>::Future: 'static,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
U::Error: fmt::Display,
U::InitError: fmt::Debug,
{
/// Set connection keep-alive setting.
///
/// Applies to HTTP/1.1 keep-alive and HTTP/2 ping-pong.
///
/// By default keep-alive is 5 seconds.
pub fn keep_alive<W: Into<KeepAlive>>(mut self, val: W) -> Self {
self.keep_alive = val.into();
self
}
/// Set connection secure state
pub fn secure(mut self) -> Self {
self.secure = true;
self
}
/// Set the local address that this service is bound to.
pub fn local_addr(mut self, addr: net::SocketAddr) -> Self {
self.local_addr = Some(addr);
self
}
/// Set client request timeout (for first request).
///
/// Defines a timeout for reading client request header. If the client does not transmit the
/// request head within this duration, the connection is terminated with a `408 Request Timeout`
/// response error.
///
/// A duration of zero disables the timeout.
///
/// By default, the client timeout is 5 seconds.
pub fn client_request_timeout(mut self, dur: Duration) -> Self {
self.client_request_timeout = dur;
self
}
#[doc(hidden)]
#[deprecated(since = "3.0.0", note = "Renamed to `client_request_timeout`.")]
pub fn client_timeout(self, dur: Duration) -> Self {
self.client_request_timeout(dur)
}
/// Set client connection disconnect timeout.
///
/// Defines a timeout for disconnect connection. If a disconnect procedure does not complete
/// within this time, the request get dropped. This timeout affects secure connections.
///
/// A duration of zero disables the timeout.
///
/// By default, the disconnect timeout is disabled.
pub fn client_disconnect_timeout(mut self, dur: Duration) -> Self {
self.client_disconnect_timeout = dur;
self
}
#[doc(hidden)]
#[deprecated(since = "3.0.0", note = "Renamed to `client_disconnect_timeout`.")]
pub fn client_disconnect(self, dur: Duration) -> Self {
self.client_disconnect_timeout(dur)
}
/// Provide service for `EXPECT: 100-Continue` support.
///
/// Service get called with request that contains `EXPECT` header.
/// Service must return request in case of success, in that case
/// request will be forwarded to main service.
pub fn expect<F, X1>(self, expect: F) -> HttpServiceBuilder<T, S, X1, U>
where
F: IntoServiceFactory<X1, Request>,
X1: ServiceFactory<Request, Config = (), Response = Request>,
X1::Error: Into<Response<BoxBody>>,
X1::InitError: fmt::Debug,
{
HttpServiceBuilder {
keep_alive: self.keep_alive,
client_request_timeout: self.client_request_timeout,
client_disconnect_timeout: self.client_disconnect_timeout,
secure: self.secure,
local_addr: self.local_addr,
expect: expect.into_factory(),
upgrade: self.upgrade,
on_connect_ext: self.on_connect_ext,
_phantom: PhantomData,
}
}
/// Provide service for custom `Connection: UPGRADE` support.
///
/// If service is provided then normal requests handling get halted
/// and this service get called with original request and framed object.
pub fn upgrade<F, U1>(self, upgrade: F) -> HttpServiceBuilder<T, S, X, U1>
where
F: IntoServiceFactory<U1, (Request, Framed<T, h1::Codec>)>,
U1: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
U1::Error: fmt::Display,
U1::InitError: fmt::Debug,
{
HttpServiceBuilder {
keep_alive: self.keep_alive,
client_request_timeout: self.client_request_timeout,
client_disconnect_timeout: self.client_disconnect_timeout,
secure: self.secure,
local_addr: self.local_addr,
expect: self.expect,
upgrade: Some(upgrade.into_factory()),
on_connect_ext: self.on_connect_ext,
_phantom: PhantomData,
}
}
/// Sets the callback to be run on connection establishment.
///
/// Has mutable access to a data container that will be merged into request extensions.
/// This enables transport layer data (like client certificates) to be accessed in middleware
/// and handlers.
pub fn on_connect_ext<F>(mut self, f: F) -> Self
where
F: Fn(&T, &mut Extensions) + 'static,
{
self.on_connect_ext = Some(Rc::new(f));
self
}
/// Finish service configuration and create a HTTP Service for HTTP/1 protocol.
pub fn h1<F, B>(self, service: F) -> H1Service<T, S, B, X, U>
where
B: MessageBody,
F: IntoServiceFactory<S, Request>,
S::Error: Into<Response<BoxBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
{
let cfg = ServiceConfig::new(
self.keep_alive,
self.client_request_timeout,
self.client_disconnect_timeout,
self.secure,
self.local_addr,
);
H1Service::with_config(cfg, service.into_factory())
.expect(self.expect)
.upgrade(self.upgrade)
.on_connect_ext(self.on_connect_ext)
}
/// Finish service configuration and create a HTTP service for HTTP/2 protocol.
#[cfg(feature = "http2")]
pub fn h2<F, B>(self, service: F) -> crate::h2::H2Service<T, S, B>
where
F: IntoServiceFactory<S, Request>,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
{
let cfg = ServiceConfig::new(
self.keep_alive,
self.client_request_timeout,
self.client_disconnect_timeout,
self.secure,
self.local_addr,
);
crate::h2::H2Service::with_config(cfg, service.into_factory())
.on_connect_ext(self.on_connect_ext)
}
/// Finish service configuration and create `HttpService` instance.
pub fn finish<F, B>(self, service: F) -> HttpService<T, S, B, X, U>
where
F: IntoServiceFactory<S, Request>,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
{
let cfg = ServiceConfig::new(
self.keep_alive,
self.client_request_timeout,
self.client_disconnect_timeout,
self.secure,
self.local_addr,
);
HttpService::with_config(cfg, service.into_factory())
.expect(self.expect)
.upgrade(self.upgrade)
.on_connect_ext(self.on_connect_ext)
}
}

236
actix-http/src/config.rs Normal file
View File

@ -0,0 +1,236 @@
use std::{
net,
rc::Rc,
time::{Duration, Instant},
};
use bytes::BytesMut;
use crate::{date::DateService, KeepAlive};
/// HTTP service configuration.
#[derive(Debug, Clone)]
pub struct ServiceConfig(Rc<Inner>);
#[derive(Debug)]
struct Inner {
keep_alive: KeepAlive,
client_request_timeout: Duration,
client_disconnect_timeout: Duration,
secure: bool,
local_addr: Option<std::net::SocketAddr>,
date_service: DateService,
}
impl Default for ServiceConfig {
fn default() -> Self {
Self::new(
KeepAlive::default(),
Duration::from_secs(5),
Duration::ZERO,
false,
None,
)
}
}
impl ServiceConfig {
/// Create instance of `ServiceConfig`
pub fn new(
keep_alive: KeepAlive,
client_request_timeout: Duration,
client_disconnect_timeout: Duration,
secure: bool,
local_addr: Option<net::SocketAddr>,
) -> ServiceConfig {
ServiceConfig(Rc::new(Inner {
keep_alive: keep_alive.normalize(),
client_request_timeout,
client_disconnect_timeout,
secure,
local_addr,
date_service: DateService::new(),
}))
}
/// Returns `true` if connection is secure (i.e., using TLS / HTTPS).
#[inline]
pub fn secure(&self) -> bool {
self.0.secure
}
/// Returns the local address that this server is bound to.
///
/// Returns `None` for connections via UDS (Unix Domain Socket).
#[inline]
pub fn local_addr(&self) -> Option<net::SocketAddr> {
self.0.local_addr
}
/// Connection keep-alive setting.
#[inline]
pub fn keep_alive(&self) -> KeepAlive {
self.0.keep_alive
}
/// Creates a time object representing the deadline for this connection's keep-alive period, if
/// enabled.
///
/// When [`KeepAlive::Os`] or [`KeepAlive::Disabled`] is set, this will return `None`.
pub fn keep_alive_deadline(&self) -> Option<Instant> {
match self.keep_alive() {
KeepAlive::Timeout(dur) => Some(self.now() + dur),
KeepAlive::Os => None,
KeepAlive::Disabled => None,
}
}
/// Creates a time object representing the deadline for the client to finish sending the head of
/// its first request.
///
/// Returns `None` if this `ServiceConfig was` constructed with `client_request_timeout: 0`.
pub fn client_request_deadline(&self) -> Option<Instant> {
let timeout = self.0.client_request_timeout;
(timeout != Duration::ZERO).then(|| self.now() + timeout)
}
/// Creates a time object representing the deadline for the client to disconnect.
pub fn client_disconnect_deadline(&self) -> Option<Instant> {
let timeout = self.0.client_disconnect_timeout;
(timeout != Duration::ZERO).then(|| self.now() + timeout)
}
pub(crate) fn now(&self) -> Instant {
self.0.date_service.now()
}
/// Writes date header to `dst` buffer.
///
/// Low-level method that utilizes the built-in efficient date service, requiring fewer syscalls
/// than normal. Note that a CRLF (`\r\n`) is included in what is written.
#[doc(hidden)]
pub fn write_date_header(&self, dst: &mut BytesMut, camel_case: bool) {
let mut buf: [u8; 37] = [0; 37];
buf[..6].copy_from_slice(if camel_case { b"Date: " } else { b"date: " });
self.0
.date_service
.with_date(|date| buf[6..35].copy_from_slice(&date.bytes));
buf[35..].copy_from_slice(b"\r\n");
dst.extend_from_slice(&buf);
}
#[allow(unused)] // used with `http2` feature flag
pub(crate) fn write_date_header_value(&self, dst: &mut BytesMut) {
self.0
.date_service
.with_date(|date| dst.extend_from_slice(&date.bytes));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{date::DATE_VALUE_LENGTH, notify_on_drop};
use actix_rt::{
task::yield_now,
time::{sleep, sleep_until},
};
use memchr::memmem;
#[actix_rt::test]
async fn test_date_service_update() {
let settings =
ServiceConfig::new(KeepAlive::Os, Duration::ZERO, Duration::ZERO, false, None);
yield_now().await;
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.write_date_header(&mut buf1, false);
let now1 = settings.now();
sleep_until((Instant::now() + Duration::from_secs(2)).into()).await;
yield_now().await;
let now2 = settings.now();
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.write_date_header(&mut buf2, false);
assert_ne!(now1, now2);
assert_ne!(buf1, buf2);
drop(settings);
// Ensure the task will drop eventually
let mut times = 0;
while !notify_on_drop::is_dropped() {
sleep(Duration::from_millis(100)).await;
times += 1;
assert!(times < 10, "Timeout waiting for task drop");
}
}
#[actix_rt::test]
async fn test_date_service_drop() {
let service = Rc::new(DateService::new());
// yield so date service have a chance to register the spawned timer update task.
yield_now().await;
let clone1 = service.clone();
let clone2 = service.clone();
let clone3 = service.clone();
drop(clone1);
assert!(!notify_on_drop::is_dropped());
drop(clone2);
assert!(!notify_on_drop::is_dropped());
drop(clone3);
assert!(!notify_on_drop::is_dropped());
drop(service);
// Ensure the task will drop eventually
let mut times = 0;
while !notify_on_drop::is_dropped() {
sleep(Duration::from_millis(100)).await;
times += 1;
assert!(times < 10, "Timeout waiting for task drop");
}
}
#[test]
fn test_date_len() {
assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
}
#[actix_rt::test]
async fn test_date() {
let settings = ServiceConfig::default();
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.write_date_header(&mut buf1, false);
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.write_date_header(&mut buf2, false);
assert_eq!(buf1, buf2);
}
#[actix_rt::test]
async fn test_date_camel_case() {
let settings = ServiceConfig::default();
let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.write_date_header(&mut buf, false);
assert!(memmem::find(&buf, b"date:").is_some());
let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.write_date_header(&mut buf, true);
assert!(memmem::find(&buf, b"Date:").is_some());
}
}

92
actix-http/src/date.rs Normal file
View File

@ -0,0 +1,92 @@
use std::{
cell::Cell,
fmt::{self, Write},
rc::Rc,
time::{Duration, Instant, SystemTime},
};
use actix_rt::{task::JoinHandle, time::interval};
/// "Thu, 01 Jan 1970 00:00:00 GMT".len()
pub(crate) const DATE_VALUE_LENGTH: usize = 29;
#[derive(Clone, Copy)]
pub(crate) struct Date {
pub(crate) bytes: [u8; DATE_VALUE_LENGTH],
pos: usize,
}
impl Date {
fn new() -> Date {
let mut date = Date {
bytes: [0; DATE_VALUE_LENGTH],
pos: 0,
};
date.update();
date
}
fn update(&mut self) {
self.pos = 0;
write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap();
}
}
impl fmt::Write for Date {
fn write_str(&mut self, s: &str) -> fmt::Result {
let len = s.len();
self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes());
self.pos += len;
Ok(())
}
}
/// Service for update Date and Instant periodically at 500 millis interval.
pub(crate) struct DateService {
current: Rc<Cell<(Date, Instant)>>,
handle: JoinHandle<()>,
}
impl DateService {
pub(crate) fn new() -> Self {
// shared date and timer for DateService and update async task.
let current = Rc::new(Cell::new((Date::new(), Instant::now())));
let current_clone = Rc::clone(&current);
// spawn an async task sleep for 500 millis and update current date/timer in a loop.
// handle is used to stop the task on DateService drop.
let handle = actix_rt::spawn(async move {
#[cfg(test)]
let _notify = crate::notify_on_drop::NotifyOnDrop::new();
let mut interval = interval(Duration::from_millis(500));
loop {
let now = interval.tick().await;
let date = Date::new();
current_clone.set((date, now.into_std()));
}
});
DateService { current, handle }
}
pub(crate) fn now(&self) -> Instant {
self.current.get().1
}
pub(crate) fn with_date<F: FnMut(&Date)>(&self, mut f: F) {
f(&self.current.get().0);
}
}
impl fmt::Debug for DateService {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DateService").finish_non_exhaustive()
}
}
impl Drop for DateService {
fn drop(&mut self) {
// stop the timer update async task on drop.
self.handle.abort();
}
}

View File

@ -0,0 +1,304 @@
//! Stream decoders.
use std::{
future::Future,
io::{self, Write as _},
pin::Pin,
task::{Context, Poll},
};
use actix_rt::task::{spawn_blocking, JoinHandle};
use bytes::Bytes;
use futures_core::{ready, Stream};
#[cfg(feature = "compress-gzip")]
use flate2::write::{GzDecoder, ZlibDecoder};
#[cfg(feature = "compress-zstd")]
use zstd::stream::write::Decoder as ZstdDecoder;
use crate::{
encoding::Writer,
error::PayloadError,
header::{ContentEncoding, HeaderMap, CONTENT_ENCODING},
};
const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049;
pin_project_lite::pin_project! {
pub struct Decoder<S> {
decoder: Option<ContentDecoder>,
#[pin]
stream: S,
eof: bool,
fut: Option<JoinHandle<Result<(Option<Bytes>, ContentDecoder), io::Error>>>,
}
}
impl<S> Decoder<S>
where
S: Stream<Item = Result<Bytes, PayloadError>>,
{
/// Construct a decoder.
#[inline]
pub fn new(stream: S, encoding: ContentEncoding) -> Decoder<S> {
let decoder = match encoding {
#[cfg(feature = "compress-brotli")]
ContentEncoding::Brotli => Some(ContentDecoder::Brotli(Box::new(
brotli::DecompressorWriter::new(Writer::new(), 8_096),
))),
#[cfg(feature = "compress-gzip")]
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
ZlibDecoder::new(Writer::new()),
))),
#[cfg(feature = "compress-gzip")]
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new(
Writer::new(),
)))),
#[cfg(feature = "compress-zstd")]
ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new(
ZstdDecoder::new(Writer::new()).expect(
"Failed to create zstd decoder. This is a bug. \
Please report it to the actix-web repository.",
),
))),
_ => None,
};
Decoder {
decoder,
stream,
fut: None,
eof: false,
}
}
/// Construct decoder based on headers.
#[inline]
pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder<S> {
// check content-encoding
let encoding = headers
.get(&CONTENT_ENCODING)
.and_then(|val| val.to_str().ok())
.and_then(|x| x.parse().ok())
.unwrap_or(ContentEncoding::Identity);
Self::new(stream, encoding)
}
}
impl<S> Stream for Decoder<S>
where
S: Stream<Item = Result<Bytes, PayloadError>>,
{
type Item = Result<Bytes, PayloadError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut this = self.project();
loop {
if let Some(ref mut fut) = this.fut {
let (chunk, decoder) = ready!(Pin::new(fut).poll(cx)).map_err(|_| {
PayloadError::Io(io::Error::new(
io::ErrorKind::Other,
"Blocking task was cancelled unexpectedly",
))
})??;
*this.decoder = Some(decoder);
this.fut.take();
if let Some(chunk) = chunk {
return Poll::Ready(Some(Ok(chunk)));
}
}
if *this.eof {
return Poll::Ready(None);
}
match ready!(this.stream.as_mut().poll_next(cx)) {
Some(Err(err)) => return Poll::Ready(Some(Err(err))),
Some(Ok(chunk)) => {
if let Some(mut decoder) = this.decoder.take() {
if chunk.len() < MAX_CHUNK_SIZE_DECODE_IN_PLACE {
let chunk = decoder.feed_data(chunk)?;
*this.decoder = Some(decoder);
if let Some(chunk) = chunk {
return Poll::Ready(Some(Ok(chunk)));
}
} else {
*this.fut = Some(spawn_blocking(move || {
let chunk = decoder.feed_data(chunk)?;
Ok((chunk, decoder))
}));
}
continue;
} else {
return Poll::Ready(Some(Ok(chunk)));
}
}
None => {
*this.eof = true;
return if let Some(mut decoder) = this.decoder.take() {
match decoder.feed_eof() {
Ok(Some(res)) => Poll::Ready(Some(Ok(res))),
Ok(None) => Poll::Ready(None),
Err(err) => Poll::Ready(Some(Err(err.into()))),
}
} else {
Poll::Ready(None)
};
}
}
}
}
}
enum ContentDecoder {
#[cfg(feature = "compress-gzip")]
Deflate(Box<ZlibDecoder<Writer>>),
#[cfg(feature = "compress-gzip")]
Gzip(Box<GzDecoder<Writer>>),
#[cfg(feature = "compress-brotli")]
Brotli(Box<brotli::DecompressorWriter<Writer>>),
// We need explicit 'static lifetime here because ZstdDecoder need lifetime
// argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static`
#[cfg(feature = "compress-zstd")]
Zstd(Box<ZstdDecoder<'static, Writer>>),
}
impl ContentDecoder {
fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
match self {
#[cfg(feature = "compress-brotli")]
ContentDecoder::Brotli(ref mut decoder) => match decoder.flush() {
Ok(()) => {
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
#[cfg(feature = "compress-gzip")]
ContentDecoder::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 = "compress-gzip")]
ContentDecoder::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),
},
#[cfg(feature = "compress-zstd")]
ContentDecoder::Zstd(ref mut decoder) => match decoder.flush() {
Ok(_) => {
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
}
}
fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
match self {
#[cfg(feature = "compress-brotli")]
ContentDecoder::Brotli(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 = "compress-gzip")]
ContentDecoder::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 = "compress-gzip")]
ContentDecoder::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),
},
#[cfg(feature = "compress-zstd")]
ContentDecoder::Zstd(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),
},
}
}
}

View File

@ -0,0 +1,429 @@
//! Stream encoders.
use std::{
error::Error as StdError,
future::Future,
io::{self, Write as _},
pin::Pin,
task::{Context, Poll},
};
use actix_rt::task::{spawn_blocking, JoinHandle};
use bytes::Bytes;
use derive_more::Display;
use futures_core::ready;
use pin_project_lite::pin_project;
#[cfg(feature = "compress-gzip")]
use flate2::write::{GzEncoder, ZlibEncoder};
#[cfg(feature = "compress-zstd")]
use zstd::stream::write::Encoder as ZstdEncoder;
use super::Writer;
use crate::{
body::{self, BodySize, MessageBody},
header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING},
ResponseHead, StatusCode,
};
const MAX_CHUNK_SIZE_ENCODE_IN_PLACE: usize = 1024;
pin_project! {
pub struct Encoder<B> {
#[pin]
body: EncoderBody<B>,
encoder: Option<ContentEncoder>,
fut: Option<JoinHandle<Result<ContentEncoder, io::Error>>>,
eof: bool,
}
}
impl<B: MessageBody> Encoder<B> {
fn none() -> Self {
Encoder {
body: EncoderBody::None {
body: body::None::new(),
},
encoder: None,
fut: None,
eof: true,
}
}
pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, body: B) -> Self {
// no need to compress an empty body
if matches!(body.size(), BodySize::None) {
return Self::none();
}
let should_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|| head.status == StatusCode::SWITCHING_PROTOCOLS
|| head.status == StatusCode::NO_CONTENT
|| encoding == ContentEncoding::Identity);
let body = match body.try_into_bytes() {
Ok(body) => EncoderBody::Full { body },
Err(body) => EncoderBody::Stream { body },
};
if should_encode {
// wrap body only if encoder is feature-enabled
if let Some(enc) = ContentEncoder::select(encoding) {
update_head(encoding, head);
return Encoder {
body,
encoder: Some(enc),
fut: None,
eof: false,
};
}
}
Encoder {
body,
encoder: None,
fut: None,
eof: false,
}
}
}
pin_project! {
#[project = EncoderBodyProj]
enum EncoderBody<B> {
None { body: body::None },
Full { body: Bytes },
Stream { #[pin] body: B },
}
}
impl<B> MessageBody for EncoderBody<B>
where
B: MessageBody,
{
type Error = EncoderError;
#[inline]
fn size(&self) -> BodySize {
match self {
EncoderBody::None { body } => body.size(),
EncoderBody::Full { body } => body.size(),
EncoderBody::Stream { body } => body.size(),
}
}
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
match self.project() {
EncoderBodyProj::None { body } => {
Pin::new(body).poll_next(cx).map_err(|err| match err {})
}
EncoderBodyProj::Full { body } => {
Pin::new(body).poll_next(cx).map_err(|err| match err {})
}
EncoderBodyProj::Stream { body } => body
.poll_next(cx)
.map_err(|err| EncoderError::Body(err.into())),
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self>
where
Self: Sized,
{
match self {
EncoderBody::None { body } => Ok(body.try_into_bytes().unwrap()),
EncoderBody::Full { body } => Ok(body.try_into_bytes().unwrap()),
_ => Err(self),
}
}
}
impl<B> MessageBody for Encoder<B>
where
B: MessageBody,
{
type Error = EncoderError;
#[inline]
fn size(&self) -> BodySize {
if self.encoder.is_some() {
BodySize::Stream
} else {
self.body.size()
}
}
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
let mut this = self.project();
loop {
if *this.eof {
return Poll::Ready(None);
}
if let Some(ref mut fut) = this.fut {
let mut encoder = ready!(Pin::new(fut).poll(cx))
.map_err(|_| {
EncoderError::Io(io::Error::new(
io::ErrorKind::Other,
"Blocking task was cancelled unexpectedly",
))
})?
.map_err(EncoderError::Io)?;
let chunk = encoder.take();
*this.encoder = Some(encoder);
this.fut.take();
if !chunk.is_empty() {
return Poll::Ready(Some(Ok(chunk)));
}
}
let result = ready!(this.body.as_mut().poll_next(cx));
match result {
Some(Err(err)) => return Poll::Ready(Some(Err(err))),
Some(Ok(chunk)) => {
if let Some(mut encoder) = this.encoder.take() {
if chunk.len() < MAX_CHUNK_SIZE_ENCODE_IN_PLACE {
encoder.write(&chunk).map_err(EncoderError::Io)?;
let chunk = encoder.take();
*this.encoder = Some(encoder);
if !chunk.is_empty() {
return Poll::Ready(Some(Ok(chunk)));
}
} else {
*this.fut = Some(spawn_blocking(move || {
encoder.write(&chunk)?;
Ok(encoder)
}));
}
} else {
return Poll::Ready(Some(Ok(chunk)));
}
}
None => {
if let Some(encoder) = this.encoder.take() {
let chunk = encoder.finish().map_err(EncoderError::Io)?;
if chunk.is_empty() {
return Poll::Ready(None);
} else {
*this.eof = true;
return Poll::Ready(Some(Ok(chunk)));
}
} else {
return Poll::Ready(None);
}
}
}
}
}
#[inline]
fn try_into_bytes(mut self) -> Result<Bytes, Self>
where
Self: Sized,
{
if self.encoder.is_some() {
Err(self)
} else {
match self.body.try_into_bytes() {
Ok(body) => Ok(body),
Err(body) => {
self.body = body;
Err(self)
}
}
}
}
}
fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
head.headers_mut()
.insert(header::CONTENT_ENCODING, encoding.to_header_value());
head.headers_mut()
.insert(header::VARY, HeaderValue::from_static("accept-encoding"));
head.no_chunking(false);
}
enum ContentEncoder {
#[cfg(feature = "compress-gzip")]
Deflate(ZlibEncoder<Writer>),
#[cfg(feature = "compress-gzip")]
Gzip(GzEncoder<Writer>),
#[cfg(feature = "compress-brotli")]
Brotli(Box<brotli::CompressorWriter<Writer>>),
// Wwe need explicit 'static lifetime here because ZstdEncoder needs a lifetime argument and we
// use `spawn_blocking` in `Encoder::poll_next` that requires `FnOnce() -> R + Send + 'static`.
#[cfg(feature = "compress-zstd")]
Zstd(ZstdEncoder<'static, Writer>),
}
impl ContentEncoder {
fn select(encoding: ContentEncoding) -> Option<Self> {
match encoding {
#[cfg(feature = "compress-gzip")]
ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new(
Writer::new(),
flate2::Compression::fast(),
))),
#[cfg(feature = "compress-gzip")]
ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new(
Writer::new(),
flate2::Compression::fast(),
))),
#[cfg(feature = "compress-brotli")]
ContentEncoding::Brotli => Some(ContentEncoder::Brotli(new_brotli_compressor())),
#[cfg(feature = "compress-zstd")]
ContentEncoding::Zstd => {
let encoder = ZstdEncoder::new(Writer::new(), 3).ok()?;
Some(ContentEncoder::Zstd(encoder))
}
_ => None,
}
}
#[inline]
pub(crate) fn take(&mut self) -> Bytes {
match *self {
#[cfg(feature = "compress-brotli")]
ContentEncoder::Brotli(ref mut encoder) => encoder.get_mut().take(),
#[cfg(feature = "compress-gzip")]
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
#[cfg(feature = "compress-gzip")]
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
#[cfg(feature = "compress-zstd")]
ContentEncoder::Zstd(ref mut encoder) => encoder.get_mut().take(),
}
}
fn finish(self) -> Result<Bytes, io::Error> {
match self {
#[cfg(feature = "compress-brotli")]
ContentEncoder::Brotli(mut encoder) => match encoder.flush() {
Ok(()) => Ok(encoder.into_inner().buf.freeze()),
Err(err) => Err(err),
},
#[cfg(feature = "compress-gzip")]
ContentEncoder::Gzip(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err),
},
#[cfg(feature = "compress-gzip")]
ContentEncoder::Deflate(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err),
},
#[cfg(feature = "compress-zstd")]
ContentEncoder::Zstd(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err),
},
}
}
fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
match *self {
#[cfg(feature = "compress-brotli")]
ContentEncoder::Brotli(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()),
Err(err) => {
log::trace!("Error decoding br encoding: {}", err);
Err(err)
}
},
#[cfg(feature = "compress-gzip")]
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()),
Err(err) => {
log::trace!("Error decoding gzip encoding: {}", err);
Err(err)
}
},
#[cfg(feature = "compress-gzip")]
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()),
Err(err) => {
log::trace!("Error decoding deflate encoding: {}", err);
Err(err)
}
},
#[cfg(feature = "compress-zstd")]
ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()),
Err(err) => {
log::trace!("Error decoding ztsd encoding: {}", err);
Err(err)
}
},
}
}
}
#[cfg(feature = "compress-brotli")]
fn new_brotli_compressor() -> Box<brotli::CompressorWriter<Writer>> {
Box::new(brotli::CompressorWriter::new(
Writer::new(),
32 * 1024, // 32 KiB buffer
3, // BROTLI_PARAM_QUALITY
22, // BROTLI_PARAM_LGWIN
))
}
#[derive(Debug, Display)]
#[non_exhaustive]
pub enum EncoderError {
/// Wrapped body stream error.
#[display(fmt = "body")]
Body(Box<dyn StdError>),
/// Generic I/O error.
#[display(fmt = "io")]
Io(io::Error),
}
impl StdError for EncoderError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
EncoderError::Body(err) => Some(&**err),
EncoderError::Io(err) => Some(err),
}
}
}
impl From<EncoderError> for crate::Error {
fn from(err: EncoderError) -> Self {
crate::Error::new_encoder().with_cause(err)
}
}

View File

@ -0,0 +1,41 @@
//! Content-Encoding support.
use std::io;
use bytes::{Bytes, BytesMut};
mod decoder;
mod encoder;
pub use self::decoder::Decoder;
pub use self::encoder::Encoder;
/// Special-purpose writer for streaming (de-)compression.
///
/// Pre-allocates 8KiB of capacity.
pub(self) struct Writer {
buf: BytesMut,
}
impl Writer {
fn new() -> Writer {
Writer {
buf: BytesMut::with_capacity(8192),
}
}
fn take(&mut self) -> Bytes {
self.buf.split().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(())
}
}

493
actix-http/src/error.rs Normal file
View File

@ -0,0 +1,493 @@
//! Error and Result module
use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error};
use derive_more::{Display, Error, From};
use http::{uri::InvalidUri, StatusCode};
use crate::{body::BoxBody, Response};
pub use http::Error as HttpError;
pub struct Error {
inner: Box<ErrorInner>,
}
pub(crate) struct ErrorInner {
#[allow(dead_code)]
kind: Kind,
cause: Option<Box<dyn StdError>>,
}
impl Error {
fn new(kind: Kind) -> Self {
Self {
inner: Box::new(ErrorInner { kind, cause: None }),
}
}
pub(crate) fn with_cause(mut self, cause: impl Into<Box<dyn StdError>>) -> Self {
self.inner.cause = Some(cause.into());
self
}
pub(crate) fn new_http() -> Self {
Self::new(Kind::Http)
}
pub(crate) fn new_parse() -> Self {
Self::new(Kind::Parse)
}
pub(crate) fn new_payload() -> Self {
Self::new(Kind::Payload)
}
pub(crate) fn new_body() -> Self {
Self::new(Kind::Body)
}
pub(crate) fn new_send_response() -> Self {
Self::new(Kind::SendResponse)
}
#[allow(unused)] // available for future use
pub(crate) fn new_io() -> Self {
Self::new(Kind::Io)
}
#[allow(unused)] // used in encoder behind feature flag so ignore unused warning
pub(crate) fn new_encoder() -> Self {
Self::new(Kind::Encoder)
}
#[allow(unused)] // used with `ws` feature flag
pub(crate) fn new_ws() -> Self {
Self::new(Kind::Ws)
}
}
impl From<Error> for Response<BoxBody> {
fn from(err: Error) -> Self {
// TODO: more appropriate error status codes, usage assessment needed
let status_code = match err.inner.kind {
Kind::Parse => StatusCode::BAD_REQUEST,
_ => StatusCode::INTERNAL_SERVER_ERROR,
};
Response::new(status_code).set_body(BoxBody::new(err.to_string()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]
pub(crate) enum Kind {
#[display(fmt = "error processing HTTP")]
Http,
#[display(fmt = "error parsing HTTP message")]
Parse,
#[display(fmt = "request payload read error")]
Payload,
#[display(fmt = "response body write error")]
Body,
#[display(fmt = "send response error")]
SendResponse,
#[display(fmt = "error in WebSocket process")]
Ws,
#[display(fmt = "connection error")]
Io,
#[display(fmt = "encoder error")]
Encoder,
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("actix_http::Error")
.field("kind", &self.inner.kind)
.field("cause", &self.inner.cause)
.finish()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.inner.cause.as_ref() {
Some(err) => write!(f, "{}: {}", &self.inner.kind, err),
None => write!(f, "{}", &self.inner.kind),
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.inner.cause.as_ref().map(Box::as_ref)
}
}
impl From<std::convert::Infallible> for Error {
fn from(err: std::convert::Infallible) -> Self {
match err {}
}
}
impl From<HttpError> for Error {
fn from(err: HttpError) -> Self {
Self::new_http().with_cause(err)
}
}
#[cfg(feature = "ws")]
impl From<crate::ws::HandshakeError> for Error {
fn from(err: crate::ws::HandshakeError) -> Self {
Self::new_ws().with_cause(err)
}
}
#[cfg(feature = "ws")]
impl From<crate::ws::ProtocolError> for Error {
fn from(err: crate::ws::ProtocolError) -> Self {
Self::new_ws().with_cause(err)
}
}
/// A set of errors that can occur during parsing HTTP streams.
#[derive(Debug, Display, Error)]
#[non_exhaustive]
pub enum ParseError {
/// An invalid `Method`, such as `GE.T`.
#[display(fmt = "Invalid Method specified")]
Method,
/// An invalid `Uri`, such as `exam ple.domain`.
#[display(fmt = "Uri error: {}", _0)]
Uri(InvalidUri),
/// An invalid `HttpVersion`, such as `HTP/1.1`
#[display(fmt = "Invalid HTTP version specified")]
Version,
/// An invalid `Header`.
#[display(fmt = "Invalid Header provided")]
Header,
/// A message head is too large to be reasonable.
#[display(fmt = "Message head is too large")]
TooLarge,
/// A message reached EOF, but is not complete.
#[display(fmt = "Message is incomplete")]
Incomplete,
/// An invalid `Status`, such as `1337 ELITE`.
#[display(fmt = "Invalid Status provided")]
Status,
/// A timeout occurred waiting for an IO event.
#[allow(dead_code)]
#[display(fmt = "Timeout")]
Timeout,
/// An `io::Error` that occurred while trying to read or write to a network stream.
#[display(fmt = "IO error: {}", _0)]
Io(io::Error),
/// Parsing a field as string failed.
#[display(fmt = "UTF8 error: {}", _0)]
Utf8(Utf8Error),
}
impl From<io::Error> for ParseError {
fn from(err: io::Error) -> ParseError {
ParseError::Io(err)
}
}
impl From<InvalidUri> for ParseError {
fn from(err: InvalidUri) -> ParseError {
ParseError::Uri(err)
}
}
impl From<Utf8Error> for ParseError {
fn from(err: Utf8Error) -> ParseError {
ParseError::Utf8(err)
}
}
impl From<FromUtf8Error> for ParseError {
fn from(err: FromUtf8Error) -> ParseError {
ParseError::Utf8(err.utf8_error())
}
}
impl From<httparse::Error> for ParseError {
fn from(err: httparse::Error) -> ParseError {
match err {
httparse::Error::HeaderName
| httparse::Error::HeaderValue
| httparse::Error::NewLine
| httparse::Error::Token => ParseError::Header,
httparse::Error::Status => ParseError::Status,
httparse::Error::TooManyHeaders => ParseError::TooLarge,
httparse::Error::Version => ParseError::Version,
}
}
}
impl From<ParseError> for Error {
fn from(err: ParseError) -> Self {
Self::new_parse().with_cause(err)
}
}
impl From<ParseError> for Response<BoxBody> {
fn from(err: ParseError) -> Self {
Error::from(err).into()
}
}
/// A set of errors that can occur during payload parsing.
#[derive(Debug, Display)]
#[non_exhaustive]
pub enum PayloadError {
/// A payload reached EOF, but is not complete.
#[display(
fmt = "A payload reached EOF, but is not complete. Inner error: {:?}",
_0
)]
Incomplete(Option<io::Error>),
/// Content encoding stream corruption.
#[display(fmt = "Can not decode content-encoding.")]
EncodingCorrupted,
/// Payload reached size limit.
#[display(fmt = "Payload reached size limit.")]
Overflow,
/// Payload length is unknown.
#[display(fmt = "Payload length is unknown.")]
UnknownLength,
/// HTTP/2 payload error.
#[cfg(feature = "http2")]
#[display(fmt = "{}", _0)]
Http2Payload(::h2::Error),
/// Generic I/O error.
#[display(fmt = "{}", _0)]
Io(io::Error),
}
impl std::error::Error for PayloadError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
PayloadError::Incomplete(None) => None,
PayloadError::Incomplete(Some(err)) => Some(err),
PayloadError::EncodingCorrupted => None,
PayloadError::Overflow => None,
PayloadError::UnknownLength => None,
#[cfg(feature = "http2")]
PayloadError::Http2Payload(err) => Some(err),
PayloadError::Io(err) => Some(err),
}
}
}
#[cfg(feature = "http2")]
impl From<::h2::Error> for PayloadError {
fn from(err: ::h2::Error) -> Self {
PayloadError::Http2Payload(err)
}
}
impl From<Option<io::Error>> for PayloadError {
fn from(err: Option<io::Error>) -> Self {
PayloadError::Incomplete(err)
}
}
impl From<io::Error> for PayloadError {
fn from(err: io::Error) -> Self {
PayloadError::Incomplete(Some(err))
}
}
impl From<PayloadError> for Error {
fn from(err: PayloadError) -> Self {
Self::new_payload().with_cause(err)
}
}
/// A set of errors that can occur during dispatching HTTP requests.
#[derive(Debug, Display, From)]
#[non_exhaustive]
pub enum DispatchError {
/// Service error.
#[display(fmt = "Service Error")]
Service(Response<BoxBody>),
/// Body streaming error.
#[display(fmt = "Body error: {}", _0)]
Body(Box<dyn StdError>),
/// Upgrade service error.
Upgrade,
/// An `io::Error` that occurred while trying to read or write to a network stream.
#[display(fmt = "IO error: {}", _0)]
Io(io::Error),
/// Request parse error.
#[display(fmt = "Request parse error: {}", _0)]
Parse(ParseError),
/// HTTP/2 error.
#[display(fmt = "{}", _0)]
#[cfg(feature = "http2")]
H2(h2::Error),
/// The first request did not complete within the specified timeout.
#[display(fmt = "The first request did not complete within the specified timeout")]
SlowRequestTimeout,
/// Disconnect timeout. Makes sense for ssl streams.
#[display(fmt = "Connection shutdown timeout")]
DisconnectTimeout,
/// Handler dropped payload before reading EOF.
#[display(fmt = "Handler dropped payload before reading EOF")]
HandlerDroppedPayload,
/// Internal error.
#[display(fmt = "Internal error")]
InternalError,
}
impl StdError for DispatchError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
DispatchError::Service(_res) => None,
DispatchError::Body(err) => Some(&**err),
DispatchError::Io(err) => Some(err),
DispatchError::Parse(err) => Some(err),
#[cfg(feature = "http2")]
DispatchError::H2(err) => Some(err),
_ => None,
}
}
}
/// A set of error that can occur during parsing content type.
#[derive(Debug, Display, Error)]
#[cfg_attr(test, derive(PartialEq))]
#[non_exhaustive]
pub enum ContentTypeError {
/// Can not parse content type
#[display(fmt = "Can not parse content type")]
ParseError,
/// Unknown content encoding
#[display(fmt = "Unknown content encoding")]
UnknownEncoding,
}
#[cfg(test)]
mod tests {
use std::io;
use http::{Error as HttpError, StatusCode};
use super::*;
#[test]
fn test_into_response() {
let resp: Response<BoxBody> = ParseError::Incomplete.into();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into();
let resp: Response<BoxBody> = Error::new_http().with_cause(err).into();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn test_as_response() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let err: Error = ParseError::Io(orig).into();
assert_eq!(
format!("{}", err),
"error parsing HTTP message: IO error: other"
);
}
#[test]
fn test_error_display() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let err = Error::new_io().with_cause(orig);
assert_eq!("connection error: other", err.to_string());
}
#[test]
fn test_error_http_response() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let err = Error::new_io().with_cause(orig);
let resp: Response<BoxBody> = err.into();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn test_payload_error() {
let err: PayloadError = io::Error::new(io::ErrorKind::Other, "ParseError").into();
assert!(err.to_string().contains("ParseError"));
let err = PayloadError::Incomplete(None);
assert_eq!(
err.to_string(),
"A payload reached EOF, but is not complete. Inner error: None"
);
}
macro_rules! from {
($from:expr => $error:pat) => {
match ParseError::from($from) {
err @ $error => {
assert!(err.to_string().len() >= 5);
}
err => unreachable!("{:?}", err),
}
};
}
macro_rules! from_and_cause {
($from:expr => $error:pat) => {
match ParseError::from($from) {
e @ $error => {
let desc = format!("{}", e);
assert_eq!(desc, format!("IO error: {}", $from));
}
_ => unreachable!("{:?}", $from),
}
};
}
#[test]
fn test_from() {
from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => ParseError::Io(..));
from!(httparse::Error::HeaderName => ParseError::Header);
from!(httparse::Error::HeaderName => ParseError::Header);
from!(httparse::Error::HeaderValue => ParseError::Header);
from!(httparse::Error::NewLine => ParseError::Header);
from!(httparse::Error::Status => ParseError::Status);
from!(httparse::Error::Token => ParseError::Header);
from!(httparse::Error::TooManyHeaders => ParseError::TooLarge);
from!(httparse::Error::Version => ParseError::Version);
}
}

View File

@ -0,0 +1,280 @@
use std::{
any::{Any, TypeId},
fmt,
};
use ahash::AHashMap;
/// A type map for request extensions.
///
/// All entries into this map must be owned types (or static references).
#[derive(Default)]
pub struct Extensions {
/// Use AHasher with a std HashMap with for faster lookups on the small `TypeId` keys.
map: AHashMap<TypeId, Box<dyn Any>>,
}
impl Extensions {
/// Creates an empty `Extensions`.
#[inline]
pub fn new() -> Extensions {
Extensions {
map: AHashMap::new(),
}
}
/// Insert an item into the map.
///
/// If an item of this type was already stored, it will be replaced and returned.
///
/// ```
/// # use actix_http::Extensions;
/// let mut map = Extensions::new();
/// assert_eq!(map.insert(""), None);
/// assert_eq!(map.insert(1u32), None);
/// assert_eq!(map.insert(2u32), Some(1u32));
/// assert_eq!(*map.get::<u32>().unwrap(), 2u32);
/// ```
pub fn insert<T: 'static>(&mut self, val: T) -> Option<T> {
self.map
.insert(TypeId::of::<T>(), Box::new(val))
.and_then(downcast_owned)
}
/// Check if map contains an item of a given type.
///
/// ```
/// # use actix_http::Extensions;
/// let mut map = Extensions::new();
/// assert!(!map.contains::<u32>());
///
/// assert_eq!(map.insert(1u32), None);
/// assert!(map.contains::<u32>());
/// ```
pub fn contains<T: 'static>(&self) -> bool {
self.map.contains_key(&TypeId::of::<T>())
}
/// Get a reference to an item of a given type.
///
/// ```
/// # use actix_http::Extensions;
/// let mut map = Extensions::new();
/// map.insert(1u32);
/// assert_eq!(map.get::<u32>(), Some(&1u32));
/// ```
pub fn get<T: 'static>(&self) -> Option<&T> {
self.map
.get(&TypeId::of::<T>())
.and_then(|boxed| boxed.downcast_ref())
}
/// Get a mutable reference to an item of a given type.
///
/// ```
/// # use actix_http::Extensions;
/// let mut map = Extensions::new();
/// map.insert(1u32);
/// assert_eq!(map.get_mut::<u32>(), Some(&mut 1u32));
/// ```
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.map
.get_mut(&TypeId::of::<T>())
.and_then(|boxed| boxed.downcast_mut())
}
/// Remove an item from the map of a given type.
///
/// If an item of this type was already stored, it will be returned.
///
/// ```
/// # use actix_http::Extensions;
/// let mut map = Extensions::new();
///
/// map.insert(1u32);
/// assert_eq!(map.get::<u32>(), Some(&1u32));
///
/// assert_eq!(map.remove::<u32>(), Some(1u32));
/// assert!(!map.contains::<u32>());
/// ```
pub fn remove<T: 'static>(&mut self) -> Option<T> {
self.map.remove(&TypeId::of::<T>()).and_then(downcast_owned)
}
/// Clear the `Extensions` of all inserted extensions.
///
/// ```
/// # use actix_http::Extensions;
/// let mut map = Extensions::new();
///
/// map.insert(1u32);
/// assert!(map.contains::<u32>());
///
/// map.clear();
/// assert!(!map.contains::<u32>());
/// ```
#[inline]
pub fn clear(&mut self) {
self.map.clear();
}
/// Extends self with the items from another `Extensions`.
pub fn extend(&mut self, other: Extensions) {
self.map.extend(other.map);
}
}
impl fmt::Debug for Extensions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Extensions").finish()
}
}
fn downcast_owned<T: 'static>(boxed: Box<dyn Any>) -> Option<T> {
boxed.downcast().ok().map(|boxed| *boxed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_remove() {
let mut map = Extensions::new();
map.insert::<i8>(123);
assert!(map.get::<i8>().is_some());
map.remove::<i8>();
assert!(map.get::<i8>().is_none());
}
#[test]
fn test_clear() {
let mut map = Extensions::new();
map.insert::<i8>(8);
map.insert::<i16>(16);
map.insert::<i32>(32);
assert!(map.contains::<i8>());
assert!(map.contains::<i16>());
assert!(map.contains::<i32>());
map.clear();
assert!(!map.contains::<i8>());
assert!(!map.contains::<i16>());
assert!(!map.contains::<i32>());
map.insert::<i8>(10);
assert_eq!(*map.get::<i8>().unwrap(), 10);
}
#[test]
fn test_integers() {
static A: u32 = 8;
let mut map = Extensions::new();
map.insert::<i8>(8);
map.insert::<i16>(16);
map.insert::<i32>(32);
map.insert::<i64>(64);
map.insert::<i128>(128);
map.insert::<u8>(8);
map.insert::<u16>(16);
map.insert::<u32>(32);
map.insert::<u64>(64);
map.insert::<u128>(128);
map.insert::<&'static u32>(&A);
assert!(map.get::<i8>().is_some());
assert!(map.get::<i16>().is_some());
assert!(map.get::<i32>().is_some());
assert!(map.get::<i64>().is_some());
assert!(map.get::<i128>().is_some());
assert!(map.get::<u8>().is_some());
assert!(map.get::<u16>().is_some());
assert!(map.get::<u32>().is_some());
assert!(map.get::<u64>().is_some());
assert!(map.get::<u128>().is_some());
assert!(map.get::<&'static u32>().is_some());
}
#[test]
fn test_composition() {
struct Magi<T>(pub T);
struct Madoka {
pub god: bool,
}
struct Homura {
pub attempts: usize,
}
struct Mami {
pub guns: usize,
}
let mut map = Extensions::new();
map.insert(Magi(Madoka { god: false }));
map.insert(Magi(Homura { attempts: 0 }));
map.insert(Magi(Mami { guns: 999 }));
assert!(!map.get::<Magi<Madoka>>().unwrap().0.god);
assert_eq!(0, map.get::<Magi<Homura>>().unwrap().0.attempts);
assert_eq!(999, map.get::<Magi<Mami>>().unwrap().0.guns);
}
#[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)));
}
#[test]
fn test_extend() {
#[derive(Debug, PartialEq)]
struct MyType(i32);
let mut extensions = Extensions::new();
extensions.insert(5i32);
extensions.insert(MyType(10));
let mut other = Extensions::new();
other.insert(15i32);
other.insert(20u8);
extensions.extend(other);
assert_eq!(extensions.get(), Some(&15i32));
assert_eq!(extensions.get_mut(), Some(&mut 15i32));
assert_eq!(extensions.remove::<i32>(), Some(15i32));
assert!(extensions.get::<i32>().is_none());
assert_eq!(extensions.get::<bool>(), None);
assert_eq!(extensions.get(), Some(&MyType(10)));
assert_eq!(extensions.get(), Some(&20u8));
assert_eq!(extensions.get_mut(), Some(&mut 20u8));
}
}

View File

@ -0,0 +1,426 @@
use std::{io, task::Poll};
use bytes::{Buf as _, Bytes, BytesMut};
macro_rules! byte (
($rdr:ident) => ({
if $rdr.len() > 0 {
let b = $rdr[0];
$rdr.advance(1);
b
} else {
return Poll::Pending
}
})
);
#[derive(Debug, PartialEq, Clone)]
pub(super) enum ChunkedState {
Size,
SizeLws,
Extension,
SizeLf,
Body,
BodyCr,
BodyLf,
EndCr,
EndLf,
End,
}
impl ChunkedState {
pub(super) fn step(
&self,
body: &mut BytesMut,
size: &mut u64,
buf: &mut Option<Bytes>,
) -> Poll<Result<ChunkedState, io::Error>> {
use self::ChunkedState::*;
match *self {
Size => ChunkedState::read_size(body, size),
SizeLws => ChunkedState::read_size_lws(body),
Extension => ChunkedState::read_extension(body),
SizeLf => ChunkedState::read_size_lf(body, *size),
Body => ChunkedState::read_body(body, size, buf),
BodyCr => ChunkedState::read_body_cr(body),
BodyLf => ChunkedState::read_body_lf(body),
EndCr => ChunkedState::read_end_cr(body),
EndLf => ChunkedState::read_end_lf(body),
End => Poll::Ready(Ok(ChunkedState::End)),
}
}
fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll<Result<ChunkedState, io::Error>> {
let radix = 16;
let rem = match byte!(rdr) {
b @ b'0'..=b'9' => b - b'0',
b @ b'a'..=b'f' => b + 10 - b'a',
b @ b'A'..=b'F' => b + 10 - b'A',
b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)),
b';' => return Poll::Ready(Ok(ChunkedState::Extension)),
b'\r' => return Poll::Ready(Ok(ChunkedState::SizeLf)),
_ => {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk size line: Invalid Size",
)));
}
};
match size.checked_mul(radix) {
Some(n) => {
*size = n as u64;
*size += rem as u64;
Poll::Ready(Ok(ChunkedState::Size))
}
None => {
log::debug!("chunk size would overflow u64");
Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk size line: Size is too big",
)))
}
}
}
fn read_size_lws(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) {
// LWS can follow the chunk size, but no more digits can come
b'\t' | b' ' => Poll::Ready(Ok(ChunkedState::SizeLws)),
b';' => Poll::Ready(Ok(ChunkedState::Extension)),
b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
_ => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk size linear white space",
))),
}
}
fn read_extension(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) {
b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
// strictly 0x20 (space) should be disallowed but we don't parse quoted strings here
0x00..=0x08 | 0x0a..=0x1f | 0x7f => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid character in chunk extension",
))),
_ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions
}
}
fn read_size_lf(rdr: &mut BytesMut, size: u64) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) {
b'\n' if size > 0 => Poll::Ready(Ok(ChunkedState::Body)),
b'\n' if size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)),
_ => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk size LF",
))),
}
}
fn read_body(
rdr: &mut BytesMut,
rem: &mut u64,
buf: &mut Option<Bytes>,
) -> Poll<Result<ChunkedState, io::Error>> {
log::trace!("Chunked read, remaining={:?}", rem);
let len = rdr.len() as u64;
if len == 0 {
Poll::Ready(Ok(ChunkedState::Body))
} else {
let slice;
if *rem > len {
slice = rdr.split().freeze();
*rem -= len;
} else {
slice = rdr.split_to(*rem as usize).freeze();
*rem = 0;
}
*buf = Some(slice);
if *rem > 0 {
Poll::Ready(Ok(ChunkedState::Body))
} else {
Poll::Ready(Ok(ChunkedState::BodyCr))
}
}
}
fn read_body_cr(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) {
b'\r' => Poll::Ready(Ok(ChunkedState::BodyLf)),
_ => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk body CR",
))),
}
}
fn read_body_lf(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) {
b'\n' => Poll::Ready(Ok(ChunkedState::Size)),
_ => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk body LF",
))),
}
}
fn read_end_cr(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) {
b'\r' => Poll::Ready(Ok(ChunkedState::EndLf)),
_ => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk end CR",
))),
}
}
fn read_end_lf(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) {
b'\n' => Poll::Ready(Ok(ChunkedState::End)),
_ => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk end LF",
))),
}
}
}
#[cfg(test)]
mod tests {
use actix_codec::Decoder as _;
use bytes::{Bytes, BytesMut};
use http::Method;
use crate::{
error::ParseError,
h1::decoder::{MessageDecoder, PayloadItem},
HttpMessage as _, Request,
};
macro_rules! parse_ready {
($e:expr) => {{
match MessageDecoder::<Request>::default().decode($e) {
Ok(Some((msg, _))) => msg,
Ok(_) => unreachable!("Eof during parsing http request"),
Err(err) => unreachable!("Error during parsing http request: {:?}", err),
}
}};
}
macro_rules! expect_parse_err {
($e:expr) => {{
match MessageDecoder::<Request>::default().decode($e) {
Err(err) => match err {
ParseError::Io(_) => unreachable!("Parse error expected"),
_ => {}
},
_ => unreachable!("Error expected"),
}
}};
}
#[test]
fn test_parse_chunked_payload_chunk_extension() {
let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\
\r\n",
);
let mut reader = MessageDecoder::<Request>::default();
let (msg, pl) = reader.decode(&mut buf).unwrap().unwrap();
let mut pl = pl.unwrap();
assert!(msg.chunked().unwrap());
buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n")
let chunk = pl.decode(&mut buf).unwrap().unwrap().chunk();
assert_eq!(chunk, Bytes::from_static(b"data"));
let chunk = pl.decode(&mut buf).unwrap().unwrap().chunk();
assert_eq!(chunk, Bytes::from_static(b"line"));
let msg = pl.decode(&mut buf).unwrap().unwrap();
assert!(msg.eof());
}
#[test]
fn test_request_chunked() {
let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n",
);
let req = parse_ready!(&mut buf);
if let Ok(val) = req.chunked() {
assert!(val);
} else {
unreachable!("Error");
}
// intentional typo in "chunked"
let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\
transfer-encoding: chnked\r\n\r\n",
);
expect_parse_err!(&mut buf);
}
#[test]
fn test_http_request_chunked_payload() {
let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n",
);
let mut reader = MessageDecoder::<Request>::default();
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
let mut pl = pl.unwrap();
assert!(req.chunked().unwrap());
buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n");
assert_eq!(
pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(),
b"data"
);
assert_eq!(
pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(),
b"line"
);
assert!(pl.decode(&mut buf).unwrap().unwrap().eof());
}
#[test]
fn test_http_request_chunked_payload_and_next_message() {
let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n",
);
let mut reader = MessageDecoder::<Request>::default();
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
let mut pl = pl.unwrap();
assert!(req.chunked().unwrap());
buf.extend(
b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\
POST /test2 HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n"
.iter(),
);
let msg = pl.decode(&mut buf).unwrap().unwrap();
assert_eq!(msg.chunk().as_ref(), b"data");
let msg = pl.decode(&mut buf).unwrap().unwrap();
assert_eq!(msg.chunk().as_ref(), b"line");
let msg = pl.decode(&mut buf).unwrap().unwrap();
assert!(msg.eof());
let (req, _) = reader.decode(&mut buf).unwrap().unwrap();
assert!(req.chunked().unwrap());
assert_eq!(*req.method(), Method::POST);
assert!(req.chunked().unwrap());
}
#[test]
fn test_http_request_chunked_payload_chunks() {
let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n",
);
let mut reader = MessageDecoder::<Request>::default();
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
let mut pl = pl.unwrap();
assert!(req.chunked().unwrap());
buf.extend(b"4\r\n1111\r\n");
let msg = pl.decode(&mut buf).unwrap().unwrap();
assert_eq!(msg.chunk().as_ref(), b"1111");
buf.extend(b"4\r\ndata\r");
let msg = pl.decode(&mut buf).unwrap().unwrap();
assert_eq!(msg.chunk().as_ref(), b"data");
buf.extend(b"\n4");
assert!(pl.decode(&mut buf).unwrap().is_none());
buf.extend(b"\r");
assert!(pl.decode(&mut buf).unwrap().is_none());
buf.extend(b"\n");
assert!(pl.decode(&mut buf).unwrap().is_none());
buf.extend(b"li");
let msg = pl.decode(&mut buf).unwrap().unwrap();
assert_eq!(msg.chunk().as_ref(), b"li");
//trailers
//buf.feed_data("test: test\r\n");
//not_ready!(reader.parse(&mut buf, &mut readbuf));
buf.extend(b"ne\r\n0\r\n");
let msg = pl.decode(&mut buf).unwrap().unwrap();
assert_eq!(msg.chunk().as_ref(), b"ne");
assert!(pl.decode(&mut buf).unwrap().is_none());
buf.extend(b"\r\n");
assert!(pl.decode(&mut buf).unwrap().unwrap().eof());
}
#[test]
fn chunk_extension_quoted() {
let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Transfer-Encoding: chunked\r\n\
\r\n\
2;hello=b;one=\"1 2 3\"\r\n\
xx",
);
let mut reader = MessageDecoder::<Request>::default();
let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap();
let mut pl = pl.unwrap();
let chunk = pl.decode(&mut buf).unwrap().unwrap();
assert_eq!(chunk, PayloadItem::Chunk(Bytes::from_static(b"xx")));
}
#[test]
fn hrs_chunk_extension_invalid() {
let mut buf = BytesMut::from(
"GET / HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Transfer-Encoding: chunked\r\n\
\r\n\
2;x\nx\r\n\
4c\r\n\
0\r\n",
);
let mut reader = MessageDecoder::<Request>::default();
let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap();
let mut pl = pl.unwrap();
let err = pl.decode(&mut buf).unwrap_err();
assert!(err
.to_string()
.contains("Invalid character in chunk extension"));
}
#[test]
fn hrs_chunk_size_overflow() {
let mut buf = BytesMut::from(
"GET / HTTP/1.1\r\n\
Host: example.com\r\n\
Transfer-Encoding: chunked\r\n\
\r\n\
f0000000000000003\r\n\
abc\r\n\
0\r\n",
);
let mut reader = MessageDecoder::<Request>::default();
let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap();
let mut pl = pl.unwrap();
let err = pl.decode(&mut buf).unwrap_err();
assert!(err
.to_string()
.contains("Invalid chunk size line: Size is too big"));
}
}

239
actix-http/src/h1/client.rs Normal file
View File

@ -0,0 +1,239 @@
use std::{fmt, io};
use actix_codec::{Decoder, Encoder};
use bitflags::bitflags;
use bytes::{Bytes, BytesMut};
use http::{Method, Version};
use super::{
decoder::{self, PayloadDecoder, PayloadItem, PayloadType},
encoder, reserve_readbuf, Message, MessageType,
};
use crate::{
body::BodySize,
error::{ParseError, PayloadError},
ConnectionType, RequestHeadType, ResponseHead, ServiceConfig,
};
bitflags! {
struct Flags: u8 {
const HEAD = 0b0000_0001;
const KEEP_ALIVE_ENABLED = 0b0000_1000;
const STREAM = 0b0001_0000;
}
}
/// HTTP/1 Codec
pub struct ClientCodec {
inner: ClientCodecInner,
}
/// HTTP/1 Payload Codec
pub struct ClientPayloadCodec {
inner: ClientCodecInner,
}
struct ClientCodecInner {
config: ServiceConfig,
decoder: decoder::MessageDecoder<ResponseHead>,
payload: Option<PayloadDecoder>,
version: Version,
conn_type: ConnectionType,
// encoder part
flags: Flags,
encoder: encoder::MessageEncoder<RequestHeadType>,
}
impl Default for ClientCodec {
fn default() -> Self {
ClientCodec::new(ServiceConfig::default())
}
}
impl fmt::Debug for ClientCodec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("h1::ClientCodec")
.field("flags", &self.inner.flags)
.finish_non_exhaustive()
}
}
impl ClientCodec {
/// Create HTTP/1 codec.
///
/// `keepalive_enabled` how response `connection` header get generated.
pub fn new(config: ServiceConfig) -> Self {
let flags = if config.keep_alive().enabled() {
Flags::KEEP_ALIVE_ENABLED
} else {
Flags::empty()
};
ClientCodec {
inner: ClientCodecInner {
config,
decoder: decoder::MessageDecoder::default(),
payload: None,
version: Version::HTTP_11,
conn_type: ConnectionType::Close,
flags,
encoder: encoder::MessageEncoder::default(),
},
}
}
/// Check if request is upgrade
pub fn upgrade(&self) -> bool {
self.inner.conn_type == ConnectionType::Upgrade
}
/// Check if last response is keep-alive
pub fn keep_alive(&self) -> bool {
self.inner.conn_type == ConnectionType::KeepAlive
}
/// Check last request's message type
pub fn message_type(&self) -> MessageType {
if self.inner.flags.contains(Flags::STREAM) {
MessageType::Stream
} else if self.inner.payload.is_none() {
MessageType::None
} else {
MessageType::Payload
}
}
/// Convert message codec to a payload codec
pub fn into_payload_codec(self) -> ClientPayloadCodec {
ClientPayloadCodec { inner: self.inner }
}
}
impl ClientPayloadCodec {
/// Check if last response is keep-alive
pub fn keep_alive(&self) -> bool {
self.inner.conn_type == ConnectionType::KeepAlive
}
/// Transform payload codec to a message codec
pub fn into_message_codec(self) -> ClientCodec {
ClientCodec { inner: self.inner }
}
}
impl Decoder for ClientCodec {
type Item = ResponseHead;
type Error = ParseError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
debug_assert!(
self.inner.payload.is_none(),
"Payload decoder should not be set"
);
if let Some((req, payload)) = self.inner.decoder.decode(src)? {
if let Some(conn_type) = req.conn_type() {
// do not use peer's keep-alive
self.inner.conn_type = if conn_type == ConnectionType::KeepAlive {
self.inner.conn_type
} else {
conn_type
};
}
if !self.inner.flags.contains(Flags::HEAD) {
match payload {
PayloadType::None => self.inner.payload = None,
PayloadType::Payload(pl) => self.inner.payload = Some(pl),
PayloadType::Stream(pl) => {
self.inner.payload = Some(pl);
self.inner.flags.insert(Flags::STREAM);
}
}
} else {
self.inner.payload = None;
}
reserve_readbuf(src);
Ok(Some(req))
} else {
Ok(None)
}
}
}
impl Decoder for ClientPayloadCodec {
type Item = Option<Bytes>;
type Error = PayloadError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
debug_assert!(
self.inner.payload.is_some(),
"Payload decoder is not specified"
);
Ok(match self.inner.payload.as_mut().unwrap().decode(src)? {
Some(PayloadItem::Chunk(chunk)) => {
reserve_readbuf(src);
Some(Some(chunk))
}
Some(PayloadItem::Eof) => {
self.inner.payload.take();
Some(None)
}
None => None,
})
}
}
impl Encoder<Message<(RequestHeadType, BodySize)>> for ClientCodec {
type Error = io::Error;
fn encode(
&mut self,
item: Message<(RequestHeadType, BodySize)>,
dst: &mut BytesMut,
) -> Result<(), Self::Error> {
match item {
Message::Item((mut head, length)) => {
let inner = &mut self.inner;
inner.version = head.as_ref().version;
inner
.flags
.set(Flags::HEAD, head.as_ref().method == Method::HEAD);
// connection status
inner.conn_type = match head.as_ref().connection_type() {
ConnectionType::KeepAlive => {
if inner.flags.contains(Flags::KEEP_ALIVE_ENABLED) {
ConnectionType::KeepAlive
} else {
ConnectionType::Close
}
}
ConnectionType::Upgrade => ConnectionType::Upgrade,
ConnectionType::Close => ConnectionType::Close,
};
inner.encoder.encode(
dst,
&mut head,
false,
false,
inner.version,
length,
inner.conn_type,
&inner.config,
)?;
}
Message::Chunk(Some(bytes)) => {
self.inner.encoder.encode_chunk(bytes.as_ref(), dst)?;
}
Message::Chunk(None) => {
self.inner.encoder.encode_eof(dst)?;
}
}
Ok(())
}
}

244
actix-http/src/h1/codec.rs Normal file
View File

@ -0,0 +1,244 @@
use std::{fmt, io};
use actix_codec::{Decoder, Encoder};
use bitflags::bitflags;
use bytes::BytesMut;
use http::{Method, Version};
use super::{
decoder::{self, PayloadDecoder, PayloadItem, PayloadType},
encoder, Message, MessageType,
};
use crate::{
body::BodySize, error::ParseError, ConnectionType, Request, Response, ServiceConfig,
};
bitflags! {
struct Flags: u8 {
const HEAD = 0b0000_0001;
const KEEP_ALIVE_ENABLED = 0b0000_0010;
const STREAM = 0b0000_0100;
}
}
/// HTTP/1 Codec
pub struct Codec {
config: ServiceConfig,
decoder: decoder::MessageDecoder<Request>,
payload: Option<PayloadDecoder>,
version: Version,
conn_type: ConnectionType,
// encoder part
flags: Flags,
encoder: encoder::MessageEncoder<Response<()>>,
}
impl Default for Codec {
fn default() -> Self {
Codec::new(ServiceConfig::default())
}
}
impl fmt::Debug for Codec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("h1::Codec")
.field("flags", &self.flags)
.finish_non_exhaustive()
}
}
impl Codec {
/// Create HTTP/1 codec.
///
/// `keepalive_enabled` how response `connection` header get generated.
pub fn new(config: ServiceConfig) -> Self {
let flags = if config.keep_alive().enabled() {
Flags::KEEP_ALIVE_ENABLED
} else {
Flags::empty()
};
Codec {
config,
flags,
decoder: decoder::MessageDecoder::default(),
payload: None,
version: Version::HTTP_11,
conn_type: ConnectionType::Close,
encoder: encoder::MessageEncoder::default(),
}
}
/// Check if request is upgrade.
#[inline]
pub fn upgrade(&self) -> bool {
self.conn_type == ConnectionType::Upgrade
}
/// Check if last response is keep-alive.
#[inline]
pub fn keep_alive(&self) -> bool {
self.conn_type == ConnectionType::KeepAlive
}
/// Check if keep-alive enabled on server level.
#[inline]
pub fn keep_alive_enabled(&self) -> bool {
self.flags.contains(Flags::KEEP_ALIVE_ENABLED)
}
/// Check last request's message type.
#[inline]
pub fn message_type(&self) -> MessageType {
if self.flags.contains(Flags::STREAM) {
MessageType::Stream
} else if self.payload.is_none() {
MessageType::None
} else {
MessageType::Payload
}
}
#[inline]
pub fn config(&self) -> &ServiceConfig {
&self.config
}
}
impl Decoder for Codec {
type Item = Message<Request>;
type Error = ParseError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if let Some(ref mut payload) = self.payload {
Ok(match payload.decode(src)? {
Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))),
Some(PayloadItem::Eof) => {
self.payload.take();
Some(Message::Chunk(None))
}
None => None,
})
} else if let Some((req, payload)) = self.decoder.decode(src)? {
let head = req.head();
self.flags.set(Flags::HEAD, head.method == Method::HEAD);
self.version = head.version;
self.conn_type = head.connection_type();
if self.conn_type == ConnectionType::KeepAlive
&& !self.flags.contains(Flags::KEEP_ALIVE_ENABLED)
{
self.conn_type = ConnectionType::Close
}
match payload {
PayloadType::None => self.payload = None,
PayloadType::Payload(pl) => self.payload = Some(pl),
PayloadType::Stream(pl) => {
self.payload = Some(pl);
self.flags.insert(Flags::STREAM);
}
}
Ok(Some(Message::Item(req)))
} else {
Ok(None)
}
}
}
impl Encoder<Message<(Response<()>, BodySize)>> for Codec {
type Error = io::Error;
fn encode(
&mut self,
item: Message<(Response<()>, BodySize)>,
dst: &mut BytesMut,
) -> Result<(), Self::Error> {
match item {
Message::Item((mut res, length)) => {
// set response version
res.head_mut().version = self.version;
// connection status
self.conn_type = if let Some(ct) = res.head().conn_type() {
if ct == ConnectionType::KeepAlive {
self.conn_type
} else {
ct
}
} else {
self.conn_type
};
// encode message
self.encoder.encode(
dst,
&mut res,
self.flags.contains(Flags::HEAD),
self.flags.contains(Flags::STREAM),
self.version,
length,
self.conn_type,
&self.config,
)?;
}
Message::Chunk(Some(bytes)) => {
self.encoder.encode_chunk(bytes.as_ref(), dst)?;
}
Message::Chunk(None) => {
self.encoder.encode_eof(dst)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use bytes::BytesMut;
use http::Method;
use super::*;
use crate::HttpMessage as _;
#[actix_rt::test]
async fn test_http_request_chunked_payload_and_next_message() {
let mut codec = Codec::default();
let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n",
);
let item = codec.decode(&mut buf).unwrap().unwrap();
let req = item.message();
assert_eq!(req.method(), Method::GET);
assert!(req.chunked().unwrap());
buf.extend(
b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\
POST /test2 HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n"
.iter(),
);
let msg = codec.decode(&mut buf).unwrap().unwrap();
assert_eq!(msg.chunk().as_ref(), b"data");
let msg = codec.decode(&mut buf).unwrap().unwrap();
assert_eq!(msg.chunk().as_ref(), b"line");
let msg = codec.decode(&mut buf).unwrap().unwrap();
assert!(msg.eof());
// decode next message
let item = codec.decode(&mut buf).unwrap().unwrap();
let req = item.message();
assert_eq!(*req.method(), Method::POST);
assert!(req.chunked().unwrap());
}
}

1041
actix-http/src/h1/decoder.rs Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,973 @@
use std::{future::Future, str, task::Poll, time::Duration};
use actix_rt::{pin, time::sleep};
use actix_service::fn_service;
use actix_utils::future::{ready, Ready};
use bytes::Bytes;
use futures_util::future::lazy;
use actix_codec::Framed;
use actix_service::Service;
use bytes::{Buf, BytesMut};
use super::dispatcher::{Dispatcher, DispatcherState, DispatcherStateProj, Flags};
use crate::{
body::MessageBody,
config::ServiceConfig,
h1::{Codec, ExpectHandler, UpgradeHandler},
service::HttpFlow,
test::{TestBuffer, TestSeqBuffer},
Error, HttpMessage, KeepAlive, Method, OnConnectData, Request, Response, StatusCode,
};
fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option<usize> {
memchr::memmem::find(&haystack[from..], needle)
}
fn stabilize_date_header(payload: &mut [u8]) {
let mut from = 0;
while let Some(pos) = find_slice(payload, b"date", from) {
payload[(from + pos)..(from + pos + 35)]
.copy_from_slice(b"date: Thu, 01 Jan 1970 12:34:56 UTC");
from += 35;
}
}
fn ok_service() -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
status_service(StatusCode::OK)
}
fn status_service(
status: StatusCode,
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
fn_service(move |_req: Request| ready(Ok::<_, Error>(Response::new(status))))
}
fn echo_path_service(
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
fn_service(|req: Request| {
let path = req.path().as_bytes();
ready(Ok::<_, Error>(
Response::ok().set_body(Bytes::copy_from_slice(path)),
))
})
}
fn drop_payload_service(
) -> impl Service<Request, Response = Response<&'static str>, Error = Error> {
fn_service(|mut req: Request| async move {
let _ = req.take_payload();
Ok::<_, Error>(Response::with_body(StatusCode::OK, "payload dropped"))
})
}
fn echo_payload_service() -> impl Service<Request, Response = Response<Bytes>, Error = Error> {
fn_service(|mut req: Request| {
Box::pin(async move {
use futures_util::stream::StreamExt as _;
let mut pl = req.take_payload();
let mut body = BytesMut::new();
while let Some(chunk) = pl.next().await {
body.extend_from_slice(chunk.unwrap().chunk())
}
Ok::<_, Error>(Response::ok().set_body(body.freeze()))
})
})
}
#[actix_rt::test]
async fn late_request() {
let mut buf = TestBuffer::empty();
let cfg = ServiceConfig::new(
KeepAlive::Disabled,
Duration::from_millis(100),
Duration::ZERO,
false,
None,
);
let services = HttpFlow::new(ok_service(), ExpectHandler, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
services,
cfg,
None,
OnConnectData::default(),
);
pin!(h1);
lazy(|cx| {
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
match h1.as_mut().poll(cx) {
Poll::Ready(_) => panic!("first poll should not be ready"),
Poll::Pending => {}
}
// polls: initial
assert_eq!(h1.poll_count, 1);
buf.extend_read_buf("GET /abcd HTTP/1.1\r\nConnection: close\r\n\r\n");
match h1.as_mut().poll(cx) {
Poll::Pending => panic!("second poll should not be pending"),
Poll::Ready(res) => assert!(res.is_ok()),
}
// polls: initial pending => handle req => shutdown
assert_eq!(h1.poll_count, 3);
let mut res = buf.take_write_buf().to_vec();
stabilize_date_header(&mut res);
let res = &res[..];
let exp = b"\
HTTP/1.1 200 OK\r\n\
content-length: 0\r\n\
connection: close\r\n\
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
";
assert_eq!(
res,
exp,
"\nexpected response not in write buffer:\n\
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(exp)
);
})
.await;
}
#[actix_rt::test]
async fn oneshot_connection() {
let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n");
let cfg = ServiceConfig::new(
KeepAlive::Disabled,
Duration::from_millis(100),
Duration::ZERO,
false,
None,
);
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
services,
cfg,
None,
OnConnectData::default(),
);
pin!(h1);
lazy(|cx| {
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
match h1.as_mut().poll(cx) {
Poll::Pending => panic!("first poll should not be pending"),
Poll::Ready(res) => assert!(res.is_ok()),
}
// polls: initial => shutdown
assert_eq!(h1.poll_count, 2);
let mut res = buf.take_write_buf().to_vec();
stabilize_date_header(&mut res);
let res = &res[..];
let exp = http_msg(
r"
HTTP/1.1 200 OK
content-length: 5
connection: close
date: Thu, 01 Jan 1970 12:34:56 UTC
/abcd
",
);
assert_eq!(
res,
exp,
"\nexpected response not in write buffer:\n\
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(&exp)
);
})
.await;
}
#[actix_rt::test]
async fn keep_alive_timeout() {
let buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n");
let cfg = ServiceConfig::new(
KeepAlive::Timeout(Duration::from_millis(200)),
Duration::from_millis(100),
Duration::ZERO,
false,
None,
);
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
services,
cfg,
None,
OnConnectData::default(),
);
pin!(h1);
lazy(|cx| {
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
assert!(
h1.as_mut().poll(cx).is_pending(),
"keep-alive should prevent poll from resolving"
);
// polls: initial
assert_eq!(h1.poll_count, 1);
let mut res = buf.take_write_buf().to_vec();
stabilize_date_header(&mut res);
let res = &res[..];
let exp = b"\
HTTP/1.1 200 OK\r\n\
content-length: 5\r\n\
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
/abcd\
";
assert_eq!(
res,
exp,
"\nexpected response not in write buffer:\n\
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(exp)
);
})
.await;
// sleep slightly longer than keep-alive timeout
sleep(Duration::from_millis(250)).await;
lazy(|cx| {
assert!(
h1.as_mut().poll(cx).is_ready(),
"keep-alive should have resolved",
);
// polls: initial => keep-alive wake-up shutdown
assert_eq!(h1.poll_count, 2);
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() {
// connection closed
assert!(inner.flags.contains(Flags::SHUTDOWN));
assert!(inner.flags.contains(Flags::WRITE_DISCONNECT));
// and nothing added to write buffer
assert!(buf.write_buf_slice().is_empty());
}
})
.await;
}
#[actix_rt::test]
async fn keep_alive_follow_up_req() {
let mut buf = TestBuffer::new("GET /abcd HTTP/1.1\r\n\r\n");
let cfg = ServiceConfig::new(
KeepAlive::Timeout(Duration::from_millis(500)),
Duration::from_millis(100),
Duration::ZERO,
false,
None,
);
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
services,
cfg,
None,
OnConnectData::default(),
);
pin!(h1);
lazy(|cx| {
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
assert!(
h1.as_mut().poll(cx).is_pending(),
"keep-alive should prevent poll from resolving"
);
// polls: initial
assert_eq!(h1.poll_count, 1);
let mut res = buf.take_write_buf().to_vec();
stabilize_date_header(&mut res);
let res = &res[..];
let exp = b"\
HTTP/1.1 200 OK\r\n\
content-length: 5\r\n\
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
/abcd\
";
assert_eq!(
res,
exp,
"\nexpected response not in write buffer:\n\
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(exp)
);
})
.await;
// sleep for less than KA timeout
sleep(Duration::from_millis(100)).await;
lazy(|cx| {
assert!(
h1.as_mut().poll(cx).is_pending(),
"keep-alive should not have resolved dispatcher yet",
);
// polls: initial => manual
assert_eq!(h1.poll_count, 2);
if let DispatcherStateProj::Normal { inner } = h1.as_mut().project().inner.project() {
// connection not closed
assert!(!inner.flags.contains(Flags::SHUTDOWN));
assert!(!inner.flags.contains(Flags::WRITE_DISCONNECT));
// and nothing added to write buffer
assert!(buf.write_buf_slice().is_empty());
}
})
.await;
lazy(|cx| {
buf.extend_read_buf(
"\
GET /efg HTTP/1.1\r\n\
Connection: close\r\n\
\r\n\r\n",
);
assert!(
h1.as_mut().poll(cx).is_ready(),
"connection close header should override keep-alive setting",
);
// polls: initial => manual => follow-up req => shutdown
assert_eq!(h1.poll_count, 4);
if let DispatcherStateProj::Normal { inner } = h1.as_mut().project().inner.project() {
// connection closed
assert!(inner.flags.contains(Flags::SHUTDOWN));
assert!(!inner.flags.contains(Flags::WRITE_DISCONNECT));
}
let mut res = buf.take_write_buf().to_vec();
stabilize_date_header(&mut res);
let res = &res[..];
let exp = b"\
HTTP/1.1 200 OK\r\n\
content-length: 4\r\n\
connection: close\r\n\
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
/efg\
";
assert_eq!(
res,
exp,
"\nexpected response not in write buffer:\n\
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(exp)
);
})
.await;
}
#[actix_rt::test]
async fn req_parse_err() {
lazy(|cx| {
let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n");
let services = HttpFlow::new(ok_service(), ExpectHandler, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
services,
ServiceConfig::default(),
None,
OnConnectData::default(),
);
pin!(h1);
match h1.as_mut().poll(cx) {
Poll::Pending => panic!(),
Poll::Ready(res) => assert!(res.is_err()),
}
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() {
assert!(inner.flags.contains(Flags::READ_DISCONNECT));
assert_eq!(
&buf.write_buf_slice()[..26],
b"HTTP/1.1 400 Bad Request\r\n"
);
}
})
.await;
}
#[actix_rt::test]
async fn pipelining_ok_then_ok() {
lazy(|cx| {
let buf = TestBuffer::new(
"\
GET /abcd HTTP/1.1\r\n\r\n\
GET /def HTTP/1.1\r\n\r\n\
",
);
let cfg = ServiceConfig::new(
KeepAlive::Disabled,
Duration::from_millis(1),
Duration::from_millis(1),
false,
None,
);
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
services,
cfg,
None,
OnConnectData::default(),
);
pin!(h1);
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
match h1.as_mut().poll(cx) {
Poll::Pending => panic!("first poll should not be pending"),
Poll::Ready(res) => assert!(res.is_ok()),
}
// polls: initial => shutdown
assert_eq!(h1.poll_count, 2);
let mut res = buf.write_buf_slice_mut();
stabilize_date_header(&mut res);
let res = &res[..];
let exp = b"\
HTTP/1.1 200 OK\r\n\
content-length: 5\r\n\
connection: close\r\n\
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
/abcd\
HTTP/1.1 200 OK\r\n\
content-length: 4\r\n\
connection: close\r\n\
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
/def\
";
assert_eq!(
res,
exp,
"\nexpected response not in write buffer:\n\
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(exp)
);
})
.await;
}
#[actix_rt::test]
async fn pipelining_ok_then_bad() {
lazy(|cx| {
let buf = TestBuffer::new(
"\
GET /abcd HTTP/1.1\r\n\r\n\
GET /def HTTP/1\r\n\r\n\
",
);
let cfg = ServiceConfig::new(
KeepAlive::Disabled,
Duration::from_millis(1),
Duration::from_millis(1),
false,
None,
);
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
services,
cfg,
None,
OnConnectData::default(),
);
pin!(h1);
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
match h1.as_mut().poll(cx) {
Poll::Pending => panic!("first poll should not be pending"),
Poll::Ready(res) => assert!(res.is_err()),
}
// polls: initial => shutdown
assert_eq!(h1.poll_count, 1);
let mut res = buf.write_buf_slice_mut();
stabilize_date_header(&mut res);
let res = &res[..];
let exp = b"\
HTTP/1.1 200 OK\r\n\
content-length: 5\r\n\
connection: close\r\n\
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
/abcd\
HTTP/1.1 400 Bad Request\r\n\
content-length: 0\r\n\
connection: close\r\n\
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
";
assert_eq!(
res,
exp,
"\nexpected response not in write buffer:\n\
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(exp)
);
})
.await;
}
#[actix_rt::test]
async fn expect_handling() {
lazy(|cx| {
let mut buf = TestSeqBuffer::empty();
let cfg = ServiceConfig::new(
KeepAlive::Disabled,
Duration::ZERO,
Duration::ZERO,
false,
None,
);
let services = HttpFlow::new(echo_payload_service(), ExpectHandler, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
services,
cfg,
None,
OnConnectData::default(),
);
buf.extend_read_buf(
"\
POST /upload HTTP/1.1\r\n\
Content-Length: 5\r\n\
Expect: 100-continue\r\n\
\r\n\
",
);
pin!(h1);
assert!(h1.as_mut().poll(cx).is_pending());
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
// polls: manual
assert_eq!(h1.poll_count, 1);
if let DispatcherState::Normal { ref inner } = h1.inner {
let io = inner.io.as_ref().unwrap();
let res = &io.write_buf()[..];
assert_eq!(
str::from_utf8(res).unwrap(),
"HTTP/1.1 100 Continue\r\n\r\n"
);
}
buf.extend_read_buf("12345");
assert!(h1.as_mut().poll(cx).is_ready());
// polls: manual manual shutdown
assert_eq!(h1.poll_count, 3);
if let DispatcherState::Normal { ref inner } = h1.inner {
let io = inner.io.as_ref().unwrap();
let mut res = (&io.write_buf()[..]).to_owned();
stabilize_date_header(&mut res);
assert_eq!(
str::from_utf8(&res).unwrap(),
"\
HTTP/1.1 100 Continue\r\n\
\r\n\
HTTP/1.1 200 OK\r\n\
content-length: 5\r\n\
connection: close\r\n\
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\
\r\n\
12345\
"
);
}
})
.await;
}
#[actix_rt::test]
async fn expect_eager() {
lazy(|cx| {
let mut buf = TestSeqBuffer::empty();
let cfg = ServiceConfig::new(
KeepAlive::Disabled,
Duration::ZERO,
Duration::ZERO,
false,
None,
);
let services = HttpFlow::new(echo_path_service(), ExpectHandler, None);
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
services,
cfg,
None,
OnConnectData::default(),
);
buf.extend_read_buf(
"\
POST /upload HTTP/1.1\r\n\
Content-Length: 5\r\n\
Expect: 100-continue\r\n\
\r\n\
",
);
pin!(h1);
assert!(h1.as_mut().poll(cx).is_ready());
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
// polls: manual shutdown
assert_eq!(h1.poll_count, 2);
if let DispatcherState::Normal { ref inner } = h1.inner {
let io = inner.io.as_ref().unwrap();
let mut res = (&io.write_buf()[..]).to_owned();
stabilize_date_header(&mut res);
// Despite the content-length header and even though the request payload has not
// been sent, this test expects a complete service response since the payload
// is not used at all. The service passed to dispatcher is path echo and doesn't
// consume payload bytes.
assert_eq!(
str::from_utf8(&res).unwrap(),
"\
HTTP/1.1 100 Continue\r\n\
\r\n\
HTTP/1.1 200 OK\r\n\
content-length: 7\r\n\
connection: close\r\n\
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\
\r\n\
/upload\
"
);
}
})
.await;
}
#[actix_rt::test]
async fn upgrade_handling() {
struct TestUpgrade;
impl<T> Service<(Request, Framed<T, Codec>)> for TestUpgrade {
type Response = ();
type Error = Error;
type Future = Ready<Result<Self::Response, Self::Error>>;
actix_service::always_ready!();
fn call(&self, (req, _framed): (Request, Framed<T, Codec>)) -> Self::Future {
assert_eq!(req.method(), Method::GET);
assert!(req.upgrade());
assert_eq!(req.headers().get("upgrade").unwrap(), "websocket");
ready(Ok(()))
}
}
lazy(|cx| {
let mut buf = TestSeqBuffer::empty();
let cfg = ServiceConfig::new(
KeepAlive::Disabled,
Duration::ZERO,
Duration::ZERO,
false,
None,
);
let services = HttpFlow::new(ok_service(), ExpectHandler, Some(TestUpgrade));
let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new(
buf.clone(),
services,
cfg,
None,
OnConnectData::default(),
);
buf.extend_read_buf(
"\
GET /ws HTTP/1.1\r\n\
Connection: Upgrade\r\n\
Upgrade: websocket\r\n\
\r\n\
",
);
pin!(h1);
assert!(h1.as_mut().poll(cx).is_ready());
assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. }));
// polls: manual shutdown
assert_eq!(h1.poll_count, 2);
})
.await;
}
#[actix_rt::test]
async fn handler_drop_payload() {
let _ = env_logger::try_init();
let mut buf = TestBuffer::new(http_msg(
r"
POST /drop-payload HTTP/1.1
Content-Length: 3
abc
",
));
let services = HttpFlow::new(
drop_payload_service(),
ExpectHandler,
None::<UpgradeHandler>,
);
let h1 = Dispatcher::new(
buf.clone(),
services,
ServiceConfig::default(),
None,
OnConnectData::default(),
);
pin!(h1);
lazy(|cx| {
assert!(h1.as_mut().poll(cx).is_pending());
// polls: manual
assert_eq!(h1.poll_count, 1);
let mut res = BytesMut::from(buf.take_write_buf().as_ref());
stabilize_date_header(&mut res);
let res = &res[..];
let exp = http_msg(
r"
HTTP/1.1 200 OK
content-length: 15
date: Thu, 01 Jan 1970 12:34:56 UTC
payload dropped
",
);
assert_eq!(
res,
exp,
"\nexpected response not in write buffer:\n\
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(&exp)
);
if let DispatcherStateProj::Normal { inner } = h1.as_mut().project().inner.project() {
assert!(inner.state.is_none());
}
})
.await;
lazy(|cx| {
// add message that claims to have payload longer than provided
buf.extend_read_buf(http_msg(
r"
POST /drop-payload HTTP/1.1
Content-Length: 200
abc
",
));
assert!(h1.as_mut().poll(cx).is_pending());
// polls: manual => manual
assert_eq!(h1.poll_count, 2);
let mut res = BytesMut::from(buf.take_write_buf().as_ref());
stabilize_date_header(&mut res);
let res = &res[..];
// expect response immediately even though request side has not finished reading payload
let exp = http_msg(
r"
HTTP/1.1 200 OK
content-length: 15
date: Thu, 01 Jan 1970 12:34:56 UTC
payload dropped
",
);
assert_eq!(
res,
exp,
"\nexpected response not in write buffer:\n\
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(&exp)
);
})
.await;
lazy(|cx| {
assert!(h1.as_mut().poll(cx).is_ready());
// polls: manual => manual => manual
assert_eq!(h1.poll_count, 3);
let mut res = BytesMut::from(buf.take_write_buf().as_ref());
stabilize_date_header(&mut res);
let res = &res[..];
// expect that unrequested error response is sent back since connection could not be cleaned
let exp = http_msg(
r"
HTTP/1.1 500 Internal Server Error
content-length: 0
connection: close
date: Thu, 01 Jan 1970 12:34:56 UTC
",
);
assert_eq!(
res,
exp,
"\nexpected response not in write buffer:\n\
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(&exp)
);
})
.await;
}
fn http_msg(msg: impl AsRef<str>) -> BytesMut {
let mut msg = msg
.as_ref()
.trim()
.split('\n')
.into_iter()
.map(|line| [line.trim_start(), "\r"].concat())
.collect::<Vec<_>>()
.join("\n");
// remove trailing \r
msg.pop();
if !msg.is_empty() && !msg.contains("\r\n\r\n") {
msg.push_str("\r\n\r\n");
}
BytesMut::from(msg.as_bytes())
}
#[test]
fn http_msg_creates_msg() {
assert_eq!(http_msg(r""), "");
assert_eq!(
http_msg(
r"
POST / HTTP/1.1
Content-Length: 3
abc
"
),
"POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc"
);
assert_eq!(
http_msg(
r"
GET / HTTP/1.1
Content-Length: 3
"
),
"GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\n"
);
}

View File

@ -0,0 +1,666 @@
use std::{
cmp,
io::{self, Write as _},
marker::PhantomData,
ptr::copy_nonoverlapping,
slice::from_raw_parts_mut,
};
use bytes::{BufMut, BytesMut};
use crate::{
body::BodySize,
header::{
map::Value, HeaderMap, HeaderName, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
},
helpers, ConnectionType, RequestHeadType, Response, ServiceConfig, StatusCode, Version,
};
const AVERAGE_HEADER_SIZE: usize = 30;
#[derive(Debug)]
pub(crate) struct MessageEncoder<T: MessageType> {
#[allow(dead_code)]
pub length: BodySize,
pub te: TransferEncoding,
_phantom: PhantomData<T>,
}
impl<T: MessageType> Default for MessageEncoder<T> {
fn default() -> Self {
MessageEncoder {
length: BodySize::None,
te: TransferEncoding::empty(),
_phantom: PhantomData,
}
}
}
pub(crate) trait MessageType: Sized {
fn status(&self) -> Option<StatusCode>;
fn headers(&self) -> &HeaderMap;
fn extra_headers(&self) -> Option<&HeaderMap>;
fn camel_case(&self) -> bool {
false
}
fn chunked(&self) -> bool;
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()>;
fn encode_headers(
&mut self,
dst: &mut BytesMut,
version: Version,
mut length: BodySize,
conn_type: ConnectionType,
config: &ServiceConfig,
) -> io::Result<()> {
let chunked = self.chunked();
let mut skip_len = length != BodySize::Stream;
let camel_case = self.camel_case();
// Content length
if let Some(status) = self.status() {
match status {
StatusCode::CONTINUE
| StatusCode::SWITCHING_PROTOCOLS
| StatusCode::PROCESSING
| StatusCode::NO_CONTENT => {
// skip content-length and transfer-encoding headers
// see https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.1
// and https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2
skip_len = true;
length = BodySize::None
}
StatusCode::NOT_MODIFIED => {
// 304 responses should never have a body but should retain a manually set
// content-length header
// see https://datatracker.ietf.org/doc/html/rfc7232#section-4.1
skip_len = false;
length = BodySize::None;
}
_ => {}
}
}
match length {
BodySize::Stream => {
if chunked {
skip_len = true;
if camel_case {
dst.put_slice(b"\r\nTransfer-Encoding: chunked\r\n")
} else {
dst.put_slice(b"\r\ntransfer-encoding: chunked\r\n")
}
} else {
skip_len = false;
dst.put_slice(b"\r\n");
}
}
BodySize::Sized(0) if camel_case => dst.put_slice(b"\r\nContent-Length: 0\r\n"),
BodySize::Sized(0) => dst.put_slice(b"\r\ncontent-length: 0\r\n"),
BodySize::Sized(len) => helpers::write_content_length(len, dst, camel_case),
BodySize::None => dst.put_slice(b"\r\n"),
}
// Connection
match conn_type {
ConnectionType::Upgrade => dst.put_slice(b"connection: upgrade\r\n"),
ConnectionType::KeepAlive if version < Version::HTTP_11 => {
if camel_case {
dst.put_slice(b"Connection: keep-alive\r\n")
} else {
dst.put_slice(b"connection: keep-alive\r\n")
}
}
ConnectionType::Close if version >= Version::HTTP_11 => {
if camel_case {
dst.put_slice(b"Connection: close\r\n")
} else {
dst.put_slice(b"connection: close\r\n")
}
}
_ => {}
}
// write headers
let mut has_date = false;
let mut buf = dst.chunk_mut().as_mut_ptr();
let mut remaining = dst.capacity() - dst.len();
// tracks bytes written since last buffer resize
// since buf is a raw pointer to a bytes container storage but is written to without the
// container's knowledge, this is used to sync the containers cursor after data is written
let mut pos = 0;
self.write_headers(|key, value| {
match *key {
CONNECTION => return,
TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => return,
DATE => has_date = true,
_ => {}
}
let k = key.as_str().as_bytes();
let k_len = k.len();
for val in value.iter() {
let v = val.as_ref();
let v_len = v.len();
// key length + value length + colon + space + \r\n
let len = k_len + v_len + 4;
if len > remaining {
// SAFETY: all the bytes written up to position "pos" are initialized
// the written byte count and pointer advancement are kept in sync
unsafe {
dst.advance_mut(pos);
}
pos = 0;
dst.reserve(len * 2);
remaining = dst.capacity() - dst.len();
// re-assign buf raw pointer since it's possible that the buffer was
// reallocated and/or resized
buf = dst.chunk_mut().as_mut_ptr();
}
// SAFETY: on each write, it is enough to ensure that the advancement of
// the cursor matches the number of bytes written
unsafe {
if camel_case {
// use Camel-Case headers
write_camel_case(k, buf, k_len);
} else {
write_data(k, buf, k_len);
}
buf = buf.add(k_len);
write_data(b": ", buf, 2);
buf = buf.add(2);
write_data(v, buf, v_len);
buf = buf.add(v_len);
write_data(b"\r\n", buf, 2);
buf = buf.add(2);
};
pos += len;
remaining -= len;
}
});
// final cursor synchronization with the bytes container
//
// SAFETY: all the bytes written up to position "pos" are initialized
// the written byte count and pointer advancement are kept in sync
unsafe {
dst.advance_mut(pos);
}
if !has_date {
// optimized date header, write_date_header writes its own \r\n
config.write_date_header(dst, camel_case);
}
// end-of-headers marker
dst.extend_from_slice(b"\r\n");
Ok(())
}
fn write_headers<F>(&mut self, mut f: F)
where
F: FnMut(&HeaderName, &Value),
{
match self.extra_headers() {
Some(headers) => {
// merging headers from head and extra headers.
self.headers()
.inner
.iter()
.filter(|(name, _)| !headers.contains_key(*name))
.chain(headers.inner.iter())
.for_each(|(k, v)| f(k, v))
}
None => self.headers().inner.iter().for_each(|(k, v)| f(k, v)),
}
}
}
impl MessageType for Response<()> {
fn status(&self) -> Option<StatusCode> {
Some(self.head().status)
}
fn chunked(&self) -> bool {
self.head().chunked()
}
fn headers(&self) -> &HeaderMap {
&self.head().headers
}
fn extra_headers(&self) -> Option<&HeaderMap> {
None
}
fn camel_case(&self) -> bool {
self.head()
.flags
.contains(crate::message::Flags::CAMEL_CASE)
}
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> {
let head = self.head();
let reason = head.reason().as_bytes();
dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE + reason.len());
// status line
helpers::write_status_line(head.version, head.status.as_u16(), dst);
dst.put_slice(reason);
Ok(())
}
}
impl MessageType for RequestHeadType {
fn status(&self) -> Option<StatusCode> {
None
}
fn chunked(&self) -> bool {
self.as_ref().chunked()
}
fn camel_case(&self) -> bool {
self.as_ref().camel_case_headers()
}
fn headers(&self) -> &HeaderMap {
self.as_ref().headers()
}
fn extra_headers(&self) -> Option<&HeaderMap> {
self.extra_headers()
}
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> {
let head = self.as_ref();
dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE);
write!(
helpers::MutWriter(dst),
"{} {} {}",
head.method,
head.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"),
match head.version {
Version::HTTP_09 => "HTTP/0.9",
Version::HTTP_10 => "HTTP/1.0",
Version::HTTP_11 => "HTTP/1.1",
Version::HTTP_2 => "HTTP/2.0",
Version::HTTP_3 => "HTTP/3.0",
_ => return Err(io::Error::new(io::ErrorKind::Other, "unsupported version")),
}
)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
}
}
impl<T: MessageType> MessageEncoder<T> {
/// Encode chunk.
pub fn encode_chunk(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result<bool> {
self.te.encode(msg, buf)
}
/// Encode EOF.
pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> {
self.te.encode_eof(buf)
}
/// Encode message.
pub fn encode(
&mut self,
dst: &mut BytesMut,
message: &mut T,
head: bool,
stream: bool,
version: Version,
length: BodySize,
conn_type: ConnectionType,
config: &ServiceConfig,
) -> io::Result<()> {
// transfer encoding
if !head {
self.te = match length {
BodySize::Sized(0) => TransferEncoding::empty(),
BodySize::Sized(len) => TransferEncoding::length(len),
BodySize::Stream => {
if message.chunked() && !stream {
TransferEncoding::chunked()
} else {
TransferEncoding::eof()
}
}
BodySize::None => TransferEncoding::empty(),
};
} else {
self.te = TransferEncoding::empty();
}
message.encode_status(dst)?;
message.encode_headers(dst, version, length, conn_type, config)
}
}
/// Encoders to handle different Transfer-Encodings.
#[derive(Debug)]
pub(crate) struct TransferEncoding {
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 {
#[inline]
pub fn empty() -> TransferEncoding {
TransferEncoding {
kind: TransferEncodingKind::Length(0),
}
}
#[inline]
pub fn eof() -> TransferEncoding {
TransferEncoding {
kind: TransferEncodingKind::Eof,
}
}
#[inline]
pub fn chunked() -> TransferEncoding {
TransferEncoding {
kind: TransferEncodingKind::Chunked(false),
}
}
#[inline]
pub fn length(len: u64) -> TransferEncoding {
TransferEncoding {
kind: TransferEncodingKind::Length(len),
}
}
/// Encode message. Return `EOF` state of encoder
#[inline]
pub fn encode(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result<bool> {
match self.kind {
TransferEncodingKind::Eof => {
let eof = msg.is_empty();
buf.extend_from_slice(msg);
Ok(eof)
}
TransferEncodingKind::Chunked(ref mut eof) => {
if *eof {
return Ok(true);
}
if msg.is_empty() {
*eof = true;
buf.extend_from_slice(b"0\r\n\r\n");
} else {
writeln!(helpers::MutWriter(buf), "{:X}\r", msg.len())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
buf.reserve(msg.len() + 2);
buf.extend_from_slice(msg);
buf.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);
buf.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, buf: &mut BytesMut) -> io::Result<()> {
match self.kind {
TransferEncodingKind::Eof => Ok(()),
TransferEncodingKind::Length(rem) => {
if rem != 0 {
Err(io::Error::new(io::ErrorKind::UnexpectedEof, ""))
} else {
Ok(())
}
}
TransferEncodingKind::Chunked(ref mut eof) => {
if !*eof {
*eof = true;
buf.extend_from_slice(b"0\r\n\r\n");
}
Ok(())
}
}
}
}
/// # Safety
/// Callers must ensure that the given `len` matches the given `value` length and that `buf` is
/// valid for writes of at least `len` bytes.
unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) {
debug_assert_eq!(value.len(), len);
copy_nonoverlapping(value.as_ptr(), buf, len);
}
/// # Safety
/// Callers must ensure that the given `len` matches the given `value` length and that `buf` is
/// valid for writes of at least `len` bytes.
unsafe fn write_camel_case(value: &[u8], buf: *mut u8, len: usize) {
// first copy entire (potentially wrong) slice to output
write_data(value, buf, len);
// SAFETY: We just initialized the buffer with `value`
let buffer = from_raw_parts_mut(buf, len);
let mut iter = value.iter();
// first character should be uppercase
if let Some(c @ b'a'..=b'z') = iter.next() {
buffer[0] = c & 0b1101_1111;
}
// track 1 ahead of the current position since that's the location being assigned to
let mut index = 2;
// remaining characters after hyphens should also be uppercase
while let Some(&c) = iter.next() {
if c == b'-' {
// advance iter by one and uppercase if needed
if let Some(c @ b'a'..=b'z') = iter.next() {
buffer[index] = c & 0b1101_1111;
}
}
index += 1;
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use bytes::Bytes;
use http::header::AUTHORIZATION;
use super::*;
use crate::{
header::{HeaderValue, CONTENT_TYPE},
RequestHead,
};
#[test]
fn test_chunked_te() {
let mut bytes = BytesMut::new();
let mut enc = TransferEncoding::chunked();
{
assert!(!enc.encode(b"test", &mut bytes).ok().unwrap());
assert!(enc.encode(b"", &mut bytes).ok().unwrap());
}
assert_eq!(
bytes.split().freeze(),
Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n")
);
}
#[actix_rt::test]
async fn test_camel_case() {
let mut bytes = BytesMut::with_capacity(2048);
let mut head = RequestHead::default();
head.set_camel_case_headers(true);
head.headers.insert(DATE, HeaderValue::from_static("date"));
head.headers
.insert(CONTENT_TYPE, HeaderValue::from_static("plain/text"));
let mut head = RequestHeadType::Owned(head);
let _ = head.encode_headers(
&mut bytes,
Version::HTTP_11,
BodySize::Sized(0),
ConnectionType::Close,
&ServiceConfig::default(),
);
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(data.contains("Content-Length: 0\r\n"));
assert!(data.contains("Connection: close\r\n"));
assert!(data.contains("Content-Type: plain/text\r\n"));
assert!(data.contains("Date: date\r\n"));
let _ = head.encode_headers(
&mut bytes,
Version::HTTP_11,
BodySize::Stream,
ConnectionType::KeepAlive,
&ServiceConfig::default(),
);
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(data.contains("Transfer-Encoding: chunked\r\n"));
assert!(data.contains("Content-Type: plain/text\r\n"));
assert!(data.contains("Date: date\r\n"));
let mut head = RequestHead::default();
head.set_camel_case_headers(false);
head.headers.insert(DATE, HeaderValue::from_static("date"));
head.headers
.insert(CONTENT_TYPE, HeaderValue::from_static("plain/text"));
head.headers
.append(CONTENT_TYPE, HeaderValue::from_static("xml"));
let mut head = RequestHeadType::Owned(head);
let _ = head.encode_headers(
&mut bytes,
Version::HTTP_11,
BodySize::Stream,
ConnectionType::KeepAlive,
&ServiceConfig::default(),
);
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(data.contains("transfer-encoding: chunked\r\n"));
assert!(data.contains("content-type: xml\r\n"));
assert!(data.contains("content-type: plain/text\r\n"));
assert!(data.contains("date: date\r\n"));
}
#[actix_rt::test]
async fn test_extra_headers() {
let mut bytes = BytesMut::with_capacity(2048);
let mut head = RequestHead::default();
head.headers.insert(
AUTHORIZATION,
HeaderValue::from_static("some authorization"),
);
let mut extra_headers = HeaderMap::new();
extra_headers.insert(
AUTHORIZATION,
HeaderValue::from_static("another authorization"),
);
extra_headers.insert(DATE, HeaderValue::from_static("date"));
let mut head = RequestHeadType::Rc(Rc::new(head), Some(extra_headers));
let _ = head.encode_headers(
&mut bytes,
Version::HTTP_11,
BodySize::Sized(0),
ConnectionType::Close,
&ServiceConfig::default(),
);
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(data.contains("content-length: 0\r\n"));
assert!(data.contains("connection: close\r\n"));
assert!(data.contains("authorization: another authorization\r\n"));
assert!(data.contains("date: date\r\n"));
}
#[actix_rt::test]
async fn test_no_content_length() {
let mut bytes = BytesMut::with_capacity(2048);
let mut res = Response::with_body(StatusCode::SWITCHING_PROTOCOLS, ());
res.headers_mut().insert(DATE, HeaderValue::from_static(""));
res.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
let _ = res.encode_headers(
&mut bytes,
Version::HTTP_11,
BodySize::Stream,
ConnectionType::Upgrade,
&ServiceConfig::default(),
);
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(!data.contains("content-length: 0\r\n"));
assert!(!data.contains("transfer-encoding: chunked\r\n"));
}
}

View File

@ -0,0 +1,33 @@
use actix_service::{Service, ServiceFactory};
use actix_utils::future::{ready, Ready};
use crate::{Error, Request};
pub struct ExpectHandler;
impl ServiceFactory<Request> for ExpectHandler {
type Response = Request;
type Error = Error;
type Config = ();
type Service = ExpectHandler;
type InitError = Error;
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: Self::Config) -> Self::Future {
ready(Ok(ExpectHandler))
}
}
impl Service<Request> for ExpectHandler {
type Response = Request;
type Error = Error;
type Future = Ready<Result<Self::Response, Self::Error>>;
actix_service::always_ready!();
fn call(&self, req: Request) -> Self::Future {
ready(Ok(req))
// TODO: add some way to trigger error
// Err(error::ErrorExpectationFailed("test"))
}
}

91
actix-http/src/h1/mod.rs Normal file
View File

@ -0,0 +1,91 @@
//! HTTP/1 protocol implementation.
use bytes::{Bytes, BytesMut};
mod chunked;
mod client;
mod codec;
mod decoder;
mod dispatcher;
#[cfg(test)]
mod dispatcher_tests;
mod encoder;
mod expect;
mod payload;
mod service;
mod timer;
mod upgrade;
mod utils;
pub use self::client::{ClientCodec, ClientPayloadCodec};
pub use self::codec::Codec;
pub use self::dispatcher::Dispatcher;
pub use self::expect::ExpectHandler;
pub use self::payload::Payload;
pub use self::service::{H1Service, H1ServiceHandler};
pub use self::upgrade::UpgradeHandler;
pub use self::utils::SendResponse;
#[derive(Debug)]
/// Codec message
pub enum Message<T> {
/// HTTP message.
Item(T),
/// Payload chunk.
Chunk(Option<Bytes>),
}
impl<T> From<T> for Message<T> {
fn from(item: T) -> Self {
Message::Item(item)
}
}
/// Incoming request type
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageType {
None,
Payload,
Stream,
}
const LW: usize = 2 * 1024;
const HW: usize = 32 * 1024;
pub(crate) fn reserve_readbuf(src: &mut BytesMut) {
let cap = src.capacity();
if cap < LW {
src.reserve(HW - cap);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Request;
impl Message<Request> {
pub fn message(self) -> Request {
match self {
Message::Item(req) => req,
_ => panic!("error"),
}
}
pub fn chunk(self) -> Bytes {
match self {
Message::Chunk(Some(data)) => data,
_ => panic!("error"),
}
}
pub fn eof(self) -> bool {
match self {
Message::Chunk(None) => true,
Message::Chunk(Some(_)) => false,
_ => panic!("error"),
}
}
}
}

View File

@ -0,0 +1,284 @@
//! Payload stream
use std::{
cell::RefCell,
collections::VecDeque,
pin::Pin,
rc::{Rc, Weak},
task::{Context, Poll, Waker},
};
use bytes::Bytes;
use futures_core::Stream;
use crate::error::PayloadError;
/// max buffer size 32k
pub(crate) const MAX_BUFFER_SIZE: usize = 32_768;
#[derive(Debug, PartialEq)]
pub enum PayloadStatus {
Read,
Pause,
Dropped,
}
/// Buffered stream of bytes chunks
///
/// Payload stores chunks in a vector. First chunk can be received with `poll_next`. Payload does
/// not notify current task when new data is available.
///
/// Payload can be used as `Response` body stream.
#[derive(Debug)]
pub struct Payload {
inner: Rc<RefCell<Inner>>,
}
impl Payload {
/// Creates a payload stream.
///
/// This method construct two objects responsible for bytes stream generation:
/// - `PayloadSender` - *Sender* side of the stream
/// - `Payload` - *Receiver* side of the stream
pub fn create(eof: bool) -> (PayloadSender, Payload) {
let shared = Rc::new(RefCell::new(Inner::new(eof)));
(
PayloadSender::new(Rc::downgrade(&shared)),
Payload { inner: shared },
)
}
/// Creates an empty payload.
pub(crate) fn empty() -> Payload {
Payload {
inner: Rc::new(RefCell::new(Inner::new(true))),
}
}
/// Length of the data in this payload
#[cfg(test)]
pub fn len(&self) -> usize {
self.inner.borrow().len()
}
/// Is payload empty
#[cfg(test)]
pub fn is_empty(&self) -> bool {
self.inner.borrow().len() == 0
}
/// Put unused data back to payload
#[inline]
pub fn unread_data(&mut self, data: Bytes) {
self.inner.borrow_mut().unread_data(data);
}
}
impl Stream for Payload {
type Item = Result<Bytes, PayloadError>;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, PayloadError>>> {
Pin::new(&mut *self.inner.borrow_mut()).poll_next(cx)
}
}
/// Sender part of the payload stream
pub struct PayloadSender {
inner: Weak<RefCell<Inner>>,
}
impl PayloadSender {
fn new(inner: Weak<RefCell<Inner>>) -> Self {
Self { inner }
}
#[inline]
pub fn set_error(&mut self, err: PayloadError) {
if let Some(shared) = self.inner.upgrade() {
shared.borrow_mut().set_error(err)
}
}
#[inline]
pub fn feed_eof(&mut self) {
if let Some(shared) = self.inner.upgrade() {
shared.borrow_mut().feed_eof()
}
}
#[inline]
pub fn feed_data(&mut self, data: Bytes) {
if let Some(shared) = self.inner.upgrade() {
shared.borrow_mut().feed_data(data)
}
}
#[inline]
pub fn need_read(&self, cx: &mut Context<'_>) -> PayloadStatus {
// we check need_read only if Payload (other side) is alive,
// otherwise always return true (consume payload)
if let Some(shared) = self.inner.upgrade() {
if shared.borrow().need_read {
PayloadStatus::Read
} else {
shared.borrow_mut().register_io(cx);
PayloadStatus::Pause
}
} else {
PayloadStatus::Dropped
}
}
}
#[derive(Debug)]
struct Inner {
len: usize,
eof: bool,
err: Option<PayloadError>,
need_read: bool,
items: VecDeque<Bytes>,
task: Option<Waker>,
io_task: Option<Waker>,
}
impl Inner {
fn new(eof: bool) -> Self {
Inner {
eof,
len: 0,
err: None,
items: VecDeque::new(),
need_read: true,
task: None,
io_task: None,
}
}
/// Wake up future waiting for payload data to be available.
fn wake(&mut self) {
if let Some(waker) = self.task.take() {
waker.wake();
}
}
/// Wake up future feeding data to Payload.
fn wake_io(&mut self) {
if let Some(waker) = self.io_task.take() {
waker.wake();
}
}
/// Register future waiting data from payload.
/// Waker would be used in `Inner::wake`
fn register(&mut self, cx: &mut Context<'_>) {
if self
.task
.as_ref()
.map_or(true, |w| !cx.waker().will_wake(w))
{
self.task = Some(cx.waker().clone());
}
}
// Register future feeding data to payload.
/// Waker would be used in `Inner::wake_io`
fn register_io(&mut self, cx: &mut Context<'_>) {
if self
.io_task
.as_ref()
.map_or(true, |w| !cx.waker().will_wake(w))
{
self.io_task = Some(cx.waker().clone());
}
}
#[inline]
fn set_error(&mut self, err: PayloadError) {
self.err = Some(err);
}
#[inline]
fn feed_eof(&mut self) {
self.eof = true;
}
#[inline]
fn feed_data(&mut self, data: Bytes) {
self.len += data.len();
self.items.push_back(data);
self.need_read = self.len < MAX_BUFFER_SIZE;
self.wake();
}
#[cfg(test)]
fn len(&self) -> usize {
self.len
}
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, PayloadError>>> {
if let Some(data) = self.items.pop_front() {
self.len -= data.len();
self.need_read = self.len < MAX_BUFFER_SIZE;
if self.need_read && !self.eof {
self.register(cx);
}
self.wake_io();
Poll::Ready(Some(Ok(data)))
} else if let Some(err) = self.err.take() {
Poll::Ready(Some(Err(err)))
} else if self.eof {
Poll::Ready(None)
} else {
self.need_read = true;
self.register(cx);
self.wake_io();
Poll::Pending
}
}
fn unread_data(&mut self, data: Bytes) {
self.len += data.len();
self.items.push_front(data);
}
}
#[cfg(test)]
mod tests {
use std::panic::{RefUnwindSafe, UnwindSafe};
use actix_utils::future::poll_fn;
use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::*;
assert_impl_all!(Payload: Unpin);
assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe);
assert_impl_all!(Inner: Unpin, Send, Sync);
assert_not_impl_any!(Inner: UnwindSafe, RefUnwindSafe);
#[actix_rt::test]
async fn test_unread_data() {
let (_, mut payload) = Payload::create(false);
payload.unread_data(Bytes::from("data"));
assert!(!payload.is_empty());
assert_eq!(payload.len(), 4);
assert_eq!(
Bytes::from("data"),
poll_fn(|cx| Pin::new(&mut payload).poll_next(cx))
.await
.unwrap()
.unwrap()
);
}
}

View File

@ -0,0 +1,369 @@
use std::{
fmt,
marker::PhantomData,
net,
rc::Rc,
task::{Context, Poll},
};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_rt::net::TcpStream;
use actix_service::{
fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _,
};
use actix_utils::future::ready;
use futures_core::future::LocalBoxFuture;
use crate::{
body::{BoxBody, MessageBody},
config::ServiceConfig,
error::DispatchError,
service::HttpServiceHandler,
ConnectCallback, OnConnectData, Request, Response,
};
use super::{codec::Codec, dispatcher::Dispatcher, ExpectHandler, UpgradeHandler};
/// `ServiceFactory` implementation for HTTP1 transport
pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler> {
srv: S,
cfg: ServiceConfig,
expect: X,
upgrade: Option<U>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
_phantom: PhantomData<B>,
}
impl<T, S, B> H1Service<T, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Response<BoxBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
{
/// Create new `HttpService` instance with config.
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
cfg: ServiceConfig,
service: F,
) -> Self {
H1Service {
cfg,
srv: service.into_factory(),
expect: ExpectHandler,
upgrade: None,
on_connect_ext: None,
_phantom: PhantomData,
}
}
}
impl<S, B, X, U> H1Service<TcpStream, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<TcpStream, Codec>), Config = (), Response = ()>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create simple tcp stream service
pub fn tcp(
self,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = DispatchError,
InitError = (),
> {
fn_service(|io: TcpStream| {
let peer_addr = io.peer_addr().ok();
ready(Ok((io, peer_addr)))
})
.and_then(self)
}
}
#[cfg(feature = "openssl")]
mod openssl {
use super::*;
use actix_tls::accept::{
openssl::{
reexports::{Error as SslError, SslAcceptor},
Acceptor, TlsStream,
},
TlsError,
};
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
(Request, Framed<TlsStream<TcpStream>, Codec>),
Config = (),
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create OpenSSL based service.
pub fn openssl(
self,
acceptor: SslAcceptor,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<SslError, DispatchError>,
InitError = (),
> {
Acceptor::new(acceptor)
.map_init_err(|_| {
unreachable!("TLS acceptor service factory does not error on init")
})
.map_err(TlsError::into_service_error)
.map(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().peer_addr().ok();
(io, peer_addr)
})
.and_then(self.map_err(TlsError::Service))
}
}
}
#[cfg(feature = "rustls")]
mod rustls {
use std::io;
use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{
rustls::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError,
};
use super::*;
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
(Request, Framed<TlsStream<TcpStream>, Codec>),
Config = (),
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create Rustls based service.
pub fn rustls(
self,
config: ServerConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = (),
> {
Acceptor::new(config)
.map_init_err(|_| {
unreachable!("TLS acceptor service factory does not error on init")
})
.map_err(TlsError::into_service_error)
.map(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok();
(io, peer_addr)
})
.and_then(self.map_err(TlsError::Service))
}
}
}
impl<T, S, B, X, U> H1Service<T, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Response<BoxBody>>,
S::Response: Into<Response<B>>,
S::InitError: fmt::Debug,
B: MessageBody,
{
pub fn expect<X1>(self, expect: X1) -> H1Service<T, S, B, X1, U>
where
X1: ServiceFactory<Request, Response = Request>,
X1::Error: Into<Response<BoxBody>>,
X1::InitError: fmt::Debug,
{
H1Service {
expect,
cfg: self.cfg,
srv: self.srv,
upgrade: self.upgrade,
on_connect_ext: self.on_connect_ext,
_phantom: PhantomData,
}
}
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> H1Service<T, S, B, X, U1>
where
U1: ServiceFactory<(Request, Framed<T, Codec>), Response = ()>,
U1::Error: fmt::Display,
U1::InitError: fmt::Debug,
{
H1Service {
upgrade,
cfg: self.cfg,
srv: self.srv,
expect: self.expect,
on_connect_ext: self.on_connect_ext,
_phantom: PhantomData,
}
}
/// Set on connect callback.
pub(crate) fn on_connect_ext(mut self, f: Option<Rc<ConnectCallback<T>>>) -> Self {
self.on_connect_ext = f;
self
}
}
impl<T, S, B, X, U> ServiceFactory<(T, Option<net::SocketAddr>)> for H1Service<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin + 'static,
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>>,
S::Response: Into<Response<B>>,
S::InitError: fmt::Debug,
B: MessageBody,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
type Response = ();
type Error = DispatchError;
type Config = ();
type Service = H1ServiceHandler<T, S::Service, B, X::Service, U::Service>;
type InitError = ();
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future {
let service = self.srv.new_service(());
let expect = self.expect.new_service(());
let upgrade = self.upgrade.as_ref().map(|s| s.new_service(()));
let on_connect_ext = self.on_connect_ext.clone();
let cfg = self.cfg.clone();
Box::pin(async move {
let expect = expect
.await
.map_err(|e| log::error!("Init http expect service error: {:?}", e))?;
let upgrade = match upgrade {
Some(upgrade) => {
let upgrade = upgrade
.await
.map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?;
Some(upgrade)
}
None => None,
};
let service = service
.await
.map_err(|e| log::error!("Init http service error: {:?}", e))?;
Ok(H1ServiceHandler::new(
cfg,
service,
expect,
upgrade,
on_connect_ext,
))
})
}
}
/// `Service` implementation for HTTP/1 transport
pub type H1ServiceHandler<T, S, B, X, U> = HttpServiceHandler<T, S, B, X, U>;
impl<T, S, B, X, U> Service<(T, Option<net::SocketAddr>)> for HttpServiceHandler<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Response<BoxBody>>,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request, Response = Request>,
X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display + Into<Response<BoxBody>>,
{
type Response = ();
type Error = DispatchError;
type Future = Dispatcher<T, S, B, X, U>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self._poll_ready(cx).map_err(|err| {
log::error!("HTTP/1 service readiness error: {:?}", err);
DispatchError::Service(err)
})
}
fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
Dispatcher::new(io, self.flow.clone(), self.cfg.clone(), addr, conn_data)
}
}

Some files were not shown because too many files have changed in this diff Show More