diff options
Diffstat (limited to 'SocketHttpListener/Net/HttpEndPointListener.cs')
| -rw-r--r-- | SocketHttpListener/Net/HttpEndPointListener.cs | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/SocketHttpListener/Net/HttpEndPointListener.cs b/SocketHttpListener/Net/HttpEndPointListener.cs new file mode 100644 index 000000000..254e76140 --- /dev/null +++ b/SocketHttpListener/Net/HttpEndPointListener.cs @@ -0,0 +1,539 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.System; +using MediaBrowser.Model.Text; +using SocketHttpListener.Primitives; +using ProtocolType = MediaBrowser.Model.Net.ProtocolType; +using SocketType = MediaBrowser.Model.Net.SocketType; +using System.Threading.Tasks; + +namespace SocketHttpListener.Net +{ + internal sealed class HttpEndPointListener + { + private HttpListener _listener; + private IPEndPoint _endpoint; + private Socket _socket; + private Dictionary<ListenerPrefix, HttpListener> _prefixes; + private List<ListenerPrefix> _unhandledPrefixes; // host = '*' + private List<ListenerPrefix> _allPrefixes; // host = '+' + private X509Certificate _cert; + private bool _secure; + private Dictionary<HttpConnection, HttpConnection> _unregisteredConnections; + + private readonly ILogger _logger; + private bool _closed; + private bool _enableDualMode; + private readonly ICryptoProvider _cryptoProvider; + private readonly ISocketFactory _socketFactory; + private readonly ITextEncoding _textEncoding; + private readonly IStreamHelper _streamHelper; + private readonly IFileSystem _fileSystem; + private readonly IEnvironmentInfo _environment; + + public HttpEndPointListener(HttpListener listener, IPAddress addr, int port, bool secure, X509Certificate cert, ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, IStreamHelper streamHelper, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) + { + this._listener = listener; + _logger = logger; + _cryptoProvider = cryptoProvider; + _socketFactory = socketFactory; + _streamHelper = streamHelper; + _textEncoding = textEncoding; + _fileSystem = fileSystem; + _environment = environment; + + this._secure = secure; + this._cert = cert; + + _enableDualMode = addr.Equals(IPAddress.IPv6Any); + _endpoint = new IPEndPoint(addr, port); + + _prefixes = new Dictionary<ListenerPrefix, HttpListener>(); + _unregisteredConnections = new Dictionary<HttpConnection, HttpConnection>(); + + CreateSocket(); + } + + internal HttpListener Listener + { + get + { + return _listener; + } + } + + private void CreateSocket() + { + try + { + _socket = CreateSocket(_endpoint.Address.AddressFamily, _enableDualMode); + } + catch (SocketCreateException ex) + { + if (_enableDualMode && _endpoint.Address.Equals(IPAddress.IPv6Any) && + (string.Equals(ex.ErrorCode, "AddressFamilyNotSupported", StringComparison.OrdinalIgnoreCase) || + // mono 4.8.1 and lower on bsd is throwing this + string.Equals(ex.ErrorCode, "ProtocolNotSupported", StringComparison.OrdinalIgnoreCase) || + // mono 5.2 on bsd is throwing this + string.Equals(ex.ErrorCode, "OperationNotSupported", StringComparison.OrdinalIgnoreCase))) + { + _endpoint = new IPEndPoint(IPAddress.Any, _endpoint.Port); + _enableDualMode = false; + _socket = CreateSocket(_endpoint.Address.AddressFamily, _enableDualMode); + } + else + { + throw; + } + } + + try + { + _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + } + catch (SocketException) + { + // This is not supported on all operating systems (qnap) + } + + _socket.Bind(_endpoint); + + // This is the number TcpListener uses. + _socket.Listen(2147483647); + + Accept(); + + _closed = false; + } + + private void Accept() + { + var acceptEventArg = new SocketAsyncEventArgs(); + acceptEventArg.UserToken = this; + acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(OnAccept); + + Accept(acceptEventArg); + } + + private static void TryCloseAndDispose(Socket socket) + { + try + { + using (socket) + { + socket.Close(); + } + } + catch + { + + } + } + + private static void TryClose(Socket socket) + { + try + { + socket.Close(); + } + catch + { + + } + } + + private void Accept(SocketAsyncEventArgs acceptEventArg) + { + // acceptSocket must be cleared since the context object is being reused + acceptEventArg.AcceptSocket = null; + + try + { + bool willRaiseEvent = _socket.AcceptAsync(acceptEventArg); + + if (!willRaiseEvent) + { + ProcessAccept(acceptEventArg); + } + } + catch (ObjectDisposedException) + { + } + catch (Exception ex) + { + HttpEndPointListener epl = (HttpEndPointListener)acceptEventArg.UserToken; + + epl._logger.ErrorException("Error in socket.AcceptAsync", ex); + } + } + + // This method is the callback method associated with Socket.AcceptAsync + // operations and is invoked when an accept operation is complete + // + private static void OnAccept(object sender, SocketAsyncEventArgs e) + { + ProcessAccept(e); + } + + private static async void ProcessAccept(SocketAsyncEventArgs args) + { + HttpEndPointListener epl = (HttpEndPointListener)args.UserToken; + + if (epl._closed) + { + return; + } + + // http://msdn.microsoft.com/en-us/library/system.net.sockets.acceptSocket.acceptasync%28v=vs.110%29.aspx + // Under certain conditions ConnectionReset can occur + // Need to attept to re-accept + var socketError = args.SocketError; + var accepted = args.AcceptSocket; + + epl.Accept(args); + + if (socketError == SocketError.ConnectionReset) + { + epl._logger.Error("SocketError.ConnectionReset reported. Attempting to re-accept."); + return; + } + + if(accepted == null) + { + return; + } + + if (epl._secure && epl._cert == null) + { + TryClose(accepted); + return; + } + + try + { + var remoteEndPointString = accepted.RemoteEndPoint == null ? string.Empty : accepted.RemoteEndPoint.ToString(); + var localEndPointString = accepted.LocalEndPoint == null ? string.Empty : accepted.LocalEndPoint.ToString(); + //_logger.Info("HttpEndPointListener Accepting connection from {0} to {1} secure connection requested: {2}", remoteEndPointString, localEndPointString, _secure); + + HttpConnection conn = new HttpConnection(epl._logger, accepted, epl, epl._secure, epl._cert, epl._cryptoProvider, epl._streamHelper, epl._textEncoding, epl._fileSystem, epl._environment); + + await conn.Init().ConfigureAwait(false); + + //_logger.Debug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId); + lock (epl._unregisteredConnections) + { + epl._unregisteredConnections[conn] = conn; + } + conn.BeginReadRequest(); + } + catch (Exception ex) + { + epl._logger.ErrorException("Error in ProcessAccept", ex); + + TryClose(accepted); + epl.Accept(); + return; + } + } + + private Socket CreateSocket(AddressFamily addressFamily, bool dualMode) + { + try + { + var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); + + if (dualMode) + { + socket.DualMode = true; + } + + return socket; + } + catch (SocketException ex) + { + throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex); + } + catch (ArgumentException ex) + { + if (dualMode) + { + // Mono for BSD incorrectly throws ArgumentException instead of SocketException + throw new SocketCreateException("AddressFamilyNotSupported", ex); + } + else + { + throw; + } + } + } + + internal void RemoveConnection(HttpConnection conn) + { + lock (_unregisteredConnections) + { + _unregisteredConnections.Remove(conn); + } + } + + public bool BindContext(HttpListenerContext context) + { + HttpListenerRequest req = context.Request; + ListenerPrefix prefix; + HttpListener listener = SearchListener(req.Url, out prefix); + if (listener == null) + return false; + + context.Connection.Prefix = prefix; + return true; + } + + public void UnbindContext(HttpListenerContext context) + { + if (context == null || context.Request == null) + return; + + _listener.UnregisterContext(context); + } + + private HttpListener SearchListener(Uri uri, out ListenerPrefix prefix) + { + prefix = null; + if (uri == null) + return null; + + string host = uri.Host; + int port = uri.Port; + string path = WebUtility.UrlDecode(uri.AbsolutePath); + string pathSlash = path[path.Length - 1] == '/' ? path : path + "/"; + + HttpListener bestMatch = null; + int bestLength = -1; + + if (host != null && host != "") + { + Dictionary<ListenerPrefix, HttpListener> localPrefixes = _prefixes; + foreach (ListenerPrefix p in localPrefixes.Keys) + { + string ppath = p.Path; + if (ppath.Length < bestLength) + continue; + + if (p.Host != host || p.Port != port) + continue; + + if (path.StartsWith(ppath) || pathSlash.StartsWith(ppath)) + { + bestLength = ppath.Length; + bestMatch = localPrefixes[p]; + prefix = p; + } + } + if (bestLength != -1) + return bestMatch; + } + + List<ListenerPrefix> list = _unhandledPrefixes; + bestMatch = MatchFromList(host, path, list, out prefix); + + if (path != pathSlash && bestMatch == null) + bestMatch = MatchFromList(host, pathSlash, list, out prefix); + + if (bestMatch != null) + return bestMatch; + + list = _allPrefixes; + bestMatch = MatchFromList(host, path, list, out prefix); + + if (path != pathSlash && bestMatch == null) + bestMatch = MatchFromList(host, pathSlash, list, out prefix); + + if (bestMatch != null) + return bestMatch; + + return null; + } + + private HttpListener MatchFromList(string host, string path, List<ListenerPrefix> list, out ListenerPrefix prefix) + { + prefix = null; + if (list == null) + return null; + + HttpListener bestMatch = null; + int bestLength = -1; + + foreach (ListenerPrefix p in list) + { + string ppath = p.Path; + if (ppath.Length < bestLength) + continue; + + if (path.StartsWith(ppath)) + { + bestLength = ppath.Length; + bestMatch = p._listener; + prefix = p; + } + } + + return bestMatch; + } + + private void AddSpecial(List<ListenerPrefix> list, ListenerPrefix prefix) + { + if (list == null) + return; + + foreach (ListenerPrefix p in list) + { + if (p.Path == prefix.Path) + throw new Exception("net_listener_already"); + } + list.Add(prefix); + } + + private bool RemoveSpecial(List<ListenerPrefix> list, ListenerPrefix prefix) + { + if (list == null) + return false; + + int c = list.Count; + for (int i = 0; i < c; i++) + { + ListenerPrefix p = list[i]; + if (p.Path == prefix.Path) + { + list.RemoveAt(i); + return true; + } + } + return false; + } + + private void CheckIfRemove() + { + if (_prefixes.Count > 0) + return; + + List<ListenerPrefix> list = _unhandledPrefixes; + if (list != null && list.Count > 0) + return; + + list = _allPrefixes; + if (list != null && list.Count > 0) + return; + + HttpEndPointManager.RemoveEndPoint(this, _endpoint); + } + + public void Close() + { + _closed = true; + _socket.Close(); + lock (_unregisteredConnections) + { + // Clone the list because RemoveConnection can be called from Close + var connections = new List<HttpConnection>(_unregisteredConnections.Keys); + + foreach (HttpConnection c in connections) + c.Close(true); + _unregisteredConnections.Clear(); + } + } + + public void AddPrefix(ListenerPrefix prefix, HttpListener listener) + { + List<ListenerPrefix> current; + List<ListenerPrefix> future; + if (prefix.Host == "*") + { + do + { + current = _unhandledPrefixes; + future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>(); + prefix._listener = listener; + AddSpecial(future, prefix); + } while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current); + return; + } + + if (prefix.Host == "+") + { + do + { + current = _allPrefixes; + future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>(); + prefix._listener = listener; + AddSpecial(future, prefix); + } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current); + return; + } + + Dictionary<ListenerPrefix, HttpListener> prefs, p2; + do + { + prefs = _prefixes; + if (prefs.ContainsKey(prefix)) + { + throw new Exception("net_listener_already"); + } + p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs); + p2[prefix] = listener; + } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs); + } + + public void RemovePrefix(ListenerPrefix prefix, HttpListener listener) + { + List<ListenerPrefix> current; + List<ListenerPrefix> future; + if (prefix.Host == "*") + { + do + { + current = _unhandledPrefixes; + future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>(); + if (!RemoveSpecial(future, prefix)) + break; // Prefix not found + } while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current); + + CheckIfRemove(); + return; + } + + if (prefix.Host == "+") + { + do + { + current = _allPrefixes; + future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>(); + if (!RemoveSpecial(future, prefix)) + break; // Prefix not found + } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current); + CheckIfRemove(); + return; + } + + Dictionary<ListenerPrefix, HttpListener> prefs, p2; + do + { + prefs = _prefixes; + if (!prefs.ContainsKey(prefix)) + break; + + p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs); + p2.Remove(prefix); + } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs); + CheckIfRemove(); + } + } +} |
