mirror of
https://github.com/fafhrd91/actix-web
synced 2024-11-24 00:21:08 +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:
parent
a172f5968d
commit
e7987e7429
@ -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))
|
||||
/// )
|
||||
/// );
|
||||
///
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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))),
|
||||
|
Loading…
Reference in New Issue
Block a user