Throw when setting Frame.StatusCode or Frame.ReasonPhrase after response has already started (#805).
This commit is contained in:
parent
cb284b9d58
commit
3f4e2323f4
|
|
@ -121,8 +121,42 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
|||
public IHeaderDictionary RequestHeaders { get; set; }
|
||||
public Stream RequestBody { get; set; }
|
||||
|
||||
public int StatusCode { get; set; }
|
||||
public string ReasonPhrase { get; set; }
|
||||
private int _statusCode;
|
||||
public int StatusCode
|
||||
{
|
||||
get
|
||||
{
|
||||
return _statusCode;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (HasResponseStarted)
|
||||
{
|
||||
throw new InvalidOperationException("Status code cannot be set, response has already started.");
|
||||
}
|
||||
|
||||
_statusCode = value;
|
||||
}
|
||||
}
|
||||
|
||||
private string _reasonPhrase;
|
||||
public string ReasonPhrase
|
||||
{
|
||||
get
|
||||
{
|
||||
return _reasonPhrase;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (HasResponseStarted)
|
||||
{
|
||||
throw new InvalidOperationException("Reason phrase cannot be set, response had already started.");
|
||||
}
|
||||
|
||||
_reasonPhrase = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IHeaderDictionary ResponseHeaders { get; set; }
|
||||
public Stream ResponseBody { get; set; }
|
||||
|
||||
|
|
@ -579,6 +613,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
|||
{
|
||||
if (_requestRejected || _applicationException != null)
|
||||
{
|
||||
if (HasResponseStarted)
|
||||
{
|
||||
// We can no longer change the response, so we simply close the connection.
|
||||
_requestProcessingStopping = true;
|
||||
return TaskUtilities.CompletedTask;
|
||||
}
|
||||
|
||||
if (_requestRejected)
|
||||
{
|
||||
// 400 Bad Request
|
||||
|
|
@ -591,30 +632,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
|||
StatusCode = 500;
|
||||
}
|
||||
|
||||
if (HasResponseStarted)
|
||||
ReasonPhrase = null;
|
||||
|
||||
var responseHeaders = _frameHeaders.ResponseHeaders;
|
||||
responseHeaders.Reset();
|
||||
var dateHeaderValues = DateHeaderValueManager.GetDateHeaderValues();
|
||||
|
||||
responseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes);
|
||||
responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
|
||||
|
||||
if (ServerOptions.AddServerHeader)
|
||||
{
|
||||
// We can no longer respond with a 500, so we simply close the connection.
|
||||
_requestProcessingStopping = true;
|
||||
return TaskUtilities.CompletedTask;
|
||||
responseHeaders.SetRawServer(Constants.ServerName, Headers.BytesServer);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReasonPhrase = null;
|
||||
|
||||
var responseHeaders = _frameHeaders.ResponseHeaders;
|
||||
responseHeaders.Reset();
|
||||
var dateHeaderValues = DateHeaderValueManager.GetDateHeaderValues();
|
||||
|
||||
responseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes);
|
||||
responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
|
||||
|
||||
if (ServerOptions.AddServerHeader)
|
||||
{
|
||||
responseHeaders.SetRawServer(Constants.ServerName, Headers.BytesServer);
|
||||
}
|
||||
|
||||
ResponseHeaders = responseHeaders;
|
||||
}
|
||||
ResponseHeaders = responseHeaders;
|
||||
}
|
||||
|
||||
if (!HasResponseStarted)
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||
{
|
||||
public class FrameFacts
|
||||
{
|
||||
[Fact]
|
||||
public void ResetResetsScheme()
|
||||
{
|
||||
// Arrange
|
||||
var connectionContext = new ConnectionContext()
|
||||
{
|
||||
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||
};
|
||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||
frame.Scheme = "https";
|
||||
|
||||
// Act
|
||||
frame.Reset();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("http", ((IFeatureCollection)frame).Get<IHttpRequestFeature>().Scheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -125,5 +125,47 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
((IDictionary<string, StringValues>)responseHeaders).Add(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsWhenAddingHeaderAfterReadOnlyIsSet()
|
||||
{
|
||||
var headers = new FrameResponseHeaders();
|
||||
headers.SetReadOnly();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => ((IDictionary<string, StringValues>)headers).Add("my-header", new[] { "value" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsWhenChangingHeaderAfterReadOnlyIsSet()
|
||||
{
|
||||
var headers = new FrameResponseHeaders();
|
||||
var dictionary = (IDictionary<string, StringValues>)headers;
|
||||
dictionary.Add("my-header", new[] { "value" });
|
||||
headers.SetReadOnly();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => dictionary["my-header"] = "other-value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsWhenRemovingHeaderAfterReadOnlyIsSet()
|
||||
{
|
||||
var headers = new FrameResponseHeaders();
|
||||
var dictionary = (IDictionary<string, StringValues>)headers;
|
||||
dictionary.Add("my-header", new[] { "value" });
|
||||
headers.SetReadOnly();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => dictionary.Remove("my-header"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsWhenClearingHeadersAfterReadOnlyIsSet()
|
||||
{
|
||||
var headers = new FrameResponseHeaders();
|
||||
var dictionary = (IDictionary<string, StringValues>)headers;
|
||||
dictionary.Add("my-header", new[] { "value" });
|
||||
headers.SetReadOnly();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => dictionary.Clear());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
||||
using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||
|
|
@ -50,5 +52,68 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
Assert.True(scan.IsEnd);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetResetsScheme()
|
||||
{
|
||||
// Arrange
|
||||
var connectionContext = new ConnectionContext()
|
||||
{
|
||||
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||
};
|
||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||
frame.Scheme = "https";
|
||||
|
||||
// Act
|
||||
frame.Reset();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("http", ((IFeatureCollection)frame).Get<IHttpRequestFeature>().Scheme);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsWhenStatusCodeIsSetAfterResponseStarted()
|
||||
{
|
||||
// Arrange
|
||||
var connectionContext = new ConnectionContext()
|
||||
{
|
||||
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
||||
ServerOptions = new KestrelServerOptions(),
|
||||
SocketOutput = new MockSocketOuptut()
|
||||
};
|
||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||
frame.InitializeHeaders();
|
||||
|
||||
// Act
|
||||
frame.Write(new ArraySegment<byte>(new byte[1]));
|
||||
|
||||
// Assert
|
||||
Assert.True(frame.HasResponseStarted);
|
||||
Assert.Throws<InvalidOperationException>(() => ((IHttpResponseFeature)frame).StatusCode = 404);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsWhenReasonPhraseIsSetAfterResponseStarted()
|
||||
{
|
||||
// Arrange
|
||||
var connectionContext = new ConnectionContext()
|
||||
{
|
||||
DateHeaderValueManager = new DateHeaderValueManager(),
|
||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000"),
|
||||
ServerOptions = new KestrelServerOptions(),
|
||||
SocketOutput = new MockSocketOuptut()
|
||||
};
|
||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||
frame.InitializeHeaders();
|
||||
|
||||
// Act
|
||||
frame.Write(new ArraySegment<byte>(new byte[1]));
|
||||
|
||||
// Assert
|
||||
Assert.True(frame.HasResponseStarted);
|
||||
Assert.Throws<InvalidOperationException>(() => ((IHttpResponseFeature)frame).ReasonPhrase = "Reason phrase");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests.TestHelpers
|
||||
{
|
||||
public class MockSocketOuptut : ISocketOutput
|
||||
{
|
||||
public void ProducingComplete(MemoryPoolIterator end)
|
||||
{
|
||||
}
|
||||
|
||||
public MemoryPoolIterator ProducingStart()
|
||||
{
|
||||
return new MemoryPoolIterator();
|
||||
}
|
||||
|
||||
public void Write(ArraySegment<byte> buffer, bool chunk = false)
|
||||
{
|
||||
}
|
||||
|
||||
public Task WriteAsync(ArraySegment<byte> buffer, bool chunk = false, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue