// // System.Web.HttpRuntime // // Authors: // Patrik Torstensson (ptorsten@hotmail.com) // Gaurav Vaish (gvaish@iitk.ac.in) // Gonzalo Paniagua Javier (gonzalo@ximian.com) // // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.IO; using System.Text; using System.Security; using System.Security.Permissions; using System.Threading; using System.Web.Configuration; using System.Web.UI; using System.Web.Util; using System.Web.Caching; namespace System.Web { public sealed class HttpRuntime { // Security permission helper objects private static IStackWalk appPathDiscoveryStackWalk; private static IStackWalk ctrlPrincipalStackWalk; private static IStackWalk sensitiveInfoStackWalk; private static IStackWalk unmgdCodeStackWalk; private static IStackWalk unrestrictedStackWalk; private static IStackWalk reflectionStackWalk; private static HttpRuntime _runtime; private static string appDomainAppId; private static string appDomainId; private static string appDomainAppPath; private static string appDomainAppVirtualPath; private Cache _cache; private int _activeRequests; private HttpWorkerRequest.EndOfSendNotification _endOfSendCallback; private AsyncCallback _handlerCallback; private WaitCallback unloadDomainCallback; private bool _firstRequestStarted; //private bool _firstRequestExecuted; private Exception _initError; private TimeoutManager timeoutManager; private QueueManager queueManager; private TraceManager traceManager; private WaitCallback doRequestCallback; private int pendingCallbacks; static HttpRuntime () { _runtime = new HttpRuntime (); _runtime.Init(); } public HttpRuntime () { doRequestCallback = new WaitCallback (DoRequest); } static internal object CreateInternalObject(Type type) { return Activator.CreateInstance(type, true); } private void Init () { try { _cache = new Cache (); timeoutManager = new TimeoutManager (); _endOfSendCallback = new HttpWorkerRequest.EndOfSendNotification (OnEndOfSend); _handlerCallback = new AsyncCallback (OnHandlerReady); unloadDomainCallback = new WaitCallback (DoUnload); AppDomain.CurrentDomain.DomainUnload += new EventHandler (OnDomainUnload); } catch (Exception error) { _initError = error; } } private void OnFirstRequestStart(HttpContext context) { if (_initError != null) throw _initError; try { WebConfigurationSettings.Init (context); traceManager = new TraceManager (); queueManager = new QueueManager (); } catch (Exception e) { _initError = e; } // If we got an error during init, throw to client now.. if (null != _initError) throw _initError; } /* Not used private void OnFirstRequestEnd() { } */ private void OnHandlerReady(IAsyncResult ar) { HttpContext context = (HttpContext) ar.AsyncState; try { IHttpAsyncHandler handler = context.AsyncHandler; try { handler.EndProcessRequest(ar); } catch (Exception error) { context.AddError(error); } } finally { context.AsyncHandler = null; } FinishRequest(context, context.Error); } private void OnEndOfSend(HttpWorkerRequest request, object data) { HttpContext context = (HttpContext) data; context.Request.Dispose(); context.Response.Dispose(); } internal void FinishRequest(HttpContext context, Exception error) { if (error == null) { try { context.Response.FlushAtEndOfRequest(); } catch (Exception obj) { error = obj; } } HttpWorkerRequest request = context.WorkerRequest; if (null != error) { WebTrace.WriteLine (error.ToString ()); // We don't want the 'headers sent error' context.Response.SetHeadersSent (false); context.Response.Clear (); context.Response.ClearHeaders (); if (!(error is HttpException)) { error = new HttpException (String.Empty, error); context.Response.StatusCode = 500; } else { context.Response.StatusCode = ((HttpException) error).GetHttpCode (); } if (!RedirectCustomError (context)) context.Response.Write (((HttpException) error).GetHtmlErrorMessage ()); context.Response.FinalFlush (); } /* * This is not being used. OnFirstRequestEnd is empty. if (!_firstRequestExecuted) { lock (this) { if (!_firstRequestExecuted) { _firstRequestExecuted = true; OnFirstRequestEnd(); } } } */ Interlocked.Decrement(ref _activeRequests); if (null != request) request.EndOfRequest(); TryExecuteQueuedRequests (); } bool RedirectCustomError (HttpContext context) { if (!context.IsCustomErrorEnabled) return false; CustomErrorsConfig config = null; try { config = (CustomErrorsConfig) context.GetConfig ("system.web/customErrors"); } catch { } if (config == null) { if (context.ErrorPage != null) return context.Response.RedirectCustomError (context.ErrorPage); return false; } string redirect = config [context.Response.StatusCode]; if (redirect == null) { redirect = context.ErrorPage; if (redirect == null) redirect = config.DefaultRedirect; } if (redirect == null) return false; return context.Response.RedirectCustomError (redirect); } internal static void FinishUnavailable (HttpWorkerRequest wr) { HttpContext context = new HttpContext (wr); HttpException exception = new HttpException (503, "Service unavailable"); Interlocked.Increment (ref _runtime._activeRequests); context.Response.InitializeWriter (); _runtime.FinishRequest (context, exception); } void DoUnload (object state) { AppDomain.Unload (AppDomain.CurrentDomain); } internal void Dispose() { WaitForRequests (2000); queueManager.Dispose (); // Send a 503 to all queued requests queueManager = null; _cache = null; HttpApplicationFactory.EndApplication (); } void OnDomainUnload (object o, EventArgs args) { HttpApplicationFactory.EndApplication (); } internal void ByeByeDomain () { HttpApplicationFactory.EndApplication (); ThreadPool.QueueUserWorkItem (unloadDomainCallback); } internal void WaitForRequests(int ms) { DateTime timeout = DateTime.Now.AddMilliseconds(ms); do { if (Interlocked.CompareExchange (ref _activeRequests, 0, 0) == 0) return; Thread.Sleep (100); } while (timeout > DateTime.Now); } internal void InternalExecuteRequest (HttpWorkerRequest request) { IHttpHandler handler; IHttpAsyncHandler async_handler; HttpContext context = new HttpContext(request); request.SetEndOfSendNotification(_endOfSendCallback, context); Interlocked.Increment(ref _activeRequests); try { if (!_firstRequestStarted) { lock (this) { if (!_firstRequestStarted) { OnFirstRequestStart(context); _firstRequestStarted = true; } } } // This *must* be done after the configuration is initialized. context.Response.InitializeWriter (); handler = HttpApplicationFactory.GetInstance(context); if (null == handler) throw new HttpException(FormatResourceString("unable_to_create_app")); if (handler is IHttpAsyncHandler) { async_handler = (IHttpAsyncHandler) handler; context.AsyncHandler = async_handler; async_handler.BeginProcessRequest(context, _handlerCallback, context); } else { handler.ProcessRequest(context); FinishRequest(context, null); } } catch (Exception error) { context.Response.InitializeWriter (); FinishRequest(context, error); } } void DoRequest (object o) { Interlocked.Decrement (ref pendingCallbacks); InternalExecuteRequest ((HttpWorkerRequest) o); } void TryExecuteQueuedRequests () { // Wait for pending jobs to start if (Interlocked.CompareExchange (ref pendingCallbacks, 3, 3) >= 3) return; HttpWorkerRequest wr = queueManager.GetNextRequest (null); if (wr == null) return; Interlocked.Increment (ref pendingCallbacks); ThreadPool.QueueUserWorkItem (doRequestCallback, wr); TryExecuteQueuedRequests (); } public static void ProcessRequest (HttpWorkerRequest request) { if (request == null) throw new ArgumentNullException ("request"); QueueManager mgr = _runtime.queueManager; if (_runtime._firstRequestStarted && mgr != null) { request = mgr.GetNextRequest (request); // We're busy, return immediately if (request == null) return; } _runtime.InternalExecuteRequest (request); } #if NET_1_1 public static void UnloadAppDomain () { _runtime.ByeByeDomain (); } #endif public static Cache Cache { get { return _runtime._cache; } } public static string AppDomainAppId { get { if (appDomainAppId == null) appDomainAppId = (string) AppDomain.CurrentDomain.GetData (".appId"); return appDomainAppId; } } public static string AppDomainAppPath { get { if (appDomainAppPath == null) appDomainAppPath = (string) AppDomain.CurrentDomain.GetData (".appPath"); return appDomainAppPath; } } public static string AppDomainAppVirtualPath { get { if (appDomainAppVirtualPath == null) appDomainAppVirtualPath = (string) AppDomain.CurrentDomain.GetData (".appVPath"); return appDomainAppVirtualPath; } } public static string AppDomainId { get { if (appDomainId == null) appDomainId = (string) AppDomain.CurrentDomain.GetData (".domainId"); return appDomainId; } } public static string AspInstallDirectory { get { return ICalls.GetMachineInstallDirectory (); } } public static string BinDirectory { get { return Path.Combine (AppDomainAppPath, "bin"); } } public static string ClrInstallDirectory { get { return ICalls.GetMachineInstallDirectory (); } } public static string CodegenDir { get { return AppDomain.CurrentDomain.SetupInformation.DynamicBase; } } public static bool IsOnUNCShare { get { // IsUnc broken under unix? return (!((int) Environment.OSVersion.Platform == 128) && new Uri ("file://" + ClrInstallDirectory).IsUnc); } } public static string MachineConfigurationDirectory { get { return Path.GetDirectoryName (WebConfigurationSettings.MachineConfigPath); } } internal static TimeoutManager TimeoutManager { get { return HttpRuntime._runtime.timeoutManager; } } internal static TraceManager TraceManager { get { return HttpRuntime._runtime.traceManager; } } public static void Close () { _runtime.Dispose (); } internal static string FormatResourceString (string key) { return GetResourceString (key); } internal static string FormatResourceString (string key, string arg0) { /*string format = GetResourceString (key); if (format == null) return null; return String.Format (format, arg0); */ return String.Format ("{0}: {1}", key, arg0); } [MonoTODO ("FormatResourceString (string, string, string)")] internal static string FormatResourceString (string key, string arg0, string type) { return String.Format ("{0}: {1} {2}", key, arg0, type); } [MonoTODO ("FormatResourceString (string, string, string, string)")] internal static string FormatResourceString (string key, string arg0, string arg1, string arg2) { return String.Format ("{0}: {1} {2} {3}", key, arg0, arg1, arg2); } [MonoTODO ("FormatResourceString (string, string[]")] internal static string FormatResourceString (string key, string[] args) { //StringBuilder sb = new StringBuilder (); /*sb.AppendFormat ("{0}: ", key); foreach (string s in args) sb.AppendFormat ("{0} ", s); if (sb.Length > 0) sb.Length--; return sb.ToString ();*/ string s = key + ": "; if (args != null) foreach (string k in args) s += k + " "; return s; } private static string GetResourceString (string key) { return _runtime.GetResourceStringFromResourceManager (key); } [MonoTODO ("GetResourceStringFromResourceManager (string)")] private string GetResourceStringFromResourceManager (string key) { return key; } #region Security Internal Methods (not impl) [MonoTODO ("Get Application path from the appdomain object")] internal static IStackWalk AppPathDiscovery { get { if (appPathDiscoveryStackWalk == null) { appPathDiscoveryStackWalk = new FileIOPermission ( FileIOPermissionAccess.PathDiscovery, ""); } return appPathDiscoveryStackWalk; } } internal static IStackWalk ControlPrincipal { get { if (ctrlPrincipalStackWalk == null) { ctrlPrincipalStackWalk = new SecurityPermission ( SecurityPermissionFlag.ControlPrincipal); } return ctrlPrincipalStackWalk; } } internal static IStackWalk Reflection { get { if (reflectionStackWalk == null) { reflectionStackWalk = new ReflectionPermission ( ReflectionPermissionFlag.TypeInformation | ReflectionPermissionFlag.MemberAccess); } return reflectionStackWalk; } } internal static IStackWalk SensitiveInformation { get { if (sensitiveInfoStackWalk == null) { sensitiveInfoStackWalk = new EnvironmentPermission ( PermissionState.Unrestricted); } return sensitiveInfoStackWalk; } } internal static IStackWalk UnmanagedCode { get { if (unmgdCodeStackWalk == null) { unmgdCodeStackWalk = new SecurityPermission ( SecurityPermissionFlag.UnmanagedCode); } return unmgdCodeStackWalk; } } internal static IStackWalk Unrestricted { get { if (unrestrictedStackWalk == null) { unrestrictedStackWalk = new PermissionSet ( PermissionState.Unrestricted); } return unrestrictedStackWalk; } } internal static IStackWalk FileReadAccess (string file) { return new FileIOPermission (FileIOPermissionAccess.Read, file); } internal static IStackWalk PathDiscoveryAccess (string path) { return new FileIOPermission (FileIOPermissionAccess.PathDiscovery, path); } #endregion } }