From e5144e31396fce3d092f3a97ead4a4769488a9df Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Thu, 30 Jul 2015 20:46:02 -0700 Subject: [PATCH] Include Server and Date in the initial response header dictionary --- .../KnownHeaders.cs | 15 +++++- .../Http/Frame.cs | 2 +- .../Http/FrameHeaders.Generated.cs | 10 ++++ .../EngineTests.cs | 15 +++++- .../FrameResponseHeadersTests.cs | 47 +++++++++++++++++++ .../TestConnection.cs | 32 +++++++++++++ 6 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 test/Microsoft.AspNet.Server.KestrelTests/FrameResponseHeadersTests.cs diff --git a/src/Microsoft.AspNet.Server.Kestrel.GeneratedCode/KnownHeaders.cs b/src/Microsoft.AspNet.Server.Kestrel.GeneratedCode/KnownHeaders.cs index da93d4df6a..8fd6913052 100644 --- a/src/Microsoft.AspNet.Server.Kestrel.GeneratedCode/KnownHeaders.cs +++ b/src/Microsoft.AspNet.Server.Kestrel.GeneratedCode/KnownHeaders.cs @@ -170,7 +170,20 @@ using System; using System.Collections.Generic; namespace Microsoft.AspNet.Server.Kestrel.Http -{{{Each(loops, loop => $@" +{{ + public partial class FrameResponseHeaders + {{ + public FrameResponseHeaders() + {{ + _Server = new[] {{ ""Kestrel"" }}; + _Date = new[] {{ DateTime.UtcNow.ToString(""r"") }}; + _bits = { + 1L << responseHeaders.First(header => header.Name == "Server").Index | + 1L << responseHeaders.First(header => header.Name == "Date").Index + }L; + }} + }} +{Each(loops, loop => $@" public partial class {loop.ClassName} {{ long _bits = 0; diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs index eaeeb3ca0d..4faca625a3 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -338,7 +338,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http // the app func has failed. https://github.com/aspnet/KestrelHttpServer/issues/43 _onStarting = null; - ResponseHeaders.Clear(); + ResponseHeaders = new FrameResponseHeaders(); ResponseHeaders["Content-Length"] = new[] { "0" }; } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/FrameHeaders.Generated.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/FrameHeaders.Generated.cs index 8c1e497a64..686527fadc 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/FrameHeaders.Generated.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/FrameHeaders.Generated.cs @@ -4,6 +4,16 @@ using System.Collections.Generic; namespace Microsoft.AspNet.Server.Kestrel.Http { + public partial class FrameResponseHeaders + { + public FrameResponseHeaders() + { + _Server = new[] { "Kestrel" }; + _Date = new[] { DateTime.UtcNow.ToString("r") }; + _bits = 67108868L; + } + } + public partial class FrameRequestHeaders { long _bits = 0; diff --git a/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs b/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs index b8f0a95a14..99fe1b4140 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs @@ -22,6 +22,7 @@ namespace Microsoft.AspNet.Server.KestrelTests { private async Task App(Frame frame) { + frame.ResponseHeaders.Clear(); for (; ;) { var buffer = new byte[8192]; @@ -59,6 +60,7 @@ namespace Microsoft.AspNet.Server.KestrelTests private async Task AppChunked(Frame frame) { + frame.ResponseHeaders.Clear(); var data = new MemoryStream(); for (; ;) { @@ -358,6 +360,7 @@ namespace Microsoft.AspNet.Server.KestrelTests }, null); // Anything added to the ResponseHeaders dictionary is ignored + frame.ResponseHeaders.Clear(); frame.ResponseHeaders["Content-Length"] = new[] { "11" }; throw new Exception(); })) @@ -371,12 +374,20 @@ namespace Microsoft.AspNet.Server.KestrelTests "Connection: close", "", ""); - await connection.ReceiveEnd( + await connection.Receive( "HTTP/1.1 500 Internal Server Error", + ""); + await connection.ReceiveStartsWith("Date:"); + await connection.Receive( "Content-Length: 0", + "Server: Kestrel", "", "HTTP/1.1 500 Internal Server Error", + ""); + await connection.ReceiveStartsWith("Date:"); + await connection.ReceiveEnd( "Content-Length: 0", + "Server: Kestrel", "Connection: close", "", ""); @@ -399,6 +410,7 @@ namespace Microsoft.AspNet.Server.KestrelTests return Task.FromResult(null); }, null); + frame.ResponseHeaders.Clear(); frame.ResponseHeaders["Content-Length"] = new[] { "11" }; await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Hello World"), 0, 11); throw new Exception(); @@ -434,6 +446,7 @@ namespace Microsoft.AspNet.Server.KestrelTests return Task.FromResult(null); }, null); + frame.ResponseHeaders.Clear(); frame.ResponseHeaders["Content-Length"] = new[] { "11" }; await frame.ResponseBody.WriteAsync(Encoding.ASCII.GetBytes("Hello"), 0, 5); throw new Exception(); diff --git a/test/Microsoft.AspNet.Server.KestrelTests/FrameResponseHeadersTests.cs b/test/Microsoft.AspNet.Server.KestrelTests/FrameResponseHeadersTests.cs new file mode 100644 index 0000000000..49e573c730 --- /dev/null +++ b/test/Microsoft.AspNet.Server.KestrelTests/FrameResponseHeadersTests.cs @@ -0,0 +1,47 @@ +// 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.Collections.Generic; +using Microsoft.AspNet.Server.Kestrel.Http; +using Xunit; + +namespace Microsoft.AspNet.Server.KestrelTests +{ + public class FrameResponseHeadersTests + { + [Fact] + public void InitialDictionaryContainsServerAndDate() + { + IDictionary headers = new FrameResponseHeaders(); + + Assert.Equal(2, headers.Count); + + string[] serverHeader; + Assert.True(headers.TryGetValue("Server", out serverHeader)); + Assert.Equal(1, serverHeader.Length); + Assert.Equal("Kestrel", serverHeader[0]); + + string[] dateHeader; + DateTime date; + Assert.True(headers.TryGetValue("Date", out dateHeader)); + Assert.Equal(1, dateHeader.Length); + Assert.True(DateTime.TryParse(dateHeader[0], out date)); + Assert.True(DateTime.Now - date <= TimeSpan.FromMinutes(1)); + + Assert.False(headers.IsReadOnly); + } + + [Fact] + public void InitialEntriesCanBeCleared() + { + IDictionary headers = new FrameResponseHeaders(); + + headers.Clear(); + + Assert.Equal(0, headers.Count); + Assert.False(headers.ContainsKey("Server")); + Assert.False(headers.ContainsKey("Date")); + } + } +} diff --git a/test/Microsoft.AspNet.Server.KestrelTests/TestConnection.cs b/test/Microsoft.AspNet.Server.KestrelTests/TestConnection.cs index 085589beb0..4afd1628d9 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/TestConnection.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/TestConnection.cs @@ -84,6 +84,38 @@ namespace Microsoft.AspNet.Server.KestrelTests 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(1000), "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);