@@ -59,6 +59,7 @@ public abstract partial class Frame : ConnectionContext, IFrameControl
59
59
60
60
protected RequestProcessingStatus _requestProcessingStatus ;
61
61
protected bool _keepAlive ;
62
+ private bool _canHaveBody ;
62
63
private bool _autoChunk ;
63
64
protected Exception _applicationException ;
64
65
@@ -486,17 +487,24 @@ public void Write(ArraySegment<byte> data)
486
487
{
487
488
ProduceStartAndFireOnStarting ( ) . GetAwaiter ( ) . GetResult ( ) ;
488
489
489
- if ( _autoChunk )
490
+ if ( _canHaveBody )
490
491
{
491
- if ( data . Count == 0 )
492
+ if ( _autoChunk )
492
493
{
493
- return ;
494
+ if ( data . Count == 0 )
495
+ {
496
+ return ;
497
+ }
498
+ WriteChunked ( data ) ;
499
+ }
500
+ else
501
+ {
502
+ SocketOutput . Write ( data ) ;
494
503
}
495
- WriteChunked ( data ) ;
496
504
}
497
505
else
498
506
{
499
- SocketOutput . Write ( data ) ;
507
+ HandleNonBodyResponseWrite ( data . Count ) ;
500
508
}
501
509
}
502
510
@@ -507,36 +515,53 @@ public Task WriteAsync(ArraySegment<byte> data, CancellationToken cancellationTo
507
515
return WriteAsyncAwaited ( data , cancellationToken ) ;
508
516
}
509
517
510
- if ( _autoChunk )
518
+ if ( _canHaveBody )
511
519
{
512
- if ( data . Count == 0 )
520
+ if ( _autoChunk )
513
521
{
514
- return TaskUtilities . CompletedTask ;
522
+ if ( data . Count == 0 )
523
+ {
524
+ return TaskUtilities . CompletedTask ;
525
+ }
526
+ return WriteChunkedAsync ( data , cancellationToken ) ;
527
+ }
528
+ else
529
+ {
530
+ return SocketOutput . WriteAsync ( data , cancellationToken : cancellationToken ) ;
515
531
}
516
- return WriteChunkedAsync ( data , cancellationToken ) ;
517
532
}
518
533
else
519
534
{
520
- return SocketOutput . WriteAsync ( data , cancellationToken : cancellationToken ) ;
535
+ HandleNonBodyResponseWrite ( data . Count ) ;
536
+ return TaskUtilities . CompletedTask ;
521
537
}
522
538
}
523
539
524
540
public async Task WriteAsyncAwaited ( ArraySegment < byte > data , CancellationToken cancellationToken )
525
541
{
526
542
await ProduceStartAndFireOnStarting ( ) ;
527
543
528
- if ( _autoChunk )
544
+ if ( _canHaveBody )
529
545
{
530
- if ( data . Count == 0 )
546
+ if ( _autoChunk )
531
547
{
532
- return ;
548
+ if ( data . Count == 0 )
549
+ {
550
+ return ;
551
+ }
552
+ await WriteChunkedAsync ( data , cancellationToken ) ;
553
+ }
554
+ else
555
+ {
556
+ await SocketOutput . WriteAsync ( data , cancellationToken : cancellationToken ) ;
533
557
}
534
- await WriteChunkedAsync ( data , cancellationToken ) ;
535
558
}
536
559
else
537
560
{
538
- await SocketOutput . WriteAsync ( data , cancellationToken : cancellationToken ) ;
561
+ HandleNonBodyResponseWrite ( data . Count ) ;
562
+ return ;
539
563
}
564
+
540
565
}
541
566
542
567
private void WriteChunked ( ArraySegment < byte > data )
@@ -664,19 +689,7 @@ protected Task ProduceEnd()
664
689
StatusCode = 500 ;
665
690
}
666
691
667
- ReasonPhrase = null ;
668
-
669
- var responseHeaders = FrameResponseHeaders ;
670
- responseHeaders . Reset ( ) ;
671
- var dateHeaderValues = DateHeaderValueManager . GetDateHeaderValues ( ) ;
672
-
673
- responseHeaders . SetRawDate ( dateHeaderValues . String , dateHeaderValues . Bytes ) ;
674
- responseHeaders . SetRawContentLength ( "0" , _bytesContentLengthZero ) ;
675
-
676
- if ( ServerOptions . AddServerHeader )
677
- {
678
- responseHeaders . SetRawServer ( Constants . ServerName , _bytesServer ) ;
679
- }
692
+ ErrorResetHeadersToDefaults ( ) ;
680
693
}
681
694
682
695
if ( ! HasResponseStarted )
@@ -729,10 +742,12 @@ private void CreateResponseHeader(
729
742
bool appCompleted )
730
743
{
731
744
var responseHeaders = FrameResponseHeaders ;
732
- responseHeaders . SetReadOnly ( ) ;
733
745
734
746
var hasConnection = responseHeaders . HasConnection ;
735
747
748
+ // Set whether response can have body
749
+ _canHaveBody = StatusCanHaveBody ( StatusCode ) && ! ReferenceEquals ( Method , KnownStrings . HttpHeadMethod ) ;
750
+
736
751
var end = SocketOutput . ProducingStart ( ) ;
737
752
if ( _keepAlive && hasConnection )
738
753
{
@@ -746,39 +761,48 @@ private void CreateResponseHeader(
746
761
}
747
762
}
748
763
749
- if ( _keepAlive && ! responseHeaders . HasTransferEncoding && ! responseHeaders . HasContentLength )
764
+ if ( _canHaveBody )
750
765
{
751
- if ( appCompleted )
766
+ if ( _keepAlive && ! responseHeaders . HasTransferEncoding && ! responseHeaders . HasContentLength )
752
767
{
753
- // Don't set the Content-Length or Transfer-Encoding headers
754
- // automatically for HEAD requests or 101, 204, 205, 304 responses.
755
- if ( Method != "HEAD" && StatusCanHaveBody ( StatusCode ) )
768
+ if ( appCompleted )
756
769
{
757
770
// Since the app has completed and we are only now generating
758
771
// the headers we can safely set the Content-Length to 0.
759
772
responseHeaders . SetRawContentLength ( "0" , _bytesContentLengthZero ) ;
760
773
}
761
- }
762
- else
763
- {
764
- // Note for future reference: never change this to set _autoChunk to true on HTTP/1.0
765
- // connections, even if we were to infer the client supports it because an HTTP/1.0 request
766
- // was received that used chunked encoding. Sending a chunked response to an HTTP/1.0
767
- // client would break compliance with RFC 7230 (section 3.3.1):
768
- //
769
- // A server MUST NOT send a response containing Transfer-Encoding unless the corresponding
770
- // request indicates HTTP/1.1 (or later).
771
- if ( _httpVersion == HttpVersionType . Http11 )
772
- {
773
- _autoChunk = true ;
774
- responseHeaders . SetRawTransferEncoding ( "chunked" , _bytesTransferEncodingChunked ) ;
775
- }
776
774
else
777
775
{
778
- _keepAlive = false ;
776
+ // Note for future reference: never change this to set _autoChunk to true on HTTP/1.0
777
+ // connections, even if we were to infer the client supports it because an HTTP/1.0 request
778
+ // was received that used chunked encoding. Sending a chunked response to an HTTP/1.0
779
+ // client would break compliance with RFC 7230 (section 3.3.1):
780
+ //
781
+ // A server MUST NOT send a response containing Transfer-Encoding unless the corresponding
782
+ // request indicates HTTP/1.1 (or later).
783
+ if ( _httpVersion == HttpVersionType . Http11 )
784
+ {
785
+ _autoChunk = true ;
786
+ responseHeaders . SetRawTransferEncoding ( "chunked" , _bytesTransferEncodingChunked ) ;
787
+ }
788
+ else
789
+ {
790
+ _keepAlive = false ;
791
+ }
779
792
}
780
793
}
781
794
}
795
+ else
796
+ {
797
+ // Don't set the Content-Length or Transfer-Encoding headers
798
+ // automatically for HEAD requests or 101, 204, 205, 304 responses.
799
+ if ( responseHeaders . HasTransferEncoding )
800
+ {
801
+ RejectNonBodyTransferEncodingResponse ( appCompleted ) ;
802
+ }
803
+ }
804
+
805
+ responseHeaders . SetReadOnly ( ) ;
782
806
783
807
if ( ! _keepAlive && ! hasConnection && _httpVersion != HttpVersionType . Http10 )
784
808
{
@@ -1232,10 +1256,65 @@ public bool StatusCanHaveBody(int statusCode)
1232
1256
statusCode != 304 ;
1233
1257
}
1234
1258
1259
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
1260
+ private void RejectNonBodyTransferEncodingResponse ( bool appCompleted )
1261
+ {
1262
+ var ex = new BadHttpResponseException ( $ "Transfer-Encoding set on a { StatusCode } non-body request.") ;
1263
+ if ( ! appCompleted )
1264
+ {
1265
+ // Back out of header creation surface exeception in user code
1266
+ _requestProcessingStatus = RequestProcessingStatus . RequestStarted ;
1267
+ throw ex ;
1268
+ }
1269
+ else
1270
+ {
1271
+ ReportApplicationError ( ex ) ;
1272
+ // 500 Internal Server Error
1273
+ StatusCode = 500 ;
1274
+
1275
+ ErrorResetHeadersToDefaults ( ) ;
1276
+ }
1277
+ }
1278
+
1279
+ private void ErrorResetHeadersToDefaults ( )
1280
+ {
1281
+ ReasonPhrase = null ;
1282
+
1283
+ var responseHeaders = FrameResponseHeaders ;
1284
+ responseHeaders . Reset ( ) ;
1285
+ var dateHeaderValues = DateHeaderValueManager . GetDateHeaderValues ( ) ;
1286
+
1287
+ responseHeaders . SetRawDate ( dateHeaderValues . String , dateHeaderValues . Bytes ) ;
1288
+ responseHeaders . SetRawContentLength ( "0" , _bytesContentLengthZero ) ;
1289
+
1290
+ if ( ServerOptions . AddServerHeader )
1291
+ {
1292
+ responseHeaders . SetRawServer ( Constants . ServerName , _bytesServer ) ;
1293
+ }
1294
+ }
1295
+
1296
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
1297
+ public void HandleNonBodyResponseWrite ( int count )
1298
+ {
1299
+ if ( ReferenceEquals ( Method , KnownStrings . HttpHeadMethod ) )
1300
+ {
1301
+ // Don't write to body for HEAD requests.
1302
+ if ( Log . IsEnabled ( LogLevel . Debug ) )
1303
+ {
1304
+ Log . ConnectionHeadResponseBodyWrite ( ConnectionId , count ) ;
1305
+ }
1306
+ }
1307
+ else
1308
+ {
1309
+ // Throw Exception for 101, 204, 205, 304 responses.
1310
+ throw new BadHttpResponseException ( $ "Write to non-body { StatusCode } response.") ;
1311
+ }
1312
+ }
1313
+
1235
1314
[ MethodImpl ( MethodImplOptions . NoInlining ) ]
1236
1315
private void ThrowResponseAlreadyStartedException ( string value )
1237
1316
{
1238
- throw new InvalidOperationException ( value + " cannot be set, response had already started." ) ;
1317
+ throw new BadHttpResponseException ( value + " cannot be set, response had already started." ) ;
1239
1318
}
1240
1319
1241
1320
[ MethodImpl ( MethodImplOptions . NoInlining ) ]
0 commit comments