Make StaticFiles Noop for middleware

Teaches all of the static files middleware (incl default files,
directory browser) to noop when an endpoint is selected. This is
desirable so you can place them after routing if you want with no ill
effect.
This commit is contained in:
Ryan Nowak 2019-03-05 12:42:42 -08:00 committed by Ryan Nowak
parent f150e89125
commit 7e63e2da43
8 changed files with 144 additions and 5 deletions

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Endpoints;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
@ -62,7 +63,8 @@ namespace Microsoft.AspNetCore.StaticFiles
/// <returns></returns>
public Task Invoke(HttpContext context)
{
if (Helpers.IsGetOrHeadMethod(context.Request.Method)
if (context.GetEndpoint() == null &&
Helpers.IsGetOrHeadMethod(context.Request.Method)
&& Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out var subpath))
{
var dirContents = _fileProvider.GetDirectoryContents(subpath.Value);

View File

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Endpoints;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
@ -78,8 +79,9 @@ namespace Microsoft.AspNetCore.StaticFiles
/// <returns></returns>
public Task Invoke(HttpContext context)
{
// Check if the URL matches any expected paths
if (Helpers.IsGetOrHeadMethod(context.Request.Method)
// Check if the URL matches any expected paths, skip if an endpoint was selected
if (context.GetEndpoint() == null &&
Helpers.IsGetOrHeadMethod(context.Request.Method)
&& Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out var subpath)
&& TryGetDirectoryInfo(subpath, out var contents))
{

View File

@ -24,6 +24,7 @@ namespace Microsoft.AspNetCore.StaticFiles
private static Action<ILogger, StringValues, string, Exception> _sendingFileRange;
private static Action<ILogger, StringValues, string, Exception> _copyingFileRange;
private static Action<ILogger, Exception> _writeCancelled;
private static Action<ILogger, Exception> _endpointMatched;
static LoggerExtensions()
{
@ -75,6 +76,10 @@ namespace Microsoft.AspNetCore.StaticFiles
logLevel: LogLevel.Debug,
eventId: new EventId(14, "WriteCancelled"),
formatString: "The file transmission was cancelled");
_endpointMatched = LoggerMessage.Define(
logLevel: LogLevel.Debug,
eventId: new EventId(15, "EndpointMatched"),
formatString: "Static files was skipped as the request already matched an endpoint.");
}
public static void RequestMethodNotSupported(this ILogger logger, string method)
@ -91,6 +96,11 @@ namespace Microsoft.AspNetCore.StaticFiles
_fileServed(logger, virtualPath, physicalPath, null);
}
public static void EndpointMatched(this ILogger logger)
{
_endpointMatched(logger, null);
}
public static void PathMismatch(this ILogger logger, string path)
{
_pathMismatch(logger, path, null);

View File

@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Endpoints;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Headers;
@ -108,6 +109,12 @@ namespace Microsoft.AspNetCore.StaticFiles
get { return _fileInfo?.PhysicalPath; }
}
public bool ValidateNoEndpoint()
{
// Return true because we only want to run if there is no endpoint.
return _context.GetEndpoint() == null;
}
public bool ValidateMethod()
{
_method = _request.Method;

View File

@ -72,7 +72,11 @@ namespace Microsoft.AspNetCore.StaticFiles
{
var fileContext = new StaticFileContext(context, _options, _matchUrl, _logger, _fileProvider, _contentTypeProvider);
if (!fileContext.ValidateMethod())
if (!fileContext.ValidateNoEndpoint())
{
_logger.EndpointMatched();
}
else if (!fileContext.ValidateMethod())
{
_logger.RequestMethodNotSupported(context.Request.Method);
}

View File

@ -14,6 +14,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Endpoints;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
using Microsoft.AspNetCore.Testing;
@ -45,6 +46,44 @@ namespace Microsoft.AspNetCore.StaticFiles
}
}
[Fact]
public async Task Endpoint_PassesThrough()
{
var builder = new WebHostBuilder()
.ConfigureServices(services => services.AddSingleton(LoggerFactory))
.UseKestrel()
.UseWebRoot(AppContext.BaseDirectory)
.Configure(app =>
{
// Routing first => static files noops
app.Use(next => context =>
{
// Assign an endpoint, this will make the default files noop.
context.SetEndpoint(new Endpoint((c) =>
{
return context.Response.WriteAsync("Hi from endpoint.");
},
new EndpointMetadataCollection(),
"test"));
return next(context);
});
app.UseStaticFiles();
});
using (var server = builder.Start(TestUrlHelper.GetTestUrl(ServerType.Kestrel)))
{
using (var client = new HttpClient { BaseAddress = new Uri(server.GetAddress()) })
{
var response = await client.GetAsync("TestDocument.txt");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Hi from endpoint.", await response.Content.ReadAsStringAsync());
}
}
}
[Fact]
public async Task FoundFile_LastModifiedTrimsSeconds()
{

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
@ -9,9 +9,11 @@ using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Endpoints;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.TestHost;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Xunit;
@ -72,6 +74,41 @@ namespace Microsoft.AspNetCore.StaticFiles
}
}
[Fact]
public async Task Endpoint_PassesThrough()
{
using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, ".")))
{
var server = StaticFilesTestServer.Create(
app =>
{
app.Use(next => context =>
{
// Assign an endpoint, this will make the default files noop.
context.SetEndpoint(new Endpoint((c) =>
{
return context.Response.WriteAsync(context.Request.Path.Value);
},
new EndpointMetadataCollection(),
"test"));
return next(context);
});
app.UseDefaultFiles(new DefaultFilesOptions
{
RequestPath = new PathString(""),
FileProvider = fileProvider
});
},
services => services.AddDirectoryBrowser());
var response = await server.CreateRequest("/SubFolder/").GetAsync();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("/SubFolder/", await response.Content.ReadAsStringAsync()); // Should not be modified
}
}
[Theory]
[InlineData("", @".", "/SubFolder/")]
[InlineData("", @"./", "/SubFolder/")]

View File

@ -9,6 +9,8 @@ using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Endpoints;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.TestHost;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.DependencyInjection;
@ -86,6 +88,42 @@ namespace Microsoft.AspNetCore.StaticFiles
}
}
[Fact]
public async Task Endpoint_PassesThrough()
{
using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, ".")))
{
var server = StaticFilesTestServer.Create(
app =>
{
app.Use(next => context =>
{
// Assign an endpoint, this will make the directory browser noop
context.SetEndpoint(new Endpoint((c) =>
{
c.Response.StatusCode = (int)HttpStatusCode.NotAcceptable;
return c.Response.WriteAsync("Hi from endpoint.");
},
new EndpointMetadataCollection(),
"test"));
return next(context);
});
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
RequestPath = new PathString(""),
FileProvider = fileProvider
});
},
services => services.AddDirectoryBrowser());
var response = await server.CreateRequest("/").GetAsync();
Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
Assert.Equal("Hi from endpoint.", await response.Content.ReadAsStringAsync());
}
}
[Theory]
[InlineData("", @".", "/")]
[InlineData("", @".", "/SubFolder/")]