/* * Copyright (c) 2003, 2004 The University of Wroclaw. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the University may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL THE UNIVERSITY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ namespace Sioux { using Nemerle.Utility; using Nemerle.Collections; using System; using System.Net; using System.Net.Sockets; /** * An HTTP server capable of hosting Nemerle web applications. */ public class Httpd { /* -- INTERNAL PROPERTIES ---------------------------------------------- */ #if DEBUG /** * Returns the number of sessions in session table */ internal static SessionCount : int { get { session_table.Count } } #endif /* -- PUBLIC METHODS --------------------------------------------------- */ /** * Initializes the server. */ public static Initialize (config_file_name : option [string], log_file_name : option [string], handler : Request * Response -> void ) : void { config = Config (config_file_name); logger = Logger (log_file_name); dynamic_handler = handler; session_table = Hashtable(); application_table = Hashtable(); session_mutex = System.Threading.Mutex(); connected_users_mutex = System.Threading.Mutex(); connected_users_count = 0; // initialize session id generator SessionID.Initialize() } /** * Starts listening for incoming connections */ public static Run () : void { run (); } /** * Deinitializes the server */ public static Destroy () : void { logger.Destroy (); } /** * */ public static GetLogger () : Logger { logger } /** * */ public static GetConfig () : Config { config } /* -- INTERNAL METHODS -------------------------------------------------- */ #if DEBUG internal static ContainsSession(id : string) : bool { session_table.Contains(id) } #endif internal static GetSession(id : string) : option [Session] { check_validity_of_all_sessions(); session_table.Get(id) } internal static AddSession(session : Session) : void { ignore(session_mutex.WaitOne()); session_table.Add(session.SessionID,session); ignore(session_mutex.ReleaseMutex()); } internal static RemoveSession(id : string) : void { ignore(session_mutex.WaitOne()); session_table.Remove(id); ignore(session_mutex.ReleaseMutex()); } internal static ContainsApplication(id : string) : bool { application_table.Contains(id); } internal static GetApplication(id : string) : option [Application] { application_table.Get(id); } internal static AddApplication(id : string,app : Application) : void { application_table.Add(id,app); } /* -- PRIVATE METHODS -------------------------------------------------- */ /** * Starts to listen for incoming connections. */ private static open_connection () : TcpListener { def addr_s = config.Get ("server/address"); def addr = if (addr_s != null) IPAddress.Parse (config.Get ("server/address")) else null; def port = try { System.Int32.Parse (config.Get ("server/port")) } catch { _ => 80 }; def server = if (addr != null) TcpListener (addr, port) else TcpListener (port); server.Start (); server } /** * The HTTP server's main loop. */ private static run () : void { def server = open_connection (); def max_connections = Int32.Parse( config.Get ("server/max_connections")); def max_sessions = Int32.Parse( config.Get ("server/max_sessions")); while (true) { def client = server.AcceptSocket (); def handle_request () { def stream = NetworkStream (client); def request = Request (stream); def response = Response (stream); match (request.RequestInfo) { | RequestInfo.Invalid => response.WriteInvalid () | RequestInfo.StaticGet (url) => response.ServeStaticFile (url) | RequestInfo.DynamicGet | RequestInfo.Post => dynamic_handler (request, response) }; try { // this is crucial! otherwise browser gets ECONNRESET client.Shutdown (SocketShutdown.Both) } catch { | _ => () }; ignore(connected_users_mutex.WaitOne()); connected_users_count -= 1; ignore(connected_users_mutex.ReleaseMutex()); stream.Close () }; ignore(connected_users_mutex.WaitOne()); connected_users_count += 1; ignore(connected_users_mutex.ReleaseMutex()); mutable make_thread = false; mutable th = None(); while(!make_thread) { ignore(connected_users_mutex.WaitOne()); ignore(session_mutex.WaitOne()); when((connected_users_count <= max_connections || max_connections == -1) && (session_table.Count <= max_sessions || max_sessions == -1)) { make_thread = true; th = Some(System.Threading.Thread (System.Threading.ThreadStart (handle_request))); } ignore(session_mutex.ReleaseMutex()); ignore(connected_users_mutex.ReleaseMutex()); if(make_thread) { def t = Option.UnSome(th); t.Start() } else System.Threading.Thread.Sleep(500); } } } /** * Check if all sessions are valid, removes invalid */ private static check_validity_of_all_sessions() : void { def remove_invalid_session ( _ ,s : Session) { when(s.LastAccesedTime.AddMinutes((s.Timeout :> double)) < DateTime.Now || !s.IsValid) { def remove() { ignore(session_mutex.WaitOne()); session_table.Remove(s.SessionID); ignore(session_mutex.ReleaseMutex()); } def th = System.Threading.Thread (System.Threading.ThreadStart (remove)); th.Start() } } ignore(session_mutex.WaitOne()); session_table.Iter(remove_invalid_session); ignore(session_mutex.ReleaseMutex()); } /* -- PRIVATE FIELDS --------------------------------------------------- */ private static mutable session_table : Hashtable [string,Session]; private static mutable application_table : Hashtable [string,Application]; private static mutable logger : Logger; private static mutable config : Config; private static mutable session_mutex : System.Threading.Mutex; private static mutable connected_users_mutex : System.Threading.Mutex; private static mutable connected_users_count : int; private static mutable dynamic_handler : Request * Response -> void; } }