Browse Source

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.
Ryan Nowak 7 years ago
parent
commit
7e63e2da43

+ 3 - 1
src/Middleware/StaticFiles/src/DefaultFilesMiddleware.cs

@@ -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);

+ 4 - 2
src/Middleware/StaticFiles/src/DirectoryBrowserMiddleware.cs

@@ -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))
             {

+ 10 - 0
src/Middleware/StaticFiles/src/LoggerExtensions.cs

@@ -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);

+ 7 - 0
src/Middleware/StaticFiles/src/StaticFileContext.cs

@@ -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;

+ 5 - 1
src/Middleware/StaticFiles/src/StaticFileMiddleware.cs

@@ -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);
             }

+ 39 - 0
src/Middleware/StaticFiles/test/FunctionalTests/StaticFileMiddlewareTests.cs

@@ -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()
         {

+ 38 - 1
src/Middleware/StaticFiles/test/UnitTests/DefaultFilesMiddlewareTests.cs

@@ -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/")]

+ 38 - 0
src/Middleware/StaticFiles/test/UnitTests/DirectoryBrowserMiddlewareTests.cs

@@ -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/")]