From 8c97e367a2258438ad3bc7644644cfa76eac08ee Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 13 Feb 2013 06:12:43 +0600 Subject: [PATCH] Adds DatabaseServerRegistrar and ServerRegistrationEventHandler, we can now ensure that all server add themselves to the database table automatically. --- .../Factories/ServerRegistrationFactory.cs | 2 +- src/Umbraco.Core/Services/ServiceContext.cs | 12 ++ .../Sync/ConfigServerRegistrar.cs | 1 + .../Sync/DatabaseServerRegistrar.cs | 23 ++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../config/ClientDependency.config | 2 +- .../Routing/EnsureRoutableOutcome.cs | 14 +++ .../Routing/RoutableAttemptEventArgs.cs | 18 +++ .../Routing/UmbracoRequestEventArgs.cs | 20 ++++ .../ServerRegistrationEventHandler.cs | 105 ++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 4 + src/Umbraco.Web/UmbracoModule.cs | 54 ++++++--- 12 files changed, 239 insertions(+), 17 deletions(-) create mode 100644 src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs create mode 100644 src/Umbraco.Web/Routing/EnsureRoutableOutcome.cs create mode 100644 src/Umbraco.Web/Routing/RoutableAttemptEventArgs.cs create mode 100644 src/Umbraco.Web/Routing/UmbracoRequestEventArgs.cs create mode 100644 src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs diff --git a/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs b/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs index edeeff0fab..ec0ef6ab28 100644 --- a/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ServerRegistrationFactory.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Persistence.Factories public ServerRegistration BuildEntity(ServerRegistrationDto dto) { - return new ServerRegistration(dto.Id, dto.ComputerName, dto.Address, dto.DateRegistered, dto.LastNotified); + return new ServerRegistration(dto.Id, dto.Address, dto.ComputerName, dto.DateRegistered, dto.LastNotified); } public ServerRegistrationDto BuildDto(ServerRegistration entity) diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index aba7ac5232..7d7d877a86 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -20,6 +20,7 @@ namespace Umbraco.Core.Services private Lazy _dataTypeService; private Lazy _fileService; private Lazy _localizationService; + private Lazy _serverRegistrationService; /// /// Constructor @@ -47,6 +48,9 @@ namespace Umbraco.Core.Services var provider = dbUnitOfWorkProvider; var fileProvider = fileUnitOfWorkProvider; + if (_serverRegistrationService == null) + _serverRegistrationService = new Lazy(() => new ServerRegistrationService(provider, repositoryFactory.Value)); + if (_userService == null) _userService = new Lazy(() => new UserService(provider, repositoryFactory.Value)); @@ -72,6 +76,14 @@ namespace Umbraco.Core.Services _localizationService = new Lazy(() => new LocalizationService(provider, repositoryFactory.Value)); } + /// + /// Gets the + /// + internal ServerRegistrationService ServerRegistrationService + { + get { return _serverRegistrationService.Value; } + } + /// /// Gets the /// diff --git a/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs b/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs index 27610285cd..e037eb148d 100644 --- a/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs +++ b/src/Umbraco.Core/Sync/ConfigServerRegistrar.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Xml; using Umbraco.Core.Configuration; +using Umbraco.Core.Models; namespace Umbraco.Core.Sync { diff --git a/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs b/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs new file mode 100644 index 0000000000..a2e7ba187e --- /dev/null +++ b/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Sync +{ + /// + /// A registrar that stores registered server nodes in a database + /// + internal class DatabaseServerRegistrar : IServerRegistrar + { + private readonly ServerRegistrationService _registrationService; + + public DatabaseServerRegistrar(ServerRegistrationService registrationService) + { + _registrationService = registrationService; + } + + public IEnumerable Registrations + { + get { return _registrationService.GetActiveServers(); } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3b3754d4f7..b183015b23 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -676,6 +676,7 @@ + diff --git a/src/Umbraco.Web.UI/config/ClientDependency.config b/src/Umbraco.Web.UI/config/ClientDependency.config index 3eb97e5788..936b7e6332 100644 --- a/src/Umbraco.Web.UI/config/ClientDependency.config +++ b/src/Umbraco.Web.UI/config/ClientDependency.config @@ -10,7 +10,7 @@ NOTES: * Compression/Combination/Minification is not enabled unless debug="false" is specified on the 'compiliation' element in the web.config * A new version will invalidate both client and server cache and create new persisted files --> - + diff --git a/src/Umbraco.Web/Routing/EnsureRoutableOutcome.cs b/src/Umbraco.Web/Routing/EnsureRoutableOutcome.cs new file mode 100644 index 0000000000..9060b9f773 --- /dev/null +++ b/src/Umbraco.Web/Routing/EnsureRoutableOutcome.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Web.Routing +{ + /// + /// Reasons a request was not routable on the front-end + /// + internal enum EnsureRoutableOutcome + { + IsRoutable = 0, + NotDocumentRequest = 10, + NotReady = 11, + NotConfigured = 12, + NoContent = 13 + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/RoutableAttemptEventArgs.cs b/src/Umbraco.Web/Routing/RoutableAttemptEventArgs.cs new file mode 100644 index 0000000000..7a7bc37d5c --- /dev/null +++ b/src/Umbraco.Web/Routing/RoutableAttemptEventArgs.cs @@ -0,0 +1,18 @@ +using System.Web; + +namespace Umbraco.Web.Routing +{ + /// + /// Event args containing information about why the request was not routable, or if it is routable + /// + internal class RoutableAttemptEventArgs : UmbracoRequestEventArgs + { + public EnsureRoutableOutcome Outcome { get; private set; } + + public RoutableAttemptEventArgs(EnsureRoutableOutcome reason, UmbracoContext umbracoContext, HttpContextBase httpContext) + : base(umbracoContext, httpContext) + { + Outcome = reason; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/UmbracoRequestEventArgs.cs b/src/Umbraco.Web/Routing/UmbracoRequestEventArgs.cs new file mode 100644 index 0000000000..2c50c972f5 --- /dev/null +++ b/src/Umbraco.Web/Routing/UmbracoRequestEventArgs.cs @@ -0,0 +1,20 @@ +using System; +using System.Web; + +namespace Umbraco.Web.Routing +{ + /// + /// Event args used for event launched during a request (like in the UmbracoModule) + /// + internal class UmbracoRequestEventArgs : EventArgs + { + public UmbracoContext UmbracoContext { get; private set; } + public HttpContextBase HttpContext { get; private set; } + + public UmbracoRequestEventArgs(UmbracoContext umbracoContext, HttpContextBase httpContext) + { + UmbracoContext = umbracoContext; + HttpContext = httpContext; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs new file mode 100644 index 0000000000..9760ec95fa --- /dev/null +++ b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Web; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.Strategies +{ + /// + /// This will ensure that the server is automatically registered in the database as an active node + /// on application startup and whenever a back office request occurs. + /// + /// + /// We do this on app startup to ensure that the server is in the database but we also do it for the first 'x' times + /// a back office request is made so that we can tell if they are using https protocol which would update to that address + /// in the database. The first front-end request probably wouldn't be an https request. + /// + /// For back office requests (so that we don't constantly make db calls), we'll only update the database when we detect at least + /// a timespan of 1 minute between requests. + /// + public sealed class ServerRegistrationEventHandler : ApplicationEventHandler + { + private static bool _initUpdated = false; + private static DateTime _lastUpdated = DateTime.MinValue; + private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + + /// + /// Update the database with this entry and bind to request events + /// + /// + /// + protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + //bind to event + UmbracoModule.RouteAttempt += UmbracoModuleRouteAttempt; + } + + + static void UmbracoModuleRouteAttempt(object sender, Routing.RoutableAttemptEventArgs e) + { + if (e.HttpContext.Request == null || e.HttpContext.Request.Url == null) return; + + if (e.Outcome == EnsureRoutableOutcome.IsRoutable) + { + using (var lck = new UpgradeableReadLock(Locker)) + { + //we only want to do the initial update once + if (!_initUpdated) + { + lck.UpgradeToWriteLock(); + _initUpdated = true; + UpdateServerEntry(e.HttpContext, e.UmbracoContext.Application); + return; + } + } + } + + //if it is not a document request, we'll check if it is a back end request + if (e.Outcome == EnsureRoutableOutcome.NotDocumentRequest) + { + var authority = e.HttpContext.Request.Url.GetLeftPart(UriPartial.Authority); + var afterAuthority = e.HttpContext.Request.Url.GetLeftPart(UriPartial.Query) + .TrimStart(authority) + .TrimStart("/"); + + //check if this is in the umbraco back office + if (afterAuthority.InvariantStartsWith(GlobalSettings.Path.TrimStart("/"))) + { + //yup it's a back office request! + using (var lck = new UpgradeableReadLock(Locker)) + { + //we don't want to update if it's not been at least a minute since last time + var isItAMinute = DateTime.Now.Subtract(_lastUpdated).TotalSeconds >= 60; + if (isItAMinute) + { + lck.UpgradeToWriteLock(); + _initUpdated = true; + _lastUpdated = DateTime.Now; + UpdateServerEntry(e.HttpContext, e.UmbracoContext.Application); + } + } + } + } + } + + + private static void UpdateServerEntry(HttpContextBase httpContext, ApplicationContext applicationContext) + { + try + { + var address = httpContext.Request.Url.GetLeftPart(UriPartial.Authority); + applicationContext.Services.ServerRegistrationService.EnsureActive(address); + } + catch (Exception e) + { + LogHelper.Error("Failed to update server record in database.", e); + } + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 8004f1e490..19395f05e1 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -351,7 +351,9 @@ + + @@ -360,6 +362,7 @@ + @@ -506,6 +509,7 @@ + ASPXCodeBehind diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index dc4fb90784..3d9ab57d90 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -94,8 +94,14 @@ namespace Umbraco.Web } // do not process if this request is not a front-end routable page - if (!EnsureUmbracoRoutablePage(umbracoContext, httpContext)) - return; + var isRoutableAttempt = EnsureUmbracoRoutablePage(umbracoContext, httpContext); + //raise event here + OnRouteAttempt(new RoutableAttemptEventArgs(isRoutableAttempt.Result, umbracoContext, httpContext)); + if (!isRoutableAttempt.Success) + { + return; + } + httpContext.Trace.Write("UmbracoModule", "Umbraco request confirmed"); @@ -148,7 +154,7 @@ namespace Umbraco.Web #region Route helper methods /// - /// This is a performance tweak to check if this is a .css, .js or .ico file request since + /// This is a performance tweak to check if this is a .css, .js or .ico, .jpg, .jpeg, .png, .gif file request since /// .Net will pass these requests through to the module when in integrated mode. /// We want to ignore all of these requests immediately. /// @@ -156,7 +162,7 @@ namespace Umbraco.Web /// internal bool IsClientSideRequest(Uri url) { - var toIgnore = new[] { ".js", ".css", ".ico" }; + var toIgnore = new[] { ".js", ".css", ".ico", ".png", ".jpg", ".jpeg", ".gif" }; return toIgnore.Any(x => Path.GetExtension(url.LocalPath).InvariantEquals(x)); } @@ -166,24 +172,34 @@ namespace Umbraco.Web /// /// /// - internal bool EnsureUmbracoRoutablePage(UmbracoContext context, HttpContextBase httpContext) + internal Attempt EnsureUmbracoRoutablePage(UmbracoContext context, HttpContextBase httpContext) { var uri = context.OriginalRequestUrl; + var reason = EnsureRoutableOutcome.IsRoutable;; + // ensure this is a document request if (!EnsureDocumentRequest(httpContext, uri)) - return false; + { + reason = EnsureRoutableOutcome.NotDocumentRequest; + } // ensure Umbraco is ready to serve documents - if (!EnsureIsReady(httpContext, uri)) - return false; + else if (!EnsureIsReady(httpContext, uri)) + { + reason = EnsureRoutableOutcome.NotReady; + } // ensure Umbraco is properly configured to serve documents - if (!EnsureIsConfigured(httpContext, uri)) - return false; + else if (!EnsureIsConfigured(httpContext, uri)) + { + reason = EnsureRoutableOutcome.NotConfigured; + } // ensure Umbraco has documents to serve - if (!EnsureHasContent(context, httpContext)) - return false; + else if (!EnsureHasContent(context, httpContext)) + { + reason = EnsureRoutableOutcome.NoContent; + } - return true; + return new Attempt(reason == EnsureRoutableOutcome.IsRoutable, reason); } /// @@ -318,8 +334,7 @@ namespace Umbraco.Web /// Rewrites to the correct Umbraco handler, either WebForms or Mvc /// /// - /// - /// + /// private void RewriteToUmbracoHandler(HttpContextBase context, PublishedContentRequest pcr) { // NOTE: we do not want to use TransferRequest even though many docs say it is better with IIS7, turns out this is @@ -432,5 +447,14 @@ namespace Umbraco.Web i.DisposeIfDisposable(); } } + + #region Events + internal static event EventHandler RouteAttempt; + private void OnRouteAttempt(RoutableAttemptEventArgs args) + { + if (RouteAttempt != null) + RouteAttempt(this, args); + } + #endregion } }