From b997281e1f5b77fd362ac97f8a5295f65fafb702 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 10 Sep 2019 14:10:26 +1000 Subject: [PATCH] Only waits for background processing to finish if it's a front-end request, required caching the check for a front-end request which required moving the check to it's own class. --- src/Umbraco.Web/RoutableDocumentLookup.cs | 109 ++++++++++++++++++ src/Umbraco.Web/Runtime/WebInitialComposer.cs | 1 + src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/Umbraco.Web/UmbracoContext.cs | 24 +++- src/Umbraco.Web/UmbracoInjectedModule.cs | 102 +++------------- 5 files changed, 148 insertions(+), 89 deletions(-) create mode 100644 src/Umbraco.Web/RoutableDocumentLookup.cs diff --git a/src/Umbraco.Web/RoutableDocumentLookup.cs b/src/Umbraco.Web/RoutableDocumentLookup.cs new file mode 100644 index 0000000000..bc75798b09 --- /dev/null +++ b/src/Umbraco.Web/RoutableDocumentLookup.cs @@ -0,0 +1,109 @@ +using System; +using System.IO; +using System.Web; +using System.Web.Routing; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Configuration; + +namespace Umbraco.Web +{ + /// + /// Utility class used to check if the current request is for a front-end request + /// + /// + /// There are various checks to determine if this is a front-end request such as checking if the request is part of any reserved paths or existing MVC routes. + /// + public sealed class RoutableDocumentLookup + { + public RoutableDocumentLookup(ILogger logger, IGlobalSettings globalSettings) + { + _logger = logger; + _globalSettings = globalSettings; + _combinedRouteCollection = new Lazy(CreateRouteCollection); + } + + /// + /// Checks if the request is a document request (i.e. one that the module should handle) + /// + /// + /// + /// + public bool IsDocumentRequest(HttpContextBase httpContext, Uri uri) + { + var maybeDoc = true; + var lpath = uri.AbsolutePath.ToLowerInvariant(); + + // handle directory-urls used for asmx + // TODO: legacy - what's the point really? + var asmxPos = lpath.IndexOf(".asmx/", StringComparison.OrdinalIgnoreCase); + if (asmxPos >= 0) + { + // use uri.AbsolutePath, not path, 'cos path has been lowercased + httpContext.RewritePath(uri.AbsolutePath.Substring(0, asmxPos + 5), // filePath + uri.AbsolutePath.Substring(asmxPos + 5), // pathInfo + uri.Query.TrimStart('?')); + maybeDoc = false; + } + + // a document request should be + // /foo/bar/nil + // /foo/bar/nil/ + // /foo/bar/nil.aspx + // where /foo is not a reserved path + + // if the path contains an extension that is not .aspx + // then it cannot be a document request + var extension = Path.GetExtension(lpath); + if (maybeDoc && extension.IsNullOrWhiteSpace() == false && extension != ".aspx") + maybeDoc = false; + + // at that point, either we have no extension, or it is .aspx + + // if the path is reserved then it cannot be a document request + if (maybeDoc && _globalSettings.IsReservedPathOrUrl(lpath, httpContext, _combinedRouteCollection.Value)) + maybeDoc = false; + + //NOTE: No need to warn, plus if we do we should log the document, as this message doesn't really tell us anything :) + //if (!maybeDoc) + //{ + // Logger.Warn("Not a document"); + //} + return maybeDoc; + } + + /// + /// This is used to be passed into the GlobalSettings.IsReservedPathOrUrl and will include some 'fake' routes + /// used to determine if a path is reserved. + /// + /// + /// This is basically used to reserve paths dynamically + /// + private readonly Lazy _combinedRouteCollection; + private readonly ILogger _logger; + private readonly IGlobalSettings _globalSettings; + + private RouteCollection CreateRouteCollection() + { + var routes = new RouteCollection(); + + foreach (var route in RouteTable.Routes) + routes.Add(route); + + foreach (var reservedPath in UmbracoModule.ReservedPaths) + { + try + { + routes.Add("_umbreserved_" + reservedPath.ReplaceNonAlphanumericChars(""), + new Route(reservedPath.TrimStart('/'), new StopRoutingHandler())); + } + catch (Exception ex) + { + _logger.Error("Could not add reserved path route", ex); + } + } + + return routes; + } + } +} diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 4b4fb488e5..7c353daa1d 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -126,6 +126,7 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(f => new DistributedCache()); composition.RegisterUnique(); + composition.RegisterUnique(); // replace some services composition.RegisterUnique(); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 1a23ae7f65..2cdff55e2d 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -230,6 +230,7 @@ + diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index e4a921375e..95a96be072 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -1,16 +1,21 @@ using System; using System.Collections.Generic; +using System.IO; using System.Web; +using System.Web.Routing; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Events; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; namespace Umbraco.Web { + /// /// Class that encapsulates Umbraco information of a specific HTTP request /// @@ -80,7 +85,7 @@ namespace Umbraco.Web /// /// Raised when the published snapshot is being created /// - internal event EventHandler CreatingPublishedSnapshot; + internal event TypedEventHandler CreatingPublishedSnapshot; /// /// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this @@ -295,6 +300,23 @@ namespace Umbraco.Web _previewing = _previewToken.IsNullOrWhiteSpace() == false; } + private bool? _isDocumentRequest; + + /// + /// Checks if the request is a document request (i.e. one that the module should handle) + /// + /// + /// + /// + internal bool IsDocumentRequest(RoutableDocumentLookup docLookup) + { + if (_isDocumentRequest.HasValue) + return _isDocumentRequest.Value; + + _isDocumentRequest = docLookup.IsDocumentRequest(HttpContext, OriginalRequestUrl); + return _isDocumentRequest.Value; + } + // say we render a macro or RTE in a give 'preview' mode that might not be the 'current' one, // then due to the way it all works at the moment, the 'current' published snapshot need to be in the proper // default 'preview' mode - somehow we have to force it. and that could be recursive. diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index cbeee34444..70d9222dfc 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -39,6 +39,7 @@ namespace Umbraco.Web private readonly IPublishedRouter _publishedRouter; private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly BackgroundPublishedSnapshotNotifier _backgroundNotifier; + private readonly RoutableDocumentLookup _routableDocumentLookup; public UmbracoInjectedModule( IGlobalSettings globalSettings, @@ -46,16 +47,16 @@ namespace Umbraco.Web ILogger logger, IPublishedRouter publishedRouter, IUmbracoContextFactory umbracoContextFactory, - BackgroundPublishedSnapshotNotifier backgroundNotifier) + BackgroundPublishedSnapshotNotifier backgroundNotifier, + RoutableDocumentLookup routableDocumentLookup) { - _combinedRouteCollection = new Lazy(CreateRouteCollection); - _globalSettings = globalSettings; _runtime = runtime; _logger = logger; _publishedRouter = publishedRouter; _umbracoContextFactory = umbracoContextFactory; _backgroundNotifier = backgroundNotifier; + _routableDocumentLookup = routableDocumentLookup; } #region HttpModule event handlers @@ -91,13 +92,16 @@ namespace Umbraco.Web /// /// /// - private void UmbracoContext_CreatingPublishedSnapshot(object sender, EventArgs e) + private void UmbracoContext_CreatingPublishedSnapshot(UmbracoContext sender, EventArgs e) { // Wait for the notifier to complete if it's in progress, this is required because // Pure Live models along with content snapshots are updated on a background thread when schema // (doc types, data types) are changed so if that is still processing we need to wait before we access // the content snapshot to make sure it's the latest version. - if (_backgroundNotifier.Wait()) + // We only want to wait if this is a front-end request (not a back office request) so need to first check + // for that and then wait. + + if (sender.IsDocumentRequest(_routableDocumentLookup) &&_backgroundNotifier.Wait()) { _logger.Debug("Request was suspended while waiting for background cache notifications to complete"); } @@ -183,18 +187,18 @@ namespace Umbraco.Web var reason = EnsureRoutableOutcome.IsRoutable; // ensure this is a document request - if (EnsureDocumentRequest(httpContext, uri) == false) + if (!context.IsDocumentRequest(_routableDocumentLookup)) { reason = EnsureRoutableOutcome.NotDocumentRequest; } // ensure the runtime is in the proper state // and deal with needed redirects, etc - else if (EnsureRuntime(httpContext, uri) == false) + else if (!EnsureRuntime(httpContext, uri)) { reason = EnsureRoutableOutcome.NotReady; } // ensure Umbraco has documents to serve - else if (EnsureHasContent(context, httpContext) == false) + else if (!EnsureHasContent(context, httpContext)) { reason = EnsureRoutableOutcome.NoContent; } @@ -202,55 +206,7 @@ namespace Umbraco.Web return Attempt.If(reason == EnsureRoutableOutcome.IsRoutable, reason); } - /// - /// Ensures that the request is a document request (i.e. one that the module should handle) - /// - /// - /// - /// - private bool EnsureDocumentRequest(HttpContextBase httpContext, Uri uri) - { - var maybeDoc = true; - var lpath = uri.AbsolutePath.ToLowerInvariant(); - - // handle directory-urls used for asmx - // TODO: legacy - what's the point really? - var asmxPos = lpath.IndexOf(".asmx/", StringComparison.OrdinalIgnoreCase); - if (asmxPos >= 0) - { - // use uri.AbsolutePath, not path, 'cos path has been lowercased - httpContext.RewritePath(uri.AbsolutePath.Substring(0, asmxPos + 5), // filePath - uri.AbsolutePath.Substring(asmxPos + 5), // pathInfo - uri.Query.TrimStart('?')); - maybeDoc = false; - } - - // a document request should be - // /foo/bar/nil - // /foo/bar/nil/ - // /foo/bar/nil.aspx - // where /foo is not a reserved path - - // if the path contains an extension that is not .aspx - // then it cannot be a document request - var extension = Path.GetExtension(lpath); - if (maybeDoc && extension.IsNullOrWhiteSpace() == false && extension != ".aspx") - maybeDoc = false; - - // at that point, either we have no extension, or it is .aspx - - // if the path is reserved then it cannot be a document request - if (maybeDoc && _globalSettings.IsReservedPathOrUrl(lpath, httpContext, _combinedRouteCollection.Value)) - maybeDoc = false; - - //NOTE: No need to warn, plus if we do we should log the document, as this message doesn't really tell us anything :) - //if (!maybeDoc) - //{ - // Logger.Warn("Not a document"); - //} - - return maybeDoc; - } + private bool EnsureRuntime(HttpContextBase httpContext, Uri uri) { @@ -506,36 +462,6 @@ namespace Umbraco.Web #endregion - /// - /// This is used to be passed into the GlobalSettings.IsReservedPathOrUrl and will include some 'fake' routes - /// used to determine if a path is reserved. - /// - /// - /// This is basically used to reserve paths dynamically - /// - private readonly Lazy _combinedRouteCollection; - - private RouteCollection CreateRouteCollection() - { - var routes = new RouteCollection(); - - foreach (var route in RouteTable.Routes) - routes.Add(route); - - foreach (var reservedPath in UmbracoModule.ReservedPaths) - { - try - { - routes.Add("_umbreserved_" + reservedPath.ReplaceNonAlphanumericChars(""), - new Route(reservedPath.TrimStart('/'), new StopRoutingHandler())); - } - catch (Exception ex) - { - _logger.Error("Could not add reserved path route", ex); - } - } - - return routes; - } + } }