@@ -35,47 +35,58 @@ public class HttpConnection : IConnection
3535
3636 protected readonly ConcurrentDictionary < int , HttpClient > Clients = new ConcurrentDictionary < int , HttpClient > ( ) ;
3737
38+ private static readonly string CanNotUseStreamResponsesWithCurlHandler =
39+ "Using Stream as TReturn does not work as expected on .NET core linux, because we can no longer guarantee this works it will be removed from the client in our 6.0 release"
40+ ;
41+
3842 private HttpClient GetClient ( RequestData requestData )
3943 {
4044 var key = GetClientKey ( requestData ) ;
4145 HttpClient client ;
42- if ( ! this . Clients . TryGetValue ( key , out client ) )
46+ if ( this . Clients . TryGetValue ( key , out client ) ) return client ;
47+ lock ( _lock )
4348 {
44- lock ( _lock )
49+ client = this . Clients . GetOrAdd ( key , h =>
4550 {
46- client = this . Clients . GetOrAdd ( key , h =>
51+ var handler = CreateHttpClientHandler ( requestData ) ;
52+ var httpClient = new HttpClient ( handler , false )
4753 {
48- var handler = CreateHttpClientHandler ( requestData ) ;
49- var httpClient = new HttpClient ( handler , false )
50- {
51- Timeout = requestData . RequestTimeout
52- } ;
53-
54- httpClient . DefaultRequestHeaders . ExpectContinue = false ;
55- return httpClient ;
56- } ) ;
57- }
54+ Timeout = requestData . RequestTimeout
55+ } ;
56+
57+ httpClient . DefaultRequestHeaders . ExpectContinue = false ;
58+ return httpClient ;
59+ } ) ;
5860 }
5961
6062 return client ;
6163 }
6264
6365 public virtual ElasticsearchResponse < TReturn > Request < TReturn > ( RequestData requestData ) where TReturn : class
6466 {
67+ //TODO remove Stream response support in 6.0, closing the stream is sufficient on desktop/mono
68+ //but not on .NET core on linux HttpClient which proxies to curl.
69+ if ( typeof ( TReturn ) == typeof ( Stream ) && ConnectionConfiguration . IsCurlHandler )
70+ throw new Exception ( CanNotUseStreamResponsesWithCurlHandler ) ;
71+
6572 var client = this . GetClient ( requestData ) ;
6673 var builder = new ResponseBuilder < TReturn > ( requestData ) ;
74+ HttpResponseMessage responseMessage = null ;
6775 try
6876 {
6977 var requestMessage = CreateHttpRequestMessage ( requestData ) ;
70- var response = client . SendAsync ( requestMessage ) . GetAwaiter ( ) . GetResult ( ) ;
78+ responseMessage = client . SendAsync ( requestMessage ) . GetAwaiter ( ) . GetResult ( ) ;
7179 requestData . MadeItToResponse = true ;
72- builder . StatusCode = ( int ) response . StatusCode ;
80+ builder . StatusCode = ( int ) responseMessage . StatusCode ;
7381 IEnumerable < string > warnings ;
74- if ( response . Headers . TryGetValues ( "Warning" , out warnings ) )
82+ if ( responseMessage . Headers . TryGetValues ( "Warning" , out warnings ) )
7583 builder . DeprecationWarnings = warnings ;
7684
77- if ( response . Content != null )
78- builder . Stream = response . Content . ReadAsStreamAsync ( ) . GetAwaiter ( ) . GetResult ( ) ;
85+ if ( responseMessage . Content != null )
86+ builder . Stream = responseMessage . Content . ReadAsStreamAsync ( ) . GetAwaiter ( ) . GetResult ( ) ;
87+ // https://github.com/elastic/elasticsearch-net/issues/2311
88+ // if stream is null call dispose on response instead.
89+ if ( builder . Stream == null || builder . Stream == Stream . Null ) responseMessage . Dispose ( ) ;
7990 }
8091 catch ( TaskCanceledException e )
8192 {
@@ -85,26 +96,38 @@ public virtual ElasticsearchResponse<TReturn> Request<TReturn>(RequestData reque
8596 {
8697 builder . Exception = e ;
8798 }
88-
89- return builder . ToResponse ( ) ;
99+ var response = builder . ToResponse ( ) ;
100+ //explicit dispose of response not needed (as documented on MSDN) on desktop CLR
101+ //but we can not guarantee this is true for all HttpMessageHandler implementations
102+ if ( typeof ( TReturn ) != typeof ( Stream ) ) responseMessage ? . Dispose ( ) ;
103+ return response ;
90104 }
91105
106+
92107 public virtual async Task < ElasticsearchResponse < TReturn > > RequestAsync < TReturn > ( RequestData requestData , CancellationToken cancellationToken ) where TReturn : class
93108 {
109+ //TODO remove Stream response support in 6.0, closing the stream is sufficient on desktop/mono
110+ //but not on .NET core on linux HttpClient which proxies to curl.
111+ if ( typeof ( TReturn ) == typeof ( Stream ) && ConnectionConfiguration . IsCurlHandler )
112+ throw new Exception ( CanNotUseStreamResponsesWithCurlHandler ) ;
113+
94114 var client = this . GetClient ( requestData ) ;
95115 var builder = new ResponseBuilder < TReturn > ( requestData , cancellationToken ) ;
116+ HttpResponseMessage responseMessage = null ;
96117 try
97118 {
98119 var requestMessage = CreateHttpRequestMessage ( requestData ) ;
99- var response = await client . SendAsync ( requestMessage , cancellationToken ) . ConfigureAwait ( false ) ;
100- requestData . MadeItToResponse = true ;
101- builder . StatusCode = ( int ) response . StatusCode ;
120+ responseMessage = await client . SendAsync ( requestMessage , cancellationToken ) . ConfigureAwait ( false ) ;
121+ builder . StatusCode = ( int ) responseMessage . StatusCode ;
102122 IEnumerable < string > warnings ;
103- if ( response . Headers . TryGetValues ( "Warning" , out warnings ) )
123+ if ( responseMessage . Headers . TryGetValues ( "Warning" , out warnings ) )
104124 builder . DeprecationWarnings = warnings ;
105125
106- if ( response . Content != null )
107- builder . Stream = await response . Content . ReadAsStreamAsync ( ) . ConfigureAwait ( false ) ;
126+ if ( responseMessage . Content != null )
127+ builder . Stream = await responseMessage . Content . ReadAsStreamAsync ( ) . ConfigureAwait ( false ) ;
128+ // https://github.com/elastic/elasticsearch-net/issues/2311
129+ // if stream is null call dispose on response instead.
130+ if ( builder . Stream == null || builder . Stream == Stream . Null ) responseMessage . Dispose ( ) ;
108131 }
109132 catch ( TaskCanceledException e )
110133 {
@@ -114,8 +137,11 @@ public virtual async Task<ElasticsearchResponse<TReturn>> RequestAsync<TReturn>(
114137 {
115138 builder . Exception = e ;
116139 }
117-
118- return await builder . ToResponseAsync ( ) . ConfigureAwait ( false ) ;
140+ var response = await builder . ToResponseAsync ( ) . ConfigureAwait ( false ) ;
141+ //explicit dispose of response not needed (as documented on MSDN) on desktop CLR
142+ //but we can not guarantee this is true for all HttpMessageHandler implementations
143+ if ( typeof ( TReturn ) != typeof ( Stream ) ) responseMessage ? . Dispose ( ) ;
144+ return response ;
119145 }
120146
121147 private static readonly string MissingConnectionLimitMethodError =
@@ -151,8 +177,11 @@ protected virtual HttpClientHandler CreateHttpClientHandler(RequestData requestD
151177 {
152178 var uri = new Uri ( requestData . ProxyAddress ) ;
153179 var proxy = new WebProxy ( uri ) ;
154- var credentials = new NetworkCredential ( requestData . ProxyUsername , requestData . ProxyPassword ) ;
155- proxy . Credentials = credentials ;
180+ if ( ! string . IsNullOrEmpty ( requestData . ProxyUsername ) )
181+ {
182+ var credentials = new NetworkCredential ( requestData . ProxyUsername , requestData . ProxyPassword ) ;
183+ proxy . Credentials = credentials ;
184+ }
156185 handler . Proxy = proxy ;
157186 }
158187
@@ -189,6 +218,10 @@ protected virtual HttpRequestMessage CreateRequestMessage(RequestData requestDat
189218 {
190219 requestMessage . Headers . TryAddWithoutValidation ( key , requestData . Headers . GetValues ( key ) ) ;
191220 }
221+ requestMessage . Headers . Connection . Clear ( ) ;
222+ requestMessage . Headers . ConnectionClose = false ;
223+ requestMessage . Headers . Connection . Add ( "Keep-Alive" ) ;
224+ //requestMessage.Headers.Connection;
192225
193226 requestMessage . Headers . Accept . Add ( new MediaTypeWithQualityHeaderValue ( requestData . Accept ) ) ;
194227
0 commit comments