Forráskód Böngészése

Rewrite WebSocket handling code

Bond_009 5 éve
szülő
commit
976459d3e8

+ 4 - 2
Emby.Dlna/PlayTo/PlayToController.cs

@@ -6,7 +6,6 @@ using System.Threading;
 using System.Threading.Tasks;
 using Emby.Dlna.Didl;
 using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
@@ -899,7 +898,8 @@ namespace Emby.Dlna.PlayTo
             return 0;
         }
 
-        public Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken)
+        /// <inheritdoc />
+        public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken)
         {
             if (_disposed)
             {
@@ -915,10 +915,12 @@ namespace Emby.Dlna.PlayTo
             {
                 return SendPlayCommand(data as PlayRequest, cancellationToken);
             }
+
             if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
             {
                 return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
             }
+
             if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
             {
                 return SendGeneralCommand(data as GeneralCommand, cancellationToken);

+ 1 - 1
Emby.Dlna/PlayTo/PlayToManager.cs

@@ -21,7 +21,7 @@ using Microsoft.Extensions.Logging;
 
 namespace Emby.Dlna.PlayTo
 {
-    class PlayToManager : IDisposable
+    public class PlayToManager : IDisposable
     {
         private readonly ILogger _logger;
         private readonly ISessionManager _sessionManager;

+ 3 - 31
Emby.Server.Implementations/ApplicationHost.cs

@@ -45,7 +45,6 @@ using Emby.Server.Implementations.ScheduledTasks;
 using Emby.Server.Implementations.Security;
 using Emby.Server.Implementations.Serialization;
 using Emby.Server.Implementations.Session;
-using Emby.Server.Implementations.SocketSharp;
 using Emby.Server.Implementations.TV;
 using Emby.Server.Implementations.Updates;
 using MediaBrowser.Api;
@@ -105,7 +104,6 @@ using MediaBrowser.Providers.TV.TheTVDB;
 using MediaBrowser.WebDashboard.Api;
 using MediaBrowser.XbmcMetadata.Providers;
 using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Http.Extensions;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
@@ -629,32 +627,8 @@ namespace Emby.Server.Implementations
             }
         }
 
-        public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
-        {
-            if (!context.WebSockets.IsWebSocketRequest)
-            {
-                await next().ConfigureAwait(false);
-                return;
-            }
-
-            await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
-        }
-
-        public async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
-        {
-            if (context.WebSockets.IsWebSocketRequest)
-            {
-                await next().ConfigureAwait(false);
-                return;
-            }
-
-            var request = context.Request;
-            var response = context.Response;
-            var localPath = context.Request.Path.ToString();
-
-            var req = new WebSocketSharpRequest(request, response, request.Path, Logger);
-            await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
-        }
+        public Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
+            => HttpServer.RequestHandler(context);
 
         /// <summary>
         /// Registers resources that classes will depend on
@@ -777,7 +751,7 @@ namespace Emby.Server.Implementations
                 NetworkManager,
                 JsonSerializer,
                 XmlSerializer,
-                CreateHttpListener())
+                LoggerFactory)
             {
                 GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading")
             };
@@ -1194,8 +1168,6 @@ namespace Emby.Server.Implementations
             });
         }
 
-        protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(Logger);
-
         private CertificateInfo GetCertificateInfo(bool generateCertificate)
         {
             // Custom cert

+ 76 - 84
Emby.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -7,11 +7,12 @@ using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Net.Sockets;
+using System.Net.WebSockets;
 using System.Reflection;
 using System.Threading;
 using System.Threading.Tasks;
-using Emby.Server.Implementations.Net;
 using Emby.Server.Implementations.Services;
+using Emby.Server.Implementations.SocketSharp;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
@@ -21,9 +22,11 @@ using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Services;
 using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
 using Microsoft.AspNetCore.WebUtilities;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
 using ServiceStack.Text.Jsv;
 
 namespace Emby.Server.Implementations.HttpServer
@@ -31,18 +34,17 @@ namespace Emby.Server.Implementations.HttpServer
     public class HttpListenerHost : IHttpServer, IDisposable
     {
         private readonly ILogger _logger;
+        private readonly ILoggerFactory _loggerFactory;
         private readonly IServerConfigurationManager _config;
         private readonly INetworkManager _networkManager;
         private readonly IServerApplicationHost _appHost;
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IXmlSerializer _xmlSerializer;
-        private readonly IHttpListener _socketListener;
         private readonly Func<Type, Func<string, object>> _funcParseFn;
         private readonly string _defaultRedirectPath;
         private readonly string _baseUrlPrefix;
         private readonly Dictionary<Type, Type> ServiceOperationsMap = new Dictionary<Type, Type>();
         private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
-        private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
         private bool _disposed = false;
 
         public HttpListenerHost(
@@ -53,7 +55,7 @@ namespace Emby.Server.Implementations.HttpServer
             INetworkManager networkManager,
             IJsonSerializer jsonSerializer,
             IXmlSerializer xmlSerializer,
-            IHttpListener socketListener)
+            ILoggerFactory loggerFactory)
         {
             _appHost = applicationHost;
             _logger = logger;
@@ -63,8 +65,7 @@ namespace Emby.Server.Implementations.HttpServer
             _networkManager = networkManager;
             _jsonSerializer = jsonSerializer;
             _xmlSerializer = xmlSerializer;
-            _socketListener = socketListener;
-            _socketListener.WebSocketConnected = OnWebSocketConnected;
+            _loggerFactory = loggerFactory;
 
             _funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
 
@@ -155,38 +156,6 @@ namespace Emby.Server.Implementations.HttpServer
             return attributes;
         }
 
-        private void OnWebSocketConnected(WebSocketConnectEventArgs e)
-        {
-            if (_disposed)
-            {
-                return;
-            }
-
-            var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger)
-            {
-                OnReceive = ProcessWebSocketMessageReceived,
-                Url = e.Url,
-                QueryString = e.QueryString
-            };
-
-            connection.Closed += OnConnectionClosed;
-
-            lock (_webSocketConnections)
-            {
-                _webSocketConnections.Add(connection);
-            }
-
-            WebSocketConnected?.Invoke(this, new GenericEventArgs<IWebSocketConnection>(connection));
-        }
-
-        private void OnConnectionClosed(object sender, EventArgs e)
-        {
-            lock (_webSocketConnections)
-            {
-                _webSocketConnections.Remove((IWebSocketConnection)sender);
-            }
-        }
-
         private static Exception GetActualException(Exception ex)
         {
             if (ex is AggregateException agg)
@@ -274,32 +243,6 @@ namespace Emby.Server.Implementations.HttpServer
             return msg;
         }
 
-        /// <summary>
-        /// Shut down the Web Service
-        /// </summary>
-        public void Stop()
-        {
-            List<IWebSocketConnection> connections;
-
-            lock (_webSocketConnections)
-            {
-                connections = _webSocketConnections.ToList();
-                _webSocketConnections.Clear();
-            }
-
-            foreach (var connection in connections)
-            {
-                try
-                {
-                    connection.Dispose();
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error disposing connection");
-                }
-            }
-        }
-
         public static string RemoveQueryStringByKey(string url, string key)
         {
             var uri = new Uri(url);
@@ -411,31 +354,47 @@ namespace Emby.Server.Implementations.HttpServer
 
         private bool ValidateSsl(string remoteIp, string urlString)
         {
-            if (_config.Configuration.RequireHttps && _appHost.EnableHttps && !_config.Configuration.IsBehindProxy)
+            if (_config.Configuration.RequireHttps
+                && _appHost.EnableHttps
+                && !_config.Configuration.IsBehindProxy
+                && !urlString.Contains("https://", StringComparison.OrdinalIgnoreCase))
             {
-                if (urlString.IndexOf("https://", StringComparison.OrdinalIgnoreCase) == -1)
+                // These are hacks, but if these ever occur on ipv6 in the local network they could be incorrectly redirected
+                if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1
+                    || urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1)
                 {
-                    // These are hacks, but if these ever occur on ipv6 in the local network they could be incorrectly redirected
-                    if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1
-                        || urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1)
-                    {
-                        return true;
-                    }
+                    return true;
+                }
 
-                    if (!_networkManager.IsInLocalNetwork(remoteIp))
-                    {
-                        return false;
-                    }
+                if (!_networkManager.IsInLocalNetwork(remoteIp))
+                {
+                    return false;
                 }
             }
 
             return true;
         }
 
+        /// <inheritdoc />
+        public Task RequestHandler(HttpContext context)
+        {
+            if (context.WebSockets.IsWebSocketRequest)
+            {
+                return WebSocketRequestHandler(context);
+            }
+
+            var request = context.Request;
+            var response = context.Response;
+            var localPath = context.Request.Path.ToString();
+
+            var req = new WebSocketSharpRequest(request, response, request.Path, _logger);
+            return RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted);
+        }
+
         /// <summary>
-        /// Overridable method that can be used to implement a custom hnandler
+        /// Overridable method that can be used to implement a custom handler
         /// </summary>
-        public async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
+        private async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
         {
             var stopWatch = new Stopwatch();
             stopWatch.Start();
@@ -552,6 +511,44 @@ namespace Emby.Server.Implementations.HttpServer
             }
         }
 
+        private async Task WebSocketRequestHandler(HttpContext context)
+        {
+            if (_disposed)
+            {
+                return;
+            }
+
+            var url = context.Request.GetDisplayUrl();
+            _logger.LogInformation("WS {Url}. UserAgent: {UserAgent}", url, context.Request.Headers[HeaderNames.UserAgent].ToString());
+
+            try
+            {
+                var webSocket = await context.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
+
+                var connection = new WebSocketConnection(
+                    _loggerFactory.CreateLogger<WebSocketConnection>(),
+                    webSocket,
+                    context.Connection.RemoteIpAddress)
+                {
+                    Url = url,
+                    QueryString = context.Request.Query,
+                    OnReceive = ProcessWebSocketMessageReceived
+                };
+
+                WebSocketConnected?.Invoke(this, new GenericEventArgs<IWebSocketConnection>(connection));
+
+                await connection.ProcessAsync().ConfigureAwait(false);
+            }
+            catch (WebSocketException ex)
+            {
+                _logger.LogError(ex, "ProcessWebSocketRequest error");
+                if (!context.Response.HasStarted)
+                {
+                    context.Response.StatusCode = 500;
+                }
+            }
+        }
+
         // Entry point for HttpListener
         public ServiceHandler GetServiceHandler(IHttpRequest httpReq)
         {
@@ -661,11 +658,6 @@ namespace Emby.Server.Implementations.HttpServer
             return _jsonSerializer.DeserializeFromStreamAsync(stream, type);
         }
 
-        public Task ProcessWebSocketRequest(HttpContext context)
-        {
-            return _socketListener.ProcessWebSocketRequest(context);
-        }
-
         private string NormalizeEmbyRoutePath(string path)
         {
             _logger.LogDebug("Normalizing /emby route");
@@ -697,7 +689,7 @@ namespace Emby.Server.Implementations.HttpServer
 
             if (disposing)
             {
-                Stop();
+                // TODO:
             }
 
             _disposed = true;

+ 0 - 40
Emby.Server.Implementations/HttpServer/IHttpListener.cs

@@ -1,40 +0,0 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using Emby.Server.Implementations.Net;
-using MediaBrowser.Model.Services;
-using Microsoft.AspNetCore.Http;
-
-namespace Emby.Server.Implementations.HttpServer
-{
-    public interface IHttpListener : IDisposable
-    {
-        /// <summary>
-        /// Gets or sets the error handler.
-        /// </summary>
-        /// <value>The error handler.</value>
-        Func<Exception, IRequest, bool, bool, Task> ErrorHandler { get; set; }
-
-        /// <summary>
-        /// Gets or sets the request handler.
-        /// </summary>
-        /// <value>The request handler.</value>
-        Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; }
-
-        /// <summary>
-        /// Gets or sets the web socket handler.
-        /// </summary>
-        /// <value>The web socket handler.</value>
-        Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
-
-        /// <summary>
-        /// Stops this instance.
-        /// </summary>
-        Task Stop();
-
-        Task ProcessWebSocketRequest(HttpContext ctx);
-    }
-}

+ 107 - 108
Emby.Server.Implementations/HttpServer/WebSocketConnection.cs

@@ -1,15 +1,16 @@
 using System;
+using System.Buffers;
+using System.IO.Pipelines;
+using System.Net;
 using System.Net.WebSockets;
-using System.Text;
+using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
-using Emby.Server.Implementations.Net;
+using MediaBrowser.Common.Json;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Serialization;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Logging;
-using UtfUnknown;
 
 namespace Emby.Server.Implementations.HttpServer
 {
@@ -24,54 +25,46 @@ namespace Emby.Server.Implementations.HttpServer
         private readonly ILogger _logger;
 
         /// <summary>
-        /// The json serializer.
+        /// The json serializer options.
         /// </summary>
-        private readonly IJsonSerializer _jsonSerializer;
+        private readonly JsonSerializerOptions _jsonOptions;
 
         /// <summary>
         /// The socket.
         /// </summary>
-        private readonly IWebSocket _socket;
+        private readonly WebSocket _socket;
+
+        private bool _disposed = false;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="WebSocketConnection" /> class.
         /// </summary>
         /// <param name="socket">The socket.</param>
         /// <param name="remoteEndPoint">The remote end point.</param>
-        /// <param name="jsonSerializer">The json serializer.</param>
         /// <param name="logger">The logger.</param>
         /// <exception cref="ArgumentNullException">socket</exception>
-        public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger)
+        public WebSocketConnection(ILogger<WebSocketConnection> logger, WebSocket socket, IPAddress remoteEndPoint)
         {
             if (socket == null)
             {
                 throw new ArgumentNullException(nameof(socket));
             }
 
-            if (string.IsNullOrEmpty(remoteEndPoint))
+            if (remoteEndPoint != null)
             {
                 throw new ArgumentNullException(nameof(remoteEndPoint));
             }
 
-            if (jsonSerializer == null)
-            {
-                throw new ArgumentNullException(nameof(jsonSerializer));
-            }
-
             if (logger == null)
             {
                 throw new ArgumentNullException(nameof(logger));
             }
 
-            Id = Guid.NewGuid();
-            _jsonSerializer = jsonSerializer;
             _socket = socket;
-            _socket.OnReceiveBytes = OnReceiveInternal;
-
             RemoteEndPoint = remoteEndPoint;
             _logger = logger;
 
-            socket.Closed += OnSocketClosed;
+            _jsonOptions = JsonDefaults.GetOptions();
         }
 
         /// <inheritdoc />
@@ -80,7 +73,7 @@ namespace Emby.Server.Implementations.HttpServer
         /// <summary>
         /// Gets or sets the remote end point.
         /// </summary>
-        public string RemoteEndPoint { get; private set; }
+        public IPAddress RemoteEndPoint { get; private set; }
 
         /// <summary>
         /// Gets or sets the receive action.
@@ -94,12 +87,6 @@ namespace Emby.Server.Implementations.HttpServer
         /// <value>The last activity date.</value>
         public DateTime LastActivityDate { get; private set; }
 
-        /// <summary>
-        /// Gets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        public Guid Id { get; private set; }
-
         /// <summary>
         /// Gets or sets the URL.
         /// </summary>
@@ -118,46 +105,78 @@ namespace Emby.Server.Implementations.HttpServer
         /// <value>The state.</value>
         public WebSocketState State => _socket.State;
 
-        void OnSocketClosed(object sender, EventArgs e)
-        {
-            Closed?.Invoke(this, EventArgs.Empty);
-        }
-
         /// <summary>
-        /// Called when [receive].
+        /// Sends a message asynchronously.
         /// </summary>
-        /// <param name="bytes">The bytes.</param>
-        private void OnReceiveInternal(byte[] bytes)
+        /// <typeparam name="T"></typeparam>
+        /// <param name="message">The message.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="ArgumentNullException">message</exception>
+        public Task SendAsync<T>(WebSocketMessage<T> message, CancellationToken cancellationToken)
         {
-            LastActivityDate = DateTime.UtcNow;
-
-            if (OnReceive == null)
+            if (message == null)
             {
-                return;
+                throw new ArgumentNullException(nameof(message));
             }
-            var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName;
 
-            if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase))
+            var json = JsonSerializer.SerializeToUtf8Bytes(message, _jsonOptions);
+            return _socket.SendAsync(json, WebSocketMessageType.Text, true, cancellationToken);
+        }
+
+        /// <inheritdoc />
+        public async Task ProcessAsync(CancellationToken cancellationToken = default)
+        {
+            var pipe = new Pipe();
+            var writer = pipe.Writer;
+
+            ValueWebSocketReceiveResult receiveresult;
+            do
             {
-                OnReceiveInternal(Encoding.UTF8.GetString(bytes, 0, bytes.Length));
-            }
-            else
+                // Allocate at least 512 bytes from the PipeWriter
+                Memory<byte> memory = writer.GetMemory(512);
+
+                receiveresult = await _socket.ReceiveAsync(memory, cancellationToken);
+                int bytesRead = receiveresult.Count;
+                if (bytesRead == 0)
+                {
+                    continue;
+                }
+
+                // Tell the PipeWriter how much was read from the Socket
+                writer.Advance(bytesRead);
+
+                // Make the data available to the PipeReader
+                FlushResult flushResult = await writer.FlushAsync();
+                if (flushResult.IsCompleted)
+                {
+                    // The PipeReader stopped reading
+                    break;
+                }
+
+                if (receiveresult.EndOfMessage)
+                {
+                    await ProcessInternal(pipe.Reader).ConfigureAwait(false);
+                }
+            } while (_socket.State == WebSocketState.Open && receiveresult.MessageType != WebSocketMessageType.Close);
+
+            if (_socket.State == WebSocketState.Open)
             {
-                OnReceiveInternal(Encoding.ASCII.GetString(bytes, 0, bytes.Length));
+                await _socket.CloseAsync(
+                    WebSocketCloseStatus.NormalClosure,
+                    string.Empty, // REVIEW: human readable explanation as to why the connection is closed.
+                    cancellationToken).ConfigureAwait(false);
             }
+
+            Closed?.Invoke(this, EventArgs.Empty);
+
+            _socket.Dispose();
         }
 
-        private void OnReceiveInternal(string message)
+        private async Task ProcessInternal(PipeReader reader)
         {
             LastActivityDate = DateTime.UtcNow;
 
-            if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase))
-            {
-                // This info is useful sometimes but also clogs up the log
-                _logger.LogDebug("Received web socket message that is not a json structure: {message}", message);
-                return;
-            }
-
             if (OnReceive == null)
             {
                 return;
@@ -165,74 +184,47 @@ namespace Emby.Server.Implementations.HttpServer
 
             try
             {
-                var stub = (WebSocketMessage<object>)_jsonSerializer.DeserializeFromString(message, typeof(WebSocketMessage<object>));
+                var result = await reader.ReadAsync().ConfigureAwait(false);
+                if (!result.IsCompleted)
+                {
+                    return;
+                }
+
+                WebSocketMessage<object> stub;
+                var buffer = result.Buffer;
+                if (buffer.IsSingleSegment)
+                {
+                    stub = JsonSerializer.Deserialize<WebSocketMessage<object>>(buffer.FirstSpan, _jsonOptions);
+                }
+                else
+                {
+                    var buf = ArrayPool<byte>.Shared.Rent(Convert.ToInt32(buffer.Length));
+                    try
+                    {
+                        buffer.CopyTo(buf);
+                        stub = JsonSerializer.Deserialize<WebSocketMessage<object>>(buf, _jsonOptions);
+                    }
+                    finally
+                    {
+                        ArrayPool<byte>.Shared.Return(buf);
+                    }
+                }
 
                 var info = new WebSocketMessageInfo
                 {
                     MessageType = stub.MessageType,
-                    Data = stub.Data?.ToString(),
+                    Data = stub.Data.ToString(),
                     Connection = this
                 };
 
-                OnReceive(info);
+                await OnReceive(info).ConfigureAwait(false);
             }
-            catch (Exception ex)
+            catch (JsonException ex)
             {
                 _logger.LogError(ex, "Error processing web socket message");
             }
         }
 
-        /// <summary>
-        /// Sends a message asynchronously.
-        /// </summary>
-        /// <typeparam name="T"></typeparam>
-        /// <param name="message">The message.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="ArgumentNullException">message</exception>
-        public Task SendAsync<T>(WebSocketMessage<T> message, CancellationToken cancellationToken)
-        {
-            if (message == null)
-            {
-                throw new ArgumentNullException(nameof(message));
-            }
-
-            var json = _jsonSerializer.SerializeToString(message);
-
-            return SendAsync(json, cancellationToken);
-        }
-
-        /// <summary>
-        /// Sends a message asynchronously.
-        /// </summary>
-        /// <param name="buffer">The buffer.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public Task SendAsync(byte[] buffer, CancellationToken cancellationToken)
-        {
-            if (buffer == null)
-            {
-                throw new ArgumentNullException(nameof(buffer));
-            }
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            return _socket.SendAsync(buffer, true, cancellationToken);
-        }
-
-        /// <inheritdoc />
-        public Task SendAsync(string text, CancellationToken cancellationToken)
-        {
-            if (string.IsNullOrEmpty(text))
-            {
-                throw new ArgumentNullException(nameof(text));
-            }
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            return _socket.SendAsync(text, true, cancellationToken);
-        }
-
         /// <inheritdoc />
         public void Dispose()
         {
@@ -246,10 +238,17 @@ namespace Emby.Server.Implementations.HttpServer
         /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
         protected virtual void Dispose(bool dispose)
         {
+            if (_disposed)
+            {
+                return;
+            }
+
             if (dispose)
             {
                 _socket.Dispose();
             }
+
+            _disposed = true;
         }
     }
 }

+ 0 - 48
Emby.Server.Implementations/Net/IWebSocket.cs

@@ -1,48 +0,0 @@
-using System;
-using System.Net.WebSockets;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Emby.Server.Implementations.Net
-{
-    /// <summary>
-    /// Interface IWebSocket
-    /// </summary>
-    public interface IWebSocket : IDisposable
-    {
-        /// <summary>
-        /// Occurs when [closed].
-        /// </summary>
-        event EventHandler<EventArgs> Closed;
-
-        /// <summary>
-        /// Gets or sets the state.
-        /// </summary>
-        /// <value>The state.</value>
-        WebSocketState State { get; }
-
-        /// <summary>
-        /// Gets or sets the receive action.
-        /// </summary>
-        /// <value>The receive action.</value>
-        Action<byte[]> OnReceiveBytes { get; set; }
-
-        /// <summary>
-        /// Sends the async.
-        /// </summary>
-        /// <param name="bytes">The bytes.</param>
-        /// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken);
-
-        /// <summary>
-        /// Sends the asynchronous.
-        /// </summary>
-        /// <param name="text">The text.</param>
-        /// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken);
-    }
-}

+ 0 - 31
Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs

@@ -1,31 +0,0 @@
-using System;
-using System.Net.WebSockets;
-using MediaBrowser.Model.Services;
-using Microsoft.AspNetCore.Http;
-
-namespace Emby.Server.Implementations.Net
-{
-    public class WebSocketConnectEventArgs : EventArgs
-    {
-        /// <summary>
-        /// Gets or sets the URL.
-        /// </summary>
-        /// <value>The URL.</value>
-        public string Url { get; set; }
-        /// <summary>
-        /// Gets or sets the query string.
-        /// </summary>
-        /// <value>The query string.</value>
-        public IQueryCollection QueryString { get; set; }
-        /// <summary>
-        /// Gets or sets the web socket.
-        /// </summary>
-        /// <value>The web socket.</value>
-        public IWebSocket WebSocket { get; set; }
-        /// <summary>
-        /// Gets or sets the endpoint.
-        /// </summary>
-        /// <value>The endpoint.</value>
-        public string Endpoint { get; set; }
-    }
-}

+ 0 - 191
Emby.Server.Implementations/Session/HttpSessionController.cs

@@ -1,191 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.Session;
-
-namespace Emby.Server.Implementations.Session
-{
-    public class HttpSessionController : ISessionController
-    {
-        private readonly IHttpClient _httpClient;
-        private readonly IJsonSerializer _json;
-        private readonly ISessionManager _sessionManager;
-
-        public SessionInfo Session { get; private set; }
-
-        private readonly string _postUrl;
-
-        public HttpSessionController(IHttpClient httpClient,
-            IJsonSerializer json,
-            SessionInfo session,
-            string postUrl, ISessionManager sessionManager)
-        {
-            _httpClient = httpClient;
-            _json = json;
-            Session = session;
-            _postUrl = postUrl;
-            _sessionManager = sessionManager;
-        }
-
-        private string PostUrl => string.Format("http://{0}{1}", Session.RemoteEndPoint, _postUrl);
-
-        public bool IsSessionActive => (DateTime.UtcNow - Session.LastActivityDate).TotalMinutes <= 5;
-
-        public bool SupportsMediaControl => true;
-
-        private Task SendMessage(string name, string messageId, CancellationToken cancellationToken)
-        {
-            return SendMessage(name, messageId, new Dictionary<string, string>(), cancellationToken);
-        }
-
-        private Task SendMessage(string name, string messageId, Dictionary<string, string> args, CancellationToken cancellationToken)
-        {
-            args["messageId"] = messageId;
-            var url = PostUrl + "/" + name + ToQueryString(args);
-
-            return SendRequest(new HttpRequestOptions
-            {
-                Url = url,
-                CancellationToken = cancellationToken,
-                BufferContent = false
-            });
-        }
-
-        private Task SendPlayCommand(PlayRequest command, string messageId, CancellationToken cancellationToken)
-        {
-            var dict = new Dictionary<string, string>();
-
-            dict["ItemIds"] = string.Join(",", command.ItemIds.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray());
-
-            if (command.StartPositionTicks.HasValue)
-            {
-                dict["StartPositionTicks"] = command.StartPositionTicks.Value.ToString(CultureInfo.InvariantCulture);
-            }
-            if (command.AudioStreamIndex.HasValue)
-            {
-                dict["AudioStreamIndex"] = command.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture);
-            }
-            if (command.SubtitleStreamIndex.HasValue)
-            {
-                dict["SubtitleStreamIndex"] = command.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture);
-            }
-            if (command.StartIndex.HasValue)
-            {
-                dict["StartIndex"] = command.StartIndex.Value.ToString(CultureInfo.InvariantCulture);
-            }
-            if (!string.IsNullOrEmpty(command.MediaSourceId))
-            {
-                dict["MediaSourceId"] = command.MediaSourceId;
-            }
-
-            return SendMessage(command.PlayCommand.ToString(), messageId, dict, cancellationToken);
-        }
-
-        private Task SendPlaystateCommand(PlaystateRequest command, string messageId, CancellationToken cancellationToken)
-        {
-            var args = new Dictionary<string, string>();
-
-            if (command.Command == PlaystateCommand.Seek)
-            {
-                if (!command.SeekPositionTicks.HasValue)
-                {
-                    throw new ArgumentException("SeekPositionTicks cannot be null");
-                }
-
-                args["SeekPositionTicks"] = command.SeekPositionTicks.Value.ToString(CultureInfo.InvariantCulture);
-            }
-
-            return SendMessage(command.Command.ToString(), messageId, args, cancellationToken);
-        }
-
-        private string[] _supportedMessages = Array.Empty<string>();
-        public Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken)
-        {
-            if (!IsSessionActive)
-            {
-                return Task.CompletedTask;
-            }
-
-            if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase))
-            {
-                return SendPlayCommand(data as PlayRequest, messageId, cancellationToken);
-            }
-            if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
-            {
-                return SendPlaystateCommand(data as PlaystateRequest, messageId, cancellationToken);
-            }
-            if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
-            {
-                var command = data as GeneralCommand;
-                return SendMessage(command.Name, messageId, command.Arguments, cancellationToken);
-            }
-
-            if (!_supportedMessages.Contains(name, StringComparer.OrdinalIgnoreCase))
-            {
-                return Task.CompletedTask;
-            }
-
-            var url = PostUrl + "/" + name;
-
-            url += "?messageId=" + messageId;
-
-            var options = new HttpRequestOptions
-            {
-                Url = url,
-                CancellationToken = cancellationToken,
-                BufferContent = false
-            };
-
-            if (data != null)
-            {
-                if (typeof(T) == typeof(string))
-                {
-                    var str = data as string;
-                    if (!string.IsNullOrEmpty(str))
-                    {
-                        options.RequestContent = str;
-                        options.RequestContentType = "application/json";
-                    }
-                }
-                else
-                {
-                    options.RequestContent = _json.SerializeToString(data);
-                    options.RequestContentType = "application/json";
-                }
-            }
-
-            return SendRequest(options);
-        }
-
-        private async Task SendRequest(HttpRequestOptions options)
-        {
-            using (var response = await _httpClient.Post(options).ConfigureAwait(false))
-            {
-
-            }
-        }
-
-        private static string ToQueryString(Dictionary<string, string> nvc)
-        {
-            var array = (from item in nvc
-                         select string.Format("{0}={1}", WebUtility.UrlEncode(item.Key), WebUtility.UrlEncode(item.Value)))
-                .ToArray();
-
-            var args = string.Join("&", array);
-
-            if (string.IsNullOrEmpty(args))
-            {
-                return args;
-            }
-
-            return "?" + args;
-        }
-    }
-}

+ 6 - 7
Emby.Server.Implementations/Session/SessionManager.cs

@@ -463,8 +463,7 @@ namespace Emby.Server.Implementations.Session
                 Client = appName,
                 DeviceId = deviceId,
                 ApplicationVersion = appVersion,
-                Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture),
-                ServerId = _appHost.SystemId
+                Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture)
             };
 
             var username = user?.Name;
@@ -1024,12 +1023,12 @@ namespace Emby.Server.Implementations.Session
 
         private static async Task SendMessageToSession<T>(SessionInfo session, string name, T data, CancellationToken cancellationToken)
         {
-            var controllers = session.SessionControllers.ToArray();
-            var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
+            var controllers = session.SessionControllers;
+            var messageId = Guid.NewGuid();
 
             foreach (var controller in controllers)
             {
-                await controller.SendMessage(name, messageId, data, controllers, cancellationToken).ConfigureAwait(false);
+                await controller.SendMessage(name, messageId, data, cancellationToken).ConfigureAwait(false);
             }
         }
 
@@ -1037,13 +1036,13 @@ namespace Emby.Server.Implementations.Session
         {
             IEnumerable<Task> GetTasks()
             {
-                var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
+                var messageId = Guid.NewGuid();
                 foreach (var session in sessions)
                 {
                     var controllers = session.SessionControllers;
                     foreach (var controller in controllers)
                     {
-                        yield return controller.SendMessage(name, messageId, data, controllers, cancellationToken);
+                        yield return controller.SendMessage(name, messageId, data, cancellationToken);
                     }
                 }
             }

+ 18 - 18
Emby.Server.Implementations/Session/SessionWebSocketListener.cs

@@ -3,7 +3,6 @@ using System.Threading.Tasks;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Serialization;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Logging;
 
@@ -12,7 +11,7 @@ namespace Emby.Server.Implementations.Session
     /// <summary>
     /// Class SessionWebSocketListener
     /// </summary>
-    public class SessionWebSocketListener : IWebSocketListener, IDisposable
+    public sealed class SessionWebSocketListener : IWebSocketListener, IDisposable
     {
         /// <summary>
         /// The _session manager
@@ -23,35 +22,34 @@ namespace Emby.Server.Implementations.Session
         /// The _logger
         /// </summary>
         private readonly ILogger _logger;
-
-        /// <summary>
-        /// The _dto service
-        /// </summary>
-        private readonly IJsonSerializer _json;
+        private readonly ILoggerFactory _loggerFactory;
 
         private readonly IHttpServer _httpServer;
 
-
         /// <summary>
         /// Initializes a new instance of the <see cref="SessionWebSocketListener" /> class.
         /// </summary>
+        /// <param name="logger">The logger.</param>
         /// <param name="sessionManager">The session manager.</param>
         /// <param name="loggerFactory">The logger factory.</param>
-        /// <param name="json">The json.</param>
         /// <param name="httpServer">The HTTP server.</param>
-        public SessionWebSocketListener(ISessionManager sessionManager, ILoggerFactory loggerFactory, IJsonSerializer json, IHttpServer httpServer)
+        public SessionWebSocketListener(
+            ILogger<SessionWebSocketListener> logger,
+            ISessionManager sessionManager,
+            ILoggerFactory loggerFactory,
+            IHttpServer httpServer)
         {
+            _logger = logger;
             _sessionManager = sessionManager;
-            _logger = loggerFactory.CreateLogger(GetType().Name);
-            _json = json;
+            _loggerFactory = loggerFactory;
             _httpServer = httpServer;
-            httpServer.WebSocketConnected += _serverManager_WebSocketConnected;
+
+            httpServer.WebSocketConnected += OnServerManagerWebSocketConnected;
         }
 
-        void _serverManager_WebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e)
+        private void OnServerManagerWebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e)
         {
-            var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint);
-
+            var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint.ToString());
             if (session != null)
             {
                 EnsureController(session, e.Argument);
@@ -79,9 +77,10 @@ namespace Emby.Server.Implementations.Session
             return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint);
         }
 
+        /// <inheritdoc />
         public void Dispose()
         {
-            _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected;
+            _httpServer.WebSocketConnected -= OnServerManagerWebSocketConnected;
         }
 
         /// <summary>
@@ -94,7 +93,8 @@ namespace Emby.Server.Implementations.Session
 
         private void EnsureController(SessionInfo session, IWebSocketConnection connection)
         {
-            var controllerInfo = session.EnsureController<WebSocketController>(s => new WebSocketController(s, _logger, _sessionManager));
+            var controllerInfo = session.EnsureController<WebSocketController>(
+                s => new WebSocketController(_loggerFactory.CreateLogger<WebSocketController>(), s, _sessionManager));
 
             var controller = (WebSocketController)controllerInfo.Item1;
             controller.AddWebSocket(connection);

+ 41 - 29
Emby.Server.Implementations/Session/WebSocketController.cs

@@ -11,60 +11,64 @@ using Microsoft.Extensions.Logging;
 
 namespace Emby.Server.Implementations.Session
 {
-    public class WebSocketController : ISessionController, IDisposable
+    public sealed class WebSocketController : ISessionController, IDisposable
     {
-        public SessionInfo Session { get; private set; }
-        public IReadOnlyList<IWebSocketConnection> Sockets { get; private set; }
-
         private readonly ILogger _logger;
-
         private readonly ISessionManager _sessionManager;
+        private readonly SessionInfo _session;
 
-        public WebSocketController(SessionInfo session, ILogger logger, ISessionManager sessionManager)
+        private List<IWebSocketConnection> _sockets;
+        private bool _disposed = false;
+
+        public WebSocketController(
+            ILogger<WebSocketController> logger,
+            SessionInfo session,
+            ISessionManager sessionManager)
         {
-            Session = session;
             _logger = logger;
+            _session = session;
             _sessionManager = sessionManager;
-            Sockets = new List<IWebSocketConnection>();
+            _sockets = new List<IWebSocketConnection>();
         }
 
         private bool HasOpenSockets => GetActiveSockets().Any();
 
+        /// <inheritdoc />
         public bool SupportsMediaControl => HasOpenSockets;
 
+        /// <inheritdoc />
         public bool IsSessionActive => HasOpenSockets;
 
         private IEnumerable<IWebSocketConnection> GetActiveSockets()
-        {
-            return Sockets
-                .OrderByDescending(i => i.LastActivityDate)
-                .Where(i => i.State == WebSocketState.Open);
-        }
+            => _sockets.Where(i => i.State == WebSocketState.Open);
 
+        /// <inheritdoc />
         public void AddWebSocket(IWebSocketConnection connection)
         {
-            var sockets = Sockets.ToList();
-            sockets.Add(connection);
+            _logger.LogDebug("Adding websocket to session {Session}", _session.Id);
+            _sockets.Add(connection);
 
-            Sockets = sockets;
-
-            connection.Closed += connection_Closed;
+            connection.Closed += OnConnectionClosed;
         }
 
-        void connection_Closed(object sender, EventArgs e)
+        private void OnConnectionClosed(object sender, EventArgs e)
         {
+            _logger.LogDebug("Removing websocket from session {Session}", _session.Id);
             var connection = (IWebSocketConnection)sender;
-            var sockets = Sockets.ToList();
-            sockets.Remove(connection);
-
-            Sockets = sockets;
-
-            _sessionManager.CloseIfNeeded(Session);
+            _sockets.Remove(connection);
+            _sessionManager.CloseIfNeeded(_session);
+            connection.Dispose();
         }
 
-        public Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken)
+        /// <inheritdoc />
+        public Task SendMessage<T>(
+            string name,
+            Guid messageId,
+            T data,
+            CancellationToken cancellationToken)
         {
             var socket = GetActiveSockets()
+                .OrderByDescending(i => i.LastActivityDate)
                 .FirstOrDefault();
 
             if (socket == null)
@@ -77,16 +81,24 @@ namespace Emby.Server.Implementations.Session
                 Data = data,
                 MessageType = name,
                 MessageId = messageId
-
             }, cancellationToken);
         }
 
+        /// <inheritdoc />
         public void Dispose()
         {
-            foreach (var socket in Sockets.ToList())
+            if (_disposed)
             {
-                socket.Closed -= connection_Closed;
+                return;
             }
+
+            foreach (var socket in _sockets)
+            {
+                socket.Closed -= OnConnectionClosed;
+                socket.Dispose();
+            }
+
+            _disposed = true;
         }
     }
 }

+ 0 - 105
Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs

@@ -1,105 +0,0 @@
-using System;
-using System.Net.WebSockets;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Emby.Server.Implementations.Net;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations.SocketSharp
-{
-    public class SharpWebSocket : IWebSocket
-    {
-        /// <summary>
-        /// The logger
-        /// </summary>
-        private readonly ILogger _logger;
-
-        public event EventHandler<EventArgs> Closed;
-
-        /// <summary>
-        /// Gets or sets the web socket.
-        /// </summary>
-        /// <value>The web socket.</value>
-        private readonly WebSocket _webSocket;
-
-        private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
-        private bool _disposed;
-
-        public SharpWebSocket(WebSocket socket, ILogger logger)
-        {
-            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
-            _webSocket = socket ?? throw new ArgumentNullException(nameof(socket));
-        }
-
-        /// <summary>
-        /// Gets the state.
-        /// </summary>
-        /// <value>The state.</value>
-        public WebSocketState State => _webSocket.State;
-
-        /// <summary>
-        /// Sends the async.
-        /// </summary>
-        /// <param name="bytes">The bytes.</param>
-        /// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken)
-        {
-            return _webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Binary, endOfMessage, cancellationToken);
-        }
-
-        /// <summary>
-        /// Sends the asynchronous.
-        /// </summary>
-        /// <param name="text">The text.</param>
-        /// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken)
-        {
-            return _webSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(text)), WebSocketMessageType.Text, endOfMessage, cancellationToken);
-        }
-
-        /// <summary>
-        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
-        /// </summary>
-        public void Dispose()
-        {
-            Dispose(true);
-            GC.SuppressFinalize(this);
-        }
-
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected virtual void Dispose(bool dispose)
-        {
-            if (_disposed)
-            {
-                return;
-            }
-
-            if (dispose)
-            {
-                _cancellationTokenSource.Cancel();
-                if (_webSocket.State == WebSocketState.Open)
-                {
-                    _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client",
-                        CancellationToken.None);
-                }
-                Closed?.Invoke(this, EventArgs.Empty);
-            }
-
-            _disposed = true;
-        }
-
-        /// <summary>
-        /// Gets or sets the receive action.
-        /// </summary>
-        /// <value>The receive action.</value>
-        public Action<byte[]> OnReceiveBytes { get; set; }
-    }
-}

+ 0 - 138
Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs

@@ -1,138 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.WebSockets;
-using System.Threading;
-using System.Threading.Tasks;
-using Emby.Server.Implementations.HttpServer;
-using Emby.Server.Implementations.Net;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Services;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Http.Extensions;
-using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
-
-namespace Emby.Server.Implementations.SocketSharp
-{
-    public class WebSocketSharpListener : IHttpListener
-    {
-        private readonly ILogger _logger;
-
-        private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
-        private CancellationToken _disposeCancellationToken;
-
-        public WebSocketSharpListener(
-            ILogger logger)
-        {
-            _logger = logger;
-
-            _disposeCancellationToken = _disposeCancellationTokenSource.Token;
-        }
-
-        public Func<Exception, IRequest, bool, bool, Task> ErrorHandler { get; set; }
-        public Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; }
-
-        public Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
-
-        private static void LogRequest(ILogger logger, HttpRequest request)
-        {
-            var url = request.GetDisplayUrl();
-
-            logger.LogInformation("WS {Url}. UserAgent: {UserAgent}", url, request.Headers[HeaderNames.UserAgent].ToString());
-        }
-
-        public async Task ProcessWebSocketRequest(HttpContext ctx)
-        {
-            try
-            {
-                LogRequest(_logger, ctx.Request);
-                var endpoint = ctx.Connection.RemoteIpAddress.ToString();
-                var url = ctx.Request.GetDisplayUrl();
-
-                var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
-                var socket = new SharpWebSocket(webSocketContext, _logger);
-
-                WebSocketConnected(new WebSocketConnectEventArgs
-                {
-                    Url = url,
-                    QueryString = ctx.Request.Query,
-                    WebSocket = socket,
-                    Endpoint = endpoint
-                });
-
-                WebSocketReceiveResult result;
-                var message = new List<byte>();
-
-                do
-                {
-                    var buffer = WebSocket.CreateServerBuffer(4096);
-                    result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken);
-                    message.AddRange(buffer.Array.Take(result.Count));
-
-                    if (result.EndOfMessage)
-                    {
-                        socket.OnReceiveBytes(message.ToArray());
-                        message.Clear();
-                    }
-                } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close);
-
-
-                if (webSocketContext.State == WebSocketState.Open)
-                {
-                    await webSocketContext.CloseAsync(
-                        result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
-                        result.CloseStatusDescription,
-                        _disposeCancellationToken).ConfigureAwait(false);
-                }
-
-                socket.Dispose();
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "AcceptWebSocketAsync error");
-                if (!ctx.Response.HasStarted)
-                {
-                    ctx.Response.StatusCode = 500;
-                }
-            }
-        }
-
-        public Task Stop()
-        {
-            _disposeCancellationTokenSource.Cancel();
-            return Task.CompletedTask;
-        }
-
-        /// <summary>
-        /// Releases the unmanaged resources and disposes of the managed resources used.
-        /// </summary>
-        public void Dispose()
-        {
-            Dispose(true);
-            GC.SuppressFinalize(this);
-        }
-
-        private bool _disposed;
-
-        /// <summary>
-        /// Releases the unmanaged resources and disposes of the managed resources used.
-        /// </summary>
-        /// <param name="disposing">Whether or not the managed resources should be disposed.</param>
-        protected virtual void Dispose(bool disposing)
-        {
-            if (_disposed)
-            {
-                return;
-            }
-
-            if (disposing)
-            {
-                Stop().GetAwaiter().GetResult();
-            }
-
-            _disposed = true;
-        }
-    }
-}

+ 0 - 2
Jellyfin.Server/Startup.cs

@@ -3,7 +3,6 @@ using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
 
@@ -64,7 +63,6 @@ namespace Jellyfin.Server
             app.UseResponseCompression();
 
             // TODO app.UseMiddleware<WebSocketMiddleware>();
-            app.Use(serverApplicationHost.ExecuteWebsocketHandlerAsync);
 
             // TODO use when old API is removed: app.UseAuthentication();
             app.UseJellyfinApiSwagger();

+ 22 - 21
MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs

@@ -31,46 +31,46 @@ namespace MediaBrowser.Api.Session
         {
             _sessionManager = sessionManager;
 
-            _sessionManager.SessionStarted += _sessionManager_SessionStarted;
-            _sessionManager.SessionEnded += _sessionManager_SessionEnded;
-            _sessionManager.PlaybackStart += _sessionManager_PlaybackStart;
-            _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped;
-            _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress;
-            _sessionManager.CapabilitiesChanged += _sessionManager_CapabilitiesChanged;
-            _sessionManager.SessionActivity += _sessionManager_SessionActivity;
+            _sessionManager.SessionStarted += OnSessionManagerSessionStarted;
+            _sessionManager.SessionEnded += OnSessionManagerSessionEnded;
+            _sessionManager.PlaybackStart += OnSessionManagerPlaybackStart;
+            _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped;
+            _sessionManager.PlaybackProgress += OnSessionManagerPlaybackProgress;
+            _sessionManager.CapabilitiesChanged += OnSessionManagerCapabilitiesChanged;
+            _sessionManager.SessionActivity += OnSessionManagerSessionActivity;
         }
 
-        void _sessionManager_SessionActivity(object sender, SessionEventArgs e)
+        private void OnSessionManagerSessionActivity(object sender, SessionEventArgs e)
         {
             SendData(false);
         }
 
-        void _sessionManager_CapabilitiesChanged(object sender, SessionEventArgs e)
+        private void OnSessionManagerCapabilitiesChanged(object sender, SessionEventArgs e)
         {
             SendData(true);
         }
 
-        void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
+        private void OnSessionManagerPlaybackProgress(object sender, PlaybackProgressEventArgs e)
         {
             SendData(!e.IsAutomated);
         }
 
-        void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)
+        private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e)
         {
             SendData(true);
         }
 
-        void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
+        private void OnSessionManagerPlaybackStart(object sender, PlaybackProgressEventArgs e)
         {
             SendData(true);
         }
 
-        void _sessionManager_SessionEnded(object sender, SessionEventArgs e)
+        private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e)
         {
             SendData(true);
         }
 
-        void _sessionManager_SessionStarted(object sender, SessionEventArgs e)
+        private void OnSessionManagerSessionStarted(object sender, SessionEventArgs e)
         {
             SendData(true);
         }
@@ -84,15 +84,16 @@ namespace MediaBrowser.Api.Session
             return Task.FromResult(_sessionManager.Sessions);
         }
 
+        /// <inheritdoc />
         protected override void Dispose(bool dispose)
         {
-            _sessionManager.SessionStarted -= _sessionManager_SessionStarted;
-            _sessionManager.SessionEnded -= _sessionManager_SessionEnded;
-            _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart;
-            _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped;
-            _sessionManager.PlaybackProgress -= _sessionManager_PlaybackProgress;
-            _sessionManager.CapabilitiesChanged -= _sessionManager_CapabilitiesChanged;
-            _sessionManager.SessionActivity -= _sessionManager_SessionActivity;
+            _sessionManager.SessionStarted -= OnSessionManagerSessionStarted;
+            _sessionManager.SessionEnded -= OnSessionManagerSessionEnded;
+            _sessionManager.PlaybackStart -= OnSessionManagerPlaybackStart;
+            _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped;
+            _sessionManager.PlaybackProgress -= OnSessionManagerPlaybackProgress;
+            _sessionManager.CapabilitiesChanged -= OnSessionManagerCapabilitiesChanged;
+            _sessionManager.SessionActivity -= OnSessionManagerSessionActivity;
 
             base.Dispose(dispose);
         }

+ 0 - 2
MediaBrowser.Controller/IServerApplicationHost.cs

@@ -92,7 +92,5 @@ namespace MediaBrowser.Controller
         string ReverseVirtualPath(string path);
 
         Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next);
-
-        Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next);
     }
 }

+ 0 - 1
MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs

@@ -154,7 +154,6 @@ namespace MediaBrowser.Controller.Net
                     {
                         MessageType = Name,
                         Data = data
-
                     }, cancellationToken).ConfigureAwait(false);
 
                     state.DateLastSendUtc = DateTime.UtcNow;

+ 2 - 20
MediaBrowser.Controller/Net/IHttpServer.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Services;
@@ -19,11 +18,6 @@ namespace MediaBrowser.Controller.Net
         /// <value>The URL prefix.</value>
         string[] UrlPrefixes { get; }
 
-        /// <summary>
-        /// Stops this instance.
-        /// </summary>
-        void Stop();
-
         /// <summary>
         /// Occurs when [web socket connected].
         /// </summary>
@@ -39,23 +33,11 @@ namespace MediaBrowser.Controller.Net
         /// </summary>
         string GlobalResponse { get; set; }
 
-        /// <summary>
-        /// Sends the http context to the socket listener
-        /// </summary>
-        /// <param name="ctx"></param>
-        /// <returns></returns>
-        Task ProcessWebSocketRequest(HttpContext ctx);
-
         /// <summary>
         /// The HTTP request handler
         /// </summary>
-        /// <param name="httpReq"></param>
-        /// <param name="urlString"></param>
-        /// <param name="host"></param>
-        /// <param name="localPath"></param>
-        /// <param name="cancellationToken"></param>
+        /// <param name="context"></param>
         /// <returns></returns>
-        Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath,
-            CancellationToken cancellationToken);
+        Task RequestHandler(HttpContext context);
     }
 }

+ 4 - 24
MediaBrowser.Controller/Net/IWebSocketConnection.cs

@@ -1,9 +1,9 @@
 using System;
+using System.Net;
 using System.Net.WebSockets;
 using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Services;
 using Microsoft.AspNetCore.Http;
 
 namespace MediaBrowser.Controller.Net
@@ -15,12 +15,6 @@ namespace MediaBrowser.Controller.Net
         /// </summary>
         event EventHandler<EventArgs> Closed;
 
-        /// <summary>
-        /// Gets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        Guid Id { get; }
-
         /// <summary>
         /// Gets the last activity date.
         /// </summary>
@@ -32,6 +26,7 @@ namespace MediaBrowser.Controller.Net
         /// </summary>
         /// <value>The URL.</value>
         string Url { get; set; }
+
         /// <summary>
         /// Gets or sets the query string.
         /// </summary>
@@ -54,7 +49,7 @@ namespace MediaBrowser.Controller.Net
         /// Gets the remote end point.
         /// </summary>
         /// <value>The remote end point.</value>
-        string RemoteEndPoint { get; }
+        IPAddress RemoteEndPoint { get; }
 
         /// <summary>
         /// Sends a message asynchronously.
@@ -66,21 +61,6 @@ namespace MediaBrowser.Controller.Net
         /// <exception cref="ArgumentNullException">message</exception>
         Task SendAsync<T>(WebSocketMessage<T> message, CancellationToken cancellationToken);
 
-        /// <summary>
-        /// Sends a message asynchronously.
-        /// </summary>
-        /// <param name="buffer">The buffer.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        Task SendAsync(byte[] buffer, CancellationToken cancellationToken);
-
-        /// <summary>
-        /// Sends a message asynchronously.
-        /// </summary>
-        /// <param name="text">The text.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="ArgumentNullException">buffer</exception>
-        Task SendAsync(string text, CancellationToken cancellationToken);
+        Task ProcessAsync(CancellationToken cancellationToken = default);
     }
 }

+ 2 - 1
MediaBrowser.Controller/Session/ISessionController.cs

@@ -1,3 +1,4 @@
+using System;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -20,6 +21,6 @@ namespace MediaBrowser.Controller.Session
         /// <summary>
         /// Sends the message.
         /// </summary>
-        Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken);
+        Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken);
     }
 }

+ 24 - 56
MediaBrowser.Controller/Session/SessionInfo.cs

@@ -10,13 +10,23 @@ using Microsoft.Extensions.Logging;
 namespace MediaBrowser.Controller.Session
 {
     /// <summary>
-    /// Class SessionInfo
+    /// Class SessionInfo.
     /// </summary>
-    public class SessionInfo : IDisposable
+    public sealed class SessionInfo : IDisposable
     {
-        private ISessionManager _sessionManager;
+        // 1 second
+        private const long ProgressIncrement = 10000000;
+
+        private readonly ISessionManager _sessionManager;
         private readonly ILogger _logger;
 
+
+        private readonly object _progressLock = new object();
+        private Timer _progressTimer;
+        private PlaybackProgressInfo _lastProgressInfo;
+
+        private bool _disposed = false;
+
         public SessionInfo(ISessionManager sessionManager, ILogger logger)
         {
             _sessionManager = sessionManager;
@@ -97,8 +107,6 @@ namespace MediaBrowser.Controller.Session
         /// <value>The name of the device.</value>
         public string DeviceName { get; set; }
 
-        public string DeviceType { get; set; }
-
         /// <summary>
         /// Gets or sets the now playing item.
         /// </summary>
@@ -126,28 +134,6 @@ namespace MediaBrowser.Controller.Session
         [JsonIgnore]
         public ISessionController[] SessionControllers { get; set; }
 
-        /// <summary>
-        /// Gets or sets the application icon URL.
-        /// </summary>
-        /// <value>The application icon URL.</value>
-        public string AppIconUrl { get; set; }
-
-        /// <summary>
-        /// Gets or sets the supported commands.
-        /// </summary>
-        /// <value>The supported commands.</value>
-        public string[] SupportedCommands
-        {
-            get
-            {
-                if (Capabilities == null)
-                {
-                    return new string[] { };
-                }
-                return Capabilities.SupportedCommands;
-            }
-        }
-
         public TranscodingInfo TranscodingInfo { get; set; }
 
         /// <summary>
@@ -219,6 +205,14 @@ namespace MediaBrowser.Controller.Session
             }
         }
 
+        public QueueItem[] NowPlayingQueue { get; set; }
+
+        public bool HasCustomDeviceName { get; set; }
+
+        public string PlaylistItemId { get; set; }
+
+        public string UserPrimaryImageTag { get; set; }
+
         public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory)
         {
             var controllers = SessionControllers.ToList();
@@ -267,10 +261,6 @@ namespace MediaBrowser.Controller.Session
             return false;
         }
 
-        private readonly object _progressLock = new object();
-        private Timer _progressTimer;
-        private PlaybackProgressInfo _lastProgressInfo;
-
         public void StartAutomaticProgress(PlaybackProgressInfo progressInfo)
         {
             if (_disposed)
@@ -293,9 +283,6 @@ namespace MediaBrowser.Controller.Session
             }
         }
 
-        // 1 second
-        private const long ProgressIncrement = 10000000;
-
         private async void OnProgressTimerCallback(object state)
         {
             if (_disposed)
@@ -354,8 +341,7 @@ namespace MediaBrowser.Controller.Session
             }
         }
 
-        private bool _disposed = false;
-
+        /// <inheritdoc />
         public void Dispose()
         {
             _disposed = true;
@@ -367,30 +353,12 @@ namespace MediaBrowser.Controller.Session
 
             foreach (var controller in controllers)
             {
-                var disposable = controller as IDisposable;
-
-                if (disposable != null)
+                if (controller is IDisposable disposable)
                 {
                     _logger.LogDebug("Disposing session controller {0}", disposable.GetType().Name);
-
-                    try
-                    {
-                        disposable.Dispose();
-                    }
-                    catch (Exception ex)
-                    {
-                        _logger.LogError(ex, "Error disposing session controller");
-                    }
+                    disposable.Dispose();
                 }
             }
-
-            _sessionManager = null;
         }
-
-        public QueueItem[] NowPlayingQueue { get; set; }
-        public bool HasCustomDeviceName { get; set; }
-        public string PlaylistItemId { get; set; }
-        public string ServerId { get; set; }
-        public string UserPrimaryImageTag { get; set; }
     }
 }

+ 6 - 2
MediaBrowser.Model/Net/WebSocketMessage.cs

@@ -1,3 +1,5 @@
+using System;
+
 namespace MediaBrowser.Model.Net
 {
     /// <summary>
@@ -11,13 +13,15 @@ namespace MediaBrowser.Model.Net
         /// </summary>
         /// <value>The type of the message.</value>
         public string MessageType { get; set; }
-        public string MessageId { get; set; }
+
+        public Guid MessageId { get; set; }
+
         public string ServerId { get; set; }
+
         /// <summary>
         /// Gets or sets the data.
         /// </summary>
         /// <value>The data.</value>
         public T Data { get; set; }
     }
-
 }