2121import java .security .KeyManagementException ;
2222import java .security .NoSuchAlgorithmException ;
2323import java .time .Duration ;
24+ import java .util .List ;
2425import java .util .Map ;
2526import java .util .Properties ;
2627import java .util .concurrent .ConcurrentHashMap ;
2930import javax .annotation .Nullable ;
3031import javax .net .ssl .TrustManager ;
3132import net .snowflake .client .jdbc .ErrorCode ;
33+ import net .snowflake .client .jdbc .HttpHeadersCustomizer ;
3234import net .snowflake .client .jdbc .RestRequest ;
3335import net .snowflake .client .jdbc .RetryContextManager ;
3436import net .snowflake .client .jdbc .SnowflakeDriver ;
@@ -290,6 +292,24 @@ static String buildUserAgent(String customSuffix) {
290292 */
291293 public static CloseableHttpClient buildHttpClient (
292294 @ Nullable HttpClientSettingsKey key , File ocspCacheFile , boolean downloadUnCompressed ) {
295+ return buildHttpClient (key , ocspCacheFile , downloadUnCompressed , null );
296+ }
297+
298+ /**
299+ * Build an Http client using our set of default.
300+ *
301+ * @param key Key to HttpClient hashmap containing OCSP mode and proxy information, could be null
302+ * @param ocspCacheFile OCSP response cache file. If null, the default OCSP response file will be
303+ * used.
304+ * @param downloadUnCompressed Whether the HTTP client should be built requesting no decompression
305+ * @param httpHeadersCustomizers List of HTTP headers customizers
306+ * @return HttpClient object
307+ */
308+ public static CloseableHttpClient buildHttpClient (
309+ @ Nullable HttpClientSettingsKey key ,
310+ File ocspCacheFile ,
311+ boolean downloadUnCompressed ,
312+ List <HttpHeadersCustomizer > httpHeadersCustomizers ) {
293313 logger .debug (
294314 "Building http client with client settings key: {}, ocsp cache file: {}, download uncompressed: {}" ,
295315 key != null ? key .toString () : null ,
@@ -422,6 +442,12 @@ public static CloseableHttpClient buildHttpClient(
422442 logger .debug ("Disabling content compression for http client" );
423443 httpClientBuilder .disableContentCompression ();
424444 }
445+ if (httpHeadersCustomizers != null && !httpHeadersCustomizers .isEmpty ()) {
446+ logger .debug ("Setting up http headers customizers" );
447+ httpClientBuilder .setRetryHandler (new AttributeEnhancingHttpRequestRetryHandler ());
448+ httpClientBuilder .addInterceptorLast (
449+ new HeaderCustomizerHttpRequestInterceptor (httpHeadersCustomizers ));
450+ }
425451 return httpClientBuilder .build ();
426452 } catch (NoSuchAlgorithmException | KeyManagementException ex ) {
427453 throw new SSLInitializationException (ex .getMessage (), ex );
@@ -464,7 +490,7 @@ public static void updateRoutePlanner(HttpClientSettingsKey key) {
464490 * @return HttpClient object shared across all connections
465491 */
466492 public static CloseableHttpClient getHttpClient (HttpClientSettingsKey ocspAndProxyKey ) {
467- return initHttpClient (ocspAndProxyKey , null );
493+ return initHttpClient (ocspAndProxyKey , null , null );
468494 }
469495
470496 /**
@@ -475,7 +501,31 @@ public static CloseableHttpClient getHttpClient(HttpClientSettingsKey ocspAndPro
475501 */
476502 public static CloseableHttpClient getHttpClientWithoutDecompression (
477503 HttpClientSettingsKey ocspAndProxyKey ) {
478- return initHttpClientWithoutDecompression (ocspAndProxyKey , null );
504+ return initHttpClientWithoutDecompression (ocspAndProxyKey , null , null );
505+ }
506+
507+ /**
508+ * Gets HttpClient with insecureMode false
509+ *
510+ * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient
511+ * @param httpHeadersCustomizers List of HTTP headers customizers
512+ * @return HttpClient object shared across all connections
513+ */
514+ public static CloseableHttpClient getHttpClient (
515+ HttpClientSettingsKey ocspAndProxyKey , List <HttpHeadersCustomizer > httpHeadersCustomizers ) {
516+ return initHttpClient (ocspAndProxyKey , null , httpHeadersCustomizers );
517+ }
518+
519+ /**
520+ * Gets HttpClient with insecureMode false and disabling decompression
521+ *
522+ * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient
523+ * @param httpHeadersCustomizers List of HTTP headers customizers
524+ * @return HttpClient object shared across all connections
525+ */
526+ public static CloseableHttpClient getHttpClientWithoutDecompression (
527+ HttpClientSettingsKey ocspAndProxyKey , List <HttpHeadersCustomizer > httpHeadersCustomizers ) {
528+ return initHttpClientWithoutDecompression (ocspAndProxyKey , null , httpHeadersCustomizers );
479529 }
480530
481531 /**
@@ -489,7 +539,7 @@ public static CloseableHttpClient initHttpClientWithoutDecompression(
489539 HttpClientSettingsKey key , File ocspCacheFile ) {
490540 updateRoutePlanner (key );
491541 return httpClientWithoutDecompression .computeIfAbsent (
492- key , k -> buildHttpClient (key , ocspCacheFile , true ));
542+ key , k -> buildHttpClient (key , ocspCacheFile , true , null ));
493543 }
494544
495545 /**
@@ -502,7 +552,42 @@ public static CloseableHttpClient initHttpClientWithoutDecompression(
502552 public static CloseableHttpClient initHttpClient (HttpClientSettingsKey key , File ocspCacheFile ) {
503553 updateRoutePlanner (key );
504554 return httpClient .computeIfAbsent (
505- key , k -> buildHttpClient (key , ocspCacheFile , key .getGzipDisabled ()));
555+ key , k -> buildHttpClient (key , ocspCacheFile , key .getGzipDisabled (), null ));
556+ }
557+
558+ /**
559+ * Accessor for the HTTP client singleton.
560+ *
561+ * @param key contains information needed to build specific HttpClient
562+ * @param ocspCacheFile OCSP response cache file name. if null, the default file will be used.
563+ * @param httpHeadersCustomizers List of HTTP headers customizers
564+ * @return HttpClient object shared across all connections
565+ */
566+ public static CloseableHttpClient initHttpClientWithoutDecompression (
567+ HttpClientSettingsKey key ,
568+ File ocspCacheFile ,
569+ List <HttpHeadersCustomizer > httpHeadersCustomizers ) {
570+ updateRoutePlanner (key );
571+ return httpClientWithoutDecompression .computeIfAbsent (
572+ key , k -> buildHttpClient (key , ocspCacheFile , true , httpHeadersCustomizers ));
573+ }
574+
575+ /**
576+ * Accessor for the HTTP client singleton.
577+ *
578+ * @param key contains information needed to build specific HttpClient
579+ * @param ocspCacheFile OCSP response cache file name. if null, the default file will be used.
580+ * @param httpHeadersCustomizers List of HTTP headers customizers
581+ * @return HttpClient object shared across all connections
582+ */
583+ public static CloseableHttpClient initHttpClient (
584+ HttpClientSettingsKey key ,
585+ File ocspCacheFile ,
586+ List <HttpHeadersCustomizer > httpHeadersCustomizers ) {
587+ updateRoutePlanner (key );
588+ return httpClient .computeIfAbsent (
589+ key ,
590+ k -> buildHttpClient (key , ocspCacheFile , key .getGzipDisabled (), httpHeadersCustomizers ));
506591 }
507592
508593 /**
@@ -622,7 +707,7 @@ static String executeRequestWithoutCookies(
622707 false , // no retry parameter
623708 true , // guid? (do we need this?)
624709 false , // no retry on HTTP 403
625- getHttpClient (ocspAndProxyKey ),
710+ getHttpClient (ocspAndProxyKey , null ),
626711 new ExecTimeTelemetryData (),
627712 null );
628713 }
@@ -680,7 +765,7 @@ public static String executeGeneralRequestOmitRequestGuid(
680765 false ,
681766 false ,
682767 false ,
683- getHttpClient (ocspAndProxyAndGzipKey ),
768+ getHttpClient (ocspAndProxyAndGzipKey , null ),
684769 new ExecTimeTelemetryData (),
685770 null );
686771 }
@@ -860,7 +945,7 @@ public static String executeRequest(
860945 includeRetryParameters ,
861946 true , // include request GUID
862947 retryOnHTTP403 ,
863- getHttpClient (ocspAndProxyKey ),
948+ getHttpClient (ocspAndProxyKey , null ),
864949 execTimeData ,
865950 retryContextManager );
866951 }
0 commit comments