2005-05-09 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / class / System.Web / System.Web / HttpRuntime.cs
1 // 
2 // System.Web.HttpRuntime
3 //
4 // Authors:
5 //      Patrik Torstensson (ptorsten@hotmail.com)
6 //      Gaurav Vaish (gvaish@iitk.ac.in)
7 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 //
9
10 //
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:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
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.
29 //
30 using System;
31 using System.Collections;
32 using System.IO;
33 using System.Text;
34 using System.Security;
35 using System.Security.Permissions;
36 using System.Threading;
37 using System.Web.Configuration;
38 using System.Web.UI;
39 using System.Web.Util;
40 using System.Web.Caching;
41
42 namespace System.Web {
43
44         public sealed class HttpRuntime {
45
46                 // Security permission helper objects
47                 private static IStackWalk appPathDiscoveryStackWalk;
48                 private static IStackWalk ctrlPrincipalStackWalk;
49                 private static IStackWalk sensitiveInfoStackWalk;
50                 private static IStackWalk unmgdCodeStackWalk;
51                 private static IStackWalk unrestrictedStackWalk;
52                 private static IStackWalk reflectionStackWalk;
53
54                 private static HttpRuntime _runtime;
55                 private static string appDomainAppId;
56                 private static string appDomainId;
57                 private static string appDomainAppPath;
58                 private static string appDomainAppVirtualPath;
59                 private Cache _cache;
60
61                 private int _activeRequests;
62                 private HttpWorkerRequest.EndOfSendNotification _endOfSendCallback;
63                 private AsyncCallback _handlerCallback;
64                 private WaitCallback unloadDomainCallback;
65
66                 private bool _firstRequestStarted;
67                 //private bool _firstRequestExecuted;
68
69                 private Exception _initError;
70                 private TimeoutManager timeoutManager;
71                 private QueueManager queueManager;
72                 private TraceManager traceManager;
73                 private WaitCallback doRequestCallback;
74                 private int pendingCallbacks;
75
76                 static HttpRuntime ()
77                 {
78                         _runtime = new HttpRuntime ();
79                         _runtime.Init();
80                 }
81
82                 public HttpRuntime ()
83                 {
84                         doRequestCallback = new WaitCallback (DoRequest);
85                 }
86
87                 static internal object CreateInternalObject(Type type) {
88                         return Activator.CreateInstance(type, true);
89                 }
90
91                 private void Init ()
92                 {
93                         try {
94                                 _cache = new Cache ();
95                                 timeoutManager = new TimeoutManager ();
96
97                                 _endOfSendCallback = new HttpWorkerRequest.EndOfSendNotification (OnEndOfSend);
98                                 _handlerCallback = new AsyncCallback (OnHandlerReady);
99                                 unloadDomainCallback = new WaitCallback (DoUnload);
100                                 AppDomain.CurrentDomain.DomainUnload += new EventHandler (OnDomainUnload);
101                         } 
102                         catch (Exception error) {
103                                 _initError = error;
104                         }
105                 }
106
107                 private void OnFirstRequestStart(HttpContext context) {
108                         if (_initError != null)
109                                 throw _initError;
110
111                         try {
112                                 WebConfigurationSettings.Init (context);
113                                 traceManager = new TraceManager ();
114                                 queueManager = new QueueManager ();
115                         } catch (Exception e) {
116                                 _initError = e;
117                         }
118
119                         // If we got an error during init, throw to client now..
120                         if (null != _initError)
121                                 throw _initError;
122                 }
123
124                 /* Not used
125                 private void OnFirstRequestEnd() {
126                 }
127                 */
128
129                 private void OnHandlerReady(IAsyncResult ar) {
130                         HttpContext context = (HttpContext) ar.AsyncState;
131                         try {
132                                 IHttpAsyncHandler handler = context.AsyncHandler;
133
134                                 try {
135                                         handler.EndProcessRequest(ar);
136                                 }
137                                 catch (Exception error) {
138                                         context.AddError(error);
139                                 }
140                         }
141                         finally {
142                                 context.AsyncHandler = null;
143                         }
144
145                         FinishRequest(context, context.Error);
146                 }
147
148                 private void OnEndOfSend(HttpWorkerRequest request, object data) {
149                         HttpContext context = (HttpContext) data;
150
151                         context.Request.Dispose();
152                         context.Response.Dispose();
153                 }
154
155                 internal void FinishRequest(HttpContext context, Exception error) {
156                         if (error == null) {
157                                 try {
158                                         context.Response.FlushAtEndOfRequest();
159                                 } catch (Exception obj) {
160                                         error = obj;
161                                 }
162                         }
163
164                         HttpWorkerRequest request = context.WorkerRequest;
165                         if (null != error) {
166                                 WebTrace.WriteLine (error.ToString ());
167
168                                 // We don't want the 'headers sent error'
169                                 context.Response.SetHeadersSent (false);
170                                 context.Response.Clear ();
171                                 context.Response.ClearHeaders ();
172
173                                 if (!(error is HttpException)) {
174                                         error = new HttpException (String.Empty, error);
175                                         context.Response.StatusCode = 500;
176                                 } else {
177                                         context.Response.StatusCode = ((HttpException) error).GetHttpCode ();
178                                 }
179
180                                 if (!RedirectCustomError (context))
181                                         context.Response.Write (((HttpException) error).GetHtmlErrorMessage ());
182
183                                 context.Response.FinalFlush ();
184                         }
185
186                         /*
187                          * This is not being used. OnFirstRequestEnd is empty.
188                         if (!_firstRequestExecuted) {
189                                 lock (this) {
190                                         if (!_firstRequestExecuted) {
191                                                 _firstRequestExecuted = true;
192                                                 OnFirstRequestEnd();
193                                         }
194                                 }
195                         }
196                         */
197
198                         Interlocked.Decrement(ref _activeRequests);
199
200                         if (null != request)
201                                 request.EndOfRequest();
202
203                         TryExecuteQueuedRequests ();
204                 }
205
206                 bool RedirectCustomError (HttpContext context)
207                 {
208                         if (!context.IsCustomErrorEnabled)
209                                 return false;
210
211                         CustomErrorsConfig config = null;
212                         try {
213                                 config = (CustomErrorsConfig) context.GetConfig ("system.web/customErrors");
214                         } catch { }
215
216                         if (config == null) {
217                                 if (context.ErrorPage != null)
218                                         return context.Response.RedirectCustomError (context.ErrorPage);
219
220                                 return false;
221                         }
222
223                         string redirect =  config [context.Response.StatusCode];
224                         if (redirect == null) {
225                                 redirect = context.ErrorPage;
226                                 if (redirect == null)
227                                         redirect = config.DefaultRedirect;
228                         }
229
230                         if (redirect == null)
231                                 return false;
232
233                         return context.Response.RedirectCustomError (redirect);
234                 }
235
236                 internal static void FinishUnavailable (HttpWorkerRequest wr)
237                 {
238                         HttpContext context = new HttpContext (wr);
239                         HttpException exception = new HttpException (503, "Service unavailable");
240                         Interlocked.Increment (ref _runtime._activeRequests);
241                         context.Response.InitializeWriter ();
242                         _runtime.FinishRequest (context, exception);
243                 }
244
245                 void DoUnload (object state)
246                 {
247                         try { 
248                                 AppDomain.Unload (AppDomain.CurrentDomain);
249                         } catch {}
250                 }
251
252                 internal void Dispose() {
253                         WaitForRequests (2000);
254                         queueManager.Dispose (); // Send a 503 to all queued requests
255                         queueManager = null;
256                         
257                         _cache = null;
258                         HttpApplicationFactory.EndApplication ();
259                 }
260
261                 void OnDomainUnload (object o, EventArgs args)
262                 {
263                         HttpApplicationFactory.EndApplication ();
264                 }
265
266                 internal void ByeByeDomain ()
267                 {
268                         HttpApplicationFactory.EndApplication ();
269                         ThreadPool.QueueUserWorkItem (unloadDomainCallback);
270                 }
271
272                 internal void WaitForRequests(int ms) {
273                         DateTime timeout = DateTime.Now.AddMilliseconds(ms);
274
275                         do {
276                                 if (Interlocked.CompareExchange (ref _activeRequests, 0, 0) == 0)
277                                         return;
278
279                                 Thread.Sleep (100);
280                         } while (timeout > DateTime.Now);
281                 }
282
283                 internal void InternalExecuteRequest (HttpWorkerRequest request)
284                 {
285                         IHttpHandler handler;
286                         IHttpAsyncHandler async_handler;
287
288                         HttpContext context = new HttpContext(request);
289
290                         request.SetEndOfSendNotification(_endOfSendCallback, context);
291                         
292                         Interlocked.Increment(ref _activeRequests);
293
294                         try {
295                                 if (!_firstRequestStarted) {
296                                         lock (this) {
297                                                 if (!_firstRequestStarted) {
298                                                         OnFirstRequestStart(context);
299                                                         _firstRequestStarted = true;
300                                                 }
301                                         }
302                                 }
303
304                                 // This *must* be done after the configuration is initialized.
305                                 context.Response.InitializeWriter ();
306                                 handler = HttpApplicationFactory.GetInstance(context);
307                                 if (null == handler)
308                                         throw new HttpException(FormatResourceString("unable_to_create_app"));
309
310                                 if (handler is IHttpAsyncHandler) {
311                                         async_handler = (IHttpAsyncHandler) handler;
312
313                                         context.AsyncHandler = async_handler;
314                                         async_handler.BeginProcessRequest(context, _handlerCallback, context);
315                                 } else {
316                                         handler.ProcessRequest(context);
317                                         FinishRequest(context, null);
318                                 }
319                         }
320                         catch (Exception error) {
321                                 context.Response.InitializeWriter ();
322                                 FinishRequest(context, error);
323                         }
324                 }
325
326                 void DoRequest (object o)
327                 {
328                         Interlocked.Decrement (ref pendingCallbacks);
329                         InternalExecuteRequest ((HttpWorkerRequest) o);
330                 }
331                 
332                 void TryExecuteQueuedRequests ()
333                 {
334                         // Wait for pending jobs to start
335                         if (Interlocked.CompareExchange (ref pendingCallbacks, 3, 3) >= 3)
336                                 return;
337
338                         HttpWorkerRequest wr = queueManager.GetNextRequest (null);
339                         if (wr == null)
340                                 return;
341
342                         Interlocked.Increment (ref pendingCallbacks);
343                         ThreadPool.QueueUserWorkItem (doRequestCallback, wr);
344                         TryExecuteQueuedRequests ();
345                 }
346
347                 public static void ProcessRequest (HttpWorkerRequest request)
348                 {
349                         if (request == null)
350                                 throw new ArgumentNullException ("request");
351
352                         QueueManager mgr = _runtime.queueManager;
353                         if (_runtime._firstRequestStarted && mgr != null) {
354                                 request = mgr.GetNextRequest (request);
355                                 // We're busy, return immediately
356                                 if (request == null)
357                                         return;
358                         }
359
360                         _runtime.InternalExecuteRequest (request);
361                 }
362
363 #if NET_1_1
364                 public static void UnloadAppDomain ()
365                 {
366                         _runtime.ByeByeDomain ();
367                 }
368 #endif
369                 public static Cache Cache {
370                         get {
371                                 return _runtime._cache;
372                         }
373                 }      
374
375                 public static string AppDomainAppId {
376                         get {
377                                 if (appDomainAppId == null)
378                                         appDomainAppId = (string) AppDomain.CurrentDomain.GetData (".appId");
379
380                                 return appDomainAppId;
381                         }
382                 }
383
384                 public static string AppDomainAppPath {
385                         get {
386                                 if (appDomainAppPath == null)
387                                         appDomainAppPath = (string) AppDomain.CurrentDomain.GetData (".appPath");
388
389                                 return appDomainAppPath;
390                         }
391                 }
392
393                 public static string AppDomainAppVirtualPath {
394                         get {
395                                 if (appDomainAppVirtualPath == null)
396                                         appDomainAppVirtualPath = (string) AppDomain.CurrentDomain.GetData (".appVPath");
397
398                                 return appDomainAppVirtualPath;
399                         }
400                 }
401
402                 public static string AppDomainId {
403                         get {
404                                 if (appDomainId == null)
405                                         appDomainId = (string) AppDomain.CurrentDomain.GetData (".domainId");
406
407                                 return appDomainId;
408                         }
409                 }
410
411                 public static string AspInstallDirectory {
412                         get {
413                                 return ICalls.GetMachineInstallDirectory ();
414                         }
415                 }
416
417                 public static string BinDirectory {
418                         get {
419                                 return Path.Combine (AppDomainAppPath, "bin");
420                         }
421                 }
422
423                 public static string ClrInstallDirectory {
424                         get {
425                                 return ICalls.GetMachineInstallDirectory ();
426                         }
427                 }
428
429                 public static string CodegenDir {
430                         get {
431                                 return AppDomain.CurrentDomain.SetupInformation.DynamicBase;
432                         }
433                 }
434
435                 public static bool IsOnUNCShare {
436                         get {
437                                 // IsUnc broken under unix?
438 #if NET_2_0
439                                 return (Environment.OSVersion.Platform != PlatformID.Unix &&
440 #else
441                                 return (!((int) Environment.OSVersion.Platform == 128) &&
442 #endif
443                                         new Uri ("file://" + ClrInstallDirectory).IsUnc);
444                         }
445                 }
446
447                 public static string MachineConfigurationDirectory {
448                         get {
449                                 return Path.GetDirectoryName (WebConfigurationSettings.MachineConfigPath);
450                         }
451                 }
452
453                 internal static TimeoutManager TimeoutManager {
454                         get {
455                                 return HttpRuntime._runtime.timeoutManager;
456                         }
457                 }
458
459                 internal static TraceManager TraceManager {
460                         get {
461                                 return HttpRuntime._runtime.traceManager;
462                         }
463                 }
464
465                 public static void Close ()
466                 {
467                         _runtime.Dispose ();
468                 }
469
470                 internal static string FormatResourceString (string key)
471                 {
472                         return GetResourceString (key);
473                 }
474
475                 internal static string FormatResourceString (string key, string arg0)
476                 {
477                         /*string format = GetResourceString (key);
478
479                         if (format == null)
480                                 return null;
481                         
482                         return String.Format (format, arg0);
483                         */
484                         return String.Format ("{0}: {1}", key, arg0);
485                 }
486
487                 [MonoTODO ("FormatResourceString (string, string, string)")]
488                 internal static string FormatResourceString (string key, string arg0, string type) {
489                         return String.Format ("{0}: {1} {2}", key, arg0, type);
490                 }
491
492                 [MonoTODO ("FormatResourceString (string, string, string, string)")]
493                 internal static string FormatResourceString (string key, string arg0,
494                                                              string arg1, string arg2)
495                 {
496                         return String.Format ("{0}: {1} {2} {3}", key, arg0, arg1, arg2);
497                 }
498
499                 [MonoTODO ("FormatResourceString (string, string[]")]
500                 internal static string FormatResourceString (string key, string[] args)
501                 {
502                         //StringBuilder sb = new StringBuilder ();
503                         /*sb.AppendFormat ("{0}: ", key);
504                         foreach (string s in args)
505                                 sb.AppendFormat ("{0} ", s);
506
507                         if (sb.Length > 0)
508                                 sb.Length--;
509                         return sb.ToString ();*/
510                         string s = key + ": ";
511                         if (args != null)
512                                 foreach (string k in args)
513                                         s += k + " ";
514                         return s;
515                 }
516
517                 private static string GetResourceString (string key) {
518                         return _runtime.GetResourceStringFromResourceManager (key);
519                 }
520
521                 [MonoTODO ("GetResourceStringFromResourceManager (string)")]
522                 private string GetResourceStringFromResourceManager (string key) {
523                         return key;
524                 }
525
526                 #region Security Internal Methods (not impl)
527                 [MonoTODO ("Get Application path from the appdomain object")]
528                 internal static IStackWalk AppPathDiscovery {
529                         get {
530                                 if (appPathDiscoveryStackWalk == null) {
531                                         appPathDiscoveryStackWalk = new FileIOPermission (
532                                                 FileIOPermissionAccess.PathDiscovery, "<apppath>");
533                                 }
534                                 return appPathDiscoveryStackWalk;
535                         }
536                 }
537
538                 internal static IStackWalk ControlPrincipal {
539                         get {
540                                 if (ctrlPrincipalStackWalk == null) {
541                                         ctrlPrincipalStackWalk = new SecurityPermission (
542                                                 SecurityPermissionFlag.ControlPrincipal);
543                                 }
544                                 return ctrlPrincipalStackWalk;
545                         }
546                 }
547
548                 internal static IStackWalk Reflection {
549                         get {
550                                 if (reflectionStackWalk == null) {
551                                         reflectionStackWalk = new ReflectionPermission (
552                                                 ReflectionPermissionFlag.TypeInformation |
553                                                 ReflectionPermissionFlag.MemberAccess);
554                                 }
555                                 return reflectionStackWalk;
556                         }
557                 }
558
559                 internal static IStackWalk SensitiveInformation {
560                         get {
561                                 if (sensitiveInfoStackWalk == null) {
562                                         sensitiveInfoStackWalk = new EnvironmentPermission (
563                                                 PermissionState.Unrestricted);
564                                 }
565                                 return sensitiveInfoStackWalk;
566                         }
567                 }
568
569                 internal static IStackWalk UnmanagedCode {
570                         get {
571                                 if (unmgdCodeStackWalk == null) {
572                                         unmgdCodeStackWalk = new SecurityPermission (
573                                                 SecurityPermissionFlag.UnmanagedCode);
574                                 }
575                                 return unmgdCodeStackWalk;
576                         }
577                 }
578
579                 internal static IStackWalk Unrestricted {
580                         get {
581                                 if (unrestrictedStackWalk == null) {
582                                         unrestrictedStackWalk = new PermissionSet (
583                                                 PermissionState.Unrestricted);
584                                 }
585                                 return unrestrictedStackWalk;
586                         }
587                 }
588
589                 internal static IStackWalk FileReadAccess (string file)
590                 {
591                         return new FileIOPermission (FileIOPermissionAccess.Read, file);
592                 }
593
594                 internal static IStackWalk PathDiscoveryAccess (string path)
595                 {
596                         return new FileIOPermission (FileIOPermissionAccess.PathDiscovery, path);
597                 }
598                 #endregion
599         }
600 }