websocket-sharp/websocket-sharp/Net/HttpListener.cs
2012-09-10 01:36:22 +09:00

373 lines
9.2 KiB
C#

//
// 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<HttpConnection, HttpConnection> connections;
List<HttpListenerContext> ctx_queue;
bool disposed;
bool ignore_write_exceptions;
bool listening;
HttpListenerPrefixCollection prefixes;
string realm;
Dictionary<HttpListenerContext, HttpListenerContext> registry;
bool unsafe_ntlm_auth;
List<ListenerAsyncResult> wait_queue;
#endregion
#region Constructor
public HttpListener ()
{
prefixes = new HttpListenerPrefixCollection (this);
registry = new Dictionary<HttpListenerContext, HttpListenerContext> ();
connections = new Dictionary<HttpConnection, HttpConnection> ();
ctx_queue = new List<HttpListenerContext> ();
wait_queue = new List<ListenerAsyncResult> ();
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
}
}