Dont index style nodes.
[beagle.git] / beagled / WebServices / WebServer / ApplicationServer.cs
blobf84969e2cdd3e3a250eb694bb9f7f6e3adf4a757
1 //
2 // ApplicationServer.cs
3 //
4 // Authors:
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 // Lluis Sanchez Gual (lluis@ximian.com)
7 //
8 // Copyright (c) Copyright 2002,2003,2004 Novell, Inc
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System;
32 using System.Net;
33 using System.Net.Sockets;
34 using System.Xml;
35 using System.Web;
36 using System.Web.Hosting;
37 using System.Collections;
38 using System.Text;
39 using System.Threading;
40 using System.IO;
41 using System.Globalization;
42 using System.Runtime.InteropServices;
44 namespace Mono.ASPNET
46 // ApplicationServer runs the main server thread, which accepts client
47 // connections and forwards the requests to the correct web application.
48 // ApplicationServer takes an IWebSource object as parameter in the
49 // constructor. IWebSource provides methods for getting some objects
50 // whose behavior is specific to XSP or mod_mono.
52 // Each web application lives in its own application domain, and incoming
53 // requests are processed in the corresponding application domain.
54 // Since the client Socket can't be passed from one domain to the other, the
55 // flow of information must go through the cross-app domain channel.
57 // For each application two objects are created:
58 // 1) a IApplicationHost object is created in the application domain
59 // 2) a IRequestBroker is created in the main domain.
61 // The IApplicationHost is used by the ApplicationServer to start the
62 // processing of a request in the application domain.
63 // The IRequestBroker is used from the application domain to access
64 // information in the main domain.
66 // The complete sequence of servicing a request is the following:
68 // 1) The listener accepts an incoming connection.
69 // 2) An IWorker object is created (through the IWebSource), and it is
70 // queued in the thread pool.
71 // 3) When the IWorker's run method is called, it registers itself in
72 // the application's request broker, and gets a request id. All this is
73 // done in the main domain.
74 // 4) The IWorker starts the request processing by making a cross-app domain
75 // call to the application host. It passes as parameters the request id
76 // and other information already read from the request.
77 // 5) The application host executes the request. When it needs to read or
78 // write request data, it performs remote calls to the request broker,
79 // passing the request id provided by the IWorker.
80 // 6) When the request broker receives a call from the application host,
81 // it locates the IWorker registered with the provided request id and
82 // forwards the call to it.
84 public class ApplicationServer : MarshalByRefObject
86 IWebSource webSource;
87 bool started;
88 bool stop;
89 bool verbose;
90 Socket listen_socket;
92 Thread runner;
94 // This is much faster than hashtable for typical cases.
95 ArrayList vpathToHost = new ArrayList ();
97 public ApplicationServer (IWebSource source)
99 webSource = source;
102 public bool Verbose {
103 get { return verbose; }
104 set { verbose = value; }
107 private void AddApplication (string vhost, int vport, string vpath, string fullPath)
109 // TODO - check for duplicates, sort, optimize, etc.
110 if (started)
111 throw new InvalidOperationException ("The server is already started.");
113 if (verbose) {
114 Console.WriteLine("Registering application:");
115 Console.WriteLine(" Host: {0}", (vhost != null) ? vhost : "any");
116 Console.WriteLine(" Port: {0}", (vport != -1) ?
117 vport.ToString () : "any");
119 Console.WriteLine(" Virtual path: {0}", vpath);
120 Console.WriteLine(" Physical path: {0}", fullPath);
123 vpathToHost.Add (new VPathToHost (vhost, vport, vpath, fullPath));
126 public void AddApplicationsFromConfigDirectory (string directoryName)
128 if (verbose) {
129 Console.WriteLine ("Adding applications from *.webapp files in " +
130 "directory '{0}'", directoryName);
133 DirectoryInfo di = new DirectoryInfo (directoryName);
134 if (!di.Exists) {
135 Console.Error.WriteLine ("Directory {0} does not exist.", directoryName);
136 return;
139 foreach (FileInfo fi in di.GetFiles ("*.webapp"))
140 AddApplicationsFromConfigFile (fi.FullName);
143 public void AddApplicationsFromConfigFile (string fileName)
145 if (verbose) {
146 Console.WriteLine ("Adding applications from config file '{0}'", fileName);
149 try {
150 XmlDocument doc = new XmlDocument ();
151 doc.Load (fileName);
153 foreach (XmlElement el in doc.SelectNodes ("//web-application")) {
154 AddApplicationFromElement (el);
156 } catch {
157 Console.WriteLine ("Error loading '{0}'", fileName);
158 throw;
162 void AddApplicationFromElement (XmlElement el)
164 XmlNode n;
166 n = el.SelectSingleNode ("enabled");
167 if (n != null && n.InnerText.Trim () == "false")
168 return;
170 string vpath = el.SelectSingleNode ("vpath").InnerText;
171 string path = el.SelectSingleNode ("path").InnerText;
173 string vhost = null;
174 n = el.SelectSingleNode ("vhost");
175 #if !MOD_MONO_SERVER
176 if (n != null)
177 vhost = n.InnerText;
178 #else
179 // TODO: support vhosts in xsp.exe
180 string name = el.SelectSingleNode ("name").InnerText;
181 if (verbose)
182 Console.WriteLine ("Ignoring vhost {0} for {1}", n.InnerText, name);
183 #endif
185 int vport = -1;
186 n = el.SelectSingleNode ("vport");
187 #if !MOD_MONO_SERVER
188 if (n != null)
189 vport = Convert.ToInt32 (n.InnerText);
190 #else
191 // TODO: Listen on different ports
192 if (verbose)
193 Console.WriteLine ("Ignoring vport {0} for {1}", n.InnerText, name);
194 #endif
196 AddApplication (vhost, vport, vpath, path);
199 public void AddApplicationsFromCommandLine (string applications)
201 if (applications == null)
202 throw new ArgumentNullException ("applications");
204 if (applications == "")
205 return;
207 if (verbose) {
208 Console.WriteLine("Adding applications '{0}'...", applications);
211 string [] apps = applications.Split (',');
213 foreach (string str in apps) {
214 string [] app = str.Split (':');
216 if (app.Length < 2 || app.Length > 4)
217 throw new ArgumentException ("Should be something like " +
218 "[[hostname:]port:]VPath:realpath");
220 int vport;
221 string vhost;
222 string vpath;
223 string realpath;
224 int pos = 0;
226 if (app.Length >= 3) {
227 vhost = app[pos++];
228 } else {
229 vhost = null;
232 if (app.Length >= 4) {
233 // FIXME: support more than one listen port.
234 vport = Convert.ToInt16 (app[pos++]);
235 } else {
236 vport = -1;
239 vpath = app [pos++];
240 realpath = app[pos++];
242 if (!vpath.EndsWith ("/"))
243 vpath += "/";
245 string fullPath = System.IO.Path.GetFullPath (realpath);
246 AddApplication (vhost, vport, vpath, fullPath);
250 public bool Start (bool bgThread)
252 if (started)
253 throw new InvalidOperationException ("The server is already started.");
255 if (vpathToHost == null)
256 throw new InvalidOperationException ("SetApplications must be called first.");
258 if (vpathToHost.Count == 0)
259 throw new InvalidOperationException ("No applications defined or all of them are disabled");
261 listen_socket = webSource.CreateSocket ();
262 listen_socket.Listen (500);
263 listen_socket.Blocking = false;
264 runner = new Thread (new ThreadStart (RunServer));
265 runner.IsBackground = bgThread;
266 runner.Start ();
267 stop = false;
268 WebTrace.WriteLine ("Server started.");
269 return true;
272 public void Stop ()
274 if (!started)
275 throw new InvalidOperationException ("The server is not started.");
277 if (stop)
278 return; // Just ignore, as we're already stopping
280 stop = true;
281 webSource.Dispose ();
283 // A foreground thread is required to end cleanly
284 Thread stopThread = new Thread (new ThreadStart (RealStop));
285 stopThread.Start ();
288 void RealStop ()
290 runner.Abort ();
291 listen_socket.Close ();
292 UnloadAll ();
293 Thread.Sleep (1000);
294 WebTrace.WriteLine ("Server stopped.");
297 public void UnloadAll ()
299 lock (vpathToHost) {
300 foreach (VPathToHost v in vpathToHost) {
301 v.UnloadHost ();
306 void SetSocketOptions (Socket sock)
308 #if !MOD_MONO_SERVER
309 try {
310 sock.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 15000); // 15s
311 sock.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 15000); // 15s
312 } catch {
313 // Ignore exceptions here for systems that do not support these options.
315 #endif
318 SocketPool spool = new SocketPool ();
320 void RunServer ()
322 spool.AddReadSocket (listen_socket);
323 started = true;
324 Socket client;
325 int w;
326 while (!stop){
327 ArrayList wSockets = spool.SelectRead ();
328 w = wSockets.Count;
329 for (int i = 0; i < w; i++) {
330 Socket s = (Socket) wSockets [i];
331 if (s == listen_socket) {
332 try {
333 client = s.Accept ();
334 client.Blocking = true;
335 } catch (Exception e) {
336 continue;
338 WebTrace.WriteLine ("Accepted connection.");
339 SetSocketOptions (client);
340 spool.AddReadSocket (client, DateTime.UtcNow);
341 continue;
344 spool.RemoveReadSocket (s);
345 IWorker worker = webSource.CreateWorker (s, this);
346 ThreadPool.QueueUserWorkItem (new WaitCallback (worker.Run));
350 started = false;
353 public VPathToHost GetApplicationForPath (string vhost, int port, string path,
354 bool defaultToRoot)
356 VPathToHost bestMatch = null;
357 int bestMatchLength = 0;
359 // Console.WriteLine("GetApplicationForPath({0},{1},{2},{3})", vhost, port,
360 // path, defaultToRoot);
361 for (int i = vpathToHost.Count - 1; i >= 0; i--) {
362 VPathToHost v = (VPathToHost) vpathToHost [i];
363 int matchLength = v.vpath.Length;
364 if (matchLength <= bestMatchLength || !v.Match (vhost, port, path))
365 continue;
367 bestMatchLength = matchLength;
368 bestMatch = v;
371 if (bestMatch != null) {
372 lock (bestMatch) {
373 if (bestMatch.AppHost == null)
374 bestMatch.CreateHost (this, webSource);
376 return bestMatch;
379 if (defaultToRoot)
380 return GetApplicationForPath (vhost, port, "/", false);
382 if (verbose)
383 Console.WriteLine ("No application defined for: {0}:{1}{2}", vhost, port, path);
385 return null;
388 public void DestroyHost (IApplicationHost host)
390 // Called when the host appdomain is being unloaded
391 for (int i = vpathToHost.Count - 1; i >= 0; i--) {
392 VPathToHost v = (VPathToHost) vpathToHost [i];
393 if (v.TryClearHost (host))
394 break;
398 public override object InitializeLifetimeService ()
400 return null;
403 public int GetAvailableReuses (Socket sock)
405 int res = spool.GetReuseCount (sock);
406 if (res == -1 || res >= 100)
407 return -1;
409 return 100 - res;
412 public void ReuseSocket (Socket sock)
414 IWorker worker = webSource.CreateWorker (sock, this);
415 spool.IncrementReuseCount (sock);
416 ThreadPool.QueueUserWorkItem (new WaitCallback (worker.Run));
419 public void CloseSocket (Socket sock)
421 spool.RemoveReadSocket (sock);
425 class SocketPool {
426 ArrayList readSockets = new ArrayList ();
427 Hashtable timeouts = new Hashtable ();
428 Hashtable uses = new Hashtable ();
429 object locker = new object ();
431 public ArrayList SelectRead ()
433 if (readSockets.Count == 0)
434 throw new InvalidOperationException ("There are no sockets.");
436 ArrayList wSockets = new ArrayList (readSockets);
437 // A bug on MS (or is it just me?) makes the following select return immediately
438 // when there's only one socket (the listen socket) in the array:
439 // Socket.Select (wSockets, null, null, (w == 1) ? -1 : 1000 * 1000); // 1s
440 // so i have to do this for the MS runtime not to hung all the CPU.
441 if (wSockets.Count > 1) {
442 Socket.Select (wSockets, null, null, 1000 * 1000); // 1s
443 } else {
444 Socket sock = (Socket) wSockets [0];
445 sock.Poll (-1, SelectMode.SelectRead);
446 // wSockets already contains listen_socket.
449 lock (locker)
450 CheckTimeouts (wSockets);
452 return wSockets;
455 void CheckTimeouts (ArrayList wSockets)
457 int w = timeouts.Count;
458 if (w > 0) {
459 Socket [] socks_timeout = new Socket [w];
460 timeouts.Keys.CopyTo (socks_timeout, 0);
461 DateTime now = DateTime.UtcNow;
462 foreach (Socket k in socks_timeout) {
463 if (wSockets.Contains (k))
464 continue;
466 DateTime atime = (DateTime) timeouts [k];
467 TimeSpan diff = now - atime;
468 if (diff.TotalMilliseconds > 15 * 1000) {
469 RemoveReadSocket (k);
470 wSockets.Remove (k);
471 k.Close ();
472 continue;
478 public void IncrementReuseCount (Socket sock)
480 lock (locker) {
481 if (uses.ContainsKey (sock)) {
482 int n = (int) uses [sock];
483 uses [sock] = n + 1;
484 } else {
485 uses [sock] = 1;
490 public int GetReuseCount (Socket sock)
492 lock (locker) {
493 if (uses.ContainsKey (sock))
494 return (int) uses [sock];
496 uses [sock] = 1;
497 return 1;
501 public void AddReadSocket (Socket sock)
503 lock (locker)
504 readSockets.Add (sock);
507 public void AddReadSocket (Socket sock, DateTime time)
509 lock (locker) {
510 if (readSockets.Contains (sock)) {
511 timeouts [sock] = time;
512 return;
515 readSockets.Add (sock);
516 timeouts [sock] = time;
520 public void RemoveReadSocket (Socket sock)
522 lock (locker) {
523 readSockets.Remove (sock);
524 timeouts.Remove (sock);
525 uses.Remove (sock);
530 public class VPathToHost
532 public readonly string vhost;
533 public readonly int vport;
534 public readonly string vpath;
535 public readonly string realPath;
536 public readonly bool haveWildcard;
538 public IApplicationHost AppHost;
539 public IRequestBroker RequestBroker;
541 public VPathToHost (string vhost, int vport, string vpath, string realPath)
543 this.vhost = (vhost != null) ? vhost.ToLower (CultureInfo.InvariantCulture) : null;
544 this.vport = vport;
545 this.vpath = vpath;
546 if (vpath == null || vpath == "" || vpath [0] != '/')
547 throw new ArgumentException ("Virtual path must begin with '/': " + vpath,
548 "vpath");
550 this.realPath = realPath;
551 this.AppHost = null;
553 if (vhost != null && this.vhost.Length != 0 && this.vhost [0] == '*') {
554 haveWildcard = true;
555 if (this.vhost.Length > 2 && this.vhost [1] == '.')
556 this.vhost = this.vhost.Substring (2);
561 public bool TryClearHost (IApplicationHost host)
563 if (this.AppHost == host) {
564 this.AppHost = null;
565 return true;
568 return false;
571 public void UnloadHost ()
573 if (AppHost != null)
574 AppHost.Unload ();
576 AppHost = null;
579 public bool Redirect (string path, out string redirect)
581 redirect = null;
582 int plen = path.Length;
583 if (plen == this.vpath.Length - 1) {
584 redirect = this.vpath;
585 return true;
588 return false;
591 public bool Match (string vhost, int vport, string vpath)
593 if (vport != -1 && this.vport != -1 && vport != this.vport)
594 return false;
596 if (vhost != null && this.vhost != null) {
597 int length = this.vhost.Length;
598 string lwrvhost = vhost.ToLower (CultureInfo.InvariantCulture);
599 if (haveWildcard) {
600 if (this.vhost == "*")
601 return true;
603 if (length > vhost.Length)
604 return false;
606 if (length == vhost.Length && this.vhost != lwrvhost)
607 return false;
609 if (vhost [vhost.Length - length - 1] != '.')
610 return false;
612 if (!lwrvhost.EndsWith (this.vhost))
613 return false;
615 } else if (this.vhost != lwrvhost) {
616 return false;
620 int local = vpath.Length;
621 int vlength = this.vpath.Length;
622 if (vlength > local) {
623 // Check for /xxx requests to be redirected to /xxx/
624 if (this.vpath [vlength - 1] != '/')
625 return false;
627 return (vlength - 1 == local && this.vpath.Substring (0, vlength - 1) == vpath);
630 return (vpath.StartsWith (this.vpath));
633 public void CreateHost (ApplicationServer server, IWebSource webSource)
635 string v = vpath;
636 if (v != "/" && v.EndsWith ("/")) {
637 v = v.Substring (0, v.Length - 1);
640 AppHost = ApplicationHost.CreateApplicationHost (webSource.GetApplicationHostType(), v, realPath) as IApplicationHost;
641 AppHost.Server = server;
643 // Link the host in the application domain with a request broker in the main domain
644 RequestBroker = webSource.CreateRequestBroker ();
645 AppHost.RequestBroker = RequestBroker;
649 class HttpErrors
651 static byte [] error500;
652 static byte [] badRequest;
654 static HttpErrors ()
656 string s = "HTTP/1.0 500 Server error\r\n" +
657 "Connection: close\r\n\r\n" +
658 "<html><head><title>500 Server Error</title><body><h1>Server error</h1>\r\n" +
659 "Your client sent a request that was not understood by this server.\r\n" +
660 "</body></html>\r\n";
661 error500 = Encoding.ASCII.GetBytes (s);
663 string br = "HTTP/1.0 400 Bad Request\r\n" +
664 "Connection: close\r\n\r\n" +
665 "<html><head><title>400 Bad Request</title></head>" +
666 "<body><h1>Bad Request</h1>The request was not understood" +
667 "<p></body></html>";
669 badRequest = Encoding.ASCII.GetBytes (br);
672 public static byte [] NotFound (string uri)
674 string s = String.Format ("HTTP/1.0 404 Not Found\r\n" +
675 "Connection: close\r\n\r\n" +
676 "<html><head><title>404 Not Found</title></head>\r\n" +
677 "<body><h1>Not Found</h1>The requested URL {0} was not found on this " +
678 "server.<p>\r\n</body></html>\r\n", uri);
680 return Encoding.ASCII.GetBytes (s);
683 public static byte [] BadRequest ()
685 return badRequest;
688 public static byte [] ServerError ()
690 return error500;
694 class Paths {
695 private Paths ()
699 public static void GetPathsFromUri (string uri, out string realUri, out string pathInfo)
701 // There's a hidden missing feature here... :)
702 realUri = uri; pathInfo = "";
703 string basepath = HttpRuntime.AppDomainAppPath;
704 string vpath = HttpRuntime.AppDomainAppVirtualPath;
705 if (vpath [vpath.Length - 1] != '/')
706 vpath += '/';
708 if (vpath.Length > uri.Length)
709 return;
711 uri = uri.Substring (vpath.Length);
712 while (uri.Length > 0 && uri [0] == '/')
713 uri = uri.Substring (1);
715 int dot, slash;
716 int lastSlash = uri.Length;
717 bool windows = (Path.DirectorySeparatorChar == '\\');
719 for (dot = uri.LastIndexOf ('.'); dot > 0; dot = uri.LastIndexOf ('.', dot - 1)) {
720 slash = uri.IndexOf ('/', dot);
721 string partial;
722 if (slash == -1)
723 slash = lastSlash;
725 partial = uri.Substring (0, slash);
726 lastSlash = slash;
727 string partial_win = null;
728 if (windows)
729 partial_win = partial.Replace ('/', '\\');
731 string path = Path.Combine (basepath, (windows ? partial_win : partial));
732 if (!File.Exists (path))
733 continue;
735 realUri = vpath + uri.Substring (0, slash);
736 pathInfo = uri.Substring (slash);
737 break;