mirror of https://github.com/fafhrd91/actix-web
update dep override. merge branch master
This commit is contained in:
commit
3e1fc93ab7
|
@ -1,21 +1,21 @@
|
||||||
<!-- Thanks for considering contributing actix! -->
|
<!-- Thanks for considering contributing actix! -->
|
||||||
<!-- Please fill out the following to make our reviews easy. -->
|
<!-- Please fill out the following to get your PR reviewed quicker. -->
|
||||||
|
|
||||||
## PR Type
|
## PR Type
|
||||||
<!-- What kind of change does this PR make? -->
|
<!-- What kind of change does this PR make? -->
|
||||||
<!-- Bug Fix / Feature / Refactor / Code Style / Other -->
|
<!-- Bug Fix / Feature / Refactor / Code Style / Other -->
|
||||||
INSERT_PR_TYPE
|
PR_TYPE
|
||||||
|
|
||||||
|
|
||||||
## PR Checklist
|
## PR Checklist
|
||||||
Check your PR fulfills the following:
|
<!-- Check your PR fulfills the following items. ->>
|
||||||
|
|
||||||
<!-- For draft PRs check the boxes as you complete them. -->
|
<!-- For draft PRs check the boxes as you complete them. -->
|
||||||
|
|
||||||
- [ ] Tests for the changes have been added / updated.
|
- [ ] Tests for the changes have been added / updated.
|
||||||
- [ ] Documentation comments have been added / updated.
|
- [ ] Documentation comments have been added / updated.
|
||||||
- [ ] A changelog entry has been made for the appropriate packages.
|
- [ ] A changelog entry has been made for the appropriate packages.
|
||||||
- [ ] Format code with the latest stable rustfmt
|
- [ ] Format code with the latest stable rustfmt.
|
||||||
|
- [ ] (Team) Label with affected crates and semver status.
|
||||||
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
|
@ -12,13 +12,22 @@
|
||||||
`ServiceRequest::from_request` would not fail and no payload would be generated [#1893]
|
`ServiceRequest::from_request` would not fail and no payload would be generated [#1893]
|
||||||
* Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894]
|
* Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Multiple calls `App::data` with the same type now keeps the latest call's data. [#1906]
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* Public field of `web::Path` has been made private. [#1894]
|
* Public field of `web::Path` has been made private. [#1894]
|
||||||
* Public field of `web::Query` has been made private. [#1894]
|
* Public field of `web::Query` has been made private. [#1894]
|
||||||
|
* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869]
|
||||||
|
* `AppService::set_service_data`; for custom HTTP service factories adding application data, use the
|
||||||
|
layered data model by calling `ServiceRequest::add_data_container` when handling
|
||||||
|
requests instead. [#1906]
|
||||||
|
|
||||||
[#1891]: https://github.com/actix/actix-web/pull/1891
|
[#1891]: https://github.com/actix/actix-web/pull/1891
|
||||||
[#1893]: https://github.com/actix/actix-web/pull/1893
|
[#1893]: https://github.com/actix/actix-web/pull/1893
|
||||||
[#1894]: https://github.com/actix/actix-web/pull/1894
|
[#1894]: https://github.com/actix/actix-web/pull/1894
|
||||||
|
[#1869]: https://github.com/actix/actix-web/pull/1869
|
||||||
|
[#1906]: https://github.com/actix/actix-web/pull/1906
|
||||||
|
|
||||||
|
|
||||||
## 4.0.0-beta.1 - 2021-01-07
|
## 4.0.0-beta.1 - 2021-01-07
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -132,11 +132,11 @@ actix-multipart = { path = "actix-multipart" }
|
||||||
actix-files = { path = "actix-files" }
|
actix-files = { path = "actix-files" }
|
||||||
awc = { path = "awc" }
|
awc = { path = "awc" }
|
||||||
|
|
||||||
actix-service = { git = "https://github.com/actix/actix-net.git", branch="feat/immutable" }
|
actix-service = { git = "https://github.com/actix/actix-net.git" }
|
||||||
actix-server = { git = "https://github.com/actix/actix-net.git", branch = "feat/immutable" }
|
actix-server = { git = "https://github.com/actix/actix-net.git" }
|
||||||
actix-tls = { git = "https://github.com/actix/actix-net.git", branch = "feat/immutable" }
|
actix-tls = { git = "https://github.com/actix/actix-net.git" }
|
||||||
actix-utils = { git = "https://github.com/actix/actix-net.git", branch = "feat/immutable" }
|
actix-utils = { git = "https://github.com/actix/actix-net.git" }
|
||||||
actix-router = { git = "https://github.com/actix/actix-net.git", branch = "feat/immutable" }
|
actix-router = { git = "https://github.com/actix/actix-net.git" }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "server"
|
name = "server"
|
||||||
|
|
|
@ -101,7 +101,7 @@ mod tests {
|
||||||
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
|
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(header::IF_MODIFIED_SINCE, since)
|
.insert_header((header::IF_MODIFIED_SINCE, since))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
||||||
|
@ -113,7 +113,7 @@ mod tests {
|
||||||
let since = file.last_modified().unwrap();
|
let since = file.last_modified().unwrap();
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(header::IF_MODIFIED_SINCE, since)
|
.insert_header((header::IF_MODIFIED_SINCE, since))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
||||||
|
@ -126,8 +126,8 @@ mod tests {
|
||||||
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
|
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(header::IF_NONE_MATCH, "miss_etag")
|
.insert_header((header::IF_NONE_MATCH, "miss_etag"))
|
||||||
.header(header::IF_MODIFIED_SINCE, since)
|
.insert_header((header::IF_MODIFIED_SINCE, since))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req).await.unwrap();
|
||||||
assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
|
assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
|
||||||
|
@ -139,7 +139,7 @@ mod tests {
|
||||||
let since = file.last_modified().unwrap();
|
let since = file.last_modified().unwrap();
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(header::IF_UNMODIFIED_SINCE, since)
|
.insert_header((header::IF_UNMODIFIED_SINCE, since))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
@ -151,7 +151,7 @@ mod tests {
|
||||||
let since = header::HttpDate::from(SystemTime::UNIX_EPOCH);
|
let since = header::HttpDate::from(SystemTime::UNIX_EPOCH);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(header::IF_UNMODIFIED_SINCE, since)
|
.insert_header((header::IF_UNMODIFIED_SINCE, since))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let resp = file.respond_to(&req).await.unwrap();
|
let resp = file.respond_to(&req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED);
|
assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED);
|
||||||
|
@ -398,7 +398,7 @@ mod tests {
|
||||||
// Valid range header
|
// Valid range header
|
||||||
let request = TestRequest::get()
|
let request = TestRequest::get()
|
||||||
.uri("/t%65st/Cargo.toml")
|
.uri("/t%65st/Cargo.toml")
|
||||||
.header(header::RANGE, "bytes=10-20")
|
.insert_header((header::RANGE, "bytes=10-20"))
|
||||||
.to_request();
|
.to_request();
|
||||||
let response = test::call_service(&mut srv, request).await;
|
let response = test::call_service(&mut srv, request).await;
|
||||||
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
|
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
|
||||||
|
@ -406,7 +406,7 @@ mod tests {
|
||||||
// Invalid range header
|
// Invalid range header
|
||||||
let request = TestRequest::get()
|
let request = TestRequest::get()
|
||||||
.uri("/t%65st/Cargo.toml")
|
.uri("/t%65st/Cargo.toml")
|
||||||
.header(header::RANGE, "bytes=1-0")
|
.insert_header((header::RANGE, "bytes=1-0"))
|
||||||
.to_request();
|
.to_request();
|
||||||
let response = test::call_service(&mut srv, request).await;
|
let response = test::call_service(&mut srv, request).await;
|
||||||
|
|
||||||
|
@ -420,7 +420,7 @@ mod tests {
|
||||||
// Valid range header
|
// Valid range header
|
||||||
let response = srv
|
let response = srv
|
||||||
.get("/tests/test.binary")
|
.get("/tests/test.binary")
|
||||||
.header(header::RANGE, "bytes=10-20")
|
.insert_header((header::RANGE, "bytes=10-20"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -430,7 +430,7 @@ mod tests {
|
||||||
// Invalid range header
|
// Invalid range header
|
||||||
let response = srv
|
let response = srv
|
||||||
.get("/tests/test.binary")
|
.get("/tests/test.binary")
|
||||||
.header(header::RANGE, "bytes=10-5")
|
.insert_header((header::RANGE, "bytes=10-5"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -445,7 +445,7 @@ mod tests {
|
||||||
// Valid range header
|
// Valid range header
|
||||||
let response = srv
|
let response = srv
|
||||||
.get("/tests/test.binary")
|
.get("/tests/test.binary")
|
||||||
.header(header::RANGE, "bytes=10-20")
|
.insert_header((header::RANGE, "bytes=10-20"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -455,7 +455,7 @@ mod tests {
|
||||||
// Valid range header, starting from 0
|
// Valid range header, starting from 0
|
||||||
let response = srv
|
let response = srv
|
||||||
.get("/tests/test.binary")
|
.get("/tests/test.binary")
|
||||||
.header(header::RANGE, "bytes=0-20")
|
.insert_header((header::RANGE, "bytes=0-20"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -560,7 +560,7 @@ mod tests {
|
||||||
|
|
||||||
let request = TestRequest::get()
|
let request = TestRequest::get()
|
||||||
.uri("/")
|
.uri("/")
|
||||||
.header(header::ACCEPT_ENCODING, "gzip")
|
.insert_header((header::ACCEPT_ENCODING, "gzip"))
|
||||||
.to_request();
|
.to_request();
|
||||||
let res = test::call_service(&mut srv, request).await;
|
let res = test::call_service(&mut srv, request).await;
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
@ -580,7 +580,7 @@ mod tests {
|
||||||
|
|
||||||
let request = TestRequest::get()
|
let request = TestRequest::get()
|
||||||
.uri("/")
|
.uri("/")
|
||||||
.header(header::ACCEPT_ENCODING, "gzip")
|
.insert_header((header::ACCEPT_ENCODING, "gzip"))
|
||||||
.to_request();
|
.to_request();
|
||||||
let res = test::call_service(&mut srv, request).await;
|
let res = test::call_service(&mut srv, request).await;
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
|
|
@ -282,16 +282,16 @@ impl NamedFile {
|
||||||
|
|
||||||
if self.flags.contains(Flags::PREFER_UTF8) {
|
if self.flags.contains(Flags::PREFER_UTF8) {
|
||||||
let ct = equiv_utf8_text(self.content_type.clone());
|
let ct = equiv_utf8_text(self.content_type.clone());
|
||||||
res.header(header::CONTENT_TYPE, ct.to_string());
|
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
|
||||||
} else {
|
} else {
|
||||||
res.header(header::CONTENT_TYPE, self.content_type.to_string());
|
res.insert_header((header::CONTENT_TYPE, self.content_type.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
|
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
|
||||||
res.header(
|
res.insert_header((
|
||||||
header::CONTENT_DISPOSITION,
|
header::CONTENT_DISPOSITION,
|
||||||
self.content_disposition.to_string(),
|
self.content_disposition.to_string(),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(current_encoding) = self.encoding {
|
if let Some(current_encoding) = self.encoding {
|
||||||
|
@ -361,16 +361,16 @@ impl NamedFile {
|
||||||
|
|
||||||
if self.flags.contains(Flags::PREFER_UTF8) {
|
if self.flags.contains(Flags::PREFER_UTF8) {
|
||||||
let ct = equiv_utf8_text(self.content_type.clone());
|
let ct = equiv_utf8_text(self.content_type.clone());
|
||||||
resp.header(header::CONTENT_TYPE, ct.to_string());
|
resp.insert_header((header::CONTENT_TYPE, ct.to_string()));
|
||||||
} else {
|
} else {
|
||||||
resp.header(header::CONTENT_TYPE, self.content_type.to_string());
|
resp.insert_header((header::CONTENT_TYPE, self.content_type.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
|
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
|
||||||
resp.header(
|
resp.insert_header((
|
||||||
header::CONTENT_DISPOSITION,
|
header::CONTENT_DISPOSITION,
|
||||||
self.content_disposition.to_string(),
|
self.content_disposition.to_string(),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// default compressing
|
// default compressing
|
||||||
|
@ -379,14 +379,14 @@ impl NamedFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(lm) = last_modified {
|
if let Some(lm) = last_modified {
|
||||||
resp.header(header::LAST_MODIFIED, lm.to_string());
|
resp.insert_header((header::LAST_MODIFIED, lm.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(etag) = etag {
|
if let Some(etag) = etag {
|
||||||
resp.header(header::ETAG, etag.to_string());
|
resp.insert_header((header::ETAG, etag.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.header(header::ACCEPT_RANGES, "bytes");
|
resp.insert_header((header::ACCEPT_RANGES, "bytes"));
|
||||||
|
|
||||||
let mut length = self.md.len();
|
let mut length = self.md.len();
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
@ -399,7 +399,7 @@ impl NamedFile {
|
||||||
offset = ranges[0].start;
|
offset = ranges[0].start;
|
||||||
|
|
||||||
resp.encoding(ContentEncoding::Identity);
|
resp.encoding(ContentEncoding::Identity);
|
||||||
resp.header(
|
resp.insert_header((
|
||||||
header::CONTENT_RANGE,
|
header::CONTENT_RANGE,
|
||||||
format!(
|
format!(
|
||||||
"bytes {}-{}/{}",
|
"bytes {}-{}/{}",
|
||||||
|
@ -407,9 +407,12 @@ impl NamedFile {
|
||||||
offset + length - 1,
|
offset + length - 1,
|
||||||
self.md.len()
|
self.md.len()
|
||||||
),
|
),
|
||||||
);
|
));
|
||||||
} else {
|
} else {
|
||||||
resp.header(header::CONTENT_RANGE, format!("bytes */{}", length));
|
resp.insert_header((
|
||||||
|
header::CONTENT_RANGE,
|
||||||
|
format!("bytes */{}", length),
|
||||||
|
));
|
||||||
return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
|
return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -71,7 +71,7 @@ impl Service<ServiceRequest> for FilesService {
|
||||||
if !is_method_valid {
|
if !is_method_valid {
|
||||||
return Either::Left(ok(req.into_response(
|
return Either::Left(ok(req.into_response(
|
||||||
actix_web::HttpResponse::MethodNotAllowed()
|
actix_web::HttpResponse::MethodNotAllowed()
|
||||||
.header(header::CONTENT_TYPE, "text/plain")
|
.insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8))
|
||||||
.body("Request did not meet this resource's requirements."),
|
.body("Request did not meet this resource's requirements."),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ impl Service<ServiceRequest> for FilesService {
|
||||||
|
|
||||||
return Either::Left(ok(req.into_response(
|
return Either::Left(ok(req.into_response(
|
||||||
HttpResponse::Found()
|
HttpResponse::Found()
|
||||||
.header(header::LOCATION, redirect_to)
|
.insert_header((header::LOCATION, redirect_to))
|
||||||
.body("")
|
.body("")
|
||||||
.into_body(),
|
.into_body(),
|
||||||
)));
|
)));
|
||||||
|
|
|
@ -106,7 +106,6 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
|
||||||
|
|
||||||
Client::builder().connector(connector).finish()
|
Client::builder().connector(connector).finish()
|
||||||
};
|
};
|
||||||
actix_tls::connect::start_default_resolver().await.unwrap();
|
|
||||||
|
|
||||||
TestServer {
|
TestServer {
|
||||||
addr,
|
addr,
|
||||||
|
|
|
@ -1,9 +1,34 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
* `Response::content_type` now takes an `impl IntoHeaderValue` to support `mime` types. [#1894]
|
### Added
|
||||||
|
* `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
|
||||||
|
* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869]
|
||||||
|
* `ResponseBuilder::append_header` method which allows using typed headers. [#1869]
|
||||||
|
* `TestRequest::insert_header` method which allows using typed headers. [#1869]
|
||||||
|
* `ContentEncoding` implements all necessary header traits. [#1912]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed
|
||||||
|
`mime` types. [#1894]
|
||||||
|
* Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
|
||||||
|
`TryInto` trait. [#1894]
|
||||||
|
* `Extensions::insert` returns Option of replaced item. [#1904]
|
||||||
|
* Remove `HttpResponseBuilder::json2()` and make `HttpResponseBuilder::json()` take a value by
|
||||||
|
reference. [#1903]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869]
|
||||||
|
* `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869]
|
||||||
|
* `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869]
|
||||||
|
* `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869]
|
||||||
|
* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869]
|
||||||
|
|
||||||
|
[#1869]: https://github.com/actix/actix-web/pull/1869
|
||||||
[#1894]: https://github.com/actix/actix-web/pull/1894
|
[#1894]: https://github.com/actix/actix-web/pull/1894
|
||||||
|
[#1903]: https://github.com/actix/actix-web/pull/1903
|
||||||
|
[#1904]: https://github.com/actix/actix-web/pull/1904
|
||||||
|
[#1912]: https://github.com/actix/actix-web/pull/1912
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.1 - 2021-01-07
|
## 3.0.0-beta.1 - 2021-01-07
|
||||||
|
@ -34,6 +59,7 @@
|
||||||
[#1864]: https://github.com/actix/actix-web/pull/1864
|
[#1864]: https://github.com/actix/actix-web/pull/1864
|
||||||
[#1878]: https://github.com/actix/actix-web/pull/1878
|
[#1878]: https://github.com/actix/actix-web/pull/1878
|
||||||
|
|
||||||
|
|
||||||
## 2.2.0 - 2020-11-25
|
## 2.2.0 - 2020-11-25
|
||||||
### Added
|
### Added
|
||||||
* HttpResponse builders for 1xx status codes. [#1768]
|
* HttpResponse builders for 1xx status codes. [#1768]
|
||||||
|
|
|
@ -55,10 +55,10 @@ cookie = { version = "0.14.1", features = ["percent-encode"] }
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
either = "1.5.3"
|
either = "1.5.3"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
futures-channel = { version = "0.3.7", default-features = false }
|
futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
|
||||||
fxhash = "0.2.1"
|
ahash = "0.6"
|
||||||
h2 = "0.3.0"
|
h2 = "0.3.0"
|
||||||
http = "0.2.2"
|
http = "0.2.2"
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
|
@ -75,6 +75,7 @@ regex = "1.3"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
sha-1 = "0.9"
|
sha-1 = "0.9"
|
||||||
|
smallvec = "1.6"
|
||||||
slab = "0.4"
|
slab = "0.4"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
time = { version = "0.2.7", default-features = false, features = ["std"] }
|
time = { version = "0.2.7", default-features = false, features = ["std"] }
|
||||||
|
|
|
@ -26,7 +26,10 @@ async fn main() -> io::Result<()> {
|
||||||
info!("request body: {:?}", body);
|
info!("request body: {:?}", body);
|
||||||
Ok::<_, Error>(
|
Ok::<_, Error>(
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.header("x-head", HeaderValue::from_static("dummy value!"))
|
.insert_header((
|
||||||
|
"x-head",
|
||||||
|
HeaderValue::from_static("dummy value!"),
|
||||||
|
))
|
||||||
.body(body),
|
.body(body),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,7 +15,7 @@ async fn handle_request(mut req: Request) -> Result<Response, Error> {
|
||||||
|
|
||||||
info!("request body: {:?}", body);
|
info!("request body: {:?}", body);
|
||||||
Ok(Response::Ok()
|
Ok(Response::Ok()
|
||||||
.header("x-head", HeaderValue::from_static("dummy value!"))
|
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
|
||||||
.body(body))
|
.body(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,10 @@ async fn main() -> io::Result<()> {
|
||||||
.finish(|_req| {
|
.finish(|_req| {
|
||||||
info!("{:?}", _req);
|
info!("{:?}", _req);
|
||||||
let mut res = Response::Ok();
|
let mut res = Response::Ok();
|
||||||
res.header("x-head", HeaderValue::from_static("dummy value!"));
|
res.insert_header((
|
||||||
|
"x-head",
|
||||||
|
HeaderValue::from_static("dummy value!"),
|
||||||
|
));
|
||||||
future::ok::<_, ()>(res.body("Hello world!"))
|
future::ok::<_, ()>(res.body("Hello world!"))
|
||||||
})
|
})
|
||||||
.tcp()
|
.tcp()
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
// These values are taken from hyper/src/proto/h2/client.rs
|
const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2MB
|
||||||
const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2mb
|
const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1MB
|
||||||
const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1mb
|
|
||||||
|
|
||||||
/// Connector configuration
|
/// Connector configuration
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
@ -5,7 +5,8 @@ use std::{fmt, io, time};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::future::{err, Either, FutureExt, LocalBoxFuture, Ready};
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
use futures_util::future::{err, Either, FutureExt, Ready};
|
||||||
use h2::client::SendRequest;
|
use h2::client::SendRequest;
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use actix_tls::connect::resolver::ResolveError;
|
|
||||||
use derive_more::{Display, From};
|
use derive_more::{Display, From};
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
|
@ -23,7 +22,7 @@ pub enum ConnectError {
|
||||||
|
|
||||||
/// Failed to resolve the hostname
|
/// Failed to resolve the hostname
|
||||||
#[display(fmt = "Failed resolving hostname: {}", _0)]
|
#[display(fmt = "Failed resolving hostname: {}", _0)]
|
||||||
Resolver(ResolveError),
|
Resolver(Box<dyn std::error::Error>),
|
||||||
|
|
||||||
/// No dns records
|
/// No dns records
|
||||||
#[display(fmt = "No dns records found for the input")]
|
#[display(fmt = "No dns records found for the input")]
|
||||||
|
|
|
@ -45,7 +45,7 @@ where
|
||||||
Some(port) => write!(wrt, "{}:{}", host, port),
|
Some(port) => write!(wrt, "{}:{}", host, port),
|
||||||
};
|
};
|
||||||
|
|
||||||
match wrt.get_mut().split().freeze().try_into() {
|
match wrt.get_mut().split().freeze().try_into_value() {
|
||||||
Ok(value) => match head {
|
Ok(value) => match head {
|
||||||
RequestHeadType::Owned(ref mut head) => {
|
RequestHeadType::Owned(ref mut head) => {
|
||||||
head.headers.insert(HOST, value)
|
head.headers.insert(HOST, value)
|
||||||
|
|
|
@ -171,7 +171,7 @@ async fn send_body<B: MessageBody>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// release SendRequest object
|
/// release SendRequest object
|
||||||
fn release<T: AsyncRead + AsyncWrite + Unpin + 'static>(
|
fn release<T: AsyncRead + AsyncWrite + Unpin + 'static>(
|
||||||
io: SendRequest<Bytes>,
|
io: SendRequest<Bytes>,
|
||||||
pool: Option<Acquired<T>>,
|
pool: Option<Acquired<T>>,
|
||||||
|
|
|
@ -10,10 +10,11 @@ use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
use actix_rt::time::{sleep, Sleep};
|
use actix_rt::time::{sleep, Sleep};
|
||||||
use actix_service::Service;
|
use actix_service::Service;
|
||||||
use actix_utils::task::LocalWaker;
|
use actix_utils::task::LocalWaker;
|
||||||
|
use ahash::AHashMap;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_channel::oneshot;
|
use futures_channel::oneshot;
|
||||||
use futures_util::future::{poll_fn, FutureExt, LocalBoxFuture};
|
use futures_core::future::LocalBoxFuture;
|
||||||
use fxhash::FxHashMap;
|
use futures_util::future::{poll_fn, FutureExt};
|
||||||
use h2::client::{Connection, SendRequest};
|
use h2::client::{Connection, SendRequest};
|
||||||
use http::uri::Authority;
|
use http::uri::Authority;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
|
@ -59,7 +60,7 @@ where
|
||||||
acquired: 0,
|
acquired: 0,
|
||||||
waiters: Slab::new(),
|
waiters: Slab::new(),
|
||||||
waiters_queue: IndexSet::new(),
|
waiters_queue: IndexSet::new(),
|
||||||
available: FxHashMap::default(),
|
available: AHashMap::default(),
|
||||||
waker: LocalWaker::new(),
|
waker: LocalWaker::new(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -257,7 +258,7 @@ struct AvailableConnection<Io> {
|
||||||
pub(crate) struct Inner<Io> {
|
pub(crate) struct Inner<Io> {
|
||||||
config: ConnectorConfig,
|
config: ConnectorConfig,
|
||||||
acquired: usize,
|
acquired: usize,
|
||||||
available: FxHashMap<Key, VecDeque<AvailableConnection<Io>>>,
|
available: AHashMap<Key, VecDeque<AvailableConnection<Io>>>,
|
||||||
waiters: Slab<
|
waiters: Slab<
|
||||||
Option<(
|
Option<(
|
||||||
Connect,
|
Connect,
|
||||||
|
|
|
@ -9,7 +9,7 @@ use bytes::BytesMut;
|
||||||
use futures_util::{future, FutureExt};
|
use futures_util::{future, FutureExt};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
/// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
||||||
const DATE_VALUE_LENGTH: usize = 29;
|
const DATE_VALUE_LENGTH: usize = 29;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
|
|
@ -18,7 +18,6 @@ use serde::de::value::Error as DeError;
|
||||||
use serde_json::error::Error as JsonError;
|
use serde_json::error::Error as JsonError;
|
||||||
use serde_urlencoded::ser::Error as FormError;
|
use serde_urlencoded::ser::Error as FormError;
|
||||||
|
|
||||||
// re-export for convenience
|
|
||||||
use crate::body::Body;
|
use crate::body::Body;
|
||||||
pub use crate::cookie::ParseError as CookieParseError;
|
pub use crate::cookie::ParseError as CookieParseError;
|
||||||
use crate::helpers::Writer;
|
use crate::helpers::Writer;
|
||||||
|
|
|
@ -1,62 +1,119 @@
|
||||||
use std::any::{Any, TypeId};
|
use std::{
|
||||||
use std::{fmt, mem};
|
any::{Any, TypeId},
|
||||||
|
fmt, mem,
|
||||||
|
};
|
||||||
|
|
||||||
use fxhash::FxHashMap;
|
use ahash::AHashMap;
|
||||||
|
|
||||||
/// A type map of request extensions.
|
/// A type map for request extensions.
|
||||||
|
///
|
||||||
|
/// All entries into this map must be owned types (or static references).
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Extensions {
|
pub struct Extensions {
|
||||||
/// Use FxHasher with a std HashMap with for faster
|
/// Use FxHasher with a std HashMap with for faster
|
||||||
/// lookups on the small `TypeId` (u64 equivalent) keys.
|
/// lookups on the small `TypeId` (u64 equivalent) keys.
|
||||||
map: FxHashMap<TypeId, Box<dyn Any>>,
|
map: AHashMap<TypeId, Box<dyn Any>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Extensions {
|
impl Extensions {
|
||||||
/// Create an empty `Extensions`.
|
/// Creates an empty `Extensions`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> Extensions {
|
pub fn new() -> Extensions {
|
||||||
Extensions {
|
Extensions {
|
||||||
map: FxHashMap::default(),
|
map: AHashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a type into this `Extensions`.
|
/// Insert an item into the map.
|
||||||
///
|
///
|
||||||
/// If a extension of this type already existed, it will
|
/// If an item of this type was already stored, it will be replaced and returned.
|
||||||
/// be returned.
|
///
|
||||||
pub fn insert<T: 'static>(&mut self, val: T) {
|
/// ```
|
||||||
self.map.insert(TypeId::of::<T>(), Box::new(val));
|
/// # 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 container contains entry
|
/// 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 {
|
pub fn contains<T: 'static>(&self) -> bool {
|
||||||
self.map.contains_key(&TypeId::of::<T>())
|
self.map.contains_key(&TypeId::of::<T>())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to a type previously inserted on this `Extensions`.
|
/// 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> {
|
pub fn get<T: 'static>(&self) -> Option<&T> {
|
||||||
self.map
|
self.map
|
||||||
.get(&TypeId::of::<T>())
|
.get(&TypeId::of::<T>())
|
||||||
.and_then(|boxed| boxed.downcast_ref())
|
.and_then(|boxed| boxed.downcast_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to a type previously inserted on this `Extensions`.
|
/// 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> {
|
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||||
self.map
|
self.map
|
||||||
.get_mut(&TypeId::of::<T>())
|
.get_mut(&TypeId::of::<T>())
|
||||||
.and_then(|boxed| boxed.downcast_mut())
|
.and_then(|boxed| boxed.downcast_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a type from this `Extensions`.
|
/// Remove an item from the map of a given type.
|
||||||
///
|
///
|
||||||
/// If a extension of this type existed, it will be returned.
|
/// 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> {
|
pub fn remove<T: 'static>(&mut self) -> Option<T> {
|
||||||
self.map
|
self.map.remove(&TypeId::of::<T>()).and_then(downcast_owned)
|
||||||
.remove(&TypeId::of::<T>())
|
|
||||||
.and_then(|boxed| boxed.downcast().ok().map(|boxed| *boxed))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear the `Extensions` of all inserted extensions.
|
/// 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]
|
#[inline]
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.map.clear();
|
self.map.clear();
|
||||||
|
@ -79,6 +136,10 @@ impl fmt::Debug for Extensions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn downcast_owned<T: 'static>(boxed: Box<dyn Any>) -> Option<T> {
|
||||||
|
boxed.downcast().ok().map(|boxed| *boxed)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -642,83 +642,82 @@ where
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> Result<(), DispatchError> {
|
) -> Result<(), DispatchError> {
|
||||||
let mut this = self.as_mut().project();
|
let mut this = self.as_mut().project();
|
||||||
if this.ka_timer.is_none() {
|
|
||||||
// shutdown timeout
|
|
||||||
if this.flags.contains(Flags::SHUTDOWN) {
|
|
||||||
if let Some(interval) = this.codec.config().client_disconnect_timer() {
|
|
||||||
this.ka_timer.set(Some(sleep_until(interval)));
|
|
||||||
} else {
|
|
||||||
this.flags.insert(Flags::READ_DISCONNECT);
|
|
||||||
if let Some(mut payload) = this.payload.take() {
|
|
||||||
payload.set_error(PayloadError::Incomplete(None));
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx) {
|
// when a branch is not explicit return early it's meant to fall through
|
||||||
Poll::Ready(()) => {
|
// and return as Ok(())
|
||||||
// if we get timeout during shutdown, drop connection
|
match this.ka_timer.as_mut().as_pin_mut() {
|
||||||
|
None => {
|
||||||
|
// conditionally go into shutdown timeout
|
||||||
if this.flags.contains(Flags::SHUTDOWN) {
|
if this.flags.contains(Flags::SHUTDOWN) {
|
||||||
return Err(DispatchError::DisconnectTimeout);
|
if let Some(deadline) = this.codec.config().client_disconnect_timer()
|
||||||
} else if this.ka_timer.as_mut().as_pin_mut().unwrap().deadline()
|
{
|
||||||
>= *this.ka_expire
|
// write client disconnect time out and poll again to
|
||||||
{
|
// go into Some<Pin<&mut Sleep>> branch
|
||||||
// check for any outstanding tasks
|
this.ka_timer.set(Some(sleep_until(deadline)));
|
||||||
if this.state.is_empty() && this.write_buf.is_empty() {
|
return self.poll_keepalive(cx);
|
||||||
if this.flags.contains(Flags::STARTED) {
|
} else {
|
||||||
trace!("Keep-alive timeout, close connection");
|
this.flags.insert(Flags::READ_DISCONNECT);
|
||||||
this.flags.insert(Flags::SHUTDOWN);
|
if let Some(mut payload) = this.payload.take() {
|
||||||
|
payload.set_error(PayloadError::Incomplete(None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(mut timer) => {
|
||||||
|
// only operate when keep-alive timer is resolved.
|
||||||
|
if timer.as_mut().poll(cx).is_ready() {
|
||||||
|
// got timeout during shutdown, drop connection
|
||||||
|
if this.flags.contains(Flags::SHUTDOWN) {
|
||||||
|
return Err(DispatchError::DisconnectTimeout);
|
||||||
|
// exceed deadline. check for any outstanding tasks
|
||||||
|
} else if timer.deadline() >= *this.ka_expire {
|
||||||
|
// have no task at hand.
|
||||||
|
if this.state.is_empty() && this.write_buf.is_empty() {
|
||||||
|
if this.flags.contains(Flags::STARTED) {
|
||||||
|
trace!("Keep-alive timeout, close connection");
|
||||||
|
this.flags.insert(Flags::SHUTDOWN);
|
||||||
|
|
||||||
// start shutdown timer
|
// start shutdown timeout
|
||||||
if let Some(deadline) =
|
if let Some(deadline) =
|
||||||
this.codec.config().client_disconnect_timer()
|
this.codec.config().client_disconnect_timer()
|
||||||
{
|
|
||||||
if let Some(mut timer) =
|
|
||||||
this.ka_timer.as_mut().as_pin_mut()
|
|
||||||
{
|
{
|
||||||
timer.as_mut().reset(deadline);
|
timer.as_mut().reset(deadline);
|
||||||
let _ = timer.poll(cx);
|
let _ = timer.poll(cx);
|
||||||
|
} else {
|
||||||
|
// no shutdown timeout, drop socket
|
||||||
|
this.flags.insert(Flags::WRITE_DISCONNECT);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// no shutdown timeout, drop socket
|
// timeout on first request (slow request) return 408
|
||||||
this.flags.insert(Flags::WRITE_DISCONNECT);
|
if !this.flags.contains(Flags::STARTED) {
|
||||||
return Ok(());
|
trace!("Slow request timeout");
|
||||||
|
let _ = self.as_mut().send_response(
|
||||||
|
Response::RequestTimeout().finish().drop_body(),
|
||||||
|
ResponseBody::Other(Body::Empty),
|
||||||
|
);
|
||||||
|
this = self.project();
|
||||||
|
} else {
|
||||||
|
trace!("Keep-alive connection timeout");
|
||||||
|
}
|
||||||
|
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
|
||||||
|
this.state.set(State::None);
|
||||||
}
|
}
|
||||||
} else {
|
// still have unfinished task. try to reset and register keep-alive.
|
||||||
// timeout on first request (slow request) return 408
|
} else if let Some(deadline) =
|
||||||
if !this.flags.contains(Flags::STARTED) {
|
this.codec.config().keep_alive_expire()
|
||||||
trace!("Slow request timeout");
|
{
|
||||||
let _ = self.as_mut().send_response(
|
|
||||||
Response::RequestTimeout().finish().drop_body(),
|
|
||||||
ResponseBody::Other(Body::Empty),
|
|
||||||
);
|
|
||||||
this = self.as_mut().project();
|
|
||||||
} else {
|
|
||||||
trace!("Keep-alive connection timeout");
|
|
||||||
}
|
|
||||||
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
|
|
||||||
this.state.set(State::None);
|
|
||||||
}
|
|
||||||
} else if let Some(deadline) =
|
|
||||||
this.codec.config().keep_alive_expire()
|
|
||||||
{
|
|
||||||
if let Some(mut timer) = this.ka_timer.as_mut().as_pin_mut() {
|
|
||||||
timer.as_mut().reset(deadline);
|
timer.as_mut().reset(deadline);
|
||||||
let _ = timer.poll(cx);
|
let _ = timer.poll(cx);
|
||||||
}
|
}
|
||||||
|
// timer resolved but still have not met the keep-alive expire deadline.
|
||||||
|
// reset and register for later wakeup.
|
||||||
|
} else {
|
||||||
|
timer.as_mut().reset(*this.ka_expire);
|
||||||
|
let _ = timer.poll(cx);
|
||||||
}
|
}
|
||||||
} else if let Some(mut timer) = this.ka_timer.as_mut().as_pin_mut() {
|
|
||||||
timer.as_mut().reset(*this.ka_expire);
|
|
||||||
let _ = timer.poll(cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Poll::Pending => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,50 +32,36 @@ header! {
|
||||||
/// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c`
|
/// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c`
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
|
||||||
/// extern crate mime;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{Accept, qitem};
|
/// use actix_http::http::header::{Accept, qitem};
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
///
|
/// builder.insert_header(
|
||||||
/// builder.set(
|
|
||||||
/// Accept(vec![
|
/// Accept(vec![
|
||||||
/// qitem(mime::TEXT_HTML),
|
/// qitem(mime::TEXT_HTML),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
|
||||||
/// extern crate mime;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{Accept, qitem};
|
/// use actix_http::http::header::{Accept, qitem};
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
///
|
/// builder.insert_header(
|
||||||
/// builder.set(
|
|
||||||
/// Accept(vec![
|
/// Accept(vec![
|
||||||
/// qitem(mime::APPLICATION_JSON),
|
/// qitem(mime::APPLICATION_JSON),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
|
||||||
/// extern crate mime;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{Accept, QualityItem, q, qitem};
|
/// use actix_http::http::header::{Accept, QualityItem, q, qitem};
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
///
|
/// builder.insert_header(
|
||||||
/// builder.set(
|
|
||||||
/// Accept(vec![
|
/// Accept(vec![
|
||||||
/// qitem(mime::TEXT_HTML),
|
/// qitem(mime::TEXT_HTML),
|
||||||
/// qitem("application/xhtml+xml".parse().unwrap()),
|
/// qitem("application/xhtml+xml".parse().unwrap()),
|
||||||
|
@ -90,7 +76,6 @@ header! {
|
||||||
/// ),
|
/// ),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
(Accept, header::ACCEPT) => (QualityItem<Mime>)+
|
(Accept, header::ACCEPT) => (QualityItem<Mime>)+
|
||||||
|
|
||||||
|
@ -132,7 +117,7 @@ header! {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fuzzing1() {
|
fn test_fuzzing1() {
|
||||||
use crate::test::TestRequest;
|
use crate::test::TestRequest;
|
||||||
let req = TestRequest::with_header(crate::header::ACCEPT, "chunk#;e").finish();
|
let req = TestRequest::default().insert_header((crate::header::ACCEPT, "chunk#;e")).finish();
|
||||||
let header = Accept::parse(&req);
|
let header = Accept::parse(&req);
|
||||||
assert!(header.is_ok());
|
assert!(header.is_ok());
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,44 +21,37 @@ header! {
|
||||||
/// * `iso-8859-5, unicode-1-1;q=0.8`
|
/// * `iso-8859-5, unicode-1-1;q=0.8`
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{AcceptCharset, Charset, qitem};
|
/// use actix_http::http::header::{AcceptCharset, Charset, qitem};
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// AcceptCharset(vec![qitem(Charset::Us_Ascii)])
|
/// AcceptCharset(vec![qitem(Charset::Us_Ascii)])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
/// ```rust
|
///
|
||||||
/// # extern crate actix_http;
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem};
|
/// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem};
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// AcceptCharset(vec![
|
/// AcceptCharset(vec![
|
||||||
/// QualityItem::new(Charset::Us_Ascii, q(900)),
|
/// QualityItem::new(Charset::Us_Ascii, q(900)),
|
||||||
/// QualityItem::new(Charset::Iso_8859_10, q(200)),
|
/// QualityItem::new(Charset::Iso_8859_10, q(200)),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
/// ```rust
|
///
|
||||||
/// # extern crate actix_http;
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{AcceptCharset, Charset, qitem};
|
/// use actix_http::http::header::{AcceptCharset, Charset, qitem};
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))])
|
/// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
(AcceptCharset, ACCEPT_CHARSET) => (QualityItem<Charset>)+
|
(AcceptCharset, ACCEPT_CHARSET) => (QualityItem<Charset>)+
|
||||||
|
|
||||||
|
|
|
@ -22,41 +22,35 @@ header! {
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
/// use language_tags::langtag;
|
||||||
/// # extern crate language_tags;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem};
|
/// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem};
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// let mut langtag: LanguageTag = Default::default();
|
/// let mut langtag: LanguageTag = Default::default();
|
||||||
/// langtag.language = Some("en".to_owned());
|
/// langtag.language = Some("en".to_owned());
|
||||||
/// langtag.region = Some("US".to_owned());
|
/// langtag.region = Some("US".to_owned());
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// AcceptLanguage(vec![
|
/// AcceptLanguage(vec![
|
||||||
/// qitem(langtag),
|
/// qitem(langtag),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
/// use language_tags::langtag;
|
||||||
/// # #[macro_use] extern crate language_tags;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem};
|
/// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem};
|
||||||
/// #
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// AcceptLanguage(vec![
|
/// AcceptLanguage(vec![
|
||||||
/// qitem(langtag!(da)),
|
/// qitem(langtag!(da)),
|
||||||
/// QualityItem::new(langtag!(en;;;GB), q(800)),
|
/// QualityItem::new(langtag!(en;;;GB), q(800)),
|
||||||
/// QualityItem::new(langtag!(en), q(700)),
|
/// QualityItem::new(langtag!(en), q(700)),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
(AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem<LanguageTag>)+
|
(AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem<LanguageTag>)+
|
||||||
|
|
||||||
|
|
|
@ -22,38 +22,28 @@ header! {
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate http;
|
|
||||||
/// # extern crate actix_http;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::Allow;
|
/// use actix_http::http::{header::Allow, Method};
|
||||||
/// use http::Method;
|
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// Allow(vec![Method::GET])
|
/// Allow(vec![Method::GET])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate http;
|
|
||||||
/// # extern crate actix_http;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::Allow;
|
/// use actix_http::http::{header::Allow, Method};
|
||||||
/// use http::Method;
|
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// Allow(vec![
|
/// Allow(vec![
|
||||||
/// Method::GET,
|
/// Method::GET,
|
||||||
/// Method::POST,
|
/// Method::POST,
|
||||||
/// Method::PATCH,
|
/// Method::PATCH,
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
(Allow, header::ALLOW) => (Method)*
|
(Allow, header::ALLOW) => (Method)*
|
||||||
|
|
||||||
|
|
|
@ -28,12 +28,12 @@ use crate::header::{
|
||||||
/// * `max-age=30`
|
/// * `max-age=30`
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{CacheControl, CacheDirective};
|
/// use actix_http::http::header::{CacheControl, CacheDirective};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)]));
|
/// builder.insert_header(CacheControl(vec![CacheDirective::MaxAge(86400u32)]));
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
@ -41,7 +41,7 @@ use crate::header::{
|
||||||
/// use actix_http::http::header::{CacheControl, CacheDirective};
|
/// use actix_http::http::header::{CacheControl, CacheDirective};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(CacheControl(vec![
|
/// builder.insert_header(CacheControl(vec![
|
||||||
/// CacheDirective::NoCache,
|
/// CacheDirective::NoCache,
|
||||||
/// CacheDirective::Private,
|
/// CacheDirective::Private,
|
||||||
/// CacheDirective::MaxAge(360u32),
|
/// CacheDirective::MaxAge(360u32),
|
||||||
|
@ -82,7 +82,7 @@ impl fmt::Display for CacheControl {
|
||||||
impl IntoHeaderValue for CacheControl {
|
impl IntoHeaderValue for CacheControl {
|
||||||
type Error = header::InvalidHeaderValue;
|
type Error = header::InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<header::HeaderValue, Self::Error> {
|
||||||
let mut writer = Writer::new();
|
let mut writer = Writer::new();
|
||||||
let _ = write!(&mut writer, "{}", self);
|
let _ = write!(&mut writer, "{}", self);
|
||||||
header::HeaderValue::from_maybe_shared(writer.take())
|
header::HeaderValue::from_maybe_shared(writer.take())
|
||||||
|
@ -196,7 +196,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_multiple_headers() {
|
fn test_parse_multiple_headers() {
|
||||||
let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private")
|
let req = TestRequest::default()
|
||||||
|
.insert_header((header::CACHE_CONTROL, "no-cache, private"))
|
||||||
.finish();
|
.finish();
|
||||||
let cache = Header::parse(&req);
|
let cache = Header::parse(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -210,9 +211,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_argument() {
|
fn test_parse_argument() {
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private")
|
.insert_header((header::CACHE_CONTROL, "max-age=100, private"))
|
||||||
.finish();
|
.finish();
|
||||||
let cache = Header::parse(&req);
|
let cache = Header::parse(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cache.ok(),
|
cache.ok(),
|
||||||
|
@ -225,8 +226,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_quote_form() {
|
fn test_parse_quote_form() {
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish();
|
.insert_header((header::CACHE_CONTROL, "max-age=\"200\""))
|
||||||
|
.finish();
|
||||||
let cache = Header::parse(&req);
|
let cache = Header::parse(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cache.ok(),
|
cache.ok(),
|
||||||
|
@ -236,8 +238,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_extension() {
|
fn test_parse_extension() {
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish();
|
.insert_header((header::CACHE_CONTROL, "foo, bar=baz"))
|
||||||
|
.finish();
|
||||||
let cache = Header::parse(&req);
|
let cache = Header::parse(&req);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cache.ok(),
|
cache.ok(),
|
||||||
|
@ -250,7 +253,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_bad_syntax() {
|
fn test_parse_bad_syntax() {
|
||||||
let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish();
|
let req = TestRequest::default()
|
||||||
|
.insert_header((header::CACHE_CONTROL, "foo="))
|
||||||
|
.finish();
|
||||||
let cache: Result<CacheControl, _> = Header::parse(&req);
|
let cache: Result<CacheControl, _> = Header::parse(&req);
|
||||||
assert_eq!(cache.ok(), None)
|
assert_eq!(cache.ok(), None)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// # References
|
//! # References
|
||||||
//
|
//!
|
||||||
// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt
|
//! "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt
|
||||||
// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt
|
//! "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt
|
||||||
// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt
|
//! "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt
|
||||||
// Browser conformance tests at: http://greenbytes.de/tech/tc2231/
|
//! Browser conformance tests at: http://greenbytes.de/tech/tc2231/
|
||||||
// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml
|
//! IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
@ -454,7 +454,7 @@ impl ContentDisposition {
|
||||||
impl IntoHeaderValue for ContentDisposition {
|
impl IntoHeaderValue for ContentDisposition {
|
||||||
type Error = header::InvalidHeaderValue;
|
type Error = header::InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<header::HeaderValue, Self::Error> {
|
||||||
let mut writer = Writer::new();
|
let mut writer = Writer::new();
|
||||||
let _ = write!(&mut writer, "{}", self);
|
let _ = write!(&mut writer, "{}", self);
|
||||||
header::HeaderValue::from_maybe_shared(writer.take())
|
header::HeaderValue::from_maybe_shared(writer.take())
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
use std::{convert::Infallible, str::FromStr};
|
||||||
|
|
||||||
|
use http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::ParseError,
|
||||||
|
header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, IntoHeaderValue},
|
||||||
|
HttpMessage,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents a supported content encoding.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum ContentEncoding {
|
||||||
|
/// Automatically select encoding based on encoding negotiation.
|
||||||
|
Auto,
|
||||||
|
|
||||||
|
/// A format using the Brotli algorithm.
|
||||||
|
Br,
|
||||||
|
|
||||||
|
/// A format using the zlib structure with deflate algorithm.
|
||||||
|
Deflate,
|
||||||
|
|
||||||
|
/// Gzip algorithm.
|
||||||
|
Gzip,
|
||||||
|
|
||||||
|
/// Indicates the identity function (i.e. no compression, nor modification).
|
||||||
|
Identity,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContentEncoding {
|
||||||
|
/// Is the content compressed?
|
||||||
|
#[inline]
|
||||||
|
pub fn is_compression(self) -> bool {
|
||||||
|
matches!(self, ContentEncoding::Identity | ContentEncoding::Auto)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert content encoding to string
|
||||||
|
#[inline]
|
||||||
|
pub fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ContentEncoding::Br => "br",
|
||||||
|
ContentEncoding::Gzip => "gzip",
|
||||||
|
ContentEncoding::Deflate => "deflate",
|
||||||
|
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default Q-factor (quality) value.
|
||||||
|
#[inline]
|
||||||
|
pub fn quality(self) -> f64 {
|
||||||
|
match self {
|
||||||
|
ContentEncoding::Br => 1.1,
|
||||||
|
ContentEncoding::Gzip => 1.0,
|
||||||
|
ContentEncoding::Deflate => 0.9,
|
||||||
|
ContentEncoding::Identity | ContentEncoding::Auto => 0.1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ContentEncoding {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Identity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ContentEncoding {
|
||||||
|
type Err = Infallible;
|
||||||
|
|
||||||
|
fn from_str(val: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Self::from(val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for ContentEncoding {
|
||||||
|
fn from(val: &str) -> ContentEncoding {
|
||||||
|
let val = val.trim();
|
||||||
|
|
||||||
|
if val.eq_ignore_ascii_case("br") {
|
||||||
|
ContentEncoding::Br
|
||||||
|
} else if val.eq_ignore_ascii_case("gzip") {
|
||||||
|
ContentEncoding::Gzip
|
||||||
|
} else if val.eq_ignore_ascii_case("deflate") {
|
||||||
|
ContentEncoding::Deflate
|
||||||
|
} else {
|
||||||
|
ContentEncoding::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for ContentEncoding {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
fn try_into_value(self) -> Result<http::HeaderValue, Self::Error> {
|
||||||
|
Ok(HeaderValue::from_static(self.as_str()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header for ContentEncoding {
|
||||||
|
fn name() -> HeaderName {
|
||||||
|
header::CONTENT_ENCODING
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError> {
|
||||||
|
from_one_raw_str(msg.headers().get(Self::name()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,38 +23,31 @@ header! {
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
/// use language_tags::langtag;
|
||||||
/// # #[macro_use] extern crate language_tags;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// # use actix_http::http::header::{ContentLanguage, qitem};
|
/// use actix_http::http::header::{ContentLanguage, qitem};
|
||||||
/// #
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// ContentLanguage(vec![
|
/// ContentLanguage(vec![
|
||||||
/// qitem(langtag!(en)),
|
/// qitem(langtag!(en)),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate actix_http;
|
/// use language_tags::langtag;
|
||||||
/// # #[macro_use] extern crate language_tags;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// # use actix_http::http::header::{ContentLanguage, qitem};
|
/// use actix_http::http::header::{ContentLanguage, qitem};
|
||||||
/// #
|
|
||||||
/// # fn main() {
|
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// ContentLanguage(vec![
|
/// ContentLanguage(vec![
|
||||||
/// qitem(langtag!(da)),
|
/// qitem(langtag!(da)),
|
||||||
/// qitem(langtag!(en;;;GB)),
|
/// qitem(langtag!(en;;;GB)),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
(ContentLanguage, CONTENT_LANGUAGE) => (QualityItem<LanguageTag>)+
|
(ContentLanguage, CONTENT_LANGUAGE) => (QualityItem<LanguageTag>)+
|
||||||
|
|
||||||
|
|
|
@ -200,7 +200,7 @@ impl Display for ContentRangeSpec {
|
||||||
impl IntoHeaderValue for ContentRangeSpec {
|
impl IntoHeaderValue for ContentRangeSpec {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
let mut writer = Writer::new();
|
let mut writer = Writer::new();
|
||||||
let _ = write!(&mut writer, "{}", self);
|
let _ = write!(&mut writer, "{}", self);
|
||||||
HeaderValue::from_maybe_shared(writer.take())
|
HeaderValue::from_maybe_shared(writer.take())
|
||||||
|
|
|
@ -30,31 +30,24 @@ header! {
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::ContentType;
|
/// use actix_http::http::header::ContentType;
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// ContentType::json()
|
/// ContentType::json()
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// # extern crate mime;
|
|
||||||
/// # extern crate actix_http;
|
|
||||||
/// use mime::TEXT_HTML;
|
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::ContentType;
|
/// use actix_http::http::header::ContentType;
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// ContentType(TEXT_HTML)
|
/// ContentType(mime::TEXT_HTML)
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
|
||||||
/// ```
|
/// ```
|
||||||
(ContentType, CONTENT_TYPE) => [Mime]
|
(ContentType, CONTENT_TYPE) => [Mime]
|
||||||
|
|
||||||
|
@ -99,6 +92,7 @@ impl ContentType {
|
||||||
pub fn form_url_encoded() -> ContentType {
|
pub fn form_url_encoded() -> ContentType {
|
||||||
ContentType(mime::APPLICATION_WWW_FORM_URLENCODED)
|
ContentType(mime::APPLICATION_WWW_FORM_URLENCODED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create a `Content-Type: image/jpeg` header.
|
/// A constructor to easily create a `Content-Type: image/jpeg` header.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn jpeg() -> ContentType {
|
pub fn jpeg() -> ContentType {
|
||||||
|
|
|
@ -19,13 +19,15 @@ header! {
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
|
/// use std::time::SystemTime;
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::Date;
|
/// use actix_http::http::header::Date;
|
||||||
/// use std::time::SystemTime;
|
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(Date(SystemTime::now().into()));
|
/// builder.insert_header(
|
||||||
|
/// Date(SystemTime::now().into())
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
(Date, DATE) => [HttpDate]
|
(Date, DATE) => [HttpDate]
|
||||||
|
|
||||||
|
|
|
@ -27,20 +27,24 @@ header! {
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{ETag, EntityTag};
|
/// use actix_http::http::header::{ETag, EntityTag};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned())));
|
/// builder.insert_header(
|
||||||
|
/// ETag(EntityTag::new(false, "xyzzy".to_owned()))
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{ETag, EntityTag};
|
/// use actix_http::http::header::{ETag, EntityTag};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned())));
|
/// builder.insert_header(
|
||||||
|
/// ETag(EntityTag::new(true, "xyzzy".to_owned()))
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
(ETag, ETAG) => [EntityTag]
|
(ETag, ETAG) => [EntityTag]
|
||||||
|
|
||||||
|
|
|
@ -21,14 +21,16 @@ header! {
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
|
/// use std::time::{SystemTime, Duration};
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::Expires;
|
/// use actix_http::http::header::Expires;
|
||||||
/// use std::time::{SystemTime, Duration};
|
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24);
|
/// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24);
|
||||||
/// builder.set(Expires(expiration.into()));
|
/// builder.insert_header(
|
||||||
|
/// Expires(expiration.into())
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
(Expires, EXPIRES) => [HttpDate]
|
(Expires, EXPIRES) => [HttpDate]
|
||||||
|
|
||||||
|
|
|
@ -29,20 +29,20 @@ header! {
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::IfMatch;
|
/// use actix_http::http::header::IfMatch;
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(IfMatch::Any);
|
/// builder.insert_header(IfMatch::Any);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{IfMatch, EntityTag};
|
/// use actix_http::http::header::{IfMatch, EntityTag};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// IfMatch::Items(vec![
|
/// IfMatch::Items(vec![
|
||||||
/// EntityTag::new(false, "xyzzy".to_owned()),
|
/// EntityTag::new(false, "xyzzy".to_owned()),
|
||||||
/// EntityTag::new(false, "foobar".to_owned()),
|
/// EntityTag::new(false, "foobar".to_owned()),
|
||||||
|
|
|
@ -21,14 +21,16 @@ header! {
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
|
/// use std::time::{SystemTime, Duration};
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::IfModifiedSince;
|
/// use actix_http::http::header::IfModifiedSince;
|
||||||
/// use std::time::{SystemTime, Duration};
|
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||||
/// builder.set(IfModifiedSince(modified.into()));
|
/// builder.insert_header(
|
||||||
|
/// IfModifiedSince(modified.into())
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
(IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate]
|
(IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate]
|
||||||
|
|
||||||
|
|
|
@ -31,20 +31,20 @@ header! {
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::IfNoneMatch;
|
/// use actix_http::http::header::IfNoneMatch;
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(IfNoneMatch::Any);
|
/// builder.insert_header(IfNoneMatch::Any);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{IfNoneMatch, EntityTag};
|
/// use actix_http::http::header::{IfNoneMatch, EntityTag};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(
|
/// builder.insert_header(
|
||||||
/// IfNoneMatch::Items(vec![
|
/// IfNoneMatch::Items(vec![
|
||||||
/// EntityTag::new(false, "xyzzy".to_owned()),
|
/// EntityTag::new(false, "xyzzy".to_owned()),
|
||||||
/// EntityTag::new(false, "foobar".to_owned()),
|
/// EntityTag::new(false, "foobar".to_owned()),
|
||||||
|
@ -73,13 +73,15 @@ mod tests {
|
||||||
fn test_if_none_match() {
|
fn test_if_none_match() {
|
||||||
let mut if_none_match: Result<IfNoneMatch, _>;
|
let mut if_none_match: Result<IfNoneMatch, _>;
|
||||||
|
|
||||||
let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish();
|
let req = TestRequest::default()
|
||||||
|
.insert_header((IF_NONE_MATCH, "*"))
|
||||||
|
.finish();
|
||||||
if_none_match = Header::parse(&req);
|
if_none_match = Header::parse(&req);
|
||||||
assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any));
|
assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any));
|
||||||
|
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..])
|
.insert_header((IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]))
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
if_none_match = Header::parse(&req);
|
if_none_match = Header::parse(&req);
|
||||||
let mut entities: Vec<EntityTag> = Vec::new();
|
let mut entities: Vec<EntityTag> = Vec::new();
|
||||||
|
|
|
@ -35,31 +35,34 @@ use crate::httpmessage::HttpMessage;
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::{EntityTag, IfRange};
|
/// use actix_http::http::header::{EntityTag, IfRange};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// builder.set(IfRange::EntityTag(EntityTag::new(
|
/// builder.insert_header(
|
||||||
/// false,
|
/// IfRange::EntityTag(
|
||||||
/// "xyzzy".to_owned(),
|
/// EntityTag::new(false, "abc".to_owned())
|
||||||
/// )));
|
/// )
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use actix_http::Response;
|
|
||||||
/// use actix_http::http::header::IfRange;
|
|
||||||
/// use std::time::{Duration, SystemTime};
|
/// use std::time::{Duration, SystemTime};
|
||||||
|
/// use actix_http::{http::header::IfRange, Response};
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||||
/// builder.set(IfRange::Date(fetched.into()));
|
/// builder.insert_header(
|
||||||
|
/// IfRange::Date(fetched.into())
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum IfRange {
|
pub enum IfRange {
|
||||||
/// The entity-tag the client has of the resource
|
/// The entity-tag the client has of the resource.
|
||||||
EntityTag(EntityTag),
|
EntityTag(EntityTag),
|
||||||
/// The date when the client retrieved the resource
|
|
||||||
|
/// The date when the client retrieved the resource.
|
||||||
Date(HttpDate),
|
Date(HttpDate),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +101,7 @@ impl Display for IfRange {
|
||||||
impl IntoHeaderValue for IfRange {
|
impl IntoHeaderValue for IfRange {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
let mut writer = Writer::new();
|
let mut writer = Writer::new();
|
||||||
let _ = write!(&mut writer, "{}", self);
|
let _ = write!(&mut writer, "{}", self);
|
||||||
HeaderValue::from_maybe_shared(writer.take())
|
HeaderValue::from_maybe_shared(writer.take())
|
||||||
|
@ -110,7 +113,8 @@ mod test_if_range {
|
||||||
use super::IfRange as HeaderField;
|
use super::IfRange as HeaderField;
|
||||||
use crate::header::*;
|
use crate::header::*;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
|
test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
|
||||||
test_header!(test2, vec![b"\"xyzzy\""]);
|
test_header!(test2, vec![b"\"abc\""]);
|
||||||
test_header!(test3, vec![b"this-is-invalid"], None::<IfRange>);
|
test_header!(test3, vec![b"this-is-invalid"], None::<IfRange>);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,14 +22,16 @@ header! {
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
|
/// use std::time::{SystemTime, Duration};
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::IfUnmodifiedSince;
|
/// use actix_http::http::header::IfUnmodifiedSince;
|
||||||
/// use std::time::{SystemTime, Duration};
|
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||||
/// builder.set(IfUnmodifiedSince(modified.into()));
|
/// builder.insert_header(
|
||||||
|
/// IfUnmodifiedSince(modified.into())
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
(IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate]
|
(IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate]
|
||||||
|
|
||||||
|
|
|
@ -21,14 +21,16 @@ header! {
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```
|
||||||
|
/// use std::time::{SystemTime, Duration};
|
||||||
/// use actix_http::Response;
|
/// use actix_http::Response;
|
||||||
/// use actix_http::http::header::LastModified;
|
/// use actix_http::http::header::LastModified;
|
||||||
/// use std::time::{SystemTime, Duration};
|
|
||||||
///
|
///
|
||||||
/// let mut builder = Response::Ok();
|
/// let mut builder = Response::Ok();
|
||||||
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||||
/// builder.set(LastModified(modified.into()));
|
/// builder.insert_header(
|
||||||
|
/// LastModified(modified.into())
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
(LastModified, LAST_MODIFIED) => [HttpDate]
|
(LastModified, LAST_MODIFIED) => [HttpDate]
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ pub use self::content_disposition::{
|
||||||
};
|
};
|
||||||
pub use self::content_language::ContentLanguage;
|
pub use self::content_language::ContentLanguage;
|
||||||
pub use self::content_range::{ContentRange, ContentRangeSpec};
|
pub use self::content_range::{ContentRange, ContentRangeSpec};
|
||||||
|
pub use self::content_encoding::{ContentEncoding};
|
||||||
pub use self::content_type::ContentType;
|
pub use self::content_type::ContentType;
|
||||||
pub use self::date::Date;
|
pub use self::date::Date;
|
||||||
pub use self::etag::ETag;
|
pub use self::etag::ETag;
|
||||||
|
@ -83,7 +84,7 @@ macro_rules! test_header {
|
||||||
let a: Vec<Vec<u8>> = raw.iter().map(|x| x.to_vec()).collect();
|
let a: Vec<Vec<u8>> = raw.iter().map(|x| x.to_vec()).collect();
|
||||||
let mut req = test::TestRequest::default();
|
let mut req = test::TestRequest::default();
|
||||||
for item in a {
|
for item in a {
|
||||||
req = req.header(HeaderField::name(), item).take();
|
req = req.insert_header((HeaderField::name(), item)).take();
|
||||||
}
|
}
|
||||||
let req = req.finish();
|
let req = req.finish();
|
||||||
let value = HeaderField::parse(&req);
|
let value = HeaderField::parse(&req);
|
||||||
|
@ -110,7 +111,7 @@ macro_rules! test_header {
|
||||||
let a: Vec<Vec<u8>> = $raw.iter().map(|x| x.to_vec()).collect();
|
let a: Vec<Vec<u8>> = $raw.iter().map(|x| x.to_vec()).collect();
|
||||||
let mut req = test::TestRequest::default();
|
let mut req = test::TestRequest::default();
|
||||||
for item in a {
|
for item in a {
|
||||||
req.header(HeaderField::name(), item);
|
req.insert_header((HeaderField::name(), item));
|
||||||
}
|
}
|
||||||
let req = req.finish();
|
let req = req.finish();
|
||||||
let val = HeaderField::parse(&req);
|
let val = HeaderField::parse(&req);
|
||||||
|
@ -168,7 +169,7 @@ macro_rules! header {
|
||||||
impl $crate::http::header::IntoHeaderValue for $id {
|
impl $crate::http::header::IntoHeaderValue for $id {
|
||||||
type Error = $crate::http::header::InvalidHeaderValue;
|
type Error = $crate::http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let mut writer = $crate::http::header::Writer::new();
|
let mut writer = $crate::http::header::Writer::new();
|
||||||
let _ = write!(&mut writer, "{}", self);
|
let _ = write!(&mut writer, "{}", self);
|
||||||
|
@ -204,7 +205,7 @@ macro_rules! header {
|
||||||
impl $crate::http::header::IntoHeaderValue for $id {
|
impl $crate::http::header::IntoHeaderValue for $id {
|
||||||
type Error = $crate::http::header::InvalidHeaderValue;
|
type Error = $crate::http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let mut writer = $crate::http::header::Writer::new();
|
let mut writer = $crate::http::header::Writer::new();
|
||||||
let _ = write!(&mut writer, "{}", self);
|
let _ = write!(&mut writer, "{}", self);
|
||||||
|
@ -240,8 +241,8 @@ macro_rules! header {
|
||||||
impl $crate::http::header::IntoHeaderValue for $id {
|
impl $crate::http::header::IntoHeaderValue for $id {
|
||||||
type Error = $crate::http::header::InvalidHeaderValue;
|
type Error = $crate::http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
||||||
self.0.try_into()
|
self.0.try_into_value()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -289,7 +290,7 @@ macro_rules! header {
|
||||||
impl $crate::http::header::IntoHeaderValue for $id {
|
impl $crate::http::header::IntoHeaderValue for $id {
|
||||||
type Error = $crate::http::header::InvalidHeaderValue;
|
type Error = $crate::http::header::InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let mut writer = $crate::http::header::Writer::new();
|
let mut writer = $crate::http::header::Writer::new();
|
||||||
let _ = write!(&mut writer, "{}", self);
|
let _ = write!(&mut writer, "{}", self);
|
||||||
|
@ -333,13 +334,14 @@ macro_rules! header {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod accept_charset;
|
mod accept_charset;
|
||||||
//mod accept_encoding;
|
// mod accept_encoding;
|
||||||
mod accept;
|
mod accept;
|
||||||
mod accept_language;
|
mod accept_language;
|
||||||
mod allow;
|
mod allow;
|
||||||
mod cache_control;
|
mod cache_control;
|
||||||
mod content_disposition;
|
mod content_disposition;
|
||||||
mod content_language;
|
mod content_language;
|
||||||
|
mod content_encoding;
|
||||||
mod content_range;
|
mod content_range;
|
||||||
mod content_type;
|
mod content_type;
|
||||||
mod date;
|
mod date;
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use http::{
|
||||||
|
header::{HeaderName, InvalidHeaderName, InvalidHeaderValue},
|
||||||
|
Error as HttpError, HeaderValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Header, IntoHeaderValue};
|
||||||
|
|
||||||
|
/// Transforms structures into header K/V pairs for inserting into `HeaderMap`s.
|
||||||
|
pub trait IntoHeaderPair: Sized {
|
||||||
|
type Error: Into<HttpError>;
|
||||||
|
|
||||||
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InvalidHeaderPart {
|
||||||
|
Name(InvalidHeaderName),
|
||||||
|
Value(InvalidHeaderValue),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InvalidHeaderPart> for HttpError {
|
||||||
|
fn from(part_err: InvalidHeaderPart) -> Self {
|
||||||
|
match part_err {
|
||||||
|
InvalidHeaderPart::Name(err) => err.into(),
|
||||||
|
InvalidHeaderPart::Value(err) => err.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> IntoHeaderPair for (HeaderName, V)
|
||||||
|
where
|
||||||
|
V: IntoHeaderValue,
|
||||||
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
|
{
|
||||||
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
|
let (name, value) = self;
|
||||||
|
let value = value
|
||||||
|
.try_into_value()
|
||||||
|
.map_err(|err| InvalidHeaderPart::Value(err.into()))?;
|
||||||
|
Ok((name, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> IntoHeaderPair for (&HeaderName, V)
|
||||||
|
where
|
||||||
|
V: IntoHeaderValue,
|
||||||
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
|
{
|
||||||
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
|
let (name, value) = self;
|
||||||
|
let value = value
|
||||||
|
.try_into_value()
|
||||||
|
.map_err(|err| InvalidHeaderPart::Value(err.into()))?;
|
||||||
|
Ok((name.clone(), value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> IntoHeaderPair for (&[u8], V)
|
||||||
|
where
|
||||||
|
V: IntoHeaderValue,
|
||||||
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
|
{
|
||||||
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
|
let (name, value) = self;
|
||||||
|
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
|
||||||
|
let value = value
|
||||||
|
.try_into_value()
|
||||||
|
.map_err(|err| InvalidHeaderPart::Value(err.into()))?;
|
||||||
|
Ok((name, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> IntoHeaderPair for (&str, V)
|
||||||
|
where
|
||||||
|
V: IntoHeaderValue,
|
||||||
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
|
{
|
||||||
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
|
let (name, value) = self;
|
||||||
|
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
|
||||||
|
let value = value
|
||||||
|
.try_into_value()
|
||||||
|
.map_err(|err| InvalidHeaderPart::Value(err.into()))?;
|
||||||
|
Ok((name, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> IntoHeaderPair for (String, V)
|
||||||
|
where
|
||||||
|
V: IntoHeaderValue,
|
||||||
|
V::Error: Into<InvalidHeaderValue>,
|
||||||
|
{
|
||||||
|
type Error = InvalidHeaderPart;
|
||||||
|
|
||||||
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
|
let (name, value) = self;
|
||||||
|
(name.as_str(), value).try_into_header_pair()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Header> IntoHeaderPair for T {
|
||||||
|
type Error = <T as IntoHeaderValue>::Error;
|
||||||
|
|
||||||
|
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||||
|
Ok((T::name(), self.try_into_value()?))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue};
|
||||||
|
use mime::Mime;
|
||||||
|
|
||||||
|
/// A trait for any object that can be Converted to a `HeaderValue`
|
||||||
|
pub trait IntoHeaderValue: Sized {
|
||||||
|
/// The type returned in the event of a conversion error.
|
||||||
|
type Error: Into<HttpError>;
|
||||||
|
|
||||||
|
/// Try to convert value to a HeaderValue.
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for HeaderValue {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for &HeaderValue {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
Ok(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for &str {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
self.parse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for &[u8] {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::from_bytes(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for Bytes {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::from_maybe_shared(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for Vec<u8> {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for String {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for usize {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for i64 {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for u64 {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for i32 {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for u32 {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeaderValue for Mime {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::try_from(format!("{}", self))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,33 +1,36 @@
|
||||||
use std::collections::hash_map::{self, Entry};
|
use std::{
|
||||||
use std::convert::TryFrom;
|
collections::hash_map::{self, Entry},
|
||||||
|
convert::TryFrom,
|
||||||
|
};
|
||||||
|
|
||||||
|
use ahash::AHashMap;
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use fxhash::FxHashMap;
|
|
||||||
use http::header::{HeaderName, HeaderValue};
|
use http::header::{HeaderName, HeaderValue};
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
/// A set of HTTP headers
|
/// A multi-map of HTTP headers.
|
||||||
///
|
///
|
||||||
/// `HeaderMap` is an multi-map of [`HeaderName`] to values.
|
/// `HeaderMap` is a "multi-map" of [`HeaderName`] to one or more values.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct HeaderMap {
|
pub struct HeaderMap {
|
||||||
pub(crate) inner: FxHashMap<HeaderName, Value>,
|
pub(crate) inner: AHashMap<HeaderName, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) enum Value {
|
pub(crate) enum Value {
|
||||||
One(HeaderValue),
|
One(HeaderValue),
|
||||||
Multi(Vec<HeaderValue>),
|
Multi(SmallVec<[HeaderValue; 4]>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
fn get(&self) -> &HeaderValue {
|
fn first(&self) -> &HeaderValue {
|
||||||
match self {
|
match self {
|
||||||
Value::One(ref val) => val,
|
Value::One(ref val) => val,
|
||||||
Value::Multi(ref val) => &val[0],
|
Value::Multi(ref val) => &val[0],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_mut(&mut self) -> &mut HeaderValue {
|
fn first_mut(&mut self) -> &mut HeaderValue {
|
||||||
match self {
|
match self {
|
||||||
Value::One(ref mut val) => val,
|
Value::One(ref mut val) => val,
|
||||||
Value::Multi(ref mut val) => &mut val[0],
|
Value::Multi(ref mut val) => &mut val[0],
|
||||||
|
@ -37,7 +40,7 @@ impl Value {
|
||||||
fn append(&mut self, val: HeaderValue) {
|
fn append(&mut self, val: HeaderValue) {
|
||||||
match self {
|
match self {
|
||||||
Value::One(_) => {
|
Value::One(_) => {
|
||||||
let data = std::mem::replace(self, Value::Multi(vec![val]));
|
let data = std::mem::replace(self, Value::Multi(smallvec![val]));
|
||||||
match data {
|
match data {
|
||||||
Value::One(val) => self.append(val),
|
Value::One(val) => self.append(val),
|
||||||
Value::Multi(_) => unreachable!(),
|
Value::Multi(_) => unreachable!(),
|
||||||
|
@ -55,7 +58,7 @@ impl HeaderMap {
|
||||||
/// allocate.
|
/// allocate.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
HeaderMap {
|
HeaderMap {
|
||||||
inner: FxHashMap::default(),
|
inner: AHashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +72,7 @@ impl HeaderMap {
|
||||||
/// More capacity than requested may be allocated.
|
/// More capacity than requested may be allocated.
|
||||||
pub fn with_capacity(capacity: usize) -> HeaderMap {
|
pub fn with_capacity(capacity: usize) -> HeaderMap {
|
||||||
HeaderMap {
|
HeaderMap {
|
||||||
inner: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
|
inner: AHashMap::with_capacity_and_hasher(capacity, Default::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +121,7 @@ impl HeaderMap {
|
||||||
/// is returned. Use `get_all` to get all values associated with a given
|
/// is returned. Use `get_all` to get all values associated with a given
|
||||||
/// key. Returns `None` if there are no values associated with the key.
|
/// key. Returns `None` if there are no values associated with the key.
|
||||||
pub fn get<N: AsName>(&self, name: N) -> Option<&HeaderValue> {
|
pub fn get<N: AsName>(&self, name: N) -> Option<&HeaderValue> {
|
||||||
self.get2(name).map(|v| v.get())
|
self.get2(name).map(|v| v.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get2<N: AsName>(&self, name: N) -> Option<&Value> {
|
fn get2<N: AsName>(&self, name: N) -> Option<&Value> {
|
||||||
|
@ -134,11 +137,11 @@ impl HeaderMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a view of all values associated with a key.
|
/// Returns an iterator of all values associated with a key.
|
||||||
///
|
///
|
||||||
/// The returned view does not incur any allocations and allows iterating
|
/// The returned view does not incur any allocations and allows iterating the values associated
|
||||||
/// the values associated with the key. See [`GetAll`] for more details.
|
/// with the key. Returns `None` if there are no values associated with the key. Iteration order
|
||||||
/// Returns `None` if there are no values associated with the key.
|
/// is not guaranteed to be the same as insertion order.
|
||||||
pub fn get_all<N: AsName>(&self, name: N) -> GetAll<'_> {
|
pub fn get_all<N: AsName>(&self, name: N) -> GetAll<'_> {
|
||||||
GetAll {
|
GetAll {
|
||||||
idx: 0,
|
idx: 0,
|
||||||
|
@ -153,10 +156,10 @@ impl HeaderMap {
|
||||||
/// key. Returns `None` if there are no values associated with the key.
|
/// key. Returns `None` if there are no values associated with the key.
|
||||||
pub fn get_mut<N: AsName>(&mut self, name: N) -> Option<&mut HeaderValue> {
|
pub fn get_mut<N: AsName>(&mut self, name: N) -> Option<&mut HeaderValue> {
|
||||||
match name.as_name() {
|
match name.as_name() {
|
||||||
Either::Left(name) => self.inner.get_mut(name).map(|v| v.get_mut()),
|
Either::Left(name) => self.inner.get_mut(name).map(|v| v.first_mut()),
|
||||||
Either::Right(s) => {
|
Either::Right(s) => {
|
||||||
if let Ok(name) = HeaderName::try_from(s) {
|
if let Ok(name) = HeaderName::try_from(s) {
|
||||||
self.inner.get_mut(&name).map(|v| v.get_mut())
|
self.inner.get_mut(&name).map(|v| v.first_mut())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -282,6 +285,7 @@ impl<'a> AsName for &'a String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Iterator for all values in a `HeaderMap` with the same name.
|
||||||
pub struct GetAll<'a> {
|
pub struct GetAll<'a> {
|
||||||
idx: usize,
|
idx: usize,
|
||||||
item: Option<&'a Value>,
|
item: Option<&'a Value>,
|
||||||
|
@ -337,7 +341,7 @@ impl<'a> IntoIterator for &'a HeaderMap {
|
||||||
|
|
||||||
pub struct Iter<'a> {
|
pub struct Iter<'a> {
|
||||||
idx: usize,
|
idx: usize,
|
||||||
current: Option<(&'a HeaderName, &'a Vec<HeaderValue>)>,
|
current: Option<(&'a HeaderName, &'a SmallVec<[HeaderValue; 4]>)>,
|
||||||
iter: hash_map::Iter<'a, HeaderName, Value>,
|
iter: hash_map::Iter<'a, HeaderName, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
//! Various http headers
|
//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing/conversion and other
|
||||||
// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header)
|
//! header utility methods.
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
use std::fmt;
|
||||||
use std::{fmt, str::FromStr};
|
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use http::Error as HttpError;
|
|
||||||
use mime::Mime;
|
|
||||||
use percent_encoding::{AsciiSet, CONTROLS};
|
use percent_encoding::{AsciiSet, CONTROLS};
|
||||||
|
|
||||||
pub use http::header::*;
|
pub use http::header::*;
|
||||||
|
@ -14,22 +11,27 @@ pub use http::header::*;
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
use crate::httpmessage::HttpMessage;
|
use crate::httpmessage::HttpMessage;
|
||||||
|
|
||||||
|
mod into_pair;
|
||||||
|
mod into_value;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
pub(crate) mod map;
|
pub(crate) mod map;
|
||||||
mod shared;
|
mod shared;
|
||||||
|
|
||||||
pub use self::common::*;
|
pub use self::common::*;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use self::shared::*;
|
pub use self::shared::*;
|
||||||
|
|
||||||
|
pub use self::into_pair::IntoHeaderPair;
|
||||||
|
pub use self::into_value::IntoHeaderValue;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use self::map::GetAll;
|
pub use self::map::GetAll;
|
||||||
pub use self::map::HeaderMap;
|
pub use self::map::HeaderMap;
|
||||||
|
pub use self::utils::*;
|
||||||
|
|
||||||
/// A trait for any object that will represent a header field and value.
|
/// A trait for any object that already represents a valid header field and value.
|
||||||
pub trait Header
|
pub trait Header: IntoHeaderValue {
|
||||||
where
|
|
||||||
Self: IntoHeaderValue,
|
|
||||||
{
|
|
||||||
/// Returns the name of the header field
|
/// Returns the name of the header field
|
||||||
fn name() -> HeaderName;
|
fn name() -> HeaderName;
|
||||||
|
|
||||||
|
@ -37,159 +39,6 @@ where
|
||||||
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
|
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for any object that can be Converted to a `HeaderValue`
|
|
||||||
pub trait IntoHeaderValue: Sized {
|
|
||||||
/// The type returned in the event of a conversion error.
|
|
||||||
type Error: Into<HttpError>;
|
|
||||||
|
|
||||||
/// Try to convert value to a Header value.
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for HeaderValue {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoHeaderValue for &'a str {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
self.parse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoHeaderValue for &'a [u8] {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
HeaderValue::from_bytes(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for Bytes {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
HeaderValue::from_maybe_shared(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for Vec<u8> {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
HeaderValue::try_from(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for String {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
HeaderValue::try_from(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for usize {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
let s = format!("{}", self);
|
|
||||||
HeaderValue::try_from(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for u64 {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
let s = format!("{}", self);
|
|
||||||
HeaderValue::try_from(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoHeaderValue for Mime {
|
|
||||||
type Error = InvalidHeaderValue;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
|
||||||
HeaderValue::try_from(format!("{}", self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents supported types of content encodings
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
||||||
pub enum ContentEncoding {
|
|
||||||
/// Automatically select encoding based on encoding negotiation
|
|
||||||
Auto,
|
|
||||||
/// A format using the Brotli algorithm
|
|
||||||
Br,
|
|
||||||
/// A format using the zlib structure with deflate algorithm
|
|
||||||
Deflate,
|
|
||||||
/// Gzip algorithm
|
|
||||||
Gzip,
|
|
||||||
/// Indicates the identity function (i.e. no compression, nor modification)
|
|
||||||
Identity,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContentEncoding {
|
|
||||||
#[inline]
|
|
||||||
/// Is the content compressed?
|
|
||||||
pub fn is_compression(self) -> bool {
|
|
||||||
matches!(self, ContentEncoding::Identity | ContentEncoding::Auto)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Convert content encoding to string
|
|
||||||
pub fn as_str(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
ContentEncoding::Br => "br",
|
|
||||||
ContentEncoding::Gzip => "gzip",
|
|
||||||
ContentEncoding::Deflate => "deflate",
|
|
||||||
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// default quality value
|
|
||||||
pub fn quality(self) -> f64 {
|
|
||||||
match self {
|
|
||||||
ContentEncoding::Br => 1.1,
|
|
||||||
ContentEncoding::Gzip => 1.0,
|
|
||||||
ContentEncoding::Deflate => 0.9,
|
|
||||||
ContentEncoding::Identity | ContentEncoding::Auto => 0.1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for ContentEncoding {
|
|
||||||
fn from(s: &'a str) -> ContentEncoding {
|
|
||||||
let s = s.trim();
|
|
||||||
|
|
||||||
if s.eq_ignore_ascii_case("br") {
|
|
||||||
ContentEncoding::Br
|
|
||||||
} else if s.eq_ignore_ascii_case("gzip") {
|
|
||||||
ContentEncoding::Gzip
|
|
||||||
} else if s.eq_ignore_ascii_case("deflate") {
|
|
||||||
ContentEncoding::Deflate
|
|
||||||
} else {
|
|
||||||
ContentEncoding::Identity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub(crate) struct Writer {
|
pub(crate) struct Writer {
|
||||||
buf: BytesMut,
|
buf: BytesMut,
|
||||||
|
@ -201,6 +50,7 @@ impl Writer {
|
||||||
buf: BytesMut::new(),
|
buf: BytesMut::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take(&mut self) -> Bytes {
|
fn take(&mut self) -> Bytes {
|
||||||
self.buf.split().freeze()
|
self.buf.split().freeze()
|
||||||
}
|
}
|
||||||
|
@ -219,164 +69,7 @@ impl fmt::Write for Writer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
/// Convert `http::HeaderMap` to our `HeaderMap`.
|
||||||
#[doc(hidden)]
|
|
||||||
/// Reads a comma-delimited raw header into a Vec.
|
|
||||||
pub fn from_comma_delimited<'a, I: Iterator<Item = &'a HeaderValue> + 'a, T: FromStr>(
|
|
||||||
all: I,
|
|
||||||
) -> Result<Vec<T>, ParseError> {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
for h in all {
|
|
||||||
let s = h.to_str().map_err(|_| ParseError::Header)?;
|
|
||||||
result.extend(
|
|
||||||
s.split(',')
|
|
||||||
.filter_map(|x| match x.trim() {
|
|
||||||
"" => None,
|
|
||||||
y => Some(y),
|
|
||||||
})
|
|
||||||
.filter_map(|x| x.trim().parse().ok()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[doc(hidden)]
|
|
||||||
/// Reads a single string when parsing a header.
|
|
||||||
pub fn from_one_raw_str<T: FromStr>(val: Option<&HeaderValue>) -> Result<T, ParseError> {
|
|
||||||
if let Some(line) = val {
|
|
||||||
let line = line.to_str().map_err(|_| ParseError::Header)?;
|
|
||||||
if !line.is_empty() {
|
|
||||||
return T::from_str(line).or(Err(ParseError::Header));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(ParseError::Header)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[doc(hidden)]
|
|
||||||
/// Format an array into a comma-delimited string.
|
|
||||||
pub fn fmt_comma_delimited<T>(f: &mut fmt::Formatter<'_>, parts: &[T]) -> fmt::Result
|
|
||||||
where
|
|
||||||
T: fmt::Display,
|
|
||||||
{
|
|
||||||
let mut iter = parts.iter();
|
|
||||||
if let Some(part) = iter.next() {
|
|
||||||
fmt::Display::fmt(part, f)?;
|
|
||||||
}
|
|
||||||
for part in iter {
|
|
||||||
f.write_str(", ")?;
|
|
||||||
fmt::Display::fmt(part, f)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// From hyper v0.11.27 src/header/parsing.rs
|
|
||||||
|
|
||||||
/// The value part of an extended parameter consisting of three parts:
|
|
||||||
/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`),
|
|
||||||
/// and a character sequence representing the actual value (`value`), separated by single quote
|
|
||||||
/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct ExtendedValue {
|
|
||||||
/// The character set that is used to encode the `value` to a string.
|
|
||||||
pub charset: Charset,
|
|
||||||
/// The human language details of the `value`, if available.
|
|
||||||
pub language_tag: Option<LanguageTag>,
|
|
||||||
/// The parameter value, as expressed in octets.
|
|
||||||
pub value: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses extended header parameter values (`ext-value`), as defined in
|
|
||||||
/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
|
|
||||||
///
|
|
||||||
/// Extended values are denoted by parameter names that end with `*`.
|
|
||||||
///
|
|
||||||
/// ## ABNF
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// ext-value = charset "'" [ language ] "'" value-chars
|
|
||||||
/// ; like RFC 2231's <extended-initial-value>
|
|
||||||
/// ; (see [RFC2231], Section 7)
|
|
||||||
///
|
|
||||||
/// charset = "UTF-8" / "ISO-8859-1" / mime-charset
|
|
||||||
///
|
|
||||||
/// mime-charset = 1*mime-charsetc
|
|
||||||
/// mime-charsetc = ALPHA / DIGIT
|
|
||||||
/// / "!" / "#" / "$" / "%" / "&"
|
|
||||||
/// / "+" / "-" / "^" / "_" / "`"
|
|
||||||
/// / "{" / "}" / "~"
|
|
||||||
/// ; as <mime-charset> in Section 2.3 of [RFC2978]
|
|
||||||
/// ; except that the single quote is not included
|
|
||||||
/// ; SHOULD be registered in the IANA charset registry
|
|
||||||
///
|
|
||||||
/// language = <Language-Tag, defined in [RFC5646], Section 2.1>
|
|
||||||
///
|
|
||||||
/// value-chars = *( pct-encoded / attr-char )
|
|
||||||
///
|
|
||||||
/// pct-encoded = "%" HEXDIG HEXDIG
|
|
||||||
/// ; see [RFC3986], Section 2.1
|
|
||||||
///
|
|
||||||
/// attr-char = ALPHA / DIGIT
|
|
||||||
/// / "!" / "#" / "$" / "&" / "+" / "-" / "."
|
|
||||||
/// / "^" / "_" / "`" / "|" / "~"
|
|
||||||
/// ; token except ( "*" / "'" / "%" )
|
|
||||||
/// ```
|
|
||||||
pub fn parse_extended_value(
|
|
||||||
val: &str,
|
|
||||||
) -> Result<ExtendedValue, crate::error::ParseError> {
|
|
||||||
// Break into three pieces separated by the single-quote character
|
|
||||||
let mut parts = val.splitn(3, '\'');
|
|
||||||
|
|
||||||
// Interpret the first piece as a Charset
|
|
||||||
let charset: Charset = match parts.next() {
|
|
||||||
None => return Err(crate::error::ParseError::Header),
|
|
||||||
Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Interpret the second piece as a language tag
|
|
||||||
let language_tag: Option<LanguageTag> = match parts.next() {
|
|
||||||
None => return Err(crate::error::ParseError::Header),
|
|
||||||
Some("") => None,
|
|
||||||
Some(s) => match s.parse() {
|
|
||||||
Ok(lt) => Some(lt),
|
|
||||||
Err(_) => return Err(crate::error::ParseError::Header),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Interpret the third piece as a sequence of value characters
|
|
||||||
let value: Vec<u8> = match parts.next() {
|
|
||||||
None => return Err(crate::error::ParseError::Header),
|
|
||||||
Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(ExtendedValue {
|
|
||||||
value,
|
|
||||||
charset,
|
|
||||||
language_tag,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ExtendedValue {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let encoded_value =
|
|
||||||
percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
|
|
||||||
if let Some(ref lang) = self.language_tag {
|
|
||||||
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
|
|
||||||
} else {
|
|
||||||
write!(f, "{}''{}", self.charset, encoded_value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Percent encode a sequence of bytes with a character set defined in
|
|
||||||
/// <https://tools.ietf.org/html/rfc5987#section-3.2>
|
|
||||||
pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
|
|
||||||
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
|
|
||||||
fmt::Display::fmt(&encoded, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert http::HeaderMap to a HeaderMap
|
|
||||||
impl From<http::HeaderMap> for HeaderMap {
|
impl From<http::HeaderMap> for HeaderMap {
|
||||||
fn from(map: http::HeaderMap) -> HeaderMap {
|
fn from(map: http::HeaderMap) -> HeaderMap {
|
||||||
let mut new_map = HeaderMap::with_capacity(map.capacity());
|
let mut new_map = HeaderMap::with_capacity(map.capacity());
|
||||||
|
@ -387,8 +80,8 @@ impl From<http::HeaderMap> for HeaderMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This encode set is used for HTTP header values and is defined at
|
/// This encode set is used for HTTP header values and is defined at
|
||||||
// https://tools.ietf.org/html/rfc5987#section-3.2
|
/// https://tools.ietf.org/html/rfc5987#section-3.2.
|
||||||
pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS
|
pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS
|
||||||
.add(b' ')
|
.add(b' ')
|
||||||
.add(b'"')
|
.add(b'"')
|
||||||
|
@ -410,91 +103,3 @@ pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS
|
||||||
.add(b']')
|
.add(b']')
|
||||||
.add(b'{')
|
.add(b'{')
|
||||||
.add(b'}');
|
.add(b'}');
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::shared::Charset;
|
|
||||||
use super::{parse_extended_value, ExtendedValue};
|
|
||||||
use language_tags::LanguageTag;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_extended_value_with_encoding_and_language_tag() {
|
|
||||||
let expected_language_tag = "en".parse::<LanguageTag>().unwrap();
|
|
||||||
// RFC 5987, Section 3.2.2
|
|
||||||
// Extended notation, using the Unicode character U+00A3 (POUND SIGN)
|
|
||||||
let result = parse_extended_value("iso-8859-1'en'%A3%20rates");
|
|
||||||
assert!(result.is_ok());
|
|
||||||
let extended_value = result.unwrap();
|
|
||||||
assert_eq!(Charset::Iso_8859_1, extended_value.charset);
|
|
||||||
assert!(extended_value.language_tag.is_some());
|
|
||||||
assert_eq!(expected_language_tag, extended_value.language_tag.unwrap());
|
|
||||||
assert_eq!(
|
|
||||||
vec![163, b' ', b'r', b'a', b't', b'e', b's'],
|
|
||||||
extended_value.value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_extended_value_with_encoding() {
|
|
||||||
// RFC 5987, Section 3.2.2
|
|
||||||
// Extended notation, using the Unicode characters U+00A3 (POUND SIGN)
|
|
||||||
// and U+20AC (EURO SIGN)
|
|
||||||
let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
|
|
||||||
assert!(result.is_ok());
|
|
||||||
let extended_value = result.unwrap();
|
|
||||||
assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset);
|
|
||||||
assert!(extended_value.language_tag.is_none());
|
|
||||||
assert_eq!(
|
|
||||||
vec![
|
|
||||||
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
|
||||||
b't', b'e', b's',
|
|
||||||
],
|
|
||||||
extended_value.value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_extended_value_missing_language_tag_and_encoding() {
|
|
||||||
// From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2
|
|
||||||
let result = parse_extended_value("foo%20bar.html");
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_extended_value_partially_formatted() {
|
|
||||||
let result = parse_extended_value("UTF-8'missing third part");
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_extended_value_partially_formatted_blank() {
|
|
||||||
let result = parse_extended_value("blank second part'");
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fmt_extended_value_with_encoding_and_language_tag() {
|
|
||||||
let extended_value = ExtendedValue {
|
|
||||||
charset: Charset::Iso_8859_1,
|
|
||||||
language_tag: Some("en".parse().expect("Could not parse language tag")),
|
|
||||||
value: vec![163, b' ', b'r', b'a', b't', b'e', b's'],
|
|
||||||
};
|
|
||||||
assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fmt_extended_value_with_encoding() {
|
|
||||||
let extended_value = ExtendedValue {
|
|
||||||
charset: Charset::Ext("UTF-8".to_string()),
|
|
||||||
language_tag: None,
|
|
||||||
value: vec![
|
|
||||||
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
|
||||||
b't', b'e', b's',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
"UTF-8''%C2%A3%20and%20%E2%82%AC%20rates",
|
|
||||||
format!("{}", extended_value)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -161,7 +161,7 @@ impl FromStr for EntityTag {
|
||||||
impl IntoHeaderValue for EntityTag {
|
impl IntoHeaderValue for EntityTag {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
let mut wrt = Writer::new();
|
let mut wrt = Writer::new();
|
||||||
write!(wrt, "{}", self).unwrap();
|
write!(wrt, "{}", self).unwrap();
|
||||||
HeaderValue::from_maybe_shared(wrt.take())
|
HeaderValue::from_maybe_shared(wrt.take())
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
use std::{fmt, str::FromStr};
|
||||||
|
|
||||||
|
use language_tags::LanguageTag;
|
||||||
|
|
||||||
|
use crate::header::{Charset, HTTP_VALUE};
|
||||||
|
|
||||||
|
// From hyper v0.11.27 src/header/parsing.rs
|
||||||
|
|
||||||
|
/// The value part of an extended parameter consisting of three parts:
|
||||||
|
/// - The REQUIRED character set name (`charset`).
|
||||||
|
/// - The OPTIONAL language information (`language_tag`).
|
||||||
|
/// - A character sequence representing the actual value (`value`), separated by single quotes.
|
||||||
|
///
|
||||||
|
/// It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct ExtendedValue {
|
||||||
|
/// The character set that is used to encode the `value` to a string.
|
||||||
|
pub charset: Charset,
|
||||||
|
|
||||||
|
/// The human language details of the `value`, if available.
|
||||||
|
pub language_tag: Option<LanguageTag>,
|
||||||
|
|
||||||
|
/// The parameter value, as expressed in octets.
|
||||||
|
pub value: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses extended header parameter values (`ext-value`), as defined in
|
||||||
|
/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
|
||||||
|
///
|
||||||
|
/// Extended values are denoted by parameter names that end with `*`.
|
||||||
|
///
|
||||||
|
/// ## ABNF
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// ext-value = charset "'" [ language ] "'" value-chars
|
||||||
|
/// ; like RFC 2231's <extended-initial-value>
|
||||||
|
/// ; (see [RFC2231], Section 7)
|
||||||
|
///
|
||||||
|
/// charset = "UTF-8" / "ISO-8859-1" / mime-charset
|
||||||
|
///
|
||||||
|
/// mime-charset = 1*mime-charsetc
|
||||||
|
/// mime-charsetc = ALPHA / DIGIT
|
||||||
|
/// / "!" / "#" / "$" / "%" / "&"
|
||||||
|
/// / "+" / "-" / "^" / "_" / "`"
|
||||||
|
/// / "{" / "}" / "~"
|
||||||
|
/// ; as <mime-charset> in Section 2.3 of [RFC2978]
|
||||||
|
/// ; except that the single quote is not included
|
||||||
|
/// ; SHOULD be registered in the IANA charset registry
|
||||||
|
///
|
||||||
|
/// language = <Language-Tag, defined in [RFC5646], Section 2.1>
|
||||||
|
///
|
||||||
|
/// value-chars = *( pct-encoded / attr-char )
|
||||||
|
///
|
||||||
|
/// pct-encoded = "%" HEXDIG HEXDIG
|
||||||
|
/// ; see [RFC3986], Section 2.1
|
||||||
|
///
|
||||||
|
/// attr-char = ALPHA / DIGIT
|
||||||
|
/// / "!" / "#" / "$" / "&" / "+" / "-" / "."
|
||||||
|
/// / "^" / "_" / "`" / "|" / "~"
|
||||||
|
/// ; token except ( "*" / "'" / "%" )
|
||||||
|
/// ```
|
||||||
|
pub fn parse_extended_value(
|
||||||
|
val: &str,
|
||||||
|
) -> Result<ExtendedValue, crate::error::ParseError> {
|
||||||
|
// Break into three pieces separated by the single-quote character
|
||||||
|
let mut parts = val.splitn(3, '\'');
|
||||||
|
|
||||||
|
// Interpret the first piece as a Charset
|
||||||
|
let charset: Charset = match parts.next() {
|
||||||
|
None => return Err(crate::error::ParseError::Header),
|
||||||
|
Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interpret the second piece as a language tag
|
||||||
|
let language_tag: Option<LanguageTag> = match parts.next() {
|
||||||
|
None => return Err(crate::error::ParseError::Header),
|
||||||
|
Some("") => None,
|
||||||
|
Some(s) => match s.parse() {
|
||||||
|
Ok(lt) => Some(lt),
|
||||||
|
Err(_) => return Err(crate::error::ParseError::Header),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interpret the third piece as a sequence of value characters
|
||||||
|
let value: Vec<u8> = match parts.next() {
|
||||||
|
None => return Err(crate::error::ParseError::Header),
|
||||||
|
Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ExtendedValue {
|
||||||
|
value,
|
||||||
|
charset,
|
||||||
|
language_tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ExtendedValue {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let encoded_value =
|
||||||
|
percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
|
||||||
|
if let Some(ref lang) = self.language_tag {
|
||||||
|
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
|
||||||
|
} else {
|
||||||
|
write!(f, "{}''{}", self.charset, encoded_value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_extended_value_with_encoding_and_language_tag() {
|
||||||
|
let expected_language_tag = "en".parse::<LanguageTag>().unwrap();
|
||||||
|
// RFC 5987, Section 3.2.2
|
||||||
|
// Extended notation, using the Unicode character U+00A3 (POUND SIGN)
|
||||||
|
let result = parse_extended_value("iso-8859-1'en'%A3%20rates");
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let extended_value = result.unwrap();
|
||||||
|
assert_eq!(Charset::Iso_8859_1, extended_value.charset);
|
||||||
|
assert!(extended_value.language_tag.is_some());
|
||||||
|
assert_eq!(expected_language_tag, extended_value.language_tag.unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
vec![163, b' ', b'r', b'a', b't', b'e', b's'],
|
||||||
|
extended_value.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_extended_value_with_encoding() {
|
||||||
|
// RFC 5987, Section 3.2.2
|
||||||
|
// Extended notation, using the Unicode characters U+00A3 (POUND SIGN)
|
||||||
|
// and U+20AC (EURO SIGN)
|
||||||
|
let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let extended_value = result.unwrap();
|
||||||
|
assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset);
|
||||||
|
assert!(extended_value.language_tag.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
vec![
|
||||||
|
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
||||||
|
b't', b'e', b's',
|
||||||
|
],
|
||||||
|
extended_value.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_extended_value_missing_language_tag_and_encoding() {
|
||||||
|
// From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2
|
||||||
|
let result = parse_extended_value("foo%20bar.html");
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_extended_value_partially_formatted() {
|
||||||
|
let result = parse_extended_value("UTF-8'missing third part");
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_extended_value_partially_formatted_blank() {
|
||||||
|
let result = parse_extended_value("blank second part'");
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fmt_extended_value_with_encoding_and_language_tag() {
|
||||||
|
let extended_value = ExtendedValue {
|
||||||
|
charset: Charset::Iso_8859_1,
|
||||||
|
language_tag: Some("en".parse().expect("Could not parse language tag")),
|
||||||
|
value: vec![163, b' ', b'r', b'a', b't', b'e', b's'],
|
||||||
|
};
|
||||||
|
assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fmt_extended_value_with_encoding() {
|
||||||
|
let extended_value = ExtendedValue {
|
||||||
|
charset: Charset::Ext("UTF-8".to_string()),
|
||||||
|
language_tag: None,
|
||||||
|
value: vec![
|
||||||
|
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
||||||
|
b't', b'e', b's',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
"UTF-8''%C2%A3%20and%20%E2%82%AC%20rates",
|
||||||
|
format!("{}", extended_value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,7 +48,7 @@ impl From<SystemTime> for HttpDate {
|
||||||
impl IntoHeaderValue for HttpDate {
|
impl IntoHeaderValue for HttpDate {
|
||||||
type Error = InvalidHeaderValue;
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
let mut wrt = BytesMut::with_capacity(29).writer();
|
let mut wrt = BytesMut::with_capacity(29).writer();
|
||||||
write!(
|
write!(
|
||||||
wrt,
|
wrt,
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
//! Copied for `hyper::header::shared`;
|
//! Originally taken from `hyper::header::shared`.
|
||||||
|
|
||||||
pub use self::charset::Charset;
|
|
||||||
pub use self::encoding::Encoding;
|
|
||||||
pub use self::entity::EntityTag;
|
|
||||||
pub use self::httpdate::HttpDate;
|
|
||||||
pub use self::quality_item::{q, qitem, Quality, QualityItem};
|
|
||||||
pub use language_tags::LanguageTag;
|
|
||||||
|
|
||||||
mod charset;
|
mod charset;
|
||||||
mod encoding;
|
mod encoding;
|
||||||
mod entity;
|
mod entity;
|
||||||
|
mod extended;
|
||||||
mod httpdate;
|
mod httpdate;
|
||||||
mod quality_item;
|
mod quality_item;
|
||||||
|
|
||||||
|
pub use self::charset::Charset;
|
||||||
|
pub use self::encoding::Encoding;
|
||||||
|
pub use self::entity::EntityTag;
|
||||||
|
pub use self::extended::{parse_extended_value, ExtendedValue};
|
||||||
|
pub use self::httpdate::HttpDate;
|
||||||
|
pub use self::quality_item::{q, qitem, Quality, QualityItem};
|
||||||
|
pub use language_tags::LanguageTag;
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
use std::{fmt, str::FromStr};
|
||||||
|
|
||||||
|
use http::HeaderValue;
|
||||||
|
|
||||||
|
use crate::{error::ParseError, header::HTTP_VALUE};
|
||||||
|
|
||||||
|
/// Reads a comma-delimited raw header into a Vec.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_comma_delimited<'a, I, T>(all: I) -> Result<Vec<T>, ParseError>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a HeaderValue> + 'a,
|
||||||
|
T: FromStr,
|
||||||
|
{
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for h in all {
|
||||||
|
let s = h.to_str().map_err(|_| ParseError::Header)?;
|
||||||
|
result.extend(
|
||||||
|
s.split(',')
|
||||||
|
.filter_map(|x| match x.trim() {
|
||||||
|
"" => None,
|
||||||
|
y => Some(y),
|
||||||
|
})
|
||||||
|
.filter_map(|x| x.trim().parse().ok()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a single string when parsing a header.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_one_raw_str<T: FromStr>(val: Option<&HeaderValue>) -> Result<T, ParseError> {
|
||||||
|
if let Some(line) = val {
|
||||||
|
let line = line.to_str().map_err(|_| ParseError::Header)?;
|
||||||
|
if !line.is_empty() {
|
||||||
|
return T::from_str(line).or(Err(ParseError::Header));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ParseError::Header)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format an array into a comma-delimited string.
|
||||||
|
#[inline]
|
||||||
|
pub fn fmt_comma_delimited<T>(f: &mut fmt::Formatter<'_>, parts: &[T]) -> fmt::Result
|
||||||
|
where
|
||||||
|
T: fmt::Display,
|
||||||
|
{
|
||||||
|
let mut iter = parts.iter();
|
||||||
|
if let Some(part) = iter.next() {
|
||||||
|
fmt::Display::fmt(part, f)?;
|
||||||
|
}
|
||||||
|
for part in iter {
|
||||||
|
f.write_str(", ")?;
|
||||||
|
fmt::Display::fmt(part, f)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Percent encode a sequence of bytes with a character set defined in
|
||||||
|
/// <https://tools.ietf.org/html/rfc5987#section-3.2>
|
||||||
|
pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
|
||||||
|
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
|
||||||
|
fmt::Display::fmt(&encoded, f)
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ use crate::payload::Payload;
|
||||||
|
|
||||||
struct Cookies(Vec<Cookie<'static>>);
|
struct Cookies(Vec<Cookie<'static>>);
|
||||||
|
|
||||||
/// Trait that implements general purpose operations on http messages
|
/// Trait that implements general purpose operations on HTTP messages.
|
||||||
pub trait HttpMessage: Sized {
|
pub trait HttpMessage: Sized {
|
||||||
/// Type of message payload stream
|
/// Type of message payload stream
|
||||||
type Stream;
|
type Stream;
|
||||||
|
@ -30,8 +30,8 @@ pub trait HttpMessage: Sized {
|
||||||
/// Mutable reference to a the request's extensions container
|
/// Mutable reference to a the request's extensions container
|
||||||
fn extensions_mut(&self) -> RefMut<'_, Extensions>;
|
fn extensions_mut(&self) -> RefMut<'_, Extensions>;
|
||||||
|
|
||||||
|
/// Get a header.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// Get a header
|
|
||||||
fn get_header<H: Header>(&self) -> Option<H>
|
fn get_header<H: Header>(&self) -> Option<H>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
|
@ -43,8 +43,8 @@ pub trait HttpMessage: Sized {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the request content type. If request does not contain
|
/// Read the request content type. If request did not contain a *Content-Type* header, an empty
|
||||||
/// *Content-Type* header, empty str get returned.
|
/// string is returned.
|
||||||
fn content_type(&self) -> &str {
|
fn content_type(&self) -> &str {
|
||||||
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
|
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
|
||||||
if let Ok(content_type) = content_type.to_str() {
|
if let Ok(content_type) = content_type.to_str() {
|
||||||
|
@ -90,7 +90,7 @@ pub trait HttpMessage: Sized {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if request has chunked transfer encoding
|
/// Check if request has chunked transfer encoding.
|
||||||
fn chunked(&self) -> Result<bool, ParseError> {
|
fn chunked(&self) -> Result<bool, ParseError> {
|
||||||
if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
|
if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
|
||||||
if let Ok(s) = encodings.to_str() {
|
if let Ok(s) = encodings.to_str() {
|
||||||
|
@ -173,11 +173,13 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_content_type() {
|
fn test_content_type() {
|
||||||
let req = TestRequest::with_header("content-type", "text/plain").finish();
|
let req = TestRequest::default()
|
||||||
|
.insert_header(("content-type", "text/plain"))
|
||||||
|
.finish();
|
||||||
assert_eq!(req.content_type(), "text/plain");
|
assert_eq!(req.content_type(), "text/plain");
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header("content-type", "application/json; charset=utf=8")
|
.insert_header(("content-type", "application/json; charset=utf=8"))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(req.content_type(), "application/json");
|
assert_eq!(req.content_type(), "application/json");
|
||||||
let req = TestRequest::default().finish();
|
let req = TestRequest::default().finish();
|
||||||
assert_eq!(req.content_type(), "");
|
assert_eq!(req.content_type(), "");
|
||||||
|
@ -185,13 +187,15 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mime_type() {
|
fn test_mime_type() {
|
||||||
let req = TestRequest::with_header("content-type", "application/json").finish();
|
let req = TestRequest::default()
|
||||||
|
.insert_header(("content-type", "application/json"))
|
||||||
|
.finish();
|
||||||
assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON));
|
assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON));
|
||||||
let req = TestRequest::default().finish();
|
let req = TestRequest::default().finish();
|
||||||
assert_eq!(req.mime_type().unwrap(), None);
|
assert_eq!(req.mime_type().unwrap(), None);
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header("content-type", "application/json; charset=utf-8")
|
.insert_header(("content-type", "application/json; charset=utf-8"))
|
||||||
.finish();
|
.finish();
|
||||||
let mt = req.mime_type().unwrap().unwrap();
|
let mt = req.mime_type().unwrap().unwrap();
|
||||||
assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8));
|
assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8));
|
||||||
assert_eq!(mt.type_(), mime::APPLICATION);
|
assert_eq!(mt.type_(), mime::APPLICATION);
|
||||||
|
@ -200,11 +204,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mime_type_error() {
|
fn test_mime_type_error() {
|
||||||
let req = TestRequest::with_header(
|
let req = TestRequest::default()
|
||||||
"content-type",
|
.insert_header(("content-type", "applicationadfadsfasdflknadsfklnadsfjson"))
|
||||||
"applicationadfadsfasdflknadsfklnadsfjson",
|
.finish();
|
||||||
)
|
|
||||||
.finish();
|
|
||||||
assert_eq!(Err(ContentTypeError::ParseError), req.mime_type());
|
assert_eq!(Err(ContentTypeError::ParseError), req.mime_type());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,27 +215,27 @@ mod tests {
|
||||||
let req = TestRequest::default().finish();
|
let req = TestRequest::default().finish();
|
||||||
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
|
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
|
||||||
|
|
||||||
let req = TestRequest::with_header("content-type", "application/json").finish();
|
let req = TestRequest::default()
|
||||||
|
.insert_header(("content-type", "application/json"))
|
||||||
|
.finish();
|
||||||
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
|
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
|
||||||
|
|
||||||
let req = TestRequest::with_header(
|
let req = TestRequest::default()
|
||||||
"content-type",
|
.insert_header(("content-type", "application/json; charset=ISO-8859-2"))
|
||||||
"application/json; charset=ISO-8859-2",
|
.finish();
|
||||||
)
|
|
||||||
.finish();
|
|
||||||
assert_eq!(ISO_8859_2, req.encoding().unwrap());
|
assert_eq!(ISO_8859_2, req.encoding().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encoding_error() {
|
fn test_encoding_error() {
|
||||||
let req = TestRequest::with_header("content-type", "applicatjson").finish();
|
let req = TestRequest::default()
|
||||||
|
.insert_header(("content-type", "applicatjson"))
|
||||||
|
.finish();
|
||||||
assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
|
assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
|
||||||
|
|
||||||
let req = TestRequest::with_header(
|
let req = TestRequest::default()
|
||||||
"content-type",
|
.insert_header(("content-type", "application/json; charset=kkkttktk"))
|
||||||
"application/json; charset=kkkttktk",
|
.finish();
|
||||||
)
|
|
||||||
.finish();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(ContentTypeError::UnknownEncoding),
|
Some(ContentTypeError::UnknownEncoding),
|
||||||
req.encoding().err()
|
req.encoding().err()
|
||||||
|
@ -245,15 +247,16 @@ mod tests {
|
||||||
let req = TestRequest::default().finish();
|
let req = TestRequest::default().finish();
|
||||||
assert!(!req.chunked().unwrap());
|
assert!(!req.chunked().unwrap());
|
||||||
|
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
|
.finish();
|
||||||
assert!(req.chunked().unwrap());
|
assert!(req.chunked().unwrap());
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::TRANSFER_ENCODING,
|
header::TRANSFER_ENCODING,
|
||||||
Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"),
|
Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"),
|
||||||
)
|
))
|
||||||
.finish();
|
.finish();
|
||||||
assert!(req.chunked().is_err());
|
assert!(req.chunked().is_err());
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ pub use self::response::{Response, ResponseBuilder};
|
||||||
pub use self::service::HttpService;
|
pub use self::service::HttpService;
|
||||||
|
|
||||||
pub mod http {
|
pub mod http {
|
||||||
//! Various HTTP related types
|
//! Various HTTP related types.
|
||||||
|
|
||||||
// re-exports
|
// re-exports
|
||||||
pub use http::header::{HeaderName, HeaderValue};
|
pub use http::header::{HeaderName, HeaderValue};
|
||||||
|
@ -64,7 +64,7 @@ pub mod http {
|
||||||
pub use crate::cookie::{Cookie, CookieBuilder};
|
pub use crate::cookie::{Cookie, CookieBuilder};
|
||||||
pub use crate::header::HeaderMap;
|
pub use crate::header::HeaderMap;
|
||||||
|
|
||||||
/// Various http headers
|
/// A collection of HTTP headers and helpers.
|
||||||
pub mod header {
|
pub mod header {
|
||||||
pub use crate::header::*;
|
pub use crate::header::*;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
use std::cell::{Ref, RefMut};
|
//! HTTP requests.
|
||||||
use std::{fmt, net};
|
|
||||||
|
use std::{
|
||||||
|
cell::{Ref, RefMut},
|
||||||
|
fmt, net,
|
||||||
|
};
|
||||||
|
|
||||||
use http::{header, Method, Uri, Version};
|
use http::{header, Method, Uri, Version};
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
//! Http response
|
//! HTTP responses.
|
||||||
use std::cell::{Ref, RefMut};
|
|
||||||
use std::convert::TryFrom;
|
use std::{
|
||||||
use std::future::Future;
|
cell::{Ref, RefMut},
|
||||||
use std::pin::Pin;
|
convert::TryInto,
|
||||||
use std::task::{Context, Poll};
|
fmt,
|
||||||
use std::{fmt, str};
|
future::Future,
|
||||||
|
ops,
|
||||||
|
pin::Pin,
|
||||||
|
str,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
|
@ -14,7 +19,7 @@ use crate::body::{Body, BodyStream, MessageBody, ResponseBody};
|
||||||
use crate::cookie::{Cookie, CookieJar};
|
use crate::cookie::{Cookie, CookieJar};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::extensions::Extensions;
|
use crate::extensions::Extensions;
|
||||||
use crate::header::{Header, IntoHeaderValue};
|
use crate::header::{IntoHeaderPair, IntoHeaderValue};
|
||||||
use crate::http::header::{self, HeaderName, HeaderValue};
|
use crate::http::header::{self, HeaderName, HeaderValue};
|
||||||
use crate::http::{Error as HttpError, HeaderMap, StatusCode};
|
use crate::http::{Error as HttpError, HeaderMap, StatusCode};
|
||||||
use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead};
|
use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead};
|
||||||
|
@ -341,93 +346,96 @@ impl ResponseBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a header.
|
/// Insert a header, replacing any that were set with an equivalent field name.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_http::{http, Request, Response, Result};
|
/// # use actix_http::Response;
|
||||||
|
/// use actix_http::http::header::ContentType;
|
||||||
///
|
///
|
||||||
/// fn index(req: Request) -> Result<Response> {
|
/// Response::Ok()
|
||||||
/// Ok(Response::Ok()
|
/// .insert_header(ContentType(mime::APPLICATION_JSON))
|
||||||
/// .set(http::header::IfModifiedSince(
|
/// .insert_header(("X-TEST", "value"))
|
||||||
/// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?,
|
/// .finish();
|
||||||
/// ))
|
|
||||||
/// .finish())
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
#[doc(hidden)]
|
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
|
||||||
pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
|
|
||||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
|
||||||
match hdr.try_into() {
|
|
||||||
Ok(value) => {
|
|
||||||
parts.headers.append(H::name(), value);
|
|
||||||
}
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Append a header to existing headers.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use actix_http::{http, Request, Response};
|
|
||||||
///
|
|
||||||
/// fn index(req: Request) -> Response {
|
|
||||||
/// Response::Ok()
|
|
||||||
/// .header("X-TEST", "value")
|
|
||||||
/// .header(http::header::CONTENT_TYPE, "application/json")
|
|
||||||
/// .finish()
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
H: IntoHeaderPair,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
|
||||||
V: IntoHeaderValue,
|
|
||||||
{
|
{
|
||||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
match HeaderName::try_from(key) {
|
match header.try_into_header_pair() {
|
||||||
Ok(key) => match value.try_into() {
|
Ok((key, value)) => parts.headers.insert(key, value),
|
||||||
Ok(value) => {
|
|
||||||
parts.headers.append(key, value);
|
|
||||||
}
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
|
||||||
},
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a header.
|
/// Append a header, keeping any that were set with an equivalent field name.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_http::{http, Request, Response};
|
/// # use actix_http::Response;
|
||||||
|
/// use actix_http::http::header::ContentType;
|
||||||
///
|
///
|
||||||
/// fn index(req: Request) -> Response {
|
/// Response::Ok()
|
||||||
/// Response::Ok()
|
/// .append_header(ContentType(mime::APPLICATION_JSON))
|
||||||
/// .set_header("X-TEST", "value")
|
/// .append_header(("X-TEST", "value1"))
|
||||||
/// .set_header(http::header::CONTENT_TYPE, "application/json")
|
/// .append_header(("X-TEST", "value2"))
|
||||||
/// .finish()
|
/// .finish();
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
|
pub fn append_header<H>(&mut self, header: H) -> &mut Self
|
||||||
|
where
|
||||||
|
H: IntoHeaderPair,
|
||||||
|
{
|
||||||
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
|
match header.try_into_header_pair() {
|
||||||
|
Ok((key, value)) => parts.headers.append(key, value),
|
||||||
|
Err(e) => self.err = Some(e.into()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replaced with [`Self::insert_header()`].
|
||||||
|
#[deprecated = "Replaced with `insert_header((key, value))`."]
|
||||||
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
K: TryInto<HeaderName>,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
K::Error: Into<HttpError>,
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
{
|
{
|
||||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
if self.err.is_some() {
|
||||||
match HeaderName::try_from(key) {
|
return self;
|
||||||
Ok(key) => match value.try_into() {
|
|
||||||
Ok(value) => {
|
|
||||||
parts.headers.insert(key, value);
|
|
||||||
}
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
|
||||||
},
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match (key.try_into(), value.try_into_value()) {
|
||||||
|
(Ok(name), Ok(value)) => return self.insert_header((name, value)),
|
||||||
|
(Err(err), _) => self.err = Some(err.into()),
|
||||||
|
(_, Err(err)) => self.err = Some(err.into()),
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replaced with [`Self::append_header()`].
|
||||||
|
#[deprecated = "Replaced with `append_header((key, value))`."]
|
||||||
|
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
||||||
|
where
|
||||||
|
K: TryInto<HeaderName>,
|
||||||
|
K::Error: Into<HttpError>,
|
||||||
|
V: IntoHeaderValue,
|
||||||
|
{
|
||||||
|
if self.err.is_some() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
match (key.try_into(), value.try_into_value()) {
|
||||||
|
(Ok(name), Ok(value)) => return self.append_header((name, value)),
|
||||||
|
(Err(err), _) => self.err = Some(err.into()),
|
||||||
|
(_, Err(err)) => self.err = Some(err.into()),
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,7 +466,12 @@ impl ResponseBuilder {
|
||||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
parts.set_connection_type(ConnectionType::Upgrade);
|
parts.set_connection_type(ConnectionType::Upgrade);
|
||||||
}
|
}
|
||||||
self.set_header(header::UPGRADE, value)
|
|
||||||
|
if let Ok(value) = value.try_into_value() {
|
||||||
|
self.insert_header((header::UPGRADE, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force close connection, even if it is marked as keep-alive
|
/// Force close connection, even if it is marked as keep-alive
|
||||||
|
@ -473,7 +486,7 @@ impl ResponseBuilder {
|
||||||
/// Disable chunked transfer encoding for HTTP/1.1 streaming responses.
|
/// Disable chunked transfer encoding for HTTP/1.1 streaming responses.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn no_chunking(&mut self, len: u64) -> &mut Self {
|
pub fn no_chunking(&mut self, len: u64) -> &mut Self {
|
||||||
self.header(header::CONTENT_LENGTH, len);
|
self.insert_header((header::CONTENT_LENGTH, len));
|
||||||
|
|
||||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
parts.no_chunking(true);
|
parts.no_chunking(true);
|
||||||
|
@ -488,7 +501,7 @@ impl ResponseBuilder {
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
{
|
{
|
||||||
if let Some(parts) = parts(&mut self.head, &self.err) {
|
if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
match value.try_into() {
|
match value.try_into_value() {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
parts.headers.insert(header::CONTENT_TYPE, value);
|
parts.headers.insert(header::CONTENT_TYPE, value);
|
||||||
}
|
}
|
||||||
|
@ -639,27 +652,24 @@ impl ResponseBuilder {
|
||||||
self.body(Body::from_message(BodyStream::new(stream)))
|
self.body(Body::from_message(BodyStream::new(stream)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Set a json body and generate `Response`
|
/// Set a json body and generate `Response`
|
||||||
///
|
///
|
||||||
/// `ResponseBuilder` can not be used after this call.
|
/// `ResponseBuilder` can not be used after this call.
|
||||||
pub fn json<T: Serialize>(&mut self, value: T) -> Response {
|
pub fn json<T>(&mut self, value: T) -> Response
|
||||||
self.json2(&value)
|
where
|
||||||
}
|
T: ops::Deref,
|
||||||
|
T::Target: Serialize,
|
||||||
/// Set a json body and generate `Response`
|
{
|
||||||
///
|
match serde_json::to_string(&*value) {
|
||||||
/// `ResponseBuilder` can not be used after this call.
|
|
||||||
pub fn json2<T: Serialize>(&mut self, value: &T) -> Response {
|
|
||||||
match serde_json::to_string(value) {
|
|
||||||
Ok(body) => {
|
Ok(body) => {
|
||||||
let contains = if let Some(parts) = parts(&mut self.head, &self.err) {
|
let contains = if let Some(parts) = parts(&mut self.head, &self.err) {
|
||||||
parts.headers.contains_key(header::CONTENT_TYPE)
|
parts.headers.contains_key(header::CONTENT_TYPE)
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
|
|
||||||
if !contains {
|
if !contains {
|
||||||
self.header(header::CONTENT_TYPE, "application/json");
|
self.insert_header(header::ContentType(mime::APPLICATION_JSON));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.body(Body::from(body))
|
self.body(Body::from(body))
|
||||||
|
@ -848,6 +858,8 @@ impl From<BytesMut> for Response {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::body::Body;
|
use crate::body::Body;
|
||||||
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE};
|
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE};
|
||||||
|
@ -855,8 +867,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_debug() {
|
fn test_debug() {
|
||||||
let resp = Response::Ok()
|
let resp = Response::Ok()
|
||||||
.header(COOKIE, HeaderValue::from_static("cookie1=value1; "))
|
.append_header((COOKIE, HeaderValue::from_static("cookie1=value1; ")))
|
||||||
.header(COOKIE, HeaderValue::from_static("cookie2=value2; "))
|
.append_header((COOKIE, HeaderValue::from_static("cookie2=value2; ")))
|
||||||
.finish();
|
.finish();
|
||||||
let dbg = format!("{:?}", resp);
|
let dbg = format!("{:?}", resp);
|
||||||
assert!(dbg.contains("Response"));
|
assert!(dbg.contains("Response"));
|
||||||
|
@ -867,8 +879,8 @@ mod tests {
|
||||||
use crate::httpmessage::HttpMessage;
|
use crate::httpmessage::HttpMessage;
|
||||||
|
|
||||||
let req = crate::test::TestRequest::default()
|
let req = crate::test::TestRequest::default()
|
||||||
.header(COOKIE, "cookie1=value1")
|
.append_header((COOKIE, "cookie1=value1"))
|
||||||
.header(COOKIE, "cookie2=value2")
|
.append_header((COOKIE, "cookie2=value2"))
|
||||||
.finish();
|
.finish();
|
||||||
let cookies = req.cookies().unwrap();
|
let cookies = req.cookies().unwrap();
|
||||||
|
|
||||||
|
@ -922,7 +934,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic_builder() {
|
fn test_basic_builder() {
|
||||||
let resp = Response::Ok().header("X-TEST", "value").finish();
|
let resp = Response::Ok().insert_header(("X-TEST", "value")).finish();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -963,26 +975,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_json_ct() {
|
fn test_json_ct() {
|
||||||
let resp = Response::build(StatusCode::OK)
|
let resp = Response::build(StatusCode::OK)
|
||||||
.header(CONTENT_TYPE, "text/json")
|
.insert_header((CONTENT_TYPE, "text/json"))
|
||||||
.json(vec!["v1", "v2", "v3"]);
|
.json(&vec!["v1", "v2", "v3"]);
|
||||||
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
|
|
||||||
assert_eq!(ct, HeaderValue::from_static("text/json"));
|
|
||||||
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_json2() {
|
|
||||||
let resp = Response::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]);
|
|
||||||
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
|
|
||||||
assert_eq!(ct, HeaderValue::from_static("application/json"));
|
|
||||||
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_json2_ct() {
|
|
||||||
let resp = Response::build(StatusCode::OK)
|
|
||||||
.header(CONTENT_TYPE, "text/json")
|
|
||||||
.json2(&vec!["v1", "v2", "v3"]);
|
|
||||||
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
|
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
|
||||||
assert_eq!(ct, HeaderValue::from_static("text/json"));
|
assert_eq!(ct, HeaderValue::from_static("text/json"));
|
||||||
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
|
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
|
||||||
|
@ -1081,4 +1075,54 @@ mod tests {
|
||||||
let cookie = resp.cookies().next().unwrap();
|
let cookie = resp.cookies().next().unwrap();
|
||||||
assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100"));
|
assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_builder_header_insert_kv() {
|
||||||
|
let mut res = Response::Ok();
|
||||||
|
res.insert_header(("Content-Type", "application/octet-stream"));
|
||||||
|
let res = res.finish();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
res.headers().get("Content-Type"),
|
||||||
|
Some(&HeaderValue::from_static("application/octet-stream"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_builder_header_insert_typed() {
|
||||||
|
let mut res = Response::Ok();
|
||||||
|
res.insert_header(header::ContentType(mime::APPLICATION_OCTET_STREAM));
|
||||||
|
let res = res.finish();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
res.headers().get("Content-Type"),
|
||||||
|
Some(&HeaderValue::from_static("application/octet-stream"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_builder_header_append_kv() {
|
||||||
|
let mut res = Response::Ok();
|
||||||
|
res.append_header(("Content-Type", "application/octet-stream"));
|
||||||
|
res.append_header(("Content-Type", "application/json"));
|
||||||
|
let res = res.finish();
|
||||||
|
|
||||||
|
let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
|
||||||
|
assert_eq!(headers.len(), 2);
|
||||||
|
assert!(headers.contains(&HeaderValue::from_static("application/octet-stream")));
|
||||||
|
assert!(headers.contains(&HeaderValue::from_static("application/json")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_builder_header_append_typed() {
|
||||||
|
let mut res = Response::Ok();
|
||||||
|
res.append_header(header::ContentType(mime::APPLICATION_OCTET_STREAM));
|
||||||
|
res.append_header(header::ContentType(mime::APPLICATION_JSON));
|
||||||
|
let res = res.finish();
|
||||||
|
|
||||||
|
let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect();
|
||||||
|
assert_eq!(headers.len(), 2);
|
||||||
|
assert!(headers.contains(&HeaderValue::from_static("application/octet-stream")));
|
||||||
|
assert!(headers.contains(&HeaderValue::from_static("application/json")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Ref, RefCell},
|
cell::{Ref, RefCell},
|
||||||
convert::TryFrom,
|
|
||||||
io::{self, Read, Write},
|
io::{self, Read, Write},
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
@ -12,14 +11,17 @@ use std::{
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use http::header::{self, HeaderName, HeaderValue};
|
use http::{
|
||||||
use http::{Error as HttpError, Method, Uri, Version};
|
header::{self, HeaderValue},
|
||||||
|
Method, Uri, Version,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::cookie::{Cookie, CookieJar};
|
use crate::{
|
||||||
use crate::header::HeaderMap;
|
cookie::{Cookie, CookieJar},
|
||||||
use crate::header::{Header, IntoHeaderValue};
|
header::{HeaderMap, IntoHeaderPair},
|
||||||
use crate::payload::Payload;
|
payload::Payload,
|
||||||
use crate::Request;
|
Request,
|
||||||
|
};
|
||||||
|
|
||||||
/// Test `Request` builder
|
/// Test `Request` builder
|
||||||
///
|
///
|
||||||
|
@ -36,7 +38,7 @@ use crate::Request;
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let resp = TestRequest::with_header("content-type", "text/plain")
|
/// let resp = TestRequest::default().insert_header("content-type", "text/plain")
|
||||||
/// .run(&index)
|
/// .run(&index)
|
||||||
/// .unwrap();
|
/// .unwrap();
|
||||||
/// assert_eq!(resp.status(), StatusCode::OK);
|
/// assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
@ -69,76 +71,73 @@ impl Default for TestRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestRequest {
|
impl TestRequest {
|
||||||
/// Create TestRequest and set request uri
|
/// Create a default TestRequest and then set its URI.
|
||||||
pub fn with_uri(path: &str) -> TestRequest {
|
pub fn with_uri(path: &str) -> TestRequest {
|
||||||
TestRequest::default().uri(path).take()
|
TestRequest::default().uri(path).take()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create TestRequest and set header
|
/// Set HTTP version of this request.
|
||||||
pub fn with_hdr<H: Header>(hdr: H) -> TestRequest {
|
|
||||||
TestRequest::default().set(hdr).take()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create TestRequest and set header
|
|
||||||
pub fn with_header<K, V>(key: K, value: V) -> TestRequest
|
|
||||||
where
|
|
||||||
HeaderName: TryFrom<K>,
|
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
|
||||||
V: IntoHeaderValue,
|
|
||||||
{
|
|
||||||
TestRequest::default().header(key, value).take()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set HTTP version of this request
|
|
||||||
pub fn version(&mut self, ver: Version) -> &mut Self {
|
pub fn version(&mut self, ver: Version) -> &mut Self {
|
||||||
parts(&mut self.0).version = ver;
|
parts(&mut self.0).version = ver;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set HTTP method of this request
|
/// Set HTTP method of this request.
|
||||||
pub fn method(&mut self, meth: Method) -> &mut Self {
|
pub fn method(&mut self, meth: Method) -> &mut Self {
|
||||||
parts(&mut self.0).method = meth;
|
parts(&mut self.0).method = meth;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set HTTP Uri of this request
|
/// Set URI of this request.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If provided URI is invalid.
|
||||||
pub fn uri(&mut self, path: &str) -> &mut Self {
|
pub fn uri(&mut self, path: &str) -> &mut Self {
|
||||||
parts(&mut self.0).uri = Uri::from_str(path).unwrap();
|
parts(&mut self.0).uri = Uri::from_str(path).unwrap();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a header
|
/// Insert a header, replacing any that were set with an equivalent field name.
|
||||||
pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
|
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
|
||||||
if let Ok(value) = hdr.try_into() {
|
|
||||||
parts(&mut self.0).headers.append(H::name(), value);
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
panic!("Can not set header");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a header
|
|
||||||
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
H: IntoHeaderPair,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
|
||||||
V: IntoHeaderValue,
|
|
||||||
{
|
{
|
||||||
if let Ok(key) = HeaderName::try_from(key) {
|
match header.try_into_header_pair() {
|
||||||
if let Ok(value) = value.try_into() {
|
Ok((key, value)) => {
|
||||||
parts(&mut self.0).headers.append(key, value);
|
parts(&mut self.0).headers.insert(key, value);
|
||||||
return self;
|
}
|
||||||
|
Err(err) => {
|
||||||
|
panic!("Error inserting test header: {}.", err.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic!("Can not create header");
|
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set cookie for this request
|
/// Append a header, keeping any that were set with an equivalent field name.
|
||||||
|
pub fn append_header<H>(&mut self, header: H) -> &mut Self
|
||||||
|
where
|
||||||
|
H: IntoHeaderPair,
|
||||||
|
{
|
||||||
|
match header.try_into_header_pair() {
|
||||||
|
Ok((key, value)) => {
|
||||||
|
parts(&mut self.0).headers.append(key, value);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
panic!("Error inserting test header: {}.", err.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set cookie for this request.
|
||||||
pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self {
|
pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self {
|
||||||
parts(&mut self.0).cookies.add(cookie.into_owned());
|
parts(&mut self.0).cookies.add(cookie.into_owned());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set request payload
|
/// Set request payload.
|
||||||
pub fn set_payload<B: Into<Bytes>>(&mut self, data: B) -> &mut Self {
|
pub fn set_payload<B: Into<Bytes>>(&mut self, data: B) -> &mut Self {
|
||||||
let mut payload = crate::h1::Payload::empty();
|
let mut payload = crate::h1::Payload::empty();
|
||||||
payload.unread_data(data.into());
|
payload.unread_data(data.into());
|
||||||
|
@ -150,7 +149,7 @@ impl TestRequest {
|
||||||
TestRequest(self.0.take())
|
TestRequest(self.0.take())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Complete request creation and generate `Request` instance
|
/// Complete request creation and generate `Request` instance.
|
||||||
pub fn finish(&mut self) -> Request {
|
pub fn finish(&mut self) -> Request {
|
||||||
let inner = self.0.take().expect("cannot reuse test request builder");
|
let inner = self.0.take().expect("cannot reuse test request builder");
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use std::ptr::copy_nonoverlapping;
|
use std::ptr::copy_nonoverlapping;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
|
||||||
// Holds a slice guaranteed to be shorter than 8 bytes
|
/// Holds a slice guaranteed to be shorter than 8 bytes.
|
||||||
struct ShortSlice<'a> {
|
struct ShortSlice<'a> {
|
||||||
inner: &'a mut [u8],
|
inner: &'a mut [u8],
|
||||||
}
|
}
|
||||||
|
@ -80,8 +80,10 @@ unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] {
|
||||||
slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3)
|
slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Splits a slice into three parts: an unaligned short head and tail, plus an aligned
|
/// Splits a slice into three parts:
|
||||||
// u64 mid section.
|
/// - an unaligned short head
|
||||||
|
/// - an aligned `u64` slice mid section
|
||||||
|
/// - an unaligned short tail
|
||||||
#[inline]
|
#[inline]
|
||||||
fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) {
|
fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) {
|
||||||
let start_ptr = buf.as_ptr() as usize;
|
let start_ptr = buf.as_ptr() as usize;
|
||||||
|
|
|
@ -101,7 +101,7 @@ impl ResponseError for HandshakeError {
|
||||||
fn error_response(&self) -> Response {
|
fn error_response(&self) -> Response {
|
||||||
match self {
|
match self {
|
||||||
HandshakeError::GetMethodRequired => Response::MethodNotAllowed()
|
HandshakeError::GetMethodRequired => Response::MethodNotAllowed()
|
||||||
.header(header::ALLOW, "GET")
|
.insert_header((header::ALLOW, "GET"))
|
||||||
.finish(),
|
.finish(),
|
||||||
|
|
||||||
HandshakeError::NoWebsocketUpgrade => Response::BadRequest()
|
HandshakeError::NoWebsocketUpgrade => Response::BadRequest()
|
||||||
|
@ -128,18 +128,12 @@ impl ResponseError for HandshakeError {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify `WebSocket` handshake request and create handshake response.
|
/// Verify `WebSocket` handshake request and create handshake response.
|
||||||
// /// `protocols` is a sequence of known protocols. On successful handshake,
|
|
||||||
// /// the returned response headers contain the first protocol in this list
|
|
||||||
// /// which the server also knows.
|
|
||||||
pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
|
pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
|
||||||
verify_handshake(req)?;
|
verify_handshake(req)?;
|
||||||
Ok(handshake_response(req))
|
Ok(handshake_response(req))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify `WebSocket` handshake request.
|
/// Verify `WebSocket` handshake request.
|
||||||
// /// `protocols` is a sequence of known protocols. On successful handshake,
|
|
||||||
// /// the returned response headers contain the first protocol in this list
|
|
||||||
// /// which the server also knows.
|
|
||||||
pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> {
|
pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> {
|
||||||
// WebSocket accepts only GET
|
// WebSocket accepts only GET
|
||||||
if req.method != Method::GET {
|
if req.method != Method::GET {
|
||||||
|
@ -198,8 +192,8 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
|
||||||
|
|
||||||
Response::build(StatusCode::SWITCHING_PROTOCOLS)
|
Response::build(StatusCode::SWITCHING_PROTOCOLS)
|
||||||
.upgrade("websocket")
|
.upgrade("websocket")
|
||||||
.header(header::TRANSFER_ENCODING, "chunked")
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
.header(header::SEC_WEBSOCKET_ACCEPT, key.as_str())
|
.insert_header((header::SEC_WEBSOCKET_ACCEPT, key))
|
||||||
.take()
|
.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +218,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(header::UPGRADE, header::HeaderValue::from_static("test"))
|
.insert_header((header::UPGRADE, header::HeaderValue::from_static("test")))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoWebsocketUpgrade,
|
HandshakeError::NoWebsocketUpgrade,
|
||||||
|
@ -232,10 +226,10 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoConnectionUpgrade,
|
HandshakeError::NoConnectionUpgrade,
|
||||||
|
@ -243,14 +237,14 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoVersionHeader,
|
HandshakeError::NoVersionHeader,
|
||||||
|
@ -258,18 +252,18 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_VERSION,
|
header::SEC_WEBSOCKET_VERSION,
|
||||||
header::HeaderValue::from_static("5"),
|
header::HeaderValue::from_static("5"),
|
||||||
)
|
))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::UnsupportedVersion,
|
HandshakeError::UnsupportedVersion,
|
||||||
|
@ -277,18 +271,18 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_VERSION,
|
header::SEC_WEBSOCKET_VERSION,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::BadWebsocketKey,
|
HandshakeError::BadWebsocketKey,
|
||||||
|
@ -296,22 +290,22 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_VERSION,
|
header::SEC_WEBSOCKET_VERSION,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_KEY,
|
header::SEC_WEBSOCKET_KEY,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.finish();
|
.finish();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
StatusCode::SWITCHING_PROTOCOLS,
|
StatusCode::SWITCHING_PROTOCOLS,
|
||||||
|
|
|
@ -38,7 +38,7 @@ async fn test_h1_v2() {
|
||||||
let response = srv.get("/").send().await.unwrap();
|
let response = srv.get("/").send().await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
let request = srv.get("/").header("x-test", "111").send();
|
let request = srv.get("/").insert_header(("x-test", "111")).send();
|
||||||
let mut response = request.await.unwrap();
|
let mut response = request.await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
|
|
@ -173,8 +173,8 @@ async fn test_h2_headers() {
|
||||||
HttpService::build().h2(move |_| {
|
HttpService::build().h2(move |_| {
|
||||||
let mut builder = Response::Ok();
|
let mut builder = Response::Ok();
|
||||||
for idx in 0..90 {
|
for idx in 0..90 {
|
||||||
builder.header(
|
builder.insert_header(
|
||||||
format!("X-TEST-{}", idx).as_str(),
|
(format!("X-TEST-{}", idx).as_str(),
|
||||||
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
|
@ -188,7 +188,7 @@ async fn test_h2_headers() {
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
ok::<_, ()>(builder.body(data.clone()))
|
ok::<_, ()>(builder.body(data.clone()))
|
||||||
})
|
})
|
||||||
|
@ -341,7 +341,7 @@ async fn test_h2_body_chunked_explicit() {
|
||||||
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
||||||
ok::<_, ()>(
|
ok::<_, ()>(
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.header(header::TRANSFER_ENCODING, "chunked")
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
.streaming(body),
|
.streaming(body),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -369,7 +369,7 @@ async fn test_h2_response_http_error_handling() {
|
||||||
let broken_header = Bytes::from_static(b"\0\0\0");
|
let broken_header = Bytes::from_static(b"\0\0\0");
|
||||||
ok::<_, ()>(
|
ok::<_, ()>(
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.header(header::CONTENT_TYPE, broken_header)
|
.insert_header((header::CONTENT_TYPE, broken_header))
|
||||||
.body(STR),
|
.body(STR),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
|
@ -408,7 +408,9 @@ async fn test_h2_service_error() {
|
||||||
async fn test_h2_on_connect() {
|
async fn test_h2_on_connect() {
|
||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.on_connect_ext(|_, data| data.insert(20isize))
|
.on_connect_ext(|_, data| {
|
||||||
|
data.insert(20isize);
|
||||||
|
})
|
||||||
.h2(|req: Request| {
|
.h2(|req: Request| {
|
||||||
assert!(req.extensions().contains::<isize>());
|
assert!(req.extensions().contains::<isize>());
|
||||||
ok::<_, ()>(Response::Ok().finish())
|
ok::<_, ()>(Response::Ok().finish())
|
||||||
|
|
|
@ -181,7 +181,7 @@ async fn test_h2_headers() {
|
||||||
HttpService::build().h2(move |_| {
|
HttpService::build().h2(move |_| {
|
||||||
let mut config = Response::Ok();
|
let mut config = Response::Ok();
|
||||||
for idx in 0..90 {
|
for idx in 0..90 {
|
||||||
config.header(
|
config.insert_header((
|
||||||
format!("X-TEST-{}", idx).as_str(),
|
format!("X-TEST-{}", idx).as_str(),
|
||||||
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
|
@ -196,7 +196,7 @@ async fn test_h2_headers() {
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
future::ok::<_, ()>(config.body(data.clone()))
|
future::ok::<_, ()>(config.body(data.clone()))
|
||||||
})
|
})
|
||||||
|
@ -352,7 +352,7 @@ async fn test_h2_body_chunked_explicit() {
|
||||||
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
||||||
ok::<_, ()>(
|
ok::<_, ()>(
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.header(header::TRANSFER_ENCODING, "chunked")
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
.streaming(body),
|
.streaming(body),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -380,7 +380,7 @@ async fn test_h2_response_http_error_handling() {
|
||||||
let broken_header = Bytes::from_static(b"\0\0\0");
|
let broken_header = Bytes::from_static(b"\0\0\0");
|
||||||
ok::<_, ()>(
|
ok::<_, ()>(
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.header(http::header::CONTENT_TYPE, broken_header)
|
.insert_header((http::header::CONTENT_TYPE, broken_header))
|
||||||
.body(STR),
|
.body(STR),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -392,7 +392,7 @@ async fn test_h1_headers() {
|
||||||
HttpService::build().h1(move |_| {
|
HttpService::build().h1(move |_| {
|
||||||
let mut builder = Response::Ok();
|
let mut builder = Response::Ok();
|
||||||
for idx in 0..90 {
|
for idx in 0..90 {
|
||||||
builder.header(
|
builder.insert_header((
|
||||||
format!("X-TEST-{}", idx).as_str(),
|
format!("X-TEST-{}", idx).as_str(),
|
||||||
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
|
@ -407,7 +407,7 @@ async fn test_h1_headers() {
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
|
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
future::ok::<_, ()>(builder.body(data.clone()))
|
future::ok::<_, ()>(builder.body(data.clone()))
|
||||||
}).tcp()
|
}).tcp()
|
||||||
|
@ -561,7 +561,7 @@ async fn test_h1_body_chunked_explicit() {
|
||||||
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
||||||
ok::<_, ()>(
|
ok::<_, ()>(
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.header(header::TRANSFER_ENCODING, "chunked")
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
.streaming(body),
|
.streaming(body),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -625,7 +625,7 @@ async fn test_h1_response_http_error_handling() {
|
||||||
let broken_header = Bytes::from_static(b"\0\0\0");
|
let broken_header = Bytes::from_static(b"\0\0\0");
|
||||||
ok::<_, ()>(
|
ok::<_, ()>(
|
||||||
Response::Ok()
|
Response::Ok()
|
||||||
.header(http::header::CONTENT_TYPE, broken_header)
|
.insert_header((http::header::CONTENT_TYPE, broken_header))
|
||||||
.body(STR),
|
.body(STR),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
|
@ -662,7 +662,9 @@ async fn test_h1_service_error() {
|
||||||
async fn test_h1_on_connect() {
|
async fn test_h1_on_connect() {
|
||||||
let srv = test_server(|| {
|
let srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.on_connect_ext(|_, data| data.insert(20isize))
|
.on_connect_ext(|_, data| {
|
||||||
|
data.insert(20isize);
|
||||||
|
})
|
||||||
.h1(|req: Request| {
|
.h1(|req: Request| {
|
||||||
assert!(req.extensions().contains::<isize>());
|
assert!(req.extensions().contains::<isize>());
|
||||||
future::ok::<_, ()>(Response::Ok().finish())
|
future::ok::<_, ()>(Response::Ok().finish())
|
||||||
|
|
|
@ -166,11 +166,11 @@ pub fn handshake_with_protocols(
|
||||||
|
|
||||||
let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS)
|
let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS)
|
||||||
.upgrade("websocket")
|
.upgrade("websocket")
|
||||||
.header(header::SEC_WEBSOCKET_ACCEPT, key.as_str())
|
.insert_header((header::SEC_WEBSOCKET_ACCEPT, key))
|
||||||
.take();
|
.take();
|
||||||
|
|
||||||
if let Some(protocol) = protocol {
|
if let Some(protocol) = protocol {
|
||||||
response.header(&header::SEC_WEBSOCKET_PROTOCOL, protocol);
|
response.insert_header((header::SEC_WEBSOCKET_PROTOCOL, protocol));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
|
@ -573,7 +573,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(header::UPGRADE, header::HeaderValue::from_static("test"))
|
.insert_header((header::UPGRADE, header::HeaderValue::from_static("test")))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoWebsocketUpgrade,
|
HandshakeError::NoWebsocketUpgrade,
|
||||||
|
@ -581,10 +581,10 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoConnectionUpgrade,
|
HandshakeError::NoConnectionUpgrade,
|
||||||
|
@ -592,14 +592,14 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::NoVersionHeader,
|
HandshakeError::NoVersionHeader,
|
||||||
|
@ -607,18 +607,18 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_VERSION,
|
header::SEC_WEBSOCKET_VERSION,
|
||||||
header::HeaderValue::from_static("5"),
|
header::HeaderValue::from_static("5"),
|
||||||
)
|
))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::UnsupportedVersion,
|
HandshakeError::UnsupportedVersion,
|
||||||
|
@ -626,18 +626,18 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_VERSION,
|
header::SEC_WEBSOCKET_VERSION,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
HandshakeError::BadWebsocketKey,
|
HandshakeError::BadWebsocketKey,
|
||||||
|
@ -645,22 +645,22 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_VERSION,
|
header::SEC_WEBSOCKET_VERSION,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_KEY,
|
header::SEC_WEBSOCKET_KEY,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
|
|
||||||
let resp = handshake(&req).unwrap().finish();
|
let resp = handshake(&req).unwrap().finish();
|
||||||
|
@ -669,26 +669,26 @@ mod tests {
|
||||||
assert_eq!(None, resp.headers().get(&header::TRANSFER_ENCODING));
|
assert_eq!(None, resp.headers().get(&header::TRANSFER_ENCODING));
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_VERSION,
|
header::SEC_WEBSOCKET_VERSION,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_KEY,
|
header::SEC_WEBSOCKET_KEY,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_PROTOCOL,
|
header::SEC_WEBSOCKET_PROTOCOL,
|
||||||
header::HeaderValue::from_static("graphql"),
|
header::HeaderValue::from_static("graphql"),
|
||||||
)
|
))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
|
|
||||||
let protocols = ["graphql"];
|
let protocols = ["graphql"];
|
||||||
|
@ -710,26 +710,26 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_VERSION,
|
header::SEC_WEBSOCKET_VERSION,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_KEY,
|
header::SEC_WEBSOCKET_KEY,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_PROTOCOL,
|
header::SEC_WEBSOCKET_PROTOCOL,
|
||||||
header::HeaderValue::from_static("p1, p2, p3"),
|
header::HeaderValue::from_static("p1, p2, p3"),
|
||||||
)
|
))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
|
|
||||||
let protocols = vec!["p3", "p2"];
|
let protocols = vec!["p3", "p2"];
|
||||||
|
@ -751,26 +751,26 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::UPGRADE,
|
header::UPGRADE,
|
||||||
header::HeaderValue::from_static("websocket"),
|
header::HeaderValue::from_static("websocket"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONNECTION,
|
header::CONNECTION,
|
||||||
header::HeaderValue::from_static("upgrade"),
|
header::HeaderValue::from_static("upgrade"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_VERSION,
|
header::SEC_WEBSOCKET_VERSION,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_KEY,
|
header::SEC_WEBSOCKET_KEY,
|
||||||
header::HeaderValue::from_static("13"),
|
header::HeaderValue::from_static("13"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::SEC_WEBSOCKET_PROTOCOL,
|
header::SEC_WEBSOCKET_PROTOCOL,
|
||||||
header::HeaderValue::from_static("p1,p2,p3"),
|
header::HeaderValue::from_static("p1,p2,p3"),
|
||||||
)
|
))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
|
|
||||||
let protocols = vec!["p3", "p2"];
|
let protocols = vec!["p3", "p2"];
|
||||||
|
|
|
@ -1,6 +1,17 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
### Added
|
||||||
|
* `ClientRequest::insert_header` method which allows using typed headers. [#1869]
|
||||||
|
* `ClientRequest::append_header` method which allows using typed headers. [#1869]
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869]
|
||||||
|
* `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869]
|
||||||
|
* `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869]
|
||||||
|
* `ClientRequest::header`; use `ClientRequest::append_header`. [#1869]
|
||||||
|
|
||||||
|
[#1869]: https://github.com/actix/actix-web/pull/1869
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.1 - 2021-01-07
|
## 3.0.0-beta.1 - 2021-01-07
|
||||||
|
|
|
@ -67,8 +67,10 @@ actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] }
|
||||||
actix-utils = "3.0.0-beta.1"
|
actix-utils = "3.0.0-beta.1"
|
||||||
actix-server = "2.0.0-beta.2"
|
actix-server = "2.0.0-beta.2"
|
||||||
actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] }
|
actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] }
|
||||||
|
|
||||||
brotli2 = "0.3.2"
|
brotli2 = "0.3.2"
|
||||||
flate2 = "1.0.13"
|
flate2 = "1.0.13"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
|
rcgen = "0.8"
|
||||||
webpki = "0.21"
|
webpki = "0.21"
|
||||||
|
|
|
@ -133,7 +133,7 @@ impl ClientBuilder {
|
||||||
V::Error: fmt::Debug,
|
V::Error: fmt::Debug,
|
||||||
{
|
{
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => match value.try_into() {
|
Ok(key) => match value.try_into_value() {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
self.headers.append(key, value);
|
self.headers.append(key, value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,7 +144,7 @@ impl FrozenSendBuilder {
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
{
|
{
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => match value.try_into() {
|
Ok(key) => match value.try_into_value() {
|
||||||
Ok(value) => self.extra_headers.insert(key, value),
|
Ok(value) => self.extra_headers.insert(key, value),
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||||
//! let mut client = awc::Client::default();
|
//! let mut client = awc::Client::default();
|
||||||
//! let response = client.get("http://www.rust-lang.org") // <- Create request builder
|
//! let response = client.get("http://www.rust-lang.org") // <- Create request builder
|
||||||
//! .header("User-Agent", "Actix-web")
|
//! .insert_header(("User-Agent", "Actix-web"))
|
||||||
//! .send() // <- Send http request
|
//! .send() // <- Send http request
|
||||||
//! .await?;
|
//! .await?;
|
||||||
//!
|
//!
|
||||||
|
@ -134,7 +134,7 @@ use self::connect::{Connect, ConnectorWrapper};
|
||||||
/// let mut client = Client::default();
|
/// let mut client = Client::default();
|
||||||
///
|
///
|
||||||
/// let res = client.get("http://www.rust-lang.org") // <- Create request builder
|
/// let res = client.get("http://www.rust-lang.org") // <- Create request builder
|
||||||
/// .header("User-Agent", "Actix-web")
|
/// .insert_header(("User-Agent", "Actix-web"))
|
||||||
/// .send() // <- Send http request
|
/// .send() // <- Send http request
|
||||||
/// .await; // <- send request and wait for response
|
/// .await; // <- send request and wait for response
|
||||||
///
|
///
|
||||||
|
@ -182,8 +182,8 @@ impl Client {
|
||||||
{
|
{
|
||||||
let mut req = ClientRequest::new(method, url, self.0.clone());
|
let mut req = ClientRequest::new(method, url, self.0.clone());
|
||||||
|
|
||||||
for (key, value) in self.0.headers.iter() {
|
for header in self.0.headers.iter() {
|
||||||
req = req.set_header_if_none(key.clone(), value.clone());
|
req = req.insert_header_if_none(header);
|
||||||
}
|
}
|
||||||
req
|
req
|
||||||
}
|
}
|
||||||
|
@ -198,8 +198,8 @@ impl Client {
|
||||||
<Uri as TryFrom<U>>::Error: Into<HttpError>,
|
<Uri as TryFrom<U>>::Error: Into<HttpError>,
|
||||||
{
|
{
|
||||||
let mut req = self.request(head.method.clone(), url);
|
let mut req = self.request(head.method.clone(), url);
|
||||||
for (key, value) in head.headers.iter() {
|
for header in head.headers.iter() {
|
||||||
req = req.set_header_if_none(key.clone(), value.clone());
|
req = req.insert_header_if_none(header);
|
||||||
}
|
}
|
||||||
req
|
req
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ use serde::Serialize;
|
||||||
|
|
||||||
use actix_http::body::Body;
|
use actix_http::body::Body;
|
||||||
use actix_http::cookie::{Cookie, CookieJar};
|
use actix_http::cookie::{Cookie, CookieJar};
|
||||||
use actix_http::http::header::{self, Header, IntoHeaderValue};
|
use actix_http::http::header::{self, IntoHeaderPair};
|
||||||
use actix_http::http::{
|
use actix_http::http::{
|
||||||
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue, Method,
|
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri,
|
||||||
Uri, Version,
|
Version,
|
||||||
};
|
};
|
||||||
use actix_http::{Error, RequestHead};
|
use actix_http::{Error, RequestHead};
|
||||||
|
|
||||||
|
@ -37,13 +37,11 @@ cfg_if::cfg_if! {
|
||||||
/// builder-like pattern.
|
/// builder-like pattern.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_rt::System;
|
|
||||||
///
|
|
||||||
/// #[actix_rt::main]
|
/// #[actix_rt::main]
|
||||||
/// async fn main() {
|
/// async fn main() {
|
||||||
/// let response = awc::Client::new()
|
/// let response = awc::Client::new()
|
||||||
/// .get("http://www.rust-lang.org") // <- Create request builder
|
/// .get("http://www.rust-lang.org") // <- Create request builder
|
||||||
/// .header("User-Agent", "Actix-web")
|
/// .insert_header(("User-Agent", "Actix-web"))
|
||||||
/// .send() // <- Send http request
|
/// .send() // <- Send http request
|
||||||
/// .await;
|
/// .await;
|
||||||
///
|
///
|
||||||
|
@ -143,110 +141,71 @@ impl ClientRequest {
|
||||||
&self.head.peer_addr
|
&self.head.peer_addr
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Returns request's headers.
|
/// Returns request's headers.
|
||||||
|
#[inline]
|
||||||
pub fn headers(&self) -> &HeaderMap {
|
pub fn headers(&self) -> &HeaderMap {
|
||||||
&self.head.headers
|
&self.head.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Returns request's mutable headers.
|
/// Returns request's mutable headers.
|
||||||
|
#[inline]
|
||||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||||
&mut self.head.headers
|
&mut self.head.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a header.
|
/// Insert a header, replacing any that were set with an equivalent field name.
|
||||||
///
|
pub fn insert_header<H>(mut self, header: H) -> Self
|
||||||
/// ```rust
|
|
||||||
/// fn main() {
|
|
||||||
/// # actix_rt::System::new("test").block_on(futures_util::future::lazy(|_| {
|
|
||||||
/// let req = awc::Client::new()
|
|
||||||
/// .get("http://www.rust-lang.org")
|
|
||||||
/// .set(awc::http::header::Date::now())
|
|
||||||
/// .set(awc::http::header::ContentType(mime::TEXT_HTML));
|
|
||||||
/// # Ok::<_, ()>(())
|
|
||||||
/// # }));
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn set<H: Header>(mut self, hdr: H) -> Self {
|
|
||||||
match hdr.try_into() {
|
|
||||||
Ok(value) => {
|
|
||||||
self.head.headers.insert(H::name(), value);
|
|
||||||
}
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Append a header.
|
|
||||||
///
|
|
||||||
/// Header gets appended to existing header.
|
|
||||||
/// To override header use `set_header()` method.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use awc::{http, Client};
|
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// # actix_rt::System::new("test").block_on(async {
|
|
||||||
/// let req = Client::new()
|
|
||||||
/// .get("http://www.rust-lang.org")
|
|
||||||
/// .header("X-TEST", "value")
|
|
||||||
/// .header(http::header::CONTENT_TYPE, "application/json");
|
|
||||||
/// # Ok::<_, ()>(())
|
|
||||||
/// # });
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
H: IntoHeaderPair,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
|
||||||
V: IntoHeaderValue,
|
|
||||||
{
|
{
|
||||||
match HeaderName::try_from(key) {
|
match header.try_into_header_pair() {
|
||||||
Ok(key) => match value.try_into() {
|
Ok((key, value)) => self.head.headers.insert(key, value),
|
||||||
Ok(value) => self.head.headers.append(key, value),
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
|
||||||
},
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
}
|
};
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert a header, replaces existing header.
|
|
||||||
pub fn set_header<K, V>(mut self, key: K, value: V) -> Self
|
|
||||||
where
|
|
||||||
HeaderName: TryFrom<K>,
|
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
|
||||||
V: IntoHeaderValue,
|
|
||||||
{
|
|
||||||
match HeaderName::try_from(key) {
|
|
||||||
Ok(key) => match value.try_into() {
|
|
||||||
Ok(value) => self.head.headers.insert(key, value),
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
|
||||||
},
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
|
||||||
}
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a header only if it is not yet set.
|
/// Insert a header only if it is not yet set.
|
||||||
pub fn set_header_if_none<K, V>(mut self, key: K, value: V) -> Self
|
pub fn insert_header_if_none<H>(mut self, header: H) -> Self
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
H: IntoHeaderPair,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
|
||||||
V: IntoHeaderValue,
|
|
||||||
{
|
{
|
||||||
match HeaderName::try_from(key) {
|
match header.try_into_header_pair() {
|
||||||
Ok(key) => {
|
Ok((key, value)) => {
|
||||||
if !self.head.headers.contains_key(&key) {
|
if !self.head.headers.contains_key(&key) {
|
||||||
match value.try_into() {
|
self.head.headers.insert(key, value);
|
||||||
Ok(value) => self.head.headers.insert(key, value),
|
|
||||||
Err(e) => self.err = Some(e.into()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append a header, keeping any that were set with an equivalent field name.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[actix_rt::main]
|
||||||
|
/// # async fn main() {
|
||||||
|
/// # use awc::Client;
|
||||||
|
/// use awc::http::header::ContentType;
|
||||||
|
///
|
||||||
|
/// Client::new()
|
||||||
|
/// .get("http://www.rust-lang.org")
|
||||||
|
/// .insert_header(("X-TEST", "value"))
|
||||||
|
/// .insert_header(ContentType(mime::APPLICATION_JSON));
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn append_header<H>(mut self, header: H) -> Self
|
||||||
|
where
|
||||||
|
H: IntoHeaderPair,
|
||||||
|
{
|
||||||
|
match header.try_into_header_pair() {
|
||||||
|
Ok((key, value)) => self.head.headers.append(key, value),
|
||||||
|
Err(e) => self.err = Some(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,7 +241,7 @@ impl ClientRequest {
|
||||||
/// Set content length
|
/// Set content length
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn content_length(self, len: u64) -> Self {
|
pub fn content_length(self, len: u64) -> Self {
|
||||||
self.header(header::CONTENT_LENGTH, len)
|
self.append_header((header::CONTENT_LENGTH, len))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set HTTP basic authorization header
|
/// Set HTTP basic authorization header
|
||||||
|
@ -294,10 +253,10 @@ impl ClientRequest {
|
||||||
Some(password) => format!("{}:{}", username, password),
|
Some(password) => format!("{}:{}", username, password),
|
||||||
None => format!("{}:", username),
|
None => format!("{}:", username),
|
||||||
};
|
};
|
||||||
self.header(
|
self.append_header((
|
||||||
header::AUTHORIZATION,
|
header::AUTHORIZATION,
|
||||||
format!("Basic {}", base64::encode(&auth)),
|
format!("Basic {}", base64::encode(&auth)),
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set HTTP bearer authentication header
|
/// Set HTTP bearer authentication header
|
||||||
|
@ -305,7 +264,7 @@ impl ClientRequest {
|
||||||
where
|
where
|
||||||
T: fmt::Display,
|
T: fmt::Display,
|
||||||
{
|
{
|
||||||
self.header(header::AUTHORIZATION, format!("Bearer {}", token))
|
self.append_header((header::AUTHORIZATION, format!("Bearer {}", token)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a cookie
|
/// Set a cookie
|
||||||
|
@ -557,12 +516,15 @@ impl ClientRequest {
|
||||||
.unwrap_or(true);
|
.unwrap_or(true);
|
||||||
|
|
||||||
if https {
|
if https {
|
||||||
slf = slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING)
|
slf =
|
||||||
|
slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING))
|
||||||
} else {
|
} else {
|
||||||
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
|
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||||
{
|
{
|
||||||
slf =
|
slf = slf.insert_header_if_none((
|
||||||
slf.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate")
|
header::ACCEPT_ENCODING,
|
||||||
|
"gzip, deflate",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -595,7 +557,7 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_debug() {
|
async fn test_debug() {
|
||||||
let request = Client::new().get("/").header("x-test", "111");
|
let request = Client::new().get("/").append_header(("x-test", "111"));
|
||||||
let repr = format!("{:?}", request);
|
let repr = format!("{:?}", request);
|
||||||
assert!(repr.contains("ClientRequest"));
|
assert!(repr.contains("ClientRequest"));
|
||||||
assert!(repr.contains("x-test"));
|
assert!(repr.contains("x-test"));
|
||||||
|
@ -606,18 +568,18 @@ mod tests {
|
||||||
let req = Client::new()
|
let req = Client::new()
|
||||||
.put("/")
|
.put("/")
|
||||||
.version(Version::HTTP_2)
|
.version(Version::HTTP_2)
|
||||||
.set(header::Date(SystemTime::now().into()))
|
.insert_header(header::Date(SystemTime::now().into()))
|
||||||
.content_type("plain/text")
|
.content_type("plain/text")
|
||||||
.header(header::SERVER, "awc");
|
.append_header((header::SERVER, "awc"));
|
||||||
|
|
||||||
let req = if let Some(val) = Some("server") {
|
let req = if let Some(val) = Some("server") {
|
||||||
req.header(header::USER_AGENT, val)
|
req.append_header((header::USER_AGENT, val))
|
||||||
} else {
|
} else {
|
||||||
req
|
req
|
||||||
};
|
};
|
||||||
|
|
||||||
let req = if let Some(_val) = Option::<&str>::None {
|
let req = if let Some(_val) = Option::<&str>::None {
|
||||||
req.header(header::ALLOW, "1")
|
req.append_header((header::ALLOW, "1"))
|
||||||
} else {
|
} else {
|
||||||
req
|
req
|
||||||
};
|
};
|
||||||
|
@ -660,7 +622,7 @@ mod tests {
|
||||||
.header(header::CONTENT_TYPE, "111")
|
.header(header::CONTENT_TYPE, "111")
|
||||||
.finish()
|
.finish()
|
||||||
.get("/")
|
.get("/")
|
||||||
.set_header(header::CONTENT_TYPE, "222");
|
.insert_header((header::CONTENT_TYPE, "222"));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
req.head
|
req.head
|
||||||
|
|
|
@ -296,7 +296,7 @@ impl RequestSender {
|
||||||
match self {
|
match self {
|
||||||
RequestSender::Owned(head) => {
|
RequestSender::Owned(head) => {
|
||||||
if !head.headers.contains_key(&key) {
|
if !head.headers.contains_key(&key) {
|
||||||
match value.try_into() {
|
match value.try_into_value() {
|
||||||
Ok(value) => head.headers.insert(key, value),
|
Ok(value) => head.headers.insert(key, value),
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
}
|
}
|
||||||
|
@ -306,7 +306,7 @@ impl RequestSender {
|
||||||
if !head.headers.contains_key(&key)
|
if !head.headers.contains_key(&key)
|
||||||
&& !extra_headers.iter().any(|h| h.contains_key(&key))
|
&& !extra_headers.iter().any(|h| h.contains_key(&key))
|
||||||
{
|
{
|
||||||
match value.try_into() {
|
match value.try_into_value() {
|
||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
let h = extra_headers.get_or_insert(HeaderMap::new());
|
let h = extra_headers.get_or_insert(HeaderMap::new());
|
||||||
h.insert(key, v)
|
h.insert(key, v)
|
||||||
|
|
|
@ -45,7 +45,7 @@ impl TestResponse {
|
||||||
|
|
||||||
/// Set a header
|
/// Set a header
|
||||||
pub fn set<H: Header>(mut self, hdr: H) -> Self {
|
pub fn set<H: Header>(mut self, hdr: H) -> Self {
|
||||||
if let Ok(value) = hdr.try_into() {
|
if let Ok(value) = hdr.try_into_value() {
|
||||||
self.head.headers.append(H::name(), value);
|
self.head.headers.append(H::name(), value);
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ impl TestResponse {
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
{
|
{
|
||||||
if let Ok(key) = HeaderName::try_from(key) {
|
if let Ok(key) = HeaderName::try_from(key) {
|
||||||
if let Ok(value) = value.try_into() {
|
if let Ok(value) = value.try_into_value() {
|
||||||
self.head.headers.append(key, value);
|
self.head.headers.append(key, value);
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,7 +170,7 @@ impl WebsocketsRequest {
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
{
|
{
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => match value.try_into() {
|
Ok(key) => match value.try_into_value() {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
self.head.headers.append(key, value);
|
self.head.headers.append(key, value);
|
||||||
}
|
}
|
||||||
|
@ -189,7 +189,7 @@ impl WebsocketsRequest {
|
||||||
V: IntoHeaderValue,
|
V: IntoHeaderValue,
|
||||||
{
|
{
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => match value.try_into() {
|
Ok(key) => match value.try_into_value() {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
self.head.headers.insert(key, value);
|
self.head.headers.insert(key, value);
|
||||||
}
|
}
|
||||||
|
@ -210,7 +210,7 @@ impl WebsocketsRequest {
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => {
|
Ok(key) => {
|
||||||
if !self.head.headers.contains_key(&key) {
|
if !self.head.headers.contains_key(&key) {
|
||||||
match value.try_into() {
|
match value.try_into_value() {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
self.head.headers.insert(key, value);
|
self.head.headers.insert(key, value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,17 +9,20 @@ use bytes::Bytes;
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use flate2::write::GzEncoder;
|
use flate2::write::GzEncoder;
|
||||||
use flate2::Compression;
|
use flate2::Compression;
|
||||||
use futures_util::future::ok;
|
use futures_util::{future::ok, stream};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
use actix_http::HttpService;
|
use actix_http::{
|
||||||
|
http::{self, StatusCode},
|
||||||
|
HttpService,
|
||||||
|
};
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
use actix_service::{map_config, pipeline_factory};
|
use actix_service::{map_config, pipeline_factory};
|
||||||
use actix_web::dev::{AppConfig, BodyEncoding};
|
|
||||||
use actix_web::http::Cookie;
|
|
||||||
use actix_web::middleware::Compress;
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
http::header, test, web, App, Error, HttpMessage, HttpRequest, HttpResponse,
|
dev::{AppConfig, BodyEncoding},
|
||||||
|
http::{header, Cookie},
|
||||||
|
middleware::Compress,
|
||||||
|
test, web, App, Error, HttpMessage, HttpRequest, HttpResponse,
|
||||||
};
|
};
|
||||||
use awc::error::SendRequestError;
|
use awc::error::SendRequestError;
|
||||||
|
|
||||||
|
@ -52,7 +55,7 @@ async fn test_simple() {
|
||||||
.service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))))
|
.service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))))
|
||||||
});
|
});
|
||||||
|
|
||||||
let request = srv.get("/").header("x-test", "111").send();
|
let request = srv.get("/").insert_header(("x-test", "111")).send();
|
||||||
let mut response = request.await.unwrap();
|
let mut response = request.await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
@ -82,7 +85,7 @@ async fn test_json() {
|
||||||
|
|
||||||
let request = srv
|
let request = srv
|
||||||
.get("/")
|
.get("/")
|
||||||
.header("x-test", "111")
|
.insert_header(("x-test", "111"))
|
||||||
.send_json(&"TEST".to_string());
|
.send_json(&"TEST".to_string());
|
||||||
let response = request.await.unwrap();
|
let response = request.await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
@ -99,7 +102,10 @@ async fn test_form() {
|
||||||
let mut data = HashMap::new();
|
let mut data = HashMap::new();
|
||||||
let _ = data.insert("key".to_string(), "TEST".to_string());
|
let _ = data.insert("key".to_string(), "TEST".to_string());
|
||||||
|
|
||||||
let request = srv.get("/").header("x-test", "111").send_form(&data);
|
let request = srv
|
||||||
|
.get("/")
|
||||||
|
.append_header(("x-test", "111"))
|
||||||
|
.send_form(&data);
|
||||||
let response = request.await.unwrap();
|
let response = request.await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
}
|
}
|
||||||
|
@ -114,9 +120,7 @@ async fn test_timeout() {
|
||||||
});
|
});
|
||||||
|
|
||||||
let connector = awc::Connector::new()
|
let connector = awc::Connector::new()
|
||||||
.connector(actix_tls::connect::new_connector(
|
.connector(actix_tls::connect::default_connector())
|
||||||
actix_tls::connect::start_default_resolver().await.unwrap(),
|
|
||||||
))
|
|
||||||
.timeout(Duration::from_secs(15))
|
.timeout(Duration::from_secs(15))
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
|
@ -438,7 +442,7 @@ async fn test_client_gzip_encoding() {
|
||||||
let data = e.finish().unwrap();
|
let data = e.finish().unwrap();
|
||||||
|
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.header("content-encoding", "gzip")
|
.insert_header(("content-encoding", "gzip"))
|
||||||
.body(data)
|
.body(data)
|
||||||
})))
|
})))
|
||||||
});
|
});
|
||||||
|
@ -461,7 +465,7 @@ async fn test_client_gzip_encoding_large() {
|
||||||
let data = e.finish().unwrap();
|
let data = e.finish().unwrap();
|
||||||
|
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.header("content-encoding", "gzip")
|
.insert_header(("content-encoding", "gzip"))
|
||||||
.body(data)
|
.body(data)
|
||||||
})))
|
})))
|
||||||
});
|
});
|
||||||
|
@ -489,7 +493,7 @@ async fn test_client_gzip_encoding_large_random() {
|
||||||
e.write_all(&data).unwrap();
|
e.write_all(&data).unwrap();
|
||||||
let data = e.finish().unwrap();
|
let data = e.finish().unwrap();
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.header("content-encoding", "gzip")
|
.insert_header(("content-encoding", "gzip"))
|
||||||
.body(data)
|
.body(data)
|
||||||
})))
|
})))
|
||||||
});
|
});
|
||||||
|
@ -511,7 +515,7 @@ async fn test_client_brotli_encoding() {
|
||||||
e.write_all(&data).unwrap();
|
e.write_all(&data).unwrap();
|
||||||
let data = e.finish().unwrap();
|
let data = e.finish().unwrap();
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.header("content-encoding", "br")
|
.insert_header(("content-encoding", "br"))
|
||||||
.body(data)
|
.body(data)
|
||||||
})))
|
})))
|
||||||
});
|
});
|
||||||
|
@ -539,7 +543,7 @@ async fn test_client_brotli_encoding_large_random() {
|
||||||
e.write_all(&data).unwrap();
|
e.write_all(&data).unwrap();
|
||||||
let data = e.finish().unwrap();
|
let data = e.finish().unwrap();
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.header("content-encoding", "br")
|
.insert_header(("content-encoding", "br"))
|
||||||
.body(data)
|
.body(data)
|
||||||
})))
|
})))
|
||||||
});
|
});
|
||||||
|
@ -554,113 +558,94 @@ async fn test_client_brotli_encoding_large_random() {
|
||||||
assert_eq!(bytes, Bytes::from(data));
|
assert_eq!(bytes, Bytes::from(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[actix_rt::test]
|
#[actix_rt::test]
|
||||||
// async fn test_client_deflate_encoding() {
|
async fn test_client_deflate_encoding() {
|
||||||
// let srv = test::TestServer::start(|app| {
|
let srv = test::start(|| {
|
||||||
// app.handler(|req: &HttpRequest| {
|
App::new().default_service(web::to(|body: Bytes| {
|
||||||
// req.body()
|
HttpResponse::Ok()
|
||||||
// .and_then(|bytes: Bytes| {
|
.encoding(http::ContentEncoding::Br)
|
||||||
// Ok(HttpResponse::Ok()
|
.body(body)
|
||||||
// .content_encoding(http::ContentEncoding::Br)
|
}))
|
||||||
// .body(bytes))
|
});
|
||||||
// })
|
|
||||||
// .responder()
|
|
||||||
// })
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // client request
|
let req = srv.post("/").send_body(STR);
|
||||||
// let request = srv
|
|
||||||
// .post()
|
|
||||||
// .content_encoding(http::ContentEncoding::Deflate)
|
|
||||||
// .body(STR)
|
|
||||||
// .unwrap();
|
|
||||||
// let response = srv.execute(request.send()).unwrap();
|
|
||||||
// assert!(response.status().is_success());
|
|
||||||
|
|
||||||
// // read response
|
let mut res = req.await.unwrap();
|
||||||
// let bytes = srv.execute(response.body()).unwrap();
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
// assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[actix_rt::test]
|
let bytes = res.body().await.unwrap();
|
||||||
// async fn test_client_deflate_encoding_large_random() {
|
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||||
// let data = rand::thread_rng()
|
}
|
||||||
// .sample_iter(&rand::distributions::Alphanumeric)
|
|
||||||
// .take(70_000)
|
|
||||||
// .collect::<String>();
|
|
||||||
|
|
||||||
// let srv = test::TestServer::start(|app| {
|
#[actix_rt::test]
|
||||||
// app.handler(|req: &HttpRequest| {
|
async fn test_client_deflate_encoding_large_random() {
|
||||||
// req.body()
|
let data = rand::thread_rng()
|
||||||
// .and_then(|bytes: Bytes| {
|
.sample_iter(rand::distributions::Alphanumeric)
|
||||||
// Ok(HttpResponse::Ok()
|
.map(char::from)
|
||||||
// .content_encoding(http::ContentEncoding::Br)
|
.take(70_000)
|
||||||
// .body(bytes))
|
.collect::<String>();
|
||||||
// })
|
|
||||||
// .responder()
|
|
||||||
// })
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // client request
|
let srv = test::start(|| {
|
||||||
// let request = srv
|
App::new().default_service(web::to(|body: Bytes| {
|
||||||
// .post()
|
HttpResponse::Ok()
|
||||||
// .content_encoding(http::ContentEncoding::Deflate)
|
.encoding(http::ContentEncoding::Br)
|
||||||
// .body(data.clone())
|
.body(body)
|
||||||
// .unwrap();
|
}))
|
||||||
// let response = srv.execute(request.send()).unwrap();
|
});
|
||||||
// assert!(response.status().is_success());
|
|
||||||
|
|
||||||
// // read response
|
let req = srv.post("/").send_body(data.clone());
|
||||||
// let bytes = srv.execute(response.body()).unwrap();
|
|
||||||
// assert_eq!(bytes, Bytes::from(data));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[actix_rt::test]
|
let mut res = req.await.unwrap();
|
||||||
// async fn test_client_streaming_explicit() {
|
let bytes = res.body().await.unwrap();
|
||||||
// let srv = test::TestServer::start(|app| {
|
|
||||||
// app.handler(|req: &HttpRequest| {
|
|
||||||
// req.body()
|
|
||||||
// .map_err(Error::from)
|
|
||||||
// .and_then(|body| {
|
|
||||||
// Ok(HttpResponse::Ok()
|
|
||||||
// .chunked()
|
|
||||||
// .content_encoding(http::ContentEncoding::Identity)
|
|
||||||
// .body(body))
|
|
||||||
// })
|
|
||||||
// .responder()
|
|
||||||
// })
|
|
||||||
// });
|
|
||||||
|
|
||||||
// let body = once(Ok(Bytes::from_static(STR.as_ref())));
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_eq!(bytes, Bytes::from(data));
|
||||||
|
}
|
||||||
|
|
||||||
// let request = srv.get("/").body(Body::Streaming(Box::new(body))).unwrap();
|
#[actix_rt::test]
|
||||||
// let response = srv.execute(request.send()).unwrap();
|
async fn test_client_streaming_explicit() {
|
||||||
// assert!(response.status().is_success());
|
let srv = test::start(|| {
|
||||||
|
App::new().default_service(web::to(|body: web::Payload| {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.encoding(http::ContentEncoding::Identity)
|
||||||
|
.streaming(body)
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
// // read response
|
let body = stream::once(async {
|
||||||
// let bytes = srv.execute(response.body()).unwrap();
|
Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes()))
|
||||||
// assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
});
|
||||||
// }
|
let req = srv.post("/").send_stream(Box::pin(body));
|
||||||
|
|
||||||
// #[actix_rt::test]
|
let mut res = req.await.unwrap();
|
||||||
// async fn test_body_streaming_implicit() {
|
assert!(res.status().is_success());
|
||||||
// let srv = test::TestServer::start(|app| {
|
|
||||||
// app.handler(|_| {
|
|
||||||
// let body = once(Ok(Bytes::from_static(STR.as_ref())));
|
|
||||||
// HttpResponse::Ok()
|
|
||||||
// .content_encoding(http::ContentEncoding::Gzip)
|
|
||||||
// .body(Body::Streaming(Box::new(body)))
|
|
||||||
// })
|
|
||||||
// });
|
|
||||||
|
|
||||||
// let request = srv.get("/").finish().unwrap();
|
let bytes = res.body().await.unwrap();
|
||||||
// let response = srv.execute(request.send()).unwrap();
|
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||||
// assert!(response.status().is_success());
|
}
|
||||||
|
|
||||||
// // read response
|
#[actix_rt::test]
|
||||||
// let bytes = srv.execute(response.body()).unwrap();
|
async fn test_body_streaming_implicit() {
|
||||||
// assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
let srv = test::start(|| {
|
||||||
// }
|
App::new().default_service(web::to(|| {
|
||||||
|
let body = stream::once(async {
|
||||||
|
Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes()))
|
||||||
|
});
|
||||||
|
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.encoding(http::ContentEncoding::Gzip)
|
||||||
|
.streaming(Box::pin(body))
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
let req = srv.get("/").send();
|
||||||
|
|
||||||
|
let mut res = req.await.unwrap();
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
|
||||||
|
let bytes = res.body().await.unwrap();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_client_cookie_handling() {
|
async fn test_client_cookie_handling() {
|
||||||
|
@ -731,35 +716,35 @@ async fn test_client_cookie_handling() {
|
||||||
assert_eq!(c2, cookie2);
|
assert_eq!(c2, cookie2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[actix_rt::test]
|
#[actix_rt::test]
|
||||||
// fn client_read_until_eof() {
|
async fn client_unread_response() {
|
||||||
// let addr = test::TestServer::unused_addr();
|
let addr = test::unused_addr();
|
||||||
|
|
||||||
// thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
// let lst = net::TcpListener::bind(addr).unwrap();
|
let lst = std::net::TcpListener::bind(addr).unwrap();
|
||||||
|
|
||||||
// for stream in lst.incoming() {
|
for stream in lst.incoming() {
|
||||||
// let mut stream = stream.unwrap();
|
let mut stream = stream.unwrap();
|
||||||
// let mut b = [0; 1000];
|
let mut b = [0; 1000];
|
||||||
// let _ = stream.read(&mut b).unwrap();
|
let _ = stream.read(&mut b).unwrap();
|
||||||
// let _ = stream
|
let _ = stream.write_all(
|
||||||
// .write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!");
|
b"HTTP/1.1 200 OK\r\n\
|
||||||
// }
|
connection: close\r\n\
|
||||||
// });
|
\r\n\
|
||||||
|
welcome!",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// let mut sys = actix::System::new("test");
|
// client request
|
||||||
|
let req = awc::Client::new().get(format!("http://{}/", addr).as_str());
|
||||||
|
let mut res = req.send().await.unwrap();
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
|
||||||
// // client request
|
// awc does not read all bytes unless content-length is specified
|
||||||
// let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
|
let bytes = res.body().await.unwrap();
|
||||||
// .finish()
|
assert_eq!(bytes, Bytes::from_static(b""));
|
||||||
// .unwrap();
|
}
|
||||||
// let response = req.send().await.unwrap();
|
|
||||||
// assert!(response.status().is_success());
|
|
||||||
|
|
||||||
// // read response
|
|
||||||
// let bytes = response.body().await.unwrap();
|
|
||||||
// assert_eq!(bytes, Bytes::from_static(b"welcome!"));
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn client_basic_auth() {
|
async fn client_basic_auth() {
|
||||||
|
|
|
@ -1,57 +1,57 @@
|
||||||
#![cfg(feature = "rustls")]
|
#![cfg(feature = "rustls")]
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
||||||
use std::sync::Arc;
|
extern crate rust_tls as rustls;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
io::BufReader,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_http::HttpService;
|
use actix_http::HttpService;
|
||||||
use actix_http_test::test_server;
|
use actix_http_test::test_server;
|
||||||
use actix_service::{map_config, pipeline_factory, ServiceFactoryExt};
|
use actix_service::{map_config, pipeline_factory, ServiceFactoryExt};
|
||||||
use actix_web::http::Version;
|
use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse};
|
||||||
use actix_web::{dev::AppConfig, web, App, HttpResponse};
|
|
||||||
use futures_util::future::ok;
|
use futures_util::future::ok;
|
||||||
use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslVerifyMode};
|
use rustls::internal::pemfile::{certs, pkcs8_private_keys};
|
||||||
use rust_tls::ClientConfig;
|
use rustls::{ClientConfig, NoClientAuth, ServerConfig};
|
||||||
|
|
||||||
#[allow(unused)]
|
fn tls_config() -> ServerConfig {
|
||||||
fn ssl_acceptor() -> SslAcceptor {
|
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||||
// load ssl keys
|
let cert_file = cert.serialize_pem().unwrap();
|
||||||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
let key_file = cert.serialize_private_key_pem();
|
||||||
builder.set_verify_callback(SslVerifyMode::NONE, |_, _| true);
|
|
||||||
builder
|
let mut config = ServerConfig::new(NoClientAuth::new());
|
||||||
.set_private_key_file("../tests/key.pem", SslFiletype::PEM)
|
let cert_file = &mut BufReader::new(cert_file.as_bytes());
|
||||||
.unwrap();
|
let key_file = &mut BufReader::new(key_file.as_bytes());
|
||||||
builder
|
|
||||||
.set_certificate_chain_file("../tests/cert.pem")
|
let cert_chain = certs(cert_file).unwrap();
|
||||||
.unwrap();
|
let mut keys = pkcs8_private_keys(key_file).unwrap();
|
||||||
builder.set_alpn_select_callback(|_, protos| {
|
config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
|
||||||
const H2: &[u8] = b"\x02h2";
|
|
||||||
if protos.windows(3).any(|window| window == H2) {
|
config
|
||||||
Ok(b"h2")
|
|
||||||
} else {
|
|
||||||
Err(open_ssl::ssl::AlpnError::NOACK)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.set_alpn_protos(b"\x02h2").unwrap();
|
|
||||||
builder.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod danger {
|
mod danger {
|
||||||
pub struct NoCertificateVerification {}
|
pub struct NoCertificateVerification;
|
||||||
|
|
||||||
impl rust_tls::ServerCertVerifier for NoCertificateVerification {
|
impl rustls::ServerCertVerifier for NoCertificateVerification {
|
||||||
fn verify_server_cert(
|
fn verify_server_cert(
|
||||||
&self,
|
&self,
|
||||||
_roots: &rust_tls::RootCertStore,
|
_roots: &rustls::RootCertStore,
|
||||||
_presented_certs: &[rust_tls::Certificate],
|
_presented_certs: &[rustls::Certificate],
|
||||||
_dns_name: webpki::DNSNameRef<'_>,
|
_dns_name: webpki::DNSNameRef<'_>,
|
||||||
_ocsp: &[u8],
|
_ocsp: &[u8],
|
||||||
) -> Result<rust_tls::ServerCertVerified, rust_tls::TLSError> {
|
) -> Result<rustls::ServerCertVerified, rustls::TLSError> {
|
||||||
Ok(rust_tls::ServerCertVerified::assertion())
|
Ok(rustls::ServerCertVerified::assertion())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn _test_connection_reuse_h2() {
|
async fn test_connection_reuse_h2() {
|
||||||
let num = Arc::new(AtomicUsize::new(0));
|
let num = Arc::new(AtomicUsize::new(0));
|
||||||
let num2 = num.clone();
|
let num2 = num.clone();
|
||||||
|
|
||||||
|
@ -68,19 +68,19 @@ async fn _test_connection_reuse_h2() {
|
||||||
.service(web::resource("/").route(web::to(HttpResponse::Ok))),
|
.service(web::resource("/").route(web::to(HttpResponse::Ok))),
|
||||||
|_| AppConfig::default(),
|
|_| AppConfig::default(),
|
||||||
))
|
))
|
||||||
.openssl(ssl_acceptor())
|
.rustls(tls_config())
|
||||||
.map_err(|_| ()),
|
.map_err(|_| ()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// disable ssl verification
|
// disable TLS verification
|
||||||
let mut config = ClientConfig::new();
|
let mut config = ClientConfig::new();
|
||||||
let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||||
config.set_protocols(&protos);
|
config.set_protocols(&protos);
|
||||||
config
|
config
|
||||||
.dangerous()
|
.dangerous()
|
||||||
.set_certificate_verifier(Arc::new(danger::NoCertificateVerification {}));
|
.set_certificate_verifier(Arc::new(danger::NoCertificateVerification));
|
||||||
|
|
||||||
let client = awc::Client::builder()
|
let client = awc::Client::builder()
|
||||||
.connector(awc::Connector::new().rustls(Arc::new(config)).finish())
|
.connector(awc::Connector::new().rustls(Arc::new(config)).finish())
|
||||||
|
|
|
@ -10,7 +10,7 @@ async fn main() -> Result<(), Error> {
|
||||||
// Create request builder, configure request and send
|
// Create request builder, configure request and send
|
||||||
let mut response = client
|
let mut response = client
|
||||||
.get("https://www.rust-lang.org/")
|
.get("https://www.rust-lang.org/")
|
||||||
.header("User-Agent", "Actix-web")
|
.append_header(("User-Agent", "Actix-web"))
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
25
src/app.rs
25
src/app.rs
|
@ -34,7 +34,6 @@ pub struct App<T, B> {
|
||||||
services: Vec<Box<dyn AppServiceFactory>>,
|
services: Vec<Box<dyn AppServiceFactory>>,
|
||||||
default: Option<Rc<HttpNewService>>,
|
default: Option<Rc<HttpNewService>>,
|
||||||
factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||||
data: Vec<Box<dyn DataFactory>>,
|
|
||||||
data_factories: Vec<FnDataFactory>,
|
data_factories: Vec<FnDataFactory>,
|
||||||
external: Vec<ResourceDef>,
|
external: Vec<ResourceDef>,
|
||||||
extensions: Extensions,
|
extensions: Extensions,
|
||||||
|
@ -48,7 +47,6 @@ impl App<AppEntry, Body> {
|
||||||
let fref = Rc::new(RefCell::new(None));
|
let fref = Rc::new(RefCell::new(None));
|
||||||
App {
|
App {
|
||||||
endpoint: AppEntry::new(fref.clone()),
|
endpoint: AppEntry::new(fref.clone()),
|
||||||
data: Vec::new(),
|
|
||||||
data_factories: Vec::new(),
|
data_factories: Vec::new(),
|
||||||
services: Vec::new(),
|
services: Vec::new(),
|
||||||
default: None,
|
default: None,
|
||||||
|
@ -101,9 +99,8 @@ where
|
||||||
/// web::resource("/index.html").route(
|
/// web::resource("/index.html").route(
|
||||||
/// web::get().to(index)));
|
/// web::get().to(index)));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn data<U: 'static>(mut self, data: U) -> Self {
|
pub fn data<U: 'static>(self, data: U) -> Self {
|
||||||
self.data.push(Box::new(Data::new(data)));
|
self.app_data(Data::new(data))
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set application data factory. This function is
|
/// Set application data factory. This function is
|
||||||
|
@ -157,8 +154,7 @@ where
|
||||||
/// some of the resource's configuration could be moved to different module.
|
/// some of the resource's configuration could be moved to different module.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// use actix_web::{web, App, HttpResponse};
|
||||||
/// use actix_web::{web, middleware, App, HttpResponse};
|
|
||||||
///
|
///
|
||||||
/// // this function could be located in different module
|
/// // this function could be located in different module
|
||||||
/// fn config(cfg: &mut web::ServiceConfig) {
|
/// fn config(cfg: &mut web::ServiceConfig) {
|
||||||
|
@ -168,12 +164,9 @@ where
|
||||||
/// );
|
/// );
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// App::new()
|
||||||
/// let app = App::new()
|
/// .configure(config) // <- register resources
|
||||||
/// .wrap(middleware::Logger::default())
|
/// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
|
||||||
/// .configure(config) // <- register resources
|
|
||||||
/// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
|
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn configure<F>(mut self, f: F) -> Self
|
pub fn configure<F>(mut self, f: F) -> Self
|
||||||
where
|
where
|
||||||
|
@ -181,10 +174,9 @@ where
|
||||||
{
|
{
|
||||||
let mut cfg = ServiceConfig::new();
|
let mut cfg = ServiceConfig::new();
|
||||||
f(&mut cfg);
|
f(&mut cfg);
|
||||||
self.data.extend(cfg.data);
|
|
||||||
self.services.extend(cfg.services);
|
self.services.extend(cfg.services);
|
||||||
self.external.extend(cfg.external);
|
self.external.extend(cfg.external);
|
||||||
self.extensions.extend(cfg.extensions);
|
self.extensions.extend(cfg.app_data);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,7 +366,6 @@ where
|
||||||
{
|
{
|
||||||
App {
|
App {
|
||||||
endpoint: apply(mw, self.endpoint),
|
endpoint: apply(mw, self.endpoint),
|
||||||
data: self.data,
|
|
||||||
data_factories: self.data_factories,
|
data_factories: self.data_factories,
|
||||||
services: self.services,
|
services: self.services,
|
||||||
default: self.default,
|
default: self.default,
|
||||||
|
@ -436,7 +427,6 @@ where
|
||||||
{
|
{
|
||||||
App {
|
App {
|
||||||
endpoint: apply_fn_factory(self.endpoint, mw),
|
endpoint: apply_fn_factory(self.endpoint, mw),
|
||||||
data: self.data,
|
|
||||||
data_factories: self.data_factories,
|
data_factories: self.data_factories,
|
||||||
services: self.services,
|
services: self.services,
|
||||||
default: self.default,
|
default: self.default,
|
||||||
|
@ -462,7 +452,6 @@ where
|
||||||
{
|
{
|
||||||
fn into_factory(self) -> AppInit<T, B> {
|
fn into_factory(self) -> AppInit<T, B> {
|
||||||
AppInit {
|
AppInit {
|
||||||
data_factories: self.data.into_boxed_slice().into(),
|
|
||||||
async_data_factories: self.data_factories.into_boxed_slice().into(),
|
async_data_factories: self.data_factories.into_boxed_slice().into(),
|
||||||
endpoint: self.endpoint,
|
endpoint: self.endpoint,
|
||||||
services: Rc::new(RefCell::new(self.services)),
|
services: Rc::new(RefCell::new(self.services)),
|
||||||
|
|
|
@ -10,7 +10,7 @@ use futures_core::future::LocalBoxFuture;
|
||||||
use futures_util::future::join_all;
|
use futures_util::future::join_all;
|
||||||
|
|
||||||
use crate::config::{AppConfig, AppService};
|
use crate::config::{AppConfig, AppService};
|
||||||
use crate::data::{DataFactory, FnDataFactory};
|
use crate::data::FnDataFactory;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::guard::Guard;
|
use crate::guard::Guard;
|
||||||
use crate::request::{HttpRequest, HttpRequestPool};
|
use crate::request::{HttpRequest, HttpRequestPool};
|
||||||
|
@ -35,7 +35,6 @@ where
|
||||||
{
|
{
|
||||||
pub(crate) endpoint: T,
|
pub(crate) endpoint: T,
|
||||||
pub(crate) extensions: RefCell<Option<Extensions>>,
|
pub(crate) extensions: RefCell<Option<Extensions>>,
|
||||||
pub(crate) data_factories: Rc<[Box<dyn DataFactory>]>,
|
|
||||||
pub(crate) async_data_factories: Rc<[FnDataFactory]>,
|
pub(crate) async_data_factories: Rc<[FnDataFactory]>,
|
||||||
pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>,
|
pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>,
|
||||||
pub(crate) default: Option<Rc<HttpNewService>>,
|
pub(crate) default: Option<Rc<HttpNewService>>,
|
||||||
|
@ -71,8 +70,7 @@ where
|
||||||
});
|
});
|
||||||
|
|
||||||
// App config
|
// App config
|
||||||
let mut config =
|
let mut config = AppService::new(config, default.clone());
|
||||||
AppService::new(config, default.clone(), self.data_factories.clone());
|
|
||||||
|
|
||||||
// register services
|
// register services
|
||||||
std::mem::take(&mut *self.services.borrow_mut())
|
std::mem::take(&mut *self.services.borrow_mut())
|
||||||
|
@ -119,8 +117,6 @@ where
|
||||||
.take()
|
.take()
|
||||||
.unwrap_or_else(Extensions::new);
|
.unwrap_or_else(Extensions::new);
|
||||||
|
|
||||||
let data_factories = self.data_factories.clone();
|
|
||||||
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
// async data factories
|
// async data factories
|
||||||
let async_data_factories = factory_futs
|
let async_data_factories = factory_futs
|
||||||
|
@ -133,12 +129,9 @@ where
|
||||||
let service = endpoint_fut.await?;
|
let service = endpoint_fut.await?;
|
||||||
|
|
||||||
// populate app data container from (async) data factories.
|
// populate app data container from (async) data factories.
|
||||||
data_factories
|
async_data_factories.iter().for_each(|factory| {
|
||||||
.iter()
|
factory.create(&mut app_data);
|
||||||
.chain(&async_data_factories)
|
});
|
||||||
.for_each(|factory| {
|
|
||||||
factory.create(&mut app_data);
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(AppInitService {
|
Ok(AppInitService {
|
||||||
service,
|
service,
|
||||||
|
@ -149,7 +142,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Service to convert `Request` to a `ServiceRequest<S>`
|
/// Service that takes a [`Request`] and delegates to a service that take a [`ServiceRequest`].
|
||||||
pub struct AppInitService<T, B>
|
pub struct AppInitService<T, B>
|
||||||
where
|
where
|
||||||
T: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
T: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
@ -159,7 +152,7 @@ where
|
||||||
app_state: Rc<AppInitServiceState>,
|
app_state: Rc<AppInitServiceState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// a collection of AppInitService state that shared between HttpRequests.
|
/// A collection of [`AppInitService`] state that shared across `HttpRequest`s.
|
||||||
pub(crate) struct AppInitServiceState {
|
pub(crate) struct AppInitServiceState {
|
||||||
rmap: Rc<ResourceMap>,
|
rmap: Rc<ResourceMap>,
|
||||||
config: AppConfig,
|
config: AppConfig,
|
||||||
|
|
|
@ -5,7 +5,7 @@ use actix_http::Extensions;
|
||||||
use actix_router::ResourceDef;
|
use actix_router::ResourceDef;
|
||||||
use actix_service::{boxed, IntoServiceFactory, ServiceFactory};
|
use actix_service::{boxed, IntoServiceFactory, ServiceFactory};
|
||||||
|
|
||||||
use crate::data::{Data, DataFactory};
|
use crate::data::Data;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::guard::Guard;
|
use crate::guard::Guard;
|
||||||
use crate::resource::Resource;
|
use crate::resource::Resource;
|
||||||
|
@ -31,20 +31,14 @@ pub struct AppService {
|
||||||
Option<Guards>,
|
Option<Guards>,
|
||||||
Option<Rc<ResourceMap>>,
|
Option<Rc<ResourceMap>>,
|
||||||
)>,
|
)>,
|
||||||
service_data: Rc<[Box<dyn DataFactory>]>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppService {
|
impl AppService {
|
||||||
/// Crate server settings instance
|
/// Crate server settings instance.
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(config: AppConfig, default: Rc<HttpNewService>) -> Self {
|
||||||
config: AppConfig,
|
|
||||||
default: Rc<HttpNewService>,
|
|
||||||
service_data: Rc<[Box<dyn DataFactory>]>,
|
|
||||||
) -> Self {
|
|
||||||
AppService {
|
AppService {
|
||||||
config,
|
config,
|
||||||
default,
|
default,
|
||||||
service_data,
|
|
||||||
root: true,
|
root: true,
|
||||||
services: Vec::new(),
|
services: Vec::new(),
|
||||||
}
|
}
|
||||||
|
@ -75,7 +69,6 @@ impl AppService {
|
||||||
default: self.default.clone(),
|
default: self.default.clone(),
|
||||||
services: Vec::new(),
|
services: Vec::new(),
|
||||||
root: false,
|
root: false,
|
||||||
service_data: self.service_data.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,15 +82,7 @@ impl AppService {
|
||||||
self.default.clone()
|
self.default.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set global route data
|
/// Register HTTP service.
|
||||||
pub fn set_service_data(&self, extensions: &mut Extensions) -> bool {
|
|
||||||
for f in self.service_data.iter() {
|
|
||||||
f.create(extensions);
|
|
||||||
}
|
|
||||||
!self.service_data.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register http service
|
|
||||||
pub fn register_service<F, S>(
|
pub fn register_service<F, S>(
|
||||||
&mut self,
|
&mut self,
|
||||||
rdef: ResourceDef,
|
rdef: ResourceDef,
|
||||||
|
@ -168,47 +153,60 @@ impl Default for AppConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Service config is used for external configuration.
|
/// Enables parts of app configuration to be declared separately from the app itself. Helpful for
|
||||||
/// Part of application configuration could be offloaded
|
/// modularizing large applications.
|
||||||
/// to set of external methods. This could help with
|
///
|
||||||
/// modularization of big application configuration.
|
/// Merge a `ServiceConfig` into an app using [`App::configure`](crate::App::configure). Scope and
|
||||||
|
/// resources services have similar methods.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{web, App, HttpResponse};
|
||||||
|
///
|
||||||
|
/// // this function could be located in different module
|
||||||
|
/// fn config(cfg: &mut web::ServiceConfig) {
|
||||||
|
/// cfg.service(web::resource("/test")
|
||||||
|
/// .route(web::get().to(|| HttpResponse::Ok()))
|
||||||
|
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // merge `/test` routes from config function to App
|
||||||
|
/// App::new().configure(config);
|
||||||
|
/// ```
|
||||||
pub struct ServiceConfig {
|
pub struct ServiceConfig {
|
||||||
pub(crate) services: Vec<Box<dyn AppServiceFactory>>,
|
pub(crate) services: Vec<Box<dyn AppServiceFactory>>,
|
||||||
pub(crate) data: Vec<Box<dyn DataFactory>>,
|
|
||||||
pub(crate) external: Vec<ResourceDef>,
|
pub(crate) external: Vec<ResourceDef>,
|
||||||
pub(crate) extensions: Extensions,
|
pub(crate) app_data: Extensions,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceConfig {
|
impl ServiceConfig {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
services: Vec::new(),
|
services: Vec::new(),
|
||||||
data: Vec::new(),
|
|
||||||
external: Vec::new(),
|
external: Vec::new(),
|
||||||
extensions: Extensions::new(),
|
app_data: Extensions::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set application data. Application data could be accessed
|
/// Add shared app data item.
|
||||||
/// by using `Data<T>` extractor where `T` is data type.
|
|
||||||
///
|
///
|
||||||
/// This is same as `App::data()` method.
|
/// Counterpart to [`App::data()`](crate::App::data).
|
||||||
pub fn data<S: 'static>(&mut self, data: S) -> &mut Self {
|
pub fn data<U: 'static>(&mut self, data: U) -> &mut Self {
|
||||||
self.data.push(Box::new(Data::new(data)));
|
self.app_data(Data::new(data));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set arbitrary data item.
|
/// Add arbitrary app data item.
|
||||||
///
|
///
|
||||||
/// This is same as `App::data()` method.
|
/// Counterpart to [`App::app_data()`](crate::App::app_data).
|
||||||
pub fn app_data<U: 'static>(&mut self, ext: U) -> &mut Self {
|
pub fn app_data<U: 'static>(&mut self, ext: U) -> &mut Self {
|
||||||
self.extensions.insert(ext);
|
self.app_data.insert(ext);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure route for a specific path.
|
/// Configure route for a specific path.
|
||||||
///
|
///
|
||||||
/// This is same as `App::route()` method.
|
/// Counterpart to [`App::route()`](crate::App::route).
|
||||||
pub fn route(&mut self, path: &str, mut route: Route) -> &mut Self {
|
pub fn route(&mut self, path: &str, mut route: Route) -> &mut Self {
|
||||||
self.service(
|
self.service(
|
||||||
Resource::new(path)
|
Resource::new(path)
|
||||||
|
@ -217,9 +215,9 @@ impl ServiceConfig {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register http service.
|
/// Register HTTP service factory.
|
||||||
///
|
///
|
||||||
/// This is same as `App::service()` method.
|
/// Counterpart to [`App::service()`](crate::App::service).
|
||||||
pub fn service<F>(&mut self, factory: F) -> &mut Self
|
pub fn service<F>(&mut self, factory: F) -> &mut Self
|
||||||
where
|
where
|
||||||
F: HttpServiceFactory + 'static,
|
F: HttpServiceFactory + 'static,
|
||||||
|
@ -231,11 +229,11 @@ impl ServiceConfig {
|
||||||
|
|
||||||
/// Register an external resource.
|
/// Register an external resource.
|
||||||
///
|
///
|
||||||
/// External resources are useful for URL generation purposes only
|
/// External resources are useful for URL generation purposes only and are never considered for
|
||||||
/// and are never considered for matching at request time. Calls to
|
/// matching at request time. Calls to [`HttpRequest::url_for()`](crate::HttpRequest::url_for)
|
||||||
/// `HttpRequest::url_for()` will work as expected.
|
/// will work as expected.
|
||||||
///
|
///
|
||||||
/// This is same as `App::external_service()` method.
|
/// Counterpart to [`App::external_resource()`](crate::App::external_resource).
|
||||||
pub fn external_resource<N, U>(&mut self, name: N, url: U) -> &mut Self
|
pub fn external_resource<N, U>(&mut self, name: N, url: U) -> &mut Self
|
||||||
where
|
where
|
||||||
N: AsRef<str>,
|
N: AsRef<str>,
|
||||||
|
|
29
src/data.rs
29
src/data.rs
|
@ -10,8 +10,9 @@ use crate::dev::Payload;
|
||||||
use crate::extract::FromRequest;
|
use crate::extract::FromRequest;
|
||||||
use crate::request::HttpRequest;
|
use crate::request::HttpRequest;
|
||||||
|
|
||||||
/// Application data factory
|
/// Data factory.
|
||||||
pub(crate) trait DataFactory {
|
pub(crate) trait DataFactory {
|
||||||
|
/// Return true if modifications were made to extensions map.
|
||||||
fn create(&self, extensions: &mut Extensions) -> bool;
|
fn create(&self, extensions: &mut Extensions) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,12 +127,8 @@ impl<T: ?Sized + 'static> FromRequest for Data<T> {
|
||||||
|
|
||||||
impl<T: ?Sized + 'static> DataFactory for Data<T> {
|
impl<T: ?Sized + 'static> DataFactory for Data<T> {
|
||||||
fn create(&self, extensions: &mut Extensions) -> bool {
|
fn create(&self, extensions: &mut Extensions) -> bool {
|
||||||
if !extensions.contains::<Data<T>>() {
|
extensions.insert(Data(self.0.clone()));
|
||||||
extensions.insert(Data(self.0.clone()));
|
true
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,6 +164,24 @@ mod tests {
|
||||||
let req = TestRequest::default().to_request();
|
let req = TestRequest::default().to_request();
|
||||||
let resp = srv.call(req).await.unwrap();
|
let resp = srv.call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
|
||||||
|
let srv = init_service(
|
||||||
|
App::new()
|
||||||
|
.data(10u32)
|
||||||
|
.data(13u32)
|
||||||
|
.app_data(12u64)
|
||||||
|
.app_data(15u64)
|
||||||
|
.default_service(web::to(|n: web::Data<u32>, req: HttpRequest| {
|
||||||
|
// in each case, the latter insertion should be preserved
|
||||||
|
assert_eq!(*req.app_data::<u64>().unwrap(), 15);
|
||||||
|
assert_eq!(*n.into_inner(), 13);
|
||||||
|
HttpResponse::Ok()
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let req = TestRequest::default().to_request();
|
||||||
|
let resp = srv.call(req).await.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
|
|
@ -347,25 +347,21 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_option() {
|
async fn test_option() {
|
||||||
let (req, mut pl) = TestRequest::with_header(
|
let (req, mut pl) = TestRequest::default()
|
||||||
header::CONTENT_TYPE,
|
.insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
|
||||||
"application/x-www-form-urlencoded",
|
.data(FormConfig::default().limit(4096))
|
||||||
)
|
.to_http_parts();
|
||||||
.data(FormConfig::default().limit(4096))
|
|
||||||
.to_http_parts();
|
|
||||||
|
|
||||||
let r = Option::<Form<Info>>::from_request(&req, &mut pl)
|
let r = Option::<Form<Info>>::from_request(&req, &mut pl)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(r, None);
|
assert_eq!(r, None);
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::with_header(
|
let (req, mut pl) = TestRequest::default()
|
||||||
header::CONTENT_TYPE,
|
.insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
|
||||||
"application/x-www-form-urlencoded",
|
.insert_header((header::CONTENT_LENGTH, "9"))
|
||||||
)
|
.set_payload(Bytes::from_static(b"hello=world"))
|
||||||
.header(header::CONTENT_LENGTH, "9")
|
.to_http_parts();
|
||||||
.set_payload(Bytes::from_static(b"hello=world"))
|
|
||||||
.to_http_parts();
|
|
||||||
|
|
||||||
let r = Option::<Form<Info>>::from_request(&req, &mut pl)
|
let r = Option::<Form<Info>>::from_request(&req, &mut pl)
|
||||||
.await
|
.await
|
||||||
|
@ -377,13 +373,11 @@ mod tests {
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::with_header(
|
let (req, mut pl) = TestRequest::default()
|
||||||
header::CONTENT_TYPE,
|
.insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
|
||||||
"application/x-www-form-urlencoded",
|
.insert_header((header::CONTENT_LENGTH, "9"))
|
||||||
)
|
.set_payload(Bytes::from_static(b"bye=world"))
|
||||||
.header(header::CONTENT_LENGTH, "9")
|
.to_http_parts();
|
||||||
.set_payload(Bytes::from_static(b"bye=world"))
|
|
||||||
.to_http_parts();
|
|
||||||
|
|
||||||
let r = Option::<Form<Info>>::from_request(&req, &mut pl)
|
let r = Option::<Form<Info>>::from_request(&req, &mut pl)
|
||||||
.await
|
.await
|
||||||
|
@ -393,13 +387,11 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_result() {
|
async fn test_result() {
|
||||||
let (req, mut pl) = TestRequest::with_header(
|
let (req, mut pl) = TestRequest::default()
|
||||||
header::CONTENT_TYPE,
|
.insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
|
||||||
"application/x-www-form-urlencoded",
|
.insert_header((header::CONTENT_LENGTH, "11"))
|
||||||
)
|
.set_payload(Bytes::from_static(b"hello=world"))
|
||||||
.header(header::CONTENT_LENGTH, "11")
|
.to_http_parts();
|
||||||
.set_payload(Bytes::from_static(b"hello=world"))
|
|
||||||
.to_http_parts();
|
|
||||||
|
|
||||||
let r = Result::<Form<Info>, Error>::from_request(&req, &mut pl)
|
let r = Result::<Form<Info>, Error>::from_request(&req, &mut pl)
|
||||||
.await
|
.await
|
||||||
|
@ -412,13 +404,11 @@ mod tests {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::with_header(
|
let (req, mut pl) = TestRequest::default()
|
||||||
header::CONTENT_TYPE,
|
.insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
|
||||||
"application/x-www-form-urlencoded",
|
.insert_header((header::CONTENT_LENGTH, 9))
|
||||||
)
|
.set_payload(Bytes::from_static(b"bye=world"))
|
||||||
.header(header::CONTENT_LENGTH, "9")
|
.to_http_parts();
|
||||||
.set_payload(Bytes::from_static(b"bye=world"))
|
|
||||||
.to_http_parts();
|
|
||||||
|
|
||||||
let r = Result::<Form<Info>, Error>::from_request(&req, &mut pl)
|
let r = Result::<Form<Info>, Error>::from_request(&req, &mut pl)
|
||||||
.await
|
.await
|
||||||
|
|
11
src/guard.rs
11
src/guard.rs
|
@ -330,7 +330,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_header() {
|
fn test_header() {
|
||||||
let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked")
|
let req = TestRequest::default()
|
||||||
|
.insert_header((header::TRANSFER_ENCODING, "chunked"))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
|
|
||||||
let pred = Header("transfer-encoding", "chunked");
|
let pred = Header("transfer-encoding", "chunked");
|
||||||
|
@ -346,10 +347,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_host() {
|
fn test_host() {
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::HOST,
|
header::HOST,
|
||||||
header::HeaderValue::from_static("www.rust-lang.org"),
|
header::HeaderValue::from_static("www.rust-lang.org"),
|
||||||
)
|
))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
|
|
||||||
let pred = Host("www.rust-lang.org");
|
let pred = Host("www.rust-lang.org");
|
||||||
|
@ -374,10 +375,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_host_scheme() {
|
fn test_host_scheme() {
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::HOST,
|
header::HOST,
|
||||||
header::HeaderValue::from_static("https://www.rust-lang.org"),
|
header::HeaderValue::from_static("https://www.rust-lang.org"),
|
||||||
)
|
))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
|
|
||||||
let pred = Host("www.rust-lang.org").scheme("https");
|
let pred = Host("www.rust-lang.org").scheme("https");
|
||||||
|
|
|
@ -101,7 +101,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlerService is both it's ServiceFactory and Service Type.
|
/// HandlerService is both it's ServiceFactory and Service Type.
|
||||||
impl<F, T, R> Service<ServiceRequest> for HandlerService<F, T, R>
|
impl<F, T, R> Service<ServiceRequest> for HandlerService<F, T, R>
|
||||||
where
|
where
|
||||||
F: Handler<T, R>,
|
F: Handler<T, R>,
|
||||||
|
|
12
src/info.rs
12
src/info.rs
|
@ -200,10 +200,10 @@ mod tests {
|
||||||
assert_eq!(info.host(), "localhost:8080");
|
assert_eq!(info.host(), "localhost:8080");
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::FORWARDED,
|
header::FORWARDED,
|
||||||
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
|
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
|
||||||
)
|
))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
|
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
|
@ -212,7 +212,7 @@ mod tests {
|
||||||
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
|
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(header::HOST, "rust-lang.org")
|
.insert_header((header::HOST, "rust-lang.org"))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
|
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
|
@ -221,20 +221,20 @@ mod tests {
|
||||||
assert_eq!(info.realip_remote_addr(), None);
|
assert_eq!(info.realip_remote_addr(), None);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(X_FORWARDED_FOR, "192.0.2.60")
|
.insert_header((X_FORWARDED_FOR, "192.0.2.60"))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
|
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(X_FORWARDED_HOST, "192.0.2.60")
|
.insert_header((X_FORWARDED_HOST, "192.0.2.60"))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
assert_eq!(info.host(), "192.0.2.60");
|
assert_eq!(info.host(), "192.0.2.60");
|
||||||
assert_eq!(info.realip_remote_addr(), None);
|
assert_eq!(info.realip_remote_addr(), None);
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(X_FORWARDED_PROTO, "https")
|
.insert_header((X_FORWARDED_PROTO, "https"))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
assert_eq!(info.scheme(), "https");
|
assert_eq!(info.scheme(), "https");
|
||||||
|
|
|
@ -211,7 +211,7 @@ pub mod client {
|
||||||
//!
|
//!
|
||||||
//! // Create request builder and send request
|
//! // Create request builder and send request
|
||||||
//! let response = client.get("http://www.rust-lang.org")
|
//! let response = client.get("http://www.rust-lang.org")
|
||||||
//! .header("User-Agent", "actix-web/3.0")
|
//! .insert_header(("User-Agent", "actix-web/3.0"))
|
||||||
//! .send() // <- Send request
|
//! .send() // <- Send request
|
||||||
//! .await; // <- Wait for response
|
//! .await; // <- Wait for response
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -110,8 +110,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// trait for convert ServiceResponse's ResponseBody<B> generic type
|
/// Convert `ServiceResponse`'s `ResponseBody<B>` generic type to `ResponseBody<Body>`.
|
||||||
// to ResponseBody<Body>
|
|
||||||
pub trait MapServiceResponseBody {
|
pub trait MapServiceResponseBody {
|
||||||
fn map_body(self) -> ServiceResponse;
|
fn map_body(self) -> ServiceResponse;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ use actix_http::{
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
use actix_service::{Service, Transform};
|
use actix_service::{Service, Transform};
|
||||||
|
use futures_core::ready;
|
||||||
use futures_util::future::{ok, Ready};
|
use futures_util::future::{ok, Ready};
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
|
@ -131,7 +132,7 @@ where
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
let this = self.project();
|
let this = self.project();
|
||||||
|
|
||||||
match futures_util::ready!(this.fut.poll(cx)) {
|
match ready!(this.fut.poll(cx)) {
|
||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
let enc = if let Some(enc) = resp.response().get_encoding() {
|
let enc = if let Some(enc) = resp.response().get_encoding() {
|
||||||
enc
|
enc
|
||||||
|
|
|
@ -212,8 +212,11 @@ mod tests {
|
||||||
|
|
||||||
let req = TestRequest::default().to_srv_request();
|
let req = TestRequest::default().to_srv_request();
|
||||||
let srv = |req: ServiceRequest| {
|
let srv = |req: ServiceRequest| {
|
||||||
ok(req
|
ok(req.into_response(
|
||||||
.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish()))
|
HttpResponse::Ok()
|
||||||
|
.insert_header((CONTENT_TYPE, "0002"))
|
||||||
|
.finish(),
|
||||||
|
))
|
||||||
};
|
};
|
||||||
let mw = DefaultHeaders::new()
|
let mw = DefaultHeaders::new()
|
||||||
.header(CONTENT_TYPE, "0001")
|
.header(CONTENT_TYPE, "0001")
|
||||||
|
|
|
@ -603,7 +603,7 @@ mod tests {
|
||||||
let srv = |req: ServiceRequest| {
|
let srv = |req: ServiceRequest| {
|
||||||
ok(req.into_response(
|
ok(req.into_response(
|
||||||
HttpResponse::build(StatusCode::OK)
|
HttpResponse::build(StatusCode::OK)
|
||||||
.header("X-Test", "ttt")
|
.insert_header(("X-Test", "ttt"))
|
||||||
.finish(),
|
.finish(),
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
@ -611,11 +611,12 @@ mod tests {
|
||||||
|
|
||||||
let srv = logger.new_transform(srv.into_service()).await.unwrap();
|
let srv = logger.new_transform(srv.into_service()).await.unwrap();
|
||||||
|
|
||||||
let req = TestRequest::with_header(
|
let req = TestRequest::default()
|
||||||
header::USER_AGENT,
|
.insert_header((
|
||||||
header::HeaderValue::from_static("ACTIX-WEB"),
|
header::USER_AGENT,
|
||||||
)
|
header::HeaderValue::from_static("ACTIX-WEB"),
|
||||||
.to_srv_request();
|
))
|
||||||
|
.to_srv_request();
|
||||||
let _res = srv.call(req).await;
|
let _res = srv.call(req).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -624,7 +625,7 @@ mod tests {
|
||||||
let srv = |req: ServiceRequest| {
|
let srv = |req: ServiceRequest| {
|
||||||
ok(req.into_response(
|
ok(req.into_response(
|
||||||
HttpResponse::build(StatusCode::OK)
|
HttpResponse::build(StatusCode::OK)
|
||||||
.header("X-Test", "ttt")
|
.insert_header(("X-Test", "ttt"))
|
||||||
.finish(),
|
.finish(),
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
@ -633,23 +634,25 @@ mod tests {
|
||||||
|
|
||||||
let srv = logger.new_transform(srv.into_service()).await.unwrap();
|
let srv = logger.new_transform(srv.into_service()).await.unwrap();
|
||||||
|
|
||||||
let req = TestRequest::with_header(
|
let req = TestRequest::default()
|
||||||
header::USER_AGENT,
|
.insert_header((
|
||||||
header::HeaderValue::from_static("ACTIX-WEB"),
|
header::USER_AGENT,
|
||||||
)
|
header::HeaderValue::from_static("ACTIX-WEB"),
|
||||||
.to_srv_request();
|
))
|
||||||
|
.to_srv_request();
|
||||||
let _res = srv.call(req).await.unwrap();
|
let _res = srv.call(req).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_url_path() {
|
async fn test_url_path() {
|
||||||
let mut format = Format::new("%T %U");
|
let mut format = Format::new("%T %U");
|
||||||
let req = TestRequest::with_header(
|
let req = TestRequest::default()
|
||||||
header::USER_AGENT,
|
.insert_header((
|
||||||
header::HeaderValue::from_static("ACTIX-WEB"),
|
header::USER_AGENT,
|
||||||
)
|
header::HeaderValue::from_static("ACTIX-WEB"),
|
||||||
.uri("/test/route/yeah")
|
))
|
||||||
.to_srv_request();
|
.uri("/test/route/yeah")
|
||||||
|
.to_srv_request();
|
||||||
|
|
||||||
let now = OffsetDateTime::now_utc();
|
let now = OffsetDateTime::now_utc();
|
||||||
for unit in &mut format.0 {
|
for unit in &mut format.0 {
|
||||||
|
@ -676,12 +679,13 @@ mod tests {
|
||||||
async fn test_default_format() {
|
async fn test_default_format() {
|
||||||
let mut format = Format::default();
|
let mut format = Format::default();
|
||||||
|
|
||||||
let req = TestRequest::with_header(
|
let req = TestRequest::default()
|
||||||
header::USER_AGENT,
|
.insert_header((
|
||||||
header::HeaderValue::from_static("ACTIX-WEB"),
|
header::USER_AGENT,
|
||||||
)
|
header::HeaderValue::from_static("ACTIX-WEB"),
|
||||||
.peer_addr("127.0.0.1:8081".parse().unwrap())
|
))
|
||||||
.to_srv_request();
|
.peer_addr("127.0.0.1:8081".parse().unwrap())
|
||||||
|
.to_srv_request();
|
||||||
|
|
||||||
let now = OffsetDateTime::now_utc();
|
let now = OffsetDateTime::now_utc();
|
||||||
for unit in &mut format.0 {
|
for unit in &mut format.0 {
|
||||||
|
@ -736,13 +740,14 @@ mod tests {
|
||||||
async fn test_remote_addr_format() {
|
async fn test_remote_addr_format() {
|
||||||
let mut format = Format::new("%{r}a");
|
let mut format = Format::new("%{r}a");
|
||||||
|
|
||||||
let req = TestRequest::with_header(
|
let req = TestRequest::default()
|
||||||
header::FORWARDED,
|
.insert_header((
|
||||||
header::HeaderValue::from_static(
|
header::FORWARDED,
|
||||||
"for=192.0.2.60;proto=http;by=203.0.113.43",
|
header::HeaderValue::from_static(
|
||||||
),
|
"for=192.0.2.60;proto=http;by=203.0.113.43",
|
||||||
)
|
),
|
||||||
.to_srv_request();
|
))
|
||||||
|
.to_srv_request();
|
||||||
|
|
||||||
let now = OffsetDateTime::now_utc();
|
let now = OffsetDateTime::now_utc();
|
||||||
for unit in &mut format.0 {
|
for unit in &mut format.0 {
|
||||||
|
|
|
@ -423,8 +423,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_debug() {
|
fn test_debug() {
|
||||||
let req =
|
let req = TestRequest::default()
|
||||||
TestRequest::with_header("content-type", "text/plain").to_http_request();
|
.insert_header(("content-type", "text/plain"))
|
||||||
|
.to_http_request();
|
||||||
let dbg = format!("{:?}", req);
|
let dbg = format!("{:?}", req);
|
||||||
assert!(dbg.contains("HttpRequest"));
|
assert!(dbg.contains("HttpRequest"));
|
||||||
}
|
}
|
||||||
|
@ -438,8 +439,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_request_cookies() {
|
fn test_request_cookies() {
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
.header(header::COOKIE, "cookie1=value1")
|
.append_header((header::COOKIE, "cookie1=value1"))
|
||||||
.header(header::COOKIE, "cookie2=value2")
|
.append_header((header::COOKIE, "cookie2=value2"))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
{
|
{
|
||||||
let cookies = req.cookies().unwrap();
|
let cookies = req.cookies().unwrap();
|
||||||
|
@ -476,7 +477,8 @@ mod tests {
|
||||||
assert!(rmap.has_resource("/user/test.html"));
|
assert!(rmap.has_resource("/user/test.html"));
|
||||||
assert!(!rmap.has_resource("/test/unknown"));
|
assert!(!rmap.has_resource("/test/unknown"));
|
||||||
|
|
||||||
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org")
|
let req = TestRequest::default()
|
||||||
|
.insert_header((header::HOST, "www.rust-lang.org"))
|
||||||
.rmap(rmap)
|
.rmap(rmap)
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
|
|
||||||
|
@ -506,7 +508,7 @@ mod tests {
|
||||||
assert!(rmap.has_resource("/index.html"));
|
assert!(rmap.has_resource("/index.html"));
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/test")
|
let req = TestRequest::with_uri("/test")
|
||||||
.header(header::HOST, "www.rust-lang.org")
|
.insert_header((header::HOST, "www.rust-lang.org"))
|
||||||
.rmap(rmap)
|
.rmap(rmap)
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let url = req.url_for_static("index");
|
let url = req.url_for_static("index");
|
||||||
|
@ -557,7 +559,7 @@ mod tests {
|
||||||
let srv = init_service(App::new().service(web::resource("/").to(
|
let srv = init_service(App::new().service(web::resource("/").to(
|
||||||
|req: HttpRequest| {
|
|req: HttpRequest| {
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.set_header("pool_cap", req.app_state().pool().cap)
|
.insert_header(("pool_cap", req.app_state().pool().cap))
|
||||||
.finish()
|
.finish()
|
||||||
},
|
},
|
||||||
)))
|
)))
|
||||||
|
|
|
@ -203,10 +203,10 @@ where
|
||||||
///
|
///
|
||||||
/// Data of different types from parent contexts will still be accessible.
|
/// Data of different types from parent contexts will still be accessible.
|
||||||
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
|
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
|
||||||
if self.app_data.is_none() {
|
self.app_data
|
||||||
self.app_data = Some(Extensions::new());
|
.get_or_insert_with(Extensions::new)
|
||||||
}
|
.insert(data);
|
||||||
self.app_data.as_mut().unwrap().insert(data);
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,18 +382,16 @@ where
|
||||||
} else {
|
} else {
|
||||||
Some(std::mem::take(&mut self.guards))
|
Some(std::mem::take(&mut self.guards))
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut rdef = if config.is_root() || !self.rdef.is_empty() {
|
let mut rdef = if config.is_root() || !self.rdef.is_empty() {
|
||||||
ResourceDef::new(insert_slash(self.rdef.clone()))
|
ResourceDef::new(insert_slash(self.rdef.clone()))
|
||||||
} else {
|
} else {
|
||||||
ResourceDef::new(self.rdef.clone())
|
ResourceDef::new(self.rdef.clone())
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(ref name) = self.name {
|
if let Some(ref name) = self.name {
|
||||||
*rdef.name_mut() = name.clone();
|
*rdef.name_mut() = name.clone();
|
||||||
}
|
}
|
||||||
// custom app data storage
|
|
||||||
if let Some(ref mut ext) = self.app_data {
|
|
||||||
config.set_service_data(ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
config.register_service(rdef, guards, self, None)
|
config.register_service(rdef, guards, self, None)
|
||||||
}
|
}
|
||||||
|
@ -479,12 +477,15 @@ impl Service<ServiceRequest> for ResourceService {
|
||||||
if let Some(ref app_data) = self.app_data {
|
if let Some(ref app_data) = self.app_data {
|
||||||
req.add_data_container(app_data.clone());
|
req.add_data_container(app_data.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
return route.call(req);
|
return route.call(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref app_data) = self.app_data {
|
if let Some(ref app_data) = self.app_data {
|
||||||
req.add_data_container(app_data.clone());
|
req.add_data_container(app_data.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.default.call(req)
|
self.default.call(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
use std::convert::TryFrom;
|
use std::fmt;
|
||||||
|
|
||||||
use actix_http::error::InternalError;
|
use actix_http::{
|
||||||
use actix_http::http::{
|
error::InternalError,
|
||||||
header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, StatusCode,
|
http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode},
|
||||||
|
ResponseBuilder,
|
||||||
};
|
};
|
||||||
use actix_http::ResponseBuilder;
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
|
|
||||||
use crate::{Error, HttpRequest, HttpResponse};
|
use crate::{Error, HttpRequest, HttpResponse};
|
||||||
|
|
||||||
/// Trait implemented by types that can be converted to a http response.
|
/// Trait implemented by types that can be converted to an HTTP response.
|
||||||
///
|
///
|
||||||
/// Types that implement this trait can be used as the return type of a handler.
|
/// Any types that implement this trait can be used in the return type of a handler.
|
||||||
pub trait Responder {
|
pub trait Responder {
|
||||||
/// Convert self to `HttpResponse`.
|
/// Convert self to `HttpResponse`.
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse;
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse;
|
||||||
|
@ -19,12 +19,11 @@ pub trait Responder {
|
||||||
/// Override a status code for a Responder.
|
/// Override a status code for a Responder.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_web::{HttpRequest, Responder, http::StatusCode};
|
/// use actix_web::{http::StatusCode, HttpRequest, Responder};
|
||||||
///
|
///
|
||||||
/// fn index(req: HttpRequest) -> impl Responder {
|
/// fn index(req: HttpRequest) -> impl Responder {
|
||||||
/// "Welcome!".with_status(StatusCode::OK)
|
/// "Welcome!".with_status(StatusCode::OK)
|
||||||
/// }
|
/// }
|
||||||
/// # fn main() {}
|
|
||||||
/// ```
|
/// ```
|
||||||
fn with_status(self, status: StatusCode) -> CustomResponder<Self>
|
fn with_status(self, status: StatusCode) -> CustomResponder<Self>
|
||||||
where
|
where
|
||||||
|
@ -33,7 +32,9 @@ pub trait Responder {
|
||||||
CustomResponder::new(self).with_status(status)
|
CustomResponder::new(self).with_status(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add header to the Responder's response.
|
/// Insert header to the final response.
|
||||||
|
///
|
||||||
|
/// Overrides other headers with the same name.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_web::{web, HttpRequest, Responder};
|
/// use actix_web::{web, HttpRequest, Responder};
|
||||||
|
@ -45,21 +46,16 @@ pub trait Responder {
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn index(req: HttpRequest) -> impl Responder {
|
/// fn index(req: HttpRequest) -> impl Responder {
|
||||||
/// web::Json(
|
/// web::Json(MyObj { name: "Name".to_owned() })
|
||||||
/// MyObj{name: "Name".to_string()}
|
/// .with_header(("x-version", "1.2.3"))
|
||||||
/// )
|
|
||||||
/// .with_header("x-version", "1.2.3")
|
|
||||||
/// }
|
/// }
|
||||||
/// # fn main() {}
|
|
||||||
/// ```
|
/// ```
|
||||||
fn with_header<K, V>(self, key: K, value: V) -> CustomResponder<Self>
|
fn with_header<H>(self, header: H) -> CustomResponder<Self>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
HeaderName: TryFrom<K>,
|
H: IntoHeaderPair,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
|
||||||
V: IntoHeaderValue,
|
|
||||||
{
|
{
|
||||||
CustomResponder::new(self).with_header(key, value)
|
CustomResponder::new(self).with_header(header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +151,7 @@ impl Responder for BytesMut {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allows to override status code and headers for a responder.
|
/// Allows overriding status code and headers for a responder.
|
||||||
pub struct CustomResponder<T> {
|
pub struct CustomResponder<T> {
|
||||||
responder: T,
|
responder: T,
|
||||||
status: Option<StatusCode>,
|
status: Option<StatusCode>,
|
||||||
|
@ -181,14 +177,15 @@ impl<T: Responder> CustomResponder<T> {
|
||||||
/// fn index(req: HttpRequest) -> impl Responder {
|
/// fn index(req: HttpRequest) -> impl Responder {
|
||||||
/// "Welcome!".with_status(StatusCode::OK)
|
/// "Welcome!".with_status(StatusCode::OK)
|
||||||
/// }
|
/// }
|
||||||
/// # fn main() {}
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn with_status(mut self, status: StatusCode) -> Self {
|
pub fn with_status(mut self, status: StatusCode) -> Self {
|
||||||
self.status = Some(status);
|
self.status = Some(status);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add header to the Responder's response.
|
/// Insert header to the final response.
|
||||||
|
///
|
||||||
|
/// Overrides other headers with the same name.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_web::{web, HttpRequest, Responder};
|
/// use actix_web::{web, HttpRequest, Responder};
|
||||||
|
@ -200,32 +197,24 @@ impl<T: Responder> CustomResponder<T> {
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn index(req: HttpRequest) -> impl Responder {
|
/// fn index(req: HttpRequest) -> impl Responder {
|
||||||
/// web::Json(
|
/// web::Json(MyObj { name: "Name".to_string() })
|
||||||
/// MyObj{name: "Name".to_string()}
|
/// .with_header(("x-version", "1.2.3"))
|
||||||
/// )
|
/// .with_header(("x-version", "1.2.3"))
|
||||||
/// .with_header("x-version", "1.2.3")
|
|
||||||
/// }
|
/// }
|
||||||
/// # fn main() {}
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn with_header<K, V>(mut self, key: K, value: V) -> Self
|
pub fn with_header<H>(mut self, header: H) -> Self
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
H: IntoHeaderPair,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
|
||||||
V: IntoHeaderValue,
|
|
||||||
{
|
{
|
||||||
if self.headers.is_none() {
|
if self.headers.is_none() {
|
||||||
self.headers = Some(HeaderMap::new());
|
self.headers = Some(HeaderMap::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
match HeaderName::try_from(key) {
|
match header.try_into_header_pair() {
|
||||||
Ok(key) => match value.try_into() {
|
Ok((key, value)) => self.headers.as_mut().unwrap().append(key, value),
|
||||||
Ok(value) => {
|
|
||||||
self.headers.as_mut().unwrap().append(key, value);
|
|
||||||
}
|
|
||||||
Err(e) => self.error = Some(e.into()),
|
|
||||||
},
|
|
||||||
Err(e) => self.error = Some(e.into()),
|
Err(e) => self.error = Some(e.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,6 +229,7 @@ impl<T: Responder> Responder for CustomResponder<T> {
|
||||||
|
|
||||||
if let Some(ref headers) = self.headers {
|
if let Some(ref headers) = self.headers {
|
||||||
for (k, v) in headers {
|
for (k, v) in headers {
|
||||||
|
// TODO: before v4, decide if this should be append instead
|
||||||
res.headers_mut().insert(k.clone(), v.clone());
|
res.headers_mut().insert(k.clone(), v.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -250,7 +240,7 @@ impl<T: Responder> Responder for CustomResponder<T> {
|
||||||
|
|
||||||
impl<T> Responder for InternalError<T>
|
impl<T> Responder for InternalError<T>
|
||||||
where
|
where
|
||||||
T: std::fmt::Debug + std::fmt::Display + 'static,
|
T: fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
|
||||||
HttpResponse::from_error(self.into())
|
HttpResponse::from_error(self.into())
|
||||||
|
@ -412,7 +402,7 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
let res = "test"
|
let res = "test"
|
||||||
.to_string()
|
.to_string()
|
||||||
.with_header("content-type", "json")
|
.with_header(("content-type", "json"))
|
||||||
.respond_to(&req);
|
.respond_to(&req);
|
||||||
|
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
@ -432,13 +422,13 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let res = ("test".to_string(), StatusCode::OK)
|
let res = ("test".to_string(), StatusCode::OK)
|
||||||
.with_header("content-type", "json")
|
.with_header((CONTENT_TYPE, mime::APPLICATION_JSON))
|
||||||
.respond_to(&req);
|
.respond_to(&req);
|
||||||
assert_eq!(res.status(), StatusCode::OK);
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
assert_eq!(res.body().bin_ref(), b"test");
|
assert_eq!(res.body().bin_ref(), b"test");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.headers().get(CONTENT_TYPE).unwrap(),
|
res.headers().get(CONTENT_TYPE).unwrap(),
|
||||||
HeaderValue::from_static("json")
|
HeaderValue::from_static("application/json")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
24
src/scope.rs
24
src/scope.rs
|
@ -155,10 +155,10 @@ where
|
||||||
///
|
///
|
||||||
/// Data of different types from parent contexts will still be accessible.
|
/// Data of different types from parent contexts will still be accessible.
|
||||||
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
|
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
|
||||||
if self.app_data.is_none() {
|
self.app_data
|
||||||
self.app_data = Some(Extensions::new());
|
.get_or_insert_with(Extensions::new)
|
||||||
}
|
.insert(data);
|
||||||
self.app_data.as_mut().unwrap().insert(data);
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,18 +200,9 @@ where
|
||||||
self.services.extend(cfg.services);
|
self.services.extend(cfg.services);
|
||||||
self.external.extend(cfg.external);
|
self.external.extend(cfg.external);
|
||||||
|
|
||||||
if !cfg.data.is_empty() {
|
|
||||||
let mut data = self.app_data.unwrap_or_else(Extensions::new);
|
|
||||||
|
|
||||||
for value in cfg.data.iter() {
|
|
||||||
value.create(&mut data);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.app_data = Some(data);
|
|
||||||
}
|
|
||||||
self.app_data
|
self.app_data
|
||||||
.get_or_insert_with(Extensions::new)
|
.get_or_insert_with(Extensions::new)
|
||||||
.extend(cfg.extensions);
|
.extend(cfg.app_data);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,11 +423,6 @@ where
|
||||||
rmap.add(&mut rdef, None);
|
rmap.add(&mut rdef, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// custom app data storage
|
|
||||||
if let Some(ref mut ext) = self.app_data {
|
|
||||||
config.set_service_data(ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
// complete scope pipeline creation
|
// complete scope pipeline creation
|
||||||
*self.factory_ref.borrow_mut() = Some(ScopeFactory {
|
*self.factory_ref.borrow_mut() = Some(ScopeFactory {
|
||||||
app_data: self.app_data.take().map(Rc::new),
|
app_data: self.app_data.take().map(Rc::new),
|
||||||
|
|
|
@ -231,8 +231,10 @@ impl ServiceRequest {
|
||||||
self.payload = payload;
|
self.payload = payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
/// Add data container to request's resolution set.
|
||||||
/// Add app data container to request's resolution set.
|
///
|
||||||
|
/// In middleware, prefer [`extensions_mut`](ServiceRequest::extensions_mut) for request-local
|
||||||
|
/// data since it is assumed that the same app data is presented for every request.
|
||||||
pub fn add_data_container(&mut self, extensions: Rc<Extensions>) {
|
pub fn add_data_container(&mut self, extensions: Rc<Extensions>) {
|
||||||
Rc::get_mut(&mut (self.req).inner)
|
Rc::get_mut(&mut (self.req).inner)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -588,14 +590,14 @@ mod tests {
|
||||||
fn test_fmt_debug() {
|
fn test_fmt_debug() {
|
||||||
let req = TestRequest::get()
|
let req = TestRequest::get()
|
||||||
.uri("/index.html?test=1")
|
.uri("/index.html?test=1")
|
||||||
.header("x-test", "111")
|
.insert_header(("x-test", "111"))
|
||||||
.to_srv_request();
|
.to_srv_request();
|
||||||
let s = format!("{:?}", req);
|
let s = format!("{:?}", req);
|
||||||
assert!(s.contains("ServiceRequest"));
|
assert!(s.contains("ServiceRequest"));
|
||||||
assert!(s.contains("test=1"));
|
assert!(s.contains("test=1"));
|
||||||
assert!(s.contains("x-test"));
|
assert!(s.contains("x-test"));
|
||||||
|
|
||||||
let res = HttpResponse::Ok().header("x-test", "111").finish();
|
let res = HttpResponse::Ok().insert_header(("x-test", "111")).finish();
|
||||||
let res = TestRequest::post()
|
let res = TestRequest::post()
|
||||||
.uri("/index.html?test=1")
|
.uri("/index.html?test=1")
|
||||||
.to_srv_response(res);
|
.to_srv_response(res);
|
||||||
|
|
89
src/test.rs
89
src/test.rs
|
@ -1,13 +1,13 @@
|
||||||
//! Various helpers for Actix applications to use during testing.
|
//! Various helpers for Actix applications to use during testing.
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::{fmt, net, thread, time};
|
use std::{fmt, net, thread, time};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
use actix_http::http::header::{ContentType, Header, HeaderName, IntoHeaderValue};
|
use actix_http::http::header::{ContentType, IntoHeaderPair};
|
||||||
use actix_http::http::{Error as HttpError, Method, StatusCode, Uri, Version};
|
use actix_http::http::{Method, StatusCode, Uri, Version};
|
||||||
use actix_http::test::TestRequest as HttpTestRequest;
|
use actix_http::test::TestRequest as HttpTestRequest;
|
||||||
use actix_http::{cookie::Cookie, ws, Extensions, HttpService, Request};
|
use actix_http::{cookie::Cookie, ws, Extensions, HttpService, Request};
|
||||||
use actix_router::{Path, ResourceDef, Url};
|
use actix_router::{Path, ResourceDef, Url};
|
||||||
|
@ -239,7 +239,7 @@ where
|
||||||
/// web::resource("/people")
|
/// web::resource("/people")
|
||||||
/// .route(web::post().to(|person: web::Json<Person>| async {
|
/// .route(web::post().to(|person: web::Json<Person>| async {
|
||||||
/// HttpResponse::Ok()
|
/// HttpResponse::Ok()
|
||||||
/// .json(person.into_inner())})
|
/// .json(person)})
|
||||||
/// ))
|
/// ))
|
||||||
/// ).await;
|
/// ).await;
|
||||||
///
|
///
|
||||||
|
@ -299,7 +299,7 @@ where
|
||||||
/// web::resource("/people")
|
/// web::resource("/people")
|
||||||
/// .route(web::post().to(|person: web::Json<Person>| async {
|
/// .route(web::post().to(|person: web::Json<Person>| async {
|
||||||
/// HttpResponse::Ok()
|
/// HttpResponse::Ok()
|
||||||
/// .json(person.into_inner())})
|
/// .json(person)})
|
||||||
/// ))
|
/// ))
|
||||||
/// ).await;
|
/// ).await;
|
||||||
///
|
///
|
||||||
|
@ -349,7 +349,7 @@ where
|
||||||
///
|
///
|
||||||
/// #[test]
|
/// #[test]
|
||||||
/// fn test_index() {
|
/// fn test_index() {
|
||||||
/// let req = test::TestRequest::with_header("content-type", "text/plain")
|
/// let req = test::TestRequest::default().insert_header("content-type", "text/plain")
|
||||||
/// .to_http_request();
|
/// .to_http_request();
|
||||||
///
|
///
|
||||||
/// let resp = index(req).await.unwrap();
|
/// let resp = index(req).await.unwrap();
|
||||||
|
@ -389,21 +389,6 @@ impl TestRequest {
|
||||||
TestRequest::default().uri(path)
|
TestRequest::default().uri(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create TestRequest and set header
|
|
||||||
pub fn with_hdr<H: Header>(hdr: H) -> TestRequest {
|
|
||||||
TestRequest::default().set(hdr)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create TestRequest and set header
|
|
||||||
pub fn with_header<K, V>(key: K, value: V) -> TestRequest
|
|
||||||
where
|
|
||||||
HeaderName: TryFrom<K>,
|
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
|
||||||
V: IntoHeaderValue,
|
|
||||||
{
|
|
||||||
TestRequest::default().header(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create TestRequest and set method to `Method::GET`
|
/// Create TestRequest and set method to `Method::GET`
|
||||||
pub fn get() -> TestRequest {
|
pub fn get() -> TestRequest {
|
||||||
TestRequest::default().method(Method::GET)
|
TestRequest::default().method(Method::GET)
|
||||||
|
@ -447,24 +432,25 @@ impl TestRequest {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a header
|
/// Insert a header, replacing any that were set with an equivalent field name.
|
||||||
pub fn set<H: Header>(mut self, hdr: H) -> Self {
|
pub fn insert_header<H>(mut self, header: H) -> Self
|
||||||
self.req.set(hdr);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a header
|
|
||||||
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
|
||||||
where
|
where
|
||||||
HeaderName: TryFrom<K>,
|
H: IntoHeaderPair,
|
||||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
|
||||||
V: IntoHeaderValue,
|
|
||||||
{
|
{
|
||||||
self.req.header(key, value);
|
self.req.insert_header(header);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set cookie for this request
|
/// Append a header, keeping any that were set with an equivalent field name.
|
||||||
|
pub fn append_header<H>(mut self, header: H) -> Self
|
||||||
|
where
|
||||||
|
H: IntoHeaderPair,
|
||||||
|
{
|
||||||
|
self.req.append_header(header);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set cookie for this request.
|
||||||
pub fn cookie(mut self, cookie: Cookie<'_>) -> Self {
|
pub fn cookie(mut self, cookie: Cookie<'_>) -> Self {
|
||||||
self.req.cookie(cookie);
|
self.req.cookie(cookie);
|
||||||
self
|
self
|
||||||
|
@ -494,7 +480,7 @@ impl TestRequest {
|
||||||
let bytes = serde_urlencoded::to_string(data)
|
let bytes = serde_urlencoded::to_string(data)
|
||||||
.expect("Failed to serialize test data as a urlencoded form");
|
.expect("Failed to serialize test data as a urlencoded form");
|
||||||
self.req.set_payload(bytes);
|
self.req.set_payload(bytes);
|
||||||
self.req.set(ContentType::form_url_encoded());
|
self.req.insert_header(ContentType::form_url_encoded());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,7 +490,7 @@ impl TestRequest {
|
||||||
let bytes =
|
let bytes =
|
||||||
serde_json::to_string(data).expect("Failed to serialize test data to json");
|
serde_json::to_string(data).expect("Failed to serialize test data to json");
|
||||||
self.req.set_payload(bytes);
|
self.req.set_payload(bytes);
|
||||||
self.req.set(ContentType::json());
|
self.req.insert_header(ContentType::json());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1029,9 +1015,10 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_basics() {
|
async fn test_basics() {
|
||||||
let req = TestRequest::with_hdr(header::ContentType::json())
|
let req = TestRequest::default()
|
||||||
.version(Version::HTTP_2)
|
.version(Version::HTTP_2)
|
||||||
.set(header::Date(SystemTime::now().into()))
|
.insert_header(header::ContentType::json())
|
||||||
|
.insert_header(header::Date(SystemTime::now().into()))
|
||||||
.param("test", "123")
|
.param("test", "123")
|
||||||
.data(10u32)
|
.data(10u32)
|
||||||
.app_data(20u64)
|
.app_data(20u64)
|
||||||
|
@ -1068,7 +1055,7 @@ mod tests {
|
||||||
|
|
||||||
let put_req = TestRequest::put()
|
let put_req = TestRequest::put()
|
||||||
.uri("/index.html")
|
.uri("/index.html")
|
||||||
.header(header::CONTENT_TYPE, "application/json")
|
.insert_header((header::CONTENT_TYPE, "application/json"))
|
||||||
.to_request();
|
.to_request();
|
||||||
|
|
||||||
let result = read_response(&app, put_req).await;
|
let result = read_response(&app, put_req).await;
|
||||||
|
@ -1076,7 +1063,7 @@ mod tests {
|
||||||
|
|
||||||
let patch_req = TestRequest::patch()
|
let patch_req = TestRequest::patch()
|
||||||
.uri("/index.html")
|
.uri("/index.html")
|
||||||
.header(header::CONTENT_TYPE, "application/json")
|
.insert_header((header::CONTENT_TYPE, "application/json"))
|
||||||
.to_request();
|
.to_request();
|
||||||
|
|
||||||
let result = read_response(&app, patch_req).await;
|
let result = read_response(&app, patch_req).await;
|
||||||
|
@ -1099,7 +1086,7 @@ mod tests {
|
||||||
|
|
||||||
let req = TestRequest::post()
|
let req = TestRequest::post()
|
||||||
.uri("/index.html")
|
.uri("/index.html")
|
||||||
.header(header::CONTENT_TYPE, "application/json")
|
.insert_header((header::CONTENT_TYPE, "application/json"))
|
||||||
.to_request();
|
.to_request();
|
||||||
|
|
||||||
let result = read_response(&app, req).await;
|
let result = read_response(&app, req).await;
|
||||||
|
@ -1134,9 +1121,7 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_response_json() {
|
async fn test_response_json() {
|
||||||
let app = init_service(App::new().service(web::resource("/people").route(
|
let app = init_service(App::new().service(web::resource("/people").route(
|
||||||
web::post().to(|person: web::Json<Person>| {
|
web::post().to(|person: web::Json<Person>| HttpResponse::Ok().json(person)),
|
||||||
HttpResponse::Ok().json(person.into_inner())
|
|
||||||
}),
|
|
||||||
)))
|
)))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -1144,7 +1129,7 @@ mod tests {
|
||||||
|
|
||||||
let req = TestRequest::post()
|
let req = TestRequest::post()
|
||||||
.uri("/people")
|
.uri("/people")
|
||||||
.header(header::CONTENT_TYPE, "application/json")
|
.insert_header((header::CONTENT_TYPE, "application/json"))
|
||||||
.set_payload(payload)
|
.set_payload(payload)
|
||||||
.to_request();
|
.to_request();
|
||||||
|
|
||||||
|
@ -1155,9 +1140,7 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_body_json() {
|
async fn test_body_json() {
|
||||||
let app = init_service(App::new().service(web::resource("/people").route(
|
let app = init_service(App::new().service(web::resource("/people").route(
|
||||||
web::post().to(|person: web::Json<Person>| {
|
web::post().to(|person: web::Json<Person>| HttpResponse::Ok().json(person)),
|
||||||
HttpResponse::Ok().json(person.into_inner())
|
|
||||||
}),
|
|
||||||
)))
|
)))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -1165,7 +1148,7 @@ mod tests {
|
||||||
|
|
||||||
let resp = TestRequest::post()
|
let resp = TestRequest::post()
|
||||||
.uri("/people")
|
.uri("/people")
|
||||||
.header(header::CONTENT_TYPE, "application/json")
|
.insert_header((header::CONTENT_TYPE, "application/json"))
|
||||||
.set_payload(payload)
|
.set_payload(payload)
|
||||||
.send_request(&app)
|
.send_request(&app)
|
||||||
.await;
|
.await;
|
||||||
|
@ -1177,9 +1160,7 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_request_response_form() {
|
async fn test_request_response_form() {
|
||||||
let app = init_service(App::new().service(web::resource("/people").route(
|
let app = init_service(App::new().service(web::resource("/people").route(
|
||||||
web::post().to(|person: web::Form<Person>| {
|
web::post().to(|person: web::Form<Person>| HttpResponse::Ok().json(person)),
|
||||||
HttpResponse::Ok().json(person.into_inner())
|
|
||||||
}),
|
|
||||||
)))
|
)))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -1203,9 +1184,7 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_request_response_json() {
|
async fn test_request_response_json() {
|
||||||
let app = init_service(App::new().service(web::resource("/people").route(
|
let app = init_service(App::new().service(web::resource("/people").route(
|
||||||
web::post().to(|person: web::Json<Person>| {
|
web::post().to(|person: web::Json<Person>| HttpResponse::Ok().json(person)),
|
||||||
HttpResponse::Ok().json(person.into_inner())
|
|
||||||
}),
|
|
||||||
)))
|
)))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
|
@ -381,11 +381,11 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_form() {
|
async fn test_form() {
|
||||||
let (req, mut pl) =
|
let (req, mut pl) = TestRequest::default()
|
||||||
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
.insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded"))
|
||||||
.header(CONTENT_LENGTH, "11")
|
.insert_header((CONTENT_LENGTH, 11))
|
||||||
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
|
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
|
||||||
let Form(s) = Form::<Info>::from_request(&req, &mut pl).await.unwrap();
|
let Form(s) = Form::<Info>::from_request(&req, &mut pl).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -414,25 +414,26 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_urlencoded_error() {
|
async fn test_urlencoded_error() {
|
||||||
let (req, mut pl) =
|
let (req, mut pl) = TestRequest::default()
|
||||||
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
.insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded"))
|
||||||
.header(CONTENT_LENGTH, "xxxx")
|
.insert_header((CONTENT_LENGTH, "xxxx"))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
|
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
|
||||||
assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength));
|
assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength));
|
||||||
|
|
||||||
let (req, mut pl) =
|
let (req, mut pl) = TestRequest::default()
|
||||||
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
.insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded"))
|
||||||
.header(CONTENT_LENGTH, "1000000")
|
.insert_header((CONTENT_LENGTH, "1000000"))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
|
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
|
||||||
assert!(eq(
|
assert!(eq(
|
||||||
info.err().unwrap(),
|
info.err().unwrap(),
|
||||||
UrlencodedError::Overflow { size: 0, limit: 0 }
|
UrlencodedError::Overflow { size: 0, limit: 0 }
|
||||||
));
|
));
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain")
|
let (req, mut pl) = TestRequest::default()
|
||||||
.header(CONTENT_LENGTH, "10")
|
.insert_header((CONTENT_TYPE, "text/plain"))
|
||||||
|
.insert_header((CONTENT_LENGTH, 10))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
|
let info = UrlEncoded::<Info>::new(&req, &mut pl).await;
|
||||||
assert!(eq(info.err().unwrap(), UrlencodedError::ContentType));
|
assert!(eq(info.err().unwrap(), UrlencodedError::ContentType));
|
||||||
|
@ -440,11 +441,11 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_urlencoded() {
|
async fn test_urlencoded() {
|
||||||
let (req, mut pl) =
|
let (req, mut pl) = TestRequest::default()
|
||||||
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
.insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded"))
|
||||||
.header(CONTENT_LENGTH, "11")
|
.insert_header((CONTENT_LENGTH, 11))
|
||||||
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
|
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
|
||||||
let info = UrlEncoded::<Info>::new(&req, &mut pl).await.unwrap();
|
let info = UrlEncoded::<Info>::new(&req, &mut pl).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -455,13 +456,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::with_header(
|
let (req, mut pl) = TestRequest::default()
|
||||||
CONTENT_TYPE,
|
.insert_header((
|
||||||
"application/x-www-form-urlencoded; charset=utf-8",
|
CONTENT_TYPE,
|
||||||
)
|
"application/x-www-form-urlencoded; charset=utf-8",
|
||||||
.header(CONTENT_LENGTH, "11")
|
))
|
||||||
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
|
.insert_header((CONTENT_LENGTH, 11))
|
||||||
.to_http_parts();
|
.set_payload(Bytes::from_static(b"hello=world&counter=123"))
|
||||||
|
.to_http_parts();
|
||||||
|
|
||||||
let info = UrlEncoded::<Info>::new(&req, &mut pl).await.unwrap();
|
let info = UrlEncoded::<Info>::new(&req, &mut pl).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -497,8 +499,8 @@ mod tests {
|
||||||
let ctype = HeaderValue::from_static("application/x-www-form-urlencoded");
|
let ctype = HeaderValue::from_static("application/x-www-form-urlencoded");
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::default()
|
let (req, mut pl) = TestRequest::default()
|
||||||
.header(CONTENT_TYPE, ctype)
|
.insert_header((CONTENT_TYPE, ctype))
|
||||||
.header(CONTENT_LENGTH, HeaderValue::from_static("20"))
|
.insert_header((CONTENT_LENGTH, HeaderValue::from_static("20")))
|
||||||
.set_payload(Bytes::from_static(b"hello=test&counter=4"))
|
.set_payload(Bytes::from_static(b"hello=test&counter=4"))
|
||||||
.app_data(web::Data::new(FormConfig::default().limit(10)))
|
.app_data(web::Data::new(FormConfig::default().limit(10)))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
|
|
@ -269,7 +269,7 @@ impl JsonConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow shared refs to default.
|
/// Allow shared refs used as default.
|
||||||
const DEFAULT_CONFIG: JsonConfig = JsonConfig {
|
const DEFAULT_CONFIG: JsonConfig = JsonConfig {
|
||||||
limit: 32_768, // 2^15 bytes, (~32kB)
|
limit: 32_768, // 2^15 bytes, (~32kB)
|
||||||
err_handler: None,
|
err_handler: None,
|
||||||
|
@ -427,7 +427,7 @@ mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
error::InternalError,
|
error::InternalError,
|
||||||
http::{
|
http::{
|
||||||
header::{self, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
|
header::{self, CONTENT_LENGTH, CONTENT_TYPE},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
},
|
},
|
||||||
test::{load_stream, TestRequest},
|
test::{load_stream, TestRequest},
|
||||||
|
@ -469,14 +469,14 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_custom_error_responder() {
|
async fn test_custom_error_responder() {
|
||||||
let (req, mut pl) = TestRequest::default()
|
let (req, mut pl) = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
header::HeaderValue::from_static("application/json"),
|
header::HeaderValue::from_static("application/json"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONTENT_LENGTH,
|
header::CONTENT_LENGTH,
|
||||||
header::HeaderValue::from_static("16"),
|
header::HeaderValue::from_static("16"),
|
||||||
)
|
))
|
||||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||||
.app_data(JsonConfig::default().limit(10).error_handler(|err, _| {
|
.app_data(JsonConfig::default().limit(10).error_handler(|err, _| {
|
||||||
let msg = MyObject {
|
let msg = MyObject {
|
||||||
|
@ -500,14 +500,14 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_extract() {
|
async fn test_extract() {
|
||||||
let (req, mut pl) = TestRequest::default()
|
let (req, mut pl) = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
header::HeaderValue::from_static("application/json"),
|
header::HeaderValue::from_static("application/json"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONTENT_LENGTH,
|
header::CONTENT_LENGTH,
|
||||||
header::HeaderValue::from_static("16"),
|
header::HeaderValue::from_static("16"),
|
||||||
)
|
))
|
||||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
|
||||||
|
@ -521,14 +521,14 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::default()
|
let (req, mut pl) = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
header::HeaderValue::from_static("application/json"),
|
header::HeaderValue::from_static("application/json"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONTENT_LENGTH,
|
header::CONTENT_LENGTH,
|
||||||
header::HeaderValue::from_static("16"),
|
header::HeaderValue::from_static("16"),
|
||||||
)
|
))
|
||||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||||
.app_data(JsonConfig::default().limit(10))
|
.app_data(JsonConfig::default().limit(10))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
@ -538,14 +538,14 @@ mod tests {
|
||||||
.contains("Json payload size is bigger than allowed"));
|
.contains("Json payload size is bigger than allowed"));
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::default()
|
let (req, mut pl) = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
header::HeaderValue::from_static("application/json"),
|
header::HeaderValue::from_static("application/json"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONTENT_LENGTH,
|
header::CONTENT_LENGTH,
|
||||||
header::HeaderValue::from_static("16"),
|
header::HeaderValue::from_static("16"),
|
||||||
)
|
))
|
||||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||||
.app_data(
|
.app_data(
|
||||||
JsonConfig::default()
|
JsonConfig::default()
|
||||||
|
@ -564,23 +564,23 @@ mod tests {
|
||||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::default()
|
let (req, mut pl) = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
header::HeaderValue::from_static("application/text"),
|
header::HeaderValue::from_static("application/text"),
|
||||||
)
|
))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await;
|
let json = JsonBody::<MyObject>::new(&req, &mut pl, None).await;
|
||||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::default()
|
let (req, mut pl) = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
header::HeaderValue::from_static("application/json"),
|
header::HeaderValue::from_static("application/json"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONTENT_LENGTH,
|
header::CONTENT_LENGTH,
|
||||||
header::HeaderValue::from_static("10000"),
|
header::HeaderValue::from_static("10000"),
|
||||||
)
|
))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
|
||||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None)
|
let json = JsonBody::<MyObject>::new(&req, &mut pl, None)
|
||||||
|
@ -589,14 +589,14 @@ mod tests {
|
||||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow));
|
assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow));
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::default()
|
let (req, mut pl) = TestRequest::default()
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
header::HeaderValue::from_static("application/json"),
|
header::HeaderValue::from_static("application/json"),
|
||||||
)
|
))
|
||||||
.header(
|
.insert_header((
|
||||||
header::CONTENT_LENGTH,
|
header::CONTENT_LENGTH,
|
||||||
header::HeaderValue::from_static("16"),
|
header::HeaderValue::from_static("16"),
|
||||||
)
|
))
|
||||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
|
||||||
|
@ -611,17 +611,18 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_with_json_and_bad_content_type() {
|
async fn test_with_json_and_bad_content_type() {
|
||||||
let (req, mut pl) = TestRequest::with_header(
|
let (req, mut pl) = TestRequest::default()
|
||||||
header::CONTENT_TYPE,
|
.insert_header((
|
||||||
header::HeaderValue::from_static("text/plain"),
|
header::CONTENT_TYPE,
|
||||||
)
|
header::HeaderValue::from_static("text/plain"),
|
||||||
.header(
|
))
|
||||||
header::CONTENT_LENGTH,
|
.insert_header((
|
||||||
header::HeaderValue::from_static("16"),
|
header::CONTENT_LENGTH,
|
||||||
)
|
header::HeaderValue::from_static("16"),
|
||||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
))
|
||||||
.app_data(JsonConfig::default().limit(4096))
|
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||||
.to_http_parts();
|
.app_data(JsonConfig::default().limit(4096))
|
||||||
|
.to_http_parts();
|
||||||
|
|
||||||
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
||||||
assert!(s.is_err())
|
assert!(s.is_err())
|
||||||
|
@ -629,19 +630,20 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_with_json_and_good_custom_content_type() {
|
async fn test_with_json_and_good_custom_content_type() {
|
||||||
let (req, mut pl) = TestRequest::with_header(
|
let (req, mut pl) = TestRequest::default()
|
||||||
header::CONTENT_TYPE,
|
.insert_header((
|
||||||
header::HeaderValue::from_static("text/plain"),
|
header::CONTENT_TYPE,
|
||||||
)
|
header::HeaderValue::from_static("text/plain"),
|
||||||
.header(
|
))
|
||||||
header::CONTENT_LENGTH,
|
.insert_header((
|
||||||
header::HeaderValue::from_static("16"),
|
header::CONTENT_LENGTH,
|
||||||
)
|
header::HeaderValue::from_static("16"),
|
||||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
))
|
||||||
.app_data(JsonConfig::default().content_type(|mime: mime::Mime| {
|
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||||
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
.app_data(JsonConfig::default().content_type(|mime: mime::Mime| {
|
||||||
}))
|
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
||||||
.to_http_parts();
|
}))
|
||||||
|
.to_http_parts();
|
||||||
|
|
||||||
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
||||||
assert!(s.is_ok())
|
assert!(s.is_ok())
|
||||||
|
@ -649,19 +651,20 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_with_json_and_bad_custom_content_type() {
|
async fn test_with_json_and_bad_custom_content_type() {
|
||||||
let (req, mut pl) = TestRequest::with_header(
|
let (req, mut pl) = TestRequest::default()
|
||||||
header::CONTENT_TYPE,
|
.insert_header((
|
||||||
header::HeaderValue::from_static("text/html"),
|
header::CONTENT_TYPE,
|
||||||
)
|
header::HeaderValue::from_static("text/html"),
|
||||||
.header(
|
))
|
||||||
header::CONTENT_LENGTH,
|
.insert_header((
|
||||||
header::HeaderValue::from_static("16"),
|
header::CONTENT_LENGTH,
|
||||||
)
|
header::HeaderValue::from_static("16"),
|
||||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
))
|
||||||
.app_data(JsonConfig::default().content_type(|mime: mime::Mime| {
|
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||||
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
.app_data(JsonConfig::default().content_type(|mime: mime::Mime| {
|
||||||
}))
|
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
||||||
.to_http_parts();
|
}))
|
||||||
|
.to_http_parts();
|
||||||
|
|
||||||
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
let s = Json::<MyObject>::from_request(&req, &mut pl).await;
|
||||||
assert!(s.is_err())
|
assert!(s.is_err())
|
||||||
|
@ -670,8 +673,8 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_with_config_in_data_wrapper() {
|
async fn test_with_config_in_data_wrapper() {
|
||||||
let (req, mut pl) = TestRequest::default()
|
let (req, mut pl) = TestRequest::default()
|
||||||
.header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
|
.insert_header((CONTENT_TYPE, mime::APPLICATION_JSON))
|
||||||
.header(CONTENT_LENGTH, HeaderValue::from_static("16"))
|
.insert_header((CONTENT_LENGTH, 16))
|
||||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||||
.app_data(web::Data::new(JsonConfig::default().limit(10)))
|
.app_data(web::Data::new(JsonConfig::default().limit(10)))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
|
|
@ -247,7 +247,7 @@ impl PayloadConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow shared refs to default.
|
/// Allow shared refs used as defaults.
|
||||||
const DEFAULT_CONFIG: PayloadConfig = PayloadConfig {
|
const DEFAULT_CONFIG: PayloadConfig = PayloadConfig {
|
||||||
limit: DEFAULT_CONFIG_LIMIT,
|
limit: DEFAULT_CONFIG_LIMIT,
|
||||||
mimetype: None,
|
mimetype: None,
|
||||||
|
@ -364,14 +364,13 @@ mod tests {
|
||||||
let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON);
|
let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON);
|
||||||
assert!(cfg.check_mimetype(&req).is_err());
|
assert!(cfg.check_mimetype(&req).is_err());
|
||||||
|
|
||||||
let req = TestRequest::with_header(
|
let req = TestRequest::default()
|
||||||
header::CONTENT_TYPE,
|
.insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded"))
|
||||||
"application/x-www-form-urlencoded",
|
.to_http_request();
|
||||||
)
|
|
||||||
.to_http_request();
|
|
||||||
assert!(cfg.check_mimetype(&req).is_err());
|
assert!(cfg.check_mimetype(&req).is_err());
|
||||||
|
|
||||||
let req = TestRequest::with_header(header::CONTENT_TYPE, "application/json")
|
let req = TestRequest::default()
|
||||||
|
.insert_header((header::CONTENT_TYPE, "application/json"))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
assert!(cfg.check_mimetype(&req).is_ok());
|
assert!(cfg.check_mimetype(&req).is_ok());
|
||||||
}
|
}
|
||||||
|
@ -432,25 +431,25 @@ mod tests {
|
||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/bytes-app-data")
|
let req = TestRequest::with_uri("/bytes-app-data")
|
||||||
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
|
.insert_header(header::ContentType(mime::APPLICATION_JSON))
|
||||||
.to_request();
|
.to_request();
|
||||||
let resp = call_service(&srv, req).await;
|
let resp = call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/bytes-data")
|
let req = TestRequest::with_uri("/bytes-data")
|
||||||
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
|
.insert_header(header::ContentType(mime::APPLICATION_JSON))
|
||||||
.to_request();
|
.to_request();
|
||||||
let resp = call_service(&srv, req).await;
|
let resp = call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/string-app-data")
|
let req = TestRequest::with_uri("/string-app-data")
|
||||||
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
|
.insert_header(header::ContentType(mime::APPLICATION_JSON))
|
||||||
.to_request();
|
.to_request();
|
||||||
let resp = call_service(&srv, req).await;
|
let resp = call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
let req = TestRequest::with_uri("/string-data")
|
let req = TestRequest::with_uri("/string-data")
|
||||||
.header(header::CONTENT_TYPE, mime::APPLICATION_JSON)
|
.insert_header(header::ContentType(mime::APPLICATION_JSON))
|
||||||
.to_request();
|
.to_request();
|
||||||
let resp = call_service(&srv, req).await;
|
let resp = call_service(&srv, req).await;
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
@ -458,7 +457,8 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_bytes() {
|
async fn test_bytes() {
|
||||||
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11")
|
let (req, mut pl) = TestRequest::default()
|
||||||
|
.insert_header((header::CONTENT_LENGTH, "11"))
|
||||||
.set_payload(Bytes::from_static(b"hello=world"))
|
.set_payload(Bytes::from_static(b"hello=world"))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
|
||||||
|
@ -468,7 +468,8 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_string() {
|
async fn test_string() {
|
||||||
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11")
|
let (req, mut pl) = TestRequest::default()
|
||||||
|
.insert_header((header::CONTENT_LENGTH, "11"))
|
||||||
.set_payload(Bytes::from_static(b"hello=world"))
|
.set_payload(Bytes::from_static(b"hello=world"))
|
||||||
.to_http_parts();
|
.to_http_parts();
|
||||||
|
|
||||||
|
@ -478,7 +479,8 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_message_body() {
|
async fn test_message_body() {
|
||||||
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx")
|
let (req, mut pl) = TestRequest::default()
|
||||||
|
.insert_header((header::CONTENT_LENGTH, "xxxx"))
|
||||||
.to_srv_request()
|
.to_srv_request()
|
||||||
.into_parts();
|
.into_parts();
|
||||||
let res = HttpMessageBody::new(&req, &mut pl).await;
|
let res = HttpMessageBody::new(&req, &mut pl).await;
|
||||||
|
@ -487,7 +489,8 @@ mod tests {
|
||||||
_ => unreachable!("error"),
|
_ => unreachable!("error"),
|
||||||
}
|
}
|
||||||
|
|
||||||
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "1000000")
|
let (req, mut pl) = TestRequest::default()
|
||||||
|
.insert_header((header::CONTENT_LENGTH, "1000000"))
|
||||||
.to_srv_request()
|
.to_srv_request()
|
||||||
.into_parts();
|
.into_parts();
|
||||||
let res = HttpMessageBody::new(&req, &mut pl).await;
|
let res = HttpMessageBody::new(&req, &mut pl).await;
|
||||||
|
|
|
@ -108,7 +108,7 @@ async fn test_body_gzip() {
|
||||||
let mut response = srv
|
let mut response = srv
|
||||||
.get("/")
|
.get("/")
|
||||||
.no_decompress()
|
.no_decompress()
|
||||||
.header(ACCEPT_ENCODING, "gzip")
|
.append_header((ACCEPT_ENCODING, "gzip"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -137,7 +137,7 @@ async fn test_body_gzip2() {
|
||||||
let mut response = srv
|
let mut response = srv
|
||||||
.get("/")
|
.get("/")
|
||||||
.no_decompress()
|
.no_decompress()
|
||||||
.header(ACCEPT_ENCODING, "gzip")
|
.append_header((ACCEPT_ENCODING, "gzip"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -178,7 +178,7 @@ async fn test_body_encoding_override() {
|
||||||
let mut response = srv
|
let mut response = srv
|
||||||
.get("/")
|
.get("/")
|
||||||
.no_decompress()
|
.no_decompress()
|
||||||
.header(ACCEPT_ENCODING, "deflate")
|
.append_header((ACCEPT_ENCODING, "deflate"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -197,7 +197,7 @@ async fn test_body_encoding_override() {
|
||||||
let mut response = srv
|
let mut response = srv
|
||||||
.request(actix_web::http::Method::GET, srv.url("/raw"))
|
.request(actix_web::http::Method::GET, srv.url("/raw"))
|
||||||
.no_decompress()
|
.no_decompress()
|
||||||
.header(ACCEPT_ENCODING, "deflate")
|
.append_header((ACCEPT_ENCODING, "deflate"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -231,7 +231,7 @@ async fn test_body_gzip_large() {
|
||||||
let mut response = srv
|
let mut response = srv
|
||||||
.get("/")
|
.get("/")
|
||||||
.no_decompress()
|
.no_decompress()
|
||||||
.header(ACCEPT_ENCODING, "gzip")
|
.append_header((ACCEPT_ENCODING, "gzip"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -269,7 +269,7 @@ async fn test_body_gzip_large_random() {
|
||||||
let mut response = srv
|
let mut response = srv
|
||||||
.get("/")
|
.get("/")
|
||||||
.no_decompress()
|
.no_decompress()
|
||||||
.header(ACCEPT_ENCODING, "gzip")
|
.append_header((ACCEPT_ENCODING, "gzip"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -300,7 +300,7 @@ async fn test_body_chunked_implicit() {
|
||||||
let mut response = srv
|
let mut response = srv
|
||||||
.get("/")
|
.get("/")
|
||||||
.no_decompress()
|
.no_decompress()
|
||||||
.header(ACCEPT_ENCODING, "gzip")
|
.append_header((ACCEPT_ENCODING, "gzip"))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -333,7 +333,7 @@ async fn test_body_br_streaming() {
|
||||||
|
|
||||||
let mut response = srv
|
let mut response = srv
|
||||||
.get("/")
|
.get("/")
|
||||||
.header(ACCEPT_ENCODING, "br")
|
.append_header((ACCEPT_ENCODING, "br"))
|
||||||
.no_decompress()
|
.no_decompress()
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
|
@ -406,7 +406,7 @@ async fn test_body_deflate() {
|
||||||
// client request
|
// client request
|
||||||
let mut response = srv
|
let mut response = srv
|
||||||
.get("/")
|
.get("/")
|
||||||
.header(ACCEPT_ENCODING, "deflate")
|
.append_header((ACCEPT_ENCODING, "deflate"))
|
||||||
.no_decompress()
|
.no_decompress()
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
|
@ -433,7 +433,7 @@ async fn test_body_brotli() {
|
||||||
// client request
|
// client request
|
||||||
let mut response = srv
|
let mut response = srv
|
||||||
.get("/")
|
.get("/")
|
||||||
.header(ACCEPT_ENCODING, "br")
|
.append_header((ACCEPT_ENCODING, "br"))
|
||||||
.no_decompress()
|
.no_decompress()
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
|
@ -466,7 +466,7 @@ async fn test_encoding() {
|
||||||
|
|
||||||
let request = srv
|
let request = srv
|
||||||
.post("/")
|
.post("/")
|
||||||
.header(CONTENT_ENCODING, "gzip")
|
.insert_header((CONTENT_ENCODING, "gzip"))
|
||||||
.send_body(enc.clone());
|
.send_body(enc.clone());
|
||||||
let mut response = request.await.unwrap();
|
let mut response = request.await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
@ -492,7 +492,7 @@ async fn test_gzip_encoding() {
|
||||||
|
|
||||||
let request = srv
|
let request = srv
|
||||||
.post("/")
|
.post("/")
|
||||||
.header(CONTENT_ENCODING, "gzip")
|
.append_header((CONTENT_ENCODING, "gzip"))
|
||||||
.send_body(enc.clone());
|
.send_body(enc.clone());
|
||||||
let mut response = request.await.unwrap();
|
let mut response = request.await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
@ -519,7 +519,7 @@ async fn test_gzip_encoding_large() {
|
||||||
|
|
||||||
let request = srv
|
let request = srv
|
||||||
.post("/")
|
.post("/")
|
||||||
.header(CONTENT_ENCODING, "gzip")
|
.append_header((CONTENT_ENCODING, "gzip"))
|
||||||
.send_body(enc.clone());
|
.send_body(enc.clone());
|
||||||
let mut response = request.await.unwrap();
|
let mut response = request.await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
@ -551,7 +551,7 @@ async fn test_reading_gzip_encoding_large_random() {
|
||||||
|
|
||||||
let request = srv
|
let request = srv
|
||||||
.post("/")
|
.post("/")
|
||||||
.header(CONTENT_ENCODING, "gzip")
|
.append_header((CONTENT_ENCODING, "gzip"))
|
||||||
.send_body(enc.clone());
|
.send_body(enc.clone());
|
||||||
let mut response = request.await.unwrap();
|
let mut response = request.await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
@ -578,7 +578,7 @@ async fn test_reading_deflate_encoding() {
|
||||||
// client request
|
// client request
|
||||||
let request = srv
|
let request = srv
|
||||||
.post("/")
|
.post("/")
|
||||||
.header(CONTENT_ENCODING, "deflate")
|
.append_header((CONTENT_ENCODING, "deflate"))
|
||||||
.send_body(enc.clone());
|
.send_body(enc.clone());
|
||||||
let mut response = request.await.unwrap();
|
let mut response = request.await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
@ -605,7 +605,7 @@ async fn test_reading_deflate_encoding_large() {
|
||||||
// client request
|
// client request
|
||||||
let request = srv
|
let request = srv
|
||||||
.post("/")
|
.post("/")
|
||||||
.header(CONTENT_ENCODING, "deflate")
|
.append_header((CONTENT_ENCODING, "deflate"))
|
||||||
.send_body(enc.clone());
|
.send_body(enc.clone());
|
||||||
let mut response = request.await.unwrap();
|
let mut response = request.await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
@ -637,7 +637,7 @@ async fn test_reading_deflate_encoding_large_random() {
|
||||||
// client request
|
// client request
|
||||||
let request = srv
|
let request = srv
|
||||||
.post("/")
|
.post("/")
|
||||||
.header(CONTENT_ENCODING, "deflate")
|
.append_header((CONTENT_ENCODING, "deflate"))
|
||||||
.send_body(enc.clone());
|
.send_body(enc.clone());
|
||||||
let mut response = request.await.unwrap();
|
let mut response = request.await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
@ -664,7 +664,7 @@ async fn test_brotli_encoding() {
|
||||||
// client request
|
// client request
|
||||||
let request = srv
|
let request = srv
|
||||||
.post("/")
|
.post("/")
|
||||||
.header(CONTENT_ENCODING, "br")
|
.append_header((CONTENT_ENCODING, "br"))
|
||||||
.send_body(enc.clone());
|
.send_body(enc.clone());
|
||||||
let mut response = request.await.unwrap();
|
let mut response = request.await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
@ -699,7 +699,7 @@ async fn test_brotli_encoding_large() {
|
||||||
// client request
|
// client request
|
||||||
let request = srv
|
let request = srv
|
||||||
.post("/")
|
.post("/")
|
||||||
.header(CONTENT_ENCODING, "br")
|
.append_header((CONTENT_ENCODING, "br"))
|
||||||
.send_body(enc.clone());
|
.send_body(enc.clone());
|
||||||
let mut response = request.await.unwrap();
|
let mut response = request.await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
@ -739,7 +739,7 @@ async fn test_brotli_encoding_large_openssl() {
|
||||||
// client request
|
// client request
|
||||||
let mut response = srv
|
let mut response = srv
|
||||||
.post("/")
|
.post("/")
|
||||||
.header(actix_web::http::header::CONTENT_ENCODING, "br")
|
.append_header((actix_web::http::header::CONTENT_ENCODING, "br"))
|
||||||
.send_body(enc)
|
.send_body(enc)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -788,7 +788,7 @@ async fn test_reading_deflate_encoding_large_random_rustls() {
|
||||||
// client request
|
// client request
|
||||||
let req = srv
|
let req = srv
|
||||||
.post("/")
|
.post("/")
|
||||||
.header(actix_web::http::header::CONTENT_ENCODING, "deflate")
|
.insert_header((actix_web::http::header::CONTENT_ENCODING, "deflate"))
|
||||||
.send_stream(TestBody::new(Bytes::from(enc), 1024));
|
.send_stream(TestBody::new(Bytes::from(enc), 1024));
|
||||||
|
|
||||||
let mut response = req.await.unwrap();
|
let mut response = req.await.unwrap();
|
||||||
|
@ -800,61 +800,58 @@ async fn test_reading_deflate_encoding_large_random_rustls() {
|
||||||
assert_eq!(bytes, Bytes::from(data));
|
assert_eq!(bytes, Bytes::from(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[actix_rt::test]
|
||||||
// fn test_server_cookies() {
|
async fn test_server_cookies() {
|
||||||
// use actix_web::http;
|
use actix_web::{http, HttpMessage};
|
||||||
|
|
||||||
// let srv = test::TestServer::with_factory(|| {
|
let srv = test::start(|| {
|
||||||
// App::new().resource("/", |r| {
|
App::new().default_service(web::to(|| {
|
||||||
// r.f(|_| {
|
HttpResponse::Ok()
|
||||||
// HttpResponse::Ok()
|
.cookie(
|
||||||
// .cookie(
|
http::CookieBuilder::new("first", "first_value")
|
||||||
// http::CookieBuilder::new("first", "first_value")
|
.http_only(true)
|
||||||
// .http_only(true)
|
.finish(),
|
||||||
// .finish(),
|
)
|
||||||
// )
|
.cookie(http::Cookie::new("second", "first_value"))
|
||||||
// .cookie(http::Cookie::new("second", "first_value"))
|
.cookie(http::Cookie::new("second", "second_value"))
|
||||||
// .cookie(http::Cookie::new("second", "second_value"))
|
.finish()
|
||||||
// .finish()
|
}))
|
||||||
// })
|
});
|
||||||
// })
|
|
||||||
// });
|
|
||||||
|
|
||||||
// let first_cookie = http::CookieBuilder::new("first", "first_value")
|
let first_cookie = http::CookieBuilder::new("first", "first_value")
|
||||||
// .http_only(true)
|
.http_only(true)
|
||||||
// .finish();
|
.finish();
|
||||||
// let second_cookie = http::Cookie::new("second", "second_value");
|
let second_cookie = http::Cookie::new("second", "second_value");
|
||||||
|
|
||||||
// let request = srv.get("/").finish().unwrap();
|
let req = srv.get("/");
|
||||||
// let response = srv.execute(request.send()).unwrap();
|
let res = req.send().await.unwrap();
|
||||||
// assert!(response.status().is_success());
|
assert!(res.status().is_success());
|
||||||
|
|
||||||
// let cookies = response.cookies().expect("To have cookies");
|
let cookies = res.cookies().expect("To have cookies");
|
||||||
// assert_eq!(cookies.len(), 2);
|
assert_eq!(cookies.len(), 2);
|
||||||
// if cookies[0] == first_cookie {
|
if cookies[0] == first_cookie {
|
||||||
// assert_eq!(cookies[1], second_cookie);
|
assert_eq!(cookies[1], second_cookie);
|
||||||
// } else {
|
} else {
|
||||||
// assert_eq!(cookies[0], second_cookie);
|
assert_eq!(cookies[0], second_cookie);
|
||||||
// assert_eq!(cookies[1], first_cookie);
|
assert_eq!(cookies[1], first_cookie);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// let first_cookie = first_cookie.to_string();
|
let first_cookie = first_cookie.to_string();
|
||||||
// let second_cookie = second_cookie.to_string();
|
let second_cookie = second_cookie.to_string();
|
||||||
// //Check that we have exactly two instances of raw cookie headers
|
// Check that we have exactly two instances of raw cookie headers
|
||||||
// let cookies = response
|
let cookies = res
|
||||||
// .headers()
|
.headers()
|
||||||
// .get_all(http::header::SET_COOKIE)
|
.get_all(http::header::SET_COOKIE)
|
||||||
// .iter()
|
.map(|header| header.to_str().expect("To str").to_string())
|
||||||
// .map(|header| header.to_str().expect("To str").to_string())
|
.collect::<Vec<_>>();
|
||||||
// .collect::<Vec<_>>();
|
assert_eq!(cookies.len(), 2);
|
||||||
// assert_eq!(cookies.len(), 2);
|
if cookies[0] == first_cookie {
|
||||||
// if cookies[0] == first_cookie {
|
assert_eq!(cookies[1], second_cookie);
|
||||||
// assert_eq!(cookies[1], second_cookie);
|
} else {
|
||||||
// } else {
|
assert_eq!(cookies[0], second_cookie);
|
||||||
// assert_eq!(cookies[0], second_cookie);
|
assert_eq!(cookies[1], first_cookie);
|
||||||
// assert_eq!(cookies[1], first_cookie);
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_slow_request() {
|
async fn test_slow_request() {
|
||||||
|
@ -889,28 +886,3 @@ async fn test_normalize() {
|
||||||
let response = srv.get("/one/").send().await.unwrap();
|
let response = srv.get("/one/").send().await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[cfg(feature = "openssl")]
|
|
||||||
// #[actix_rt::test]
|
|
||||||
// async fn test_ssl_handshake_timeout() {
|
|
||||||
// use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
|
|
||||||
// use std::net;
|
|
||||||
|
|
||||||
// // load ssl keys
|
|
||||||
// let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
|
||||||
// builder
|
|
||||||
// .set_private_key_file("tests/key.pem", SslFiletype::PEM)
|
|
||||||
// .unwrap();
|
|
||||||
// builder
|
|
||||||
// .set_certificate_chain_file("tests/cert.pem")
|
|
||||||
// .unwrap();
|
|
||||||
|
|
||||||
// let srv = test::start_with(test::config().openssl(builder.build()), || {
|
|
||||||
// App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok())))
|
|
||||||
// });
|
|
||||||
|
|
||||||
// let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
|
||||||
// let mut data = String::new();
|
|
||||||
// let _ = stream.read_to_string(&mut data);
|
|
||||||
// assert!(data.is_empty());
|
|
||||||
// }
|
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
// Regression test for #/1321
|
//! Regression test for https://github.com/actix/actix-web/issues/1321
|
||||||
|
|
||||||
/*
|
// use actix_http::body::{BodyStream, MessageBody};
|
||||||
use futures::task::{noop_waker, Context};
|
// use bytes::Bytes;
|
||||||
use futures::stream::once;
|
// use futures_channel::oneshot;
|
||||||
use actix_http::body::{MessageBody, BodyStream};
|
// use futures_util::{
|
||||||
use bytes::Bytes;
|
// stream::once,
|
||||||
|
// task::{noop_waker, Context},
|
||||||
|
// };
|
||||||
|
|
||||||
Disable weird poll until actix-web is based on actix-http 2.0.0
|
// #[test]
|
||||||
|
// fn weird_poll() {
|
||||||
|
// let (sender, receiver) = oneshot::channel();
|
||||||
|
// let mut body_stream = Ok(BodyStream::new(once(async {
|
||||||
|
// let x = Box::new(0);
|
||||||
|
// let y = &x;
|
||||||
|
// receiver.await.unwrap();
|
||||||
|
// let _z = **y;
|
||||||
|
// Ok::<_, ()>(Bytes::new())
|
||||||
|
// })));
|
||||||
|
|
||||||
#[test]
|
// let waker = noop_waker();
|
||||||
fn weird_poll() {
|
// let mut cx = Context::from_waker(&waker);
|
||||||
let (sender, receiver) = futures::channel::oneshot::channel();
|
|
||||||
let mut body_stream = Ok(BodyStream::new(once(async {
|
|
||||||
let x = Box::new(0);
|
|
||||||
let y = &x;
|
|
||||||
receiver.await.unwrap();
|
|
||||||
let _z = **y;
|
|
||||||
Ok::<_, ()>(Bytes::new())
|
|
||||||
})));
|
|
||||||
|
|
||||||
let waker = noop_waker();
|
// let _ = body_stream.as_mut().unwrap().poll_next(&mut cx);
|
||||||
let mut context = Context::from_waker(&waker);
|
// sender.send(()).unwrap();
|
||||||
|
// let _ = std::mem::replace(&mut body_stream, Err([0; 32]))
|
||||||
let _ = body_stream.as_mut().unwrap().poll_next(&mut context);
|
// .unwrap()
|
||||||
sender.send(()).unwrap();
|
// .poll_next(&mut cx);
|
||||||
let _ = std::mem::replace(&mut body_stream, Err([0; 32])).unwrap().poll_next(&mut context);
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
Loading…
Reference in New Issue