diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/DateHeaderValueManager.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/DateHeaderValueManager.cs index 87a2e60de..42f3de4d7 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Http/DateHeaderValueManager.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/DateHeaderValueManager.cs @@ -32,22 +32,24 @@ public class DateHeaderValueManager : IDisposable /// Initializes a new instance of the class. /// public DateHeaderValueManager() - : this( - systemClock: new SystemClock(), - timeWithoutRequestsUntilIdle: TimeSpan.FromSeconds(10), - timerInterval: TimeSpan.FromSeconds(1)) + : this(systemClock: new SystemClock()) { } // Internal for testing internal DateHeaderValueManager( ISystemClock systemClock, - TimeSpan timeWithoutRequestsUntilIdle, - TimeSpan timerInterval) + TimeSpan? timeWithoutRequestsUntilIdle = null, + TimeSpan? timerInterval = null) { + if (systemClock == null) + { + throw new ArgumentNullException(nameof(systemClock)); + } + _systemClock = systemClock; - _timeWithoutRequestsUntilIdle = timeWithoutRequestsUntilIdle; - _timerInterval = timerInterval; + _timeWithoutRequestsUntilIdle = timeWithoutRequestsUntilIdle ?? TimeSpan.FromSeconds(10); + _timerInterval = timerInterval ?? TimeSpan.FromSeconds(1); _dateValueTimer = new Timer(TimerLoop, state: null, dueTime: Timeout.Infinite, period: Timeout.Infinite); } diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs index 2716a94e5..63ada0446 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs @@ -33,6 +33,7 @@ public abstract partial class Frame : ConnectionContext, IFrameControl private static readonly byte[] _bytesHttpVersion11 = Encoding.ASCII.GetBytes("HTTP/1.1 "); private static readonly byte[] _bytesContentLengthZero = Encoding.ASCII.GetBytes("\r\nContent-Length: 0"); private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n"); + private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel"); private static Vector _vectorCRs = new Vector((byte)'\r'); private static Vector _vectorColons = new Vector((byte)':'); @@ -44,7 +45,6 @@ public abstract partial class Frame : ConnectionContext, IFrameControl private readonly object _onCompletedSync = new Object(); private bool _requestRejected; - private Headers _frameHeaders; private Streams _frameStreams; protected List, object>> _onStarting; @@ -210,21 +210,23 @@ public bool HasResponseStarted get { return _requestProcessingStatus == RequestProcessingStatus.ResponseStarted; } } - protected FrameRequestHeaders FrameRequestHeaders => _frameHeaders.RequestHeaders; + protected FrameRequestHeaders FrameRequestHeaders { get; private set; } + + protected FrameResponseHeaders FrameResponseHeaders { get; private set; } public void InitializeHeaders() { - if (_frameHeaders == null) + if (FrameRequestHeaders == null) { - _frameHeaders = new Headers(ServerOptions); - RequestHeaders = _frameHeaders.RequestHeaders; - ResponseHeaders = _frameHeaders.ResponseHeaders; + RequestHeaders = FrameRequestHeaders = new FrameRequestHeaders(); } - _frameHeaders.Initialize(DateHeaderValueManager); + if (FrameResponseHeaders == null) + { + ResponseHeaders = FrameResponseHeaders = new FrameResponseHeaders(); + } } - public void InitializeStreams(MessageBody messageBody) { if (_frameStreams == null) @@ -259,7 +261,8 @@ public void StopStreams() public void Reset() { - _frameHeaders?.Reset(); + FrameRequestHeaders?.Reset(); + FrameResponseHeaders?.Reset(); _onStarting = null; _onCompleted = null; @@ -598,7 +601,7 @@ protected Task TryProduceInvalidRequestResponse() { if (_requestProcessingStatus == RequestProcessingStatus.RequestStarted && _requestRejected) { - if (_frameHeaders == null) + if (FrameRequestHeaders == null || FrameResponseHeaders == null) { InitializeHeaders(); } @@ -634,7 +637,7 @@ protected Task ProduceEnd() ReasonPhrase = null; - var responseHeaders = _frameHeaders.ResponseHeaders; + var responseHeaders = FrameResponseHeaders; responseHeaders.Reset(); var dateHeaderValues = DateHeaderValueManager.GetDateHeaderValues(); @@ -643,7 +646,7 @@ protected Task ProduceEnd() if (ServerOptions.AddServerHeader) { - responseHeaders.SetRawServer(Constants.ServerName, Headers.BytesServer); + responseHeaders.SetRawServer(Constants.ServerName, _bytesServer); } ResponseHeaders = responseHeaders; @@ -698,7 +701,7 @@ private void CreateResponseHeader( byte[] statusBytes, bool appCompleted) { - var responseHeaders = _frameHeaders.ResponseHeaders; + var responseHeaders = FrameResponseHeaders; responseHeaders.SetReadOnly(); var hasConnection = responseHeaders.HasConnection; @@ -759,6 +762,17 @@ private void CreateResponseHeader( responseHeaders.SetRawConnection("keep-alive", _bytesConnectionKeepAlive); } + if (ServerOptions.AddServerHeader && !responseHeaders.HasServer) + { + responseHeaders.SetRawServer(Constants.ServerName, _bytesServer); + } + + if (!responseHeaders.HasDate) + { + var dateHeaderValues = DateHeaderValueManager.GetDateHeaderValues(); + responseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes); + } + end.CopyFrom(_bytesHttpVersion11); end.CopyFrom(statusBytes); responseHeaders.CopyTo(ref end); diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/FrameResponseHeaders.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/FrameResponseHeaders.cs index c843e9625..9697ad0e6 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Http/FrameResponseHeaders.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/FrameResponseHeaders.cs @@ -19,6 +19,9 @@ public partial class FrameResponseHeaders : FrameHeaders public bool HasContentLength => HeaderContentLength.Count != 0; + public bool HasServer => HeaderServer.Count != 0; + + public bool HasDate => HeaderDate.Count != 0; public Enumerator GetEnumerator() { diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Infrastructure/Headers.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Infrastructure/Headers.cs deleted file mode 100644 index 1f81dcdea..000000000 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Infrastructure/Headers.cs +++ /dev/null @@ -1,40 +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 System.Text; -using Microsoft.AspNetCore.Server.Kestrel.Http; - -namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure -{ - class Headers - { - public static readonly byte[] BytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel"); - - private readonly KestrelServerOptions _options; - - public Headers(KestrelServerOptions options) - { - _options = options; - } - - public void Initialize(DateHeaderValueManager dateValueManager) - { - var dateHeaderValues = dateValueManager.GetDateHeaderValues(); - ResponseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes); - - if (_options.AddServerHeader) - { - ResponseHeaders.SetRawServer("Kestrel", BytesServer); - } - } - - public FrameRequestHeaders RequestHeaders { get; } = new FrameRequestHeaders(); - public FrameResponseHeaders ResponseHeaders { get; } = new FrameResponseHeaders(); - - public void Reset() - { - RequestHeaders.Reset(); - ResponseHeaders.Reset(); - } - } -} diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/BadHttpRequestTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/BadHttpRequestTests.cs index adc90857d..a239e7102 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/BadHttpRequestTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/BadHttpRequestTests.cs @@ -70,7 +70,7 @@ public async Task TestBadRequestLines(string request) { using (var server = new TestServer(context => { return Task.FromResult(0); })) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd(request); await ReceiveBadRequestResponse(connection); @@ -87,7 +87,7 @@ public async Task ServerClosesConnectionAsSoonAsBadRequestLineIsDetected(string { using (var server = new TestServer(context => { return Task.FromResult(0); })) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.Send(request); await ReceiveBadRequestResponse(connection); @@ -103,10 +103,9 @@ await connection.Receive( await connection.Receive( "Connection: close", ""); - await connection.ReceiveStartsWith("Date: "); await connection.ReceiveEnd( + $"Date: {connection.Server.Context.DateHeaderValue}", "Content-Length: 0", - "Server: Kestrel", "", ""); } diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedRequestTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedRequestTests.cs index 217af83a6..173912d59 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedRequestTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedRequestTests.cs @@ -14,11 +14,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { public class ChunkedRequestTests { - public static TheoryData ConnectionFilterData + public static TheoryData ConnectionFilterData { get { - return new TheoryData + return new TheoryData { { new TestServiceContext() @@ -34,7 +34,6 @@ private async Task App(HttpContext httpContext) { var request = httpContext.Request; var response = httpContext.Response; - response.Headers.Clear(); while (true) { var buffer = new byte[8192]; @@ -55,18 +54,17 @@ private async Task AppChunked(HttpContext httpContext) await request.Body.CopyToAsync(data); var bytes = data.ToArray(); - response.Headers.Clear(); response.Headers["Content-Length"] = bytes.Length.ToString(); await response.Body.WriteAsync(bytes, 0, bytes.Length); } [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task Http10TransferEncoding(ServiceContext testContext) + public async Task Http10TransferEncoding(TestServiceContext testContext) { using (var server = new TestServer(App, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "POST / HTTP/1.0", @@ -78,6 +76,7 @@ await connection.SendEnd( ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "", "Hello World"); } @@ -86,11 +85,11 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task Http10KeepAliveTransferEncoding(ServiceContext testContext) + public async Task Http10KeepAliveTransferEncoding(TestServiceContext testContext) { using (var server = new TestServer(AppChunked, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "POST / HTTP/1.0", @@ -107,11 +106,13 @@ await connection.SendEnd( await connection.Receive( "HTTP/1.1 200 OK", "Connection: keep-alive", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello World"); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 7", "", "Goodbye"); @@ -121,7 +122,7 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task RequestBodyIsConsumedAutomaticallyIfAppDoesntConsumeItFully(ServiceContext testContext) + public async Task RequestBodyIsConsumedAutomaticallyIfAppDoesntConsumeItFully(TestServiceContext testContext) { using (var server = new TestServer(async httpContext => { @@ -130,13 +131,12 @@ public async Task RequestBodyIsConsumedAutomaticallyIfAppDoesntConsumeItFully(Se Assert.Equal("POST", request.Method); - response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "POST / HTTP/1.1", @@ -154,12 +154,15 @@ await connection.SendEnd( "Goodbye"); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello WorldHTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello WorldHTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello World"); @@ -169,7 +172,7 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task TrailingHeadersAreParsed(ServiceContext testContext) + public async Task TrailingHeadersAreParsed(TestServiceContext testContext) { var requestCount = 10; var requestsReceived = 0; @@ -199,7 +202,6 @@ public async Task TrailingHeadersAreParsed(ServiceContext testContext) requestsReceived++; - response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11); @@ -207,6 +209,7 @@ public async Task TrailingHeadersAreParsed(ServiceContext testContext) { var response = string.Join("\r\n", new string[] { "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello World"}); @@ -244,7 +247,7 @@ public async Task TrailingHeadersAreParsed(ServiceContext testContext) var fullRequest = sendSequence.ToArray(); - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd(fullRequest); @@ -255,7 +258,7 @@ public async Task TrailingHeadersAreParsed(ServiceContext testContext) [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ExtensionsAreIgnored(ServiceContext testContext) + public async Task ExtensionsAreIgnored(TestServiceContext testContext) { var requestCount = 10; var requestsReceived = 0; @@ -285,7 +288,6 @@ public async Task ExtensionsAreIgnored(ServiceContext testContext) requestsReceived++; - response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11); @@ -293,6 +295,7 @@ public async Task ExtensionsAreIgnored(ServiceContext testContext) { var response = string.Join("\r\n", new string[] { "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello World"}); @@ -330,7 +333,7 @@ public async Task ExtensionsAreIgnored(ServiceContext testContext) var fullRequest = sendSequence.ToArray(); - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd(fullRequest); @@ -341,7 +344,7 @@ public async Task ExtensionsAreIgnored(ServiceContext testContext) [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task InvalidLengthResultsIn400(ServiceContext testContext) + public async Task InvalidLengthResultsIn400(TestServiceContext testContext) { using (var server = new TestServer(async httpContext => { @@ -355,13 +358,12 @@ public async Task InvalidLengthResultsIn400(ServiceContext testContext) ;// read to end } - response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.Send( "POST / HTTP/1.1", @@ -373,10 +375,9 @@ await connection.Receive( "HTTP/1.1 400 Bad Request", "Connection: close", ""); - await connection.ReceiveStartsWith("Date:"); await connection.ReceiveForcedEnd( + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", - "Server: Kestrel", "", ""); } @@ -385,7 +386,7 @@ await connection.ReceiveForcedEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task InvalidSizedDataResultsIn400(ServiceContext testContext) + public async Task InvalidSizedDataResultsIn400(TestServiceContext testContext) { using (var server = new TestServer(async httpContext => { @@ -399,13 +400,12 @@ public async Task InvalidSizedDataResultsIn400(ServiceContext testContext) ;// read to end } - response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.Send( "POST / HTTP/1.1", @@ -418,10 +418,9 @@ await connection.Receive( "HTTP/1.1 400 Bad Request", "Connection: close", ""); - await connection.ReceiveStartsWith("Date:"); await connection.ReceiveForcedEnd( + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", - "Server: Kestrel", "", ""); } diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedResponseTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedResponseTests.cs index fd4035cc3..62758e36a 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedResponseTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/ChunkedResponseTests.cs @@ -12,11 +12,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { public class ChunkedResponseTests { - public static TheoryData ConnectionFilterData + public static TheoryData ConnectionFilterData { get { - return new TheoryData + return new TheoryData { { new TestServiceContext() @@ -30,17 +30,16 @@ public static TheoryData ConnectionFilterData [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ResponsesAreChunkedAutomatically(ServiceContext testContext) + public async Task ResponsesAreChunkedAutomatically(TestServiceContext testContext) { using (var server = new TestServer(async httpContext => { var response = httpContext.Response; - response.Headers.Clear(); await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello "), 0, 6); await response.Body.WriteAsync(Encoding.ASCII.GetBytes("World!"), 0, 6); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.1", @@ -48,6 +47,7 @@ await connection.SendEnd( ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Transfer-Encoding: chunked", "", "6", @@ -63,18 +63,17 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ZeroLengthWritesAreIgnored(ServiceContext testContext) + public async Task ZeroLengthWritesAreIgnored(TestServiceContext testContext) { using (var server = new TestServer(async httpContext => { var response = httpContext.Response; - response.Headers.Clear(); await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello "), 0, 6); await response.Body.WriteAsync(new byte[0], 0, 0); await response.Body.WriteAsync(Encoding.ASCII.GetBytes("World!"), 0, 6); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.1", @@ -82,6 +81,7 @@ await connection.SendEnd( ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Transfer-Encoding: chunked", "", "6", @@ -97,16 +97,15 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task EmptyResponseBodyHandledCorrectlyWithZeroLengthWrite(ServiceContext testContext) + public async Task EmptyResponseBodyHandledCorrectlyWithZeroLengthWrite(TestServiceContext testContext) { using (var server = new TestServer(async httpContext => { var response = httpContext.Response; - response.Headers.Clear(); await response.Body.WriteAsync(new byte[0], 0, 0); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.1", @@ -114,6 +113,7 @@ await connection.SendEnd( ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Transfer-Encoding: chunked", "", "0", @@ -125,17 +125,16 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ConnectionClosedIfExeptionThrownAfterWrite(ServiceContext testContext) + public async Task ConnectionClosedIfExeptionThrownAfterWrite(TestServiceContext testContext) { using (var server = new TestServer(async httpContext => { var response = httpContext.Response; - response.Headers.Clear(); await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World!"), 0, 12); throw new Exception(); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { // SendEnd is not called, so it isn't the client closing the connection. // client closing the connection. @@ -145,6 +144,7 @@ await connection.Send( ""); await connection.ReceiveForcedEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Transfer-Encoding: chunked", "", "c", @@ -156,17 +156,16 @@ await connection.ReceiveForcedEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ConnectionClosedIfExeptionThrownAfterZeroLengthWrite(ServiceContext testContext) + public async Task ConnectionClosedIfExeptionThrownAfterZeroLengthWrite(TestServiceContext testContext) { using (var server = new TestServer(async httpContext => { var response = httpContext.Response; - response.Headers.Clear(); await response.Body.WriteAsync(new byte[0], 0, 0); throw new Exception(); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { // SendEnd is not called, so it isn't the client closing the connection. await connection.Send( @@ -177,6 +176,7 @@ await connection.Send( // Headers are sent before connection is closed, but chunked body terminator isn't sent await connection.ReceiveForcedEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Transfer-Encoding: chunked", "", ""); @@ -186,14 +186,13 @@ await connection.ReceiveForcedEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task WritesAreFlushedPriorToResponseCompletion(ServiceContext testContext) + public async Task WritesAreFlushedPriorToResponseCompletion(TestServiceContext testContext) { var flushWh = new ManualResetEventSlim(); using (var server = new TestServer(async httpContext => { var response = httpContext.Response; - response.Headers.Clear(); await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello "), 0, 6); // Don't complete response until client has received the first chunk. @@ -202,7 +201,7 @@ public async Task WritesAreFlushedPriorToResponseCompletion(ServiceContext testC await response.Body.WriteAsync(Encoding.ASCII.GetBytes("World!"), 0, 6); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.1", @@ -210,6 +209,7 @@ await connection.SendEnd( ""); await connection.Receive( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Transfer-Encoding: chunked", "", "6", diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/ConnectionFilterTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/ConnectionFilterTests.cs index cb1a7fa18..2d32fd6c6 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/ConnectionFilterTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/ConnectionFilterTests.cs @@ -16,7 +16,6 @@ private async Task App(HttpContext httpContext) { var request = httpContext.Request; var response = httpContext.Response; - response.Headers.Clear(); while (true) { var buffer = new byte[8192]; @@ -39,12 +38,13 @@ public async Task CanReadAndWriteWithRewritingConnectionFilter() using (var server = new TestServer(App, serviceContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { // "?" changes to "!" await connection.SendEnd(sendString); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {serviceContext.DateHeaderValue}", "", "Hello World!"); } @@ -60,7 +60,7 @@ public async Task CanReadAndWriteWithAsyncConnectionFilter() using (var server = new TestServer(App, serviceContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "POST / HTTP/1.0", @@ -68,6 +68,7 @@ await connection.SendEnd( "Hello World?"); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {serviceContext.DateHeaderValue}", "", "Hello World!"); } @@ -81,7 +82,7 @@ public async Task ThrowingSynchronousConnectionFilterDoesNotCrashServer() using (var server = new TestServer(App, serviceContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { try { diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/DefaultHeaderTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/DefaultHeaderTests.cs new file mode 100644 index 000000000..b682dd35d --- /dev/null +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/DefaultHeaderTests.cs @@ -0,0 +1,41 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel; +using Microsoft.AspNetCore.Server.Kestrel.Infrastructure; +using Xunit; + +namespace Microsoft.AspNetCore.Server.KestrelTests +{ + public class DefaultHeaderTests + { + [Fact] + public async Task TestDefaultHeaders() + { + var testContext = new TestServiceContext() + { + ServerOptions = { AddServerHeader = true } + }; + + using (var server = new TestServer(ctx => TaskUtilities.CompletedTask, testContext)) + { + using (var connection = server.CreateConnection()) + { + await connection.SendEnd( + "GET / HTTP/1.0", + "", + ""); + + await connection.ReceiveEnd( + "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", + "Server: Kestrel", + "", + ""); + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs index f6894e7df..a611f8305 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs @@ -20,11 +20,11 @@ namespace Microsoft.AspNetCore.Server.KestrelTests /// public class EngineTests { - public static TheoryData ConnectionFilterData + public static TheoryData ConnectionFilterData { get { - return new TheoryData + return new TheoryData { { new TestServiceContext() @@ -40,7 +40,6 @@ private async Task App(HttpContext httpContext) { var request = httpContext.Request; var response = httpContext.Response; - response.Headers.Clear(); while (true) { var buffer = new byte[8192]; @@ -61,20 +60,18 @@ private async Task AppChunked(HttpContext httpContext) await request.Body.CopyToAsync(data); var bytes = data.ToArray(); - response.Headers.Clear(); response.Headers["Content-Length"] = bytes.Length.ToString(); await response.Body.WriteAsync(bytes, 0, bytes.Length); } private Task EmptyApp(HttpContext httpContext) { - httpContext.Response.Headers.Clear(); return Task.FromResult(null); } [Theory] [MemberData(nameof(ConnectionFilterData))] - public void EngineCanStartAndStop(ServiceContext testContext) + public void EngineCanStartAndStop(TestServiceContext testContext) { var engine = new KestrelEngine(testContext); engine.Start(1); @@ -121,11 +118,11 @@ public void ConnectionCanReadAndWrite(TestServiceContext testContext) [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task Http10RequestReceivesHttp11Response(ServiceContext testContext) + public async Task Http10RequestReceivesHttp11Response(TestServiceContext testContext) { using (var server = new TestServer(App, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "POST / HTTP/1.0", @@ -133,6 +130,7 @@ await connection.SendEnd( "Hello World"); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "", "Hello World"); } @@ -142,11 +140,11 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task Http11(ServiceContext testContext) + public async Task Http11(TestServiceContext testContext) { using (var server = new TestServer(AppChunked, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.1", @@ -157,10 +155,12 @@ await connection.SendEnd( "Goodbye"); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", "", "HTTP/1.1 200 OK", "Connection: close", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 7", "", "Goodbye"); @@ -170,7 +170,7 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task HeadersAndStreamsAreReused(ServiceContext testContext) + public async Task HeadersAndStreamsAreReused(TestServiceContext testContext) { var streamCount = 0; var requestHeadersCount = 0; @@ -198,21 +198,35 @@ public async Task HeadersAndStreamsAreReused(ServiceContext testContext) lastResponseHeaders = context.Response.Headers; responseHeadersCount++; } - context.Response.Headers.Clear(); return context.Request.Body.CopyToAsync(context.Response.Body); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { var requestData = Enumerable.Repeat("GET / HTTP/1.1\r\n", loopCount) .Concat(new[] { "GET / HTTP/1.1\r\nConnection: close\r\n\r\nGoodbye" }); + var response = string.Join("\r\n", new string[] { + "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", + "Content-Length: 0", + ""}); + + var lastResponse = string.Join("\r\n", new string[] + { + "HTTP/1.1 200 OK", + "Connection: close", + $"Date: {testContext.DateHeaderValue}", + "", + "Goodbye" + }); + var responseData = - Enumerable.Repeat("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n", loopCount) - .Concat(new[] { "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\nGoodbye" }); + Enumerable.Repeat(response, loopCount) + .Concat(new[] { lastResponse }); await connection.SendEnd(requestData.ToArray()); @@ -227,11 +241,11 @@ public async Task HeadersAndStreamsAreReused(ServiceContext testContext) [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task Http10ContentLength(ServiceContext testContext) + public async Task Http10ContentLength(TestServiceContext testContext) { using (var server = new TestServer(App, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "POST / HTTP/1.0", @@ -240,6 +254,7 @@ await connection.SendEnd( "Hello World"); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "", "Hello World"); } @@ -248,11 +263,11 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task Http10KeepAlive(ServiceContext testContext) + public async Task Http10KeepAlive(TestServiceContext testContext) { using (var server = new TestServer(AppChunked, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.0", @@ -264,10 +279,12 @@ await connection.SendEnd( await connection.Receive( "HTTP/1.1 200 OK", "Connection: keep-alive", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", "\r\n"); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 7", "", "Goodbye"); @@ -277,11 +294,11 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task Http10KeepAliveNotUsedIfResponseContentLengthNotSet(ServiceContext testContext) + public async Task Http10KeepAliveNotUsedIfResponseContentLengthNotSet(TestServiceContext testContext) { using (var server = new TestServer(App, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.0", @@ -295,10 +312,12 @@ await connection.SendEnd( await connection.Receive( "HTTP/1.1 200 OK", "Connection: keep-alive", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", "\r\n"); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "", "Goodbye"); } @@ -307,11 +326,11 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task Http10KeepAliveContentLength(ServiceContext testContext) + public async Task Http10KeepAliveContentLength(TestServiceContext testContext) { using (var server = new TestServer(AppChunked, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "POST / HTTP/1.0", @@ -324,11 +343,13 @@ await connection.SendEnd( await connection.Receive( "HTTP/1.1 200 OK", "Connection: keep-alive", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello World"); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 7", "", "Goodbye"); @@ -338,11 +359,11 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task Expect100ContinueForBody(ServiceContext testContext) + public async Task Expect100ContinueForBody(TestServiceContext testContext) { using (var server = new TestServer(AppChunked, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.Send( "POST / HTTP/1.1", @@ -355,6 +376,7 @@ await connection.Send( await connection.Receive( "HTTP/1.1 200 OK", "Connection: close", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello World"); @@ -364,7 +386,7 @@ await connection.Receive( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task DisconnectingClient(ServiceContext testContext) + public async Task DisconnectingClient(TestServiceContext testContext) { using (var server = new TestServer(App, testContext)) { @@ -373,13 +395,14 @@ public async Task DisconnectingClient(ServiceContext testContext) socket.Dispose(); await Task.Delay(200); - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.0", "\r\n"); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "\r\n"); } } @@ -387,11 +410,11 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ZeroContentLengthSetAutomaticallyAfterNoWrites(ServiceContext testContext) + public async Task ZeroContentLengthSetAutomaticallyAfterNoWrites(TestServiceContext testContext) { using (var server = new TestServer(EmptyApp, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.1", @@ -402,10 +425,12 @@ await connection.SendEnd( ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", "", "HTTP/1.1 200 OK", "Connection: keep-alive", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", "", ""); @@ -415,11 +440,11 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ZeroContentLengthNotSetAutomaticallyForNonKeepAliveRequests(ServiceContext testContext) + public async Task ZeroContentLengthNotSetAutomaticallyForNonKeepAliveRequests(TestServiceContext testContext) { using (var server = new TestServer(EmptyApp, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.1", @@ -429,11 +454,12 @@ await connection.SendEnd( await connection.ReceiveEnd( "HTTP/1.1 200 OK", "Connection: close", + $"Date: {testContext.DateHeaderValue}", "", ""); } - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.0", @@ -441,6 +467,7 @@ await connection.SendEnd( ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "", ""); } @@ -449,11 +476,11 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ZeroContentLengthNotSetAutomaticallyForHeadRequests(ServiceContext testContext) + public async Task ZeroContentLengthNotSetAutomaticallyForHeadRequests(TestServiceContext testContext) { using (var server = new TestServer(EmptyApp, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "HEAD / HTTP/1.1", @@ -461,6 +488,7 @@ await connection.SendEnd( ""); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "", ""); } @@ -469,13 +497,12 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes(ServiceContext testContext) + public async Task ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes(TestServiceContext testContext) { using (var server = new TestServer(async httpContext => { var request = httpContext.Request; var response = httpContext.Response; - response.Headers.Clear(); using (var reader = new StreamReader(request.Body, Encoding.ASCII)) { @@ -484,7 +511,7 @@ public async Task ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes(Serv } }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "POST / HTTP/1.1", @@ -505,14 +532,19 @@ await connection.SendEnd( "200"); await connection.ReceiveEnd( "HTTP/1.1 101 Switching Protocols", + $"Date: {testContext.DateHeaderValue}", "", "HTTP/1.1 204 No Content", + $"Date: {testContext.DateHeaderValue}", "", "HTTP/1.1 205 Reset Content", + $"Date: {testContext.DateHeaderValue}", "", "HTTP/1.1 304 Not Modified", + $"Date: {testContext.DateHeaderValue}", "", "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", "", ""); @@ -522,7 +554,7 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ThrowingResultsIn500Response(ServiceContext testContext) + public async Task ThrowingResultsIn500Response(TestServiceContext testContext) { bool onStartingCalled = false; @@ -539,12 +571,11 @@ public async Task ThrowingResultsIn500Response(ServiceContext testContext) }, null); // Anything added to the ResponseHeaders dictionary is ignored - response.Headers.Clear(); response.Headers["Content-Length"] = "11"; throw new Exception(); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.1", @@ -556,19 +587,17 @@ await connection.SendEnd( await connection.Receive( "HTTP/1.1 500 Internal Server Error", ""); - await connection.ReceiveStartsWith("Date:"); await connection.Receive( + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", - "Server: Kestrel", "", "HTTP/1.1 500 Internal Server Error", ""); await connection.Receive("Connection: close", ""); - await connection.ReceiveStartsWith("Date:"); await connection.ReceiveEnd( + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", - "Server: Kestrel", "", ""); @@ -580,7 +609,7 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ThrowingAfterWritingKillsConnection(ServiceContext testContext) + public async Task ThrowingAfterWritingKillsConnection(TestServiceContext testContext) { bool onStartingCalled = false; @@ -596,13 +625,12 @@ public async Task ThrowingAfterWritingKillsConnection(ServiceContext testContext return Task.FromResult(null); }, null); - response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11); throw new Exception(); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.Send( "GET / HTTP/1.1", @@ -610,6 +638,7 @@ await connection.Send( ""); await connection.ReceiveForcedEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello World"); @@ -622,7 +651,7 @@ await connection.ReceiveForcedEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ThrowingAfterPartialWriteKillsConnection(ServiceContext testContext) + public async Task ThrowingAfterPartialWriteKillsConnection(TestServiceContext testContext) { bool onStartingCalled = false; @@ -638,13 +667,12 @@ public async Task ThrowingAfterPartialWriteKillsConnection(ServiceContext testCo return Task.FromResult(null); }, null); - response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello"), 0, 5); throw new Exception(); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.Send( "GET / HTTP/1.1", @@ -652,6 +680,7 @@ await connection.Send( ""); await connection.ReceiveForcedEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello"); @@ -664,11 +693,11 @@ await connection.ReceiveForcedEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ConnectionClosesWhenFinReceived(ServiceContext testContext) + public async Task ConnectionClosesWhenFinReceived(TestServiceContext testContext) { using (var server = new TestServer(AppChunked, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.1", @@ -679,9 +708,11 @@ await connection.SendEnd( "Goodbye"); await connection.ReceiveEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", "", "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 7", "", "Goodbye"); @@ -691,49 +722,45 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ConnectionClosesWhenFinReceivedBeforeRequestCompletes(ServiceContext testContext) + public async Task ConnectionClosesWhenFinReceivedBeforeRequestCompletes(TestServiceContext testContext) { using (var server = new TestServer(AppChunked, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.1", "", "POST / HTTP/1.1"); - await connection.Receive( + await connection.ReceiveForcedEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", "", "HTTP/1.1 400 Bad Request", "Connection: close", - ""); - await connection.ReceiveStartsWith("Date:"); - await connection.ReceiveForcedEnd( + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", - "Server: Kestrel", "", ""); } - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.1", "", "POST / HTTP/1.1", "Content-Length: 7"); - await connection.Receive( + await connection.ReceiveForcedEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", "", "HTTP/1.1 400 Bad Request", "Connection: close", - ""); - await connection.ReceiveStartsWith("Date:"); - await connection.ReceiveForcedEnd( + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", - "Server: Kestrel", "", ""); } @@ -742,7 +769,7 @@ await connection.ReceiveForcedEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ThrowingInOnStartingResultsInFailedWritesAnd500Response(ServiceContext testContext) + public async Task ThrowingInOnStartingResultsInFailedWritesAnd500Response(TestServiceContext testContext) { var onStartingCallCount1 = 0; var onStartingCallCount2 = 0; @@ -767,7 +794,6 @@ public async Task ThrowingInOnStartingResultsInFailedWritesAnd500Response(Servic throw onStartingException; }, null); - response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "11" }; var writeException = await Assert.ThrowsAsync(async () => @@ -778,7 +804,7 @@ public async Task ThrowingInOnStartingResultsInFailedWritesAnd500Response(Servic failedWriteCount++; }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.SendEnd( "GET / HTTP/1.1", @@ -790,18 +816,16 @@ await connection.SendEnd( await connection.Receive( "HTTP/1.1 500 Internal Server Error", ""); - await connection.ReceiveStartsWith("Date:"); await connection.Receive( + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", - "Server: Kestrel", "", "HTTP/1.1 500 Internal Server Error", "Connection: close", ""); - await connection.ReceiveStartsWith("Date:"); await connection.ReceiveEnd( + $"Date: {testContext.DateHeaderValue}", "Content-Length: 0", - "Server: Kestrel", "", ""); @@ -815,7 +839,7 @@ await connection.ReceiveEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task ThrowingInOnCompletedIsLoggedAndClosesConnection(ServiceContext testContext) + public async Task ThrowingInOnCompletedIsLoggedAndClosesConnection(TestServiceContext testContext) { var onCompletedCalled1 = false; var onCompletedCalled2 = false; @@ -837,13 +861,12 @@ public async Task ThrowingInOnCompletedIsLoggedAndClosesConnection(ServiceContex throw new Exception(); }, null); - response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.Send( "GET / HTTP/1.1", @@ -851,6 +874,7 @@ await connection.Send( ""); await connection.ReceiveForcedEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello World"); @@ -865,7 +889,7 @@ await connection.ReceiveForcedEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task RequestsCanBeAbortedMidRead(ServiceContext testContext) + public async Task RequestsCanBeAbortedMidRead(TestServiceContext testContext) { var readTcs = new TaskCompletionSource(); var registrationTcs = new TaskCompletionSource(); @@ -883,7 +907,6 @@ public async Task RequestsCanBeAbortedMidRead(ServiceContext testContext) if (requestId == 1) { - response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "5" }; await response.WriteAsync("World"); @@ -908,7 +931,7 @@ public async Task RequestsCanBeAbortedMidRead(ServiceContext testContext) } }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { // Never send the body so CopyToAsync always fails. await connection.Send( @@ -922,6 +945,7 @@ await connection.Send( await connection.ReceiveForcedEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 5", "", "World"); @@ -937,7 +961,7 @@ await connection.ReceiveForcedEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task FailedWritesResultInAbortedRequest(ServiceContext testContext) + public async Task FailedWritesResultInAbortedRequest(TestServiceContext testContext) { // This should match _maxBytesPreCompleted in SocketOutput var maxBytesPreCompleted = 65536; @@ -959,8 +983,6 @@ public async Task FailedWritesResultInAbortedRequest(ServiceContext testContext) await request.Body.CopyToAsync(Stream.Null); connectionCloseWh.Wait(); - response.Headers.Clear(); - try { // Ensure write is long enough to disable write-behind buffering @@ -979,7 +1001,7 @@ public async Task FailedWritesResultInAbortedRequest(ServiceContext testContext) writeTcs.SetException(new Exception("This shouldn't be reached.")); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.Send( "POST / HTTP/1.1", @@ -1000,7 +1022,7 @@ await connection.Send( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task NoErrorsLoggedWhenServerEndsConnectionBeforeClient(ServiceContext testContext) + public async Task NoErrorsLoggedWhenServerEndsConnectionBeforeClient(TestServiceContext testContext) { var testLogger = new TestApplicationErrorLogger(); testContext.Log = new KestrelTrace(testLogger); @@ -1008,12 +1030,11 @@ public async Task NoErrorsLoggedWhenServerEndsConnectionBeforeClient(ServiceCont using (var server = new TestServer(async httpContext => { var response = httpContext.Response; - response.Headers.Clear(); response.Headers["Content-Length"] = new[] { "11" }; await response.Body.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.Send( "GET / HTTP/1.0", @@ -1021,6 +1042,7 @@ await connection.Send( ""); await connection.ReceiveForcedEnd( "HTTP/1.1 200 OK", + $"Date: {testContext.DateHeaderValue}", "Content-Length: 11", "", "Hello World"); @@ -1032,7 +1054,7 @@ await connection.ReceiveForcedEnd( [Theory] [MemberData(nameof(ConnectionFilterData))] - public async Task NoResponseSentWhenConnectionIsClosedByServerBeforeClientFinishesSendingRequest(ServiceContext testContext) + public async Task NoResponseSentWhenConnectionIsClosedByServerBeforeClientFinishesSendingRequest(TestServiceContext testContext) { var testLogger = new TestApplicationErrorLogger(); testContext.Log = new KestrelTrace(testLogger); @@ -1043,7 +1065,7 @@ public async Task NoResponseSentWhenConnectionIsClosedByServerBeforeClientFinish return Task.FromResult(0); }, testContext)) { - using (var connection = new TestConnection(server.Port)) + using (var connection = server.CreateConnection()) { await connection.Send( "POST / HTTP/1.0", diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameResponseHeadersTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameResponseHeadersTests.cs index ce6098a5b..fa5f44626 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameResponseHeadersTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameResponseHeadersTests.cs @@ -13,12 +13,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { public class FrameResponseHeadersTests { - [Theory] - [InlineData(true)] - [InlineData(false)] - public void InitialDictionaryContainsServerAndDate(bool addServerHeader) + [Fact] + public void InitialDictionaryIsEmpty() { - var serverOptions = new KestrelServerOptions { AddServerHeader = addServerHeader }; + var serverOptions = new KestrelServerOptions(); var connectionContext = new ConnectionContext { @@ -26,60 +24,17 @@ public void InitialDictionaryContainsServerAndDate(bool addServerHeader) ServerAddress = ServerAddress.FromUrl("http://localhost:5000"), ServerOptions = serverOptions, }; + var frame = new Frame(application: null, context: connectionContext); + frame.InitializeHeaders(); IDictionary headers = frame.ResponseHeaders; - if (addServerHeader) - { - Assert.Equal(2, headers.Count); - - StringValues serverHeader; - Assert.True(headers.TryGetValue("Server", out serverHeader)); - Assert.Equal(1, serverHeader.Count); - Assert.Equal("Kestrel", serverHeader[0]); - } - else - { - Assert.Equal(1, headers.Count); - - StringValues serverHeader; - Assert.False(headers.TryGetValue("Server", out serverHeader)); - } - - StringValues dateHeader; - DateTime date; - Assert.True(headers.TryGetValue("Date", out dateHeader)); - Assert.Equal(1, dateHeader.Count); - Assert.True(DateTime.TryParse(dateHeader[0], out date)); - Assert.True(DateTime.Now - date <= TimeSpan.FromMinutes(1)); - + Assert.Equal(0, headers.Count); Assert.False(headers.IsReadOnly); } - [Fact] - public void InitialEntriesCanBeCleared() - { - var serverOptions = new KestrelServerOptions(); - var connectionContext = new ConnectionContext - { - DateHeaderValueManager = new DateHeaderValueManager(), - ServerAddress = ServerAddress.FromUrl("http://localhost:5000"), - ServerOptions = serverOptions, - }; - var frame = new Frame(application: null, context: connectionContext); - frame.InitializeHeaders(); - - Assert.True(frame.ResponseHeaders.Count > 0); - - frame.ResponseHeaders.Clear(); - - Assert.Equal(0, frame.ResponseHeaders.Count); - Assert.False(frame.ResponseHeaders.ContainsKey("Server")); - Assert.False(frame.ResponseHeaders.ContainsKey("Date")); - } - [Theory] [InlineData("Server", "\r\nData")] [InlineData("Server", "\0Data")] diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/HttpsConnectionFilterTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/HttpsConnectionFilterTests.cs index f0046dee4..0afa5216f 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/HttpsConnectionFilterTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/HttpsConnectionFilterTests.cs @@ -403,7 +403,6 @@ private static async Task App(HttpContext httpContext) { var request = httpContext.Request; var response = httpContext.Response; - response.Headers.Clear(); while (true) { var buffer = new byte[8192]; diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/TestConnection.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/TestConnection.cs index 1c929bcb5..f9974bef0 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/TestConnection.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/TestConnection.cs @@ -22,11 +22,14 @@ public class TestConnection : IDisposable private NetworkStream _stream; private StreamReader _reader; - public TestConnection(int port) + public TestConnection(TestServer server) { - Create(port); + Server = server; + Create(server.Port); } + public TestServer Server { get; } + public void Create(int port) { _socket = CreateConnectedLoopbackSocket(port); @@ -85,38 +88,6 @@ public async Task Receive(params string[] lines) Assert.Equal(expected, new String(actual, 0, offset)); } - public async Task ReceiveStartsWith(string prefix, int maxLineLength = 1024) - { - var actual = new char[maxLineLength]; - var offset = 0; - - while (offset < maxLineLength) - { - // Read one char at a time so we don't read past the end of the line. - var task = _reader.ReadAsync(actual, offset, 1); - if (!Debugger.IsAttached) - { - Assert.True(task.Wait(4000), "timeout"); - } - var count = await task; - if (count == 0) - { - break; - } - - Assert.True(count == 1); - offset++; - - if (actual[offset - 1] == '\n') - { - break; - } - } - - var actualLine = new string(actual, 0, offset); - Assert.StartsWith(prefix, actualLine); - } - public async Task ReceiveEnd(params string[] lines) { await Receive(lines); diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/TestServer.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/TestServer.cs index 971999926..2a3deab41 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/TestServer.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/TestServer.cs @@ -22,15 +22,15 @@ public TestServer(RequestDelegate app) { } - public TestServer(RequestDelegate app, ServiceContext context) + public TestServer(RequestDelegate app, TestServiceContext context) : this(app, context, "http://127.0.0.1:0/") { } - public int Port => _address.Port; - - public TestServer(RequestDelegate app, ServiceContext context, string serverAddress) + public TestServer(RequestDelegate app, TestServiceContext context, string serverAddress) { + Context = context; + context.FrameFactory = connectionContext => { return new Frame(new DummyApplication(app), connectionContext); @@ -51,6 +51,15 @@ public TestServer(RequestDelegate app, ServiceContext context, string serverAddr } } + public int Port => _address.Port; + + public TestServiceContext Context { get; } + + public TestConnection CreateConnection() + { + return new TestConnection(this); + } + public void Dispose() { _server.Dispose(); diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/TestServiceContext.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/TestServiceContext.cs index 12238ae2d..3c3cf36c7 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/TestServiceContext.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/TestServiceContext.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Filter; using Microsoft.AspNetCore.Server.Kestrel.Http; using Microsoft.AspNetCore.Server.Kestrel.Infrastructure; +using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers; namespace Microsoft.AspNetCore.Server.KestrelTests { @@ -19,9 +20,9 @@ public TestServiceContext() AppLifetime = new LifetimeNotImplemented(); Log = new TestKestrelTrace(); ThreadPool = new LoggingThreadPool(Log); - DateHeaderValueManager = new DateHeaderValueManager(); - - ServerOptions = new KestrelServerOptions(); + DateHeaderValueManager = new DateHeaderValueManager(systemClock: new MockSystemClock()); + DateHeaderValue = DateHeaderValueManager.GetDateHeaderValues().String; + ServerOptions = new KestrelServerOptions { AddServerHeader = false }; ServerOptions.ShutdownTimeout = TimeSpan.FromSeconds(5); } @@ -31,6 +32,8 @@ public TestServiceContext(IConnectionFilter filter) ServerOptions.ConnectionFilter = filter; } + public string DateHeaderValue { get; } + public RequestDelegate App { get