1
1
package com .clickhouse .client .api .internal ;
2
2
3
3
import 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 ;
5
14
import com .clickhouse .client .api .enums .ProxyType ;
6
15
import com .clickhouse .client .api .http .ClickHouseHttpProto ;
7
16
import com .clickhouse .client .api .transport .Endpoint ;
17
+ import com .clickhouse .data .ClickHouseFormat ;
8
18
import net .jpountz .lz4 .LZ4Factory ;
9
19
import org .apache .hc .client5 .http .ConnectTimeoutException ;
10
20
import org .apache .hc .client5 .http .classic .methods .HttpPost ;
55
65
import java .io .IOException ;
56
66
import java .io .InputStream ;
57
67
import java .io .OutputStream ;
68
+ import java .io .UnsupportedEncodingException ;
58
69
import java .lang .reflect .Method ;
59
70
import java .net .ConnectException ;
60
71
import java .net .InetSocketAddress ;
63
74
import java .net .SocketTimeoutException ;
64
75
import java .net .URI ;
65
76
import java .net .URISyntaxException ;
77
+ import java .net .URLEncoder ;
66
78
import java .net .UnknownHostException ;
67
79
import java .nio .charset .StandardCharsets ;
68
80
import java .security .NoSuchAlgorithmException ;
79
91
import java .util .concurrent .TimeUnit ;
80
92
import java .util .concurrent .atomic .AtomicLong ;
81
93
import java .util .function .Function ;
94
+ import java .util .regex .Pattern ;
82
95
83
96
public class HttpAPIClientHelper {
84
97
private static final Logger LOG = LoggerFactory .getLogger (Client .class );
85
98
86
99
private static final int ERROR_BODY_BUFFER_SIZE = 1024 ; // Error messages are usually small
87
100
101
+ private static final Pattern PATTERN_HEADER_VALUE_ASCII = Pattern .compile (
102
+ "\\ p{Graph}+(?:[ ]\\ p{Graph}+)*" );
103
+
88
104
private final CloseableHttpClient httpClient ;
89
105
90
106
private final RequestConfig baseRequestConfig ;
@@ -287,7 +303,7 @@ public CloseableHttpClient createHttpClient(boolean initSslContext, Map<String,
287
303
SocketConfig socketConfig = soCfgBuilder .build ();
288
304
289
305
// Connection manager
290
- if (ClientConfigProperties .CONNECTION_POOL_ENABLED .getOrDefault (configuration )) {
306
+ if (ClientConfigProperties .CONNECTION_POOL_ENABLED .< Boolean > getOrDefault (configuration )) {
291
307
clientBuilder .setConnectionManager (poolConnectionManager (sslConnectionSocketFactory , socketConfig , configuration ));
292
308
} else {
293
309
clientBuilder .setConnectionManager (basicConnectionManager (sslConnectionSocketFactory , socketConfig , configuration ));
@@ -430,36 +446,55 @@ public ClassicHttpResponse executeRequest(Endpoint server, Map<String, Object> r
430
446
private static final ContentType CONTENT_TYPE = ContentType .create (ContentType .TEXT_PLAIN .getMimeType (), "UTF-8" );
431
447
432
448
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 ());
434
450
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 ());
436
455
}
437
-
438
456
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 ()) {
450
477
String user = ClientConfigProperties .USER .getOrDefault (requestConfig );
451
478
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 )));
456
484
} 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 ));
460
493
}
461
494
if (proxyAuthHeaderValue != null ) {
462
- req .addHeader (HttpHeaders .PROXY_AUTHORIZATION , proxyAuthHeaderValue );
495
+ req .addHeader (
496
+ HttpHeaders .PROXY_AUTHORIZATION ,
497
+ proxyAuthHeaderValue );
463
498
}
464
499
465
500
boolean clientCompression = ClientConfigProperties .COMPRESS_CLIENT_REQUEST .getOrDefault (requestConfig );
@@ -469,26 +504,30 @@ private void addHeaders(HttpPost req, Map<String, Object> requestConfig) {
469
504
470
505
if (useHttpCompression ) {
471
506
if (serverCompression ) {
472
- req . addHeader (HttpHeaders .ACCEPT_ENCODING , "lz4" );
507
+ addHeader (req , HttpHeaders .ACCEPT_ENCODING , "lz4" );
473
508
}
474
509
if (clientCompression && !appCompressedData ) {
475
- req . addHeader (HttpHeaders .CONTENT_ENCODING , "lz4" );
510
+ addHeader (req , HttpHeaders .CONTENT_ENCODING , "lz4" );
476
511
}
477
512
}
478
513
479
514
for (String key : requestConfig .keySet ()) {
480
515
if (key .startsWith (ClientConfigProperties .HTTP_HEADER_PREFIX )) {
481
516
Object val = requestConfig .get (key );
482
517
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 ));
484
522
}
485
523
}
486
524
}
487
525
488
-
489
526
// 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
+ {
492
531
// user has set auth header for purpose, lets remove ours
493
532
req .removeHeaders (ClickHouseHttpProto .HEADER_DB_USER );
494
533
req .removeHeaders (ClickHouseHttpProto .HEADER_DB_PASSWORD );
@@ -668,7 +707,6 @@ private void correctUserAgentHeader(HttpRequest request, Map<String, Object> req
668
707
} else if (userAgentHeader != null ) {
669
708
userAgentValue = userAgentHeader .getValue () + " " + defaultUserAgent ;
670
709
}
671
-
672
710
request .setHeader (HttpHeaders .USER_AGENT , userAgentValue );
673
711
}
674
712
@@ -720,6 +758,29 @@ public void close() {
720
758
httpClient .close (CloseMode .IMMEDIATE );
721
759
}
722
760
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
+
723
784
/**
724
785
* This factory is used only when no ssl connections are required (no https endpoints).
725
786
* Internally http client would create factory and spend time if no supplied.
0 commit comments