diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 366b77f..e327198 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,11 +55,16 @@ jobs: - run: cargo clippy --version - run: cargo clippy - run: cargo clippy --all-targets --no-default-features - - run: cargo clippy --all-targets --features native-tls - - run: cargo clippy --all-targets --features rustls-tls - - run: cargo clippy --all-targets --features rustls-tls-aws - run: cargo clippy --all-targets --all-features + # TLS + - run: cargo clippy --features native-tls + - run: cargo clippy --features rustls-tls + - run: cargo clippy --features rustls-tls-ring,rustls-tls-webpki-roots + - run: cargo clippy --features rustls-tls-ring,rustls-tls-native-roots + - run: cargo clippy --features rustls-tls-aws-lc,rustls-tls-webpki-roots + - run: cargo clippy --features rustls-tls-aws-lc,rustls-tls-native-roots + test: runs-on: ubuntu-latest steps: diff --git a/Cargo.toml b/Cargo.toml index 50bbba9..3ffbff3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,9 +61,17 @@ watch = ["dep:sha-1", "dep:serde_json", "serde/derive"] uuid = ["dep:uuid"] time = ["dep:time"] lz4 = ["dep:lz4_flex", "dep:cityhash-rs"] + +## TLS native-tls = ["dep:hyper-tls"] -rustls-tls = ["dep:hyper-rustls", "dep:rustls", "hyper-rustls?/ring"] -rustls-tls-aws = ["dep:hyper-rustls", "dep:rustls", "hyper-rustls?/aws-lc-rs"] +# ext: native-tls-alpn +# ext: native-tls-vendored + +rustls-tls = ["rustls-tls-aws-lc", "rustls-tls-webpki-roots"] +rustls-tls-aws-lc = ["dep:rustls", "dep:hyper-rustls", "hyper-rustls?/aws-lc-rs"] +rustls-tls-ring = ["dep:rustls", "dep:hyper-rustls", "hyper-rustls?/ring"] +rustls-tls-webpki-roots = ["dep:rustls", "dep:hyper-rustls", "hyper-rustls?/webpki-tokio"] +rustls-tls-native-roots = ["dep:rustls", "dep:hyper-rustls", "hyper-rustls?/native-tokio"] [dependencies] clickhouse-derive = { version = "0.2.0", path = "derive" } @@ -80,7 +88,6 @@ rustls = { version = "0.23", default-features = false, optional = true } hyper-rustls = { version = "0.27.2", default-features = false, features = [ "http1", "tls12", - "webpki-roots", ], optional = true } url = "2.1.1" futures = "0.3.5" diff --git a/src/http_client.rs b/src/http_client.rs index cbd985a..9fa08d2 100644 --- a/src/http_client.rs +++ b/src/http_client.rs @@ -1,5 +1,13 @@ +use std::time::Duration; + use hyper::Request; -use hyper_util::client::legacy::{connect::Connect, Client, ResponseFuture}; +use hyper_util::{ + client::legacy::{ + connect::{Connect, HttpConnector}, + Client, Client as HyperClient, ResponseFuture, + }, + rt::TokioExecutor, +}; use sealed::sealed; use crate::request_body::RequestBody; @@ -27,3 +35,75 @@ where self.request(req) } } + +// === Default === + +const TCP_KEEPALIVE: Duration = Duration::from_secs(60); + +// ClickHouse uses 3s by default. +// See https://github.com/ClickHouse/ClickHouse/blob/368cb74b4d222dc5472a7f2177f6bb154ebae07a/programs/server/config.xml#L201 +const POOL_IDLE_TIMEOUT: Duration = Duration::from_secs(2); + +pub(crate) fn default() -> impl HttpClient { + let mut connector = HttpConnector::new(); + + // TODO: make configurable in `Client::builder()`. + connector.set_keepalive(Some(TCP_KEEPALIVE)); + + connector.enforce_http(!cfg!(any( + feature = "native-tls", + feature = "rustls-tls-aws-lc", + feature = "rustls-tls-ring", + ))); + + #[cfg(feature = "native-tls")] + let connector = hyper_tls::HttpsConnector::new_with_connector(connector); + + #[cfg(all(feature = "rustls-tls-aws-lc", not(feature = "native-tls")))] + let connector = + prepare_hyper_rustls_connector(connector, rustls::crypto::aws_lc_rs::default_provider()); + + #[cfg(all( + feature = "rustls-tls-ring", + not(feature = "rustls-tls-aws-lc"), + not(feature = "native-tls"), + ))] + let connector = + prepare_hyper_rustls_connector(connector, rustls::crypto::ring::default_provider()); + + HyperClient::builder(TokioExecutor::new()) + .pool_idle_timeout(POOL_IDLE_TIMEOUT) + .build(connector) +} + +#[cfg(not(feature = "native-tls"))] +#[cfg(any(feature = "rustls-tls-aws-lc", feature = "rustls-tls-ring"))] +fn prepare_hyper_rustls_connector( + connector: HttpConnector, + provider: rustls::crypto::CryptoProvider, +) -> hyper_rustls::HttpsConnector { + #[cfg(not(feature = "rustls-tls-webpki-roots"))] + #[cfg(not(feature = "rustls-tls-native-roots"))] + compile_error!( + "`rustls-tls-aws-lc` and `rustls-tls-ring` features require either \ + `rustls-tls-webpki-roots` or `rustls-tls-native-roots` feature to be enabled" + ); + + #[cfg(feature = "rustls-tls-native-roots")] + let builder = hyper_rustls::HttpsConnectorBuilder::new() + .with_provider_and_native_roots(provider) + .unwrap(); + + #[cfg(all( + feature = "rustls-tls-webpki-roots", + not(feature = "rustls-tls-native-roots") + ))] + let builder = hyper_rustls::HttpsConnectorBuilder::new() + .with_provider_and_webpki_roots(provider) + .unwrap(); + + builder + .https_or_http() + .enable_http1() + .wrap_connector(connector) +} diff --git a/src/lib.rs b/src/lib.rs index 692211f..6df4a50 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,18 +5,11 @@ #[macro_use] extern crate static_assertions; -pub use clickhouse_derive::Row; -#[cfg(feature = "tls")] -use hyper_tls::HttpsConnector; -use hyper_util::{ - client::legacy::{connect::HttpConnector, Client as HyperClient}, - rt::TokioExecutor, -}; -use std::fmt::Display; -use std::{collections::HashMap, sync::Arc, time::Duration}; +use self::{error::Result, http_client::HttpClient}; +use std::{collections::HashMap, fmt::Display, sync::Arc}; pub use self::{compression::Compression, row::Row}; -use self::{error::Result, http_client::HttpClient}; +pub use clickhouse_derive::Row; pub mod error; pub mod insert; @@ -42,28 +35,6 @@ mod rowbinary; #[cfg(feature = "inserter")] mod ticks; -const TCP_KEEPALIVE: Duration = Duration::from_secs(60); - -// ClickHouse uses 3s by default. -// See https://github.com/ClickHouse/ClickHouse/blob/368cb74b4d222dc5472a7f2177f6bb154ebae07a/programs/server/config.xml#L201 -const POOL_IDLE_TIMEOUT: Duration = Duration::from_secs(2); - -#[cfg(all( - not(feature = "native-tls"), - any(feature = "rustls-tls", feature = "rustls-tls-aws") -))] -fn prepare_hyper_rustls_connector( - connector: HttpConnector, - provider: impl Into>, -) -> hyper_rustls::HttpsConnector { - hyper_rustls::HttpsConnectorBuilder::new() - .with_provider_and_webpki_roots(provider) - .unwrap() - .https_or_http() - .enable_http1() - .wrap_connector(connector) -} - /// A client containing HTTP pool. #[derive(Clone)] pub struct Client { @@ -93,41 +64,7 @@ impl Display for ProductInfo { impl Default for Client { fn default() -> Self { - #[allow(unused_mut)] - let mut connector = HttpConnector::new(); - - // TODO: make configurable in `Client::builder()`. - connector.set_keepalive(Some(TCP_KEEPALIVE)); - - #[cfg(any( - feature = "native-tls", - feature = "rustls-tls", - feature = "rustls-tls-aws" - ))] - connector.enforce_http(false); - - #[cfg(feature = "native-tls")] - let connector = hyper_tls::HttpsConnector::new_with_connector(connector); - - #[cfg(all(feature = "rustls-tls-aws", not(feature = "native-tls")))] - let connector = prepare_hyper_rustls_connector( - connector, - rustls::crypto::aws_lc_rs::default_provider(), - ); - - #[cfg(all( - feature = "rustls-tls", - not(feature = "rustls-tls-aws"), - not(feature = "native-tls") - ))] - let connector = - prepare_hyper_rustls_connector(connector, rustls::crypto::ring::default_provider()); - - let client = HyperClient::builder(TokioExecutor::new()) - .pool_idle_timeout(POOL_IDLE_TIMEOUT) - .build(connector); - - Self::with_http_client(client) + Self::with_http_client(http_client::default()) } } @@ -259,8 +196,9 @@ impl Client { /// ``` /// /// Sample User-Agent with multiple products information - /// (NB: the products are added in the reverse order of [`Client::with_product_info`] calls, - /// which could be useful to add higher abstraction layers first): + /// (NB: the products are added in the reverse order of + /// [`Client::with_product_info`] calls, which could be useful to add + /// higher abstraction layers first): /// /// ``` /// # use clickhouse::Client;