Rewrite HttpSys response caching tests #1475 (#4486)

This commit is contained in:
Chris Ross 2018-12-07 09:21:33 -08:00 committed by GitHub
parent 5d777b2c66
commit ae11d5d62e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 225 additions and 1180 deletions

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.HttpSys.Listener namespace Microsoft.AspNetCore.Server.HttpSys.Listener
@ -20,14 +19,6 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener
private static object PortLock = new object(); private static object PortLock = new object();
internal static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15); internal static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15);
// Minimum support for Windows 7 is assumed.
internal static readonly bool IsWin8orLater;
static Utilities()
{
var win8Version = new Version(6, 2);
IsWin8orLater = (Environment.OSVersion.Version >= win8Version);
}
internal static HttpSysListener CreateHttpAuthServer(AuthenticationSchemes authScheme, bool allowAnonymos, out string baseAddress) internal static HttpSysListener CreateHttpAuthServer(AuthenticationSchemes authScheme, bool allowAnonymos, out string baseAddress)
{ {

View File

@ -2,9 +2,13 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing.xunit;
using Xunit; using Xunit;
@ -12,6 +16,15 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests
{ {
public class ResponseCachingTests 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] [ConditionalFact]
public async Task Caching_NoCacheControl_NotCached() public async Task Caching_NoCacheControl_NotCached()
{ {
@ -49,6 +62,50 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests
} }
} }
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win2008R2, WindowsVersions.Win7, SkipReason = "Content type not required for caching on Win7 and Win2008R2.")]
public async Task Caching_WithoutContentType_NotCached()
{
var requestCount = 1;
string address;
using (Utilities.CreateHttpServer(out address, httpContext =>
{
// httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public, max-age=10";
httpContext.Response.ContentLength = 10;
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
}))
{
Assert.Equal("1", await SendRequestAsync(address));
Assert.Equal("2", await SendRequestAsync(address));
}
}
[ConditionalFact]
public async Task Caching_WithoutContentType_Cached_OnWin7AndWin2008R2()
{
if (Utilities.IsWin8orLater)
{
return;
}
var requestCount = 1;
string address;
using (Utilities.CreateHttpServer(out address, httpContext =>
{
// httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public, max-age=10";
httpContext.Response.ContentLength = 10;
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
}))
{
Assert.Equal("1", await SendRequestAsync(address));
Assert.Equal("1", await SendRequestAsync(address));
}
}
[ConditionalFact] [ConditionalFact]
public async Task Caching_MaxAge_Cached() public async Task Caching_MaxAge_Cached()
{ {
@ -68,6 +125,25 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests
} }
} }
[ConditionalFact]
public async Task Caching_MaxAgeHuge_Cached()
{
var requestCount = 1;
string address;
using (Utilities.CreateHttpServer(out address, httpContext =>
{
httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public, max-age=" + int.MaxValue.ToString(CultureInfo.InvariantCulture);
httpContext.Response.ContentLength = 10;
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
}))
{
Assert.Equal("1", await SendRequestAsync(address));
Assert.Equal("1", await SendRequestAsync(address));
}
}
[ConditionalFact] [ConditionalFact]
public async Task Caching_SMaxAge_Cached() public async Task Caching_SMaxAge_Cached()
{ {
@ -210,14 +286,152 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests
} }
} }
private async Task<string> SendRequestAsync(string uri) [ConditionalFact]
public async Task Caching_Flush_NotCached()
{
var requestCount = 1;
string address;
using (Utilities.CreateHttpServer(out address, httpContext =>
{
httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public, max-age=10";
httpContext.Response.ContentLength = 10;
httpContext.Response.Body.Flush();
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
}))
{
Assert.Equal("1", await SendRequestAsync(address));
Assert.Equal("2", await SendRequestAsync(address));
}
}
[ConditionalFact]
public async Task Caching_WriteFullContentLength_Cached()
{
var requestCount = 1;
string address;
using (Utilities.CreateHttpServer(out address, async httpContext =>
{
httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public, max-age=10";
httpContext.Response.ContentLength = 10;
await httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
// Http.Sys will add this for us
Assert.Null(httpContext.Response.ContentLength);
}))
{
Assert.Equal("1", await SendRequestAsync(address));
Assert.Equal("1", await SendRequestAsync(address));
}
}
[ConditionalFact]
public async Task Caching_SendFileNoContentLength_NotCached()
{
var requestCount = 1;
string address;
using (Utilities.CreateHttpServer(out address, async httpContext =>
{
httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public, max-age=10";
await httpContext.Response.SendFileAsync(_absoluteFilePath, 0, null, CancellationToken.None);
}))
{
Assert.Equal("1", await GetFileAsync(address));
Assert.Equal("2", await GetFileAsync(address));
}
}
[ConditionalFact]
public async Task Caching_SendFileWithFullContentLength_Cached()
{
var requestCount = 1;
string address;
using (Utilities.CreateHttpServer(out address, async httpContext =>
{
httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public, max-age=10";
httpContext.Response.ContentLength = _fileLength;
await httpContext.Response.SendFileAsync(_absoluteFilePath, 0, null, CancellationToken.None);
}))
{
Assert.Equal("1", await GetFileAsync(address));
Assert.Equal("1", await GetFileAsync(address));
}
}
[ConditionalFact]
public async Task Caching_VariousStatusCodes_Cached()
{
var requestCount = 1;
string address;
using (Utilities.CreateHttpServer(out address, httpContext =>
{
httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public, max-age=10";
var status = int.Parse(httpContext.Request.Path.Value.Substring(1));
httpContext.Response.StatusCode = status;
httpContext.Response.ContentLength = 10;
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
}))
{
// Http.Sys will cache almost any status code.
for (int status = 200; status < 600; status++)
{
switch (status)
{
case 206: // 206 (Partial Content) is not cached
case 407: // 407 (Proxy Authentication Required) makes CoreCLR's HttpClient throw
continue;
}
requestCount = 1;
try
{
Assert.Equal("1", await SendRequestAsync(address + status, status));
}
catch (Exception ex)
{
throw new Exception($"Failed to get first response for {status}", ex);
}
try
{
Assert.Equal("1", await SendRequestAsync(address + status, status));
}
catch (Exception ex)
{
throw new Exception($"Failed to get second response for {status}", ex);
}
}
}
}
private async Task<string> SendRequestAsync(string uri, int status = 200)
{
using (var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10) })
{
var response = await client.GetAsync(uri);
Assert.Equal(status, (int)response.StatusCode);
if (status != 204 && status != 304)
{
Assert.Equal(10, response.Content.Headers.ContentLength);
Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync());
}
return response.Headers.GetValues("x-request-count").FirstOrDefault();
}
}
private async Task<string> GetFileAsync(string uri)
{ {
using (var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10) }) using (var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10) })
{ {
var response = await client.GetAsync(uri); var response = await client.GetAsync(uri);
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
Assert.Equal(10, response.Content.Headers.ContentLength); Assert.Equal(_fileLength, response.Content.Headers.ContentLength);
Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync());
return response.Headers.GetValues("x-request-count").FirstOrDefault(); return response.Headers.GetValues("x-request-count").FirstOrDefault();
} }
} }

View File

@ -23,6 +23,14 @@ namespace Microsoft.AspNetCore.Server.HttpSys
private const int MaxPort = 8000; private const int MaxPort = 8000;
private static int NextPort = BasePort; private static int NextPort = BasePort;
private static object PortLock = new object(); private static object PortLock = new object();
// Minimum support for Windows 7 is assumed.
internal static readonly bool IsWin8orLater;
static Utilities()
{
var win8Version = new Version(6, 2);
IsWin8orLater = (Environment.OSVersion.Version >= win8Version);
}
internal static IServer CreateHttpServer(out string baseAddress, RequestDelegate app) internal static IServer CreateHttpServer(out string baseAddress, RequestDelegate app)
{ {