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 . Testing ;
@@ -207,20 +208,83 @@ public async Task ResponseHeaders_11HeadRequestStatusCodeWithoutBody_NoContentLe
207208 }
208209
209210 [ ConditionalFact ]
210- public async Task ResponseHeaders_HTTP10KeepAliveRequest_Gets11Close ( )
211+ public async Task ResponseHeaders_HTTP10KeepAliveRequest_KeepAliveHeader_RespectsSwitch ( )
212+ {
213+ string address ;
214+ using ( var server = Utilities . CreateHttpServer ( out address , respectHttp10KeepAlive : true ) )
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+ using ( var server = Utilities . CreateHttpServer ( out address , respectHttp10KeepAlive : false ) )
253+ {
254+ var handler = new SocketsHttpHandler ( ) ;
255+ using ( var client = new HttpClient ( handler ) )
256+ {
257+ // Send the first request
258+ Task < HttpResponseMessage > responseTask = SendRequestAsync ( address , usehttp11 : false , sendKeepAlive : true , httpClient : client ) ;
259+ var context = await server . AcceptAsync ( Utilities . DefaultTimeout ) . Before ( responseTask ) ;
260+ context . Dispose ( ) ;
261+
262+ HttpResponseMessage response = await responseTask ;
263+ response . EnsureSuccessStatusCode ( ) ;
264+ Assert . Equal ( new Version ( 1 , 1 ) , response . Version ) ;
265+ Assert . True ( response . Headers . ConnectionClose . Value ) ;
266+ }
267+ }
268+ }
269+
270+ [ ConditionalFact ]
271+ public async Task ResponseHeaders_HTTP10KeepAliveRequest_ChunkedTransferEncoding_Gets11Close ( )
211272 {
212273 string address ;
213274 using ( var server = Utilities . CreateHttpServer ( out address ) )
214275 {
215- // Http.Sys does not support 1.0 keep-alives.
216276 Task < HttpResponseMessage > responseTask = SendRequestAsync ( address , usehttp11 : false , sendKeepAlive : true ) ;
217277
218278 var context = await server . AcceptAsync ( Utilities . DefaultTimeout ) . Before ( responseTask ) ;
279+ context . Response . Headers [ "Transfer-Encoding" ] = new string [ ] { "chunked" } ;
280+ var responseBytes = Encoding . ASCII . GetBytes ( "10\r \n Manually Chunked\r \n 0\r \n \r \n " ) ;
281+ await context . Response . Body . WriteAsync ( responseBytes , 0 , responseBytes . Length ) ;
219282 context . Dispose ( ) ;
220283
221284 HttpResponseMessage response = await responseTask ;
222285 response . EnsureSuccessStatusCode ( ) ;
223286 Assert . Equal ( new Version ( 1 , 1 ) , response . Version ) ;
287+ Assert . True ( response . Headers . TransferEncodingChunked . HasValue , "Chunked" ) ;
224288 Assert . True ( response . Headers . ConnectionClose . Value ) ;
225289 }
226290 }
@@ -289,8 +353,9 @@ public async Task AddingControlCharactersToHeadersThrows(string key, string valu
289353 }
290354 }
291355
292- private async Task < HttpResponseMessage > SendRequestAsync ( string uri , bool usehttp11 = true , bool sendKeepAlive = false )
356+ private async Task < HttpResponseMessage > SendRequestAsync ( string uri , bool usehttp11 = true , bool sendKeepAlive = false , HttpClient httpClient = null )
293357 {
358+ httpClient ??= _client ;
294359 var request = new HttpRequestMessage ( HttpMethod . Get , uri ) ;
295360 if ( ! usehttp11 )
296361 {
@@ -300,7 +365,7 @@ private async Task<HttpResponseMessage> SendRequestAsync(string uri, bool usehtt
300365 {
301366 request . Headers . Add ( "Connection" , "Keep-Alive" ) ;
302367 }
303- return await _client . SendAsync ( request ) ;
368+ return await httpClient . SendAsync ( request ) ;
304369 }
305370
306371 private async Task < HttpResponseMessage > SendHeadRequestAsync ( string uri , bool usehttp11 = true )
@@ -312,4 +377,19 @@ private async Task<HttpResponseMessage> SendHeadRequestAsync(string uri, bool us
312377 }
313378 return await _client . SendAsync ( request ) ;
314379 }
380+
381+ private static async ValueTask < Stream > ConnectCallback ( SocketsHttpConnectionContext connectContext , CancellationToken ct )
382+ {
383+ var s = new Socket ( SocketType . Stream , ProtocolType . Tcp ) { NoDelay = true } ;
384+ try
385+ {
386+ await s . ConnectAsync ( connectContext . DnsEndPoint , ct ) ;
387+ return new NetworkStream ( s , ownsSocket : true ) ;
388+ }
389+ catch
390+ {
391+ s . Dispose ( ) ;
392+ throw ;
393+ }
394+ }
315395}
0 commit comments