@@ -406,7 +406,7 @@ public async Task RequestBodyWithStreamCloseWorks(string expected)
406406 // (1) The subsequent middleware reads right up to the buffer size (guided by the ContentLength header)
407407 while ( contentLengthBytesLeft > 0 )
408408 {
409- var res = await c . Request . Body . ReadAsync ( arr , 0 , ( int ) Math . Min ( arr . Length , contentLengthBytesLeft ) ) ;
409+ var res = await c . Request . Body . ReadAsync ( arr , 0 , arr . Length ) ;
410410 contentLengthBytesLeft -= res ;
411411 if ( res == 0 )
412412 {
@@ -1672,6 +1672,73 @@ public async Task CombineLogs_Exception_RequestLogged()
16721672 Assert . Equal ( lines . Length , i ) ;
16731673 }
16741674
1675+ [ Theory ]
1676+ [ InlineData ( HttpLoggingFields . RequestBody | HttpLoggingFields . ResponseBody , true , true ) ]
1677+ [ InlineData ( HttpLoggingFields . RequestBody , true , false ) ]
1678+ [ InlineData ( HttpLoggingFields . ResponseBody , false , true ) ]
1679+ public async Task CombineLogsWithStreamCloseWorks ( HttpLoggingFields fields , bool hasRequestBody , bool hasResponseBody )
1680+ {
1681+ var options = CreateOptionsAccessor ( ) ;
1682+ options . CurrentValue . LoggingFields = fields ;
1683+ options . CurrentValue . CombineLogs = true ;
1684+
1685+ var middleware = CreateMiddleware (
1686+ async c =>
1687+ {
1688+ var arr = new byte [ 4096 ] ;
1689+ var contentLengthBytesLeft = c . Request . Body . Length ;
1690+
1691+ // (1) The subsequent middleware reads right up to the buffer size (guided by the ContentLength header)
1692+ while ( contentLengthBytesLeft > 0 )
1693+ {
1694+ var res = await c . Request . Body . ReadAsync ( arr , 0 , arr . Length ) ;
1695+ contentLengthBytesLeft -= res ;
1696+ if ( res == 0 )
1697+ {
1698+ break ;
1699+ }
1700+ }
1701+
1702+ // (2) The subsequent middleware closes the request stream after its consumption
1703+ c . Request . Body . Close ( ) ;
1704+
1705+
1706+ c . Response . ContentType = "text/plain" ;
1707+
1708+ // (3) The subsequent middleware writes its response
1709+ await c . Response . WriteAsync ( "test response" ) ;
1710+
1711+ // (4) The subsequent middleware closes the response stream after it has completed writing to it
1712+ c . Response . Body . Close ( ) ;
1713+ } ,
1714+ options ) ;
1715+
1716+ var httpContext = new DefaultHttpContext ( ) ;
1717+ httpContext . Request . ContentType = "text/plain" ;
1718+ var requestBodyBuffer = Encoding . UTF8 . GetBytes ( "test request" ) ;
1719+ httpContext . Request . Body = new MemoryStream ( requestBodyBuffer ) ;
1720+ httpContext . Request . ContentLength = requestBodyBuffer . Length ;
1721+
1722+ await middleware . Invoke ( httpContext ) ;
1723+
1724+ var lines = Assert . Single ( TestSink . Writes . Where ( w => w . LogLevel >= LogLevel . Information ) ) . Message . Split ( Environment . NewLine ) ;
1725+ var i = 0 ;
1726+ Assert . Equal ( "Request and Response:" , lines [ i ++ ] ) ;
1727+ if ( fields . HasFlag ( HttpLoggingFields . RequestBody ) )
1728+ {
1729+ Assert . Equal ( "RequestBody: test request" , lines [ i ++ ] ) ;
1730+ // Here we expect "Only partially consumed by app" status as the middleware reads request body right to its end,
1731+ // but never further as it follows the ContentLength header. From logging middleware perspective it looks like
1732+ // a partial consumption as it can't know for sure if it has been drained to the end or not.
1733+ Assert . Equal ( "RequestBodyStatus: [Only partially consumed by app]" , lines [ i ++ ] ) ;
1734+ }
1735+ if ( fields . HasFlag ( HttpLoggingFields . ResponseBody ) )
1736+ {
1737+ Assert . Equal ( "ResponseBody: test response" , lines [ i ++ ] ) ;
1738+ }
1739+ Assert . Equal ( lines . Length , i ) ;
1740+ }
1741+
16751742 [ Fact ]
16761743 public async Task ResponseInterceptorCanDisableResponseLogs ( )
16771744 {
0 commit comments