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 }
@@ -284,8 +348,9 @@ public async Task AddingControlCharactersToHeadersThrows(string key, string valu
284348 }
285349 }
286350
287- private async Task < HttpResponseMessage > SendRequestAsync ( string uri , bool usehttp11 = true , bool sendKeepAlive = false )
351+ private async Task < HttpResponseMessage > SendRequestAsync ( string uri , bool usehttp11 = true , bool sendKeepAlive = false , HttpClient httpClient = null )
288352 {
353+ httpClient ??= _client ;
289354 var request = new HttpRequestMessage ( HttpMethod . Get , uri ) ;
290355 if ( ! usehttp11 )
291356 {
@@ -295,7 +360,7 @@ private async Task<HttpResponseMessage> SendRequestAsync(string uri, bool usehtt
295360 {
296361 request . Headers . Add ( "Connection" , "Keep-Alive" ) ;
297362 }
298- return await _client . SendAsync ( request ) ;
363+ return await httpClient . SendAsync ( request ) ;
299364 }
300365
301366 private async Task < HttpResponseMessage > SendHeadRequestAsync ( string uri , bool usehttp11 = true )
@@ -307,5 +372,20 @@ private async Task<HttpResponseMessage> SendHeadRequestAsync(string uri, bool us
307372 }
308373 return await _client . SendAsync ( request ) ;
309374 }
375+
376+ private static async ValueTask < Stream > ConnectCallback ( SocketsHttpConnectionContext connectContext , CancellationToken ct )
377+ {
378+ var s = new Socket ( SocketType . Stream , ProtocolType . Tcp ) { NoDelay = true } ;
379+ try
380+ {
381+ await s . ConnectAsync ( connectContext . DnsEndPoint , ct ) ;
382+ return new NetworkStream ( s , ownsSocket : true ) ;
383+ }
384+ catch
385+ {
386+ s . Dispose ( ) ;
387+ throw ;
388+ }
389+ }
310390 }
311391}
0 commit comments