@@ -390,6 +390,46 @@ public async Task RequestBodyCopyToAsyncWorks(string expected)
390
390
Assert . Contains ( TestSink . Writes , w => w . Message . Contains ( expected ) ) ;
391
391
}
392
392
393
+ [ Theory ]
394
+ [ MemberData ( nameof ( BodyData ) ) ]
395
+ public async Task RequestBodyWithStreamCloseWorks ( string expected )
396
+ {
397
+ var options = CreateOptionsAccessor ( ) ;
398
+ options . CurrentValue . LoggingFields = HttpLoggingFields . RequestBody ;
399
+
400
+ var middleware = CreateMiddleware (
401
+ async c =>
402
+ {
403
+ var arr = new byte [ 4096 ] ;
404
+ var contentLengthBytesLeft = c . Request . Body . Length ;
405
+
406
+ // (1) The subsequent middleware reads right up to the buffer size (guided by the ContentLength header)
407
+ while ( contentLengthBytesLeft > 0 )
408
+ {
409
+ var res = await c . Request . Body . ReadAsync ( arr , 0 , arr . Length ) ;
410
+ contentLengthBytesLeft -= res ;
411
+ if ( res == 0 )
412
+ {
413
+ break ;
414
+ }
415
+ }
416
+
417
+ // (2) The subsequent middleware closes the request stream after its consumption
418
+ c . Request . Body . Close ( ) ;
419
+ } ,
420
+ options ) ;
421
+
422
+ var httpContext = new DefaultHttpContext ( ) ;
423
+ httpContext . Request . ContentType = "text/plain" ;
424
+ var buffer = Encoding . UTF8 . GetBytes ( expected ) ;
425
+ httpContext . Request . Body = new MemoryStream ( buffer ) ;
426
+ httpContext . Request . ContentLength = buffer . Length ;
427
+
428
+ await middleware . Invoke ( httpContext ) ;
429
+
430
+ Assert . Contains ( TestSink . Writes , w => w . Message . Contains ( expected ) ) ;
431
+ }
432
+
393
433
[ Fact ]
394
434
public async Task RequestBodyReadingLimitLongCharactersWorks ( )
395
435
{
@@ -1155,6 +1195,32 @@ public async Task StartAsyncResponseHeadersLogged()
1155
1195
await middlewareTask ;
1156
1196
}
1157
1197
1198
+ [ Theory ]
1199
+ [ MemberData ( nameof ( BodyData ) ) ]
1200
+ public async Task ResponseBodyWithStreamCloseWorks ( string expected )
1201
+ {
1202
+ var options = CreateOptionsAccessor ( ) ;
1203
+ options . CurrentValue . LoggingFields = HttpLoggingFields . ResponseBody ;
1204
+ var middleware = CreateMiddleware (
1205
+ async c =>
1206
+ {
1207
+ c . Response . ContentType = "text/plain" ;
1208
+
1209
+ // (1) The subsequent middleware writes its response
1210
+ await c . Response . WriteAsync ( expected ) ;
1211
+
1212
+ // (2) The subsequent middleware closes the response stream after it has completed writing to it
1213
+ c . Response . Body . Close ( ) ;
1214
+ } ,
1215
+ options ) ;
1216
+
1217
+ var httpContext = new DefaultHttpContext ( ) ;
1218
+
1219
+ await middleware . Invoke ( httpContext ) ;
1220
+
1221
+ Assert . Contains ( TestSink . Writes , w => w . Message . Contains ( expected ) ) ;
1222
+ }
1223
+
1158
1224
[ Fact ]
1159
1225
public async Task UnrecognizedMediaType ( )
1160
1226
{
@@ -1606,6 +1672,72 @@ public async Task CombineLogs_Exception_RequestLogged()
1606
1672
Assert . Equal ( lines . Length , i ) ;
1607
1673
}
1608
1674
1675
+ [ Theory ]
1676
+ [ InlineData ( HttpLoggingFields . RequestBody | HttpLoggingFields . ResponseBody ) ]
1677
+ [ InlineData ( HttpLoggingFields . RequestBody ) ]
1678
+ [ InlineData ( HttpLoggingFields . ResponseBody ) ]
1679
+ public async Task CombineLogsWithStreamCloseWorks ( HttpLoggingFields fields )
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
+ c . Response . ContentType = "text/plain" ;
1706
+
1707
+ // (3) The subsequent middleware writes its response
1708
+ await c . Response . WriteAsync ( "test response" ) ;
1709
+
1710
+ // (4) The subsequent middleware closes the response stream after it has completed writing to it
1711
+ c . Response . Body . Close ( ) ;
1712
+ } ,
1713
+ options ) ;
1714
+
1715
+ var httpContext = new DefaultHttpContext ( ) ;
1716
+ httpContext . Request . ContentType = "text/plain" ;
1717
+ var requestBodyBuffer = Encoding . UTF8 . GetBytes ( "test request" ) ;
1718
+ httpContext . Request . Body = new MemoryStream ( requestBodyBuffer ) ;
1719
+ httpContext . Request . ContentLength = requestBodyBuffer . Length ;
1720
+
1721
+ await middleware . Invoke ( httpContext ) ;
1722
+
1723
+ var lines = Assert . Single ( TestSink . Writes . Where ( w => w . LogLevel >= LogLevel . Information ) ) . Message . Split ( Environment . NewLine ) ;
1724
+ var i = 0 ;
1725
+ Assert . Equal ( "Request and Response:" , lines [ i ++ ] ) ;
1726
+ if ( fields . HasFlag ( HttpLoggingFields . RequestBody ) )
1727
+ {
1728
+ Assert . Equal ( "RequestBody: test request" , lines [ i ++ ] ) ;
1729
+ // Here we expect "Only partially consumed by app" status as the middleware reads request body right to its end,
1730
+ // but never further as it follows the ContentLength header. From logging middleware perspective it looks like
1731
+ // a partial consumption as it can't know for sure if it has been drained to the end or not.
1732
+ Assert . Equal ( "RequestBodyStatus: [Only partially consumed by app]" , lines [ i ++ ] ) ;
1733
+ }
1734
+ if ( fields . HasFlag ( HttpLoggingFields . ResponseBody ) )
1735
+ {
1736
+ Assert . Equal ( "ResponseBody: test response" , lines [ i ++ ] ) ;
1737
+ }
1738
+ Assert . Equal ( lines . Length , i ) ;
1739
+ }
1740
+
1609
1741
[ Fact ]
1610
1742
public async Task ResponseInterceptorCanDisableResponseLogs ( )
1611
1743
{
0 commit comments