6
6
using System . Linq ;
7
7
using System . Net ;
8
8
using System . Net . Http ;
9
+ using System . Net . Sockets ;
9
10
using System . Text ;
10
11
using System . Threading . Tasks ;
11
12
using Microsoft . AspNetCore . Testing ;
@@ -207,20 +208,83 @@ public async Task ResponseHeaders_11HeadRequestStatusCodeWithoutBody_NoContentLe
207
208
}
208
209
209
210
[ 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 ( )
211
272
{
212
273
string address ;
213
274
using ( var server = Utilities . CreateHttpServer ( out address ) )
214
275
{
215
- // Http.Sys does not support 1.0 keep-alives.
216
276
Task < HttpResponseMessage > responseTask = SendRequestAsync ( address , usehttp11 : false , sendKeepAlive : true ) ;
217
277
218
278
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 ) ;
219
282
context . Dispose ( ) ;
220
283
221
284
HttpResponseMessage response = await responseTask ;
222
285
response . EnsureSuccessStatusCode ( ) ;
223
286
Assert . Equal ( new Version ( 1 , 1 ) , response . Version ) ;
287
+ Assert . True ( response . Headers . TransferEncodingChunked . HasValue , "Chunked" ) ;
224
288
Assert . True ( response . Headers . ConnectionClose . Value ) ;
225
289
}
226
290
}
@@ -289,8 +353,9 @@ public async Task AddingControlCharactersToHeadersThrows(string key, string valu
289
353
}
290
354
}
291
355
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 )
293
357
{
358
+ httpClient ??= _client ;
294
359
var request = new HttpRequestMessage ( HttpMethod . Get , uri ) ;
295
360
if ( ! usehttp11 )
296
361
{
@@ -300,7 +365,7 @@ private async Task<HttpResponseMessage> SendRequestAsync(string uri, bool usehtt
300
365
{
301
366
request . Headers . Add ( "Connection" , "Keep-Alive" ) ;
302
367
}
303
- return await _client . SendAsync ( request ) ;
368
+ return await httpClient . SendAsync ( request ) ;
304
369
}
305
370
306
371
private async Task < HttpResponseMessage > SendHeadRequestAsync ( string uri , bool usehttp11 = true )
@@ -312,4 +377,19 @@ private async Task<HttpResponseMessage> SendHeadRequestAsync(string uri, bool us
312
377
}
313
378
return await _client . SendAsync ( request ) ;
314
379
}
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
+ }
315
395
}
0 commit comments