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