diff --git a/src/Orchard.Tests/Environment/WarmUp/WarmUpUtilityTests.cs b/src/Orchard.Tests/Environment/WarmUp/WarmUpUtilityTests.cs
index d402adc42..963f6efde 100644
--- a/src/Orchard.Tests/Environment/WarmUp/WarmUpUtilityTests.cs
+++ b/src/Orchard.Tests/Environment/WarmUp/WarmUpUtilityTests.cs
@@ -4,7 +4,7 @@ using Orchard.Environment.Warmup;
namespace Orchard.Tests.Environment.Warmup {
[TestFixture]
- public class WarmUpUtilityTests {
+ public class WarmupUtilityTests {
[Test]
public void EmptyStringsAreNotAllowed() {
diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj
index cc6d269b4..b11d043f2 100644
--- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj
+++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj
@@ -240,7 +240,7 @@
-
+
diff --git a/src/Orchard.Web/Global.asax.cs b/src/Orchard.Web/Global.asax.cs
index c9d4fb80d..3a6c20533 100644
--- a/src/Orchard.Web/Global.asax.cs
+++ b/src/Orchard.Web/Global.asax.cs
@@ -1,22 +1,18 @@
using System;
-using System.IO;
using System.Threading;
using System.Web;
-using System.Web.Hosting;
using System.Web.Mvc;
using System.Web.Routing;
using Autofac;
using Orchard.Environment;
-using Orchard.Environment.Warmup;
-using Orchard.Utility.Extensions;
namespace Orchard.Web {
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : HttpApplication {
- private static StartupResult _startupResult;
- private static EventWaitHandle _waitHandle;
+ private static IOrchardHost _host;
+ private static Exception _error;
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
@@ -26,77 +22,30 @@ namespace Orchard.Web {
LaunchStartupThread();
}
- ///
- /// Initializes Orchard's Host in a separate thread
- ///
- private static void LaunchStartupThread() {
- _startupResult = new StartupResult();
- _waitHandle = new AutoResetEvent(false);
-
- ThreadPool.QueueUserWorkItem(
- state => {
- try {
- RegisterRoutes(RouteTable.Routes);
- var host = OrchardStarter.CreateHost(MvcSingletons);
- host.Initialize();
- _startupResult.Host = host;
- }
- catch (Exception e) {
- _startupResult.Error = e;
- }
- finally {
- _waitHandle.Set();
- }
- });
- }
-
protected void Application_BeginRequest() {
- // Host is still starting up?
- if (_startupResult.Host == null && _startupResult.Error == null) {
+ if (_error != null) {
+ // Host startup resulted in an error
- // use the url as it was requested by the client
- // the real url might be different if it has been translated (proxy, load balancing, ...)
- var url = Request.ToUrlString();
- var virtualFileCopy = "~/App_Data/WarmUp/" + WarmupUtility.EncodeUrl(url.Trim('/'));
- var localCopy = HostingEnvironment.MapPath(virtualFileCopy);
-
- if (File.Exists(localCopy)) {
- // result should not be cached, even on proxies
- Context.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
- Context.Response.Cache.SetValidUntilExpires(false);
- Context.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
- Context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
- Context.Response.Cache.SetNoStore();
-
- Context.Response.WriteFile(localCopy);
- Context.Response.End();
- }
- else if(!File.Exists(Request.PhysicalPath)) {
- // there is no local copy and the host is not running
- // wait for the host to initialize
- _waitHandle.WaitOne();
- }
+ // Throw error once, and restart launch; machine state may have changed
+ // so we need to simulate a "restart".
+ var error = _error;
+ LaunchStartupThread();
+ throw error;
}
- else {
- if (_startupResult.Error != null) {
- // Host startup resulted in an error
- // Throw error once, and restart launch (machine state may have changed
- // so we need to simulate a "restart".
- var error = _startupResult.Error;
- LaunchStartupThread();
- throw error;
- }
-
- Context.Items["originalHttpContext"] = Context;
- _startupResult.Host.BeginRequest();
+ // Only notify if the host has started up
+ if (_host == null) {
+ return;
}
+
+ Context.Items["originalHttpContext"] = Context;
+ _host.BeginRequest();
}
protected void Application_EndRequest() {
// Only notify if the host has started up
- if (_startupResult.Host != null) {
- _startupResult.Host.EndRequest();
+ if (_host != null) {
+ _host.EndRequest();
}
}
@@ -105,5 +54,31 @@ namespace Orchard.Web {
builder.Register(ctx => ModelBinders.Binders).SingleInstance();
builder.Register(ctx => ViewEngines.Engines).SingleInstance();
}
+
+ ///
+ /// Initializes Orchard's Host in a separate thread
+ ///
+ private static void LaunchStartupThread() {
+ RegisterRoutes(RouteTable.Routes);
+
+ _host = null;
+ _error = null;
+
+ ThreadPool.QueueUserWorkItem(
+ state => {
+ try {
+ var host = OrchardStarter.CreateHost(MvcSingletons);
+ host.Initialize();
+ _host = host;
+ }
+ catch (Exception e) {
+ _error = e;
+ }
+ finally {
+ // Execute pending actions as the host is available
+ WarmupHttpModule.Signal();
+ }
+ });
+ }
}
}
diff --git a/src/Orchard.Web/Orchard.Web.csproj b/src/Orchard.Web/Orchard.Web.csproj
index db45cf2f6..783a5c721 100644
--- a/src/Orchard.Web/Orchard.Web.csproj
+++ b/src/Orchard.Web/Orchard.Web.csproj
@@ -127,6 +127,7 @@
Global.asax
+
Designer
diff --git a/src/Orchard.Web/WarmupHttpModule.cs b/src/Orchard.Web/WarmupHttpModule.cs
new file mode 100644
index 000000000..7f4acac6d
--- /dev/null
+++ b/src/Orchard.Web/WarmupHttpModule.cs
@@ -0,0 +1,170 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Web;
+using System.Web.Hosting;
+
+namespace Orchard.Web {
+ public class WarmupHttpModule : IHttpModule {
+ private const string WarmupFilesPath = "~/App_Data/Warmup/";
+ private HttpApplication _context;
+
+ public void Init(HttpApplication context) {
+ _context = context;
+ context.AddOnBeginRequestAsync(BeginBeginRequest, EndBeginRequest, null);
+ }
+
+ static IList _awaiting = new List();
+
+ public static bool InWarmup() {
+ if (_awaiting == null) return false;
+ lock (_awaiting) {
+ return _awaiting != null;
+ }
+ }
+
+ public static void Signal() {
+ lock(typeof(WarmupHttpModule)) {
+ var awaiting = _awaiting;
+ _awaiting = null;
+ foreach (var action in awaiting) {
+ action();
+ }
+ }
+ }
+
+ public static void Await(Action action) {
+ if (_awaiting == null) {
+ action();
+ return;
+ }
+
+ lock(typeof(WarmupHttpModule)) {
+ if (_awaiting == null) {
+ action();
+ return;
+ }
+ _awaiting.Add(action);
+ }
+ }
+
+ private IAsyncResult BeginBeginRequest(object sender, EventArgs e, AsyncCallback cb, object extradata) {
+ var asyncResult = new WarmupAsyncResult(cb);
+
+ // host is available, process every requests, or file is processed
+ if (!InWarmup() || DoBeginRequest()) {
+ asyncResult.Done();
+ }
+ else {
+ // this is the "on hold" execution path
+ Await(asyncResult.Done);
+ }
+
+ return asyncResult;
+ }
+
+ private void EndBeginRequest(IAsyncResult ar) {
+ ((WarmupAsyncResult)ar).Wait();
+ }
+
+ public void Dispose() {
+ }
+
+ ///
+ /// return true to put request on hold (until call to Signal()) - return false to allow pipeline to execute immediately
+ ///
+ ///
+ private bool DoBeginRequest() {
+ // use the url as it was requested by the client
+ // the real url might be different if it has been translated (proxy, load balancing, ...)
+ var url = ToUrlString(_context.Request);
+ var virtualFileCopy = WarmupFilesPath + EncodeUrl(url.Trim('/'));
+ var localCopy = HostingEnvironment.MapPath(virtualFileCopy);
+
+ if (localCopy != null && File.Exists(localCopy)) {
+ // result should not be cached, even on proxies
+ _context.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
+ _context.Response.Cache.SetValidUntilExpires(false);
+ _context.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
+ _context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
+ _context.Response.Cache.SetNoStore();
+
+ _context.Response.WriteFile(localCopy);
+ _context.Response.End();
+ return true;
+ }
+
+ // there is no local copy and the file exists
+ // serve the static file
+ if (File.Exists(_context.Request.PhysicalPath)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public static string EncodeUrl(string url) {
+ if(String.IsNullOrWhiteSpace(url)) {
+ throw new ArgumentException("url can't be empty");
+ }
+
+ var sb = new StringBuilder();
+ foreach(var c in url.ToLowerInvariant()) {
+ // only accept alphanumeric chars
+ if((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
+ sb.Append(c);
+ }
+ // otherwise encode them in UTF8
+ else {
+ sb.Append("_");
+ foreach(var b in Encoding.UTF8.GetBytes(new [] {c})) {
+ sb.Append(b.ToString("X"));
+ }
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ public static string ToUrlString(HttpRequest request) {
+ return string.Format("{0}://{1}{2}", request.Url.Scheme, request.Headers["Host"], request.RawUrl);
+ }
+
+ private class WarmupAsyncResult : IAsyncResult {
+ readonly EventWaitHandle _eventWaitHandle = new AutoResetEvent(false);
+
+ private readonly AsyncCallback _cb;
+
+ public WarmupAsyncResult(AsyncCallback cb) {
+ _cb = cb;
+ IsCompleted = false;
+ }
+
+ public void Done() {
+ IsCompleted = true;
+ _eventWaitHandle.Set();
+ _cb(this);
+ }
+
+ public object AsyncState {
+ get { return null; }
+ }
+
+ public WaitHandle AsyncWaitHandle {
+ get { throw new NotImplementedException(); }
+ }
+
+ public bool CompletedSynchronously {
+ get { return true; }
+ }
+
+ public bool IsCompleted { get; private set; }
+
+ public void Wait() {
+ _eventWaitHandle.WaitOne();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Web.config b/src/Orchard.Web/Web.config
index 1dba63ec0..498046275 100644
--- a/src/Orchard.Web/Web.config
+++ b/src/Orchard.Web/Web.config
@@ -120,6 +120,10 @@
+
+
+
+
diff --git a/src/Orchard/Environment/Warmup/StartupResult.cs b/src/Orchard/Environment/Warmup/StartupResult.cs
deleted file mode 100644
index 0a1400f90..000000000
--- a/src/Orchard/Environment/Warmup/StartupResult.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using System;
-
-namespace Orchard.Environment.Warmup {
- public class StartupResult {
- public IOrchardHost Host { get; set; }
- public Exception Error { get; set; }
- }
-}
diff --git a/src/Orchard/Environment/Warmup/WarmupUtility.cs b/src/Orchard/Environment/Warmup/WarmupUtility.cs
index e13003fd4..b964ef244 100644
--- a/src/Orchard/Environment/Warmup/WarmupUtility.cs
+++ b/src/Orchard/Environment/Warmup/WarmupUtility.cs
@@ -1,19 +1,29 @@
using System;
-using System.Linq;
using System.Text;
-using System.Text.RegularExpressions;
namespace Orchard.Environment.Warmup {
public static class WarmupUtility {
- private const string EncodingPattern = "[^a-z0-9]";
-
public static string EncodeUrl(string url) {
if(String.IsNullOrWhiteSpace(url)) {
throw new ArgumentException("url can't be empty");
}
- return Regex.Replace(url.ToLower(), EncodingPattern, m => "_" + Encoding.UTF8.GetBytes(m.Value).Select(b => b.ToString("X")).Aggregate((a, b) => a + b));
- }
+ var sb = new StringBuilder();
+ foreach (var c in url.ToLowerInvariant()) {
+ // only accept alphanumeric chars
+ if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
+ sb.Append(c);
+ }
+ // otherwise encode them in UTF8
+ else {
+ sb.Append("_");
+ foreach(var b in Encoding.UTF8.GetBytes(new [] {c})) {
+ sb.Append(b.ToString("X"));
+ }
+ }
+ }
+ return sb.ToString();
+ }
}
}
diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj
index dc713d2b6..f6e62d379 100644
--- a/src/Orchard/Orchard.Framework.csproj
+++ b/src/Orchard/Orchard.Framework.csproj
@@ -187,7 +187,6 @@
-