2 // System.Web.HttpRuntime.cs
5 // Miguel de Icaza (miguel@novell.com)
6 // Marek Habersack <mhabersack@novell.com>
9 // Copyright (C) 2005-2010 Novell, Inc (http://www.novell.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 // TODO: Call HttpRequest.CloseInputStream when we finish a request, as we are using the IntPtr stream.
36 using System.Globalization;
37 using System.Collections;
38 using System.Reflection;
39 using System.Security;
40 using System.Security.Permissions;
41 using System.Web.Caching;
42 using System.Web.Configuration;
43 using System.Web.Management;
45 using System.Web.Util;
49 using System.Threading;
53 using System.CodeDom.Compiler;
54 using System.Web.Compilation;
59 // CAS - no InheritanceDemand here as the class is sealed
60 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
61 public sealed class HttpRuntime
63 static bool domainUnloading;
66 static QueueManager queue_manager { get { return _runtime._queue_manager; } }
67 static TraceManager trace_manager { get { return _runtime._trace_manager; } }
68 static Cache cache { get { return _runtime._cache; } }
69 static Cache internalCache { get { return _runtime._internalCache; } }
70 static WaitCallback do_RealProcessRequest;
72 QueueManager _queue_manager;
73 TraceManager _trace_manager;
79 WebConfigurationManager.Init ();
80 _queue_manager = new QueueManager ();
81 _trace_manager = new TraceManager ();
82 _cache = new Cache ();
83 _internalCache = new Cache();
84 _internalCache.DependencyCache = _cache;
87 static HttpRuntime _runtimeInstance {
89 HttpRuntime runtime = (HttpRuntime) AppDomain.CurrentDomain.GetData ("HttpRuntime");
91 lock (typeof (HttpRuntime)) {
92 runtime = (HttpRuntime) AppDomain.CurrentDomain.GetData ("HttpRuntime");
93 if (runtime == null) {
94 runtime = new HttpRuntime ();
95 AppDomain.CurrentDomain.SetData ("HttpRuntime", runtime);
101 static HttpRuntime _runtime
105 if (HttpContext.Current != null)
106 return HttpContext.Current.HttpRuntimeInstance;
108 return _runtimeInstance;
112 static QueueManager queue_manager;
113 static TraceManager trace_manager;
115 static Cache internalCache;
116 static WaitCallback do_RealProcessRequest;
117 static HttpWorkerRequest.EndOfSendNotification end_of_send_cb;
118 static Exception initialException;
119 static bool firstRun;
120 static bool assemblyMappingEnabled;
121 static object assemblyMappingLock = new object ();
122 static object appOfflineLock = new object ();
123 static HttpRuntimeSection runtime_section;
125 public HttpRuntime ()
131 static HttpRuntime ()
136 WebConfigurationManager.Init ();
138 SettingsMappingManager.Init ();
140 runtime_section = (HttpRuntimeSection) WebConfigurationManager.GetSection ("system.web/httpRuntime");
141 } catch (Exception ex) {
142 initialException = ex;
145 // The classes in whose constructors exceptions may be thrown, should be handled the same way QueueManager
146 // and TraceManager are below. The constructors themselves MUST NOT throw any exceptions - we MUST be sure
147 // the objects are created here. The exceptions will be dealt with below, in RealProcessRequest.
148 queue_manager = new QueueManager ();
149 if (queue_manager.HasException)
150 initialException = queue_manager.InitialException;
152 trace_manager = new TraceManager ();
153 if (trace_manager.HasException)
154 initialException = trace_manager.InitialException;
156 cache = new Cache ();
157 internalCache = new Cache ();
158 internalCache.DependencyCache = internalCache;
160 do_RealProcessRequest = new WaitCallback (state => {
162 RealProcessRequest (state);
165 end_of_send_cb = new HttpWorkerRequest.EndOfSendNotification (EndOfSend);
168 #region AppDomain handling
169 internal static bool DomainUnloading {
170 get { return domainUnloading; }
173 [MonoDocumentationNote ("Currently returns path to the application root")]
174 public static string AspClientScriptPhysicalPath { get { return AppDomainAppPath; } }
176 [MonoDocumentationNote ("Currently returns path to the application root")]
177 public static string AspClientScriptVirtualPath { get { return AppDomainAppVirtualPath; } }
180 // http://radio.weblogs.com/0105476/stories/2002/07/12/executingAspxPagesWithoutAWebServer.html
182 public static string AppDomainAppId {
183 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.High)]
186 // This value should not change across invocations
188 string dirname = (string) AppDomain.CurrentDomain.GetData (".appId");
189 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
190 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
196 // Physical directory for the application
197 public static string AppDomainAppPath {
199 string dirname = (string) AppDomain.CurrentDomain.GetData (".appPath");
200 if (SecurityManager.SecurityEnabled) {
201 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
207 public static string AppDomainAppVirtualPath {
209 return (string) AppDomain.CurrentDomain.GetData (".appVPath");
213 public static string AppDomainId {
214 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.High)]
216 return (string) AppDomain.CurrentDomain.GetData (".domainId");
220 public static string AspInstallDirectory {
222 string dirname = (string) AppDomain.CurrentDomain.GetData (".hostingInstallDir");
223 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
224 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
231 static string _actual_bin_directory;
232 public static string BinDirectory {
234 if (_actual_bin_directory == null) {
235 string[] parts = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath.Split (';');
236 string mypath = AppDomainAppPath;
239 foreach (string p in parts) {
240 tmp = Path.Combine (mypath, p);
241 if (Directory.Exists (tmp)) {
242 _actual_bin_directory = tmp;
247 if (_actual_bin_directory == null)
248 _actual_bin_directory = Path.Combine (mypath, "bin");
250 if (_actual_bin_directory [_actual_bin_directory.Length - 1] != Path.DirectorySeparatorChar)
251 _actual_bin_directory += Path.DirectorySeparatorChar;
254 if (SecurityManager.SecurityEnabled)
255 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, _actual_bin_directory).Demand ();
257 return _actual_bin_directory;
261 public static Cache Cache {
267 internal static Cache InternalCache {
269 return internalCache;
273 public static string ClrInstallDirectory {
275 string dirname = Path.GetDirectoryName (typeof (Object).Assembly.Location);
276 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
277 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
283 public static string CodegenDir {
285 string dirname = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
286 if (SecurityManager.SecurityEnabled) {
287 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
293 public static bool IsOnUNCShare {
294 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Low)]
296 return RuntimeHelpers.IsUncShare;
300 public static string MachineConfigurationDirectory {
302 string dirname = Path.GetDirectoryName (ICalls.GetMachineConfigPath ());
303 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
304 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
310 internal static HttpRuntimeSection Section { get { return runtime_section; } }
312 public static bool UsingIntegratedPipeline { get { return false; } }
314 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
315 public static void Close ()
317 // Remove all items from cache.
320 internal static HttpWorkerRequest QueuePendingRequest (bool started_internally)
322 HttpWorkerRequest next = queue_manager.GetNextRequest (null);
326 if (!started_internally) {
327 next.StartedInternally = true;
328 ThreadPool.QueueUserWorkItem (do_RealProcessRequest, next);
335 static readonly string[] app_offline_files = {"app_offline.htm", "App_Offline.htm", "APP_OFFLINE.HTM"};
336 static string app_offline_file;
338 static bool AppIsOffline (HttpContext context)
340 if (!HttpApplicationFactory.ApplicationDisabled || app_offline_file == null)
343 HttpResponse response = context.Response;
345 response.ContentType = "text/html";
346 response.ExpiresAbsolute = DateTime.UtcNow;
347 response.StatusCode = 503;
348 response.TransmitFile (app_offline_file, true);
350 context.Request.ReleaseResources ();
351 context.Response.ReleaseResources ();
352 HttpContext.Current = null;
353 HttpApplication.requests_total_counter.Increment ();
358 static void AppOfflineFileRenamed (object sender, RenamedEventArgs args)
360 AppOfflineFileChanged (sender, args);
363 static void AppOfflineFileChanged (object sender, FileSystemEventArgs args)
365 lock (appOfflineLock) {
368 switch (args.ChangeType) {
369 case WatcherChangeTypes.Created:
370 case WatcherChangeTypes.Changed:
374 case WatcherChangeTypes.Deleted:
378 case WatcherChangeTypes.Renamed:
379 RenamedEventArgs rargs = args as RenamedEventArgs;
382 String.Compare (rargs.Name, "app_offline.htm", StringComparison.OrdinalIgnoreCase) == 0)
392 SetOfflineMode (offline, args.FullPath);
396 static void SetOfflineMode (bool offline, string filePath)
399 app_offline_file = null;
400 if (HttpApplicationFactory.ApplicationDisabled)
401 HttpRuntime.UnloadAppDomain ();
403 app_offline_file = filePath;
404 HttpApplicationFactory.DisableWatchers ();
405 HttpApplicationFactory.ApplicationDisabled = true;
406 InternalCache.InvokePrivateCallbacks ();
407 HttpApplicationFactory.Dispose ();
411 static void SetupOfflineWatch ()
413 lock (appOfflineLock) {
414 FileSystemEventHandler seh = new FileSystemEventHandler (AppOfflineFileChanged);
415 RenamedEventHandler reh = new RenamedEventHandler (AppOfflineFileRenamed);
417 string app_dir = AppDomainAppPath;
418 ArrayList watchers = new ArrayList ();
419 FileSystemWatcher watcher;
420 string offlineFile = null, tmp;
422 foreach (string f in app_offline_files) {
423 watcher = new FileSystemWatcher ();
424 watcher.Path = Path.GetDirectoryName (app_dir);
425 watcher.Filter = Path.GetFileName (f);
426 watcher.NotifyFilter |= NotifyFilters.Size;
427 watcher.Deleted += seh;
428 watcher.Changed += seh;
429 watcher.Created += seh;
430 watcher.Renamed += reh;
431 watcher.EnableRaisingEvents = true;
433 watchers.Add (watcher);
435 tmp = Path.Combine (app_dir, f);
436 if (File.Exists (tmp))
440 if (offlineFile != null)
441 SetOfflineMode (true, offlineFile);
446 static void RealProcessRequest (object o)
448 if (domainUnloading) {
449 Console.Error.WriteLine ("Domain is unloading, not processing the request.");
453 HttpWorkerRequest req = (HttpWorkerRequest) o;
454 bool started_internally = req.StartedInternally;
457 req = QueuePendingRequest (started_internally);
458 } while (started_internally && req != null);
461 static void Process (HttpWorkerRequest req)
464 HttpContext context = HttpContext.Current;
466 context = new HttpContext (req);
468 context.SetWorkerRequest (req);
470 HttpContext context = new HttpContext (req);
472 HttpContext.Current = context;
476 SetupOfflineWatch ();
478 if (initialException != null) {
479 FinishWithException (req, HttpException.NewWithCode ("Initial exception", initialException, WebEventCodes.RuntimeErrorRequestAbort));
484 if (AppIsOffline (context))
489 // Get application instance (create or reuse an instance of the correct class)
491 HttpApplication app = null;
494 app = HttpApplicationFactory.GetApplication (context);
495 } catch (Exception e) {
496 FinishWithException (req, HttpException.NewWithCode (String.Empty, e, WebEventCodes.RuntimeErrorRequestAbort));
502 context.Request.ReleaseResources ();
503 context.Response.ReleaseResources ();
504 HttpContext.Current = null;
506 context.ApplicationInstance = app;
507 req.SetEndOfSendNotification (end_of_send_cb, context);
510 // Ask application to service the request
514 IHttpAsyncHandler ihah = app;
515 if (context.Handler == null)
516 ihah.BeginProcessRequest (context, new AsyncCallback (request_processed), context);
519 //ihh.ProcessRequest (context);
520 IHttpExtendedHandler extHandler = context.Handler as IHttpExtendedHandler;
521 if (extHandler != null && !extHandler.IsCompleted)
523 if (context.Error is UnifyRequestException)
526 ihah.EndProcessRequest (null);
528 IHttpHandler ihh = app;
529 // IAsyncResult appiar = ihah.BeginProcessRequest (context, new AsyncCallback (request_processed), context);
530 // ihah.EndProcessRequest (appiar);
531 ihh.ProcessRequest (context);
534 HttpApplicationFactory.Recycle (app);
538 static void EndOfSend (HttpWorkerRequest ignored1, object ignored2)
543 // ProcessRequest method is executed in the AppDomain of the application
546 // ProcessRequest does not guarantee that `wr' will be processed synchronously,
547 // the request can be queued and processed later.
549 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
550 public static void ProcessRequest (HttpWorkerRequest wr)
553 throw new ArgumentNullException ("wr");
555 // Queue our request, fetch the next available one from the queue
557 HttpWorkerRequest request = queue_manager.GetNextRequest (wr);
561 QueuePendingRequest (false);
562 RealProcessRequest (request);
567 // Callback to be invoked by IHttpAsyncHandler.BeginProcessRequest
569 static void request_processed (IAsyncResult iar)
571 HttpContext context = (HttpContext) iar.AsyncState;
573 context.Request.ReleaseResources ();
574 context.Response.ReleaseResources ();
579 [MonoNotSupported ("UnloadAppDomain is not supported")]
580 public static void UnloadAppDomain ()
582 throw new NotImplementedException ("UnloadAppDomain is not supported");
586 // Called when we are shutting down or we need to reload an application
587 // that has been modified (touch global.asax)
589 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
590 public static void UnloadAppDomain ()
593 // TODO: call ReleaseResources
595 domainUnloading = true;
596 HttpApplicationFactory.DisableWatchers ();
597 ThreadPool.QueueUserWorkItem (delegate {
599 ShutdownAppDomain ();
600 } catch (Exception e){
601 Console.Error.WriteLine (e);
607 // Shuts down the AppDomain
609 static void ShutdownAppDomain ()
611 queue_manager.Dispose ();
612 // This will call Session_End if needed.
613 InternalCache.InvokePrivateCallbacks ();
614 // Kill our application.
615 HttpApplicationFactory.Dispose ();
616 ThreadPool.QueueUserWorkItem (delegate {
623 static void DoUnload ()
626 // No unload support for appdomains under Grasshopper
628 AppDomain.Unload (AppDomain.CurrentDomain);
632 static string content503 = "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n" +
633 "<html><head>\n<title>503 Server Unavailable</title>\n</head><body>\n" +
634 "<h1>Server Unavailable</h1>\n" +
637 static void FinishWithException (HttpWorkerRequest wr, HttpException e)
639 int code = e.GetHttpCode ();
640 wr.SendStatus (code, HttpWorkerRequest.GetStatusDescription (code));
641 wr.SendUnknownResponseHeader ("Connection", "close");
642 Encoding enc = Encoding.ASCII;
643 wr.SendUnknownResponseHeader ("Content-Type", "text/html; charset=" + enc.WebName);
644 string msg = e.GetHtmlErrorMessage ();
645 byte [] contentBytes = enc.GetBytes (msg);
646 wr.SendUnknownResponseHeader ("Content-Length", contentBytes.Length.ToString ());
647 wr.SendResponseFromMemory (contentBytes, contentBytes.Length);
648 wr.FlushResponse (true);
649 wr.CloseConnection ();
650 HttpApplication.requests_total_counter.Increment ();
654 // This is called from the QueueManager if a request
655 // can not be processed (load, no resources, or
656 // appdomain unload).
658 static internal void FinishUnavailable (HttpWorkerRequest wr)
660 wr.SendStatus (503, "Service unavailable");
661 wr.SendUnknownResponseHeader ("Connection", "close");
662 Encoding enc = Encoding.ASCII;
663 wr.SendUnknownResponseHeader ("Content-Type", "text/html; charset=" + enc.WebName);
664 byte [] contentBytes = enc.GetBytes (content503);
665 wr.SendUnknownResponseHeader ("Content-Length", contentBytes.Length.ToString ());
666 wr.SendResponseFromMemory (contentBytes, contentBytes.Length);
667 wr.FlushResponse (true);
668 wr.CloseConnection ();
669 HttpApplication.requests_total_counter.Increment ();
672 [AspNetHostingPermissionAttribute(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Unrestricted)]
673 [MonoDocumentationNote ("Always returns null on Mono")]
674 public static NamedPermissionSet GetNamedPermissionSet ()
680 static internal void WritePreservationFile (Assembly asm, string genericNameBase)
683 throw new ArgumentNullException ("asm");
684 if (String.IsNullOrEmpty (genericNameBase))
685 throw new ArgumentNullException ("genericNameBase");
687 string compiled = Path.Combine (AppDomain.CurrentDomain.SetupInformation.DynamicBase,
688 genericNameBase + ".compiled");
689 PreservationFile pf = new PreservationFile ();
691 pf.VirtualPath = String.Concat ("/", genericNameBase, "/");
693 AssemblyName an = asm.GetName ();
694 pf.Assembly = an.Name;
695 pf.ResultType = BuildResultTypeCode.TopLevelAssembly;
697 } catch (Exception ex) {
698 throw new HttpException (
699 String.Format ("Failed to write preservation file {0}", genericNameBase + ".compiled"),
704 static Assembly ResolveAssemblyHandler(object sender, ResolveEventArgs e)
706 AssemblyName an = new AssemblyName (e.Name);
707 string dynamic_base = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
708 string compiled = Path.Combine (dynamic_base, an.Name + ".compiled");
710 if (!File.Exists (compiled))
715 pf = new PreservationFile (compiled);
716 } catch (Exception ex) {
717 throw new HttpException (
718 String.Format ("Failed to read preservation file {0}", an.Name + ".compiled"),
724 string asmPath = Path.Combine (dynamic_base, pf.Assembly + ".dll");
725 ret = Assembly.LoadFrom (asmPath);
726 } catch (Exception) {
733 internal static void EnableAssemblyMapping (bool enable)
735 lock (assemblyMappingLock) {
736 if (assemblyMappingEnabled == enable)
739 AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler (ResolveAssemblyHandler);
741 AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler (ResolveAssemblyHandler);
742 assemblyMappingEnabled = enable;
745 #endif // #if !TARGET_J2EE
747 internal static TraceManager TraceManager {
749 return trace_manager;