2
2
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3
3
4
4
using System ;
5
+ using System . IO ;
5
6
using System . Linq ;
6
7
using System . Net ;
7
8
using System . Net . Http ;
14
15
using Microsoft . AspNetCore . Server . Kestrel . Internal . Infrastructure ;
15
16
using Microsoft . AspNetCore . Testing ;
16
17
using Microsoft . Extensions . Internal ;
18
+ using Microsoft . Extensions . Logging ;
17
19
using Microsoft . Extensions . Primitives ;
18
20
using Moq ;
19
21
using Xunit ;
@@ -85,7 +87,7 @@ public async Task IgnoreNullHeaderValues(string headerName, StringValues headerV
85
87
app . Run ( async context =>
86
88
{
87
89
context . Response . Headers . Add ( headerName , headerValue ) ;
88
-
90
+
89
91
await context . Response . WriteAsync ( "" ) ;
90
92
} ) ;
91
93
} ) ;
@@ -299,7 +301,7 @@ await connection.Receive(
299
301
}
300
302
301
303
[ Fact ]
302
- public async Task ResponseBodyNotWrittenOnHeadResponse ( )
304
+ public async Task ResponseBodyNotWrittenOnHeadResponseAndLoggedOnlyOnce ( )
303
305
{
304
306
var mockKestrelTrace = new Mock < IKestrelTrace > ( ) ;
305
307
@@ -324,7 +326,207 @@ await connection.Receive(
324
326
}
325
327
326
328
mockKestrelTrace . Verify ( kestrelTrace =>
327
- kestrelTrace . ConnectionHeadResponseBodyWrite ( It . IsAny < string > ( ) , "hello, world" . Length ) ) ;
329
+ kestrelTrace . ConnectionHeadResponseBodyWrite ( It . IsAny < string > ( ) , "hello, world" . Length ) , Times . Once ) ;
330
+ }
331
+
332
+ [ Fact ]
333
+ public async Task WhenAppWritesMoreThanContentLengthWriteThrowsAndConnectionCloses ( )
334
+ {
335
+ var testLogger = new TestApplicationErrorLogger ( ) ;
336
+ var serviceContext = new TestServiceContext { Log = new TestKestrelTrace ( testLogger ) } ;
337
+
338
+ using ( var server = new TestServer ( httpContext =>
339
+ {
340
+ httpContext . Response . ContentLength = 11 ;
341
+ httpContext . Response . Body . Write ( Encoding . ASCII . GetBytes ( "hello," ) , 0 , 6 ) ;
342
+ httpContext . Response . Body . Write ( Encoding . ASCII . GetBytes ( " world" ) , 0 , 6 ) ;
343
+ return TaskCache . CompletedTask ;
344
+ } , serviceContext ) )
345
+ {
346
+ using ( var connection = server . CreateConnection ( ) )
347
+ {
348
+ await connection . Send (
349
+ "GET / HTTP/1.1" ,
350
+ "" ,
351
+ "" ) ;
352
+ await connection . ReceiveEnd (
353
+ $ "HTTP/1.1 200 OK",
354
+ $ "Date: { server . Context . DateHeaderValue } ",
355
+ "Content-Length: 11" ,
356
+ "" ,
357
+ "hello," ) ;
358
+ }
359
+ }
360
+
361
+ var logMessage = Assert . Single ( testLogger . Messages , message => message . LogLevel == LogLevel . Error ) ;
362
+ Assert . Equal (
363
+ $ "Response content length mismatch: wrote 12 bytes to 11-byte response.",
364
+ logMessage . Exception . Message ) ;
365
+ }
366
+
367
+ [ Fact ]
368
+ public async Task WhenAppWritesMoreThanContentLengthWriteAsyncThrowsAndConnectionCloses ( )
369
+ {
370
+ var testLogger = new TestApplicationErrorLogger ( ) ;
371
+ var serviceContext = new TestServiceContext { Log = new TestKestrelTrace ( testLogger ) } ;
372
+
373
+ using ( var server = new TestServer ( async httpContext =>
374
+ {
375
+ httpContext . Response . ContentLength = 11 ;
376
+ await httpContext . Response . WriteAsync ( "hello," ) ;
377
+ await httpContext . Response . WriteAsync ( " world" ) ;
378
+ } , serviceContext ) )
379
+ {
380
+ using ( var connection = server . CreateConnection ( ) )
381
+ {
382
+ await connection . Send (
383
+ "GET / HTTP/1.1" ,
384
+ "" ,
385
+ "" ) ;
386
+ await connection . ReceiveEnd (
387
+ $ "HTTP/1.1 200 OK",
388
+ $ "Date: { server . Context . DateHeaderValue } ",
389
+ "Content-Length: 11" ,
390
+ "" ,
391
+ "hello," ) ;
392
+ }
393
+ }
394
+
395
+ var logMessage = Assert . Single ( testLogger . Messages , message => message . LogLevel == LogLevel . Error ) ;
396
+ Assert . Equal (
397
+ $ "Response content length mismatch: wrote 12 bytes to 11-byte response.",
398
+ logMessage . Exception . Message ) ;
399
+ }
400
+
401
+ [ Fact ]
402
+ public async Task WhenAppWritesMoreThanContentLengthAndResponseNotStarted500ResponseSentAndConnectionCloses ( )
403
+ {
404
+ var testLogger = new TestApplicationErrorLogger ( ) ;
405
+ var serviceContext = new TestServiceContext { Log = new TestKestrelTrace ( testLogger ) } ;
406
+
407
+ using ( var server = new TestServer ( async httpContext =>
408
+ {
409
+ httpContext . Response . ContentLength = 5 ;
410
+ await httpContext . Response . WriteAsync ( "hello, world" ) ;
411
+ } , serviceContext ) )
412
+ {
413
+ using ( var connection = server . CreateConnection ( ) )
414
+ {
415
+ await connection . Send (
416
+ "GET / HTTP/1.1" ,
417
+ "" ,
418
+ "" ) ;
419
+ await connection . ReceiveEnd (
420
+ $ "HTTP/1.1 500 Internal Server Error",
421
+ "Connection: close" ,
422
+ $ "Date: { server . Context . DateHeaderValue } ",
423
+ "Content-Length: 0" ,
424
+ "" ,
425
+ "" ) ;
426
+ }
427
+ }
428
+
429
+ var logMessage = Assert . Single ( testLogger . Messages , message => message . LogLevel == LogLevel . Error ) ;
430
+ Assert . Equal (
431
+ $ "Response content length mismatch: wrote 12 bytes to 5-byte response.",
432
+ logMessage . Exception . Message ) ;
433
+ }
434
+
435
+ [ Fact ]
436
+ public async Task WhenAppWritesLessThanContentLengthErrorLogged ( )
437
+ {
438
+ var testLogger = new TestApplicationErrorLogger ( ) ;
439
+ var serviceContext = new TestServiceContext { Log = new TestKestrelTrace ( testLogger ) } ;
440
+
441
+ using ( var server = new TestServer ( async httpContext =>
442
+ {
443
+ httpContext . Response . ContentLength = 13 ;
444
+ await httpContext . Response . WriteAsync ( "hello, world" ) ;
445
+ } , serviceContext ) )
446
+ {
447
+ using ( var connection = server . CreateConnection ( ) )
448
+ {
449
+ await connection . Send (
450
+ "GET / HTTP/1.1" ,
451
+ "" ,
452
+ "" ) ;
453
+ await connection . ReceiveEnd (
454
+ $ "HTTP/1.1 200 OK",
455
+ $ "Date: { server . Context . DateHeaderValue } ",
456
+ "Content-Length: 13" ,
457
+ "" ,
458
+ "hello, world" ) ;
459
+ }
460
+ }
461
+
462
+ var errorMessage = Assert . Single ( testLogger . Messages , message => message . LogLevel == LogLevel . Error ) ;
463
+ Assert . Equal (
464
+ $ "Response content length mismatch: wrote 12 bytes to 13-byte response.",
465
+ errorMessage . Exception . Message ) ;
466
+ }
467
+
468
+ [ Fact ]
469
+ public async Task WhenAppSetsContentLengthButDoesNotWriteBody500ResponseSentAndConnectionCloses ( )
470
+ {
471
+ var testLogger = new TestApplicationErrorLogger ( ) ;
472
+ var serviceContext = new TestServiceContext { Log = new TestKestrelTrace ( testLogger ) } ;
473
+
474
+ using ( var server = new TestServer ( httpContext =>
475
+ {
476
+ httpContext . Response . ContentLength = 5 ;
477
+ return TaskCache . CompletedTask ;
478
+ } , serviceContext ) )
479
+ {
480
+ using ( var connection = server . CreateConnection ( ) )
481
+ {
482
+ await connection . Send (
483
+ "GET / HTTP/1.1" ,
484
+ "" ,
485
+ "" ) ;
486
+ await connection . ReceiveEnd (
487
+ $ "HTTP/1.1 500 Internal Server Error",
488
+ "Connection: close" ,
489
+ $ "Date: { server . Context . DateHeaderValue } ",
490
+ "Content-Length: 0" ,
491
+ "" ,
492
+ "" ) ;
493
+ }
494
+ }
495
+
496
+ var errorMessage = Assert . Single ( testLogger . Messages , message => message . LogLevel == LogLevel . Error ) ;
497
+ Assert . Equal (
498
+ $ "Response content length mismatch: wrote 0 bytes to 5-byte response.",
499
+ errorMessage . Exception . Message ) ;
500
+ }
501
+
502
+ [ Fact ]
503
+ public async Task WhenAppSetsContentLengthToZeroAndDoesNotWriteNoErrorIsThrown ( )
504
+ {
505
+ var testLogger = new TestApplicationErrorLogger ( ) ;
506
+ var serviceContext = new TestServiceContext { Log = new TestKestrelTrace ( testLogger ) } ;
507
+
508
+ using ( var server = new TestServer ( httpContext =>
509
+ {
510
+ httpContext . Response . ContentLength = 0 ;
511
+ return TaskCache . CompletedTask ;
512
+ } , serviceContext ) )
513
+ {
514
+ using ( var connection = server . CreateConnection ( ) )
515
+ {
516
+ await connection . Send (
517
+ "GET / HTTP/1.1" ,
518
+ "" ,
519
+ "" ) ;
520
+ await connection . Receive (
521
+ $ "HTTP/1.1 200 OK",
522
+ $ "Date: { server . Context . DateHeaderValue } ",
523
+ "Content-Length: 0" ,
524
+ "" ,
525
+ "" ) ;
526
+ }
527
+ }
528
+
529
+ Assert . DoesNotContain ( testLogger . Messages , message => message . LogLevel == LogLevel . Error ) ;
328
530
}
329
531
330
532
public static TheoryData < string , StringValues , string > NullHeaderData
0 commit comments