66using System . Linq ;
77using System . Net ;
88using System . Net . Http ;
9+ using System . Net . Sockets ;
910using System . Text ;
1011using System . Threading . Tasks ;
1112using Microsoft . AspNetCore . InternalTesting ;
@@ -207,20 +208,66 @@ public async Task ResponseHeaders_11HeadRequestStatusCodeWithoutBody_NoContentLe
207208 }
208209
209210 [ ConditionalFact ]
210- public async Task ResponseHeaders_HTTP10KeepAliveRequest_Gets11Close ( )
211+ public async Task ResponseHeaders_HTTP10KeepAliveRequest_KeepAliveHeader_Gets11NoClose ( )
212+ {
213+ string address ;
214+ using ( var server = Utilities . CreateHttpServer ( out address ) )
215+ {
216+ // Track the number of times ConnectCallback is invoked to ensure the underlying socket wasn't closed.
217+ int connectCallbackInvocations = 0 ;
218+ var handler = new SocketsHttpHandler ( ) ;
219+ handler . ConnectCallback = ( context , cancellationToken ) =>
220+ {
221+ Interlocked . Increment ( ref connectCallbackInvocations ) ;
222+ return ConnectCallback ( context , cancellationToken ) ;
223+ } ;
224+
225+ using ( var client = new HttpClient ( handler ) )
226+ {
227+ // Send the first request
228+ Task < HttpResponseMessage > responseTask = SendRequestAsync ( address , usehttp11 : false , sendKeepAlive : true , httpClient : client ) ;
229+ var context = await server . AcceptAsync ( Utilities . DefaultTimeout ) . Before ( responseTask ) ;
230+ context . Dispose ( ) ;
231+
232+ HttpResponseMessage response = await responseTask ;
233+ response . EnsureSuccessStatusCode ( ) ;
234+ Assert . Equal ( new Version ( 1 , 1 ) , response . Version ) ;
235+ Assert . Null ( response . Headers . ConnectionClose ) ;
236+
237+ // Send the second request
238+ responseTask = SendRequestAsync ( address , usehttp11 : false , sendKeepAlive : true , httpClient : client ) ;
239+ context = await server . AcceptAsync ( Utilities . DefaultTimeout ) . Before ( responseTask ) ;
240+ context . Dispose ( ) ;
241+
242+ response = await responseTask ;
243+ response . EnsureSuccessStatusCode ( ) ;
244+ Assert . Equal ( new Version ( 1 , 1 ) , response . Version ) ;
245+ Assert . Null ( response . Headers . ConnectionClose ) ;
246+ }
247+
248+ // Verify that ConnectCallback was only called once
249+ Assert . Equal ( 1 , connectCallbackInvocations ) ;
250+ }
251+ }
252+
253+ [ ConditionalFact ]
254+ public async Task ResponseHeaders_HTTP10KeepAliveRequest_ChunkedTransferEncoding_Gets11Close ( )
211255 {
212256 string address ;
213257 using ( var server = Utilities . CreateHttpServer ( out address ) )
214258 {
215- // Http.Sys does not support 1.0 keep-alives.
216259 Task < HttpResponseMessage > responseTask = SendRequestAsync ( address , usehttp11 : false , sendKeepAlive : true ) ;
217260
218261 var context = await server . AcceptAsync ( Utilities . DefaultTimeout ) . Before ( responseTask ) ;
262+ context . Response . Headers [ "Transfer-Encoding" ] = new string [ ] { "chunked" } ;
263+ var responseBytes = Encoding . ASCII . GetBytes ( "10\r \n Manually Chunked\r \n 0\r \n \r \n " ) ;
264+ await context . Response . Body . WriteAsync ( responseBytes , 0 , responseBytes . Length ) ;
219265 context . Dispose ( ) ;
220266
221267 HttpResponseMessage response = await responseTask ;
222268 response . EnsureSuccessStatusCode ( ) ;
223269 Assert . Equal ( new Version ( 1 , 1 ) , response . Version ) ;
270+ Assert . True ( response . Headers . TransferEncodingChunked . HasValue , "Chunked" ) ;
224271 Assert . True ( response . Headers . ConnectionClose . Value ) ;
225272 }
226273 }
@@ -289,8 +336,9 @@ public async Task AddingControlCharactersToHeadersThrows(string key, string valu
289336 }
290337 }
291338
292- private async Task < HttpResponseMessage > SendRequestAsync ( string uri , bool usehttp11 = true , bool sendKeepAlive = false )
339+ private async Task < HttpResponseMessage > SendRequestAsync ( string uri , bool usehttp11 = true , bool sendKeepAlive = false , HttpClient httpClient = null )
293340 {
341+ httpClient ??= _client ;
294342 var request = new HttpRequestMessage ( HttpMethod . Get , uri ) ;
295343 if ( ! usehttp11 )
296344 {
@@ -300,7 +348,7 @@ private async Task<HttpResponseMessage> SendRequestAsync(string uri, bool usehtt
300348 {
301349 request . Headers . Add ( "Connection" , "Keep-Alive" ) ;
302350 }
303- return await _client . SendAsync ( request ) ;
351+ return await httpClient . SendAsync ( request ) ;
304352 }
305353
306354 private async Task < HttpResponseMessage > SendHeadRequestAsync ( string uri , bool usehttp11 = true )
@@ -312,4 +360,19 @@ private async Task<HttpResponseMessage> SendHeadRequestAsync(string uri, bool us
312360 }
313361 return await _client . SendAsync ( request ) ;
314362 }
363+
364+ private static async ValueTask < Stream > ConnectCallback ( SocketsHttpConnectionContext connectContext , CancellationToken ct )
365+ {
366+ var s = new Socket ( SocketType . Stream , ProtocolType . Tcp ) { NoDelay = true } ;
367+ try
368+ {
369+ await s . ConnectAsync ( connectContext . DnsEndPoint , ct ) ;
370+ return new NetworkStream ( s , ownsSocket : true ) ;
371+ }
372+ catch
373+ {
374+ s . Dispose ( ) ;
375+ throw ;
376+ }
377+ }
315378}
0 commit comments