1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-23 16:21:06 +01:00

awc: support http2 over plain tcp with feature flag (#2439)

Co-authored-by: Rob Ede <robjtede@icloud.com>
This commit is contained in:
fakeshadow 2021-11-23 02:16:56 +08:00 committed by GitHub
parent a172f5968d
commit e7987e7429
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 136 additions and 27 deletions

View File

@ -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<HttpResponse, Error> {
@ -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))
/// )
/// );
///

View File

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

View File

@ -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<Uri, Box<dyn ConnectionIo>> {
fn into_connection_io(self) -> (Box<dyn ConnectionIo>, 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<T, U> Service<Connection<T, U>> for NoOpTlsConnectorService
where
U: ActixStream + 'static,
{
type Response = Connection<T, Box<dyn ConnectionIo>>;
type Error = io::Error;
type Future = Ready<Result<Self::Response, Self::Error>>;
actix_service::always_ready!();
fn call(&self, connection: Connection<T, U>) -> 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;
}
}

View File

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

View File

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

View File

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