mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-26 12:03:16 +08:00
#18162: Fixing Orchard.WarmupStarter location
Work Item: 18162 --HG-- branch : 1.x rename : src/Orchard.Startup/Orchard.WarmupStarter.csproj => src/Orchard.WarmupStarter/Orchard.WarmupStarter.csproj rename : src/Orchard.Startup/Properties/AssemblyInfo.cs => src/Orchard.WarmupStarter/Properties/AssemblyInfo.cs rename : src/Orchard.Startup/Starter.cs => src/Orchard.WarmupStarter/Starter.cs rename : src/Orchard.Startup/WarmupHttpModule.cs => src/Orchard.WarmupStarter/WarmupHttpModule.cs rename : src/Orchard.Startup/WarmupUtility.cs => src/Orchard.WarmupStarter/WarmupUtility.cs
This commit is contained in:
51
src/Orchard.WarmupStarter/Orchard.WarmupStarter.csproj
Normal file
51
src/Orchard.WarmupStarter/Orchard.WarmupStarter.csproj
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{5C0D5249-AEF5-4BB6-8F5F-057B91AC2D7A}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Orchard.WarmupStarter</RootNamespace>
|
||||
<AssemblyName>Orchard.WarmupStarter</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Web" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Starter.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="WarmupHttpModule.cs" />
|
||||
<Compile Include="WarmupUtility.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
39
src/Orchard.WarmupStarter/Properties/AssemblyInfo.cs
Normal file
39
src/Orchard.WarmupStarter/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Orchard.WarmupStarter")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyProduct("Orchard")]
|
||||
[assembly: AssemblyCopyright("Copyright © Outercurve Foundation 2011")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("02319f2f-22ec-448d-b4f2-568fb8502242")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.3.0")]
|
||||
[assembly: AssemblyFileVersion("1.3.0")]
|
||||
|
||||
// Enable web application to call this assembly in Full Trust
|
||||
[assembly: AllowPartiallyTrustedCallers]
|
||||
105
src/Orchard.WarmupStarter/Starter.cs
Normal file
105
src/Orchard.WarmupStarter/Starter.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
|
||||
namespace Orchard.WarmupStarter {
|
||||
public class Starter<T> where T : class {
|
||||
private readonly Func<HttpApplication, T> _initialization;
|
||||
private readonly Action<HttpApplication, T> _beginRequest;
|
||||
private readonly Action<HttpApplication, T> _endRequest;
|
||||
private readonly object _synLock = new object();
|
||||
/// <summary>
|
||||
/// The result of the initialization queued work item.
|
||||
/// Set only when initialization has completed without errors.
|
||||
/// </summary>
|
||||
private volatile T _initializationResult;
|
||||
/// <summary>
|
||||
/// The (potential) error raised by the initialization thread. This is a "one-time"
|
||||
/// error signal, so that we can restart the initialization once another request
|
||||
/// comes in.
|
||||
/// </summary>
|
||||
private volatile Exception _error;
|
||||
/// <summary>
|
||||
/// The (potential) error from the previous initiazalition. We need to
|
||||
/// keep this error active until the next initialization is finished,
|
||||
/// so that we can keep reporting the error for all incoming requests.
|
||||
/// </summary>
|
||||
private volatile Exception _previousError;
|
||||
|
||||
public Starter(Func<HttpApplication, T> initialization, Action<HttpApplication, T> beginRequest, Action<HttpApplication, T> endRequest) {
|
||||
_initialization = initialization;
|
||||
_beginRequest = beginRequest;
|
||||
_endRequest = endRequest;
|
||||
}
|
||||
|
||||
public void OnApplicationStart(HttpApplication application) {
|
||||
LaunchStartupThread(application);
|
||||
}
|
||||
|
||||
public void OnBeginRequest(HttpApplication application) {
|
||||
// Initialization resulted in an error
|
||||
if (_error != null) {
|
||||
// Save error for next requests and restart async initialization.
|
||||
// Note: The reason we have to retry the initialization is that the
|
||||
// application environment may change between requests,
|
||||
// e.g. App_Data is made read-write for the AppPool.
|
||||
bool restartInitialization = false;
|
||||
|
||||
lock (_synLock) {
|
||||
if (_error != null) {
|
||||
_previousError = _error;
|
||||
_error = null;
|
||||
restartInitialization = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (restartInitialization) {
|
||||
LaunchStartupThread(application);
|
||||
}
|
||||
}
|
||||
|
||||
// Previous initialization resulted in an error (and another initialization is running)
|
||||
if (_previousError != null) {
|
||||
throw new ApplicationException("Error during application initialization", _previousError);
|
||||
}
|
||||
|
||||
// Only notify if the initialization has successfully completed
|
||||
if (_initializationResult != null) {
|
||||
_beginRequest(application, _initializationResult);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnEndRequest(HttpApplication application) {
|
||||
// Only notify if the initialization has successfully completed
|
||||
if (_initializationResult != null) {
|
||||
_endRequest(application, _initializationResult);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the initialization delegate asynchronously in a queued work item
|
||||
/// </summary>
|
||||
public void LaunchStartupThread(HttpApplication application) {
|
||||
// Make sure incoming requests are queued
|
||||
WarmupHttpModule.SignalWarmupStart();
|
||||
|
||||
ThreadPool.QueueUserWorkItem(
|
||||
state => {
|
||||
try {
|
||||
var result = _initialization(application);
|
||||
_initializationResult = result;
|
||||
}
|
||||
catch (Exception e) {
|
||||
lock (_synLock) {
|
||||
_error = e;
|
||||
_previousError = null;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// Execute pending requests as the initialization is over
|
||||
WarmupHttpModule.SignalWarmupDone();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
159
src/Orchard.WarmupStarter/WarmupHttpModule.cs
Normal file
159
src/Orchard.WarmupStarter/WarmupHttpModule.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
|
||||
namespace Orchard.WarmupStarter {
|
||||
public class WarmupHttpModule : IHttpModule {
|
||||
private HttpApplication _context;
|
||||
private static object _synLock = new object();
|
||||
private static IList<Action> _awaiting = new List<Action>();
|
||||
|
||||
public void Init(HttpApplication context) {
|
||||
_context = context;
|
||||
context.AddOnBeginRequestAsync(BeginBeginRequest, EndBeginRequest, null);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
}
|
||||
|
||||
private static bool InWarmup() {
|
||||
lock (_synLock) {
|
||||
return _awaiting != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Warmup code is about to start: Any new incoming request is queued
|
||||
/// until "SignalWarmupDone" is called.
|
||||
/// </summary>
|
||||
public static void SignalWarmupStart() {
|
||||
lock (_synLock) {
|
||||
if (_awaiting == null) {
|
||||
_awaiting = new List<Action>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Warmup code just completed: All pending requests in the "_await" queue are processed,
|
||||
/// and any new incoming request is now processed immediately.
|
||||
/// </summary>
|
||||
public static void SignalWarmupDone() {
|
||||
IList<Action> temp;
|
||||
|
||||
lock (_synLock) {
|
||||
temp = _awaiting;
|
||||
_awaiting = null;
|
||||
}
|
||||
|
||||
if (temp != null) {
|
||||
foreach (var action in temp) {
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueue or directly process action depending on current mode.
|
||||
/// </summary>
|
||||
private void Await(Action action) {
|
||||
Action temp = action;
|
||||
|
||||
lock (_synLock) {
|
||||
if (_awaiting != null) {
|
||||
temp = null;
|
||||
_awaiting.Add(action);
|
||||
}
|
||||
}
|
||||
|
||||
if (temp != null) {
|
||||
temp();
|
||||
}
|
||||
}
|
||||
|
||||
private IAsyncResult BeginBeginRequest(object sender, EventArgs e, AsyncCallback cb, object extradata) {
|
||||
// host is available, process every requests, or file is processed
|
||||
if (!InWarmup() || WarmupUtility.DoBeginRequest(_context)) {
|
||||
var asyncResult = new DoneAsyncResult(extradata);
|
||||
cb(asyncResult);
|
||||
return asyncResult;
|
||||
}
|
||||
else {
|
||||
// this is the "on hold" execution path
|
||||
var asyncResult = new WarmupAsyncResult(cb, extradata);
|
||||
Await(asyncResult.Completed);
|
||||
return asyncResult;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EndBeginRequest(IAsyncResult ar) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AsyncResult for "on hold" request (resumes when "Completed()" is called)
|
||||
/// </summary>
|
||||
private class WarmupAsyncResult : IAsyncResult {
|
||||
private readonly EventWaitHandle _eventWaitHandle = new AutoResetEvent(false/*initialState*/);
|
||||
private readonly AsyncCallback _cb;
|
||||
private readonly object _asyncState;
|
||||
private bool _isCompleted;
|
||||
|
||||
public WarmupAsyncResult(AsyncCallback cb, object asyncState) {
|
||||
_cb = cb;
|
||||
_asyncState = asyncState;
|
||||
_isCompleted = false;
|
||||
}
|
||||
|
||||
public void Completed() {
|
||||
_isCompleted = true;
|
||||
_eventWaitHandle.Set();
|
||||
_cb(this);
|
||||
}
|
||||
|
||||
bool IAsyncResult.CompletedSynchronously {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
bool IAsyncResult.IsCompleted {
|
||||
get { return _isCompleted; }
|
||||
}
|
||||
|
||||
object IAsyncResult.AsyncState {
|
||||
get { return _asyncState; }
|
||||
}
|
||||
|
||||
WaitHandle IAsyncResult.AsyncWaitHandle {
|
||||
get { return _eventWaitHandle; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Async result for "ok to process now" requests
|
||||
/// </summary>
|
||||
private class DoneAsyncResult : IAsyncResult {
|
||||
private readonly object _asyncState;
|
||||
private static readonly WaitHandle _waitHandle = new ManualResetEvent(true/*initialState*/);
|
||||
|
||||
public DoneAsyncResult(object asyncState) {
|
||||
_asyncState = asyncState;
|
||||
}
|
||||
|
||||
bool IAsyncResult.CompletedSynchronously {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
bool IAsyncResult.IsCompleted {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
WaitHandle IAsyncResult.AsyncWaitHandle {
|
||||
get { return _waitHandle; }
|
||||
}
|
||||
|
||||
object IAsyncResult.AsyncState {
|
||||
get { return _asyncState; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
71
src/Orchard.WarmupStarter/WarmupUtility.cs
Normal file
71
src/Orchard.WarmupStarter/WarmupUtility.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using System.Web.Hosting;
|
||||
|
||||
namespace Orchard.WarmupStarter {
|
||||
public static class WarmupUtility {
|
||||
public static readonly string WarmupFilesPath = "~/App_Data/Warmup/";
|
||||
/// <summary>
|
||||
/// return true to put request on hold (until call to Signal()) - return false to allow pipeline to execute immediately
|
||||
/// </summary>
|
||||
/// <param name="httpApplication"></param>
|
||||
/// <returns></returns>
|
||||
public static bool DoBeginRequest(HttpApplication httpApplication) {
|
||||
// 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(httpApplication.Request);
|
||||
var virtualFileCopy = WarmupUtility.EncodeUrl(url.Trim('/'));
|
||||
var localCopy = Path.Combine(HostingEnvironment.MapPath(WarmupFilesPath), virtualFileCopy);
|
||||
|
||||
if (File.Exists(localCopy)) {
|
||||
// result should not be cached, even on proxies
|
||||
httpApplication.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
|
||||
httpApplication.Response.Cache.SetValidUntilExpires(false);
|
||||
httpApplication.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
|
||||
httpApplication.Response.Cache.SetCacheability(HttpCacheability.NoCache);
|
||||
httpApplication.Response.Cache.SetNoStore();
|
||||
|
||||
httpApplication.Response.WriteFile(localCopy);
|
||||
httpApplication.Response.End();
|
||||
return true;
|
||||
}
|
||||
|
||||
// there is no local copy and the file exists
|
||||
// serve the static file
|
||||
if (File.Exists(httpApplication.Request.PhysicalPath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string ToUrlString(HttpRequest request) {
|
||||
return string.Format("{0}://{1}{2}", request.Url.Scheme, request.Headers["Host"], request.RawUrl);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user