diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index a4bc6b2bb..7f55a0bf4 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -21,16 +21,15 @@ use http::Method; use socket2::{Domain, Protocol, Socket, Type}; use tokio::sync::mpsc; -/// Start test server +/// Start test server. /// -/// `TestServer` is very simple test server that simplify process of writing -/// integration tests cases for actix web applications. +/// `TestServer` is very simple test server that simplify process of writing integration tests cases +/// for HTTP applications. /// /// # Examples -/// -/// ``` +/// ```no_run /// use actix_http::HttpService; -/// use actix_http_test::TestServer; +/// use actix_http_test::test_server; /// use actix_web::{web, App, HttpResponse, Error}; /// /// async fn my_handler() -> Result { @@ -39,10 +38,9 @@ use tokio::sync::mpsc; /// /// #[actix_web::test] /// async fn test_example() { -/// let mut srv = TestServer::start( -/// || HttpService::new( -/// App::new().service( -/// web::resource("/").to(my_handler)) +/// let mut srv = TestServer::start(|| +/// HttpService::new( +/// App::new().service(web::resource("/").to(my_handler)) /// ) /// ); /// diff --git a/awc/Cargo.toml b/awc/Cargo.toml index c40621597..1dbb0ec19 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -52,6 +52,11 @@ trust-dns = ["trust-dns-resolver"] # Don't rely on these whatsoever. They may disappear at anytime. __compress = [] +# Enable dangerous feature for testing and local network usage: +# - HTTP/2 over TCP(No Tls). +# DO NOT enable this over any internet use case. +dangerous-h2c = [] + [dependencies] actix-codec = "0.4.1" actix-service = "2.0.0" diff --git a/awc/src/client/connector.rs b/awc/src/client/connector.rs index 8a162c4f8..54778d31e 100644 --- a/awc/src/client/connector.rs +++ b/awc/src/client/connector.rs @@ -67,9 +67,9 @@ impl Connector<()> { > + Clone, > { Connector { - ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]), connector: new_connector(resolver::resolver()), config: ConnectorConfig::default(), + ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]), } } @@ -189,7 +189,7 @@ where http::Version::HTTP_11 => vec![b"http/1.1".to_vec()], http::Version::HTTP_2 => vec![b"h2".to_vec(), b"http/1.1".to_vec()], _ => { - unimplemented!("actix-http:client: supported versions http/1.1, http/2") + unimplemented!("actix-http client only supports versions http/1.1 & http/2") } }; self.ssl = Connector::build_ssl(versions); @@ -279,7 +279,63 @@ where }; let tls_service = match self.ssl { - SslConnector::None => None, + SslConnector::None => { + #[cfg(not(feature = "dangerous-h2c"))] + { + None + } + #[cfg(feature = "dangerous-h2c")] + { + use std::{ + future::{ready, Ready}, + io, + }; + + use actix_tls::connect::Connection; + + impl IntoConnectionIo for TcpConnection> { + fn into_connection_io(self) -> (Box, Protocol) { + let io = self.into_parts().0; + (io, Protocol::Http2) + } + } + + /// With the `dangerous-h2c` feature enabled, this connector uses a no-op TLS + /// connection service that passes through plain TCP as a TLS connection. + /// + /// The protocol version of this fake TLS connection is set to be HTTP/2. + #[derive(Clone)] + struct NoOpTlsConnectorService; + + impl Service> for NoOpTlsConnectorService + where + U: ActixStream + 'static, + { + type Response = Connection>; + type Error = io::Error; + type Future = Ready>; + + actix_service::always_ready!(); + + fn call(&self, connection: Connection) -> Self::Future { + let (io, connection) = connection.replace_io(()); + let (_, connection) = connection.replace_io(Box::new(io) as _); + + ready(Ok(connection)) + } + } + + let handshake_timeout = self.config.handshake_timeout; + + let tls_service = TlsConnectorService { + tcp_service: tcp_service_inner, + tls_service: NoOpTlsConnectorService, + timeout: handshake_timeout, + }; + + Some(actix_service::boxed::rc_service(tls_service)) + } + } #[cfg(feature = "openssl")] SslConnector::Openssl(tls) => { const H2: &[u8] = b"h2"; @@ -760,3 +816,42 @@ mod resolver { }) } } + +#[cfg(feature = "dangerous-h2c")] +#[cfg(test)] +mod tests { + use std::convert::Infallible; + + use actix_http::{HttpService, Request, Response, Version}; + use actix_http_test::test_server; + use actix_service::ServiceFactoryExt as _; + + use super::*; + use crate::Client; + + #[actix_rt::test] + async fn h2c_connector() { + let mut srv = test_server(|| { + HttpService::build() + .h2(|_req: Request| async { Ok::<_, Infallible>(Response::ok()) }) + .tcp() + .map_err(|_| ()) + }) + .await; + + let connector = Connector { + connector: new_connector(resolver::resolver()), + config: ConnectorConfig::default(), + ssl: SslConnector::None, + }; + + let client = Client::builder().connector(connector).finish(); + + let request = client.get(srv.surl("/")).send(); + let response = request.await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); + + srv.stop().await; + } +} diff --git a/awc/src/request.rs b/awc/src/request.rs index bc3859e2e..f364b43c7 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -115,10 +115,10 @@ impl ClientRequest { &self.head.method } - #[doc(hidden)] /// Set HTTP version of this request. /// /// By default requests's HTTP version depends on network stream + #[doc(hidden)] #[inline] pub fn version(mut self, version: Version) -> Self { self.head.version = version; diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index a0af0cab6..856a4ace2 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -1,20 +1,26 @@ -use std::collections::HashMap; -use std::io::{Read, Write}; -use std::net::{IpAddr, Ipv4Addr}; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::time::Duration; +use std::{ + collections::HashMap, + io::{Read, Write}, + net::{IpAddr, Ipv4Addr}, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, + time::Duration, +}; use actix_utils::future::ok; -use brotli2::write::BrotliEncoder; use bytes::Bytes; use cookie::Cookie; -use flate2::read::GzDecoder; -use flate2::write::GzEncoder; -use flate2::Compression; use futures_util::stream; use rand::Rng; +#[cfg(feature = "compress-brotli")] +use brotli2::write::BrotliEncoder; + +#[cfg(feature = "compress-gzip")] +use flate2::{read::GzDecoder, write::GzEncoder, Compression}; + use actix_http::{ http::{self, StatusCode}, HttpService, @@ -24,7 +30,6 @@ use actix_service::{fn_service, map_config, ServiceFactoryExt as _}; use actix_web::{ dev::{AppConfig, BodyEncoding}, http::header, - middleware::Compress, web, App, Error, HttpRequest, HttpResponse, }; use awc::error::{JsonPayloadError, PayloadError, SendRequestError}; @@ -463,11 +468,12 @@ async fn test_with_query_parameter() { assert!(res.status().is_success()); } +#[cfg(feature = "compress-gzip")] #[actix_rt::test] async fn test_no_decompress() { let srv = actix_test::start(|| { App::new() - .wrap(Compress::default()) + .wrap(actix_web::middleware::Compress::default()) .service(web::resource("/").route(web::to(|| { let mut res = HttpResponse::Ok().body(STR); res.encoding(header::ContentEncoding::Gzip); @@ -507,6 +513,7 @@ async fn test_no_decompress() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } +#[cfg(feature = "compress-gzip")] #[actix_rt::test] async fn test_client_gzip_encoding() { let srv = actix_test::start(|| { @@ -530,6 +537,7 @@ async fn test_client_gzip_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[cfg(feature = "compress-gzip")] #[actix_rt::test] async fn test_client_gzip_encoding_large() { let srv = actix_test::start(|| { @@ -553,6 +561,7 @@ async fn test_client_gzip_encoding_large() { assert_eq!(bytes, Bytes::from(STR.repeat(10))); } +#[cfg(feature = "compress-gzip")] #[actix_rt::test] async fn test_client_gzip_encoding_large_random() { let data = rand::thread_rng() @@ -581,6 +590,7 @@ async fn test_client_gzip_encoding_large_random() { assert_eq!(bytes, Bytes::from(data)); } +#[cfg(feature = "compress-brotli")] #[actix_rt::test] async fn test_client_brotli_encoding() { let srv = actix_test::start(|| { @@ -603,6 +613,7 @@ async fn test_client_brotli_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[cfg(feature = "compress-brotli")] #[actix_rt::test] async fn test_client_brotli_encoding_large_random() { let data = rand::thread_rng() diff --git a/awc/tests/test_connector.rs b/awc/tests/test_connector.rs index 632f68b72..588c51463 100644 --- a/awc/tests/test_connector.rs +++ b/awc/tests/test_connector.rs @@ -39,7 +39,7 @@ fn tls_config() -> SslAcceptor { #[actix_rt::test] async fn test_connection_window_size() { - let srv = test_server(move || { + let srv = test_server(|| { HttpService::build() .h2(map_config( App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))),