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.Collections.Concurrent;
39 using System.Reflection;
40 using System.Security;
41 using System.Security.Permissions;
42 using System.Web.Caching;
43 using System.Web.Configuration;
44 using System.Web.Management;
46 using System.Web.Util;
48 using System.Threading;
49 using System.CodeDom.Compiler;
50 using System.Web.Compilation;
54 // CAS - no InheritanceDemand here as the class is sealed
55 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
56 public sealed class HttpRuntime
58 static bool domainUnloading;
59 static SplitOrderedList <string, string> registeredAssemblies;
60 static QueueManager queue_manager;
61 static TraceManager trace_manager;
63 static Cache internalCache;
64 static WaitCallback do_RealProcessRequest;
65 static HttpWorkerRequest.EndOfSendNotification end_of_send_cb;
66 static Exception initialException;
68 static bool assemblyMappingEnabled;
69 static object assemblyMappingLock = new object ();
70 static object appOfflineLock = new object ();
71 static HttpRuntimeSection runtime_section;
83 WebConfigurationManager.Init ();
84 SettingsMappingManager.Init ();
85 runtime_section = (HttpRuntimeSection) WebConfigurationManager.GetSection ("system.web/httpRuntime");
86 } catch (Exception ex) {
87 initialException = ex;
90 // The classes in whose constructors exceptions may be thrown, should be handled the same way QueueManager
91 // and TraceManager are below. The constructors themselves MUST NOT throw any exceptions - we MUST be sure
92 // the objects are created here. The exceptions will be dealt with below, in RealProcessRequest.
93 queue_manager = new QueueManager ();
94 if (queue_manager.HasException) {
95 if (initialException == null)
96 initialException = queue_manager.InitialException;
98 Console.Error.WriteLine ("Exception during QueueManager initialization:");
99 Console.Error.WriteLine (queue_manager.InitialException);
103 trace_manager = new TraceManager ();
104 if (trace_manager.HasException) {
105 if (initialException == null)
106 initialException = trace_manager.InitialException;
108 Console.Error.WriteLine ("Exception during TraceManager initialization:");
109 Console.Error.WriteLine (trace_manager.InitialException);
113 registeredAssemblies = new SplitOrderedList <string, string> (StringComparer.Ordinal);
114 cache = new Cache ();
115 internalCache = new Cache ();
116 internalCache.DependencyCache = internalCache;
117 do_RealProcessRequest = new WaitCallback (state => {
119 RealProcessRequest (state);
122 end_of_send_cb = new HttpWorkerRequest.EndOfSendNotification (EndOfSend);
125 internal static SplitOrderedList <string, string> RegisteredAssemblies {
126 get { return registeredAssemblies; }
129 #region AppDomain handling
130 internal static bool DomainUnloading {
131 get { return domainUnloading; }
134 [MonoDocumentationNote ("Currently returns path to the application root")]
135 public static string AspClientScriptPhysicalPath { get { return AppDomainAppPath; } }
137 [MonoDocumentationNote ("Currently returns path to the application root")]
138 public static string AspClientScriptVirtualPath { get { return AppDomainAppVirtualPath; } }
141 // http://radio.weblogs.com/0105476/stories/2002/07/12/executingAspxPagesWithoutAWebServer.html
143 public static string AppDomainAppId {
144 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.High)]
147 // This value should not change across invocations
149 string dirname = (string) AppDomain.CurrentDomain.GetData (".appId");
150 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
151 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
157 // Physical directory for the application
158 public static string AppDomainAppPath {
160 string dirname = (string) AppDomain.CurrentDomain.GetData (".appPath");
161 if (SecurityManager.SecurityEnabled) {
162 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
168 public static string AppDomainAppVirtualPath {
170 return (string) AppDomain.CurrentDomain.GetData (".appVPath");
174 public static string AppDomainId {
175 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.High)]
177 return (string) AppDomain.CurrentDomain.GetData (".domainId");
181 public static string AspInstallDirectory {
183 string dirname = (string) AppDomain.CurrentDomain.GetData (".hostingInstallDir");
184 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
185 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
192 static string _actual_bin_directory;
193 public static string BinDirectory {
195 if (_actual_bin_directory == null) {
196 string[] parts = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath.Split (';');
197 string mypath = AppDomainAppPath;
200 foreach (string p in parts) {
201 tmp = Path.Combine (mypath, p);
202 if (Directory.Exists (tmp)) {
203 _actual_bin_directory = tmp;
208 if (_actual_bin_directory == null)
209 _actual_bin_directory = Path.Combine (mypath, "bin");
211 if (_actual_bin_directory [_actual_bin_directory.Length - 1] != Path.DirectorySeparatorChar)
212 _actual_bin_directory += Path.DirectorySeparatorChar;
215 if (SecurityManager.SecurityEnabled)
216 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, _actual_bin_directory).Demand ();
218 return _actual_bin_directory;
222 public static Cache Cache {
228 internal static Cache InternalCache {
230 return internalCache;
234 public static string ClrInstallDirectory {
236 string dirname = Path.GetDirectoryName (typeof (Object).Assembly.Location);
237 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
238 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
244 public static string CodegenDir {
246 string dirname = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
247 if (SecurityManager.SecurityEnabled) {
248 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
254 public static bool IsOnUNCShare {
255 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Low)]
257 return RuntimeHelpers.IsUncShare;
261 public static string MachineConfigurationDirectory {
263 string dirname = Path.GetDirectoryName (ICalls.GetMachineConfigPath ());
264 if ((dirname != null) && (dirname.Length > 0) && SecurityManager.SecurityEnabled) {
265 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dirname).Demand ();
271 internal static HttpRuntimeSection Section { get { return runtime_section; } }
273 public static bool UsingIntegratedPipeline { get { return false; } }
275 public static Version IISVersion {
277 // Null means not hosted by IIS
282 public static Version TargetFramework {
284 return runtime_section.TargetFramework;
288 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
289 public static void Close ()
291 // Remove all items from cache.
294 internal static HttpWorkerRequest QueuePendingRequest (bool started_internally)
296 HttpWorkerRequest next = queue_manager.GetNextRequest (null);
300 if (!started_internally) {
301 next.StartedInternally = true;
302 ThreadPool.QueueUserWorkItem (do_RealProcessRequest, next);
308 static readonly string[] app_offline_files = {"app_offline.htm", "App_Offline.htm", "APP_OFFLINE.HTM"};
309 static string app_offline_file;
311 static bool AppIsOffline (HttpContext context)
313 if (!HttpApplicationFactory.ApplicationDisabled || app_offline_file == null)
316 HttpResponse response = context.Response;
318 response.ContentType = "text/html";
319 response.ExpiresAbsolute = DateTime.UtcNow;
320 response.StatusCode = 503;
321 response.TransmitFile (app_offline_file, true);
323 context.Request.ReleaseResources ();
324 context.Response.ReleaseResources ();
325 HttpContext.Current = null;
326 HttpApplication.requests_total_counter.Increment ();
331 static void AppOfflineFileRenamed (object sender, RenamedEventArgs args)
333 AppOfflineFileChanged (sender, args);
336 static void AppOfflineFileChanged (object sender, FileSystemEventArgs args)
338 lock (appOfflineLock) {
341 switch (args.ChangeType) {
342 case WatcherChangeTypes.Created:
343 case WatcherChangeTypes.Changed:
347 case WatcherChangeTypes.Deleted:
351 case WatcherChangeTypes.Renamed:
352 RenamedEventArgs rargs = args as RenamedEventArgs;
355 String.Compare (rargs.Name, "app_offline.htm", StringComparison.OrdinalIgnoreCase) == 0)
365 SetOfflineMode (offline, args.FullPath);
369 static void SetOfflineMode (bool offline, string filePath)
372 app_offline_file = null;
373 if (HttpApplicationFactory.ApplicationDisabled)
374 HttpRuntime.UnloadAppDomain ();
376 app_offline_file = filePath;
377 HttpApplicationFactory.DisableWatchers ();
378 HttpApplicationFactory.ApplicationDisabled = true;
379 InternalCache.InvokePrivateCallbacks ();
380 HttpApplicationFactory.Dispose ();
384 static void SetupOfflineWatch ()
386 lock (appOfflineLock) {
387 FileSystemEventHandler seh = new FileSystemEventHandler (AppOfflineFileChanged);
388 RenamedEventHandler reh = new RenamedEventHandler (AppOfflineFileRenamed);
390 string app_dir = AppDomainAppPath;
391 FileSystemWatcher watcher;
392 string offlineFile = null, tmp;
394 foreach (string f in app_offline_files) {
395 watcher = new FileSystemWatcher ();
396 watcher.Path = Path.GetDirectoryName (app_dir);
397 watcher.Filter = Path.GetFileName (f);
398 watcher.NotifyFilter |= NotifyFilters.Size;
399 watcher.Deleted += seh;
400 watcher.Changed += seh;
401 watcher.Created += seh;
402 watcher.Renamed += reh;
403 watcher.EnableRaisingEvents = true;
405 tmp = Path.Combine (app_dir, f);
406 if (File.Exists (tmp))
410 if (offlineFile != null)
411 SetOfflineMode (true, offlineFile);
415 static void RealProcessRequest (object o)
417 if (domainUnloading) {
418 Console.Error.WriteLine ("Domain is unloading, not processing the request.");
422 HttpWorkerRequest req = (HttpWorkerRequest) o;
423 bool started_internally = req.StartedInternally;
426 req = QueuePendingRequest (started_internally);
427 } while (started_internally && req != null);
430 static void Process (HttpWorkerRequest req)
435 if (initialException != null) {
436 FinishWithException (req, HttpException.NewWithCode ("Initial exception", initialException, WebEventCodes.RuntimeErrorRequestAbort));
439 SetupOfflineWatch ();
441 HttpContext context = new HttpContext (req);
442 HttpContext.Current = context;
443 if (AppIsOffline (context))
447 // Get application instance (create or reuse an instance of the correct class)
449 HttpApplication app = null;
452 app = HttpApplicationFactory.GetApplication (context);
453 } catch (Exception e) {
454 FinishWithException (req, HttpException.NewWithCode (String.Empty, e, WebEventCodes.RuntimeErrorRequestAbort));
460 context.Request.ReleaseResources ();
461 context.Response.ReleaseResources ();
462 HttpContext.Current = null;
464 context.ApplicationInstance = app;
465 req.SetEndOfSendNotification (end_of_send_cb, context);
468 // Ask application to service the request
471 IHttpHandler ihh = app;
472 // IAsyncResult appiar = ihah.BeginProcessRequest (context, new AsyncCallback (request_processed), context);
473 // ihah.EndProcessRequest (appiar);
474 ihh.ProcessRequest (context);
476 HttpApplicationFactory.Recycle (app);
480 static void EndOfSend (HttpWorkerRequest ignored1, object ignored2)
485 // ProcessRequest method is executed in the AppDomain of the application
488 // ProcessRequest does not guarantee that `wr' will be processed synchronously,
489 // the request can be queued and processed later.
491 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
492 public static void ProcessRequest (HttpWorkerRequest wr)
495 throw new ArgumentNullException ("wr");
497 // Queue our request, fetch the next available one from the queue
499 HttpWorkerRequest request = queue_manager.GetNextRequest (wr);
503 QueuePendingRequest (false);
504 RealProcessRequest (request);
509 // Called when we are shutting down or we need to reload an application
510 // that has been modified (touch global.asax)
512 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
513 public static void UnloadAppDomain ()
516 // TODO: call ReleaseResources
518 domainUnloading = true;
519 HttpApplicationFactory.DisableWatchers ();
520 ThreadPool.QueueUserWorkItem (delegate {
522 ShutdownAppDomain ();
523 } catch (Exception e){
524 Console.Error.WriteLine (e);
529 // Shuts down the AppDomain
531 static void ShutdownAppDomain ()
533 queue_manager.Dispose ();
534 // This will call Session_End if needed.
535 InternalCache.InvokePrivateCallbacks ();
536 // Kill our application.
537 HttpApplicationFactory.Dispose ();
538 ThreadPool.QueueUserWorkItem (delegate {
545 static void DoUnload ()
547 AppDomain.Unload (AppDomain.CurrentDomain);
550 static string content503 = "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n" +
551 "<html><head>\n<title>503 Server Unavailable</title>\n</head><body>\n" +
552 "<h1>Server Unavailable</h1>\n" +
555 static void FinishWithException (HttpWorkerRequest wr, HttpException e)
557 int code = e.GetHttpCode ();
558 wr.SendStatus (code, HttpWorkerRequest.GetStatusDescription (code));
559 wr.SendUnknownResponseHeader ("Connection", "close");
560 Encoding enc = Encoding.ASCII;
561 wr.SendUnknownResponseHeader ("Content-Type", "text/html; charset=" + enc.WebName);
562 string msg = e.GetHtmlErrorMessage ();
563 byte [] contentBytes = enc.GetBytes (msg);
564 wr.SendUnknownResponseHeader ("Content-Length", contentBytes.Length.ToString ());
565 wr.SendResponseFromMemory (contentBytes, contentBytes.Length);
566 wr.FlushResponse (true);
567 wr.CloseConnection ();
568 HttpApplication.requests_total_counter.Increment ();
572 // This is called from the QueueManager if a request
573 // can not be processed (load, no resources, or
574 // appdomain unload).
576 static internal void FinishUnavailable (HttpWorkerRequest wr)
578 wr.SendStatus (503, "Service unavailable");
579 wr.SendUnknownResponseHeader ("Connection", "close");
580 Encoding enc = Encoding.ASCII;
581 wr.SendUnknownResponseHeader ("Content-Type", "text/html; charset=" + enc.WebName);
582 byte [] contentBytes = enc.GetBytes (content503);
583 wr.SendUnknownResponseHeader ("Content-Length", contentBytes.Length.ToString ());
584 wr.SendResponseFromMemory (contentBytes, contentBytes.Length);
585 wr.FlushResponse (true);
586 wr.CloseConnection ();
587 HttpApplication.requests_total_counter.Increment ();
590 [AspNetHostingPermissionAttribute(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Unrestricted)]
591 [MonoDocumentationNote ("Always returns null on Mono")]
592 public static NamedPermissionSet GetNamedPermissionSet ()
597 static internal void WritePreservationFile (Assembly asm, string genericNameBase)
600 throw new ArgumentNullException ("asm");
601 if (String.IsNullOrEmpty (genericNameBase))
602 throw new ArgumentNullException ("genericNameBase");
604 string compiled = Path.Combine (AppDomain.CurrentDomain.SetupInformation.DynamicBase,
605 genericNameBase + ".compiled");
606 PreservationFile pf = new PreservationFile ();
608 pf.VirtualPath = String.Concat ("/", genericNameBase, "/");
610 AssemblyName an = asm.GetName ();
611 pf.Assembly = an.Name;
612 pf.ResultType = BuildResultTypeCode.TopLevelAssembly;
614 } catch (Exception ex) {
615 throw new HttpException (
616 String.Format ("Failed to write preservation file {0}", genericNameBase + ".compiled"),
621 static Assembly ResolveAssemblyHandler(object sender, ResolveEventArgs e)
623 AssemblyName an = new AssemblyName (e.Name);
624 string dynamic_base = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
625 string compiled = Path.Combine (dynamic_base, an.Name + ".compiled");
628 if (!File.Exists (compiled)) {
629 string fn = an.FullName;
630 if (!RegisteredAssemblies.Find ((uint)fn.GetHashCode (), fn, out asmPath))
635 pf = new PreservationFile (compiled);
636 } catch (Exception ex) {
637 throw new HttpException (
638 String.Format ("Failed to read preservation file {0}", an.Name + ".compiled"),
641 asmPath = Path.Combine (dynamic_base, pf.Assembly + ".dll");
644 if (String.IsNullOrEmpty (asmPath))
649 ret = Assembly.LoadFrom (asmPath);
650 } catch (Exception) {
657 internal static void EnableAssemblyMapping (bool enable)
659 lock (assemblyMappingLock) {
660 if (assemblyMappingEnabled == enable)
663 AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler (ResolveAssemblyHandler);
665 AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler (ResolveAssemblyHandler);
666 assemblyMappingEnabled = enable;
670 internal static TraceManager TraceManager {
672 return trace_manager;