// // HttpListener.cs // Copied from System.Net.HttpListener // // Author: // Gonzalo Paniagua Javier (gonzalo@novell.com) // // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) // Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Collections.Generic; using System.Net; using System.Threading; // TODO: logging namespace WebSocketSharp.Net { public sealed class HttpListener : IDisposable { #region Fields AuthenticationSchemes auth_schemes; AuthenticationSchemeSelector auth_selector; Dictionary connections; List ctx_queue; bool disposed; bool ignore_write_exceptions; bool listening; HttpListenerPrefixCollection prefixes; string realm; Dictionary registry; bool unsafe_ntlm_auth; List wait_queue; #endregion #region Constructor public HttpListener () { prefixes = new HttpListenerPrefixCollection (this); registry = new Dictionary (); connections = new Dictionary (); ctx_queue = new List (); wait_queue = new List (); auth_schemes = AuthenticationSchemes.Anonymous; } #endregion #region Properties // TODO: Digest, NTLM and Negotiate require ControlPrincipal public AuthenticationSchemes AuthenticationSchemes { get { return auth_schemes; } set { CheckDisposed (); auth_schemes = value; } } public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate { get { return auth_selector; } set { CheckDisposed (); auth_selector = value; } } public bool IgnoreWriteExceptions { get { return ignore_write_exceptions; } set { CheckDisposed (); ignore_write_exceptions = value; } } public bool IsListening { get { return listening; } } public static bool IsSupported { get { return true; } } public HttpListenerPrefixCollection Prefixes { get { CheckDisposed (); return prefixes; } } // TODO: Use this public string Realm { get { return realm; } set { CheckDisposed (); realm = value; } } // TODO: Support for NTLM needs some loving. public bool UnsafeConnectionNtlmAuthentication { get { return unsafe_ntlm_auth; } set { CheckDisposed (); unsafe_ntlm_auth = value; } } #endregion #region Private Methods void Cleanup (bool close_existing) { lock (((ICollection)registry).SyncRoot) { if (close_existing) { // Need to copy this since closing will call UnregisterContext ICollection keys = registry.Keys; var all = new HttpListenerContext [keys.Count]; keys.CopyTo (all, 0); registry.Clear (); for (int i = all.Length - 1; i >= 0; i--) all [i].Connection.Close (true); } lock (((ICollection)connections).SyncRoot) { ICollection keys = connections.Keys; var conns = new HttpConnection [keys.Count]; keys.CopyTo (conns, 0); connections.Clear (); for (int i = conns.Length - 1; i >= 0; i--) conns [i].Close (true); } lock (((ICollection)ctx_queue).SyncRoot) { var ctxs = ctx_queue.ToArray (); ctx_queue.Clear (); for (int i = ctxs.Length - 1; i >= 0; i--) ctxs [i].Connection.Close (true); } lock (((ICollection)wait_queue).SyncRoot) { Exception exc = new ObjectDisposedException ("listener"); foreach (ListenerAsyncResult ares in wait_queue) { ares.Complete (exc); } wait_queue.Clear (); } } } void Close (bool force) { CheckDisposed (); EndPointManager.RemoveListener (this); Cleanup (force); } // Must be called with a lock on ctx_queue HttpListenerContext GetContextFromQueue () { if (ctx_queue.Count == 0) return null; var context = ctx_queue [0]; ctx_queue.RemoveAt (0); return context; } void IDisposable.Dispose () { if (disposed) return; Close (true); //TODO: Should we force here or not? disposed = true; } #endregion #region Internal Methods internal void AddConnection (HttpConnection cnc) { connections [cnc] = cnc; } internal void CheckDisposed () { if (disposed) throw new ObjectDisposedException (GetType ().ToString ()); } internal void RegisterContext (HttpListenerContext context) { lock (((ICollection)registry).SyncRoot) registry [context] = context; ListenerAsyncResult ares = null; lock (((ICollection)wait_queue).SyncRoot) { if (wait_queue.Count == 0) { lock (((ICollection)ctx_queue).SyncRoot) ctx_queue.Add (context); } else { ares = wait_queue [0]; wait_queue.RemoveAt (0); } } if (ares != null) ares.Complete (context); } internal void RemoveConnection (HttpConnection cnc) { connections.Remove (cnc); } internal AuthenticationSchemes SelectAuthenticationScheme (HttpListenerContext context) { if (AuthenticationSchemeSelectorDelegate != null) return AuthenticationSchemeSelectorDelegate (context.Request); else return auth_schemes; } internal void UnregisterContext (HttpListenerContext context) { lock (((ICollection)registry).SyncRoot) registry.Remove (context); lock (((ICollection)ctx_queue).SyncRoot) { int idx = ctx_queue.IndexOf (context); if (idx >= 0) ctx_queue.RemoveAt (idx); } } #endregion #region Public Methods public void Abort () { if (disposed) return; if (!listening) { return; } Close (true); } public IAsyncResult BeginGetContext (AsyncCallback callback, Object state) { CheckDisposed (); if (!listening) throw new InvalidOperationException ("Please, call Start before using this method."); ListenerAsyncResult ares = new ListenerAsyncResult (callback, state); // lock wait_queue early to avoid race conditions lock (((ICollection)wait_queue).SyncRoot) { lock (((ICollection)ctx_queue).SyncRoot) { HttpListenerContext ctx = GetContextFromQueue (); if (ctx != null) { ares.Complete (ctx, true); return ares; } } wait_queue.Add (ares); } return ares; } public void Close () { if (disposed) return; if (!listening) { disposed = true; return; } Close (true); disposed = true; } public HttpListenerContext EndGetContext (IAsyncResult asyncResult) { CheckDisposed (); if (asyncResult == null) throw new ArgumentNullException ("asyncResult"); ListenerAsyncResult ares = asyncResult as ListenerAsyncResult; if (ares == null) throw new ArgumentException ("Wrong IAsyncResult.", "asyncResult"); if (ares.EndCalled) throw new ArgumentException ("Cannot reuse this IAsyncResult"); ares.EndCalled = true; if (!ares.IsCompleted) ares.AsyncWaitHandle.WaitOne (); lock (((ICollection)wait_queue).SyncRoot) { int idx = wait_queue.IndexOf (ares); if (idx >= 0) wait_queue.RemoveAt (idx); } HttpListenerContext context = ares.GetContext (); context.ParseAuthentication (SelectAuthenticationScheme (context)); return context; // This will throw on error. } public HttpListenerContext GetContext () { // The prefixes are not checked when using the async interface!? if (prefixes.Count == 0) throw new InvalidOperationException ("Please, call AddPrefix before using this method."); ListenerAsyncResult ares = (ListenerAsyncResult) BeginGetContext (null, null); ares.InGet = true; return EndGetContext (ares); } public void Start () { CheckDisposed (); if (listening) return; EndPointManager.AddListener (this); listening = true; } public void Stop () { CheckDisposed (); listening = false; Close (false); } #endregion } }