22// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33
44using System ;
5+ using System . IO ;
56using System . Linq ;
67using System . Net ;
78using System . Net . Http ;
1415using Microsoft . AspNetCore . Server . Kestrel . Internal . Infrastructure ;
1516using Microsoft . AspNetCore . Testing ;
1617using Microsoft . Extensions . Internal ;
18+ using Microsoft . Extensions . Logging ;
1719using Microsoft . Extensions . Primitives ;
1820using Moq ;
1921using Xunit ;
@@ -85,7 +87,7 @@ public async Task IgnoreNullHeaderValues(string headerName, StringValues headerV
8587 app . Run ( async context =>
8688 {
8789 context . Response . Headers . Add ( headerName , headerValue ) ;
88-
90+
8991 await context . Response . WriteAsync ( "" ) ;
9092 } ) ;
9193 } ) ;
@@ -299,7 +301,7 @@ await connection.Receive(
299301 }
300302
301303 [ Fact ]
302- public async Task ResponseBodyNotWrittenOnHeadResponse ( )
304+ public async Task ResponseBodyNotWrittenOnHeadResponseAndLoggedOnlyOnce ( )
303305 {
304306 var mockKestrelTrace = new Mock < IKestrelTrace > ( ) ;
305307
@@ -324,7 +326,207 @@ await connection.Receive(
324326 }
325327
326328 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 ) ;
328530 }
329531
330532 public static TheoryData < string , StringValues , string > NullHeaderData
0 commit comments