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.

This commit is contained in:
Shannon
2019-09-10 14:10:26 +10:00
parent 596c745a19
commit b997281e1f
5 changed files with 148 additions and 89 deletions

View File

@@ -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
{
/// <summary>
/// Utility class used to check if the current request is for a front-end request
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public sealed class RoutableDocumentLookup
{
public RoutableDocumentLookup(ILogger logger, IGlobalSettings globalSettings)
{
_logger = logger;
_globalSettings = globalSettings;
_combinedRouteCollection = new Lazy<RouteCollection>(CreateRouteCollection);
}
/// <summary>
/// Checks if the request is a document request (i.e. one that the module should handle)
/// </summary>
/// <param name="httpContext"></param>
/// <param name="uri"></param>
/// <returns></returns>
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<UmbracoModule>("Not a document");
//}
return maybeDoc;
}
/// <summary>
/// This is used to be passed into the GlobalSettings.IsReservedPathOrUrl and will include some 'fake' routes
/// used to determine if a path is reserved.
/// </summary>
/// <remarks>
/// This is basically used to reserve paths dynamically
/// </remarks>
private readonly Lazy<RouteCollection> _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<UmbracoModule>("Could not add reserved path route", ex);
}
}
return routes;
}
}
}

View File

@@ -126,6 +126,7 @@ namespace Umbraco.Web.Runtime
composition.RegisterUnique(f => new DistributedCache());
composition.RegisterUnique<BackgroundPublishedSnapshotNotifier>();
composition.RegisterUnique<RoutableDocumentLookup>();
// replace some services
composition.RegisterUnique<IEventMessagesFactory, DefaultEventMessagesFactory>();

View File

@@ -230,6 +230,7 @@
<Compile Include="PublishedCache\NuCache\Snap\GenObj.cs" />
<Compile Include="PublishedCache\NuCache\Snap\GenRef.cs" />
<Compile Include="PublishedCache\NuCache\Snap\LinkedNode.cs" />
<Compile Include="RoutableDocumentLookup.cs" />
<Compile Include="Routing\DefaultMediaUrlProvider.cs" />
<Compile Include="Routing\IMediaUrlProvider.cs" />
<Compile Include="Routing\IPublishedRouter.cs" />

View File

@@ -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
{
/// <summary>
/// Class that encapsulates Umbraco information of a specific HTTP request
/// </summary>
@@ -80,7 +85,7 @@ namespace Umbraco.Web
/// <summary>
/// Raised when the published snapshot is being created
/// </summary>
internal event EventHandler CreatingPublishedSnapshot;
internal event TypedEventHandler<UmbracoContext, EventArgs> CreatingPublishedSnapshot;
/// <summary>
/// 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;
/// <summary>
/// Checks if the request is a document request (i.e. one that the module should handle)
/// </summary>
/// <param name="httpContext"></param>
/// <param name="uri"></param>
/// <returns></returns>
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.

View File

@@ -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<RouteCollection>(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
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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<UmbracoModule>("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);
}
/// <summary>
/// Ensures that the request is a document request (i.e. one that the module should handle)
/// </summary>
/// <param name="httpContext"></param>
/// <param name="uri"></param>
/// <returns></returns>
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<UmbracoModule>("Not a document");
//}
return maybeDoc;
}
private bool EnsureRuntime(HttpContextBase httpContext, Uri uri)
{
@@ -506,36 +462,6 @@ namespace Umbraco.Web
#endregion
/// <summary>
/// This is used to be passed into the GlobalSettings.IsReservedPathOrUrl and will include some 'fake' routes
/// used to determine if a path is reserved.
/// </summary>
/// <remarks>
/// This is basically used to reserve paths dynamically
/// </remarks>
private readonly Lazy<RouteCollection> _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<UmbracoModule>("Could not add reserved path route", ex);
}
}
return routes;
}
}
}