From ee1014844487245efd79fbbe68e388751ae5b2b6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 17 Jan 2021 05:19:32 +0000 Subject: [PATCH] revive commented out tests (#1912) --- actix-http/CHANGES.md | 2 + .../src/header/common/content_encoding.rs | 44 +++- awc/Cargo.toml | 2 + awc/tests/test_client.rs | 239 ++++++++---------- awc/tests/test_rustls_client.rs | 77 +++--- src/middleware/compress.rs | 3 +- tests/test_server.rs | 98 ++++--- 7 files changed, 243 insertions(+), 222 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 8b0255286..c535b9866 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,6 +6,7 @@ * `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 @@ -24,6 +25,7 @@ [#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 [#1904]: https://github.com/actix/actix-web/pull/1904 +[#1912]: https://github.com/actix/actix-web/pull/1912 ## 3.0.0-beta.1 - 2021-01-07 diff --git a/actix-http/src/header/common/content_encoding.rs b/actix-http/src/header/common/content_encoding.rs index 49f0ff78f..b93d66101 100644 --- a/actix-http/src/header/common/content_encoding.rs +++ b/actix-http/src/header/common/content_encoding.rs @@ -1,3 +1,13 @@ +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 { @@ -47,6 +57,20 @@ impl ContentEncoding { } } +impl Default for ContentEncoding { + fn default() -> Self { + Self::Identity + } +} + +impl FromStr for ContentEncoding { + type Err = Infallible; + + fn from_str(val: &str) -> Result { + Ok(Self::from(val)) + } +} + impl From<&str> for ContentEncoding { fn from(val: &str) -> ContentEncoding { let val = val.trim(); @@ -58,7 +82,25 @@ impl From<&str> for ContentEncoding { } else if val.eq_ignore_ascii_case("deflate") { ContentEncoding::Deflate } else { - ContentEncoding::Identity + ContentEncoding::default() } } } + +impl IntoHeaderValue for ContentEncoding { + type Error = InvalidHeaderValue; + + fn try_into_value(self) -> Result { + Ok(HeaderValue::from_static(self.as_str())) + } +} + +impl Header for ContentEncoding { + fn name() -> HeaderName { + header::CONTENT_ENCODING + } + + fn parse(msg: &T) -> Result { + from_one_raw_str(msg.headers().get(Self::name())) + } +} diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 0dbf80d33..90f33c9ba 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -67,8 +67,10 @@ actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.2" actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] } + brotli2 = "0.3.2" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } env_logger = "0.7" +rcgen = "0.8" webpki = "0.21" diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 3f5bf2958..88987e639 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -9,17 +9,20 @@ use bytes::Bytes; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; -use futures_util::future::ok; +use futures_util::{future::ok, stream}; use rand::Rng; -use actix_http::HttpService; +use actix_http::{ + http::{self, StatusCode}, + HttpService, +}; use actix_http_test::test_server; 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::{ - 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; @@ -557,117 +560,94 @@ async fn test_client_brotli_encoding_large_random() { assert_eq!(bytes, Bytes::from(data)); } -// TODO: why is test ignored -// #[actix_rt::test] -// async fn test_client_deflate_encoding() { -// let srv = test::TestServer::start(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Br) -// .body(bytes)) -// }) -// .responder() -// }) -// }); +#[actix_rt::test] +async fn test_client_deflate_encoding() { + let srv = test::start(|| { + App::new().default_service(web::to(|body: Bytes| { + HttpResponse::Ok() + .encoding(http::ContentEncoding::Br) + .body(body) + })) + }); -// // client request -// let request = srv -// .post() -// .content_encoding(http::ContentEncoding::Deflate) -// .body(STR) -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + let req = srv.post("/").send_body(STR); -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } + let mut res = req.await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); -// TODO: why is test ignored -// #[actix_rt::test] -// async fn test_client_deflate_encoding_large_random() { -// let data = rand::thread_rng() -// .sample_iter(&rand::distributions::Alphanumeric) -// .take(70_000) -// .collect::(); + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} -// let srv = test::TestServer::start(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Br) -// .body(bytes)) -// }) -// .responder() -// }) -// }); +#[actix_rt::test] +async fn test_client_deflate_encoding_large_random() { + let data = rand::thread_rng() + .sample_iter(rand::distributions::Alphanumeric) + .map(char::from) + .take(70_000) + .collect::(); -// // client request -// let request = srv -// .post() -// .content_encoding(http::ContentEncoding::Deflate) -// .body(data.clone()) -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + let srv = test::start(|| { + App::new().default_service(web::to(|body: Bytes| { + HttpResponse::Ok() + .encoding(http::ContentEncoding::Br) + .body(body) + })) + }); -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from(data)); -// } + let req = srv.post("/").send_body(data.clone()); -// TODO: why is test ignored -// #[actix_rt::test] -// async fn test_client_streaming_explicit() { -// 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 mut res = req.await.unwrap(); + let bytes = res.body().await.unwrap(); -// 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(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); +#[actix_rt::test] +async fn test_client_streaming_explicit() { + let srv = test::start(|| { + App::new().default_service(web::to(|body: web::Payload| { + HttpResponse::Ok() + .encoding(http::ContentEncoding::Identity) + .streaming(body) + })) + }); -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } + let body = stream::once(async { + Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) + }); + let req = srv.post("/").send_stream(Box::pin(body)); -// TODO: why is test ignored -// #[actix_rt::test] -// async fn test_body_streaming_implicit() { -// 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 mut res = req.await.unwrap(); + assert!(res.status().is_success()); -// let request = srv.get("/").finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } +#[actix_rt::test] +async fn test_body_streaming_implicit() { + 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] async fn test_client_cookie_handling() { @@ -738,36 +718,35 @@ async fn test_client_cookie_handling() { assert_eq!(c2, cookie2); } -// TODO: why is test ignored -// #[actix_rt::test] -// fn client_read_until_eof() { -// let addr = test::TestServer::unused_addr(); +#[actix_rt::test] +async fn client_unread_response() { + let addr = test::unused_addr(); -// thread::spawn(move || { -// let lst = net::TcpListener::bind(addr).unwrap(); + std::thread::spawn(move || { + let lst = std::net::TcpListener::bind(addr).unwrap(); -// for stream in lst.incoming() { -// let mut stream = stream.unwrap(); -// let mut b = [0; 1000]; -// let _ = stream.read(&mut b).unwrap(); -// let _ = stream -// .write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!"); -// } -// }); + for stream in lst.incoming() { + let mut stream = stream.unwrap(); + let mut b = [0; 1000]; + let _ = stream.read(&mut b).unwrap(); + let _ = stream.write_all( + b"HTTP/1.1 200 OK\r\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 -// let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) -// .finish() -// .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!")); -// } + // awc does not read all bytes unless content-length is specified + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"")); +} #[actix_rt::test] async fn client_basic_auth() { diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 2f5ffeca2..2da3d9696 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -1,58 +1,57 @@ #![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_test::test_server; use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; -use actix_web::http::Version; -use actix_web::{dev::AppConfig, web, App, HttpResponse}; +use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse}; use futures_util::future::ok; -use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslVerifyMode}; -use rust_tls::ClientConfig; +use rustls::internal::pemfile::{certs, pkcs8_private_keys}; +use rustls::{ClientConfig, NoClientAuth, ServerConfig}; -#[allow(unused)] -fn ssl_acceptor() -> SslAcceptor { - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder.set_verify_callback(SslVerifyMode::NONE, |_, _| true); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(open_ssl::ssl::AlpnError::NOACK) - } - }); - builder.set_alpn_protos(b"\x02h2").unwrap(); - builder.build() +fn tls_config() -> ServerConfig { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); + let cert_file = cert.serialize_pem().unwrap(); + let key_file = cert.serialize_private_key_pem(); + + let mut config = ServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(cert_file.as_bytes()); + let key_file = &mut BufReader::new(key_file.as_bytes()); + + let cert_chain = certs(cert_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + + config } mod danger { - pub struct NoCertificateVerification {} + pub struct NoCertificateVerification; - impl rust_tls::ServerCertVerifier for NoCertificateVerification { + impl rustls::ServerCertVerifier for NoCertificateVerification { fn verify_server_cert( &self, - _roots: &rust_tls::RootCertStore, - _presented_certs: &[rust_tls::Certificate], + _roots: &rustls::RootCertStore, + _presented_certs: &[rustls::Certificate], _dns_name: webpki::DNSNameRef<'_>, _ocsp: &[u8], - ) -> Result { - Ok(rust_tls::ServerCertVerified::assertion()) + ) -> Result { + Ok(rustls::ServerCertVerified::assertion()) } } } -// TODO: why is test ignored -// #[actix_rt::test] -async fn _test_connection_reuse_h2() { +#[actix_rt::test] +async fn test_connection_reuse_h2() { let num = Arc::new(AtomicUsize::new(0)); let num2 = num.clone(); @@ -69,19 +68,19 @@ async fn _test_connection_reuse_h2() { .service(web::resource("/").route(web::to(HttpResponse::Ok))), |_| AppConfig::default(), )) - .openssl(ssl_acceptor()) + .rustls(tls_config()) .map_err(|_| ()), ) }) .await; - // disable ssl verification + // disable TLS verification let mut config = ClientConfig::new(); let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; config.set_protocols(&protos); config .dangerous() - .set_certificate_verifier(Arc::new(danger::NoCertificateVerification {})); + .set_certificate_verifier(Arc::new(danger::NoCertificateVerification)); let client = awc::Client::builder() .connector(awc::Connector::new().rustls(Arc::new(config)).finish()) diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 376719ab2..1f05b7ae3 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -16,6 +16,7 @@ use actix_http::{ Error, }; use actix_service::{Service, Transform}; +use futures_core::ready; use futures_util::future::{ok, Ready}; use pin_project::pin_project; @@ -131,7 +132,7 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); - match futures_util::ready!(this.fut.poll(cx)) { + match ready!(this.fut.poll(cx)) { Ok(resp) => { let enc = if let Some(enc) = resp.response().get_encoding() { enc diff --git a/tests/test_server.rs b/tests/test_server.rs index 7756d44d2..0f51a2fdb 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -800,62 +800,58 @@ async fn test_reading_deflate_encoding_large_random_rustls() { assert_eq!(bytes, Bytes::from(data)); } -// TODO: why is test ignored -// #[test] -// fn test_server_cookies() { -// use actix_web::http; +#[actix_rt::test] +async fn test_server_cookies() { + use actix_web::{http, HttpMessage}; -// let srv = test::TestServer::with_factory(|| { -// App::new().resource("/", |r| { -// r.f(|_| { -// HttpResponse::Ok() -// .cookie( -// http::CookieBuilder::new("first", "first_value") -// .http_only(true) -// .finish(), -// ) -// .cookie(http::Cookie::new("second", "first_value")) -// .cookie(http::Cookie::new("second", "second_value")) -// .finish() -// }) -// }) -// }); + let srv = test::start(|| { + App::new().default_service(web::to(|| { + HttpResponse::Ok() + .cookie( + http::CookieBuilder::new("first", "first_value") + .http_only(true) + .finish(), + ) + .cookie(http::Cookie::new("second", "first_value")) + .cookie(http::Cookie::new("second", "second_value")) + .finish() + })) + }); -// let first_cookie = http::CookieBuilder::new("first", "first_value") -// .http_only(true) -// .finish(); -// let second_cookie = http::Cookie::new("second", "second_value"); + let first_cookie = http::CookieBuilder::new("first", "first_value") + .http_only(true) + .finish(); + let second_cookie = http::Cookie::new("second", "second_value"); -// let request = srv.get("/").finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + let req = srv.get("/"); + let res = req.send().await.unwrap(); + assert!(res.status().is_success()); -// let cookies = response.cookies().expect("To have cookies"); -// assert_eq!(cookies.len(), 2); -// if cookies[0] == first_cookie { -// assert_eq!(cookies[1], second_cookie); -// } else { -// assert_eq!(cookies[0], second_cookie); -// assert_eq!(cookies[1], first_cookie); -// } + let cookies = res.cookies().expect("To have cookies"); + assert_eq!(cookies.len(), 2); + if cookies[0] == first_cookie { + assert_eq!(cookies[1], second_cookie); + } else { + assert_eq!(cookies[0], second_cookie); + assert_eq!(cookies[1], first_cookie); + } -// let first_cookie = first_cookie.to_string(); -// let second_cookie = second_cookie.to_string(); -// //Check that we have exactly two instances of raw cookie headers -// let cookies = response -// .headers() -// .get_all(http::header::SET_COOKIE) -// .iter() -// .map(|header| header.to_str().expect("To str").to_string()) -// .collect::>(); -// assert_eq!(cookies.len(), 2); -// if cookies[0] == first_cookie { -// assert_eq!(cookies[1], second_cookie); -// } else { -// assert_eq!(cookies[0], second_cookie); -// assert_eq!(cookies[1], first_cookie); -// } -// } + let first_cookie = first_cookie.to_string(); + let second_cookie = second_cookie.to_string(); + // Check that we have exactly two instances of raw cookie headers + let cookies = res + .headers() + .get_all(http::header::SET_COOKIE) + .map(|header| header.to_str().expect("To str").to_string()) + .collect::>(); + assert_eq!(cookies.len(), 2); + if cookies[0] == first_cookie { + assert_eq!(cookies[1], second_cookie); + } else { + assert_eq!(cookies[0], second_cookie); + assert_eq!(cookies[1], first_cookie); + } +} #[actix_rt::test] async fn test_slow_request() {