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