using System; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; 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 { /// /// Class WebSocketConnection. /// public class WebSocketConnection : IWebSocketConnection { /// /// The logger. /// private readonly ILogger _logger; /// /// The json serializer. /// private readonly IJsonSerializer _jsonSerializer; /// /// The socket. /// private readonly IWebSocket _socket; /// /// Initializes a new instance of the class. /// /// The socket. /// The remote end point. /// The json serializer. /// The logger. /// socket public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger) { if (socket == null) { throw new ArgumentNullException(nameof(socket)); } if (string.IsNullOrEmpty(remoteEndPoint)) { 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; } /// public event EventHandler Closed; /// /// Gets or sets the remote end point. /// public string RemoteEndPoint { get; private set; } /// /// Gets or sets the receive action. /// /// The receive action. public Func OnReceive { get; set; } /// /// Gets the last activity date. /// /// The last activity date. public DateTime LastActivityDate { get; private set; } /// public DateTime LastKeepAliveDate { get; set; } /// /// Gets the id. /// /// The id. public Guid Id { get; private set; } /// /// Gets or sets the URL. /// /// The URL. public string Url { get; set; } /// /// Gets or sets the query string. /// /// The query string. public IQueryCollection QueryString { get; set; } /// /// Gets the state. /// /// The state. public WebSocketState State => _socket.State; void OnSocketClosed(object sender, EventArgs e) { Closed?.Invoke(this, EventArgs.Empty); } /// /// Called when [receive]. /// /// The bytes. private void OnReceiveInternal(byte[] bytes) { LastActivityDate = DateTime.UtcNow; if (OnReceive == null) { return; } var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName; if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase)) { OnReceiveInternal(Encoding.UTF8.GetString(bytes, 0, bytes.Length)); } else { OnReceiveInternal(Encoding.ASCII.GetString(bytes, 0, bytes.Length)); } } private void OnReceiveInternal(string message) { 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; } try { var stub = (WebSocketMessage)_jsonSerializer.DeserializeFromString(message, typeof(WebSocketMessage)); var info = new WebSocketMessageInfo { MessageType = stub.MessageType, Data = stub.Data?.ToString(), Connection = this }; if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal)) { SendKeepAliveResponse(); } else { OnReceive?.Invoke(info); } } catch (Exception ex) { _logger.LogError(ex, "Error processing web socket message"); } } /// /// Sends a message asynchronously. /// /// /// The message. /// The cancellation token. /// Task. /// message public Task SendAsync(WebSocketMessage message, CancellationToken cancellationToken) { if (message == null) { throw new ArgumentNullException(nameof(message)); } var json = _jsonSerializer.SerializeToString(message); return SendAsync(json, cancellationToken); } /// /// Sends a message asynchronously. /// /// The buffer. /// The cancellation token. /// Task. public Task SendAsync(byte[] buffer, CancellationToken cancellationToken) { if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } cancellationToken.ThrowIfCancellationRequested(); return _socket.SendAsync(buffer, true, cancellationToken); } /// public Task SendAsync(string text, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(text)) { throw new ArgumentNullException(nameof(text)); } cancellationToken.ThrowIfCancellationRequested(); return _socket.SendAsync(text, true, cancellationToken); } private Task SendKeepAliveResponse() { LastKeepAliveDate = DateTime.UtcNow; return SendAsync(new WebSocketMessage { MessageType = "KeepAlive" }, CancellationToken.None); } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { if (dispose) { _socket.Dispose(); } } } }