Sfoglia il codice sorgente

Merge branch 'release/2.2'

Nate McMaster 7 anni fa
parent
commit
c612eb8730

+ 2 - 1
.gitignore

@@ -26,4 +26,5 @@ scripts/tmp/
 .tools/
 src/**/global.json
 launchSettings.json
-BenchmarkDotNet.Artifacts/
+BenchmarkDotNet.Artifacts/
+korebuild-lock.txt

+ 1 - 2
build/repo.props

@@ -77,8 +77,7 @@
                     Exclude="
                       @(ProjectToExclude);
                       $(RepositoryRoot)**\bin\**\*;
-                      $(RepositoryRoot)**\obj\**\*;
-                      $(RepositoryRoot)**\AutobahnTestApp\**\*;" />
+                      $(RepositoryRoot)**\obj\**\*;" />
   </ItemGroup>
 
   <!-- Properties for publishing -->

+ 1 - 0
eng/signcheck.exclusions.txt

@@ -3,3 +3,4 @@ content/*.js;Microsoft.DotNet.Web.Spa.ProjectTemplates.*.nupkg; Exclude JavaScri
 content/*.js;Microsoft.DotNet.Web.ProjectTemplates.*.nupkg; Exclude JavaScript files from codesigning in project templates
 Templates/*.js;Microsoft.VisualStudio.Web.CodeGenerators.Mvc.*.nupkg; Exclude JavaScript files from codesigning in code generators
 *.js;signalr-*-javadoc.jar; Exclude JavaScript files in the generated javadocs
+*.binlog; Exclude msbuild log files

+ 1 - 7
src/Servers/HttpSys/NuGetPackageVerifier.json

@@ -1,13 +1,7 @@
 {
-    "adx-nonshipping": {
-        "rules": [],
-        "packages": {
-          "Microsoft.AspNetCore.HttpSys.Sources": {}
-        }
-    },
     "Default": {
         "rules": [
             "DefaultCompositeRule"
         ]
     }
-}
+}

+ 0 - 15
src/Servers/HttpSys/test/Directory.Build.props

@@ -1,15 +0,0 @@
-<Project>
-  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
-
-  <PropertyGroup>
-    <DeveloperBuildTestTfms>netcoreapp2.2</DeveloperBuildTestTfms>
-    <StandardTestTfms>$(DeveloperBuildTestTfms)</StandardTestTfms>
-    <StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' ">$(StandardTestTfms)</StandardTestTfms>
-    <StandardTestTfms Condition=" '$(DeveloperBuild)' != 'true' AND '$(OS)' == 'Windows_NT' ">$(StandardTestTfms);net472</StandardTestTfms>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
-  </ItemGroup>
-
-</Project>

+ 0 - 1201
src/Servers/HttpSys/test/FunctionalTests/Listener/ResponseCachingTests.cs

@@ -1,1201 +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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                server.Options.AllowSynchronousIO = true;
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                // 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).Before(responseTask);
-                    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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address, method);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                // Cache the first response
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address, "GET", "x-vary", "vary1");
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address, "GET", "Authorization", "Basic abc123");
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address, "GET", "Pragma", "no-cache");
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address, "GET", "Cache-Control", "no-cache");
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address, "GET", "Range", "bytes=0-10");
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseLength = _fileLength / 2; // Make sure it handles partial files.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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))
-            {
-                address += Guid.NewGuid().ToString(); // Avoid cache collisions for failed tests.
-                var responseLength = _fileLength / 2; // Make sure it handles partial files.
-                var responseTask = SendRequestAsync(address);
-
-                var context = await server.AcceptAsync(Utilities.DefaultTimeout).Before(responseTask);
-                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);
-                }
-            }
-        }
-    }
-}

+ 0 - 8
src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs

@@ -19,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 CreateHttpServer(out string baseAddress)
         {

+ 217 - 3
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()
         {
@@ -51,6 +64,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()
         {
@@ -71,6 +128,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()
         {
@@ -220,14 +296,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();
             }
         }

+ 9 - 0
src/Servers/HttpSys/test/FunctionalTests/Utilities.cs

@@ -27,6 +27,15 @@ namespace Microsoft.AspNetCore.Server.HttpSys
         internal static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15);
         internal static readonly int WriteRetryLimit = 1000;
 
+        // 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)
         {
             string root;

+ 1 - 1
src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj

@@ -7,8 +7,8 @@
 
   <ItemGroup>
     <Compile Include="$(SharedSourceRoot)NullScope.cs" />
+    <Compile Include="$(SharedSourceRoot)Buffers.Testing\*.cs" />
     <Compile Include="$(KestrelSharedSourceRoot)test\*.cs" LinkBase="shared" />
-    <Compile Include="$(RepositoryRoot)\src\Shared\Buffers.Testing\*.cs" />
     <Content Include="$(KestrelSharedSourceRoot)test\TestCertificates\*.pfx" LinkBase="shared\TestCertificates" CopyToOutputDirectory="PreserveNewest" />
   </ItemGroup>