mirror of
https://github.com/fafhrd91/actix-web
synced 2025-06-25 06:39:22 +02:00
Http2 client configuration to improve performance (#1394)
* add defaults for http2 client configuration * fix spaces * Add changes text for extended H2 defaults buffers * client: configurable H2 window sizes and max_http_version * add H2 window size configuration and max_http_version to awc::ClientBuilder * add awc::ClientBuilder H2 window sizes and max_http_version * add test for H2 window size settings * cleanup comment * Apply code review fixes * Code review fix for awc ClientBuilder * Remove unnecessary comments on code review * pin quote version to resolve build issue * max_http_version to accept http::Version * revert fix for quote broken build
This commit is contained in:
@ -1,5 +1,11 @@
|
||||
# Changes
|
||||
|
||||
## [NEXT]
|
||||
|
||||
* ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration
|
||||
|
||||
* ClientBuilder allowing to set max_http_version to limit HTTP version to be used
|
||||
|
||||
## [1.0.1] - 2019-12-15
|
||||
|
||||
* Fix compilation with default features off
|
||||
|
@ -4,11 +4,11 @@ use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use actix_http::client::{Connect, ConnectError, Connection, Connector};
|
||||
use actix_http::http::{header, Error as HttpError, HeaderMap, HeaderName};
|
||||
use actix_http::client::{Connect as HttpConnect, ConnectError, Connection, Connector};
|
||||
use actix_http::http::{header, Error as HttpError, HeaderMap, HeaderName, self};
|
||||
use actix_service::Service;
|
||||
|
||||
use crate::connect::ConnectorWrapper;
|
||||
use crate::connect::{ConnectorWrapper, Connect};
|
||||
use crate::{Client, ClientConfig};
|
||||
|
||||
/// An HTTP Client builder
|
||||
@ -16,10 +16,15 @@ use crate::{Client, ClientConfig};
|
||||
/// This type can be used to construct an instance of `Client` through a
|
||||
/// builder-like pattern.
|
||||
pub struct ClientBuilder {
|
||||
config: ClientConfig,
|
||||
default_headers: bool,
|
||||
allow_redirects: bool,
|
||||
max_redirects: usize,
|
||||
max_http_version: Option<http::Version>,
|
||||
stream_window_size: Option<u32>,
|
||||
conn_window_size: Option<u32>,
|
||||
headers: HeaderMap,
|
||||
timeout: Option<Duration>,
|
||||
connector: Option<RefCell<Box<dyn Connect>>>,
|
||||
}
|
||||
|
||||
impl Default for ClientBuilder {
|
||||
@ -34,25 +39,24 @@ impl ClientBuilder {
|
||||
default_headers: true,
|
||||
allow_redirects: true,
|
||||
max_redirects: 10,
|
||||
config: ClientConfig {
|
||||
headers: HeaderMap::new(),
|
||||
timeout: Some(Duration::from_secs(5)),
|
||||
connector: RefCell::new(Box::new(ConnectorWrapper(
|
||||
Connector::new().finish(),
|
||||
))),
|
||||
},
|
||||
headers: HeaderMap::new(),
|
||||
timeout: Some(Duration::from_secs(5)),
|
||||
connector: None,
|
||||
max_http_version: None,
|
||||
stream_window_size: None,
|
||||
conn_window_size: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Use custom connector service.
|
||||
pub fn connector<T>(mut self, connector: T) -> Self
|
||||
where
|
||||
T: Service<Request = Connect, Error = ConnectError> + 'static,
|
||||
T: Service<Request = HttpConnect, Error = ConnectError> + 'static,
|
||||
T::Response: Connection,
|
||||
<T::Response as Connection>::Future: 'static,
|
||||
T::Future: 'static,
|
||||
{
|
||||
self.config.connector = RefCell::new(Box::new(ConnectorWrapper(connector)));
|
||||
self.connector = Some(RefCell::new(Box::new(ConnectorWrapper(connector))));
|
||||
self
|
||||
}
|
||||
|
||||
@ -61,13 +65,13 @@ impl ClientBuilder {
|
||||
/// Request timeout is the total time before a response must be received.
|
||||
/// Default value is 5 seconds.
|
||||
pub fn timeout(mut self, timeout: Duration) -> Self {
|
||||
self.config.timeout = Some(timeout);
|
||||
self.timeout = Some(timeout);
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable request timeout.
|
||||
pub fn disable_timeout(mut self) -> Self {
|
||||
self.config.timeout = None;
|
||||
self.timeout = None;
|
||||
self
|
||||
}
|
||||
|
||||
@ -79,6 +83,31 @@ impl ClientBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Maximum supported http major version
|
||||
/// Supported versions http/1.1, http/2
|
||||
pub fn max_http_version(mut self, val: http::Version) -> Self {
|
||||
self.max_http_version = Some(val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Indicates the initial window size (in octets) for
|
||||
/// HTTP2 stream-level flow control for received data.
|
||||
///
|
||||
/// The default value is 65,535 and is good for APIs, but not for big objects.
|
||||
pub fn initial_window_size(mut self, size: u32) -> Self {
|
||||
self.stream_window_size = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Indicates the initial window size (in octets) for
|
||||
/// HTTP2 connection-level flow control for received data.
|
||||
///
|
||||
/// The default value is 65,535 and is good for APIs, but not for big objects.
|
||||
pub fn initial_connection_window_size(mut self, size: u32) -> Self {
|
||||
self.conn_window_size = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set max number of redirects.
|
||||
///
|
||||
/// Max redirects is set to 10 by default.
|
||||
@ -106,7 +135,7 @@ impl ClientBuilder {
|
||||
match HeaderName::try_from(key) {
|
||||
Ok(key) => match value.try_into() {
|
||||
Ok(value) => {
|
||||
self.config.headers.append(key, value);
|
||||
self.headers.append(key, value);
|
||||
}
|
||||
Err(e) => log::error!("Header value error: {:?}", e),
|
||||
},
|
||||
@ -140,7 +169,27 @@ impl ClientBuilder {
|
||||
|
||||
/// Finish build process and create `Client` instance.
|
||||
pub fn finish(self) -> Client {
|
||||
Client(Rc::new(self.config))
|
||||
let connector = if let Some(connector) = self.connector {
|
||||
connector
|
||||
} else {
|
||||
let mut connector = Connector::new();
|
||||
if let Some(val) = self.max_http_version {
|
||||
connector = connector.max_http_version(val)
|
||||
};
|
||||
if let Some(val) = self.conn_window_size {
|
||||
connector = connector.initial_connection_window_size(val)
|
||||
};
|
||||
if let Some(val) = self.stream_window_size {
|
||||
connector = connector.initial_window_size(val)
|
||||
};
|
||||
RefCell::new(Box::new(ConnectorWrapper(connector.finish())) as Box<dyn Connect>)
|
||||
};
|
||||
let config = ClientConfig {
|
||||
headers: self.headers,
|
||||
timeout: self.timeout,
|
||||
connector,
|
||||
};
|
||||
Client(Rc::new(config))
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,7 +202,6 @@ mod tests {
|
||||
let client = ClientBuilder::new().basic_auth("username", Some("password"));
|
||||
assert_eq!(
|
||||
client
|
||||
.config
|
||||
.headers
|
||||
.get(header::AUTHORIZATION)
|
||||
.unwrap()
|
||||
@ -165,7 +213,6 @@ mod tests {
|
||||
let client = ClientBuilder::new().basic_auth("username", None);
|
||||
assert_eq!(
|
||||
client
|
||||
.config
|
||||
.headers
|
||||
.get(header::AUTHORIZATION)
|
||||
.unwrap()
|
||||
@ -180,7 +227,6 @@ mod tests {
|
||||
let client = ClientBuilder::new().bearer_auth("someS3cr3tAutht0k3n");
|
||||
assert_eq!(
|
||||
client
|
||||
.config
|
||||
.headers
|
||||
.get(header::AUTHORIZATION)
|
||||
.unwrap()
|
||||
|
61
awc/tests/test_connector.rs
Normal file
61
awc/tests/test_connector.rs
Normal file
@ -0,0 +1,61 @@
|
||||
#![cfg(feature = "openssl")]
|
||||
use actix_http::HttpService;
|
||||
use actix_http_test::test_server;
|
||||
use actix_service::{map_config, ServiceFactory};
|
||||
use actix_web::http::Version;
|
||||
use actix_web::{dev::AppConfig, web, App, HttpResponse};
|
||||
use open_ssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode};
|
||||
|
||||
fn ssl_acceptor() -> SslAcceptor {
|
||||
// load ssl keys
|
||||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
||||
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()
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_connection_window_size() {
|
||||
let srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(map_config(
|
||||
App::new().service(
|
||||
web::resource("/").route(web::to(|| HttpResponse::Ok())),
|
||||
),
|
||||
|_| AppConfig::default(),
|
||||
))
|
||||
.openssl(ssl_acceptor())
|
||||
.map_err(|_| ())
|
||||
});
|
||||
|
||||
// disable ssl verification
|
||||
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
builder.set_verify(SslVerifyMode::NONE);
|
||||
let _ = builder
|
||||
.set_alpn_protos(b"\x02h2\x08http/1.1")
|
||||
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
|
||||
|
||||
let client = awc::Client::build()
|
||||
.connector(awc::Connector::new().ssl(builder.build()).finish())
|
||||
.initial_window_size(100)
|
||||
.initial_connection_window_size(100)
|
||||
.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);
|
||||
}
|
Reference in New Issue
Block a user