clickhouse.rs icon indicating copy to clipboard operation
clickhouse.rs copied to clipboard

Add example in how to use this with a https clickhouse URL

Open joshstevens19 opened this issue 1 year ago • 1 comments

It seems to need a few moving parts to connect this lib with an HTTPS clickhouse URL and probably a lot of people would like to see an example of which they can copy in and replace their parameters without having to find it out based on other peoples code. Would be pretty useful example IMHO

joshstevens19 avatar Aug 21 '24 16:08 joshstevens19

@joshstevens19, do you need certificates or just an example of using it with an HTTPS URL with enabled TLS feature?

If it is just HTTPS, you could use this example: https://github.com/ClickHouse/clickhouse-rs/blob/master/examples/clickhouse_cloud.rs, which should be sufficient, as CH Cloud is HTTPS.

slvrtrn avatar Aug 24 '24 15:08 slvrtrn

It is now also mentioned in the website docs: https://clickhouse.com/docs/en/integrations/rust#https-or-clickhouse-cloud-connection

Feel free to re-open if you still have questions.

slvrtrn avatar Sep 10 '24 15:09 slvrtrn

Is there an example for connecting to https with self-signed certificate?

ausrasul avatar Sep 27 '24 19:09 ausrasul

+1, need this

ifokeev avatar Jan 16 '25 17:01 ifokeev

+1, need this

Got my answer: https://github.com/ClickHouse/clickhouse-rs/issues/164

ausrasul avatar Jan 16 '25 17:01 ausrasul

+1, need this

Got my answer: #164

Could you please provide an example?

ifokeev avatar Jan 16 '25 18:01 ifokeev

+1, need this

Got my answer: #164

Could you please provide an example?

The short answer was that it was not possible because it is a bad practice. The work around was too complicated and I couldn’t get it to work. So for POC i used http.

ausrasul avatar Jan 16 '25 19:01 ausrasul

This issue is about examples of connecting to HTTPS, not about providing self-signed certificates directly to the crate.

If such functionality is highly desired, feel free to create a dedicated issue (#164 was about the impossibility of using self-signed certificates while it's possible, not about a dedicated API to provide such certificates directly to the crate). The implementation will look like https://docs.rs/reqwest/latest/reqwest/tls/struct.Certificate.html, so feel free to describe whether it's enough.

loyd avatar Jan 19 '25 10:01 loyd

I did it; it was quite challenging, but now it's functioning properly with Yandex Cloud.

// clickhouse-client.rs

use clickhouse::Client;
use hyper_rustls::HttpsConnectorBuilder;
use hyper_util::client::legacy::Client as HyperClient;
use hyper_util::rt::TokioExecutor;
use rustls::pki_types::CertificateDer;
use rustls::{ClientConfig, RootCertStore};
use rustls_pemfile::Item;
use std::fs;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ClickHouseError {
    #[error("ClickHouse error: {0}")]
    ConnectionError(#[from] clickhouse::error::Error),
    #[error("Rustls error: {0}")]
    RustlsError(#[from] rustls::Error),
    #[error("IO error: {0}")]
    IoError(#[from] std::io::Error),
}

#[derive(Debug, Clone)]
pub struct ClickHouseConfig {
    pub url: String,
    pub username: String,
    pub password: String,
    pub database: Option<String>,
    pub ca_cert_path: Option<String>,
}

pub struct ClickHouseClient {
    client: Client,
}

impl ClickHouseClient {
    pub fn new(config: ClickHouseConfig) -> Result<Self, ClickHouseError> {
        let https = if let Some(cert_path) = &config.ca_cert_path {
            let cert = fs::read(cert_path)?;
            let mut cert_reader = std::io::BufReader::new(cert.as_slice());
            let cert = match rustls_pemfile::read_one(&mut cert_reader)? {
                Some(Item::X509Certificate(cert_der)) => CertificateDer::from(cert_der),
                _ => {
                    return Err(ClickHouseError::IoError(std::io::Error::new(
                        std::io::ErrorKind::InvalidData,
                        "Invalid certificate format",
                    )))
                }
            };
            let mut root_store = RootCertStore::empty();
            root_store.add(cert)?;

            let tls_config =
                ClientConfig::builder().with_root_certificates(root_store).with_no_client_auth();

            HttpsConnectorBuilder::new()
                .with_tls_config(tls_config)
                .https_only()
                .enable_http1()
                .build()
        } else {
            HttpsConnectorBuilder::new()
                .with_tls_config(
                    ClientConfig::builder()
                        .with_root_certificates(RootCertStore::empty())
                        .with_no_client_auth(),
                )
                .https_or_http()
                .enable_http1()
                .build()
        };

        let hyper_client = HyperClient::builder(TokioExecutor::new()).build(https);

        let mut client = Client::with_http_client(hyper_client)
            .with_url(config.url)
            .with_user(config.username)
            .with_password(config.password);

        if let Some(database) = config.database {
            client = client.with_database(database);
        }

        Ok(Self { client })
    }

    pub fn get_client(&self) -> &Client {
        &self.client
    }
}
// Cargo.toml

[package]
name = "utils"
version = "0.1.0"
edition = "2021"

[dependencies]
clickhouse-derive = { version = "0.2.0" }
clickhouse = "0.13.1"
rustls = "0.23"
hyper-util = { version = "0.1.6", features = ["client-legacy", "http1"] }
hyper-rustls = { version = "0.27.3", default-features = false, features = [ "http1", "tls12" ] }
rustls-pemfile = "2.2"
native-tls = "0.2.11"

I needed to synchronize the versions of various packages to get this to work.

I hope this helps someone.

ifokeev avatar Jan 21 '25 10:01 ifokeev

This is the only working example I could find. Thy you for sharing @ifokeev

4F2E4A2E avatar Aug 06 '25 09:08 4F2E4A2E