diff --git a/.gitignore b/.gitignore index a04b5e4753..f65b085bce 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ scripts/tmp/ .tools/ src/**/global.json launchSettings.json -BenchmarkDotNet.Artifacts/ \ No newline at end of file +BenchmarkDotNet.Artifacts/ +korebuild-lock.txt diff --git a/build/repo.props b/build/repo.props index d7723d4658..d25f74bd89 100644 --- a/build/repo.props +++ b/build/repo.props @@ -77,8 +77,7 @@ Exclude=" @(ProjectToExclude); $(RepositoryRoot)**\bin\**\*; - $(RepositoryRoot)**\obj\**\*; - $(RepositoryRoot)**\AutobahnTestApp\**\*;" /> + $(RepositoryRoot)**\obj\**\*;" /> diff --git a/eng/signcheck.exclusions.txt b/eng/signcheck.exclusions.txt index 72a47fb4ba..acbdca8318 100644 --- a/eng/signcheck.exclusions.txt +++ b/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 diff --git a/src/Servers/HttpSys/NuGetPackageVerifier.json b/src/Servers/HttpSys/NuGetPackageVerifier.json index c02b36b40a..c5f5582998 100644 --- a/src/Servers/HttpSys/NuGetPackageVerifier.json +++ b/src/Servers/HttpSys/NuGetPackageVerifier.json @@ -1,13 +1,7 @@ { - "adx-nonshipping": { - "rules": [], - "packages": { - "Microsoft.AspNetCore.HttpSys.Sources": {} - } - }, "Default": { "rules": [ "DefaultCompositeRule" ] } -} \ No newline at end of file +} diff --git a/src/Servers/HttpSys/test/Directory.Build.props b/src/Servers/HttpSys/test/Directory.Build.props deleted file mode 100644 index 063d11656d..0000000000 --- a/src/Servers/HttpSys/test/Directory.Build.props +++ /dev/null @@ -1,15 +0,0 @@ - - - - - netcoreapp2.2 - $(DeveloperBuildTestTfms) - $(StandardTestTfms) - $(StandardTestTfms);net472 - - - - - - - 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 91aa87d79c..0000000000 --- a/src/Servers/HttpSys/test/FunctionalTests/Listener/ResponseCachingTests.cs +++ /dev/null @@ -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 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 e17ecb0aff..d2ebe734c3 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs +++ b/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) { diff --git a/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs b/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs index 6b4c30a4db..6f63d5ca22 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() { @@ -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 SendRequestAsync(string uri) + [ConditionalFact] + public async Task Caching_Flush_NotCached() + { + var requestCount = 1; + string address; + using (Utilities.CreateHttpServer(out address, httpContext => + { + httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching + httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); + httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; + httpContext.Response.ContentLength = 10; + httpContext.Response.Body.Flush(); + return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); + })) + { + Assert.Equal("1", await SendRequestAsync(address)); + Assert.Equal("2", await SendRequestAsync(address)); + } + } + + [ConditionalFact] + public async Task Caching_WriteFullContentLength_Cached() + { + var requestCount = 1; + string address; + using (Utilities.CreateHttpServer(out address, async httpContext => + { + httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching + httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); + httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; + httpContext.Response.ContentLength = 10; + await httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); + // Http.Sys will add this for us + Assert.Null(httpContext.Response.ContentLength); + })) + { + Assert.Equal("1", await SendRequestAsync(address)); + Assert.Equal("1", await SendRequestAsync(address)); + } + } + + [ConditionalFact] + public async Task Caching_SendFileNoContentLength_NotCached() + { + var requestCount = 1; + string address; + using (Utilities.CreateHttpServer(out address, async httpContext => + { + httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching + httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); + httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; + await httpContext.Response.SendFileAsync(_absoluteFilePath, 0, null, CancellationToken.None); + })) + { + Assert.Equal("1", await GetFileAsync(address)); + Assert.Equal("2", await GetFileAsync(address)); + } + } + + [ConditionalFact] + public async Task Caching_SendFileWithFullContentLength_Cached() + { + var requestCount = 1; + string address; + using (Utilities.CreateHttpServer(out address, async httpContext => + { + httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching + httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); + httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; + httpContext.Response.ContentLength = _fileLength; + await httpContext.Response.SendFileAsync(_absoluteFilePath, 0, null, CancellationToken.None); + })) + { + Assert.Equal("1", await GetFileAsync(address)); + Assert.Equal("1", await GetFileAsync(address)); + } + } + + [ConditionalFact] + public async Task Caching_VariousStatusCodes_Cached() + { + var requestCount = 1; + string address; + using (Utilities.CreateHttpServer(out address, httpContext => + { + httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching + httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); + httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; + var status = int.Parse(httpContext.Request.Path.Value.Substring(1)); + httpContext.Response.StatusCode = status; + httpContext.Response.ContentLength = 10; + return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); + })) + { + // Http.Sys will cache almost any status code. + for (int status = 200; status < 600; status++) + { + switch (status) + { + case 206: // 206 (Partial Content) is not cached + case 407: // 407 (Proxy Authentication Required) makes CoreCLR's HttpClient throw + continue; + } + requestCount = 1; + try + { + Assert.Equal("1", await SendRequestAsync(address + status, status)); + } + catch (Exception ex) + { + throw new Exception($"Failed to get first response for {status}", ex); + } + try + { + Assert.Equal("1", await SendRequestAsync(address + status, status)); + } + catch (Exception ex) + { + throw new Exception($"Failed to get second response for {status}", ex); + } + } + } + } + + private async Task SendRequestAsync(string uri, int status = 200) + { + using (var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10) }) + { + var response = await client.GetAsync(uri); + Assert.Equal(status, (int)response.StatusCode); + if (status != 204 && status != 304) + { + Assert.Equal(10, response.Content.Headers.ContentLength); + Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync()); + } + return response.Headers.GetValues("x-request-count").FirstOrDefault(); + } + } + + private async Task GetFileAsync(string uri) { using (var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10) }) { var response = await client.GetAsync(uri); Assert.Equal(200, (int)response.StatusCode); - Assert.Equal(10, response.Content.Headers.ContentLength); - Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync()); + Assert.Equal(_fileLength, response.Content.Headers.ContentLength); return response.Headers.GetValues("x-request-count").FirstOrDefault(); } } diff --git a/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs b/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs index 5c340a93c9..eeaf0faa95 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs +++ b/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; diff --git a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj index b3e140a102..1ba8b542d1 100644 --- a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj +++ b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj @@ -7,8 +7,8 @@ + -