diff --git a/samples/SelfHostServer/Startup.cs b/samples/SelfHostServer/Startup.cs index 93251f2a1a..f4302222b9 100644 --- a/samples/SelfHostServer/Startup.cs +++ b/samples/SelfHostServer/Startup.cs @@ -47,7 +47,7 @@ namespace SelfHostServer else { context.Response.ContentType = "text/plain"; - await context.Response.WriteAsync("Hello world"); + await context.Response.WriteAsync("Hello world from " + context.Request.Host + " at " + DateTime.Now); } }); } diff --git a/src/Microsoft.Net.Http.Server/RequestProcessing/HeaderCollection.cs b/src/Microsoft.Net.Http.Server/RequestProcessing/HeaderCollection.cs index 52eaa6e33b..4699dbd1e1 100644 --- a/src/Microsoft.Net.Http.Server/RequestProcessing/HeaderCollection.cs +++ b/src/Microsoft.Net.Http.Server/RequestProcessing/HeaderCollection.cs @@ -21,11 +21,15 @@ namespace Microsoft.Net.Http.Server private IDictionary Store { get; set; } + // Readonly after the response has been sent. + internal bool Sent { get; set; } + public string this[string key] { get { return Get(key); } set { + ThrowIfSent(); if (string.IsNullOrEmpty(value)) { Remove(key); @@ -40,7 +44,11 @@ namespace Microsoft.Net.Http.Server string[] IDictionary.this[string key] { get { return Store[key]; } - set { Store[key] = value; } + set + { + ThrowIfSent(); + Store[key] = value; + } } public int Count @@ -50,7 +58,7 @@ namespace Microsoft.Net.Http.Server public bool IsReadOnly { - get { return false; } + get { return Sent; } } public ICollection Keys @@ -65,16 +73,19 @@ namespace Microsoft.Net.Http.Server public void Add(KeyValuePair item) { + ThrowIfSent(); Store.Add(item); } public void Add(string key, string[] value) { + ThrowIfSent(); Store.Add(key, value); } public void Append(string key, string value) { + ThrowIfSent(); string[] values; if (Store.TryGetValue(key, out values)) { @@ -91,6 +102,7 @@ namespace Microsoft.Net.Http.Server public void AppendValues(string key, params string[] values) { + ThrowIfSent(); string[] oldValues; if (Store.TryGetValue(key, out oldValues)) { @@ -107,6 +119,7 @@ namespace Microsoft.Net.Http.Server public void Clear() { + ThrowIfSent(); Store.Clear(); } @@ -152,21 +165,25 @@ namespace Microsoft.Net.Http.Server public bool Remove(KeyValuePair item) { + ThrowIfSent(); return Store.Remove(item); } public bool Remove(string key) { + ThrowIfSent(); return Store.Remove(key); } public void Set(string key, string value) { + ThrowIfSent(); Store[key] = new[] { value }; } public void SetValues(string key, params string[] values) { + ThrowIfSent(); Store[key] = values; } @@ -179,5 +196,13 @@ namespace Microsoft.Net.Http.Server { return GetEnumerator(); } + + private void ThrowIfSent() + { + if (Sent) + { + throw new InvalidOperationException("The response headers cannot be modified because they have already been sent."); + } + } } } \ No newline at end of file diff --git a/src/Microsoft.Net.Http.Server/RequestProcessing/Response.cs b/src/Microsoft.Net.Http.Server/RequestProcessing/Response.cs index 28ea172d40..b85010f985 100644 --- a/src/Microsoft.Net.Http.Server/RequestProcessing/Response.cs +++ b/src/Microsoft.Net.Http.Server/RequestProcessing/Response.cs @@ -583,6 +583,7 @@ namespace Microsoft.Net.Http.Server private List SerializeHeaders(bool isOpaqueUpgrade) { + Headers.Sent = true; // Prohibit further modifications. UnsafeNclNativeMethods.HttpApi.HTTP_UNKNOWN_HEADER[] unknownHeaders = null; UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO[] knownHeaderInfo = null; List pinnedHeaders; diff --git a/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/ResponseHeaderTests.cs b/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/ResponseHeaderTests.cs index c086ce3cea..66b18b3364 100644 --- a/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/ResponseHeaderTests.cs +++ b/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/ResponseHeaderTests.cs @@ -247,7 +247,7 @@ namespace Microsoft.AspNet.Server.WebListener var body = responseInfo.Body; body.Flush(); Assert.Throws(() => responseInfo.StatusCode = 404); - responseHeaders.Add("Custom3", new string[] { "value3a, value3b", "value3c" }); // Ignored + Assert.Throws(() => responseHeaders.Add("Custom3", new string[] { "value3a, value3b", "value3c" })); return Task.FromResult(0); })) { @@ -277,7 +277,7 @@ namespace Microsoft.AspNet.Server.WebListener var body = responseInfo.Body; await body.FlushAsync(); Assert.Throws(() => responseInfo.StatusCode = 404); - responseHeaders.Add("Custom3", new string[] { "value3a, value3b", "value3c" }); // Ignored + Assert.Throws(() => responseHeaders.Add("Custom3", new string[] { "value3a, value3b", "value3c" })); })) { HttpResponseMessage response = await SendRequestAsync(address); diff --git a/test/Microsoft.Net.Http.Server.FunctionalTests/OpaqueUpgradeTests.cs b/test/Microsoft.Net.Http.Server.FunctionalTests/OpaqueUpgradeTests.cs index e3076cff0f..d6dc580fc2 100644 --- a/test/Microsoft.Net.Http.Server.FunctionalTests/OpaqueUpgradeTests.cs +++ b/test/Microsoft.Net.Http.Server.FunctionalTests/OpaqueUpgradeTests.cs @@ -24,7 +24,7 @@ namespace Microsoft.Net.Http.Server byte[] body = Encoding.UTF8.GetBytes("Hello World"); context.Response.Body.Write(body, 0, body.Length); - context.Response.Headers["Upgrade"] = "WebSocket"; // Win8.1 blocks anything but WebSocket + Assert.Throws(() => context.Response.Headers["Upgrade"] = "WebSocket"); // Win8.1 blocks anything but WebSocket await Assert.ThrowsAsync(async () => await context.UpgradeAsync()); context.Dispose(); HttpResponseMessage response = await clientTask; diff --git a/test/Microsoft.Net.Http.Server.FunctionalTests/ResponseHeaderTests.cs b/test/Microsoft.Net.Http.Server.FunctionalTests/ResponseHeaderTests.cs index 8b0dad5460..3059532f5d 100644 --- a/test/Microsoft.Net.Http.Server.FunctionalTests/ResponseHeaderTests.cs +++ b/test/Microsoft.Net.Http.Server.FunctionalTests/ResponseHeaderTests.cs @@ -233,8 +233,10 @@ namespace Microsoft.Net.Http.Server responseHeaders.SetValues("Custom2", "value2a, value2b"); var body = context.Response.Body; body.Flush(); - Assert.Throws(() => context.Response.StatusCode = 404); - responseHeaders.Add("Custom3", new string[] { "value3a, value3b", "value3c" }); // Ignored + var ex = Assert.Throws(() => context.Response.StatusCode = 404); + Assert.Equal("Headers already sent.", ex.Message); + ex = Assert.Throws(() => responseHeaders.Add("Custom3", new string[] { "value3a, value3b", "value3c" })); + Assert.Equal("The response headers cannot be modified because they have already been sent.", ex.Message); context.Dispose(); @@ -265,8 +267,10 @@ namespace Microsoft.Net.Http.Server responseHeaders.SetValues("Custom2", "value2a, value2b"); var body = context.Response.Body; await body.FlushAsync(); - Assert.Throws(() => context.Response.StatusCode = 404); - responseHeaders.SetValues("Custom3", "value3a, value3b", "value3c"); // Ignored + var ex = Assert.Throws(() => context.Response.StatusCode = 404); + Assert.Equal("Headers already sent.", ex.Message); + ex = Assert.Throws(() => responseHeaders.Add("Custom3", new string[] { "value3a, value3b", "value3c" })); + Assert.Equal("The response headers cannot be modified because they have already been sent.", ex.Message); context.Dispose();