@@ -21,6 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121*/
2222
2323using System . Net . Http . Headers ;
24+ using System . Security . Cryptography . X509Certificates ;
2425using Apache . Arrow . Flight ;
2526using Apache . Arrow . Flight . Client ;
2627using Grpc . Core ;
@@ -39,7 +40,7 @@ internal class SpiceFlightClient : IDisposable
3940 private readonly HttpClient ? _httpClient ;
4041 private readonly AsyncRetryPolicy _retryPolicy ;
4142
42- private static GrpcChannelOptions GetGrpcChannelOptions ( string ? appId , string ? apiKey , string ? userAgent , bool useTls )
43+ private static GrpcChannelOptions GetGrpcChannelOptions ( string ? appId , string ? apiKey , string ? userAgent , bool useTls , string ? tlsClientCertFile = null , string ? tlsClientKeyFile = null )
4344 {
4445 var options = new GrpcChannelOptions ( ) ;
4546
@@ -57,12 +58,13 @@ private static GrpcChannelOptions GetGrpcChannelOptions(string? appId, string? a
5758 var handler = new SocketsHttpHandler
5859 {
5960 EnableMultipleHttp2Connections = true ,
60- // Force periodic connection recycling to trigger DNS re-resolution.
61- // Without this, HTTP/2 connections are kept alive indefinitely and
62- // the client can get stuck on stale IPs when backend targets change
63- // (e.g. AWS ALB target rotation).
6461 PooledConnectionLifetime = TimeSpan . FromMinutes ( 5 ) ,
6562 } ;
63+ if ( tlsClientCertFile != null && tlsClientKeyFile != null )
64+ {
65+ var clientCert = X509Certificate2 . CreateFromPemFile ( tlsClientCertFile , tlsClientKeyFile ) ;
66+ handler . SslOptions . ClientCertificates = new X509Certificate2Collection { clientCert } ;
67+ }
6668 options . HttpHandler = handler ;
6769 }
6870#endif
@@ -80,12 +82,13 @@ private static GrpcChannelOptions GetGrpcChannelOptions(string? appId, string? a
8082 messageHandler = new SocketsHttpHandler
8183 {
8284 EnableMultipleHttp2Connections = true ,
83- // Force periodic connection recycling to trigger DNS re-resolution.
84- // Without this, HTTP/2 connections are kept alive indefinitely and
85- // the client can get stuck on stale IPs when backend targets change
86- // (e.g. AWS ALB target rotation).
8785 PooledConnectionLifetime = TimeSpan . FromMinutes ( 5 ) ,
8886 } ;
87+ if ( tlsClientCertFile != null && tlsClientKeyFile != null )
88+ {
89+ var clientCert = X509Certificate2 . CreateFromPemFile ( tlsClientCertFile , tlsClientKeyFile ) ;
90+ ( ( SocketsHttpHandler ) messageHandler ) . SslOptions . ClientCertificates = new X509Certificate2Collection { clientCert } ;
91+ }
8992 }
9093 else
9194#endif
@@ -112,13 +115,13 @@ private static GrpcChannelOptions GetGrpcChannelOptions(string? appId, string? a
112115 return responseHeaders . Get ( "authorization" ) ?? trailers . Get ( "authorization" ) ;
113116 }
114117
115- internal SpiceFlightClient ( string address , int maxRetries , string ? appId , string ? apiKey , string ? userAgent , bool useTls )
118+ internal SpiceFlightClient ( string address , int maxRetries , string ? appId , string ? apiKey , string ? userAgent , bool useTls , string ? tlsClientCertFile = null , string ? tlsClientKeyFile = null )
116119 {
117120 _retryPolicy = RetryPolicyFactory . CreateRpcRetryPolicy (
118121 maxRetries ,
119122 ( ex , ts , attempt ) => RetryPolicyFactory . LogRetry ( "Flight" , ex , ts , attempt ) ) ;
120123
121- var options = GetGrpcChannelOptions ( appId , apiKey , userAgent , useTls ) ;
124+ var options = GetGrpcChannelOptions ( appId , apiKey , userAgent , useTls , tlsClientCertFile , tlsClientKeyFile ) ;
122125 _httpClient = options . HttpClient ;
123126
124127 _channel = GrpcChannel . ForAddress ( address , options ) ;
0 commit comments