11package com .clickhouse .client .api .internal ;
22
33import com .clickhouse .client .ClickHouseSslContextProvider ;
4- import com .clickhouse .client .api .*;
4+ import com .clickhouse .client .api .ClickHouseException ;
5+ import com .clickhouse .client .api .Client ;
6+ import com .clickhouse .client .api .ClientConfigProperties ;
7+ import com .clickhouse .client .api .ClientException ;
8+ import com .clickhouse .client .api .ClientFaultCause ;
9+ import com .clickhouse .client .api .ClientMisconfigurationException ;
10+ import com .clickhouse .client .api .ConnectionInitiationException ;
11+ import com .clickhouse .client .api .ConnectionReuseStrategy ;
12+ import com .clickhouse .client .api .DataTransferException ;
13+ import com .clickhouse .client .api .ServerException ;
514import com .clickhouse .client .api .enums .ProxyType ;
615import com .clickhouse .client .api .http .ClickHouseHttpProto ;
716import com .clickhouse .client .api .transport .Endpoint ;
17+ import com .clickhouse .data .ClickHouseFormat ;
818import net .jpountz .lz4 .LZ4Factory ;
919import org .apache .hc .client5 .http .ConnectTimeoutException ;
1020import org .apache .hc .client5 .http .classic .methods .HttpPost ;
5565import java .io .IOException ;
5666import java .io .InputStream ;
5767import java .io .OutputStream ;
68+ import java .io .UnsupportedEncodingException ;
5869import java .lang .reflect .Method ;
5970import java .net .ConnectException ;
6071import java .net .InetSocketAddress ;
6374import java .net .SocketTimeoutException ;
6475import java .net .URI ;
6576import java .net .URISyntaxException ;
77+ import java .net .URLEncoder ;
6678import java .net .UnknownHostException ;
6779import java .nio .charset .StandardCharsets ;
6880import java .security .NoSuchAlgorithmException ;
7991import java .util .concurrent .TimeUnit ;
8092import java .util .concurrent .atomic .AtomicLong ;
8193import java .util .function .Function ;
94+ import java .util .regex .Pattern ;
8295
8396public class HttpAPIClientHelper {
8497 private static final Logger LOG = LoggerFactory .getLogger (Client .class );
8598
8699 private static final int ERROR_BODY_BUFFER_SIZE = 1024 ; // Error messages are usually small
87100
101+ private static final Pattern PATTERN_HEADER_VALUE_ASCII = Pattern .compile (
102+ "\\ p{Graph}+(?:[ ]\\ p{Graph}+)*" );
103+
88104 private final CloseableHttpClient httpClient ;
89105
90106 private final RequestConfig baseRequestConfig ;
@@ -287,7 +303,7 @@ public CloseableHttpClient createHttpClient(boolean initSslContext, Map<String,
287303 SocketConfig socketConfig = soCfgBuilder .build ();
288304
289305 // Connection manager
290- if (ClientConfigProperties .CONNECTION_POOL_ENABLED .getOrDefault (configuration )) {
306+ if (ClientConfigProperties .CONNECTION_POOL_ENABLED .< Boolean > getOrDefault (configuration )) {
291307 clientBuilder .setConnectionManager (poolConnectionManager (sslConnectionSocketFactory , socketConfig , configuration ));
292308 } else {
293309 clientBuilder .setConnectionManager (basicConnectionManager (sslConnectionSocketFactory , socketConfig , configuration ));
@@ -430,36 +446,55 @@ public ClassicHttpResponse executeRequest(Endpoint server, Map<String, Object> r
430446 private static final ContentType CONTENT_TYPE = ContentType .create (ContentType .TEXT_PLAIN .getMimeType (), "UTF-8" );
431447
432448 private void addHeaders (HttpPost req , Map <String , Object > requestConfig ) {
433- req . addHeader (HttpHeaders .CONTENT_TYPE , CONTENT_TYPE .getMimeType ());
449+ addHeader (req , HttpHeaders .CONTENT_TYPE , CONTENT_TYPE .getMimeType ());
434450 if (requestConfig .containsKey (ClientConfigProperties .INPUT_OUTPUT_FORMAT .getKey ())) {
435- req .addHeader (ClickHouseHttpProto .HEADER_FORMAT , requestConfig .get (ClientConfigProperties .INPUT_OUTPUT_FORMAT .getKey ()));
451+ addHeader (
452+ req ,
453+ ClickHouseHttpProto .HEADER_FORMAT ,
454+ ((ClickHouseFormat ) requestConfig .get (ClientConfigProperties .INPUT_OUTPUT_FORMAT .getKey ())).name ());
436455 }
437-
438456 if (requestConfig .containsKey (ClientConfigProperties .QUERY_ID .getKey ())) {
439- req .addHeader (ClickHouseHttpProto .HEADER_QUERY_ID , requestConfig .get (ClientConfigProperties .QUERY_ID .getKey ()).toString ());
440- }
441-
442-
443- req .addHeader (ClickHouseHttpProto .HEADER_DATABASE , ClientConfigProperties .DATABASE .getOrDefault (requestConfig ));
444-
445-
446- if (ClientConfigProperties .SSL_AUTH .getOrDefault (requestConfig )) {
447- req .addHeader (ClickHouseHttpProto .HEADER_DB_USER , ClientConfigProperties .USER .getOrDefault (requestConfig ));
448- req .addHeader (ClickHouseHttpProto .HEADER_SSL_CERT_AUTH , "on" );
449- } else if (ClientConfigProperties .HTTP_USE_BASIC_AUTH .getOrDefault (requestConfig )) {
457+ addHeader (
458+ req ,
459+ ClickHouseHttpProto .HEADER_QUERY_ID ,
460+ (String ) requestConfig .get (ClientConfigProperties .QUERY_ID .getKey ()));
461+ }
462+ addHeader (
463+ req ,
464+ ClickHouseHttpProto .HEADER_DATABASE ,
465+ ClientConfigProperties .DATABASE .getOrDefault (requestConfig ));
466+
467+ if (ClientConfigProperties .SSL_AUTH .<Boolean >getOrDefault (requestConfig ).booleanValue ()) {
468+ addHeader (
469+ req ,
470+ ClickHouseHttpProto .HEADER_DB_USER ,
471+ ClientConfigProperties .USER .getOrDefault (requestConfig ));
472+ addHeader (
473+ req ,
474+ ClickHouseHttpProto .HEADER_SSL_CERT_AUTH ,
475+ "on" );
476+ } else if (ClientConfigProperties .HTTP_USE_BASIC_AUTH .<Boolean >getOrDefault (requestConfig ).booleanValue ()) {
450477 String user = ClientConfigProperties .USER .getOrDefault (requestConfig );
451478 String password = ClientConfigProperties .PASSWORD .getOrDefault (requestConfig );
452-
453- req .addHeader (HttpHeaders .AUTHORIZATION , "Basic " + Base64 .getEncoder ().encodeToString (
454- (user + ":" + password ).getBytes (StandardCharsets .UTF_8 ))
455- );
479+ // Use as-is, no encoding allowed
480+ req .addHeader (
481+ HttpHeaders .AUTHORIZATION ,
482+ "Basic " + Base64 .getEncoder ().encodeToString (
483+ (user + ":" + password ).getBytes (StandardCharsets .UTF_8 )));
456484 } else {
457- req .addHeader (ClickHouseHttpProto .HEADER_DB_USER , ClientConfigProperties .USER .getOrDefault (requestConfig ));
458- req .addHeader (ClickHouseHttpProto .HEADER_DB_PASSWORD , ClientConfigProperties .PASSWORD .getOrDefault (requestConfig ));
459-
485+ addHeader (
486+ req ,
487+ ClickHouseHttpProto .HEADER_DB_USER ,
488+ ClientConfigProperties .USER .getOrDefault (requestConfig ));
489+ addHeader (
490+ req ,
491+ ClickHouseHttpProto .HEADER_DB_PASSWORD ,
492+ ClientConfigProperties .PASSWORD .getOrDefault (requestConfig ));
460493 }
461494 if (proxyAuthHeaderValue != null ) {
462- req .addHeader (HttpHeaders .PROXY_AUTHORIZATION , proxyAuthHeaderValue );
495+ req .addHeader (
496+ HttpHeaders .PROXY_AUTHORIZATION ,
497+ proxyAuthHeaderValue );
463498 }
464499
465500 boolean clientCompression = ClientConfigProperties .COMPRESS_CLIENT_REQUEST .getOrDefault (requestConfig );
@@ -469,26 +504,30 @@ private void addHeaders(HttpPost req, Map<String, Object> requestConfig) {
469504
470505 if (useHttpCompression ) {
471506 if (serverCompression ) {
472- req . addHeader (HttpHeaders .ACCEPT_ENCODING , "lz4" );
507+ addHeader (req , HttpHeaders .ACCEPT_ENCODING , "lz4" );
473508 }
474509 if (clientCompression && !appCompressedData ) {
475- req . addHeader (HttpHeaders .CONTENT_ENCODING , "lz4" );
510+ addHeader (req , HttpHeaders .CONTENT_ENCODING , "lz4" );
476511 }
477512 }
478513
479514 for (String key : requestConfig .keySet ()) {
480515 if (key .startsWith (ClientConfigProperties .HTTP_HEADER_PREFIX )) {
481516 Object val = requestConfig .get (key );
482517 if (val != null ) {
483- req .setHeader (key .substring (ClientConfigProperties .HTTP_HEADER_PREFIX .length ()), String .valueOf (val ));
518+ addHeader (
519+ req ,
520+ key .substring (ClientConfigProperties .HTTP_HEADER_PREFIX .length ()),
521+ String .valueOf (val ));
484522 }
485523 }
486524 }
487525
488-
489526 // Special cases
490- if (req .containsHeader (HttpHeaders .AUTHORIZATION ) && (req .containsHeader (ClickHouseHttpProto .HEADER_DB_USER ) ||
491- req .containsHeader (ClickHouseHttpProto .HEADER_DB_PASSWORD ))) {
527+ if (req .containsHeader (HttpHeaders .AUTHORIZATION )
528+ && (req .containsHeader (ClickHouseHttpProto .HEADER_DB_USER ) ||
529+ req .containsHeader (ClickHouseHttpProto .HEADER_DB_PASSWORD )))
530+ {
492531 // user has set auth header for purpose, lets remove ours
493532 req .removeHeaders (ClickHouseHttpProto .HEADER_DB_USER );
494533 req .removeHeaders (ClickHouseHttpProto .HEADER_DB_PASSWORD );
@@ -668,7 +707,6 @@ private void correctUserAgentHeader(HttpRequest request, Map<String, Object> req
668707 } else if (userAgentHeader != null ) {
669708 userAgentValue = userAgentHeader .getValue () + " " + defaultUserAgent ;
670709 }
671-
672710 request .setHeader (HttpHeaders .USER_AGENT , userAgentValue );
673711 }
674712
@@ -720,6 +758,29 @@ public void close() {
720758 httpClient .close (CloseMode .IMMEDIATE );
721759 }
722760
761+ private static <T > void addHeader (HttpRequest req , String headerName ,
762+ String value )
763+ {
764+ if (value == null ) {
765+ return ;
766+ }
767+
768+ if (value .trim ().isEmpty ()) {
769+ return ;
770+ }
771+ if (PATTERN_HEADER_VALUE_ASCII .matcher (value ).matches ()) {
772+ req .addHeader (headerName , value );
773+ } else {
774+ try {
775+ req .addHeader (
776+ headerName + "*" ,
777+ "UTF-8''" + URLEncoder .encode (value , StandardCharsets .UTF_8 .name ()));
778+ } catch (UnsupportedEncodingException e ) {
779+ throw new ClientException ("Failed to convert string to UTF8" , e );
780+ }
781+ }
782+ }
783+
723784 /**
724785 * This factory is used only when no ssl connections are required (no https endpoints).
725786 * Internally http client would create factory and spend time if no supplied.
0 commit comments