squash commits

This commit is contained in:
fakeshadow 2021-02-05 14:24:18 -08:00
parent 4edeb5ce47
commit bcc60b091d
129 changed files with 2930 additions and 2662 deletions

View File

@ -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

View File

@ -11,14 +11,28 @@
* `ServiceRequest::into_parts` and `ServiceRequest::from_parts` would not fail. * `ServiceRequest::into_parts` and `ServiceRequest::from_parts` would not fail.
`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]
* `test::{call_service, read_response, read_response_json, send_request}` take `&Service`
in argument [#1905]
* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` would give `&Service` in closure
argument [#1905]
### 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
[#1905]: https://github.com/actix/actix-web/pull/1905
[#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

View File

@ -74,9 +74,9 @@ required-features = ["rustls"]
[dependencies] [dependencies]
actix-codec = "0.4.0-beta.1" actix-codec = "0.4.0-beta.1"
actix-macros = "0.1.0" actix-macros = "0.2.0"
actix-router = "0.2.4" actix-router = "0.2.6"
actix-rt = "2.0.0-beta.2" actix-rt = "2"
actix-server = "2.0.0-beta.2" actix-server = "2.0.0-beta.2"
actix-service = "2.0.0-beta.3" actix-service = "2.0.0-beta.3"
actix-utils = "3.0.0-beta.1" actix-utils = "3.0.0-beta.1"
@ -108,7 +108,7 @@ rust-tls = { package = "rustls", version = "0.19.0", optional = true }
smallvec = "1.6" smallvec = "1.6"
[dev-dependencies] [dev-dependencies]
actix = "0.11.0-beta.1" actix = { version = "0.11.0-beta.1", default-features = false }
actix-http = { version = "3.0.0-beta.1", features = ["actors"] } actix-http = { version = "3.0.0-beta.1", features = ["actors"] }
rand = "0.8" rand = "0.8"
env_logger = "0.8" env_logger = "0.8"
@ -132,6 +132,13 @@ 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" }
actix-server = { git = "https://github.com/actix/actix-net.git" }
actix-tls = { git = "https://github.com/actix/actix-net.git" }
actix-utils = { git = "https://github.com/actix/actix-net.git" }
actix-router = { git = "https://github.com/actix/actix-net.git" }
actix = { git = "https://github.com/actix/actix.git" }
[[bench]] [[bench]]
name = "server" name = "server"
harness = false harness = false

View File

@ -31,5 +31,5 @@ percent-encoding = "2.1"
v_htmlescape = "0.12" v_htmlescape = "0.12"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.0.0-beta.2" actix-rt = "2"
actix-web = "4.0.0-beta.1" actix-web = "4.0.0-beta.1"

View File

@ -73,7 +73,8 @@ mod tests {
}, },
middleware::Compress, middleware::Compress,
test::{self, TestRequest}, test::{self, TestRequest},
web, App, HttpResponse, Responder, web::{self, Bytes},
App, HttpResponse, Responder,
}; };
use futures_util::future::ok; use futures_util::future::ok;
@ -101,7 +102,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 +114,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 +127,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 +140,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 +152,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);
@ -365,7 +366,7 @@ mod tests {
DispositionType::Attachment DispositionType::Attachment
} }
let mut srv = test::init_service( let srv = test::init_service(
App::new().service( App::new().service(
Files::new("/", ".") Files::new("/", ".")
.mime_override(all_attachment) .mime_override(all_attachment)
@ -375,7 +376,7 @@ mod tests {
.await; .await;
let request = TestRequest::get().uri("/").to_request(); let request = TestRequest::get().uri("/").to_request();
let response = test::call_service(&mut srv, request).await; let response = test::call_service(&srv, request).await;
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
let content_disposition = response let content_disposition = response
@ -390,7 +391,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_named_file_ranges_status_code() { async fn test_named_file_ranges_status_code() {
let mut srv = test::init_service( let srv = test::init_service(
App::new().service(Files::new("/test", ".").index_file("Cargo.toml")), App::new().service(Files::new("/test", ".").index_file("Cargo.toml")),
) )
.await; .await;
@ -398,17 +399,17 @@ 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(&srv, request).await;
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
// 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(&srv, request).await;
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
} }
@ -420,7 +421,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 +431,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 +446,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 +456,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();
@ -495,14 +496,14 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_static_files_with_spaces() { async fn test_static_files_with_spaces() {
let mut srv = test::init_service( let srv = test::init_service(
App::new().service(Files::new("/", ".").index_file("Cargo.toml")), App::new().service(Files::new("/", ".").index_file("Cargo.toml")),
) )
.await; .await;
let request = TestRequest::get() let request = TestRequest::get()
.uri("/tests/test%20space.binary") .uri("/tests/test%20space.binary")
.to_request(); .to_request();
let response = test::call_service(&mut srv, request).await; let response = test::call_service(&srv, request).await;
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
let bytes = test::read_body(response).await; let bytes = test::read_body(response).await;
@ -512,28 +513,28 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_files_not_allowed() { async fn test_files_not_allowed() {
let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
let req = TestRequest::default() let req = TestRequest::default()
.uri("/Cargo.toml") .uri("/Cargo.toml")
.method(Method::POST) .method(Method::POST)
.to_request(); .to_request();
let resp = test::call_service(&mut srv, req).await; let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
let req = TestRequest::default() let req = TestRequest::default()
.method(Method::PUT) .method(Method::PUT)
.uri("/Cargo.toml") .uri("/Cargo.toml")
.to_request(); .to_request();
let resp = test::call_service(&mut srv, req).await; let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_files_guards() { async fn test_files_guards() {
let mut srv = test::init_service( let srv = test::init_service(
App::new().service(Files::new("/", ".").use_guards(guard::Post())), App::new().service(Files::new("/", ".").use_guards(guard::Post())),
) )
.await; .await;
@ -543,13 +544,13 @@ mod tests {
.method(Method::POST) .method(Method::POST)
.to_request(); .to_request();
let resp = test::call_service(&mut srv, req).await; let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_named_file_content_encoding() { async fn test_named_file_content_encoding() {
let mut srv = test::init_service(App::new().wrap(Compress::default()).service( let srv = test::init_service(App::new().wrap(Compress::default()).service(
web::resource("/").to(|| async { web::resource("/").to(|| async {
NamedFile::open("Cargo.toml") NamedFile::open("Cargo.toml")
.unwrap() .unwrap()
@ -560,16 +561,16 @@ 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(&srv, request).await;
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_named_file_content_encoding_gzip() { async fn test_named_file_content_encoding_gzip() {
let mut srv = test::init_service(App::new().wrap(Compress::default()).service( let srv = test::init_service(App::new().wrap(Compress::default()).service(
web::resource("/").to(|| async { web::resource("/").to(|| async {
NamedFile::open("Cargo.toml") NamedFile::open("Cargo.toml")
.unwrap() .unwrap()
@ -580,9 +581,9 @@ 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(&srv, request).await;
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert_eq!( assert_eq!(
res.headers() res.headers()
@ -604,27 +605,27 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_static_files() { async fn test_static_files() {
let mut srv = test::init_service( let srv = test::init_service(
App::new().service(Files::new("/", ".").show_files_listing()), App::new().service(Files::new("/", ".").show_files_listing()),
) )
.await; .await;
let req = TestRequest::with_uri("/missing").to_request(); let req = TestRequest::with_uri("/missing").to_request();
let resp = test::call_service(&mut srv, req).await; let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; let srv = test::init_service(App::new().service(Files::new("/", "."))).await;
let req = TestRequest::default().to_request(); let req = TestRequest::default().to_request();
let resp = test::call_service(&mut srv, req).await; let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut srv = test::init_service( let srv = test::init_service(
App::new().service(Files::new("/", ".").show_files_listing()), App::new().service(Files::new("/", ".").show_files_listing()),
) )
.await; .await;
let req = TestRequest::with_uri("/tests").to_request(); let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&mut srv, req).await; let resp = test::call_service(&srv, req).await;
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/html; charset=utf-8" "text/html; charset=utf-8"
@ -637,16 +638,16 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_redirect_to_slash_directory() { async fn test_redirect_to_slash_directory() {
// should not redirect if no index // should not redirect if no index
let mut srv = test::init_service( let srv = test::init_service(
App::new().service(Files::new("/", ".").redirect_to_slash_directory()), App::new().service(Files::new("/", ".").redirect_to_slash_directory()),
) )
.await; .await;
let req = TestRequest::with_uri("/tests").to_request(); let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&mut srv, req).await; let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
// should redirect if index present // should redirect if index present
let mut srv = test::init_service( let srv = test::init_service(
App::new().service( App::new().service(
Files::new("/", ".") Files::new("/", ".")
.index_file("test.png") .index_file("test.png")
@ -655,12 +656,12 @@ mod tests {
) )
.await; .await;
let req = TestRequest::with_uri("/tests").to_request(); let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&mut srv, req).await; let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::FOUND);
// should not redirect if the path is wrong // should not redirect if the path is wrong
let req = TestRequest::with_uri("/not_existing").to_request(); let req = TestRequest::with_uri("/not_existing").to_request();
let resp = test::call_service(&mut srv, req).await; let resp = test::call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
} }
@ -672,7 +673,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_default_handler_file_missing() { async fn test_default_handler_file_missing() {
let mut st = Files::new("/", ".") let st = Files::new("/", ".")
.default_handler(|req: ServiceRequest| { .default_handler(|req: ServiceRequest| {
ok(req.into_response(HttpResponse::Ok().body("default content"))) ok(req.into_response(HttpResponse::Ok().body("default content")))
}) })
@ -681,7 +682,7 @@ mod tests {
.unwrap(); .unwrap();
let req = TestRequest::with_uri("/missing").to_srv_request(); let req = TestRequest::with_uri("/missing").to_srv_request();
let resp = test::call_service(&mut st, req).await; let resp = test::call_service(&st, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let bytes = test::read_body(resp).await; let bytes = test::read_body(resp).await;
assert_eq!(bytes, web::Bytes::from_static(b"default content")); assert_eq!(bytes, web::Bytes::from_static(b"default content"));
@ -750,54 +751,49 @@ mod tests {
// ); // );
// } // }
// #[actix_rt::test] #[actix_rt::test]
// fn integration_serve_index() { async fn integration_serve_index() {
// let mut srv = test::TestServer::with_factory(|| { let srv = test::init_service(
// App::new().handler( App::new().service(Files::new("test", ".").index_file("Cargo.toml")),
// "test", )
// Files::new(".").index_file("Cargo.toml"), .await;
// )
// });
// let request = srv.get().uri(srv.url("/test")).finish().unwrap(); let req = TestRequest::get().uri("/test").to_request();
// let response = srv.execute(request.send()).unwrap(); let res = test::call_service(&srv, req).await;
// assert_eq!(response.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
// let bytes = srv.execute(response.body()).unwrap();
// let data = Bytes::from(fs::read("Cargo.toml").unwrap());
// assert_eq!(bytes, data);
// let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); let bytes = test::read_body(res).await;
// let response = srv.execute(request.send()).unwrap();
// assert_eq!(response.status(), StatusCode::OK);
// let bytes = srv.execute(response.body()).unwrap();
// let data = Bytes::from(fs::read("Cargo.toml").unwrap());
// assert_eq!(bytes, data);
// // nonexistent index file let data = Bytes::from(fs::read("Cargo.toml").unwrap());
// let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap(); assert_eq!(bytes, data);
// let response = srv.execute(request.send()).unwrap();
// assert_eq!(response.status(), StatusCode::NOT_FOUND);
// let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap(); let req = TestRequest::get().uri("/test/").to_request();
// let response = srv.execute(request.send()).unwrap(); let res = test::call_service(&srv, req).await;
// assert_eq!(response.status(), StatusCode::NOT_FOUND); assert_eq!(res.status(), StatusCode::OK);
// }
// #[actix_rt::test] let bytes = test::read_body(res).await;
// fn integration_percent_encoded() { let data = Bytes::from(fs::read("Cargo.toml").unwrap());
// let mut srv = test::TestServer::with_factory(|| { assert_eq!(bytes, data);
// App::new().handler(
// "test",
// Files::new(".").index_file("Cargo.toml"),
// )
// });
// let request = srv // nonexistent index file
// .get() let req = TestRequest::get().uri("/test/unknown").to_request();
// .uri(srv.url("/test/%43argo.toml")) let res = test::call_service(&srv, req).await;
// .finish() assert_eq!(res.status(), StatusCode::NOT_FOUND);
// .unwrap();
// let response = srv.execute(request.send()).unwrap(); let req = TestRequest::get().uri("/test/unknown/").to_request();
// assert_eq!(response.status(), StatusCode::OK); let res = test::call_service(&srv, req).await;
// } assert_eq!(res.status(), StatusCode::NOT_FOUND);
}
#[actix_rt::test]
async fn integration_percent_encoded() {
let srv = test::init_service(
App::new().service(Files::new("test", ".").index_file("Cargo.toml")),
)
.await;
let req = TestRequest::get().uri("/test/%43argo.toml").to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
}
} }

View File

@ -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 {

View File

@ -1,9 +1,4 @@
use std::{ use std::{fmt, io, path::PathBuf, rc::Rc, task::Poll};
fmt, io,
path::PathBuf,
rc::Rc,
task::{Context, Poll},
};
use actix_service::Service; use actix_service::Service;
use actix_web::{ use actix_web::{
@ -40,10 +35,10 @@ type FilesServiceFuture = Either<
>; >;
impl FilesService { impl FilesService {
fn handle_err(&mut self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture { fn handle_err(&self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture {
log::debug!("Failed to handle {}: {}", req.path(), e); log::debug!("Failed to handle {}: {}", req.path(), e);
if let Some(ref mut default) = self.default { if let Some(ref default) = self.default {
Either::Right(default.call(req)) Either::Right(default.call(req))
} else { } else {
Either::Left(ok(req.error_response(e))) Either::Left(ok(req.error_response(e)))
@ -62,11 +57,9 @@ impl Service<ServiceRequest> for FilesService {
type Error = Error; type Error = Error;
type Future = FilesServiceFuture; type Future = FilesServiceFuture;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&self, req: ServiceRequest) -> Self::Future {
let is_method_valid = if let Some(guard) = &self.guards { let is_method_valid = if let Some(guard) = &self.guards {
// execute user defined guards // execute user defined guards
(**guard).check(req.head()) (**guard).check(req.head())
@ -78,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."),
))); )));
} }
@ -102,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(),
))); )));

View File

@ -11,11 +11,10 @@ use actix_web::{
#[actix_rt::test] #[actix_rt::test]
async fn test_utf8_file_contents() { async fn test_utf8_file_contents() {
// use default ISO-8859-1 encoding // use default ISO-8859-1 encoding
let mut srv = let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await;
test::init_service(App::new().service(Files::new("/", "./tests"))).await;
let req = TestRequest::with_uri("/utf8.txt").to_request(); let req = TestRequest::with_uri("/utf8.txt").to_request();
let res = test::call_service(&mut srv, req).await; let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert_eq!( assert_eq!(
@ -24,13 +23,13 @@ async fn test_utf8_file_contents() {
); );
// prefer UTF-8 encoding // prefer UTF-8 encoding
let mut srv = test::init_service( let srv = test::init_service(
App::new().service(Files::new("/", "./tests").prefer_utf8(true)), App::new().service(Files::new("/", "./tests").prefer_utf8(true)),
) )
.await; .await;
let req = TestRequest::with_uri("/utf8.txt").to_request(); let req = TestRequest::with_uri("/utf8.txt").to_request();
let res = test::call_service(&mut srv, req).await; let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert_eq!( assert_eq!(

View File

@ -33,7 +33,7 @@ actix-service = "2.0.0-beta.3"
actix-codec = "0.4.0-beta.1" actix-codec = "0.4.0-beta.1"
actix-tls = "3.0.0-beta.2" actix-tls = "3.0.0-beta.2"
actix-utils = "3.0.0-beta.1" actix-utils = "3.0.0-beta.1"
actix-rt = "2.0.0-beta.2" actix-rt = "2"
actix-server = "2.0.0-beta.2" actix-server = "2.0.0-beta.2"
awc = "3.0.0-beta.1" awc = "3.0.0-beta.1"

View File

@ -60,7 +60,7 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
// run server in separate thread // run server in separate thread
thread::spawn(move || { thread::spawn(move || {
let sys = System::new("actix-test-server"); let sys = System::new();
let local_addr = tcp.local_addr().unwrap(); let local_addr = tcp.local_addr().unwrap();
let srv = Server::build() let srv = Server::build()
@ -69,7 +69,7 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
.disable_signals(); .disable_signals();
sys.block_on(async { sys.block_on(async {
srv.start(); srv.run();
tx.send((System::current(), local_addr)).unwrap(); tx.send((System::current(), local_addr)).unwrap();
}); });
@ -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,

View File

@ -1,9 +1,37 @@
# 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]
* `client::error::ConnectError` Resolver variant contains `Box<dyn std::error::Error>` type [#1905]
* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905]
### 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
[#1905]: https://github.com/actix/actix-web/pull/1905
[#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 +62,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]

View File

@ -43,9 +43,9 @@ actors = ["actix"]
actix-service = "2.0.0-beta.3" actix-service = "2.0.0-beta.3"
actix-codec = "0.4.0-beta.1" actix-codec = "0.4.0-beta.1"
actix-utils = "3.0.0-beta.1" actix-utils = "3.0.0-beta.1"
actix-rt = "2.0.0-beta.2" actix-rt = "2"
actix-tls = "3.0.0-beta.2" actix-tls = "3.0.0-beta.2"
actix = { version = "0.11.0-beta.1", optional = true } actix = { version = "0.11.0-beta.1", default-features = false, optional = true }
base64 = "0.13" base64 = "0.13"
bitflags = "1.2" bitflags = "1.2"
@ -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"] }

View File

@ -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),
) )
}) })

View File

@ -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))
} }

View File

@ -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()

View File

@ -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)]
@ -19,7 +18,7 @@ pub(crate) struct ConnectorConfig {
impl Default for ConnectorConfig { impl Default for ConnectorConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
timeout: Duration::from_secs(1), timeout: Duration::from_secs(5),
conn_lifetime: Duration::from_secs(75), conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15), conn_keep_alive: Duration::from_secs(15),
disconnect_timeout: Some(Duration::from_millis(3000)), disconnect_timeout: Some(Duration::from_millis(3000)),

View File

@ -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;

View File

@ -100,9 +100,9 @@ impl Connector<(), ()> {
fn build_ssl(protocols: Vec<Vec<u8>>) -> SslConnector { fn build_ssl(protocols: Vec<Vec<u8>>) -> SslConnector {
let mut config = ClientConfig::new(); let mut config = ClientConfig::new();
config.set_protocols(&protocols); config.set_protocols(&protocols);
config config.root_store.add_server_trust_anchors(
.root_store &actix_tls::connect::ssl::rustls::TLS_SERVER_ROOTS,
.add_server_trust_anchors(&actix_tls::accept::rustls::TLS_SERVER_ROOTS); );
SslConnector::Rustls(Arc::new(config)) SslConnector::Rustls(Arc::new(config))
} }
@ -392,11 +392,11 @@ mod connect_impl {
Ready<Result<IoConnection<Io>, ConnectError>>, Ready<Result<IoConnection<Io>, ConnectError>>,
>; >;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.tcp_pool.poll_ready(cx) self.tcp_pool.poll_ready(cx)
} }
fn call(&mut self, req: Connect) -> Self::Future { fn call(&self, req: Connect) -> Self::Future {
match req.uri.scheme_str() { match req.uri.scheme_str() {
Some("https") | Some("wss") => { Some("https") | Some("wss") => {
Either::Right(err(ConnectError::SslIsNotSupported)) Either::Right(err(ConnectError::SslIsNotSupported))
@ -460,11 +460,11 @@ mod connect_impl {
InnerConnectorResponseB<T2, Io1, Io2>, InnerConnectorResponseB<T2, Io1, Io2>,
>; >;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.tcp_pool.poll_ready(cx) self.tcp_pool.poll_ready(cx)
} }
fn call(&mut self, req: Connect) -> Self::Future { fn call(&self, req: Connect) -> Self::Future {
match req.uri.scheme_str() { match req.uri.scheme_str() {
Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB { Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB {
fut: self.ssl_pool.call(req), fut: self.ssl_pool.call(req),

View File

@ -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")]

View File

@ -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)

View File

@ -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>>,

View File

@ -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;
@ -45,7 +46,7 @@ impl From<Authority> for Key {
} }
/// Connections pool /// Connections pool
pub(crate) struct ConnectionPool<T, Io: 'static>(Rc<RefCell<T>>, Rc<RefCell<Inner<Io>>>); pub(crate) struct ConnectionPool<T, Io: 'static>(Rc<T>, Rc<RefCell<Inner<Io>>>);
impl<T, Io> ConnectionPool<T, Io> impl<T, Io> ConnectionPool<T, Io>
where where
@ -53,13 +54,13 @@ where
T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static, T: Service<Connect, Response = (Io, Protocol), Error = ConnectError> + 'static,
{ {
pub(crate) fn new(connector: T, config: ConnectorConfig) -> Self { pub(crate) fn new(connector: T, config: ConnectorConfig) -> Self {
let connector_rc = Rc::new(RefCell::new(connector)); let connector_rc = Rc::new(connector);
let inner_rc = Rc::new(RefCell::new(Inner { let inner_rc = Rc::new(RefCell::new(Inner {
config, config,
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(),
})); }));
@ -98,12 +99,12 @@ where
type Error = ConnectError; type Error = ConnectError;
type Future = LocalBoxFuture<'static, Result<IoConnection<Io>, ConnectError>>; type Future = LocalBoxFuture<'static, Result<IoConnection<Io>, ConnectError>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx) self.0.poll_ready(cx)
} }
fn call(&mut self, req: Connect) -> Self::Future { fn call(&self, req: Connect) -> Self::Future {
let mut connector = self.0.clone(); let connector = self.0.clone();
let inner = self.1.clone(); let inner = self.1.clone();
let fut = async move { let fut = async move {
@ -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,
@ -325,7 +326,7 @@ where
{ {
if let Some(timeout) = self.config.disconnect_timeout { if let Some(timeout) = self.config.disconnect_timeout {
if let ConnectionType::H1(io) = conn.io { if let ConnectionType::H1(io) = conn.io {
actix_rt::spawn(CloseConnection::new(io, timeout)) actix_rt::spawn(CloseConnection::new(io, timeout));
} }
} }
} else { } else {
@ -340,7 +341,7 @@ where
if let ConnectionType::H1(io) = io { if let ConnectionType::H1(io) = io {
actix_rt::spawn(CloseConnection::new( actix_rt::spawn(CloseConnection::new(
io, timeout, io, timeout,
)) ));
} }
} }
continue; continue;
@ -372,7 +373,7 @@ where
self.acquired -= 1; self.acquired -= 1;
if let Some(timeout) = self.config.disconnect_timeout { if let Some(timeout) = self.config.disconnect_timeout {
if let ConnectionType::H1(io) = io { if let ConnectionType::H1(io) = io {
actix_rt::spawn(CloseConnection::new(io, timeout)) actix_rt::spawn(CloseConnection::new(io, timeout));
} }
} }
self.check_availability(); self.check_availability();
@ -428,7 +429,7 @@ struct ConnectorPoolSupport<T, Io>
where where
Io: AsyncRead + AsyncWrite + Unpin + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
connector: T, connector: Rc<T>,
inner: Rc<RefCell<Inner<Io>>>, inner: Rc<RefCell<Inner<Io>>>,
} }
@ -535,7 +536,7 @@ where
rx: Some(rx), rx: Some(rx),
inner: Some(inner), inner: Some(inner),
config, config,
}) });
} }
} }

View File

@ -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)]

View File

@ -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;

View File

@ -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::*;

View File

@ -14,7 +14,7 @@ use crate::header::HeaderMap;
use crate::message::{ConnectionType, ResponseHead}; use crate::message::{ConnectionType, ResponseHead};
use crate::request::Request; use crate::request::Request;
const MAX_BUFFER_SIZE: usize = 131_072; pub(crate) const MAX_BUFFER_SIZE: usize = 131_072;
const MAX_HEADERS: usize = 96; const MAX_HEADERS: usize = 96;
/// Incoming message decoder /// Incoming message decoder
@ -203,7 +203,15 @@ impl MessageType for Request {
(len, method, uri, version, req.headers.len()) (len, method, uri, version, req.headers.len())
} }
httparse::Status::Partial => return Ok(None), httparse::Status::Partial => {
return if src.len() >= MAX_BUFFER_SIZE {
trace!("MAX_BUFFER_SIZE unprocessed data reached, closing");
Err(ParseError::TooLarge)
} else {
// Return None to notify more read are needed for parsing request
Ok(None)
};
}
} }
}; };
@ -222,9 +230,6 @@ impl MessageType for Request {
PayloadLength::None => { PayloadLength::None => {
if method == Method::CONNECT { if method == Method::CONNECT {
PayloadType::Stream(PayloadDecoder::eof()) PayloadType::Stream(PayloadDecoder::eof())
} else if src.len() >= MAX_BUFFER_SIZE {
trace!("MAX_BUFFER_SIZE unprocessed data reached, closing");
return Err(ParseError::TooLarge);
} else { } else {
PayloadType::None PayloadType::None
} }
@ -273,7 +278,14 @@ impl MessageType for ResponseHead {
(len, version, status, res.headers.len()) (len, version, status, res.headers.len())
} }
httparse::Status::Partial => return Ok(None), httparse::Status::Partial => {
return if src.len() >= MAX_BUFFER_SIZE {
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
Err(ParseError::TooLarge)
} else {
Ok(None)
}
}
} }
}; };
@ -289,9 +301,6 @@ impl MessageType for ResponseHead {
} else if status == StatusCode::SWITCHING_PROTOCOLS { } else if status == StatusCode::SWITCHING_PROTOCOLS {
// switching protocol or connect // switching protocol or connect
PayloadType::Stream(PayloadDecoder::eof()) PayloadType::Stream(PayloadDecoder::eof())
} else if src.len() >= MAX_BUFFER_SIZE {
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
return Err(ParseError::TooLarge);
} else { } else {
// for HTTP/1.0 read to eof and close connection // for HTTP/1.0 read to eof and close connection
if msg.version == Version::HTTP_10 { if msg.version == Version::HTTP_10 {

View File

@ -1,5 +1,4 @@
use std::{ use std::{
cell::RefCell,
collections::VecDeque, collections::VecDeque,
fmt, fmt,
future::Future, future::Future,
@ -46,7 +45,7 @@ bitflags! {
} }
} }
#[pin_project::pin_project] #[pin_project]
/// Dispatcher for HTTP/1.1 protocol /// Dispatcher for HTTP/1.1 protocol
pub struct Dispatcher<T, S, B, X, U> pub struct Dispatcher<T, S, B, X, U>
where where
@ -91,7 +90,7 @@ where
U: Service<(Request, Framed<T, Codec>), Response = ()>, U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
{ {
flow: Rc<RefCell<HttpFlow<S, X, U>>>, flow: Rc<HttpFlow<S, X, U>>,
on_connect_data: OnConnectData, on_connect_data: OnConnectData,
flags: Flags, flags: Flags,
peer_addr: Option<net::SocketAddr>, peer_addr: Option<net::SocketAddr>,
@ -140,27 +139,14 @@ where
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
matches!(self, State::None) matches!(self, State::None)
} }
fn is_call(&self) -> bool {
matches!(self, State::ServiceCall(_))
}
} }
enum PollResponse { enum PollResponse {
Upgrade(Request), Upgrade(Request),
DoNothing, DoNothing,
DrainWriteBuf, DrainWriteBuf,
} }
impl PartialEq for PollResponse {
fn eq(&self, other: &PollResponse) -> bool {
match self {
PollResponse::DrainWriteBuf => matches!(other, PollResponse::DrainWriteBuf),
PollResponse::DoNothing => matches!(other, PollResponse::DoNothing),
_ => false,
}
}
}
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U> impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
@ -177,7 +163,7 @@ where
pub(crate) fn new( pub(crate) fn new(
stream: T, stream: T,
config: ServiceConfig, config: ServiceConfig,
services: Rc<RefCell<HttpFlow<S, X, U>>>, flow: Rc<HttpFlow<S, X, U>>,
on_connect_data: OnConnectData, on_connect_data: OnConnectData,
peer_addr: Option<net::SocketAddr>, peer_addr: Option<net::SocketAddr>,
) -> Self { ) -> Self {
@ -187,7 +173,7 @@ where
config, config,
BytesMut::with_capacity(HW_BUFFER_SIZE), BytesMut::with_capacity(HW_BUFFER_SIZE),
None, None,
services, flow,
on_connect_data, on_connect_data,
peer_addr, peer_addr,
) )
@ -200,7 +186,7 @@ where
config: ServiceConfig, config: ServiceConfig,
read_buf: BytesMut, read_buf: BytesMut,
timeout: Option<Sleep>, timeout: Option<Sleep>,
services: Rc<RefCell<HttpFlow<S, X, U>>>, flow: Rc<HttpFlow<S, X, U>>,
on_connect_data: OnConnectData, on_connect_data: OnConnectData,
peer_addr: Option<net::SocketAddr>, peer_addr: Option<net::SocketAddr>,
) -> Self { ) -> Self {
@ -230,7 +216,7 @@ where
io: Some(io), io: Some(io),
codec, codec,
read_buf, read_buf,
flow: services, flow,
on_connect_data, on_connect_data,
flags, flags,
peer_addr, peer_addr,
@ -270,13 +256,14 @@ where
} }
// if checked is set to true, delay disconnect until all tasks have finished. // if checked is set to true, delay disconnect until all tasks have finished.
fn client_disconnected(self: Pin<&mut Self>) { fn client_disconnected(self: Pin<&mut Self>, err: impl Into<DispatchError>) {
let this = self.project(); let this = self.project();
this.flags this.flags
.insert(Flags::READ_DISCONNECT | Flags::WRITE_DISCONNECT); .insert(Flags::READ_DISCONNECT | Flags::WRITE_DISCONNECT);
if let Some(mut payload) = this.payload.take() { if let Some(mut payload) = this.payload.take() {
payload.set_error(PayloadError::Incomplete(None)); payload.set_error(PayloadError::Incomplete(None));
} }
*this.error = Some(err.into());
} }
/// Flush stream /// Flush stream
@ -325,9 +312,10 @@ where
message: Response<()>, message: Response<()>,
body: ResponseBody<B>, body: ResponseBody<B>,
) -> Result<(), DispatchError> { ) -> Result<(), DispatchError> {
let size = body.size();
let mut this = self.project(); let mut this = self.project();
this.codec this.codec
.encode(Message::Item((message, body.size())), &mut this.write_buf) .encode(Message::Item((message, size)), &mut this.write_buf)
.map_err(|err| { .map_err(|err| {
if let Some(mut payload) = this.payload.take() { if let Some(mut payload) = this.payload.take() {
payload.set_error(PayloadError::Incomplete(None)); payload.set_error(PayloadError::Incomplete(None));
@ -336,74 +324,76 @@ where
})?; })?;
this.flags.set(Flags::KEEPALIVE, this.codec.keepalive()); this.flags.set(Flags::KEEPALIVE, this.codec.keepalive());
match body.size() { match size {
BodySize::None | BodySize::Empty => this.state.set(State::None), BodySize::None | BodySize::Empty => this.state.set(State::None),
_ => this.state.set(State::SendPayload(body)), _ => this.state.set(State::SendPayload(body)),
}; };
Ok(()) Ok(())
} }
fn send_continue(self: Pin<&mut Self>) {
self.project()
.write_buf
.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
}
fn poll_response( fn poll_response(
mut self: Pin<&mut Self>, mut self: Pin<&mut Self>,
cx: &mut Context<'_>, cx: &mut Context<'_>,
) -> Result<PollResponse, DispatchError> { ) -> Result<PollResponse, DispatchError> {
loop { loop {
let mut this = self.as_mut().project(); let mut this = self.as_mut().project();
// state is not changed on Poll::Pending. match this.state.as_mut().project() {
// other variant and conditions always trigger a state change(or an error). // no future is in InnerDispatcher state. pop next message.
let state_change = match this.state.project() {
StateProj::None => match this.messages.pop_front() { StateProj::None => match this.messages.pop_front() {
// handle request message.
Some(DispatcherMessage::Item(req)) => { Some(DispatcherMessage::Item(req)) => {
self.as_mut().handle_request(req, cx)?; // Handle `EXPECT: 100-Continue` header
true if req.head().expect() {
// set InnerDispatcher state and continue loop to poll it.
let task = this.flow.expect.call(req);
this.state.set(State::ExpectCall(task));
} else {
// the same as expect call.
let task = this.flow.service.call(req);
this.state.set(State::ServiceCall(task));
};
} }
// handle error message.
Some(DispatcherMessage::Error(res)) => { Some(DispatcherMessage::Error(res)) => {
// send_response would update InnerDispatcher state to SendPayload or
// None(If response body is empty).
// continue loop to poll it.
self.as_mut() self.as_mut()
.send_response(res, ResponseBody::Other(Body::Empty))?; .send_response(res, ResponseBody::Other(Body::Empty))?;
true
} }
// return with upgrade request and poll it exclusively.
Some(DispatcherMessage::Upgrade(req)) => { Some(DispatcherMessage::Upgrade(req)) => {
return Ok(PollResponse::Upgrade(req)); return Ok(PollResponse::Upgrade(req));
} }
None => false, // all messages are dealt with.
}, None => return Ok(PollResponse::DoNothing),
StateProj::ExpectCall(fut) => match fut.poll(cx) {
Poll::Ready(Ok(req)) => {
self.as_mut().send_continue();
this = self.as_mut().project();
let fut = this.flow.borrow_mut().service.call(req);
this.state.set(State::ServiceCall(fut));
continue;
}
Poll::Ready(Err(e)) => {
let res: Response = e.into().into();
let (res, body) = res.replace_body(());
self.as_mut().send_response(res, body.into_body())?;
true
}
Poll::Pending => false,
}, },
StateProj::ServiceCall(fut) => match fut.poll(cx) { StateProj::ServiceCall(fut) => match fut.poll(cx) {
// service call resolved. send response.
Poll::Ready(Ok(res)) => { Poll::Ready(Ok(res)) => {
let (res, body) = res.into().replace_body(()); let (res, body) = res.into().replace_body(());
self.as_mut().send_response(res, body)?; self.as_mut().send_response(res, body)?;
continue;
} }
// send service call error as response
Poll::Ready(Err(e)) => { Poll::Ready(Err(e)) => {
let res: Response = e.into().into(); let res: Response = e.into().into();
let (res, body) = res.replace_body(()); let (res, body) = res.replace_body(());
self.as_mut().send_response(res, body.into_body())?; self.as_mut().send_response(res, body.into_body())?;
true
} }
Poll::Pending => false, // service call pending and could be waiting for more chunk messages.
// (pipeline message limit and/or payload can_read limit)
Poll::Pending => {
// no new message is decoded and no new payload is feed.
// nothing to do except waiting for new incoming data from client.
if !self.as_mut().poll_request(cx)? {
return Ok(PollResponse::DoNothing);
}
// otherwise keep loop.
}
}, },
StateProj::SendPayload(mut stream) => { StateProj::SendPayload(mut stream) => {
// keep populate writer buffer until buffer size limit hit,
// get blocked or finished.
loop { loop {
if this.write_buf.len() < HW_BUFFER_SIZE { if this.write_buf.len() < HW_BUFFER_SIZE {
match stream.as_mut().poll_next(cx) { match stream.as_mut().poll_next(cx) {
@ -412,50 +402,60 @@ where
Message::Chunk(Some(item)), Message::Chunk(Some(item)),
&mut this.write_buf, &mut this.write_buf,
)?; )?;
continue;
} }
Poll::Ready(None) => { Poll::Ready(None) => {
this.codec.encode( this.codec.encode(
Message::Chunk(None), Message::Chunk(None),
&mut this.write_buf, &mut this.write_buf,
)?; )?;
this = self.as_mut().project(); // payload stream finished.
this.state.set(State::None); // break and goes out of scope of borrowed stream.
break;
} }
Poll::Ready(Some(Err(_))) => { Poll::Ready(Some(Err(e))) => {
return Err(DispatchError::Unknown) return Err(DispatchError::Service(e))
} }
// Payload Stream Pending should only be given when the caller
// promise to wake it up properly.
//
// TODO: Think if it's an good idea to mix in a self wake up.
// It would turn dispatcher into a busy polling style of stream
// handling. (Or another timer as source of scheduled wake up)
// As There is no way to know when or how the caller would wake
// up the stream so a self wake up is an overhead that would
// result in a double polling(or an extra timer)
Poll::Pending => return Ok(PollResponse::DoNothing), Poll::Pending => return Ok(PollResponse::DoNothing),
} }
} else { } else {
// buffer is beyond max size.
// return and write the whole buffer to io stream.
return Ok(PollResponse::DrainWriteBuf); return Ok(PollResponse::DrainWriteBuf);
} }
break;
} }
continue; // break from Poll::Ready(None) on stream finished.
// this is for re borrow InnerDispatcher state and set it to None.
this.state.set(State::None);
} }
}; StateProj::ExpectCall(fut) => match fut.poll(cx) {
// expect resolved. write continue to buffer and set InnerDispatcher state
// state is changed and continue when the state is not Empty // to service call.
if state_change { Poll::Ready(Ok(req)) => {
if !self.state.is_empty() { this.write_buf
continue; .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
} let fut = this.flow.service.call(req);
} else { this.state.set(State::ServiceCall(fut));
// if read-backpressure is enabled and we consumed some data.
// we may read more data and retry
if self.state.is_call() {
if self.as_mut().poll_request(cx)? {
continue;
} }
} else if !self.messages.is_empty() { // send expect error as response
continue; Poll::Ready(Err(e)) => {
} let res: Response = e.into().into();
let (res, body) = res.replace_body(());
self.as_mut().send_response(res, body.into_body())?;
}
// expect must be solved before progress can be made.
Poll::Pending => return Ok(PollResponse::DoNothing),
},
} }
break;
} }
Ok(PollResponse::DoNothing)
} }
fn handle_request( fn handle_request(
@ -463,52 +463,28 @@ where
req: Request, req: Request,
cx: &mut Context<'_>, cx: &mut Context<'_>,
) -> Result<(), DispatchError> { ) -> Result<(), DispatchError> {
let mut this = self.as_mut().project();
// Handle `EXPECT: 100-Continue` header // Handle `EXPECT: 100-Continue` header
if req.head().expect() { if req.head().expect() {
// set dispatcher state so the future is pinned. // set InnerDispatcher state so the future is pinned.
let mut this = self.as_mut().project(); let task = this.flow.expect.call(req);
let task = this.flow.borrow_mut().expect.call(req);
this.state.set(State::ExpectCall(task)); this.state.set(State::ExpectCall(task));
} else { } else {
// the same as above. // the same as above.
let mut this = self.as_mut().project(); let task = this.flow.service.call(req);
let task = this.flow.borrow_mut().service.call(req);
this.state.set(State::ServiceCall(task)); this.state.set(State::ServiceCall(task));
}; };
// eagerly poll the future for once(or twice if expect is resolved immediately). // eagerly poll the future for once(or twice if expect is resolved immediately).
loop { loop {
match self.as_mut().project().state.project() { match this.state.as_mut().project() {
StateProj::ExpectCall(fut) => {
match fut.poll(cx) {
// expect is resolved. continue loop and poll the service call branch.
Poll::Ready(Ok(req)) => {
self.as_mut().send_continue();
let mut this = self.as_mut().project();
let task = this.flow.borrow_mut().service.call(req);
this.state.set(State::ServiceCall(task));
continue;
}
// future is pending. return Ok(()) to notify that a new state is
// set and the outer loop should be continue.
Poll::Pending => return Ok(()),
// future is error. send response and return a result. On success
// to notify the dispatcher a new state is set and the outer loop
// should be continue.
Poll::Ready(Err(e)) => {
let e = e.into();
let res: Response = e.into();
let (res, body) = res.replace_body(());
return self.send_response(res, body.into_body());
}
}
}
StateProj::ServiceCall(fut) => { StateProj::ServiceCall(fut) => {
// return no matter the service call future's result. // return no matter the service call future's result.
return match fut.poll(cx) { return match fut.poll(cx) {
// future is resolved. send response and return a result. On success // future is resolved. send response and return a result. On success
// to notify the dispatcher a new state is set and the outer loop // to notify the dispatcher a new InnerDispatcher state is set and the
// should be continue. // outer loop should be continue.
Poll::Ready(Ok(res)) => { Poll::Ready(Ok(res)) => {
let (res, body) = res.into().replace_body(()); let (res, body) = res.into().replace_body(());
self.send_response(res, body) self.send_response(res, body)
@ -523,6 +499,28 @@ where
} }
}; };
} }
StateProj::ExpectCall(fut) => {
match fut.poll(cx) {
// expect is resolved. continue loop and poll the service call branch.
Poll::Ready(Ok(req)) => {
this.write_buf
.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
let task = this.flow.service.call(req);
this.state.as_mut().set(State::ServiceCall(task));
}
// future is pending. return Ok(()) to notify that a new InnerDispatcher
// state is set and the outer loop should be continue.
Poll::Pending => return Ok(()),
// future is error. send response and return a result. On success
// to notify the dispatcher a new InnerDispatcher state is set and
// the outer loop should be continue.
Poll::Ready(Err(e)) => {
let res: Response = e.into().into();
let (res, body) = res.replace_body(());
return self.send_response(res, body.into_body());
}
}
}
_ => unreachable!( _ => unreachable!(
"State must be set to ServiceCall or ExceptCall in handle_request" "State must be set to ServiceCall or ExceptCall in handle_request"
), ),
@ -549,28 +547,39 @@ where
this.flags.insert(Flags::STARTED); this.flags.insert(Flags::STARTED);
match msg { match msg {
// handle new request.
Message::Item(mut req) => { Message::Item(mut req) => {
let pl = this.codec.message_type();
req.head_mut().peer_addr = *this.peer_addr; req.head_mut().peer_addr = *this.peer_addr;
// merge on_connect_ext data into request extensions // merge on_connect_ext data into request extensions
this.on_connect_data.merge_into(&mut req); this.on_connect_data.merge_into(&mut req);
if pl == MessageType::Stream match this.codec.message_type() {
&& this.flow.borrow().upgrade.is_some() // break when upgrade received.
{ // existing buffer and io stream would be handled by framed
this.messages.push_back(DispatcherMessage::Upgrade(req)); // after upgrade success.
break; MessageType::Stream if this.flow.upgrade.is_some() => {
} this.messages
if pl == MessageType::Payload || pl == MessageType::Stream { .push_back(DispatcherMessage::Upgrade(req));
let (ps, pl) = Payload::create(false); break;
let (req1, _) = }
req.replace_payload(crate::Payload::H1(pl)); // construct request and payload.
req = req1; MessageType::Payload | MessageType::Stream => {
*this.payload = Some(ps); // PayloadSender and Payload are smart pointers share the same
// state. Payload is pass to Request and handed to service
// for extracting state. PayloadSender is held by dispatcher
// to push new data/error to state.
let (ps, pl) = Payload::create(false);
let (req1, _) =
req.replace_payload(crate::Payload::H1(pl));
req = req1;
*this.payload = Some(ps);
}
// Ignore empty payload.
MessageType::None => {}
} }
// handle request early // handle request early if no future lives in InnerDispatcher state.
if this.state.is_empty() { if this.state.is_empty() {
self.as_mut().handle_request(req, cx)?; self.as_mut().handle_request(req, cx)?;
this = self.as_mut().project(); this = self.as_mut().project();
@ -578,54 +587,60 @@ where
this.messages.push_back(DispatcherMessage::Item(req)); this.messages.push_back(DispatcherMessage::Item(req));
} }
} }
Message::Chunk(Some(chunk)) => { Message::Chunk(Some(chunk)) => match this.payload {
if let Some(ref mut payload) = this.payload { Some(ref mut payload) => payload.feed_data(chunk),
payload.feed_data(chunk); None => {
} else {
error!( error!(
"Internal server error: unexpected payload chunk" "Internal server error: unexpected payload chunk"
); );
this.flags.insert(Flags::READ_DISCONNECT); self.as_mut().response_error(
this.messages.push_back(DispatcherMessage::Error(
Response::InternalServerError().finish().drop_body(), Response::InternalServerError().finish().drop_body(),
)); DispatchError::InternalError,
*this.error = Some(DispatchError::InternalError); );
this = self.project();
break; break;
} }
} },
Message::Chunk(None) => { Message::Chunk(None) => match this.payload.take() {
if let Some(mut payload) = this.payload.take() { Some(mut payload) => payload.feed_eof(),
payload.feed_eof(); None => {
} else {
error!("Internal server error: unexpected eof"); error!("Internal server error: unexpected eof");
this.flags.insert(Flags::READ_DISCONNECT); self.as_mut().response_error(
this.messages.push_back(DispatcherMessage::Error(
Response::InternalServerError().finish().drop_body(), Response::InternalServerError().finish().drop_body(),
)); DispatchError::InternalError,
*this.error = Some(DispatchError::InternalError); );
this = self.project();
break; break;
} }
} },
} }
} }
Ok(None) => break, Ok(None) => break,
Err(ParseError::Io(e)) => { Err(ParseError::Io(e)) => {
self.as_mut().client_disconnected(); self.as_mut().client_disconnected(e);
this = self.as_mut().project(); this = self.as_mut().project();
*this.error = Some(DispatchError::Io(e)); break;
}
// big size requests overflow should be responded with 413
Err(ParseError::TooLarge) => {
if let Some(mut payload) = this.payload.take() {
payload.set_error(PayloadError::EncodingCorrupted);
}
self.as_mut().response_error(
Response::PayloadTooLarge().finish().drop_body(),
ParseError::TooLarge,
);
this = self.project();
break; break;
} }
Err(e) => { Err(e) => {
if let Some(mut payload) = this.payload.take() { if let Some(mut payload) = this.payload.take() {
payload.set_error(PayloadError::EncodingCorrupted); payload.set_error(PayloadError::EncodingCorrupted);
} }
// Malformed requests should be responded with 400 // Malformed requests should be responded with 400
this.messages.push_back(DispatcherMessage::Error( self.as_mut()
Response::BadRequest().finish().drop_body(), .response_error(Response::BadRequest().finish().drop_body(), e);
)); this = self.project();
this.flags.insert(Flags::READ_DISCONNECT);
*this.error = Some(e.into());
break; break;
} }
} }
@ -636,6 +651,7 @@ where
*this.ka_expire = expire; *this.ka_expire = expire;
} }
} }
Ok(updated) Ok(updated)
} }
@ -645,85 +661,172 @@ 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(())
} }
/// Returns true when io stream can be disconnected after write to it.
///
/// It covers these conditions:
///
/// - `Flags::READ_DISCONNECT` flag active.
/// - `std::io::ErrorKind::ConnectionReset` after partial read.
/// - all data read done.
#[inline(always)]
fn read_available(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Result<bool, DispatchError> {
let this = self.project();
if this.flags.contains(Flags::READ_DISCONNECT) {
return Ok(true);
};
let mut io = Pin::new(this.io.as_mut().unwrap());
let mut read_some = false;
loop {
// grow buffer if necessary.
let remaining = this.read_buf.capacity() - this.read_buf.len();
if remaining < LW_BUFFER_SIZE {
this.read_buf.reserve(HW_BUFFER_SIZE - remaining);
}
match actix_codec::poll_read_buf(io.as_mut(), cx, this.read_buf) {
Poll::Pending => return Ok(false),
Poll::Ready(Ok(n)) => {
if n == 0 {
return Ok(true);
} else {
// Return early when read buf exceed decoder's max buffer size.
if this.read_buf.len() >= super::decoder::MAX_BUFFER_SIZE {
// at this point it's not known io is still scheduled to
// be waked up. so force wake up dispatcher just in case.
// TODO: figure out the overhead.
cx.waker().wake_by_ref();
return Ok(false);
}
read_some = true;
}
}
Poll::Ready(Err(err)) => {
return if err.kind() == io::ErrorKind::WouldBlock {
Ok(false)
} else if err.kind() == io::ErrorKind::ConnectionReset && read_some {
Ok(true)
} else {
Err(DispatchError::Io(err))
}
}
}
}
}
/// call upgrade service with request.
fn upgrade(self: Pin<&mut Self>, req: Request) -> U::Future {
let this = self.project();
let mut parts = FramedParts::with_read_buf(
this.io.take().unwrap(),
mem::take(this.codec),
mem::take(this.read_buf),
);
parts.write_buf = mem::take(this.write_buf);
let framed = Framed::from_parts(parts);
this.flow.upgrade.as_ref().unwrap().call((req, framed))
}
/// response error handler.
fn response_error(
self: Pin<&mut Self>,
res: Response<()>,
err: impl Into<DispatchError>,
) {
let this = self.project();
// set flag to read disconnect so no new data is read.
this.flags.insert(Flags::READ_DISCONNECT);
// add response to message and send back to client.
this.messages.push_back(DispatcherMessage::Error(res));
// attach error for resolve dispatcher future with error.
*this.error = Some(err.into());
}
} }
impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U> impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
@ -757,9 +860,10 @@ where
if inner.flags.contains(Flags::WRITE_DISCONNECT) { if inner.flags.contains(Flags::WRITE_DISCONNECT) {
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} else { } else {
// flush buffer // flush buffer.
inner.as_mut().poll_flush(cx)?; inner.as_mut().poll_flush(cx)?;
if !inner.write_buf.is_empty() { if !inner.write_buf.is_empty() {
// still have unfinished data. wait.
Poll::Pending Poll::Pending
} else { } else {
Pin::new(inner.project().io.as_mut().unwrap()) Pin::new(inner.project().io.as_mut().unwrap())
@ -768,61 +872,46 @@ where
} }
} }
} else { } else {
// read socket into a buf // read from io stream and fill read buffer.
let should_disconnect = let should_disconnect = inner.as_mut().read_available(cx)?;
if !inner.flags.contains(Flags::READ_DISCONNECT) {
let mut inner_p = inner.as_mut().project();
read_available(
cx,
inner_p.io.as_mut().unwrap(),
&mut inner_p.read_buf,
)?
} else {
None
};
inner.as_mut().poll_request(cx)?; inner.as_mut().poll_request(cx)?;
if let Some(true) = should_disconnect {
let inner_p = inner.as_mut().project(); // io stream should to be closed.
inner_p.flags.insert(Flags::READ_DISCONNECT); if should_disconnect {
if let Some(mut payload) = inner_p.payload.take() { let inner = inner.as_mut().project();
inner.flags.insert(Flags::READ_DISCONNECT);
if let Some(mut payload) = inner.payload.take() {
payload.feed_eof(); payload.feed_eof();
} }
}; };
loop { loop {
let inner_p = inner.as_mut().project(); // grow buffer if necessary.
let remaining = {
inner_p.write_buf.capacity() - inner_p.write_buf.len(); let inner = inner.as_mut().project();
if remaining < LW_BUFFER_SIZE { let remaining =
inner_p.write_buf.reserve(HW_BUFFER_SIZE - remaining); inner.write_buf.capacity() - inner.write_buf.len();
if remaining < LW_BUFFER_SIZE {
inner.write_buf.reserve(HW_BUFFER_SIZE - remaining);
}
} }
let result = inner.as_mut().poll_response(cx)?;
let drain = result == PollResponse::DrainWriteBuf;
// switch to upgrade handler // poll_response and populate write buffer.
if let PollResponse::Upgrade(req) = result { // drain indicate if write buffer should be emptied before next run.
let inner_p = inner.as_mut().project(); let drain = match inner.as_mut().poll_response(cx)? {
let mut parts = FramedParts::with_read_buf( PollResponse::DrainWriteBuf => true,
inner_p.io.take().unwrap(), PollResponse::DoNothing => false,
mem::take(inner_p.codec), // upgrade request and goes Upgrade variant of DispatcherState.
mem::take(inner_p.read_buf), PollResponse::Upgrade(req) => {
); let upgrade = inner.upgrade(req);
parts.write_buf = mem::take(inner_p.write_buf); self.as_mut()
let framed = Framed::from_parts(parts); .project()
let upgrade = inner_p .inner
.flow .set(DispatcherState::Upgrade(upgrade));
.borrow_mut() return self.poll(cx);
.upgrade }
.take() };
.unwrap()
.call((req, framed));
self.as_mut()
.project()
.inner
.set(DispatcherState::Upgrade(upgrade));
return self.poll(cx);
}
// we didn't get WouldBlock from write operation, // we didn't get WouldBlock from write operation,
// so data get written to kernel completely (macOS) // so data get written to kernel completely (macOS)
@ -840,28 +929,29 @@ where
return Poll::Ready(Ok(())); return Poll::Ready(Ok(()));
} }
// check if still have unsolved future in InnerDispatcher state.
let is_empty = inner.state.is_empty(); let is_empty = inner.state.is_empty();
let inner_p = inner.as_mut().project(); let inner = inner.as_mut().project();
// read half is closed and we do not processing any responses // read half is closed and we do not processing any responses
if inner_p.flags.contains(Flags::READ_DISCONNECT) && is_empty { if inner.flags.contains(Flags::READ_DISCONNECT) && is_empty {
inner_p.flags.insert(Flags::SHUTDOWN); inner.flags.insert(Flags::SHUTDOWN);
} }
// keep-alive and stream errors // keep-alive and stream errors
if is_empty && inner_p.write_buf.is_empty() { if is_empty && inner.write_buf.is_empty() {
if let Some(err) = inner_p.error.take() { if let Some(err) = inner.error.take() {
Poll::Ready(Err(err)) Poll::Ready(Err(err))
} }
// disconnect if keep-alive is not enabled // disconnect if keep-alive is not enabled
else if inner_p.flags.contains(Flags::STARTED) else if inner.flags.contains(Flags::STARTED)
&& !inner_p.flags.intersects(Flags::KEEPALIVE) && !inner.flags.intersects(Flags::KEEPALIVE)
{ {
inner_p.flags.insert(Flags::SHUTDOWN); inner.flags.insert(Flags::SHUTDOWN);
self.poll(cx) self.poll(cx)
} }
// disconnect if shutdown // disconnect if shutdown
else if inner_p.flags.contains(Flags::SHUTDOWN) { else if inner.flags.contains(Flags::SHUTDOWN) {
self.poll(cx) self.poll(cx)
} else { } else {
Poll::Pending Poll::Pending
@ -879,61 +969,6 @@ where
} }
} }
/// Returns either:
/// - `Ok(Some(true))` - data was read and done reading all data.
/// - `Ok(Some(false))` - data was read but there should be more to read.
/// - `Ok(None)` - no data was read but there should be more to read later.
/// - Unhandled Errors
fn read_available<T>(
cx: &mut Context<'_>,
io: &mut T,
buf: &mut BytesMut,
) -> Result<Option<bool>, io::Error>
where
T: AsyncRead + Unpin,
{
let mut read_some = false;
loop {
// If buf is full return but do not disconnect since
// there is more reading to be done
if buf.len() >= HW_BUFFER_SIZE {
return Ok(Some(false));
}
let remaining = buf.capacity() - buf.len();
if remaining < LW_BUFFER_SIZE {
buf.reserve(HW_BUFFER_SIZE - remaining);
}
match actix_codec::poll_read_buf(Pin::new(io), cx, buf) {
Poll::Pending => {
return if read_some { Ok(Some(false)) } else { Ok(None) };
}
Poll::Ready(Ok(n)) => {
if n == 0 {
return Ok(Some(true));
} else {
read_some = true;
}
}
Poll::Ready(Err(err)) => {
return if err.kind() == io::ErrorKind::WouldBlock {
if read_some {
Ok(Some(false))
} else {
Ok(None)
}
} else if err.kind() == io::ErrorKind::ConnectionReset && read_some {
Ok(Some(true))
} else {
Err(err)
}
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::str; use std::str;

View File

@ -1,4 +1,4 @@
use std::task::{Context, Poll}; use std::task::Poll;
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use futures_util::future::{ready, Ready}; use futures_util::future::{ready, Ready};
@ -26,11 +26,9 @@ impl Service<Request> for ExpectHandler {
type Error = Error; type Error = Error;
type Future = Ready<Result<Self::Response, Self::Error>>; type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request) -> Self::Future { fn call(&self, req: Request) -> Self::Future {
ready(Ok(req)) ready(Ok(req))
// TODO: add some way to trigger error // TODO: add some way to trigger error
// Err(error::ErrorExpectationFailed("test")) // Err(error::ErrorExpectationFailed("test"))

View File

@ -1,4 +1,3 @@
use std::cell::RefCell;
use std::future::Future; use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin; use std::pin::Pin;
@ -367,7 +366,7 @@ where
X: Service<Request>, X: Service<Request>,
U: Service<(Request, Framed<T, Codec>)>, U: Service<(Request, Framed<T, Codec>)>,
{ {
flow: Rc<RefCell<HttpFlow<S, X, U>>>, flow: Rc<HttpFlow<S, X, U>>,
on_connect_ext: Option<Rc<ConnectCallback<T>>>, on_connect_ext: Option<Rc<ConnectCallback<T>>>,
cfg: ServiceConfig, cfg: ServiceConfig,
_phantom: PhantomData<B>, _phantom: PhantomData<B>,
@ -417,9 +416,9 @@ where
type Error = DispatchError; type Error = DispatchError;
type Future = Dispatcher<T, S, B, X, U>; type Future = Dispatcher<T, S, B, X, U>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let mut flow = self.flow.borrow_mut(); let ready = self
let ready = flow .flow
.expect .expect
.poll_ready(cx) .poll_ready(cx)
.map_err(|e| { .map_err(|e| {
@ -429,7 +428,8 @@ where
})? })?
.is_ready(); .is_ready();
let ready = flow let ready = self
.flow
.service .service
.poll_ready(cx) .poll_ready(cx)
.map_err(|e| { .map_err(|e| {
@ -440,7 +440,7 @@ where
.is_ready() .is_ready()
&& ready; && ready;
let ready = if let Some(ref mut upg) = flow.upgrade { let ready = if let Some(ref upg) = self.flow.upgrade {
upg.poll_ready(cx) upg.poll_ready(cx)
.map_err(|e| { .map_err(|e| {
let e = e.into(); let e = e.into();
@ -460,7 +460,7 @@ where
} }
} }
fn call(&mut self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future { fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
let on_connect_data = let on_connect_data =
OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); OnConnectData::from_io(&io, self.on_connect_ext.as_deref());

View File

@ -1,4 +1,4 @@
use std::task::{Context, Poll}; use std::task::Poll;
use actix_codec::Framed; use actix_codec::Framed;
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
@ -28,11 +28,9 @@ impl<T> Service<(Request, Framed<T, Codec>)> for UpgradeHandler {
type Error = Error; type Error = Error;
type Future = Ready<Result<Self::Response, Self::Error>>; type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, _: (Request, Framed<T, Codec>)) -> Self::Future { fn call(&self, _: (Request, Framed<T, Codec>)) -> Self::Future {
ready(Ok(())) ready(Ok(()))
} }
} }

View File

@ -1,4 +1,3 @@
use std::cell::RefCell;
use std::future::Future; use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::net; use std::net;
@ -37,7 +36,7 @@ where
S: Service<Request>, S: Service<Request>,
B: MessageBody, B: MessageBody,
{ {
flow: Rc<RefCell<HttpFlow<S, X, U>>>, flow: Rc<HttpFlow<S, X, U>>,
connection: Connection<T, Bytes>, connection: Connection<T, Bytes>,
on_connect_data: OnConnectData, on_connect_data: OnConnectData,
config: ServiceConfig, config: ServiceConfig,
@ -56,7 +55,7 @@ where
B: MessageBody, B: MessageBody,
{ {
pub(crate) fn new( pub(crate) fn new(
services: Rc<RefCell<HttpFlow<S, X, U>>>, flow: Rc<HttpFlow<S, X, U>>,
connection: Connection<T, Bytes>, connection: Connection<T, Bytes>,
on_connect_data: OnConnectData, on_connect_data: OnConnectData,
config: ServiceConfig, config: ServiceConfig,
@ -80,7 +79,7 @@ where
}; };
Dispatcher { Dispatcher {
flow: services, flow,
config, config,
peer_addr, peer_addr,
connection, connection,
@ -138,7 +137,7 @@ where
let svc = ServiceResponse::<S::Future, S::Response, S::Error, B> { let svc = ServiceResponse::<S::Future, S::Response, S::Error, B> {
state: ServiceResponseState::ServiceCall( state: ServiceResponseState::ServiceCall(
this.flow.borrow_mut().service.call(req), this.flow.service.call(req),
Some(res), Some(res),
), ),
config: this.config.clone(), config: this.config.clone(),

View File

@ -1,4 +1,3 @@
use std::cell::RefCell;
use std::future::Future; use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin; use std::pin::Pin;
@ -249,7 +248,7 @@ pub struct H2ServiceHandler<T, S, B>
where where
S: Service<Request>, S: Service<Request>,
{ {
flow: Rc<RefCell<HttpFlow<S, (), ()>>>, flow: Rc<HttpFlow<S, (), ()>>,
cfg: ServiceConfig, cfg: ServiceConfig,
on_connect_ext: Option<Rc<ConnectCallback<T>>>, on_connect_ext: Option<Rc<ConnectCallback<T>>>,
_phantom: PhantomData<B>, _phantom: PhantomData<B>,
@ -290,15 +289,15 @@ where
type Error = DispatchError; type Error = DispatchError;
type Future = H2ServiceHandlerResponse<T, S, B>; type Future = H2ServiceHandlerResponse<T, S, B>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.flow.borrow_mut().service.poll_ready(cx).map_err(|e| { self.flow.service.poll_ready(cx).map_err(|e| {
let e = e.into(); let e = e.into();
error!("Service readiness error: {:?}", e); error!("Service readiness error: {:?}", e);
DispatchError::Service(e) DispatchError::Service(e)
}) })
} }
fn call(&mut self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future { fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
let on_connect_data = let on_connect_data =
OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
@ -321,7 +320,7 @@ where
{ {
Incoming(Dispatcher<T, S, B, (), ()>), Incoming(Dispatcher<T, S, B, (), ()>),
Handshake( Handshake(
Option<Rc<RefCell<HttpFlow<S, (), ()>>>>, Option<Rc<HttpFlow<S, (), ()>>>,
Option<ServiceConfig>, Option<ServiceConfig>,
Option<net::SocketAddr>, Option<net::SocketAddr>,
OnConnectData, OnConnectData,

View File

@ -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());
} }

View File

@ -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>)+

View File

@ -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>)+

View File

@ -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)*

View File

@ -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)
} }

View File

@ -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())

View File

@ -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()))
}
}

View File

@ -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>)+

View File

@ -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())

View File

@ -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 {

View File

@ -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]

View File

@ -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]

View File

@ -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]

View File

@ -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()),

View File

@ -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]

View File

@ -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();

View File

@ -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>);
} }

View File

@ -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]

View File

@ -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]

View File

@ -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;

View File

@ -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()?))
}
}

View File

@ -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::from_str(self.as_ref())
}
}

View File

@ -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>,
} }

View File

@ -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)
);
}
}

View File

@ -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())

View File

@ -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)
);
}
}

View File

@ -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,

View File

@ -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;

View File

@ -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)
}

View File

@ -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());
} }

View File

@ -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::*;
} }

View File

@ -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};

View File

@ -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")));
}
} }

View File

@ -1,4 +1,3 @@
use std::cell::RefCell;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
@ -440,7 +439,7 @@ where
X: Service<Request>, X: Service<Request>,
U: Service<(Request, Framed<T, h1::Codec>)>, U: Service<(Request, Framed<T, h1::Codec>)>,
{ {
flow: Rc<RefCell<HttpFlow<S, X, U>>>, flow: Rc<HttpFlow<S, X, U>>,
cfg: ServiceConfig, cfg: ServiceConfig,
on_connect_ext: Option<Rc<ConnectCallback<T>>>, on_connect_ext: Option<Rc<ConnectCallback<T>>>,
_phantom: PhantomData<B>, _phantom: PhantomData<B>,
@ -454,12 +453,12 @@ pub(super) struct HttpFlow<S, X, U> {
} }
impl<S, X, U> HttpFlow<S, X, U> { impl<S, X, U> HttpFlow<S, X, U> {
pub(super) fn new(service: S, expect: X, upgrade: Option<U>) -> Rc<RefCell<Self>> { pub(super) fn new(service: S, expect: X, upgrade: Option<U>) -> Rc<Self> {
Rc::new(RefCell::new(Self { Rc::new(Self {
service, service,
expect, expect,
upgrade, upgrade,
})) })
} }
} }
@ -509,9 +508,9 @@ where
type Error = DispatchError; type Error = DispatchError;
type Future = HttpServiceHandlerResponse<T, S, B, X, U>; type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let mut flow = self.flow.borrow_mut(); let ready = self
let ready = flow .flow
.expect .expect
.poll_ready(cx) .poll_ready(cx)
.map_err(|e| { .map_err(|e| {
@ -521,7 +520,8 @@ where
})? })?
.is_ready(); .is_ready();
let ready = flow let ready = self
.flow
.service .service
.poll_ready(cx) .poll_ready(cx)
.map_err(|e| { .map_err(|e| {
@ -532,7 +532,7 @@ where
.is_ready() .is_ready()
&& ready; && ready;
let ready = if let Some(ref mut upg) = flow.upgrade { let ready = if let Some(ref upg) = self.flow.upgrade {
upg.poll_ready(cx) upg.poll_ready(cx)
.map_err(|e| { .map_err(|e| {
let e = e.into(); let e = e.into();
@ -553,7 +553,7 @@ where
} }
fn call( fn call(
&mut self, &self,
(io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>), (io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>),
) -> Self::Future { ) -> Self::Future {
let on_connect_data = let on_connect_data =
@ -604,7 +604,7 @@ where
Option<( Option<(
Handshake<T, Bytes>, Handshake<T, Bytes>,
ServiceConfig, ServiceConfig,
Rc<RefCell<HttpFlow<S, X, U>>>, Rc<HttpFlow<S, X, U>>,
OnConnectData, OnConnectData,
Option<net::SocketAddr>, Option<net::SocketAddr>,
)>, )>,

View File

@ -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");

View File

@ -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;

View File

@ -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,

View File

@ -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());

View File

@ -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())

View File

@ -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),
) )
})) }))

View File

@ -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())

View File

@ -21,7 +21,7 @@ impl<T> WsService<T> {
WsService(Arc::new(Mutex::new((PhantomData, Cell::new(false))))) WsService(Arc::new(Mutex::new((PhantomData, Cell::new(false)))))
} }
fn set_polled(&mut self) { fn set_polled(&self) {
*self.0.lock().unwrap().1.get_mut() = true; *self.0.lock().unwrap().1.get_mut() = true;
} }
@ -44,15 +44,12 @@ where
type Error = Error; type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<(), Error>>>>; type Future = Pin<Box<dyn Future<Output = Result<(), Error>>>>;
fn poll_ready(&mut self, _ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, _ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.set_polled(); self.set_polled();
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} }
fn call( fn call(&self, (req, mut framed): (Request, Framed<T, h1::Codec>)) -> Self::Future {
&mut self,
(req, mut framed): (Request, Framed<T, h1::Codec>),
) -> Self::Future {
let fut = async move { let fut = async move {
let res = ws::handshake(req.head()).unwrap().message_body(()); let res = ws::handshake(req.head()).unwrap().message_body(());

View File

@ -28,5 +28,5 @@ mime = "0.3"
twoway = "0.2" twoway = "0.2"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.0.0-beta.2" actix-rt = "2"
actix-http = "3.0.0-beta.1" actix-http = "3.0.0-beta.1"

View File

@ -16,7 +16,7 @@ name = "actix_web_actors"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix = "0.11.0-beta.1" actix = { version = "0.11.0-beta.1", default-features = false }
actix-codec = "0.4.0-beta.1" actix-codec = "0.4.0-beta.1"
actix-http = "3.0.0-beta.1" actix-http = "3.0.0-beta.1"
actix-web = { version = "4.0.0-beta.1", default-features = false } actix-web = { version = "4.0.0-beta.1", default-features = false }
@ -28,6 +28,6 @@ pin-project = "1.0.0"
tokio = { version = "1", features = ["sync"] } tokio = { version = "1", features = ["sync"] }
[dev-dependencies] [dev-dependencies]
actix-rt = "2.0.0-beta.2" actix-rt = "2"
env_logger = "0.7" env_logger = "0.7"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }

View File

@ -233,14 +233,13 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_default_resource() { async fn test_default_resource() {
let mut srv = let srv = init_service(App::new().service(web::resource("/test").to(|| {
init_service(App::new().service(web::resource("/test").to(|| { HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 }))
HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 })) })))
}))) .await;
.await;
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await; let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let body = read_body(resp).await; let body = read_body(resp).await;

View File

@ -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"];

View File

@ -19,7 +19,7 @@ syn = { version = "1", features = ["full", "parsing"] }
proc-macro2 = "1" proc-macro2 = "1"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.0.0-beta.2" actix-rt = "2"
actix-web = "4.0.0-beta.1" actix-web = "4.0.0-beta.1"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }
trybuild = "1" trybuild = "1"

View File

@ -175,7 +175,6 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
let vis = &input.vis; let vis = &input.vis;
let sig = &mut input.sig; let sig = &mut input.sig;
let body = &input.block; let body = &input.block;
let name = &sig.ident;
if sig.asyncness.is_none() { if sig.asyncness.is_none() {
return syn::Error::new_spanned(sig.fn_token, "only async fn is supported") return syn::Error::new_spanned(sig.fn_token, "only async fn is supported")
@ -188,7 +187,7 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
(quote! { (quote! {
#(#attrs)* #(#attrs)*
#vis #sig { #vis #sig {
actix_web::rt::System::new(stringify!(#name)) actix_web::rt::System::new()
.block_on(async move { #body }) .block_on(async move { #body })
} }
}) })

View File

@ -1,5 +1,4 @@
use std::future::Future; use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
@ -8,7 +7,7 @@ use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder};
use actix_web_codegen::{ use actix_web_codegen::{
connect, delete, get, head, options, patch, post, put, route, trace, connect, delete, get, head, options, patch, post, put, route, trace,
}; };
use futures_util::future; use futures_util::future::{self, LocalBoxFuture};
// Make sure that we can name function as 'config' // Make sure that we can name function as 'config'
#[get("/config")] #[get("/config")]
@ -117,14 +116,13 @@ where
{ {
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = Error; type Error = Error;
#[allow(clippy::type_complexity)] type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx) self.service.poll_ready(cx)
} }
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&self, req: ServiceRequest) -> Self::Future {
let fut = self.service.call(req); let fut = self.service.call(req);
Box::pin(async move { Box::pin(async move {

View File

@ -1,6 +1,21 @@
# 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]
### Changed
* Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905]
### 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
[#1905]: https://github.com/actix/actix-web/pull/1905
## 3.0.0-beta.1 - 2021-01-07 ## 3.0.0-beta.1 - 2021-01-07

View File

@ -40,7 +40,7 @@ compress = ["actix-http/compress"]
actix-codec = "0.4.0-beta.1" actix-codec = "0.4.0-beta.1"
actix-service = "2.0.0-beta.3" actix-service = "2.0.0-beta.3"
actix-http = "3.0.0-beta.1" actix-http = "3.0.0-beta.1"
actix-rt = "2.0.0-beta.2" actix-rt = "2"
base64 = "0.13" base64 = "0.13"
bytes = "1" bytes = "1"
@ -58,17 +58,16 @@ open-ssl = { version = "0.10", package = "openssl", optional = true }
rust-tls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } rust-tls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] }
[dev-dependencies] [dev-dependencies]
# TODO: actix is temporary added as dev dep for actix-macro reason.
# Can be removed when it does not impact tests.
actix = "0.11.0-beta.1"
actix-web = { version = "4.0.0-beta.1", features = ["openssl"] } actix-web = { version = "4.0.0-beta.1", features = ["openssl"] }
actix-http = { version = "3.0.0-beta.1", features = ["openssl"] } actix-http = { version = "3.0.0-beta.1", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } 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"

View File

@ -21,7 +21,7 @@ use actix_rt::System;
use awc::Client; use awc::Client;
fn main() { fn main() {
System::new("test").block_on(async { System::new().block_on(async {
let client = Client::default(); let client = Client::default();
let res = client let res = client

View File

@ -1,4 +1,3 @@
use std::cell::RefCell;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt; use std::fmt;
use std::rc::Rc; use std::rc::Rc;
@ -24,7 +23,7 @@ pub struct ClientBuilder {
conn_window_size: Option<u32>, conn_window_size: Option<u32>,
headers: HeaderMap, headers: HeaderMap,
timeout: Option<Duration>, timeout: Option<Duration>,
connector: Option<RefCell<Box<dyn Connect>>>, connector: Option<Box<dyn Connect>>,
} }
impl Default for ClientBuilder { impl Default for ClientBuilder {
@ -56,7 +55,7 @@ impl ClientBuilder {
<T::Response as Connection>::Future: 'static, <T::Response as Connection>::Future: 'static,
T::Future: 'static, T::Future: 'static,
{ {
self.connector = Some(RefCell::new(Box::new(ConnectorWrapper(connector)))); self.connector = Some(Box::new(ConnectorWrapper(connector)));
self self
} }
@ -133,7 +132,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);
} }
@ -182,9 +181,7 @@ impl ClientBuilder {
if let Some(val) = self.stream_window_size { if let Some(val) = self.stream_window_size {
connector = connector.initial_window_size(val) connector = connector.initial_window_size(val)
}; };
RefCell::new( Box::new(ConnectorWrapper(connector.finish())) as _
Box::new(ConnectorWrapper(connector.finish())) as Box<dyn Connect>
)
}; };
let config = ClientConfig { let config = ClientConfig {
headers: self.headers, headers: self.headers,

View File

@ -20,14 +20,14 @@ pub(crate) struct ConnectorWrapper<T>(pub T);
pub(crate) trait Connect { pub(crate) trait Connect {
fn send_request( fn send_request(
&mut self, &self,
head: RequestHead, head: RequestHead,
body: Body, body: Body,
addr: Option<net::SocketAddr>, addr: Option<net::SocketAddr>,
) -> Pin<Box<dyn Future<Output = Result<ClientResponse, SendRequestError>>>>; ) -> Pin<Box<dyn Future<Output = Result<ClientResponse, SendRequestError>>>>;
fn send_request_extra( fn send_request_extra(
&mut self, &self,
head: Rc<RequestHead>, head: Rc<RequestHead>,
extra_headers: Option<HeaderMap>, extra_headers: Option<HeaderMap>,
body: Body, body: Body,
@ -36,7 +36,7 @@ pub(crate) trait Connect {
/// Send request, returns Response and Framed /// Send request, returns Response and Framed
fn open_tunnel( fn open_tunnel(
&mut self, &self,
head: RequestHead, head: RequestHead,
addr: Option<net::SocketAddr>, addr: Option<net::SocketAddr>,
) -> Pin< ) -> Pin<
@ -52,7 +52,7 @@ pub(crate) trait Connect {
/// Send request and extra headers, returns Response and Framed /// Send request and extra headers, returns Response and Framed
fn open_tunnel_extra( fn open_tunnel_extra(
&mut self, &self,
head: Rc<RequestHead>, head: Rc<RequestHead>,
extra_headers: Option<HeaderMap>, extra_headers: Option<HeaderMap>,
addr: Option<net::SocketAddr>, addr: Option<net::SocketAddr>,
@ -78,7 +78,7 @@ where
T::Future: 'static, T::Future: 'static,
{ {
fn send_request( fn send_request(
&mut self, &self,
head: RequestHead, head: RequestHead,
body: Body, body: Body,
addr: Option<net::SocketAddr>, addr: Option<net::SocketAddr>,
@ -101,7 +101,7 @@ where
} }
fn send_request_extra( fn send_request_extra(
&mut self, &self,
head: Rc<RequestHead>, head: Rc<RequestHead>,
extra_headers: Option<HeaderMap>, extra_headers: Option<HeaderMap>,
body: Body, body: Body,
@ -126,7 +126,7 @@ where
} }
fn open_tunnel( fn open_tunnel(
&mut self, &self,
head: RequestHead, head: RequestHead,
addr: Option<net::SocketAddr>, addr: Option<net::SocketAddr>,
) -> Pin< ) -> Pin<
@ -158,7 +158,7 @@ where
} }
fn open_tunnel_extra( fn open_tunnel_extra(
&mut self, &self,
head: Rc<RequestHead>, head: Rc<RequestHead>,
extra_headers: Option<HeaderMap>, extra_headers: Option<HeaderMap>,
addr: Option<net::SocketAddr>, addr: Option<net::SocketAddr>,

View File

@ -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()),
}, },

View File

@ -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?;
//! //!
@ -93,7 +93,6 @@
#![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
use std::cell::RefCell;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
@ -134,7 +133,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
/// ///
@ -145,7 +144,7 @@ use self::connect::{Connect, ConnectorWrapper};
pub struct Client(Rc<ClientConfig>); pub struct Client(Rc<ClientConfig>);
pub(crate) struct ClientConfig { pub(crate) struct ClientConfig {
pub(crate) connector: RefCell<Box<dyn Connect>>, pub(crate) connector: Box<dyn Connect>,
pub(crate) headers: HeaderMap, pub(crate) headers: HeaderMap,
pub(crate) timeout: Option<Duration>, pub(crate) timeout: Option<Duration>,
} }
@ -153,9 +152,7 @@ pub(crate) struct ClientConfig {
impl Default for Client { impl Default for Client {
fn default() -> Self { fn default() -> Self {
Client(Rc::new(ClientConfig { Client(Rc::new(ClientConfig {
connector: RefCell::new(Box::new(ConnectorWrapper( connector: Box::new(ConnectorWrapper(Connector::new().finish())),
Connector::new().finish(),
))),
headers: HeaderMap::new(), headers: HeaderMap::new(),
timeout: Some(Duration::from_secs(5)), timeout: Some(Duration::from_secs(5)),
})) }))
@ -182,8 +179,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 +195,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
} }

View File

@ -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

View File

@ -183,15 +183,13 @@ impl RequestSender {
where where
B: Into<Body>, B: Into<Body>,
{ {
let mut connector = config.connector.borrow_mut();
let fut = match self { let fut = match self {
RequestSender::Owned(head) => { RequestSender::Owned(head) => {
connector.send_request(head, body.into(), addr) config.connector.send_request(head, body.into(), addr)
}
RequestSender::Rc(head, extra_headers) => {
connector.send_request_extra(head, extra_headers, body.into(), addr)
} }
RequestSender::Rc(head, extra_headers) => config
.connector
.send_request_extra(head, extra_headers, body.into(), addr),
}; };
SendClientRequest::new(fut, response_decompress, timeout.or(config.timeout)) SendClientRequest::new(fut, response_decompress, timeout.or(config.timeout))
@ -296,7 +294,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 +304,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)

View File

@ -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;
} }

View File

@ -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);
} }
@ -325,11 +325,7 @@ impl WebsocketsRequest {
let max_size = self.max_size; let max_size = self.max_size;
let server_mode = self.server_mode; let server_mode = self.server_mode;
let fut = self let fut = self.config.connector.open_tunnel(head, self.addr);
.config
.connector
.borrow_mut()
.open_tunnel(head, self.addr);
// set request timeout // set request timeout
let (head, framed) = if let Some(to) = self.config.timeout { let (head, framed) = if let Some(to) = self.config.timeout {

View File

@ -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 || { let lst = std::net::TcpListener::bind(addr).unwrap();
// let lst = net::TcpListener::bind(addr).unwrap();
// for stream in lst.incoming() { std::thread::spawn(move || {
// let mut stream = stream.unwrap(); for stream in lst.incoming() {
// let mut b = [0; 1000]; let mut stream = stream.unwrap();
// let _ = stream.read(&mut b).unwrap(); let mut b = [0; 1000];
// let _ = stream let _ = stream.read(&mut b).unwrap();
// .write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!"); let _ = stream.write_all(
// } 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() {

View File

@ -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())

View File

@ -68,7 +68,7 @@ impl<T: Responder> Responder for OptionResponder<T> {
} }
fn future_responder(c: &mut Criterion) { fn future_responder(c: &mut Criterion) {
let rt = actix_rt::System::new("test"); let rt = actix_rt::System::new();
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
c.bench_function("future_responder", move |b| { c.bench_function("future_responder", move |b| {
@ -91,7 +91,7 @@ fn future_responder(c: &mut Criterion) {
} }
fn responder(c: &mut Criterion) { fn responder(c: &mut Criterion) {
let rt = actix_rt::System::new("test"); let rt = actix_rt::System::new();
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
c.bench_function("responder", move |b| { c.bench_function("responder", move |b| {
b.iter_custom(|_| { b.iter_custom(|_| {

View File

@ -29,7 +29,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
fn bench_async_burst(c: &mut Criterion) { fn bench_async_burst(c: &mut Criterion) {
// We are using System here, since Runtime requires preinitialized tokio // We are using System here, since Runtime requires preinitialized tokio
// Maybe add to actix_rt docs // Maybe add to actix_rt docs
let rt = actix_rt::System::new("test"); let rt = actix_rt::System::new();
let srv = rt.block_on(async { let srv = rt.block_on(async {
test::start(|| { test::start(|| {

View File

@ -25,7 +25,7 @@ pub fn bench_async_service<S>(c: &mut Criterion, srv: S, name: &str)
where where
S: Service<ServiceRequest, Response = ServiceResponse, Error = Error> + 'static, S: Service<ServiceRequest, Response = ServiceResponse, Error = Error> + 'static,
{ {
let rt = actix_rt::System::new("test"); let rt = actix_rt::System::new();
let srv = Rc::new(RefCell::new(srv)); let srv = Rc::new(RefCell::new(srv));
let req = TestRequest::default().to_srv_request(); let req = TestRequest::default().to_srv_request();
@ -67,7 +67,7 @@ async fn index(req: ServiceRequest) -> Result<ServiceResponse, Error> {
// Sample results on MacBook Pro '14 // Sample results on MacBook Pro '14
// time: [2.0724 us 2.1345 us 2.2074 us] // time: [2.0724 us 2.1345 us 2.2074 us]
fn async_web_service(c: &mut Criterion) { fn async_web_service(c: &mut Criterion) {
let rt = actix_rt::System::new("test"); let rt = actix_rt::System::new();
let srv = Rc::new(RefCell::new(rt.block_on(init_service( let srv = Rc::new(RefCell::new(rt.block_on(init_service(
App::new().service(web::service("/").finish(index)), App::new().service(web::service("/").finish(index)),
)))); ))));

View File

@ -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?;

View File

@ -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,
@ -431,12 +422,11 @@ where
> >
where where
B1: MessageBody, B1: MessageBody,
F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, F: Fn(ServiceRequest, &T::Service) -> R + Clone,
R: Future<Output = Result<ServiceResponse<B1>, Error>>, R: Future<Output = Result<ServiceResponse<B1>, Error>>,
{ {
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)),
@ -491,7 +480,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_default_resource() { async fn test_default_resource() {
let mut srv = init_service( let srv = init_service(
App::new().service(web::resource("/test").to(HttpResponse::Ok)), App::new().service(web::resource("/test").to(HttpResponse::Ok)),
) )
.await; .await;
@ -503,7 +492,7 @@ mod tests {
let resp = srv.call(req).await.unwrap(); let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut srv = init_service( let srv = init_service(
App::new() App::new()
.service(web::resource("/test").to(HttpResponse::Ok)) .service(web::resource("/test").to(HttpResponse::Ok))
.service( .service(
@ -536,7 +525,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_data_factory() { async fn test_data_factory() {
let mut srv = let srv =
init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service( init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()), web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
)) ))
@ -545,7 +534,7 @@ mod tests {
let resp = srv.call(req).await.unwrap(); let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let mut srv = let srv =
init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service( init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()), web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
)) ))
@ -568,7 +557,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_extension() { async fn test_extension() {
let mut srv = init_service(App::new().app_data(10usize).service( let srv = init_service(App::new().app_data(10usize).service(
web::resource("/").to(|req: HttpRequest| { web::resource("/").to(|req: HttpRequest| {
assert_eq!(*req.app_data::<usize>().unwrap(), 10); assert_eq!(*req.app_data::<usize>().unwrap(), 10);
HttpResponse::Ok() HttpResponse::Ok()
@ -582,7 +571,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_wrap() { async fn test_wrap() {
let mut srv = init_service( let srv = init_service(
App::new() App::new()
.wrap( .wrap(
DefaultHeaders::new() DefaultHeaders::new()
@ -592,7 +581,7 @@ mod tests {
) )
.await; .await;
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await; let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@ -602,7 +591,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_router_wrap() { async fn test_router_wrap() {
let mut srv = init_service( let srv = init_service(
App::new() App::new()
.route("/test", web::get().to(HttpResponse::Ok)) .route("/test", web::get().to(HttpResponse::Ok))
.wrap( .wrap(
@ -612,7 +601,7 @@ mod tests {
) )
.await; .await;
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await; let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@ -622,7 +611,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_wrap_fn() { async fn test_wrap_fn() {
let mut srv = init_service( let srv = init_service(
App::new() App::new()
.wrap_fn(|req, srv| { .wrap_fn(|req, srv| {
let fut = srv.call(req); let fut = srv.call(req);
@ -639,7 +628,7 @@ mod tests {
) )
.await; .await;
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await; let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@ -649,7 +638,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_router_wrap_fn() { async fn test_router_wrap_fn() {
let mut srv = init_service( let srv = init_service(
App::new() App::new()
.route("/test", web::get().to(HttpResponse::Ok)) .route("/test", web::get().to(HttpResponse::Ok))
.wrap_fn(|req, srv| { .wrap_fn(|req, srv| {
@ -666,7 +655,7 @@ mod tests {
) )
.await; .await;
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await; let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@ -676,7 +665,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_external_resource() { async fn test_external_resource() {
let mut srv = init_service( let srv = init_service(
App::new() App::new()
.external_resource("youtube", "https://youtube.com/watch/{video_id}") .external_resource("youtube", "https://youtube.com/watch/{video_id}")
.route( .route(
@ -690,7 +679,7 @@ mod tests {
) )
.await; .await;
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req).await; let resp = call_service(&srv, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let body = read_body(resp).await; let body = read_body(resp).await;
assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));

View File

@ -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,
@ -203,7 +196,7 @@ where
actix_service::forward_ready!(service); actix_service::forward_ready!(service);
fn call(&mut self, req: Request) -> Self::Future { fn call(&self, req: Request) -> Self::Future {
let (head, payload) = req.into_parts(); let (head, payload) = req.into_parts();
let req = if let Some(mut req) = self.app_state.pool().pop() { let req = if let Some(mut req) = self.app_state.pool().pop() {
@ -294,8 +287,8 @@ impl Service<ServiceRequest> for AppRouting {
actix_service::always_ready!(); actix_service::always_ready!();
fn call(&mut self, mut req: ServiceRequest) -> Self::Future { fn call(&self, mut req: ServiceRequest) -> Self::Future {
let res = self.router.recognize_mut_checked(&mut req, |req, guards| { let res = self.router.recognize_checked(&mut req, |req, guards| {
if let Some(ref guards) = guards { if let Some(ref guards) = guards {
for f in guards { for f in guards {
if !f.check(req.head()) { if !f.check(req.head()) {
@ -361,7 +354,7 @@ mod tests {
let data = Arc::new(AtomicBool::new(false)); let data = Arc::new(AtomicBool::new(false));
{ {
let mut app = init_service( let app = init_service(
App::new() App::new()
.data(DropData(data.clone())) .data(DropData(data.clone()))
.service(web::resource("/test").to(HttpResponse::Ok)), .service(web::resource("/test").to(HttpResponse::Ok)),

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