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 b3bc680e724e9b602018992273ca2591dbbd6d03..0000000000000000000000000000000000000000 --- 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<HttpResponseMessage> 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 b3fb1b1c8c355cf56a1081d2637bf61e04bc4f97..79376d21aae9823e5d98f6e978df7874c5ea0e8d 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 6a2fe8c57d8d67fc1cc138eb831c4fb3d0c9b2e1..acac2989fdf962307ddeb98d04a9f718fe8a714b 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<string> 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<string> 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<string> 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 cecc4e270ab95409ce4d40a9d3ee91b49a5f09e0..d0cfc171275f5b814b9ae01c46b94590020fc54a 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) {