@@ -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
@@ -131,7 +132,7 @@ public int StatusCode
131
132
{
132
133
if ( HasResponseStarted )
133
134
{
134
- ThrowResponseAlreadyStartedException ( nameof ( StatusCode ) ) ;
135
+ throw BadHttpResponseException . GetException ( ResponseRejectionReasons . ValueCannotBeSetResponseStarted , nameof ( StatusCode ) ) ;
135
136
}
136
137
137
138
_statusCode = value ;
@@ -149,7 +150,7 @@ public string ReasonPhrase
149
150
{
150
151
if ( HasResponseStarted )
151
152
{
152
- ThrowResponseAlreadyStartedException ( nameof ( ReasonPhrase ) ) ;
153
+ throw BadHttpResponseException . GetException ( ResponseRejectionReasons . ValueCannotBeSetResponseStarted , nameof ( ReasonPhrase ) ) ;
153
154
}
154
155
155
156
_reasonPhrase = value ;
@@ -471,17 +472,24 @@ public void Write(ArraySegment<byte> data)
471
472
{
472
473
ProduceStartAndFireOnStarting ( ) . GetAwaiter ( ) . GetResult ( ) ;
473
474
474
- if ( _autoChunk )
475
+ if ( _canHaveBody )
475
476
{
476
- if ( data . Count == 0 )
477
+ if ( _autoChunk )
478
+ {
479
+ if ( data . Count == 0 )
480
+ {
481
+ return ;
482
+ }
483
+ WriteChunked ( data ) ;
484
+ }
485
+ else
477
486
{
478
- return ;
487
+ SocketOutput . Write ( data ) ;
479
488
}
480
- WriteChunked ( data ) ;
481
489
}
482
490
else
483
491
{
484
- SocketOutput . Write ( data ) ;
492
+ HandleNonBodyResponseWrite ( data . Count ) ;
485
493
}
486
494
}
487
495
@@ -492,36 +500,53 @@ public Task WriteAsync(ArraySegment<byte> data, CancellationToken cancellationTo
492
500
return WriteAsyncAwaited ( data , cancellationToken ) ;
493
501
}
494
502
495
- if ( _autoChunk )
503
+ if ( _canHaveBody )
496
504
{
497
- if ( data . Count == 0 )
505
+ if ( _autoChunk )
498
506
{
499
- return TaskUtilities . CompletedTask ;
507
+ if ( data . Count == 0 )
508
+ {
509
+ return TaskUtilities . CompletedTask ;
510
+ }
511
+ return WriteChunkedAsync ( data , cancellationToken ) ;
512
+ }
513
+ else
514
+ {
515
+ return SocketOutput . WriteAsync ( data , cancellationToken : cancellationToken ) ;
500
516
}
501
- return WriteChunkedAsync ( data , cancellationToken ) ;
502
517
}
503
518
else
504
519
{
505
- return SocketOutput . WriteAsync ( data , cancellationToken : cancellationToken ) ;
520
+ HandleNonBodyResponseWrite ( data . Count ) ;
521
+ return TaskUtilities . CompletedTask ;
506
522
}
507
523
}
508
524
509
525
public async Task WriteAsyncAwaited ( ArraySegment < byte > data , CancellationToken cancellationToken )
510
526
{
511
527
await ProduceStartAndFireOnStarting ( ) ;
512
528
513
- if ( _autoChunk )
529
+ if ( _canHaveBody )
514
530
{
515
- if ( data . Count == 0 )
531
+ if ( _autoChunk )
532
+ {
533
+ if ( data . Count == 0 )
534
+ {
535
+ return ;
536
+ }
537
+ await WriteChunkedAsync ( data , cancellationToken ) ;
538
+ }
539
+ else
516
540
{
517
- return ;
541
+ await SocketOutput . WriteAsync ( data , cancellationToken : cancellationToken ) ;
518
542
}
519
- await WriteChunkedAsync ( data , cancellationToken ) ;
520
543
}
521
544
else
522
545
{
523
- await SocketOutput . WriteAsync ( data , cancellationToken : cancellationToken ) ;
546
+ HandleNonBodyResponseWrite ( data . Count ) ;
547
+ return ;
524
548
}
549
+
525
550
}
526
551
527
552
private void WriteChunked ( ArraySegment < byte > data )
@@ -636,28 +661,14 @@ protected Task ProduceEnd()
636
661
637
662
if ( _requestRejected )
638
663
{
639
- // 400 Bad Request
640
- StatusCode = 400 ;
641
664
_keepAlive = false ;
665
+ // 400 Bad Request
666
+ ErrorResetHeadersToDefaults ( statusCode : 400 ) ;
642
667
}
643
668
else
644
669
{
645
670
// 500 Internal Server Error
646
- StatusCode = 500 ;
647
- }
648
-
649
- ReasonPhrase = null ;
650
-
651
- var responseHeaders = FrameResponseHeaders ;
652
- responseHeaders . Reset ( ) ;
653
- var dateHeaderValues = DateHeaderValueManager . GetDateHeaderValues ( ) ;
654
-
655
- responseHeaders . SetRawDate ( dateHeaderValues . String , dateHeaderValues . Bytes ) ;
656
- responseHeaders . SetRawContentLength ( "0" , _bytesContentLengthZero ) ;
657
-
658
- if ( ServerOptions . AddServerHeader )
659
- {
660
- responseHeaders . SetRawServer ( Constants . ServerName , _bytesServer ) ;
671
+ ErrorResetHeadersToDefaults ( statusCode : 500 ) ;
661
672
}
662
673
}
663
674
@@ -711,10 +722,12 @@ private void CreateResponseHeader(
711
722
bool appCompleted )
712
723
{
713
724
var responseHeaders = FrameResponseHeaders ;
714
- responseHeaders . SetReadOnly ( ) ;
715
725
716
726
var hasConnection = responseHeaders . HasConnection ;
717
727
728
+ // Set whether response can have body
729
+ _canHaveBody = StatusCanHaveBody ( StatusCode ) && Method != "HEAD" ;
730
+
718
731
var end = SocketOutput . ProducingStart ( ) ;
719
732
if ( _keepAlive && hasConnection )
720
733
{
@@ -728,39 +741,48 @@ private void CreateResponseHeader(
728
741
}
729
742
}
730
743
731
- if ( _keepAlive && ! responseHeaders . HasTransferEncoding && ! responseHeaders . HasContentLength )
744
+ if ( _canHaveBody )
732
745
{
733
- if ( appCompleted )
746
+ if ( _keepAlive && ! responseHeaders . HasTransferEncoding && ! responseHeaders . HasContentLength )
734
747
{
735
- // Don't set the Content-Length or Transfer-Encoding headers
736
- // automatically for HEAD requests or 101, 204, 205, 304 responses.
737
- if ( Method != "HEAD" && StatusCanHaveBody ( StatusCode ) )
748
+ if ( appCompleted )
738
749
{
739
750
// Since the app has completed and we are only now generating
740
751
// the headers we can safely set the Content-Length to 0.
741
752
responseHeaders . SetRawContentLength ( "0" , _bytesContentLengthZero ) ;
742
753
}
743
- }
744
- else
745
- {
746
- // Note for future reference: never change this to set _autoChunk to true on HTTP/1.0
747
- // connections, even if we were to infer the client supports it because an HTTP/1.0 request
748
- // was received that used chunked encoding. Sending a chunked response to an HTTP/1.0
749
- // client would break compliance with RFC 7230 (section 3.3.1):
750
- //
751
- // A server MUST NOT send a response containing Transfer-Encoding unless the corresponding
752
- // request indicates HTTP/1.1 (or later).
753
- if ( _httpVersion == Http . HttpVersion . Http11 )
754
- {
755
- _autoChunk = true ;
756
- responseHeaders . SetRawTransferEncoding ( "chunked" , _bytesTransferEncodingChunked ) ;
757
- }
758
754
else
759
755
{
760
- _keepAlive = false ;
756
+ // Note for future reference: never change this to set _autoChunk to true on HTTP/1.0
757
+ // connections, even if we were to infer the client supports it because an HTTP/1.0 request
758
+ // was received that used chunked encoding. Sending a chunked response to an HTTP/1.0
759
+ // client would break compliance with RFC 7230 (section 3.3.1):
760
+ //
761
+ // A server MUST NOT send a response containing Transfer-Encoding unless the corresponding
762
+ // request indicates HTTP/1.1 (or later).
763
+ if ( _httpVersion == Http . HttpVersion . Http11 )
764
+ {
765
+ _autoChunk = true ;
766
+ responseHeaders . SetRawTransferEncoding ( "chunked" , _bytesTransferEncodingChunked ) ;
767
+ }
768
+ else
769
+ {
770
+ _keepAlive = false ;
771
+ }
761
772
}
762
773
}
763
774
}
775
+ else
776
+ {
777
+ // Don't set the Content-Length or Transfer-Encoding headers
778
+ // automatically for HEAD requests or 101, 204, 205, 304 responses.
779
+ if ( responseHeaders . HasTransferEncoding )
780
+ {
781
+ RejectNonBodyTransferEncodingResponse ( appCompleted ) ;
782
+ }
783
+ }
784
+
785
+ responseHeaders . SetReadOnly ( ) ;
764
786
765
787
if ( ! _keepAlive && ! hasConnection && _httpVersion != Http . HttpVersion . Http10 )
766
788
{
@@ -1214,12 +1236,66 @@ public bool StatusCanHaveBody(int statusCode)
1214
1236
statusCode != 304 ;
1215
1237
}
1216
1238
1217
- private void ThrowResponseAlreadyStartedException ( string value )
1239
+ private void RejectNonBodyTransferEncodingResponse ( bool appCompleted )
1240
+ {
1241
+ var ex = BadHttpResponseException . GetException ( ResponseRejectionReasons . TransferEncodingSetOnNonBodyResponse , StatusCode . ToString ( ) ) ;
1242
+ if ( ! appCompleted )
1243
+ {
1244
+ // Back out of header creation surface exeception in user code
1245
+ _requestProcessingStatus = RequestProcessingStatus . RequestStarted ;
1246
+ throw ex ;
1247
+ }
1248
+ else
1249
+ {
1250
+ ReportApplicationError ( ex ) ;
1251
+ // 500 Internal Server Error
1252
+ ErrorResetHeadersToDefaults ( statusCode : 500 ) ;
1253
+ }
1254
+ }
1255
+
1256
+ private void ErrorResetHeadersToDefaults ( int statusCode )
1257
+ {
1258
+ StatusCode = statusCode ;
1259
+ ReasonPhrase = null ;
1260
+
1261
+ var responseHeaders = FrameResponseHeaders ;
1262
+ responseHeaders . Reset ( ) ;
1263
+ var dateHeaderValues = DateHeaderValueManager . GetDateHeaderValues ( ) ;
1264
+
1265
+ responseHeaders . SetRawDate ( dateHeaderValues . String , dateHeaderValues . Bytes ) ;
1266
+ responseHeaders . SetRawContentLength ( "0" , _bytesContentLengthZero ) ;
1267
+
1268
+ if ( ServerOptions . AddServerHeader )
1269
+ {
1270
+ responseHeaders . SetRawServer ( Constants . ServerName , _bytesServer ) ;
1271
+ }
1272
+ }
1273
+
1274
+ public void HandleNonBodyResponseWrite ( int count )
1218
1275
{
1219
- throw new InvalidOperationException ( value + " cannot be set, response had already started." ) ;
1276
+ if ( Method == "HEAD" )
1277
+ {
1278
+ // Don't write to body for HEAD requests.
1279
+ if ( Log . IsEnabled ( LogLevel . Debug ) )
1280
+ {
1281
+ Log . ConnectionHeadResponseBodyWrite ( ConnectionId , count ) ;
1282
+ }
1283
+ }
1284
+ else
1285
+ {
1286
+ // Throw Exception for 101, 204, 205, 304 responses.
1287
+ throw BadHttpResponseException . GetException ( ResponseRejectionReasons . WriteToNonBodyResponse , StatusCode . ToString ( ) ) ;
1288
+ }
1220
1289
}
1221
1290
1222
1291
private void ThrowResponseAbortedException ( )
1292
+ {
1293
+ throw new ObjectDisposedException (
1294
+ "The response has been aborted due to an unhandled application exception." ,
1295
+ _applicationException ) ;
1296
+ }
1297
+
1298
+ public void RejectRequest ( string message )
1223
1299
{
1224
1300
throw new ObjectDisposedException (
1225
1301
"The response has been aborted due to an unhandled application exception." ,
0 commit comments