From ae11d5d62ef0df79e2b3f5e1a852434185f2d60d Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Fri, 7 Dec 2018 09:21:33 -0800 Subject: [PATCH 1/2] Rewrite HttpSys response caching tests #1475 (#4486) --- .../Listener/ResponseCachingTests.cs | 1168 ----------------- .../FunctionalTests/Listener/Utilities.cs | 9 - .../FunctionalTests/ResponseCachingTests.cs | 220 +++- .../HttpSys/test/FunctionalTests/Utilities.cs | 8 + 4 files changed, 225 insertions(+), 1180 deletions(-) delete mode 100644 src/Servers/HttpSys/test/FunctionalTests/Listener/ResponseCachingTests.cs diff --git a/src/Servers/HttpSys/test/FunctionalTests/Listener/ResponseCachingTests.cs b/src/Servers/HttpSys/test/FunctionalTests/Listener/ResponseCachingTests.cs deleted file mode 100644 index b3bc680e72..0000000000 --- a/src/Servers/HttpSys/test/FunctionalTests/Listener/ResponseCachingTests.cs +++ /dev/null @@ -1,1168 +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; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Testing.xunit; -using Xunit; - -namespace Microsoft.AspNetCore.Server.HttpSys.Listener -{ - public class ResponseCachingTests - { - private readonly string _absoluteFilePath; - private readonly long _fileLength; - - public ResponseCachingTests() - { - _absoluteFilePath = Directory.GetFiles(Directory.GetCurrentDirectory()).First(); - _fileLength = new FileInfo(_absoluteFilePath).Length; - } - - [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win2008R2, WindowsVersions.Win7, SkipReason = "Content type not required for caching on Win7 and Win2008R2.")] - public async Task Caching_SetTtlWithoutContentType_NotCached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - responseTask = SendRequestAsync(address); - - context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "2"; - // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("2", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - [ConditionalFact] - public async Task Caching_SetTtlWithoutContentType_Cached_OnWin7AndWin2008R2() - { - if (Utilities.IsWin8orLater) - { - return; - } - - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - // Http.sys does not require a content-type to cache on Win7 and Win2008R2 - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - // Send a second request and make sure we get the same response (without listening for one on the server). - response = await SendRequestAsync(address); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - [ConditionalFact] - public async Task Caching_SetTtlWithContentType_Cached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - // Send a second request and make sure we get the same response (without listening for one on the server). - response = await SendRequestAsync(address); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - [ConditionalFact] - // Http.Sys does not set the optional Age header for cached content. - // http://tools.ietf.org/html/rfc7234#section-5.1 - public async Task Caching_CheckAge_NotSentWithCachedContent() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - Assert.False(response.Headers.Age.HasValue); - - // Send a second request and make sure we get the same response (without listening for one on the server). - response = await SendRequestAsync(address); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - Assert.False(response.Headers.Age.HasValue); - } - } - - [ConditionalFact] - // Http.Sys does not update the optional Age header for cached content. - // http://tools.ietf.org/html/rfc7234#section-5.1 - public async Task Caching_SetAge_AgeHeaderCachedAndNotUpdated() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.Headers["age"] = "12345"; - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - Assert.True(response.Headers.Age.HasValue); - Assert.Equal(TimeSpan.FromSeconds(12345), response.Headers.Age.Value); - - // Send a second request and make sure we get the same response (without listening for one on the server). - response = await SendRequestAsync(address); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - Assert.True(response.Headers.Age.HasValue); - Assert.Equal(TimeSpan.FromSeconds(12345), response.Headers.Age.Value); - } - } - - [ConditionalFact] - public async Task Caching_SetTtlZeroSeconds_NotCached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(0); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - responseTask = SendRequestAsync(address); - - context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "2"; - // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("2", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - [ConditionalFact] - public async Task Caching_SetTtlMiliseconds_NotCached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromMilliseconds(900); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - responseTask = SendRequestAsync(address); - - context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "2"; - // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("2", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - [ConditionalFact] - public async Task Caching_SetTtlNegative_NotCached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(-10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - responseTask = SendRequestAsync(address); - - context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "2"; - // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("2", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - [ConditionalFact] - public async Task Caching_SetTtlHuge_Cached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.MaxValue; - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - // Send a second request and make sure we get the same response (without listening for one on the server). - response = await SendRequestAsync(address); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - [ConditionalFact] - public async Task Caching_SetTtlAndWriteBody_Cached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.ContentLength = 10; - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - await context.Response.Body.WriteAsync(new byte[10], 0, 10); - // Http.Sys will add this for us - Assert.Null(context.Response.ContentLength); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync()); - - // Send a second request and make sure we get the same response (without listening for one on the server). - response = await SendRequestAsync(address); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync()); - } - } - - [ConditionalFact] - public async Task Caching_SetTtlAndWriteAsyncBody_Cached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.ContentLength = 10; - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - await context.Response.Body.WriteAsync(new byte[10], 0, 10); - // Http.Sys will add this for us - Assert.Null(context.Response.ContentLength); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync()); - - // Send a second request and make sure we get the same response (without listening for one on the server). - response = await SendRequestAsync(address); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync()); - } - } - - [ConditionalFact] - public async Task Caching_Flush_NotCached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - server.Options.AllowSynchronousIO = true; - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Response.Body.Flush(); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - responseTask = SendRequestAsync(address); - - context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "2"; - context.Dispose(); - - response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("2", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - [ConditionalFact] - public async Task Caching_WriteFlush_NotCached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - await context.Response.Body.WriteAsync(new byte[10], 0, 10); - await context.Response.Body.FlushAsync(); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync()); - - responseTask = SendRequestAsync(address); - - context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "2"; - context.Dispose(); - - response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("2", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - [ConditionalFact] - public async Task Caching_WriteFullContentLength_Cached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.ContentLength = 10; - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - await context.Response.Body.WriteAsync(new byte[10], 0, 10); - // Http.Sys will add this for us - Assert.Null(context.Response.ContentLength); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(10, response.Content.Headers.ContentLength); - - // Send a second request and make sure we get the same response (without listening for one on the server). - response = await SendRequestAsync(address); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(10, response.Content.Headers.ContentLength); - } - } - - [ConditionalFact] - public async Task Caching_SendFileNoContentLength_NotCached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - await context.Response.SendFileAsync(_absoluteFilePath, 0, null, CancellationToken.None); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(_fileLength, response.Content.Headers.ContentLength); - - responseTask = SendRequestAsync(address); - - context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "2"; - context.Dispose(); - - response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("2", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - [ConditionalFact] - public async Task Caching_SendFileWithFullContentLength_Cached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.ContentLength =_fileLength; - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - await context.Response.SendFileAsync(_absoluteFilePath, 0, null, CancellationToken.None); - // Http.Sys will add this for us - Assert.Null(context.Response.ContentLength); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(_fileLength, response.Content.Headers.ContentLength); - - // Send a second request and make sure we get the same response (without listening for one on the server). - response = await SendRequestAsync(address); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(_fileLength, response.Content.Headers.ContentLength); - } - } - - [ConditionalFact] - public async Task Caching_SetTtlAndStatusCode_Cached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - // Http.Sys will cache almost any status code. - for (int status = 200; status < 600; status++) - { - switch (status) - { - case 206: // 206 (Partial Content) is not cached - case 407: // 407 (Proxy Authentication Required) makes CoreCLR's HttpClient throw - continue; - } - - var responseTask = SendRequestAsync(address + status); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.StatusCode = status; - context.Response.Headers["x-request-count"] = status.ToString(); - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - HttpResponseMessage response; - try - { - response = await responseTask; - } - catch (Exception ex) - { - throw new Exception($"Failed to get first response for {status}", ex); - } - Assert.Equal(status, (int)response.StatusCode); - Assert.Equal(status.ToString(), response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - // Send a second request and make sure we get the same response (without listening for one on the server). - try - { - response = await SendRequestAsync(address + status); - } - catch (Exception ex) - { - throw new Exception($"Failed to get second response for {status}", ex); - } - Assert.Equal(status, (int)response.StatusCode); - Assert.Equal(status.ToString(), response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - } - - // Only GET requests can have cached responses. - [ConditionalTheory] - // See HTTP_VERB for known verbs - [InlineData("HEAD")] - [InlineData("UNKNOWN")] - [InlineData("INVALID")] - [InlineData("OPTIONS")] - [InlineData("DELETE")] - [InlineData("TRACE")] - [InlineData("TRACK")] - [InlineData("MOVE")] - [InlineData("COPY")] - [InlineData("PROPFIND")] - [InlineData("PROPPATCH")] - [InlineData("MKCOL")] - [InlineData("LOCK")] - [InlineData("UNLOCK")] - [InlineData("SEARCH")] - [InlineData("CUSTOMVERB")] - [InlineData("PATCH")] - [InlineData("POST")] - [InlineData("PUT")] - // [InlineData("CONNECT", null)] 400 bad request if it's not a WebSocket handshake. - public async Task Caching_VariousUnsupportedRequestMethods_NotCached(string method) - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address, method); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = context.Request.Method + "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal(method + "1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - responseTask = SendRequestAsync(address, method); - - context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = context.Request.Method + "2"; - // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal(method + "2", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - // RFC violation. http://tools.ietf.org/html/rfc7234#section-4.4 - // "A cache MUST invalidate the effective Request URI ... when a non-error status code - // is received in response to an unsafe request method." - [ConditionalTheory] - // See HTTP_VERB for known verbs - [InlineData("HEAD")] - [InlineData("UNKNOWN")] - [InlineData("INVALID")] - [InlineData("OPTIONS")] - [InlineData("DELETE")] - [InlineData("TRACE")] - [InlineData("TRACK")] - [InlineData("MOVE")] - [InlineData("COPY")] - [InlineData("PROPFIND")] - [InlineData("PROPPATCH")] - [InlineData("MKCOL")] - [InlineData("LOCK")] - [InlineData("UNLOCK")] - [InlineData("SEARCH")] - [InlineData("CUSTOMVERB")] - [InlineData("PATCH")] - [InlineData("POST")] - [InlineData("PUT")] - // [InlineData("CONNECT", null)] 400 bad request if it's not a WebSocket handshake. - public async Task Caching_UnsupportedRequestMethods_BypassCacheAndLeaveItIntact(string method) - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - // Cache the first response - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = context.Request.Method + "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("GET1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - // Try to clear the cache with a second request - responseTask = SendRequestAsync(address, method); - - context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = context.Request.Method + "2"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Dispose(); - - response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal(method + "2", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - // Send a third request to check the cache. - responseTask = SendRequestAsync(address); - - // The cache wasn't cleared when it should have been - response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("GET1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - // RFC violation / implementation limiation, Vary is not respected. - // http://tools.ietf.org/html/rfc7234#section-4.1 - [ConditionalFact] - public async Task Caching_SetVary_NotRespected() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address, "GET", "x-vary", "vary1"); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.Headers["vary"] = "x-vary"; - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal("x-vary", response.Headers.GetValues("vary").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - // Send a second request and make sure we get the same response (without listening for one on the server). - response = await SendRequestAsync(address, "GET", "x-vary", "vary2"); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal("x-vary", response.Headers.GetValues("vary").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - // http://tools.ietf.org/html/rfc7234#section-3.2 - [ConditionalFact] - public async Task Caching_RequestAuthorization_NotCached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address, "GET", "Authorization", "Basic abc123"); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - responseTask = SendRequestAsync(address, "GET", "Authorization", "Basic abc123"); - - context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "2"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Dispose(); - - // Send a second request and make sure we get the same response (without listening for one on the server). - response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("2", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - [ConditionalFact] - public async Task Caching_RequestAuthorization_NotServedFromCache() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - responseTask = SendRequestAsync(address, "GET", "Authorization", "Basic abc123"); - - context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "2"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Dispose(); - - // Send a second request and make sure we get the same response (without listening for one on the server). - response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("2", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - // Responses can be cached for requests with Pragma: no-cache. - // http://tools.ietf.org/html/rfc7234#section-5.2.1.4 - [ConditionalFact] - public async Task Caching_RequestPragmaNoCache_Cached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address, "GET", "Pragma", "no-cache"); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - response = await SendRequestAsync(address); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - // RFC violation, Requests with Pragma: no-cache should not be served from cache. - // http://tools.ietf.org/html/rfc7234#section-5.4 - // http://tools.ietf.org/html/rfc7234#section-5.2.1.4 - [ConditionalFact] - public async Task Caching_RequestPragmaNoCache_NotRespectedAndServedFromCache() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - response = await SendRequestAsync(address, "GET", "Pragma", "no-cache"); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - // Responses can be cached for requests with cache-control: no-cache. - // http://tools.ietf.org/html/rfc7234#section-5.2.1.4 - [ConditionalFact] - public async Task Caching_RequestCacheControlNoCache_Cached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address, "GET", "Cache-Control", "no-cache"); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - response = await SendRequestAsync(address); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - // RFC violation, Requests with Cache-Control: no-cache should not be served from cache. - // http://tools.ietf.org/html/rfc7234#section-5.2.1.4 - [ConditionalFact] - public async Task Caching_RequestCacheControlNoCache_NotRespectedAndServedFromCache() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - response = await SendRequestAsync(address, "GET", "Cache-Control", "no-cache"); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - // RFC violation - // http://tools.ietf.org/html/rfc7234#section-5.2.1.1 - [ConditionalFact] - public async Task Caching_RequestCacheControlMaxAgeZero_NotRespectedAndServedFromCache() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - response = await SendRequestAsync(address, "GET", "Cache-Control", "min-fresh=0"); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - // RFC violation - // http://tools.ietf.org/html/rfc7234#section-5.2.1.3 - [ConditionalFact] - public async Task Caching_RequestCacheControlMinFreshOutOfRange_NotRespectedAndServedFromCache() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - - response = await SendRequestAsync(address, "GET", "Cache-Control", "min-fresh=20"); - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync()); - } - } - - // Http.Sys limitation, partial responses are not cached. - [ConditionalFact] - public async Task Caching_CacheRange_NotCached() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address, "GET", "Range", "bytes=0-10"); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.StatusCode = 206; - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.Headers["content-range"] = "bytes 0-10/100"; - context.Response.ContentLength = 11; - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - await context.Response.Body.WriteAsync(new byte[100], 0, 11); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(206, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[11], await response.Content.ReadAsByteArrayAsync()); - - responseTask = SendRequestAsync(address, "GET", "Range", "bytes=0-10"); - - context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.StatusCode = 206; - context.Response.Headers["x-request-count"] = "2"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.Headers["content-range"] = "bytes 0-10/100"; - context.Response.ContentLength = 11; - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - await context.Response.Body.WriteAsync(new byte[100], 0, 11); - context.Dispose(); - - response = await responseTask; - Assert.Equal(206, (int)response.StatusCode); - Assert.Equal("2", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal("bytes 0-10/100", response.Content.Headers.GetValues("content-range").FirstOrDefault()); - Assert.Equal(new byte[11], await response.Content.ReadAsByteArrayAsync()); - } - } - - // http://tools.ietf.org/html/rfc7233#section-4.1 - [ConditionalFact] - public async Task Caching_RequestRangeFromCache_RangeServedFromCache() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.ContentLength = 100; - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - await context.Response.Body.WriteAsync(new byte[100], 0, 100); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[100], await response.Content.ReadAsByteArrayAsync()); - - response = await SendRequestAsync(address, "GET", "Range", "bytes=0-10", HttpCompletionOption.ResponseHeadersRead); - Assert.Equal(206, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal("bytes 0-10/100", response.Content.Headers.GetValues("content-range").FirstOrDefault()); - Assert.Equal(11, response.Content.Headers.ContentLength); - } - } - - // http://tools.ietf.org/html/rfc7233#section-4.1 - [ConditionalFact] - public async Task Caching_RequestMultipleRangesFromCache_RangesServedFromCache() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.ContentLength = 100; - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - await context.Response.Body.WriteAsync(new byte[100], 0, 100); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(new byte[100], await response.Content.ReadAsByteArrayAsync()); - - response = await SendRequestAsync(address, "GET", "Range", "bytes=0-10,15-20"); - Assert.Equal(206, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.StartsWith("multipart/byteranges;", response.Content.Headers.GetValues("content-type").First()); - } - } - - [ConditionalFact] - public async Task Caching_RequestRangeFromCachedFile_ServedFromCache() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseLength = _fileLength / 2; // Make sure it handles partial files. - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.ContentLength = responseLength; - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - await context.Response.SendFileAsync(_absoluteFilePath, 0, responseLength, CancellationToken.None); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(responseLength, response.Content.Headers.ContentLength); - - // Send a second request and make sure we get the same response (without listening for one on the server). - var rangeLength = responseLength / 2; - response = await SendRequestAsync(address, "GET", "Range", "bytes=0-" + (rangeLength - 1), HttpCompletionOption.ResponseHeadersRead); - Assert.Equal(206, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(rangeLength, response.Content.Headers.ContentLength); - Assert.Equal("bytes 0-" + (rangeLength - 1) + "/" + responseLength, response.Content.Headers.GetValues("content-range").FirstOrDefault()); - } - } - - [ConditionalFact] - public async Task Caching_RequestMultipleRangesFromCachedFile_ServedFromCache() - { - string address; - using (var server = Utilities.CreateHttpServer(out address)) - { - var responseLength = _fileLength / 2; // Make sure it handles partial files. - var responseTask = SendRequestAsync(address); - - var context = await server.AcceptAsync(Utilities.DefaultTimeout); - context.Response.Headers["x-request-count"] = "1"; - context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache - context.Response.ContentLength = responseLength; - context.Response.CacheTtl = TimeSpan.FromSeconds(10); - await context.Response.SendFileAsync(_absoluteFilePath, 0, responseLength, CancellationToken.None); - context.Dispose(); - - var response = await responseTask; - Assert.Equal(200, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.Equal(responseLength, response.Content.Headers.ContentLength); - // Send a second request and make sure we get the same response (without listening for one on the server). - var rangeLength = responseLength / 4; - response = await SendRequestAsync(address, "GET", "Range", "bytes=0-" + (rangeLength - 1) + "," + rangeLength + "-" + (rangeLength + rangeLength - 1), HttpCompletionOption.ResponseHeadersRead); - Assert.Equal(206, (int)response.StatusCode); - Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); - Assert.StartsWith("multipart/byteranges;", response.Content.Headers.GetValues("content-type").First()); - } - } - - private async Task SendRequestAsync(string uri, string method = "GET", string extraHeader = null, string extraHeaderValue = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) - { - using (var handler = new HttpClientHandler() { AllowAutoRedirect = false }) - { - using (var client = new HttpClient(handler) { Timeout = Utilities.DefaultTimeout }) - { - var request = new HttpRequestMessage(new HttpMethod(method), uri); - if (!string.IsNullOrEmpty(extraHeader)) - { - request.Headers.Add(extraHeader, extraHeaderValue); - } - return await client.SendAsync(request, httpCompletionOption); - } - } - } - } -} diff --git a/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs b/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs index b3fb1b1c8c..79376d21aa 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs @@ -3,7 +3,6 @@ using System; using System.Threading.Tasks; -using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.HttpSys.Listener @@ -20,14 +19,6 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener private static object PortLock = new object(); internal static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15); - // Minimum support for Windows 7 is assumed. - internal static readonly bool IsWin8orLater; - - static Utilities() - { - var win8Version = new Version(6, 2); - IsWin8orLater = (Environment.OSVersion.Version >= win8Version); - } internal static HttpSysListener CreateHttpAuthServer(AuthenticationSchemes authScheme, bool allowAnonymos, out string baseAddress) { diff --git a/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs b/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs index 6a2fe8c57d..acac2989fd 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs @@ -2,9 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Globalization; +using System.IO; using System.Linq; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Testing.xunit; using Xunit; @@ -12,6 +16,15 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests { public class ResponseCachingTests { + private readonly string _absoluteFilePath; + private readonly long _fileLength; + + public ResponseCachingTests() + { + _absoluteFilePath = Directory.GetFiles(Directory.GetCurrentDirectory()).First(); + _fileLength = new FileInfo(_absoluteFilePath).Length; + } + [ConditionalFact] public async Task Caching_NoCacheControl_NotCached() { @@ -49,6 +62,50 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests } } + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win2008R2, WindowsVersions.Win7, SkipReason = "Content type not required for caching on Win7 and Win2008R2.")] + public async Task Caching_WithoutContentType_NotCached() + { + var requestCount = 1; + string address; + using (Utilities.CreateHttpServer(out address, httpContext => + { + // httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching + httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); + httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; + httpContext.Response.ContentLength = 10; + return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); + })) + { + Assert.Equal("1", await SendRequestAsync(address)); + Assert.Equal("2", await SendRequestAsync(address)); + } + } + + [ConditionalFact] + public async Task Caching_WithoutContentType_Cached_OnWin7AndWin2008R2() + { + if (Utilities.IsWin8orLater) + { + return; + } + + var requestCount = 1; + string address; + using (Utilities.CreateHttpServer(out address, httpContext => + { + // httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching + httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); + httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; + httpContext.Response.ContentLength = 10; + return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); + })) + { + Assert.Equal("1", await SendRequestAsync(address)); + Assert.Equal("1", await SendRequestAsync(address)); + } + } + [ConditionalFact] public async Task Caching_MaxAge_Cached() { @@ -68,6 +125,25 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests } } + [ConditionalFact] + public async Task Caching_MaxAgeHuge_Cached() + { + var requestCount = 1; + string address; + using (Utilities.CreateHttpServer(out address, httpContext => + { + httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching + httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); + httpContext.Response.Headers["Cache-Control"] = "public, max-age=" + int.MaxValue.ToString(CultureInfo.InvariantCulture); + httpContext.Response.ContentLength = 10; + return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); + })) + { + Assert.Equal("1", await SendRequestAsync(address)); + Assert.Equal("1", await SendRequestAsync(address)); + } + } + [ConditionalFact] public async Task Caching_SMaxAge_Cached() { @@ -210,14 +286,152 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests } } - private async Task SendRequestAsync(string uri) + [ConditionalFact] + public async Task Caching_Flush_NotCached() + { + var requestCount = 1; + string address; + using (Utilities.CreateHttpServer(out address, httpContext => + { + httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching + httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); + httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; + httpContext.Response.ContentLength = 10; + httpContext.Response.Body.Flush(); + return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); + })) + { + Assert.Equal("1", await SendRequestAsync(address)); + Assert.Equal("2", await SendRequestAsync(address)); + } + } + + [ConditionalFact] + public async Task Caching_WriteFullContentLength_Cached() + { + var requestCount = 1; + string address; + using (Utilities.CreateHttpServer(out address, async httpContext => + { + httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching + httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); + httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; + httpContext.Response.ContentLength = 10; + await httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); + // Http.Sys will add this for us + Assert.Null(httpContext.Response.ContentLength); + })) + { + Assert.Equal("1", await SendRequestAsync(address)); + Assert.Equal("1", await SendRequestAsync(address)); + } + } + + [ConditionalFact] + public async Task Caching_SendFileNoContentLength_NotCached() + { + var requestCount = 1; + string address; + using (Utilities.CreateHttpServer(out address, async httpContext => + { + httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching + httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); + httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; + await httpContext.Response.SendFileAsync(_absoluteFilePath, 0, null, CancellationToken.None); + })) + { + Assert.Equal("1", await GetFileAsync(address)); + Assert.Equal("2", await GetFileAsync(address)); + } + } + + [ConditionalFact] + public async Task Caching_SendFileWithFullContentLength_Cached() + { + var requestCount = 1; + string address; + using (Utilities.CreateHttpServer(out address, async httpContext => + { + httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching + httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); + httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; + httpContext.Response.ContentLength = _fileLength; + await httpContext.Response.SendFileAsync(_absoluteFilePath, 0, null, CancellationToken.None); + })) + { + Assert.Equal("1", await GetFileAsync(address)); + Assert.Equal("1", await GetFileAsync(address)); + } + } + + [ConditionalFact] + public async Task Caching_VariousStatusCodes_Cached() + { + var requestCount = 1; + string address; + using (Utilities.CreateHttpServer(out address, httpContext => + { + httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching + httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); + httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; + var status = int.Parse(httpContext.Request.Path.Value.Substring(1)); + httpContext.Response.StatusCode = status; + httpContext.Response.ContentLength = 10; + return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); + })) + { + // Http.Sys will cache almost any status code. + for (int status = 200; status < 600; status++) + { + switch (status) + { + case 206: // 206 (Partial Content) is not cached + case 407: // 407 (Proxy Authentication Required) makes CoreCLR's HttpClient throw + continue; + } + requestCount = 1; + try + { + Assert.Equal("1", await SendRequestAsync(address + status, status)); + } + catch (Exception ex) + { + throw new Exception($"Failed to get first response for {status}", ex); + } + try + { + Assert.Equal("1", await SendRequestAsync(address + status, status)); + } + catch (Exception ex) + { + throw new Exception($"Failed to get second response for {status}", ex); + } + } + } + } + + private async Task SendRequestAsync(string uri, int status = 200) + { + using (var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10) }) + { + var response = await client.GetAsync(uri); + Assert.Equal(status, (int)response.StatusCode); + if (status != 204 && status != 304) + { + Assert.Equal(10, response.Content.Headers.ContentLength); + Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync()); + } + return response.Headers.GetValues("x-request-count").FirstOrDefault(); + } + } + + private async Task GetFileAsync(string uri) { using (var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10) }) { var response = await client.GetAsync(uri); Assert.Equal(200, (int)response.StatusCode); - Assert.Equal(10, response.Content.Headers.ContentLength); - Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync()); + Assert.Equal(_fileLength, response.Content.Headers.ContentLength); return response.Headers.GetValues("x-request-count").FirstOrDefault(); } } diff --git a/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs b/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs index cecc4e270a..d0cfc17127 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs @@ -23,6 +23,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys private const int MaxPort = 8000; private static int NextPort = BasePort; private static object PortLock = new object(); + // Minimum support for Windows 7 is assumed. + internal static readonly bool IsWin8orLater; + + static Utilities() + { + var win8Version = new Version(6, 2); + IsWin8orLater = (Environment.OSVersion.Version >= win8Version); + } internal static IServer CreateHttpServer(out string baseAddress, RequestDelegate app) { From df3e4b98a2202a29309275cea5efba0720c486b1 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 7 Dec 2018 12:20:13 -0800 Subject: [PATCH 2/2] Backport infrastructure cleanup to share sources and project references (#4513) --- .gitignore | 1 + Directory.Build.targets | 2 +- build/artifacts.props | 1 - build/repo.props | 5 ++- src/MetaPackages/korebuild-lock.txt | 2 -- src/ServerTests/korebuild-lock.txt | 2 -- src/Servers/HttpSys/.gitignore | 32 ------------------- src/Servers/HttpSys/NuGetPackageVerifier.json | 8 +---- .../HttpSys/test/Directory.Build.props | 14 -------- ...spNetCore.Server.Kestrel.Core.Tests.csproj | 2 +- .../Buffers.Testing/Directory.Build.props | 6 ---- src/Shared/Diagnostics/Directory.Build.props | 7 ---- src/Shared/Directory.Build.props | 6 ---- .../Directory.Build.props | 4 ++- src/Shared/HttpSys/Directory.Build.props | 7 ---- ...t.AspNetCore.Http.Connections.Tests.csproj | 2 +- src/Templating/korebuild-lock.txt | 2 -- 17 files changed, 10 insertions(+), 93 deletions(-) delete mode 100644 src/MetaPackages/korebuild-lock.txt delete mode 100644 src/ServerTests/korebuild-lock.txt delete mode 100644 src/Servers/HttpSys/.gitignore delete mode 100644 src/Servers/HttpSys/test/Directory.Build.props delete mode 100644 src/Shared/Buffers.Testing/Directory.Build.props delete mode 100644 src/Shared/Diagnostics/Directory.Build.props delete mode 100644 src/Shared/Directory.Build.props delete mode 100644 src/Shared/HttpSys/Directory.Build.props delete mode 100644 src/Templating/korebuild-lock.txt diff --git a/.gitignore b/.gitignore index fe965fc58f..39bacfa7c7 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ scripts/tmp/ .dotnet/ .tools/ launchSettings.json +korebuild-lock.txt diff --git a/Directory.Build.targets b/Directory.Build.targets index 6f505f9875..32ea0d5590 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -9,7 +9,7 @@ - true + true $(PackagesInPatch.Contains(' $(PackageId);')) diff --git a/build/artifacts.props b/build/artifacts.props index 09e81d5424..d9887567e1 100644 --- a/build/artifacts.props +++ b/build/artifacts.props @@ -193,7 +193,6 @@ - diff --git a/build/repo.props b/build/repo.props index 73cec397f5..ffb92248ac 100644 --- a/build/repo.props +++ b/build/repo.props @@ -17,7 +17,7 @@ - + @@ -70,8 +70,7 @@ Exclude=" @(ProjectToExclude); $(RepositoryRoot)**\bin\**\*; - $(RepositoryRoot)**\obj\**\*; - $(RepositoryRoot)**\AutobahnTestApp\**\*;" /> + $(RepositoryRoot)**\obj\**\*;" /> diff --git a/src/MetaPackages/korebuild-lock.txt b/src/MetaPackages/korebuild-lock.txt deleted file mode 100644 index d38b7a9c0e..0000000000 --- a/src/MetaPackages/korebuild-lock.txt +++ /dev/null @@ -1,2 +0,0 @@ -version:2.1.3-rtm-15847 -commithash:08641cb93aa5a9d52dc56c7516828b73aa448690 diff --git a/src/ServerTests/korebuild-lock.txt b/src/ServerTests/korebuild-lock.txt deleted file mode 100644 index d38b7a9c0e..0000000000 --- a/src/ServerTests/korebuild-lock.txt +++ /dev/null @@ -1,2 +0,0 @@ -version:2.1.3-rtm-15847 -commithash:08641cb93aa5a9d52dc56c7516828b73aa448690 diff --git a/src/Servers/HttpSys/.gitignore b/src/Servers/HttpSys/.gitignore deleted file mode 100644 index e062ff6d76..0000000000 --- a/src/Servers/HttpSys/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -[Oo]bj/ -[Bb]in/ -TestResults/ -.nuget/ -_ReSharper.*/ -packages/ -artifacts/ -PublishProfiles/ -*.user -*.suo -*.cache -*.docstates -_ReSharper.* -nuget.exe -*net45.csproj -*net451.csproj -*k10.csproj -*.psess -*.vsp -*.pidb -*.userprefs -*DS_Store -*.ncrunchsolution -*.*sdf -*.ipch -*.sln.ide -project.lock.json -/.vs -.vscode/ -.build/ -.testPublish/ -global.json diff --git a/src/Servers/HttpSys/NuGetPackageVerifier.json b/src/Servers/HttpSys/NuGetPackageVerifier.json index c02b36b40a..c5f5582998 100644 --- a/src/Servers/HttpSys/NuGetPackageVerifier.json +++ b/src/Servers/HttpSys/NuGetPackageVerifier.json @@ -1,13 +1,7 @@ { - "adx-nonshipping": { - "rules": [], - "packages": { - "Microsoft.AspNetCore.HttpSys.Sources": {} - } - }, "Default": { "rules": [ "DefaultCompositeRule" ] } -} \ No newline at end of file +} diff --git a/src/Servers/HttpSys/test/Directory.Build.props b/src/Servers/HttpSys/test/Directory.Build.props deleted file mode 100644 index a143d14d6a..0000000000 --- a/src/Servers/HttpSys/test/Directory.Build.props +++ /dev/null @@ -1,14 +0,0 @@ - - - - - netcoreapp2.1 - $(DeveloperBuildTestTfms) - $(StandardTestTfms);netcoreapp2.0 - $(StandardTestTfms);net461 - - - - - - diff --git a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj index e11845c901..d40f17e64a 100644 --- a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj +++ b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Shared/Buffers.Testing/Directory.Build.props b/src/Shared/Buffers.Testing/Directory.Build.props deleted file mode 100644 index 0f500dff85..0000000000 --- a/src/Shared/Buffers.Testing/Directory.Build.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - Microsoft.Extensions.Buffers.Testing.Sources - - \ No newline at end of file diff --git a/src/Shared/Diagnostics/Directory.Build.props b/src/Shared/Diagnostics/Directory.Build.props deleted file mode 100644 index dabb33e076..0000000000 --- a/src/Shared/Diagnostics/Directory.Build.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - - false - - \ No newline at end of file diff --git a/src/Shared/Directory.Build.props b/src/Shared/Directory.Build.props deleted file mode 100644 index 8c771f4a3f..0000000000 --- a/src/Shared/Directory.Build.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - false - - \ No newline at end of file diff --git a/src/Shared/Hosting.WebHostBuilderFactory/Directory.Build.props b/src/Shared/Hosting.WebHostBuilderFactory/Directory.Build.props index 77fc28117e..f3d32f04dd 100644 --- a/src/Shared/Hosting.WebHostBuilderFactory/Directory.Build.props +++ b/src/Shared/Hosting.WebHostBuilderFactory/Directory.Build.props @@ -2,5 +2,7 @@ Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources + true + false - \ No newline at end of file + diff --git a/src/Shared/HttpSys/Directory.Build.props b/src/Shared/HttpSys/Directory.Build.props deleted file mode 100644 index 4eb7e43a44..0000000000 --- a/src/Shared/HttpSys/Directory.Build.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - - false - - \ No newline at end of file diff --git a/src/SignalR/test/Microsoft.AspNetCore.Http.Connections.Tests/Microsoft.AspNetCore.Http.Connections.Tests.csproj b/src/SignalR/test/Microsoft.AspNetCore.Http.Connections.Tests/Microsoft.AspNetCore.Http.Connections.Tests.csproj index b161cd535b..e50d6131ab 100644 --- a/src/SignalR/test/Microsoft.AspNetCore.Http.Connections.Tests/Microsoft.AspNetCore.Http.Connections.Tests.csproj +++ b/src/SignalR/test/Microsoft.AspNetCore.Http.Connections.Tests/Microsoft.AspNetCore.Http.Connections.Tests.csproj @@ -6,6 +6,7 @@ + PreserveNewest @@ -19,7 +20,6 @@ - diff --git a/src/Templating/korebuild-lock.txt b/src/Templating/korebuild-lock.txt deleted file mode 100644 index d38b7a9c0e..0000000000 --- a/src/Templating/korebuild-lock.txt +++ /dev/null @@ -1,2 +0,0 @@ -version:2.1.3-rtm-15847 -commithash:08641cb93aa5a9d52dc56c7516828b73aa448690