From a33c8dc97b06e75eb2980b82d475d37f12d7e398 Mon Sep 17 00:00:00 2001 From: lofcz Date: Mon, 9 Jan 2023 03:12:48 +0100 Subject: [PATCH 1/3] Update SocketIO from https://github.com/doghappy/socket.io-client-csharp/commit/5dbafea2b517907b8f408748c2b9b439bda7200f to: https://github.com/doghappy/socket.io-client-csharp/commit/88ef0c733779e4a9bbf1115b84e657a775f14f87 --- ElectronSharp.API/BridgeConnector.cs | 2 +- ElectronSharp.API/SocketIO/EngineIO.cs | 8 + .../Exceptions/ConnectionException.cs | 9 + .../SocketIO/Exceptions/TransportException.cs | 11 + .../CancellationTokenSourceExtensions.cs | 5 +- .../Extensions/EventHandlerExtensions.cs | 15 + .../SocketIO/Messages/BinaryMessage.cs | 4 +- .../SocketIO/Messages/ClientAckMessage.cs | 4 +- .../Messages/ClientBinaryAckMessage.cs | 4 +- .../SocketIO/Messages/ConnectedMessage.cs | 14 +- .../SocketIO/Messages/DisconnectedMessage.cs | 2 +- .../SocketIO/Messages/ErrorMessage.cs | 4 +- .../SocketIO/Messages/EventMessage.cs | 4 +- .../SocketIO/Messages/IJsonMessage.cs | 10 + .../SocketIO/Messages/IMessage.cs | 2 +- .../SocketIO/Messages/MessageFactory.cs | 8 +- .../SocketIO/Messages/OpenedMessage.cs | 2 +- .../SocketIO/Messages/PingMessage.cs | 2 +- .../SocketIO/Messages/PongMessage.cs | 2 +- .../SocketIO/Messages/ServerAckMessage.cs | 2 +- .../Messages/ServerBinaryAckMessage.cs | 4 +- ElectronSharp.API/SocketIO/SocketIO.cs | 349 ++++++++++-------- ElectronSharp.API/SocketIO/SocketIOOptions.cs | 7 +- .../SocketIO/Transport/BaseTransport.cs | 111 +++--- .../Transport/Http/DefaultHttpClient.cs | 59 +++ .../{ => Http}/Eio3HttpPollingHandler.cs | 23 +- .../{ => Http}/Eio4HttpPollingHandler.cs | 18 +- .../{ => Http}/HttpPollingHandler.cs | 62 ++-- .../SocketIO/Transport/Http/HttpTransport.cs | 145 ++++++++ .../SocketIO/Transport/Http/IHttpClient.cs | 16 + .../{ => Http}/IHttpPollingHandler.cs | 11 +- .../SocketIO/Transport/HttpTransport.cs | 121 ------ .../SystemNetWebSocketsClientWebSocket.cs | 143 ------- .../Transport/TransportMessageType.cs | 5 +- .../SocketIO/Transport/TransportOptions.cs | 13 + .../SocketIO/Transport/WebSocketTransport.cs | 92 ----- .../Transport/WebSockets/ChunkSize.cs | 10 + .../{ => WebSockets}/IClientWebSocket.cs | 9 +- .../SystemNetWebSocketsClientWebSocket.cs | 115 ++++++ .../WebSockets/WebSocketReceiveResult.cs | 10 + .../Transport/WebSockets/WebSocketState.cs | 13 + .../WebSockets/WebSocketTransport.cs | 241 ++++++++++++ .../SocketIO/UriConverters/IUriConverter.cs | 10 - .../SocketIO/UriConverters/UriConverter.cs | 6 +- 44 files changed, 1037 insertions(+), 670 deletions(-) create mode 100644 ElectronSharp.API/SocketIO/EngineIO.cs create mode 100644 ElectronSharp.API/SocketIO/Exceptions/ConnectionException.cs create mode 100644 ElectronSharp.API/SocketIO/Exceptions/TransportException.cs create mode 100644 ElectronSharp.API/SocketIO/Messages/IJsonMessage.cs create mode 100644 ElectronSharp.API/SocketIO/Transport/Http/DefaultHttpClient.cs rename ElectronSharp.API/SocketIO/Transport/{ => Http}/Eio3HttpPollingHandler.cs (76%) rename ElectronSharp.API/SocketIO/Transport/{ => Http}/Eio4HttpPollingHandler.cs (75%) rename ElectronSharp.API/SocketIO/Transport/{ => Http}/HttpPollingHandler.cs (68%) create mode 100644 ElectronSharp.API/SocketIO/Transport/Http/HttpTransport.cs create mode 100644 ElectronSharp.API/SocketIO/Transport/Http/IHttpClient.cs rename ElectronSharp.API/SocketIO/Transport/{ => Http}/IHttpPollingHandler.cs (62%) delete mode 100644 ElectronSharp.API/SocketIO/Transport/HttpTransport.cs delete mode 100644 ElectronSharp.API/SocketIO/Transport/SystemNetWebSocketsClientWebSocket.cs create mode 100644 ElectronSharp.API/SocketIO/Transport/TransportOptions.cs delete mode 100644 ElectronSharp.API/SocketIO/Transport/WebSocketTransport.cs create mode 100644 ElectronSharp.API/SocketIO/Transport/WebSockets/ChunkSize.cs rename ElectronSharp.API/SocketIO/Transport/{ => WebSockets}/IClientWebSocket.cs (62%) create mode 100644 ElectronSharp.API/SocketIO/Transport/WebSockets/SystemNetWebSocketsClientWebSocket.cs create mode 100644 ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketReceiveResult.cs create mode 100644 ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketState.cs create mode 100644 ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketTransport.cs delete mode 100644 ElectronSharp.API/SocketIO/UriConverters/IUriConverter.cs diff --git a/ElectronSharp.API/BridgeConnector.cs b/ElectronSharp.API/BridgeConnector.cs index 6e0a2485..43178fd5 100644 --- a/ElectronSharp.API/BridgeConnector.cs +++ b/ElectronSharp.API/BridgeConnector.cs @@ -440,7 +440,7 @@ private static void EnsureSocketTaskIsCreated() { var socket = new SocketIO($"http://localhost:{BridgeSettings.SocketPort}", new SocketIOOptions() { - EIO = 4, + EIO = EngineIO.V4, Reconnection = true, ReconnectionAttempts = int.MaxValue, ReconnectionDelay = 500, diff --git a/ElectronSharp.API/SocketIO/EngineIO.cs b/ElectronSharp.API/SocketIO/EngineIO.cs new file mode 100644 index 00000000..1ba78c4e --- /dev/null +++ b/ElectronSharp.API/SocketIO/EngineIO.cs @@ -0,0 +1,8 @@ +namespace SocketIOClient +{ + public enum EngineIO + { + V3 = 3, + V4 = 4 + } +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Exceptions/ConnectionException.cs b/ElectronSharp.API/SocketIO/Exceptions/ConnectionException.cs new file mode 100644 index 00000000..e6b89c15 --- /dev/null +++ b/ElectronSharp.API/SocketIO/Exceptions/ConnectionException.cs @@ -0,0 +1,9 @@ +using System; + +namespace SocketIOClient +{ + public class ConnectionException : Exception + { + public ConnectionException(string message, Exception innerException) : base(message, innerException) { } + } +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Exceptions/TransportException.cs b/ElectronSharp.API/SocketIO/Exceptions/TransportException.cs new file mode 100644 index 00000000..97c97c3c --- /dev/null +++ b/ElectronSharp.API/SocketIO/Exceptions/TransportException.cs @@ -0,0 +1,11 @@ +using System; + +namespace SocketIOClient.Transport +{ + public class TransportException : Exception + { + public TransportException() : base() { } + public TransportException(string message) : base(message) { } + public TransportException(string message, Exception innerException) : base(message, innerException) { } + } +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Extensions/CancellationTokenSourceExtensions.cs b/ElectronSharp.API/SocketIO/Extensions/CancellationTokenSourceExtensions.cs index ff5ae99d..a7c4639b 100644 --- a/ElectronSharp.API/SocketIO/Extensions/CancellationTokenSourceExtensions.cs +++ b/ElectronSharp.API/SocketIO/Extensions/CancellationTokenSourceExtensions.cs @@ -12,7 +12,10 @@ public static void TryDispose(this CancellationTokenSource cts) public static void TryCancel(this CancellationTokenSource cts) { - cts?.Cancel(); + if (cts != null && !cts.IsCancellationRequested) + { + cts.Cancel(); + } } } } diff --git a/ElectronSharp.API/SocketIO/Extensions/EventHandlerExtensions.cs b/ElectronSharp.API/SocketIO/Extensions/EventHandlerExtensions.cs index b05b8f38..73ce4ffa 100644 --- a/ElectronSharp.API/SocketIO/Extensions/EventHandlerExtensions.cs +++ b/ElectronSharp.API/SocketIO/Extensions/EventHandlerExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace SocketIOClient.Extensions { @@ -13,5 +14,19 @@ public static void TryInvoke(this EventHandler handler, object sender, EventArgs { handler?.Invoke(sender, args); } + + public static void TryInvoke(this Action action, T arg1) + { + action?.Invoke(arg1); + } + + public static async Task TryInvokeAsync(this Func func, T arg1) + { + if (func is null) + { + return; + } + await func(arg1); + } } } diff --git a/ElectronSharp.API/SocketIO/Messages/BinaryMessage.cs b/ElectronSharp.API/SocketIO/Messages/BinaryMessage.cs index df2a7c00..3efb1c1f 100644 --- a/ElectronSharp.API/SocketIO/Messages/BinaryMessage.cs +++ b/ElectronSharp.API/SocketIO/Messages/BinaryMessage.cs @@ -5,7 +5,7 @@ namespace SocketIOClient.Messages { - public class BinaryMessage : IMessage + public class BinaryMessage : IJsonMessage { public MessageType Type => MessageType.BinaryMessage; @@ -21,7 +21,7 @@ public class BinaryMessage : IMessage public int BinaryCount { get; set; } - public int Eio { get; set; } + public EngineIO EIO { get; set; } public TransportProtocol Protocol { get; set; } diff --git a/ElectronSharp.API/SocketIO/Messages/ClientAckMessage.cs b/ElectronSharp.API/SocketIO/Messages/ClientAckMessage.cs index 2b4dcda6..8bc5829d 100644 --- a/ElectronSharp.API/SocketIO/Messages/ClientAckMessage.cs +++ b/ElectronSharp.API/SocketIO/Messages/ClientAckMessage.cs @@ -9,7 +9,7 @@ namespace SocketIOClient.Messages /// /// The server calls the client's callback /// - public class ClientAckMessage : IMessage + public class ClientAckMessage : IJsonMessage { public MessageType Type => MessageType.AckMessage; @@ -29,7 +29,7 @@ public class ClientAckMessage : IMessage public int BinaryCount { get; } - public int Eio { get; set; } + public EngineIO EIO { get; set; } public TransportProtocol Protocol { get; set; } diff --git a/ElectronSharp.API/SocketIO/Messages/ClientBinaryAckMessage.cs b/ElectronSharp.API/SocketIO/Messages/ClientBinaryAckMessage.cs index f1512683..0edbcef7 100644 --- a/ElectronSharp.API/SocketIO/Messages/ClientBinaryAckMessage.cs +++ b/ElectronSharp.API/SocketIO/Messages/ClientBinaryAckMessage.cs @@ -9,7 +9,7 @@ namespace SocketIOClient.Messages /// /// The server calls the client's callback with binary /// - public class ClientBinaryAckMessage : IMessage + public class ClientBinaryAckMessage : IJsonMessage { public MessageType Type => MessageType.BinaryAckMessage; @@ -25,7 +25,7 @@ public class ClientBinaryAckMessage : IMessage public int BinaryCount { get; set; } - public int Eio { get; set; } + public EngineIO EIO { get; set; } public TransportProtocol Protocol { get; set; } diff --git a/ElectronSharp.API/SocketIO/Messages/ConnectedMessage.cs b/ElectronSharp.API/SocketIO/Messages/ConnectedMessage.cs index 0663ad58..d23bca6a 100644 --- a/ElectronSharp.API/SocketIO/Messages/ConnectedMessage.cs +++ b/ElectronSharp.API/SocketIO/Messages/ConnectedMessage.cs @@ -20,7 +20,7 @@ public class ConnectedMessage : IMessage public int BinaryCount { get; } - public int Eio { get; set; } + public EngineIO EIO { get; set; } public TransportProtocol Protocol { get; set; } @@ -29,7 +29,7 @@ public class ConnectedMessage : IMessage public void Read(string msg) { - if (Eio == 3) + if (EIO == EngineIO.V3) { Eio3Read(msg); } @@ -41,14 +41,14 @@ public void Read(string msg) public string Write() { - if (Eio == 3) + if (EIO == EngineIO.V3) { return Eio3Write(); } return Eio4Write(); } - public void Eio4Read(string msg) + private void Eio4Read(string msg) { int index = msg.IndexOf('{'); if (index > 0) @@ -63,7 +63,7 @@ public void Eio4Read(string msg) Sid = JsonDocument.Parse(msg).RootElement.GetProperty("sid").GetString(); } - public string Eio4Write() + private string Eio4Write() { var builder = new StringBuilder("40"); if (!string.IsNullOrEmpty(Namespace)) @@ -74,7 +74,7 @@ public string Eio4Write() return builder.ToString(); } - public void Eio3Read(string msg) + private void Eio3Read(string msg) { if (msg.Length >= 2) { @@ -96,7 +96,7 @@ public void Eio3Read(string msg) } } - public string Eio3Write() + private string Eio3Write() { if (string.IsNullOrEmpty(Namespace)) { diff --git a/ElectronSharp.API/SocketIO/Messages/DisconnectedMessage.cs b/ElectronSharp.API/SocketIO/Messages/DisconnectedMessage.cs index 4bceba9b..ca0bdfc7 100644 --- a/ElectronSharp.API/SocketIO/Messages/DisconnectedMessage.cs +++ b/ElectronSharp.API/SocketIO/Messages/DisconnectedMessage.cs @@ -15,7 +15,7 @@ public class DisconnectedMessage : IMessage public int BinaryCount { get; } - public int Eio { get; set; } + public EngineIO EIO { get; set; } public TransportProtocol Protocol { get; set; } diff --git a/ElectronSharp.API/SocketIO/Messages/ErrorMessage.cs b/ElectronSharp.API/SocketIO/Messages/ErrorMessage.cs index f36d78b0..be081fa4 100644 --- a/ElectronSharp.API/SocketIO/Messages/ErrorMessage.cs +++ b/ElectronSharp.API/SocketIO/Messages/ErrorMessage.cs @@ -19,13 +19,13 @@ public class ErrorMessage : IMessage public int BinaryCount { get; } - public int Eio { get; set; } + public EngineIO EIO { get; set; } public TransportProtocol Protocol { get; set; } public void Read(string msg) { - if (Eio == 3) + if (EIO == EngineIO.V3) { Message = msg.Trim('"'); } diff --git a/ElectronSharp.API/SocketIO/Messages/EventMessage.cs b/ElectronSharp.API/SocketIO/Messages/EventMessage.cs index 522bd209..75b4060f 100644 --- a/ElectronSharp.API/SocketIO/Messages/EventMessage.cs +++ b/ElectronSharp.API/SocketIO/Messages/EventMessage.cs @@ -5,7 +5,7 @@ namespace SocketIOClient.Messages { - public class EventMessage : IMessage + public class EventMessage : IJsonMessage { public MessageType Type => MessageType.EventMessage; @@ -25,7 +25,7 @@ public class EventMessage : IMessage public int BinaryCount { get; } - public int Eio { get; set; } + public EngineIO EIO { get; set; } public TransportProtocol Protocol { get; set; } diff --git a/ElectronSharp.API/SocketIO/Messages/IJsonMessage.cs b/ElectronSharp.API/SocketIO/Messages/IJsonMessage.cs new file mode 100644 index 00000000..3428a223 --- /dev/null +++ b/ElectronSharp.API/SocketIO/Messages/IJsonMessage.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Text.Json; + +namespace SocketIOClient.Messages +{ + public interface IJsonMessage : IMessage + { + List JsonElements { get; } + } +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Messages/IMessage.cs b/ElectronSharp.API/SocketIO/Messages/IMessage.cs index c7f0e250..f5dd4ada 100644 --- a/ElectronSharp.API/SocketIO/Messages/IMessage.cs +++ b/ElectronSharp.API/SocketIO/Messages/IMessage.cs @@ -13,7 +13,7 @@ public interface IMessage int BinaryCount { get; } - int Eio { get; set; } + EngineIO EIO { get; set; } TransportProtocol Protocol { get; set; } diff --git a/ElectronSharp.API/SocketIO/Messages/MessageFactory.cs b/ElectronSharp.API/SocketIO/Messages/MessageFactory.cs index 227a6f72..1842fa05 100644 --- a/ElectronSharp.API/SocketIO/Messages/MessageFactory.cs +++ b/ElectronSharp.API/SocketIO/Messages/MessageFactory.cs @@ -37,7 +37,7 @@ private static IMessage CreateMessage(MessageType type) private static readonly Dictionary _messageTypes = Enum.GetValues().ToDictionary(v => ((int)v).ToString(), v => v); - public static IMessage CreateMessage(int eio, string msg) + public static IMessage CreateMessage(EngineIO eio, string msg) { foreach (var (prefix,item) in _messageTypes) { @@ -46,7 +46,7 @@ public static IMessage CreateMessage(int eio, string msg) IMessage result = CreateMessage(item); if (result != null) { - result.Eio = eio; + result.EIO = eio; result.Read(msg.Substring(prefix.Length)); return result; } @@ -60,12 +60,12 @@ public static OpenedMessage CreateOpenedMessage(string msg) var openedMessage = new OpenedMessage(); if (msg[0] == '0') { - openedMessage.Eio = 4; + openedMessage.EIO = EngineIO.V4; openedMessage.Read(msg.Substring(1)); } else { - openedMessage.Eio = 3; + openedMessage.EIO = EngineIO.V3; int index = msg.IndexOf(':'); openedMessage.Read(msg.Substring(index + 2)); } diff --git a/ElectronSharp.API/SocketIO/Messages/OpenedMessage.cs b/ElectronSharp.API/SocketIO/Messages/OpenedMessage.cs index df297eae..f4d1badc 100644 --- a/ElectronSharp.API/SocketIO/Messages/OpenedMessage.cs +++ b/ElectronSharp.API/SocketIO/Messages/OpenedMessage.cs @@ -25,7 +25,7 @@ public class OpenedMessage : IMessage public int BinaryCount { get; } - public int Eio { get; set; } + public EngineIO EIO { get; set; } public TransportProtocol Protocol { get; set; } diff --git a/ElectronSharp.API/SocketIO/Messages/PingMessage.cs b/ElectronSharp.API/SocketIO/Messages/PingMessage.cs index fa2b1346..78c83ce6 100644 --- a/ElectronSharp.API/SocketIO/Messages/PingMessage.cs +++ b/ElectronSharp.API/SocketIO/Messages/PingMessage.cs @@ -13,7 +13,7 @@ public class PingMessage : IMessage public int BinaryCount { get; } - public int Eio { get; set; } + public EngineIO EIO { get; set; } public TransportProtocol Protocol { get; set; } diff --git a/ElectronSharp.API/SocketIO/Messages/PongMessage.cs b/ElectronSharp.API/SocketIO/Messages/PongMessage.cs index fb4ccfa4..ba12306a 100644 --- a/ElectronSharp.API/SocketIO/Messages/PongMessage.cs +++ b/ElectronSharp.API/SocketIO/Messages/PongMessage.cs @@ -14,7 +14,7 @@ public class PongMessage : IMessage public int BinaryCount { get; } - public int Eio { get; set; } + public EngineIO EIO { get; set; } public TransportProtocol Protocol { get; set; } diff --git a/ElectronSharp.API/SocketIO/Messages/ServerAckMessage.cs b/ElectronSharp.API/SocketIO/Messages/ServerAckMessage.cs index ce8ce1ae..9f781841 100644 --- a/ElectronSharp.API/SocketIO/Messages/ServerAckMessage.cs +++ b/ElectronSharp.API/SocketIO/Messages/ServerAckMessage.cs @@ -23,7 +23,7 @@ public class ServerAckMessage : IMessage public int BinaryCount { get; } - public int Eio { get; set; } + public EngineIO EIO { get; set; } public TransportProtocol Protocol { get; set; } diff --git a/ElectronSharp.API/SocketIO/Messages/ServerBinaryAckMessage.cs b/ElectronSharp.API/SocketIO/Messages/ServerBinaryAckMessage.cs index 199f309f..5a0f14e7 100644 --- a/ElectronSharp.API/SocketIO/Messages/ServerBinaryAckMessage.cs +++ b/ElectronSharp.API/SocketIO/Messages/ServerBinaryAckMessage.cs @@ -8,7 +8,7 @@ namespace SocketIOClient.Messages /// /// The client calls the server's callback with binary /// - public class ServerBinaryAckMessage : IMessage + public class ServerBinaryAckMessage : IJsonMessage { public MessageType Type => MessageType.BinaryAckMessage; @@ -22,7 +22,7 @@ public class ServerBinaryAckMessage : IMessage public int BinaryCount { get; } - public int Eio { get; set; } + public EngineIO EIO { get; set; } public TransportProtocol Protocol { get; set; } diff --git a/ElectronSharp.API/SocketIO/SocketIO.cs b/ElectronSharp.API/SocketIO/SocketIO.cs index edf2f1c3..c8e4b943 100644 --- a/ElectronSharp.API/SocketIO/SocketIO.cs +++ b/ElectronSharp.API/SocketIO/SocketIO.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Net.Http; using System.Net.WebSockets; @@ -9,9 +10,13 @@ using SocketIOClient.JsonSerializer; using SocketIOClient.Messages; using SocketIOClient.Transport; +using SocketIOClient.Transport.Http; +using SocketIOClient.Transport.WebSockets; using SocketIOClient.UriConverters; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; + +#if DEBUG +using System.Diagnostics; +#endif namespace SocketIOClient { @@ -24,20 +29,26 @@ public class SocketIO : IDisposable /// Create SocketIO object with default options /// /// - public SocketIO(string uri) : this(new Uri(uri)) { } + public SocketIO(string uri) : this(new Uri(uri)) + { + } /// /// Create SocketIO object with options /// /// - public SocketIO(Uri uri) : this(uri, new SocketIOOptions()) { } + public SocketIO(Uri uri) : this(uri, new SocketIOOptions()) + { + } /// /// Create SocketIO object with options /// /// /// - public SocketIO(string uri, SocketIOOptions options) : this(new Uri(uri), options) { } + public SocketIO(string uri, SocketIOOptions options) : this(new Uri(uri), options) + { + } /// /// Create SocketIO object with options @@ -52,6 +63,7 @@ public SocketIO(Uri uri, SocketIOOptions options) } Uri _serverUri; + private Uri ServerUri { get => _serverUri; @@ -92,45 +104,28 @@ private Uri ServerUri public IJsonSerializer JsonSerializer { get; set; } - public IUriConverter UriConverter { get; set; } - - internal ILogger Logger { get; set; } - - ILoggerFactory _loggerFactory; - public ILoggerFactory LoggerFactory - { - get => _loggerFactory; - set - { - _loggerFactory = value ?? throw new ArgumentNullException(nameof(LoggerFactory)); - Logger = _loggerFactory.CreateLogger(); - } - } - public HttpClient HttpClient { get; set; } public Func ClientWebSocketProvider { get; set; } - private IClientWebSocket _clientWebsocket; + public Func HttpClientAdapterProvider { get; set; } + + List _resources = new List(); BaseTransport _transport; List _expectedExceptions; int _packetId; - bool _isConnectCoreRunning; - Uri _realServerUri; - Exception _connectCoreException; + Exception _backgroundException; Dictionary> _ackHandlers; List _onAnyHandlers; Dictionary> _eventHandlers; - CancellationTokenSource _connectionTokenSource; double _reconnectionDelay; - bool _hasError; - bool _isFaild; - readonly static object _connectionLock = new object(); #region Socket.IO event + public event EventHandler OnConnected; + //public event EventHandler OnConnectError; //public event EventHandler OnConnectTimeout; public event EventHandler OnError; @@ -155,14 +150,17 @@ public ILoggerFactory LoggerFactory /// Fired when couldn’t reconnect within reconnectionAttempts /// public event EventHandler OnReconnectFailed; + public event EventHandler OnPing; public event EventHandler OnPong; #endregion #region Observable Event + //Subject _onConnected; //public IObservable ConnectedObservable { get; private set; } + #endregion private void Initialize() @@ -173,10 +171,10 @@ private void Initialize() _onAnyHandlers = new List(); JsonSerializer = new SystemTextJsonSerializer(); - UriConverter = new UriConverter(); HttpClient = new HttpClient(); - ClientWebSocketProvider = () => new SystemNetWebSocketsClientWebSocket(Options.EIO); + ClientWebSocketProvider = () => new DefaultClientWebSocket(); + HttpClientAdapterProvider = () => new DefaultHttpClient(); _expectedExceptions = new List { typeof(TimeoutException), @@ -185,28 +183,53 @@ private void Initialize() typeof(OperationCanceledException), typeof(TaskCanceledException) }; - LoggerFactory = NullLoggerFactory.Instance; } - private async Task CreateTransportAsync() + private async Task InitTransportAsync() { Options.Transport = await GetProtocolAsync(); + var transportOptions = new TransportOptions + { + EIO = Options.EIO, + Query = Options.Query, + Auth = GetAuth(Options.Auth), + ConnectionTimeout = Options.ConnectionTimeout + }; if (Options.Transport == TransportProtocol.Polling) { - HttpPollingHandler handler; - if (Options.EIO == 3) - handler = new Eio3HttpPollingHandler(HttpClient); - else - handler = new Eio4HttpPollingHandler(HttpClient); - _transport = new HttpTransport(HttpClient, handler, Options, JsonSerializer, Logger); + var adapter = HttpClientAdapterProvider(); + if (adapter is null) + { + throw new ArgumentNullException(nameof(HttpClientAdapterProvider), $"{HttpClientAdapterProvider} returns a null"); + } + _resources.Add(adapter); + var handler = HttpPollingHandler.CreateHandler(transportOptions.EIO, adapter); + _transport = new HttpTransport(transportOptions, handler); } else { - _clientWebsocket = ClientWebSocketProvider(); - _transport = new WebSocketTransport(_clientWebsocket, Options, JsonSerializer, Logger); + var ws = ClientWebSocketProvider(); + if (ws is null) + { + throw new ArgumentNullException(nameof(ClientWebSocketProvider), $"{ClientWebSocketProvider} returns a null"); + } + _resources.Add(ws); + _transport = new WebSocketTransport(transportOptions, ws); } + _resources.Add(_transport); _transport.Namespace = _namespace; SetHeaders(); + _transport.SetProxy(Options.Proxy); + _transport.OnReceived = OnMessageReceived; + _transport.OnError = OnErrorReceived; + } + + private string GetAuth(object auth) + { + if (auth == null) + return string.Empty; + var result = JsonSerializer.Serialize(new[] { auth }); + return result.Json.TrimStart('[').TrimEnd(']'); } private void SetHeaders() @@ -215,84 +238,103 @@ private void SetHeaders() { foreach (var item in Options.ExtraHeaders) { - _transport.AddHeader(item.Key, item.Value); + try + { + _transport.AddHeader(item.Key, item.Value); + } + catch (Exception e) + { + OnErrorReceived(e); + } } } } - private void SyncExceptionToMain(Exception e) + private void DisposeResources() { - _connectCoreException = e; - _isConnectCoreRunning = false; + foreach (var item in _resources) + { + item.TryDispose(); + } + _resources.Clear(); } - private void ConnectCore() + private void ConnectInBackground(CancellationToken cancellationToken) { - DisposeForReconnect(); - _reconnectionDelay = Options.ReconnectionDelay; - _connectionTokenSource = new CancellationTokenSource(); - var cct = _connectionTokenSource.Token; Task.Factory.StartNew(async () => { while (true) { - _clientWebsocket.TryDispose(); - _transport.TryDispose(); - CreateTransportAsync().Wait(); - _realServerUri = UriConverter.GetServerUri(Options.Transport == TransportProtocol.WebSocket, ServerUri, Options.EIO, Options.Path, Options.Query); + if (cancellationToken.IsCancellationRequested) + break; + DisposeResources(); + await InitTransportAsync().ConfigureAwait(false); + var serverUri = UriConverter.GetServerUri(Options.Transport == TransportProtocol.WebSocket, ServerUri, Options.EIO, Options.Path, Options.Query); + if (_attempts > 0) + OnReconnectAttempt.TryInvoke(this, _attempts); try { - if (cct.IsCancellationRequested) + using (var cts = new CancellationTokenSource(Options.ConnectionTimeout)) + { + await _transport.ConnectAsync(serverUri, cts.Token).ConfigureAwait(false); break; - if (_attempts > 0) - OnReconnectAttempt.TryInvoke(this, _attempts); - var timeoutCts = new CancellationTokenSource(Options.ConnectionTimeout); - _transport.Subscribe(OnMessageReceived, OnErrorReceived); - await _transport.ConnectAsync(_realServerUri, timeoutCts.Token).ConfigureAwait(false); - break; + } } catch (Exception e) { - if (_expectedExceptions.Contains(e.GetType())) - { - if (!Options.Reconnection) - { - SyncExceptionToMain(e); - throw; - } - if (_attempts > 0) - { - OnReconnectError.TryInvoke(this, e); - } - _attempts++; - if (_attempts <= Options.ReconnectionAttempts) - { - if (_reconnectionDelay < Options.ReconnectionDelayMax) - { - _reconnectionDelay += 2 * Options.RandomizationFactor; - } - if (_reconnectionDelay > Options.ReconnectionDelayMax) - { - _reconnectionDelay = Options.ReconnectionDelayMax; - } - Thread.Sleep((int)_reconnectionDelay); - } - else - { - _isFaild = true; - OnReconnectFailed.TryInvoke(this, EventArgs.Empty); - break; - } - } - else - { - SyncExceptionToMain(e); - throw; - } + var needBreak = await AttemptAsync(e); + if (needBreak) break; + + var canHandle = CanHandleException(e); + if (!canHandle) throw e; } } - _isConnectCoreRunning = false; - }); + }, cancellationToken); + } + + private async Task AttemptAsync(Exception e) + { + if (_attempts > 0) + { + OnReconnectError.TryInvoke(this, e); + } + _attempts++; + if (_attempts <= Options.ReconnectionAttempts) + { + if (_reconnectionDelay < Options.ReconnectionDelayMax) + { + _reconnectionDelay += 2 * Options.RandomizationFactor; + } + if (_reconnectionDelay > Options.ReconnectionDelayMax) + { + _reconnectionDelay = Options.ReconnectionDelayMax; + } + await Task.Delay((int)_reconnectionDelay); + } + else + { + OnReconnectFailed.TryInvoke(this, EventArgs.Empty); + return true; + } + return false; + } + + private bool CanHandleException(Exception e) + { + if (_expectedExceptions.Contains(e.GetType())) + { + if (!Options.Reconnection) + { + _backgroundException = e; + return false; + } + } + else + { + _backgroundException = e; + return false; + } + return true; } private async Task GetProtocolAsync() @@ -310,52 +352,58 @@ private async Task GetProtocolAsync() } catch (Exception e) { - Logger.LogWarning(e, e.Message); +#if DEBUG + Debug.WriteLine(e); +#endif } } return Options.Transport; } - public async Task ConnectAsync() + private readonly SemaphoreSlim _connectingLock = new SemaphoreSlim(1, 1); + private CancellationTokenSource _connCts; + + private void ConnectInBackground() { - if (Connected || _isConnectCoreRunning) - return; + _connCts.TryCancel(); + _connCts.TryDispose(); + _connCts = new CancellationTokenSource(); + ConnectInBackground(_connCts.Token); + } - lock (_connectionLock) - { - if (_isConnectCoreRunning) - return; - _isConnectCoreRunning = true; - } - ConnectCore(); - while (_isConnectCoreRunning) - { - await Task.Delay(100); - } - if (_connectCoreException != null) - { - Logger.LogError(_connectCoreException, _connectCoreException.Message); - throw _connectCoreException; - } - int ms = 0; - while (!Connected) + public async Task ConnectAsync() + { + await _connectingLock.WaitAsync().ConfigureAwait(false); + try { - if (_hasError) - { - Logger.LogWarning($"Got a connection error, try to use '{nameof(OnError)}' to detect it."); - break; - } - if (_isFaild) - { - Logger.LogWarning($"Reconnect failed, try to use '{nameof(OnReconnectFailed)}' to detect it."); - break; - } - ms += 100; - if (ms > Options.ConnectionTimeout.TotalMilliseconds) + if (Connected) return; + + ConnectInBackground(); + + var ms = 0; + while (true) { - throw new TimeoutException(); + if (_connCts.IsCancellationRequested) + { + break; + } + + if (_backgroundException != null) + { + throw new ConnectionException($"Cannot connect to server '{ServerUri}'", _backgroundException); + } + + ms += 100; + if (ms > Options.ConnectionTimeout.TotalMilliseconds) + { + throw new ConnectionException($"Cannot connect to server '{ServerUri}'", new TimeoutException()); + } + await Task.Delay(100); } - await Task.Delay(100); + } + finally + { + _connectingLock.Release(); } } @@ -373,6 +421,7 @@ private void ConnectedHandler(ConnectedMessage msg) { Id = msg.Sid; Connected = true; + _connCts.Cancel(); OnConnected.TryInvoke(this, EventArgs.Empty); if (_attempts > 0) { @@ -414,7 +463,6 @@ private void AckMessageHandler(ClientAckMessage m) private void ErrorMessageHandler(ErrorMessage msg) { - _hasError = true; OnError.TryInvoke(this, msg.Message); } @@ -450,7 +498,7 @@ private void BinaryAckMessageHandler(ClientBinaryAckMessage msg) private void OnErrorReceived(Exception ex) { - Logger.LogError(ex, ex.Message); + //Logger.LogError(ex, ex.Message); _ = InvokeDisconnect(DisconnectReason.TransportClose); } @@ -491,7 +539,9 @@ private void OnMessageReceived(IMessage msg) } catch (Exception e) { - Logger.LogError(e, e.Message); +#if DEBUG + Debug.WriteLine(e); +#endif } } @@ -509,7 +559,9 @@ public async Task DisconnectAsync() } catch (Exception e) { - Logger.LogError(e, e.Message); +#if DEBUG + Debug.WriteLine(e); +#endif } await InvokeDisconnect(DisconnectReason.IOClientDisconnect); } @@ -530,7 +582,6 @@ public void On(string eventName, Action callback) } - /// /// Unregister a new handler for the given event. /// @@ -722,14 +773,16 @@ private async Task InvokeDisconnect(string reason) } catch (Exception e) { - Logger.LogError(e, e.Message); +#if DEBUG + Debug.WriteLine(e); +#endif } if (reason != DisconnectReason.IOServerDisconnect && reason != DisconnectReason.IOClientDisconnect) { //In the this cases (explicit disconnection), the client will not try to reconnect and you need to manually call socket.connect(). if (Options.Reconnection) { - ConnectCore(); + ConnectInBackground(); } } } @@ -743,18 +796,6 @@ public void AddExpectedException(Type type) } } - private void DisposeForReconnect() - { - _hasError = false; - _isFaild = false; - _packetId = -1; - _ackHandlers.Clear(); - _connectCoreException = null; - _hasError = false; - _connectionTokenSource.TryCancel(); - _connectionTokenSource.TryDispose(); - } - public void Dispose() { HttpClient.Dispose(); @@ -762,8 +803,6 @@ public void Dispose() _ackHandlers.Clear(); _onAnyHandlers.Clear(); _eventHandlers.Clear(); - _connectionTokenSource.TryCancel(); - _connectionTokenSource.TryDispose(); } } } \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/SocketIOOptions.cs b/ElectronSharp.API/SocketIO/SocketIOOptions.cs index dc154bb9..0229386b 100644 --- a/ElectronSharp.API/SocketIO/SocketIOOptions.cs +++ b/ElectronSharp.API/SocketIO/SocketIOOptions.cs @@ -1,6 +1,7 @@ using SocketIOClient.Transport; using System; using System.Collections.Generic; +using System.Net; namespace SocketIOClient { @@ -16,7 +17,7 @@ public SocketIOOptions() ConnectionTimeout = TimeSpan.FromSeconds(20); Reconnection = true; Transport = TransportProtocol.Polling; - EIO = 4; + EIO = EngineIO.V4; AutoUpgrade = true; } @@ -56,10 +57,12 @@ public double RandomizationFactor public TransportProtocol Transport { get; set; } - public int EIO { get; set; } + public EngineIO EIO { get; set; } public bool AutoUpgrade { get; set; } public object Auth { get; set; } + + public IWebProxy Proxy { get; set; } } } diff --git a/ElectronSharp.API/SocketIO/Transport/BaseTransport.cs b/ElectronSharp.API/SocketIO/Transport/BaseTransport.cs index efbdc644..336eb339 100644 --- a/ElectronSharp.API/SocketIO/Transport/BaseTransport.cs +++ b/ElectronSharp.API/SocketIO/Transport/BaseTransport.cs @@ -1,45 +1,45 @@ using System; +using System.Collections.Generic; +using System.Net; using System.Threading; using System.Threading.Tasks; -using System.Collections.Generic; -using System.Reactive.Subjects; -using Microsoft.Extensions.Logging; -using SocketIOClient.JsonSerializer; +using SocketIOClient.Extensions; using SocketIOClient.Messages; using SocketIOClient.UriConverters; +#if DEBUG +using System.Diagnostics; +#endif + namespace SocketIOClient.Transport { - public abstract class BaseTransport : IObserver, IObserver, IObservable, IDisposable + public abstract class BaseTransport : IDisposable { - public BaseTransport(SocketIOOptions options, IJsonSerializer jsonSerializer, ILogger logger) + protected BaseTransport(TransportOptions options) { - Options = options; - MessageSubject = new Subject(); - JsonSerializer = jsonSerializer; - UriConverter = new UriConverter(); + Options = options ?? throw new ArgumentNullException(nameof(options)); _messageQueue = new Queue(); - _logger = logger; } + protected const string DirtyMessage = "Invalid object's current state, may need to create a new object."; + DateTime _pingTime; readonly Queue _messageQueue; - readonly ILogger _logger; + protected TransportOptions Options { get; } - protected SocketIOOptions Options { get; } - protected Subject MessageSubject { get; } + public Action OnReceived { get; set; } - protected IJsonSerializer JsonSerializer { get; } + protected abstract TransportProtocol Protocol { get; } protected CancellationTokenSource PingTokenSource { get; private set; } protected OpenedMessage OpenedMessage { get; private set; } public string Namespace { get; set; } - public IUriConverter UriConverter { get; set; } + public Action OnError { get; set; } public async Task SendAsync(IMessage msg, CancellationToken cancellationToken) { - msg.Eio = Options.EIO; - msg.Protocol = Options.Transport; + msg.EIO = Options.EIO; + msg.Protocol = Protocol; var payload = new Payload { Text = msg.Write() @@ -54,22 +54,19 @@ public async Task SendAsync(IMessage msg, CancellationToken cancellationToken) protected virtual async Task OpenAsync(OpenedMessage msg) { OpenedMessage = msg; - if (Options.EIO == 3 && string.IsNullOrEmpty(Namespace)) + if (Options.EIO == EngineIO.V3 && string.IsNullOrEmpty(Namespace)) { return; } var connectMsg = new ConnectedMessage { Namespace = Namespace, - Eio = Options.EIO, + EIO = Options.EIO, Query = Options.Query, }; - if (Options.EIO == 4) + if (Options.EIO == EngineIO.V4) { - if (Options.Auth != null) - { - connectMsg.AuthJsonStr = JsonSerializer.Serialize(new[] { Options.Auth }).Json.TrimStart('[').TrimEnd(']'); - } + connectMsg.AuthJsonStr = Options.Auth; } for (int i = 1; i <= 3; i++) @@ -82,7 +79,7 @@ protected virtual async Task OpenAsync(OpenedMessage msg) catch (Exception e) { if (i == 3) - OnError(e); + OnError.TryInvoke(e); else await Task.Delay(TimeSpan.FromMilliseconds(Math.Pow(2, i) * 100)); } @@ -96,7 +93,7 @@ protected virtual async Task OpenAsync(OpenedMessage msg) /// private void StartPing(CancellationToken cancellationToken) { - _logger.LogDebug($"[Ping] Interval: {OpenedMessage.PingInterval}"); + // _logger.LogDebug($"[Ping] Interval: {OpenedMessage.PingInterval}"); Task.Factory.StartNew(async () => { while (!cancellationToken.IsCancellationRequested) @@ -109,16 +106,16 @@ private void StartPing(CancellationToken cancellationToken) try { var ping = new PingMessage(); - _logger.LogDebug($"[Ping] Sending"); + // _logger.LogDebug($"[Ping] Sending"); await SendAsync(ping, CancellationToken.None).ConfigureAwait(false); - _logger.LogDebug($"[Ping] Has been sent"); + // _logger.LogDebug($"[Ping] Has been sent"); _pingTime = DateTime.Now; - MessageSubject.OnNext(ping); + OnReceived.TryInvoke(ping); } catch (Exception e) { - _logger.LogDebug($"[Ping] Failed to send, {e.Message}"); - MessageSubject.OnError(e); + // _logger.LogDebug($"[Ping] Failed to send, {e.Message}"); + OnError.TryInvoke(e); break; } } @@ -130,10 +127,10 @@ private void StartPing(CancellationToken cancellationToken) public abstract Task DisconnectAsync(CancellationToken cancellationToken); public abstract void AddHeader(string key, string val); + public abstract void SetProxy(IWebProxy proxy); public virtual void Dispose() { - MessageSubject.Dispose(); _messageQueue.Clear(); if (PingTokenSource != null) { @@ -144,24 +141,17 @@ public virtual void Dispose() public abstract Task SendAsync(Payload payload, CancellationToken cancellationToken); - public void OnCompleted() - { - throw new NotImplementedException(); - } - - public void OnError(Exception error) - { - MessageSubject.OnError(error); - } - - public void OnNext(string text) + protected async Task OnTextReceived(string text) { - _logger.LogDebug($"[Receive] {text}"); +#if DEBUG + Debug.WriteLine($"[{Protocol}⬇] {text}"); +#endif var msg = MessageFactory.CreateMessage(Options.EIO, text); if (msg == null) { return; } + msg.Protocol = Protocol; if (msg.BinaryCount > 0) { msg.IncomingBytes = new List(msg.BinaryCount); @@ -170,10 +160,10 @@ public void OnNext(string text) } if (msg.Type == MessageType.Opened) { - OpenAsync(msg as OpenedMessage).ConfigureAwait(false); + await OpenAsync(msg as OpenedMessage).ConfigureAwait(false); } - if (Options.EIO == 3) + if (Options.EIO == EngineIO.V3) { if (msg.Type == MessageType.Connected) { @@ -200,46 +190,43 @@ public void OnNext(string text) } } - MessageSubject.OnNext(msg); + OnReceived.TryInvoke(msg); if (msg.Type == MessageType.Ping) { _pingTime = DateTime.Now; try { - SendAsync(new PongMessage(), CancellationToken.None).ConfigureAwait(false); - MessageSubject.OnNext(new PongMessage + await SendAsync(new PongMessage(), CancellationToken.None).ConfigureAwait(false); + OnReceived.TryInvoke(new PongMessage { - Eio = Options.EIO, - Protocol = Options.Transport, + EIO = Options.EIO, + Protocol = Protocol, Duration = DateTime.Now - _pingTime }); } catch (Exception e) { - OnError(e); + OnError.TryInvoke(e); } } } - public void OnNext(byte[] bytes) + protected void OnBinaryReceived(byte[] bytes) { - _logger.LogDebug($"[Receive] binary message"); +#if DEBUG + Debug.WriteLine($"[{Protocol} Receive] bytes"); +#endif if (_messageQueue.Count > 0) { var msg = _messageQueue.Peek(); msg.IncomingBytes.Add(bytes); if (msg.IncomingBytes.Count == msg.BinaryCount) { - MessageSubject.OnNext(msg); + OnReceived.TryInvoke(msg); _messageQueue.Dequeue(); } } } - - public IDisposable Subscribe(IObserver observer) - { - return MessageSubject.Subscribe(observer); - } } -} +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Transport/Http/DefaultHttpClient.cs b/ElectronSharp.API/SocketIO/Transport/Http/DefaultHttpClient.cs new file mode 100644 index 00000000..12ffc1ce --- /dev/null +++ b/ElectronSharp.API/SocketIO/Transport/Http/DefaultHttpClient.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace SocketIOClient.Transport.Http +{ + public class DefaultHttpClient : IHttpClient + { + public DefaultHttpClient() + { + _handler = new HttpClientHandler(); + _httpClient = new HttpClient(_handler); + } + + readonly HttpClientHandler _handler; + private readonly HttpClient _httpClient; + + private static readonly HashSet allowedHeaders = new HashSet + { + "user-agent", + }; + + public void AddHeader(string name, string value) + { + if (allowedHeaders.Contains(name.ToLower())) + { + _httpClient.DefaultRequestHeaders.TryAddWithoutValidation(name, value); + } + else + { + // _httpClient.DefaultRequestHeaders.UserAgent. + _httpClient.DefaultRequestHeaders.Add(name, value); + } + } + + public void SetProxy(IWebProxy proxy) + { + _handler.Proxy = proxy; + } + + public Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return _httpClient.SendAsync(request, cancellationToken); + } + + public Task PostAsync(string requestUri, HttpContent content, CancellationToken cancellationToken) + { + return _httpClient.PostAsync(requestUri, content, cancellationToken); + } + + public void Dispose() + { + _httpClient.Dispose(); + _handler.Dispose(); + } + } +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Transport/Eio3HttpPollingHandler.cs b/ElectronSharp.API/SocketIO/Transport/Http/Eio3HttpPollingHandler.cs similarity index 76% rename from ElectronSharp.API/SocketIO/Transport/Eio3HttpPollingHandler.cs rename to ElectronSharp.API/SocketIO/Transport/Http/Eio3HttpPollingHandler.cs index 8134c3f6..9f645627 100644 --- a/ElectronSharp.API/SocketIO/Transport/Eio3HttpPollingHandler.cs +++ b/ElectronSharp.API/SocketIO/Transport/Http/Eio3HttpPollingHandler.cs @@ -4,12 +4,15 @@ using System.Threading.Tasks; using System.Linq; using System.Net.Http.Headers; +using SocketIOClient.Extensions; -namespace SocketIOClient.Transport +namespace SocketIOClient.Transport.Http { public class Eio3HttpPollingHandler : HttpPollingHandler { - public Eio3HttpPollingHandler(HttpClient httpClient) : base(httpClient) { } + public Eio3HttpPollingHandler(IHttpClient adapter) : base(adapter) + { + } public override async Task PostAsync(string uri, IEnumerable bytes, CancellationToken cancellationToken) { @@ -28,7 +31,7 @@ public override async Task PostAsync(string uri, IEnumerable bytes, Canc await HttpClient.PostAsync(AppendRandom(uri), content, cancellationToken).ConfigureAwait(false); } - private List SplitInt(int number) + private static List SplitInt(int number) { List list = new List(); while (number > 0) @@ -40,7 +43,7 @@ private List SplitInt(int number) return list; } - protected override void ProduceText(string text) + protected override async Task ProduceText(string text) { int p = 0; while (true) @@ -53,7 +56,7 @@ protected override void ProduceText(string text) if (int.TryParse(text.Substring(p, index - p), out int length)) { string msg = text.Substring(index + 1, length); - TextSubject.OnNext(msg); + await OnTextReceived.TryInvokeAsync(msg); } else { @@ -67,10 +70,14 @@ protected override void ProduceText(string text) } } - public override Task PostAsync(string uri, string content, CancellationToken cancellationToken) + public override async Task PostAsync(string uri, string content, CancellationToken cancellationToken) { + if (string.IsNullOrEmpty(content)) + { + return; + } content = content.Length + ":" + content; - return base.PostAsync(uri, content, cancellationToken); + await base.PostAsync(uri, content, cancellationToken); } } -} +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Transport/Eio4HttpPollingHandler.cs b/ElectronSharp.API/SocketIO/Transport/Http/Eio4HttpPollingHandler.cs similarity index 75% rename from ElectronSharp.API/SocketIO/Transport/Eio4HttpPollingHandler.cs rename to ElectronSharp.API/SocketIO/Transport/Http/Eio4HttpPollingHandler.cs index a8b12089..abd29572 100644 --- a/ElectronSharp.API/SocketIO/Transport/Eio4HttpPollingHandler.cs +++ b/ElectronSharp.API/SocketIO/Transport/Http/Eio4HttpPollingHandler.cs @@ -1,17 +1,19 @@ using System; using System.Collections.Generic; -using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; +using SocketIOClient.Extensions; -namespace SocketIOClient.Transport +namespace SocketIOClient.Transport.Http { public class Eio4HttpPollingHandler : HttpPollingHandler { - public Eio4HttpPollingHandler(HttpClient httpClient) : base(httpClient) { } + public Eio4HttpPollingHandler(IHttpClient adapter) : base(adapter) + { + } - const char Separator = '\u001E'; //1E  + const char Separator = '\u001E'; public override async Task PostAsync(string uri, IEnumerable bytes, CancellationToken cancellationToken) { @@ -28,7 +30,7 @@ public override async Task PostAsync(string uri, IEnumerable bytes, Canc await PostAsync(uri, text, cancellationToken); } - protected override void ProduceText(string text) + protected override async Task ProduceText(string text) { string[] items = text.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries); foreach (var item in items) @@ -36,13 +38,13 @@ protected override void ProduceText(string text) if (item[0] == 'b') { byte[] bytes = Convert.FromBase64String(item.Substring(1)); - BytesSubject.OnNext(bytes); + OnBytes(bytes); } else { - TextSubject.OnNext(item); + await OnTextReceived.TryInvokeAsync(item); } } } } -} +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Transport/HttpPollingHandler.cs b/ElectronSharp.API/SocketIO/Transport/Http/HttpPollingHandler.cs similarity index 68% rename from ElectronSharp.API/SocketIO/Transport/HttpPollingHandler.cs rename to ElectronSharp.API/SocketIO/Transport/Http/HttpPollingHandler.cs index 48147dc8..c1c7179e 100644 --- a/ElectronSharp.API/SocketIO/Transport/HttpPollingHandler.cs +++ b/ElectronSharp.API/SocketIO/Transport/Http/HttpPollingHandler.cs @@ -1,37 +1,41 @@ using System; using System.Collections.Generic; +using System.Net; using System.Net.Http; -using System.Reactive.Linq; -using System.Reactive.Subjects; using System.Text; using System.Threading; using System.Threading.Tasks; +using SocketIOClient.Extensions; -namespace SocketIOClient.Transport +namespace SocketIOClient.Transport.Http { public abstract class HttpPollingHandler : IHttpPollingHandler { - public HttpPollingHandler(HttpClient httpClient) + protected HttpPollingHandler(IHttpClient adapter) { - HttpClient = httpClient; - TextSubject = new Subject(); - BytesSubject = new Subject(); - TextObservable = TextSubject.AsObservable(); - BytesObservable = BytesSubject.AsObservable(); + HttpClient = adapter ?? throw new ArgumentNullException(nameof(adapter)); } - protected HttpClient HttpClient { get; } - protected Subject TextSubject{get;} - protected Subject BytesSubject{get;} + protected IHttpClient HttpClient { get; } + public Func OnTextReceived { get; set; } + public Action OnBytesReceived { get; set; } - public IObservable TextObservable { get; } - public IObservable BytesObservable { get; } + public void AddHeader(string key, string val) + { + HttpClient.AddHeader(key, val); + } + + public void SetProxy(IWebProxy proxy) + { + HttpClient.SetProxy(proxy); + } - protected string AppendRandom(string uri) + protected static string AppendRandom(string uri) { return uri + "&t=" + DateTimeOffset.Now.ToUnixTimeSeconds(); } + public async Task GetAsync(string uri, CancellationToken cancellationToken) { var req = new HttpRequestMessage(HttpMethod.Get, AppendRandom(uri)); @@ -53,7 +57,7 @@ public async Task SendAsync(HttpRequestMessage req, CancellationToken cancellati await ProduceMessageAsync(resMsg).ConfigureAwait(false); } - public async virtual Task PostAsync(string uri, string content, CancellationToken cancellationToken) + public virtual async Task PostAsync(string uri, string content, CancellationToken cancellationToken) { var httpContent = new StringContent(content); var resMsg = await HttpClient.PostAsync(AppendRandom(uri), httpContent, cancellationToken).ConfigureAwait(false); @@ -67,18 +71,23 @@ private async Task ProduceMessageAsync(HttpResponseMessage resMsg) if (resMsg.Content.Headers.ContentType.MediaType == "application/octet-stream") { byte[] bytes = await resMsg.Content.ReadAsByteArrayAsync().ConfigureAwait(false); - ProduceBytes(bytes); + await ProduceBytes(bytes); } else { string text = await resMsg.Content.ReadAsStringAsync().ConfigureAwait(false); - ProduceText(text); + await ProduceText(text); } } - protected abstract void ProduceText(string text); + protected abstract Task ProduceText(string text); + + protected void OnBytes(byte[] bytes) + { + OnBytesReceived.TryInvoke(bytes); + } - private void ProduceBytes(byte[] bytes) + private async Task ProduceBytes(byte[] bytes) { int i = 0; while (bytes.Length > i + 4) @@ -97,22 +106,23 @@ private void ProduceBytes(byte[] bytes) { var buffer = new byte[length]; Buffer.BlockCopy(bytes, i, buffer, 0, buffer.Length); - TextSubject.OnNext(Encoding.UTF8.GetString(buffer)); + await OnTextReceived.TryInvokeAsync(Encoding.UTF8.GetString(buffer)); } else if (type == 1) { var buffer = new byte[length - 1]; Buffer.BlockCopy(bytes, i + 1, buffer, 0, buffer.Length); - BytesSubject.OnNext(buffer); + OnBytes(buffer); } i += length; } } - public void Dispose() + public static IHttpPollingHandler CreateHandler(EngineIO eio, IHttpClient adapter) { - TextSubject.Dispose(); - BytesSubject.Dispose(); + if (eio == EngineIO.V3) + return new Eio3HttpPollingHandler(adapter); + return new Eio4HttpPollingHandler(adapter); } } -} +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Transport/Http/HttpTransport.cs b/ElectronSharp.API/SocketIO/Transport/Http/HttpTransport.cs new file mode 100644 index 00000000..009b3b3a --- /dev/null +++ b/ElectronSharp.API/SocketIO/Transport/Http/HttpTransport.cs @@ -0,0 +1,145 @@ +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using SocketIOClient.Extensions; +using SocketIOClient.Messages; + +namespace SocketIOClient.Transport.Http +{ + public class HttpTransport : BaseTransport + { + public HttpTransport(TransportOptions options, IHttpPollingHandler pollingHandler) : base(options) + { + _pollingHandler = pollingHandler ?? throw new ArgumentNullException(nameof(pollingHandler)); + _pollingHandler.OnTextReceived = OnTextReceived; + _pollingHandler.OnBytesReceived = OnBinaryReceived; + _sendLock = new SemaphoreSlim(1, 1); + } + + bool _dirty; + string _httpUri; + readonly SemaphoreSlim _sendLock; + CancellationTokenSource _pollingTokenSource; + + private readonly IHttpPollingHandler _pollingHandler; + + protected override TransportProtocol Protocol => TransportProtocol.Polling; + + private void StartPolling(CancellationToken cancellationToken) + { + Task.Factory.StartNew(async () => + { + while (!cancellationToken.IsCancellationRequested) + { + // if (!_httpUri.Contains("&sid=")) + // { + // await Task.Delay(20, cancellationToken); + // continue; + // } + try + { + await _pollingHandler.GetAsync(_httpUri, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception e) + { + OnError(e); + break; + } + } + }, TaskCreationOptions.LongRunning); + } + + /// + /// + /// + /// + /// + /// + public override async Task ConnectAsync(Uri uri, CancellationToken cancellationToken) + { + if (_dirty) + throw new InvalidOperationException(DirtyMessage); + var req = new HttpRequestMessage(HttpMethod.Get, uri); + _httpUri = uri.ToString(); + + try + { + await _pollingHandler.SendAsync(req, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + throw new TransportException($"Could not connect to '{uri}'", e); + } + + _dirty = true; + } + + public override Task DisconnectAsync(CancellationToken cancellationToken) + { + _pollingTokenSource.Cancel(); + if (PingTokenSource != null) + { + PingTokenSource.Cancel(); + } + return Task.CompletedTask; + } + + public override void AddHeader(string key, string val) + { + _pollingHandler.AddHeader(key, val); + } + + public override void SetProxy(IWebProxy proxy) + { + if (_dirty) + { + throw new InvalidOperationException("Unable to set proxy after connecting"); + } + _pollingHandler.SetProxy(proxy); + } + + public override void Dispose() + { + base.Dispose(); + _pollingTokenSource.TryCancel(); + _pollingTokenSource.TryDispose(); + } + + public override async Task SendAsync(Payload payload, CancellationToken cancellationToken) + { + try + { + await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false); + if (!string.IsNullOrEmpty(payload.Text)) + { + await _pollingHandler.PostAsync(_httpUri, payload.Text, cancellationToken); +#if DEBUG + Debug.WriteLine($"[Polling⬆] {payload.Text}"); +#endif + } + if (payload.Bytes != null && payload.Bytes.Count > 0) + { + await _pollingHandler.PostAsync(_httpUri, payload.Bytes, cancellationToken); +#if DEBUG + Debug.WriteLine("[Polling⬆]0️⃣1️⃣0️⃣1️⃣"); +#endif + } + } + finally + { + _sendLock.Release(); + } + } + + protected override async Task OpenAsync(OpenedMessage msg) + { + _httpUri += "&sid=" + msg.Sid; + _pollingTokenSource = new CancellationTokenSource(); + StartPolling(_pollingTokenSource.Token); + await base.OpenAsync(msg); + } + } +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Transport/Http/IHttpClient.cs b/ElectronSharp.API/SocketIO/Transport/Http/IHttpClient.cs new file mode 100644 index 00000000..1514c99a --- /dev/null +++ b/ElectronSharp.API/SocketIO/Transport/Http/IHttpClient.cs @@ -0,0 +1,16 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace SocketIOClient.Transport.Http +{ + public interface IHttpClient : IDisposable + { + void AddHeader(string name, string value); + void SetProxy(IWebProxy proxy); + Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken); + Task PostAsync(string requestUri, HttpContent content, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Transport/IHttpPollingHandler.cs b/ElectronSharp.API/SocketIO/Transport/Http/IHttpPollingHandler.cs similarity index 62% rename from ElectronSharp.API/SocketIO/Transport/IHttpPollingHandler.cs rename to ElectronSharp.API/SocketIO/Transport/Http/IHttpPollingHandler.cs index f1170109..5a90d42d 100644 --- a/ElectronSharp.API/SocketIO/Transport/IHttpPollingHandler.cs +++ b/ElectronSharp.API/SocketIO/Transport/Http/IHttpPollingHandler.cs @@ -3,16 +3,19 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using System.Net; -namespace SocketIOClient.Transport +namespace SocketIOClient.Transport.Http { - public interface IHttpPollingHandler : IDisposable + public interface IHttpPollingHandler { - IObservable TextObservable { get; } - IObservable BytesObservable { get; } + Func OnTextReceived { get; set; } + Action OnBytesReceived { get; set; } Task GetAsync(string uri, CancellationToken cancellationToken); Task SendAsync(HttpRequestMessage req, CancellationToken cancellationToken); Task PostAsync(string uri, string content, CancellationToken cancellationToken); Task PostAsync(string uri, IEnumerable bytes, CancellationToken cancellationToken); + void AddHeader(string key, string val); + void SetProxy(IWebProxy proxy); } } diff --git a/ElectronSharp.API/SocketIO/Transport/HttpTransport.cs b/ElectronSharp.API/SocketIO/Transport/HttpTransport.cs deleted file mode 100644 index 4e7c6bf0..00000000 --- a/ElectronSharp.API/SocketIO/Transport/HttpTransport.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using SocketIOClient.JsonSerializer; -using SocketIOClient.Messages; - -namespace SocketIOClient.Transport -{ - public class HttpTransport : BaseTransport - { - public HttpTransport(HttpClient http, - IHttpPollingHandler pollingHandler, - SocketIOOptions options, - IJsonSerializer jsonSerializer, - ILogger logger) : base(options, jsonSerializer, logger) - { - _http = http; - _httpPollingHandler = pollingHandler; - _httpPollingHandler.TextObservable.Subscribe(this); - _httpPollingHandler.BytesObservable.Subscribe(this); - } - - string _httpUri; - CancellationTokenSource _pollingTokenSource; - - readonly HttpClient _http; - readonly IHttpPollingHandler _httpPollingHandler; - - private void StartPolling(CancellationToken cancellationToken) - { - Task.Factory.StartNew(async () => - { - int retry = 0; - while (!cancellationToken.IsCancellationRequested) - { - if (!_httpUri.Contains("&sid=")) - { - await Task.Delay(20); - continue; - } - try - { - await _httpPollingHandler.GetAsync(_httpUri, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception e) - { - retry++; - if (retry >= 3) - { - MessageSubject.OnError(e); - break; - } - await Task.Delay(100 * (int)Math.Pow(2, retry)); - } - } - }, TaskCreationOptions.LongRunning); - } - - public override async Task ConnectAsync(Uri uri, CancellationToken cancellationToken) - { - var req = new HttpRequestMessage(HttpMethod.Get, uri); - // if (_options.ExtraHeaders != null) - // { - // foreach (var item in _options.ExtraHeaders) - // { - // req.Headers.Add(item.Key, item.Value); - // } - // } - - _httpUri = uri.ToString(); - await _httpPollingHandler.SendAsync(req, new CancellationTokenSource(Options.ConnectionTimeout).Token).ConfigureAwait(false); - if (_pollingTokenSource != null) - { - _pollingTokenSource.Cancel(); - } - _pollingTokenSource = new CancellationTokenSource(); - StartPolling(_pollingTokenSource.Token); - } - - public override Task DisconnectAsync(CancellationToken cancellationToken) - { - _pollingTokenSource.Cancel(); - if (PingTokenSource != null) - { - PingTokenSource.Cancel(); - } - return Task.CompletedTask; - } - - public override void AddHeader(string key, string val) - { - _http.DefaultRequestHeaders.Add(key, val); - } - - public override void Dispose() - { - base.Dispose(); - _httpPollingHandler.Dispose(); - } - - public override async Task SendAsync(Payload payload, CancellationToken cancellationToken) - { - await _httpPollingHandler.PostAsync(_httpUri, payload.Text, cancellationToken); - if (payload.Bytes != null && payload.Bytes.Count > 0) - { - await _httpPollingHandler.PostAsync(_httpUri, payload.Bytes, cancellationToken); - } - } - - protected override async Task OpenAsync(OpenedMessage msg) - { - //if (!_httpUri.Contains("&sid=")) - //{ - //} - _httpUri += "&sid=" + msg.Sid; - await base.OpenAsync(msg); - } - } -} diff --git a/ElectronSharp.API/SocketIO/Transport/SystemNetWebSocketsClientWebSocket.cs b/ElectronSharp.API/SocketIO/Transport/SystemNetWebSocketsClientWebSocket.cs deleted file mode 100644 index 5a49dbd6..00000000 --- a/ElectronSharp.API/SocketIO/Transport/SystemNetWebSocketsClientWebSocket.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Net.WebSockets; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace SocketIOClient.Transport -{ - public class SystemNetWebSocketsClientWebSocket : IClientWebSocket - { - public SystemNetWebSocketsClientWebSocket(int eio) - { - _eio = eio; - _textSubject = new Subject(); - _bytesSubject = new Subject(); - TextObservable = _textSubject.AsObservable(); - BytesObservable = _bytesSubject.AsObservable(); - _ws = new ClientWebSocket(); - _listenCancellation = new CancellationTokenSource(); - _sendLock = new SemaphoreSlim(1, 1); - } - - const int ReceiveChunkSize = 1024 * 8; - - readonly int _eio; - readonly ClientWebSocket _ws; - readonly Subject _textSubject; - readonly Subject _bytesSubject; - readonly CancellationTokenSource _listenCancellation; - readonly SemaphoreSlim _sendLock; - - public IObservable TextObservable { get; } - public IObservable BytesObservable { get; } - - private void Listen() - { - Task.Factory.StartNew(async() => - { - while (true) - { - if (_listenCancellation.IsCancellationRequested) - { - break; - } - var buffer = new byte[ReceiveChunkSize]; - int count = 0; - WebSocketReceiveResult result = null; - - while (_ws.State == WebSocketState.Open) - { - var subBuffer = new byte[ReceiveChunkSize]; - try - { - result = await _ws.ReceiveAsync(new ArraySegment(subBuffer), CancellationToken.None).ConfigureAwait(false); - - // resize - if (buffer.Length - count < result.Count) - { - Array.Resize(ref buffer, buffer.Length + result.Count); - } - Buffer.BlockCopy(subBuffer, 0, buffer, count, result.Count); - count += result.Count; - if (result.EndOfMessage) - { - break; - } - } - catch (Exception e) - { - _textSubject.OnError(e); - break; - } - } - - if (result == null) - { - break; - } - - switch (result.MessageType) - { - case WebSocketMessageType.Text: - string text = Encoding.UTF8.GetString(buffer, 0, count); - _textSubject.OnNext(text); - break; - case WebSocketMessageType.Binary: - byte[] bytes; - if (_eio == 3) - { - bytes = new byte[count - 1]; - Buffer.BlockCopy(buffer, 1, bytes, 0, bytes.Length); - } - else - { - bytes = new byte[count]; - Buffer.BlockCopy(buffer, 0, bytes, 0, bytes.Length); - } - _bytesSubject.OnNext(bytes); - break; - case WebSocketMessageType.Close: - _textSubject.OnError(new WebSocketException("Received a Close message")); - break; - } - } - }); - } - - public async Task ConnectAsync(Uri uri, CancellationToken cancellationToken) - { - await _ws.ConnectAsync(uri, cancellationToken); - Listen(); - } - - public async Task DisconnectAsync(CancellationToken cancellationToken) - { - await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken); - } - - public async Task SendAsync(byte[] bytes, TransportMessageType type, bool endOfMessage, CancellationToken cancellationToken) - { - var msgType = WebSocketMessageType.Text; - if (type == TransportMessageType.Binary) - { - msgType = WebSocketMessageType.Binary; - } - await _ws.SendAsync(new ArraySegment(bytes), msgType, endOfMessage, cancellationToken).ConfigureAwait(false); - } - - public void AddHeader(string key, string val) - { - _ws.Options.SetRequestHeader(key, val); - } - - public void Dispose() - { - _textSubject.Dispose(); - _bytesSubject.Dispose(); - _ws.Dispose(); - } - } -} diff --git a/ElectronSharp.API/SocketIO/Transport/TransportMessageType.cs b/ElectronSharp.API/SocketIO/Transport/TransportMessageType.cs index 24f9aebb..d3a78436 100644 --- a/ElectronSharp.API/SocketIO/Transport/TransportMessageType.cs +++ b/ElectronSharp.API/SocketIO/Transport/TransportMessageType.cs @@ -2,7 +2,8 @@ { public enum TransportMessageType { - Text = 0, - Binary = 1 + Text, + Binary, + Close } } diff --git a/ElectronSharp.API/SocketIO/Transport/TransportOptions.cs b/ElectronSharp.API/SocketIO/Transport/TransportOptions.cs new file mode 100644 index 00000000..d99cfc18 --- /dev/null +++ b/ElectronSharp.API/SocketIO/Transport/TransportOptions.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace SocketIOClient.Transport +{ + public class TransportOptions + { + public EngineIO EIO { get; set; } + public IEnumerable> Query { get; set; } + public string Auth { get; set; } + public TimeSpan ConnectionTimeout { get; set; } + } +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Transport/WebSocketTransport.cs b/ElectronSharp.API/SocketIO/Transport/WebSocketTransport.cs deleted file mode 100644 index 532ac66c..00000000 --- a/ElectronSharp.API/SocketIO/Transport/WebSocketTransport.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Reactive.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using SocketIOClient.JsonSerializer; - -namespace SocketIOClient.Transport -{ - public class WebSocketTransport : BaseTransport - { - public WebSocketTransport(IClientWebSocket ws, SocketIOOptions options, IJsonSerializer jsonSerializer, ILogger logger) - : base(options, jsonSerializer, logger) - { - _ws = ws; - _sendLock = new SemaphoreSlim(1, 1); - _ws.TextObservable.Subscribe(this); - _ws.BytesObservable.Subscribe(this); - } - - const int ReceiveChunkSize = 1024 * 8; - const int SendChunkSize = 1024 * 8; - - readonly IClientWebSocket _ws; - readonly SemaphoreSlim _sendLock; - - private async Task SendAsync(TransportMessageType type, byte[] bytes, CancellationToken cancellationToken) - { - try - { - await _sendLock.WaitAsync().ConfigureAwait(false); - if (type == TransportMessageType.Binary && Options.EIO == 3) - { - byte[] buffer = new byte[bytes.Length + 1]; - buffer[0] = 4; - Buffer.BlockCopy(bytes, 0, buffer, 1, bytes.Length); - bytes = buffer; - } - int pages = (int)Math.Ceiling(bytes.Length * 1.0 / SendChunkSize); - for (int i = 0; i < pages; i++) - { - int offset = i * SendChunkSize; - int length = SendChunkSize; - if (offset + length > bytes.Length) - { - length = bytes.Length - offset; - } - byte[] subBuffer = new byte[length]; - Buffer.BlockCopy(bytes, offset, subBuffer, 0, subBuffer.Length); - bool endOfMessage = pages - 1 == i; - await _ws.SendAsync(subBuffer, type, endOfMessage, cancellationToken).ConfigureAwait(false); - } - } - finally - { - _sendLock.Release(); - } - } - - public override async Task ConnectAsync(Uri uri, CancellationToken cancellationToken) - { - await _ws.ConnectAsync(uri, cancellationToken); - } - - public override async Task DisconnectAsync(CancellationToken cancellationToken) - { - await _ws.DisconnectAsync(cancellationToken); - } - - public override async Task SendAsync(Payload payload, CancellationToken cancellationToken) - { - byte[] bytes = Encoding.UTF8.GetBytes(payload.Text); - await SendAsync(TransportMessageType.Text, bytes, cancellationToken); - if (payload.Bytes != null) - { - foreach (var item in payload.Bytes) - { - await SendAsync(TransportMessageType.Binary, item, cancellationToken); - } - } - } - - public override void AddHeader(string key, string val) => _ws.AddHeader(key, val); - - public override void Dispose() - { - base.Dispose(); - _sendLock.Dispose(); - } - } -} diff --git a/ElectronSharp.API/SocketIO/Transport/WebSockets/ChunkSize.cs b/ElectronSharp.API/SocketIO/Transport/WebSockets/ChunkSize.cs new file mode 100644 index 00000000..de0a6dbf --- /dev/null +++ b/ElectronSharp.API/SocketIO/Transport/WebSockets/ChunkSize.cs @@ -0,0 +1,10 @@ +namespace SocketIOClient.Transport.WebSockets +{ + public static class ChunkSize + { + public const int Size1K = 1024; + public const int Size8K = 8 * Size1K; + public const int Size16K = 16 * Size1K; + public const int Size32K = 32 * Size1K; + } +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Transport/IClientWebSocket.cs b/ElectronSharp.API/SocketIO/Transport/WebSockets/IClientWebSocket.cs similarity index 62% rename from ElectronSharp.API/SocketIO/Transport/IClientWebSocket.cs rename to ElectronSharp.API/SocketIO/Transport/WebSockets/IClientWebSocket.cs index a3f75fd8..e0d9dbbb 100644 --- a/ElectronSharp.API/SocketIO/Transport/IClientWebSocket.cs +++ b/ElectronSharp.API/SocketIO/Transport/WebSockets/IClientWebSocket.cs @@ -1,16 +1,19 @@ using System; +using System.Net; +using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; -namespace SocketIOClient.Transport +namespace SocketIOClient.Transport.WebSockets { public interface IClientWebSocket : IDisposable { - IObservable TextObservable { get; } - IObservable BytesObservable { get; } + WebSocketState State { get; } Task ConnectAsync(Uri uri, CancellationToken cancellationToken); Task DisconnectAsync(CancellationToken cancellationToken); Task SendAsync(byte[] bytes, TransportMessageType type, bool endOfMessage, CancellationToken cancellationToken); + Task ReceiveAsync(int bufferSize, CancellationToken cancellationToken); void AddHeader(string key, string val); + void SetProxy(IWebProxy proxy); } } diff --git a/ElectronSharp.API/SocketIO/Transport/WebSockets/SystemNetWebSocketsClientWebSocket.cs b/ElectronSharp.API/SocketIO/Transport/WebSockets/SystemNetWebSocketsClientWebSocket.cs new file mode 100644 index 00000000..07beca38 --- /dev/null +++ b/ElectronSharp.API/SocketIO/Transport/WebSockets/SystemNetWebSocketsClientWebSocket.cs @@ -0,0 +1,115 @@ +using System; +using System.Net; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; + +#if NET461_OR_GREATER +using System.Reflection; +using System.Collections.Generic; +#endif + +namespace SocketIOClient.Transport.WebSockets +{ + public class DefaultClientWebSocket : IClientWebSocket + { + public DefaultClientWebSocket() + { + _ws = new ClientWebSocket(); +#if NET461_OR_GREATER + AllowHeaders(); +#endif + } + +#if NET461_OR_GREATER + rivate static readonly HashSet allowedHeaders = new HashSet + { + "User-Agent" + }; + + private void AllowHeaders() + { + var property = _ws.Options + .GetType() + .GetProperty("RequestHeaders", BindingFlags.NonPublic | BindingFlags.Instance); + var headers = property.GetValue(_ws.Options); + var hinfoField = headers.GetType().GetField("HInfo", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + var hinfo = hinfoField.GetValue(null); + var hhtField = hinfo.GetType().GetField("HeaderHashTable", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + var hashTable = hhtField.GetValue(null) as System.Collections.Hashtable; + + foreach (string key in hashTable.Keys) + { + if (!allowedHeaders.Contains(key)) + { + continue; + } + var headerInfo = hashTable[key]; + foreach (var item in headerInfo.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)) + { + + if (item.Name == "IsRequestRestricted") + { + bool isRequestRestricted = (bool)item.GetValue(headerInfo); + if (isRequestRestricted) + { + item.SetValue(headerInfo, false); + } + + } + } + + } + } +#endif + + readonly ClientWebSocket _ws; + + public WebSocketState State => (WebSocketState)_ws.State; + + public async Task ConnectAsync(Uri uri, CancellationToken cancellationToken) + { + await _ws.ConnectAsync(uri, cancellationToken).ConfigureAwait(false); + } + + public async Task DisconnectAsync(CancellationToken cancellationToken) + { + await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); + } + + public async Task SendAsync(byte[] bytes, TransportMessageType type, bool endOfMessage, CancellationToken cancellationToken) + { + var msgType = WebSocketMessageType.Text; + if (type == TransportMessageType.Binary) + { + msgType = WebSocketMessageType.Binary; + } + await _ws.SendAsync(new ArraySegment(bytes), msgType, endOfMessage, cancellationToken).ConfigureAwait(false); + } + + public async Task ReceiveAsync(int bufferSize, CancellationToken cancellationToken) + { + var buffer = new byte[bufferSize]; + var result = await _ws.ReceiveAsync(new ArraySegment(buffer), cancellationToken).ConfigureAwait(false); + return new WebSocketReceiveResult + { + Count = result.Count, + MessageType = (TransportMessageType)result.MessageType, + EndOfMessage = result.EndOfMessage, + Buffer = buffer, + }; + } + + public void AddHeader(string key, string val) + { + _ws.Options.SetRequestHeader(key, val); + } + + public void SetProxy(IWebProxy proxy) => _ws.Options.Proxy = proxy; + + public void Dispose() + { + _ws.Dispose(); + } + } +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketReceiveResult.cs b/ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketReceiveResult.cs new file mode 100644 index 00000000..abf8473a --- /dev/null +++ b/ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketReceiveResult.cs @@ -0,0 +1,10 @@ +namespace SocketIOClient.Transport.WebSockets +{ + public class WebSocketReceiveResult + { + public int Count { get; set; } + public bool EndOfMessage { get; set; } + public TransportMessageType MessageType { get; set; } + public byte[] Buffer { get; set; } + } +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketState.cs b/ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketState.cs new file mode 100644 index 00000000..4fbfe9e6 --- /dev/null +++ b/ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketState.cs @@ -0,0 +1,13 @@ +namespace SocketIOClient.Transport.WebSockets +{ + public enum WebSocketState + { + None = 0, + Connecting = 1, + Open = 2, + CloseSent = 3, + CloseReceived = 4, + Closed = 5, + Aborted = 6 + } +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketTransport.cs b/ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketTransport.cs new file mode 100644 index 00000000..46aa8c03 --- /dev/null +++ b/ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketTransport.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using SocketIOClient.Extensions; + +#if DEBUG +using System.Diagnostics; +#endif + +namespace SocketIOClient.Transport.WebSockets +{ + public class WebSocketTransport : BaseTransport + { + public WebSocketTransport(TransportOptions options, IClientWebSocket ws) : base(options) + { + _ws = ws; + _sendLock = new SemaphoreSlim(1, 1); + _listenCancellation = new CancellationTokenSource(); + } + + protected override TransportProtocol Protocol => TransportProtocol.WebSocket; + + readonly IClientWebSocket _ws; + readonly SemaphoreSlim _sendLock; + readonly CancellationTokenSource _listenCancellation; + int _sendChunkSize = ChunkSize.Size8K; + int _receiveChunkSize = ChunkSize.Size8K; + bool _dirty; + + private async Task SendAsync(TransportMessageType type, byte[] bytes, CancellationToken cancellationToken) + { + if (type == TransportMessageType.Binary && Options.EIO == EngineIO.V3) + { + byte[] buffer = new byte[bytes.Length + 1]; + buffer[0] = 4; + Buffer.BlockCopy(bytes, 0, buffer, 1, bytes.Length); + bytes = buffer; + } + int pages = (int)Math.Ceiling(bytes.Length * 1.0 / _sendChunkSize); + for (int i = 0; i < pages; i++) + { + int offset = i * _sendChunkSize; + int length = _sendChunkSize; + if (offset + length > bytes.Length) + { + length = bytes.Length - offset; + } + byte[] subBuffer = new byte[length]; + Buffer.BlockCopy(bytes, offset, subBuffer, 0, subBuffer.Length); + bool endOfMessage = pages - 1 == i; + await _ws.SendAsync(subBuffer, type, endOfMessage, cancellationToken).ConfigureAwait(false); + } + } + + private void Listen(CancellationToken cancellationToken) + { + Task.Factory.StartNew(async () => + { + while (!cancellationToken.IsCancellationRequested) + { + var binary = new byte[_receiveChunkSize]; + int count = 0; + WebSocketReceiveResult result = null; + + while (_ws.State == WebSocketState.Open) + { + try + { + result = await _ws.ReceiveAsync(_receiveChunkSize, cancellationToken).ConfigureAwait(false); + + // resize + if (binary.Length - count < result.Count) + { + Array.Resize(ref binary, binary.Length + result.Count); + } + Buffer.BlockCopy(result.Buffer, 0, binary, count, result.Count); + count += result.Count; + if (result.EndOfMessage) + { + break; + } + } + catch (Exception e) + { + OnError.TryInvoke(e); + break; + } + } + + if (result == null) + { + break; + } + + try + { + switch (result.MessageType) + { + case TransportMessageType.Text: + string text = Encoding.UTF8.GetString(binary, 0, count); + await OnTextReceived(text); + break; + case TransportMessageType.Binary: + byte[] bytes; + if (Options.EIO == EngineIO.V3) + { + bytes = new byte[count - 1]; + Buffer.BlockCopy(binary, 1, bytes, 0, bytes.Length); + } + else + { + bytes = new byte[count]; + Buffer.BlockCopy(binary, 0, bytes, 0, bytes.Length); + } + + OnBinaryReceived(bytes); + break; + case TransportMessageType.Close: + OnError.TryInvoke(new TransportException("Received a Close message")); + break; + } + } + catch (Exception e) + { + OnError.TryInvoke(e); + +#if DEBUG + Debug.WriteLine($"[{Protocol}❌] {e}"); +#endif + break; + } + } + }, cancellationToken); + } + + public override async Task ConnectAsync(Uri uri, CancellationToken cancellationToken) + { + if (_dirty) + throw new InvalidOperationException(DirtyMessage); + _dirty = true; + try + { + await _ws.ConnectAsync(uri, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + throw new TransportException($"Could not connect to '{uri}'", e); + } + Listen(_listenCancellation.Token); + } + + public override async Task DisconnectAsync(CancellationToken cancellationToken) + { + await _ws.DisconnectAsync(cancellationToken).ConfigureAwait(false); + } + + public override async Task SendAsync(Payload payload, CancellationToken cancellationToken) + { + try + { + await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false); + if (!string.IsNullOrEmpty(payload.Text)) + { + byte[] bytes = Encoding.UTF8.GetBytes(payload.Text); + await SendAsync(TransportMessageType.Text, bytes, cancellationToken); +#if DEBUG + Debug.WriteLine($"[WebSocket⬆] {payload.Text}"); +#endif + } + + if (payload.Bytes != null) + { + foreach (var item in payload.Bytes) + { + await SendAsync(TransportMessageType.Binary, item, cancellationToken).ConfigureAwait(false); +#if DEBUG + Debug.WriteLine($"[WebSocket⬆] {Convert.ToBase64String(item)}"); +#endif + } + } + } + finally + { + _sendLock.Release(); + } + } + + public async Task ChangeSendChunkSizeAsync(int size) + { + try + { + await _sendLock.WaitAsync().ConfigureAwait(false); + _sendChunkSize = size; + } + finally + { + _sendLock.Release(); + } + } + + public async Task ChangeReceiveChunkSizeAsync(int size) + { + try + { + await _sendLock.WaitAsync().ConfigureAwait(false); + _sendChunkSize = size; + } + finally + { + _sendLock.Release(); + } + } + + public override void AddHeader(string key, string val) + { + if (_dirty) + { + throw new InvalidOperationException("Unable to add header after connecting"); + } + _ws.AddHeader(key, val); + } + + public override void SetProxy(IWebProxy proxy) + { + if (_dirty) + { + throw new InvalidOperationException("Unable to set proxy after connecting"); + } + _ws.SetProxy(proxy); + } + + public override void Dispose() + { + base.Dispose(); + _sendLock.Dispose(); + } + } +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/UriConverters/IUriConverter.cs b/ElectronSharp.API/SocketIO/UriConverters/IUriConverter.cs deleted file mode 100644 index 923f8132..00000000 --- a/ElectronSharp.API/SocketIO/UriConverters/IUriConverter.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace SocketIOClient.UriConverters -{ - public interface IUriConverter - { - Uri GetServerUri(bool ws, Uri serverUri, int eio, string path, IEnumerable> queryParams); - } -} diff --git a/ElectronSharp.API/SocketIO/UriConverters/UriConverter.cs b/ElectronSharp.API/SocketIO/UriConverters/UriConverter.cs index 922ee9dc..73a581c4 100644 --- a/ElectronSharp.API/SocketIO/UriConverters/UriConverter.cs +++ b/ElectronSharp.API/SocketIO/UriConverters/UriConverter.cs @@ -4,9 +4,9 @@ namespace SocketIOClient.UriConverters { - public class UriConverter : IUriConverter + public static class UriConverter { - public Uri GetServerUri(bool ws, Uri serverUri, int eio, string path, IEnumerable> queryParams) + public static Uri GetServerUri(bool ws, Uri serverUri, EngineIO eio, string path, IEnumerable> queryParams) { var builder = new StringBuilder(); if (serverUri.Scheme == "https" || serverUri.Scheme == "wss") @@ -36,7 +36,7 @@ public Uri GetServerUri(bool ws, Uri serverUri, int eio, string path, IEnumerabl } builder .Append("/?EIO=") - .Append(eio) + .Append((int)eio) .Append("&transport=") .Append(ws ? "websocket" : "polling"); From a5ef51082647a1ed10767723cc02e0b123f7cfa5 Mon Sep 17 00:00:00 2001 From: lofcz Date: Thu, 26 Jan 2023 14:16:48 +0100 Subject: [PATCH 2/3] Update SocketIO --- ElectronSharp.API/SocketIO/SocketIO.cs | 119 +++++++++--------- .../SocketIO/Transport/BaseTransport.cs | 15 ++- .../Transport/Http/DefaultHttpClient.cs | 6 + .../SocketIO/Transport/Http/IHttpClient.cs | 1 + .../SocketIO/Transport/ITransport.cs | 20 +++ .../WebSockets/WebSocketTransport.cs | 7 +- 6 files changed, 105 insertions(+), 63 deletions(-) create mode 100644 ElectronSharp.API/SocketIO/Transport/ITransport.cs diff --git a/ElectronSharp.API/SocketIO/SocketIO.cs b/ElectronSharp.API/SocketIO/SocketIO.cs index c8e4b943..41396e57 100644 --- a/ElectronSharp.API/SocketIO/SocketIO.cs +++ b/ElectronSharp.API/SocketIO/SocketIO.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; using System.Net.Http; using System.Net.WebSockets; using System.Threading; @@ -94,25 +92,16 @@ private Uri ServerUri int _attempts; - [Obsolete] - /// - /// Whether or not the socket is disconnected from the server. - /// - public bool Disconnected => !Connected; - public SocketIOOptions Options { get; } public IJsonSerializer JsonSerializer { get; set; } - - public HttpClient HttpClient { get; set; } + public ITransport Transport { get; set; } + public Func HttpClientProvider { get; set; } public Func ClientWebSocketProvider { get; set; } - public Func HttpClientAdapterProvider { get; set; } List _resources = new List(); - BaseTransport _transport; - List _expectedExceptions; int _packetId; @@ -172,19 +161,30 @@ private void Initialize() JsonSerializer = new SystemTextJsonSerializer(); - HttpClient = new HttpClient(); + HttpClientProvider = () => new DefaultHttpClient(); ClientWebSocketProvider = () => new DefaultClientWebSocket(); - HttpClientAdapterProvider = () => new DefaultHttpClient(); _expectedExceptions = new List { typeof(TimeoutException), typeof(WebSocketException), typeof(HttpRequestException), typeof(OperationCanceledException), - typeof(TaskCanceledException) + typeof(TaskCanceledException), + typeof(TransportException), }; } + private IHttpClient GetHttpClient() + { + var http = HttpClientProvider(); + if (http is null) + { + throw new ArgumentNullException(nameof(HttpClientProvider), $"{HttpClientProvider} returns a null"); + } + + return http; + } + private async Task InitTransportAsync() { Options.Transport = await GetProtocolAsync(); @@ -197,14 +197,8 @@ private async Task InitTransportAsync() }; if (Options.Transport == TransportProtocol.Polling) { - var adapter = HttpClientAdapterProvider(); - if (adapter is null) - { - throw new ArgumentNullException(nameof(HttpClientAdapterProvider), $"{HttpClientAdapterProvider} returns a null"); - } - _resources.Add(adapter); - var handler = HttpPollingHandler.CreateHandler(transportOptions.EIO, adapter); - _transport = new HttpTransport(transportOptions, handler); + var handler = HttpPollingHandler.CreateHandler(transportOptions.EIO, GetHttpClient()); + Transport = new HttpTransport(transportOptions, handler); } else { @@ -214,14 +208,14 @@ private async Task InitTransportAsync() throw new ArgumentNullException(nameof(ClientWebSocketProvider), $"{ClientWebSocketProvider} returns a null"); } _resources.Add(ws); - _transport = new WebSocketTransport(transportOptions, ws); + Transport = new WebSocketTransport(transportOptions, ws); } - _resources.Add(_transport); - _transport.Namespace = _namespace; + _resources.Add(Transport); + Transport.Namespace = _namespace; SetHeaders(); - _transport.SetProxy(Options.Proxy); - _transport.OnReceived = OnMessageReceived; - _transport.OnError = OnErrorReceived; + Transport.SetProxy(Options.Proxy); + Transport.OnReceived = OnMessageReceived; + Transport.OnError = OnErrorReceived; } private string GetAuth(object auth) @@ -240,7 +234,7 @@ private void SetHeaders() { try { - _transport.AddHeader(item.Key, item.Value); + Transport.AddHeader(item.Key, item.Value); } catch (Exception e) { @@ -276,7 +270,7 @@ private void ConnectInBackground(CancellationToken cancellationToken) { using (var cts = new CancellationTokenSource(Options.ConnectionTimeout)) { - await _transport.ConnectAsync(serverUri, cts.Token).ConfigureAwait(false); + await Transport.ConnectAsync(serverUri, cts.Token).ConfigureAwait(false); break; } } @@ -344,10 +338,13 @@ private async Task GetProtocolAsync() Uri uri = UriConverter.GetServerUri(false, ServerUri, Options.EIO, Options.Path, Options.Query); try { - string text = await HttpClient.GetStringAsync(uri); - if (text.Contains("websocket")) + using (var http = GetHttpClient()) { - return TransportProtocol.WebSocket; + string text = await http.GetStringAsync(uri); + if (text.Contains("websocket")) + { + return TransportProtocol.WebSocket; + } } } catch (Exception e) @@ -498,7 +495,9 @@ private void BinaryAckMessageHandler(ClientBinaryAckMessage msg) private void OnErrorReceived(Exception ex) { - //Logger.LogError(ex, ex.Message); +#if DEBUG + Debug.WriteLine(ex); +#endif _ = InvokeDisconnect(DisconnectReason.TransportClose); } @@ -547,24 +546,23 @@ private void OnMessageReceived(IMessage msg) public async Task DisconnectAsync() { - if (Connected) + _connCts.TryCancel(); + _connCts.TryDispose(); + var msg = new DisconnectedMessage + { + Namespace = _namespace + }; + try + { + await Transport.SendAsync(msg, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception e) { - var msg = new DisconnectedMessage - { - Namespace = _namespace - }; - try - { - await _transport.SendAsync(msg, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception e) - { #if DEBUG - Debug.WriteLine(e); + Debug.WriteLine(e); #endif - } - await InvokeDisconnect(DisconnectReason.IOClientDisconnect); } + await InvokeDisconnect(DisconnectReason.IOClientDisconnect); } /// @@ -654,7 +652,7 @@ internal async Task ClientAckAsync(int packetId, CancellationToken cancellationT Id = packetId }; } - await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false); + await Transport.SendAsync(msg, cancellationToken).ConfigureAwait(false); } /// @@ -682,7 +680,7 @@ public async Task EmitAsync(string eventName, CancellationToken cancellationToke Event = eventName, Json = result.Json }; - await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false); + await Transport.SendAsync(msg, cancellationToken).ConfigureAwait(false); } else { @@ -692,7 +690,7 @@ public async Task EmitAsync(string eventName, CancellationToken cancellationToke Event = eventName, Json = result.Json }; - await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false); + await Transport.SendAsync(msg, cancellationToken).ConfigureAwait(false); } } else @@ -702,7 +700,7 @@ public async Task EmitAsync(string eventName, CancellationToken cancellationToke Namespace = _namespace, Event = eventName }; - await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false); + await Transport.SendAsync(msg, cancellationToken).ConfigureAwait(false); } } @@ -734,7 +732,7 @@ public async Task EmitAsync(string eventName, CancellationToken cancellationToke Id = _packetId, OutgoingBytes = new List(result.Bytes) }; - await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false); + await Transport.SendAsync(msg, cancellationToken).ConfigureAwait(false); } else { @@ -745,7 +743,7 @@ public async Task EmitAsync(string eventName, CancellationToken cancellationToke Id = _packetId, Json = result.Json }; - await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false); + await Transport.SendAsync(msg, cancellationToken).ConfigureAwait(false); } } else @@ -756,7 +754,7 @@ public async Task EmitAsync(string eventName, CancellationToken cancellationToke Namespace = _namespace, Id = _packetId }; - await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false); + await Transport.SendAsync(msg, cancellationToken).ConfigureAwait(false); } } @@ -769,7 +767,7 @@ private async Task InvokeDisconnect(string reason) OnDisconnected.TryInvoke(this, reason); try { - await _transport.DisconnectAsync(CancellationToken.None).ConfigureAwait(false); + await Transport.DisconnectAsync(CancellationToken.None).ConfigureAwait(false); } catch (Exception e) { @@ -798,8 +796,9 @@ public void AddExpectedException(Type type) public void Dispose() { - HttpClient.Dispose(); - _transport.TryDispose(); + _connCts.TryCancel(); + _connCts.TryDispose(); + Transport.TryDispose(); _ackHandlers.Clear(); _onAnyHandlers.Clear(); _eventHandlers.Clear(); diff --git a/ElectronSharp.API/SocketIO/Transport/BaseTransport.cs b/ElectronSharp.API/SocketIO/Transport/BaseTransport.cs index 336eb339..78580f90 100644 --- a/ElectronSharp.API/SocketIO/Transport/BaseTransport.cs +++ b/ElectronSharp.API/SocketIO/Transport/BaseTransport.cs @@ -13,7 +13,7 @@ namespace SocketIOClient.Transport { - public abstract class BaseTransport : IDisposable + public abstract class BaseTransport : ITransport { protected BaseTransport(TransportOptions options) { @@ -143,6 +143,7 @@ public virtual void Dispose() protected async Task OnTextReceived(string text) { + // TODO: refactor #if DEBUG Debug.WriteLine($"[{Protocol}⬇] {text}"); #endif @@ -167,6 +168,18 @@ protected async Task OnTextReceived(string text) { if (msg.Type == MessageType.Connected) { + int ms = 0; + while (OpenedMessage is null) + { + await Task.Delay(10); + ms += 10; + if (ms > Options.ConnectionTimeout.TotalMilliseconds) + { + OnError.TryInvoke(new TimeoutException()); + return; + } + } + var connectMsg = msg as ConnectedMessage; connectMsg.Sid = OpenedMessage.Sid; if ((string.IsNullOrEmpty(Namespace) && string.IsNullOrEmpty(connectMsg.Namespace)) || connectMsg.Namespace == Namespace) diff --git a/ElectronSharp.API/SocketIO/Transport/Http/DefaultHttpClient.cs b/ElectronSharp.API/SocketIO/Transport/Http/DefaultHttpClient.cs index 12ffc1ce..9d517d4c 100644 --- a/ElectronSharp.API/SocketIO/Transport/Http/DefaultHttpClient.cs +++ b/ElectronSharp.API/SocketIO/Transport/Http/DefaultHttpClient.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System; using System.Net; using System.Net.Http; using System.Threading; @@ -49,6 +50,11 @@ public Task PostAsync(string requestUri, HttpContent conten { return _httpClient.PostAsync(requestUri, content, cancellationToken); } + + public Task GetStringAsync(Uri requestUri) + { + return _httpClient.GetStringAsync(requestUri); + } public void Dispose() { diff --git a/ElectronSharp.API/SocketIO/Transport/Http/IHttpClient.cs b/ElectronSharp.API/SocketIO/Transport/Http/IHttpClient.cs index 1514c99a..fd9eba0d 100644 --- a/ElectronSharp.API/SocketIO/Transport/Http/IHttpClient.cs +++ b/ElectronSharp.API/SocketIO/Transport/Http/IHttpClient.cs @@ -12,5 +12,6 @@ public interface IHttpClient : IDisposable void SetProxy(IWebProxy proxy); Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken); Task PostAsync(string requestUri, HttpContent content, CancellationToken cancellationToken); + Task GetStringAsync(Uri requestUri); } } \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Transport/ITransport.cs b/ElectronSharp.API/SocketIO/Transport/ITransport.cs new file mode 100644 index 00000000..b8246701 --- /dev/null +++ b/ElectronSharp.API/SocketIO/Transport/ITransport.cs @@ -0,0 +1,20 @@ +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using SocketIOClient.Messages; + +namespace SocketIOClient.Transport +{ + public interface ITransport : IDisposable + { + Action OnReceived { get; set; } + Action OnError { get; set; } + string Namespace { get; set; } + Task SendAsync(IMessage msg, CancellationToken cancellationToken); + Task ConnectAsync(Uri uri, CancellationToken cancellationToken); + Task DisconnectAsync(CancellationToken cancellationToken); + void AddHeader(string key, string val); + void SetProxy(IWebProxy proxy); + } +} \ No newline at end of file diff --git a/ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketTransport.cs b/ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketTransport.cs index 46aa8c03..d1c46150 100644 --- a/ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketTransport.cs +++ b/ElectronSharp.API/SocketIO/Transport/WebSockets/WebSocketTransport.cs @@ -86,13 +86,16 @@ private void Listen(CancellationToken cancellationToken) catch (Exception e) { OnError.TryInvoke(e); - break; +#if DEBUG + Debug.WriteLine($"[{Protocol}❌] {e}"); +#endif + return; } } if (result == null) { - break; + return; } try From 47052dd9aee6b648eca1d84d8247dd35598056ef Mon Sep 17 00:00:00 2001 From: theolivenbaum Date: Fri, 27 Jan 2023 14:51:55 +0100 Subject: [PATCH 3/3] minor changes while reviewing --- .../CancellationTokenSourceExtensions.cs | 11 +++++++-- .../Extensions/EventHandlerExtensions.cs | 8 ++++++- .../SocketIO/Messages/ConnectedMessage.cs | 23 +++++++++++++++---- ElectronSharp.API/SocketIO/SocketIO.cs | 12 +++++----- .../Properties/launchSettings.json | 6 ++--- 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/ElectronSharp.API/SocketIO/Extensions/CancellationTokenSourceExtensions.cs b/ElectronSharp.API/SocketIO/Extensions/CancellationTokenSourceExtensions.cs index a7c4639b..0d1a9b9b 100644 --- a/ElectronSharp.API/SocketIO/Extensions/CancellationTokenSourceExtensions.cs +++ b/ElectronSharp.API/SocketIO/Extensions/CancellationTokenSourceExtensions.cs @@ -7,14 +7,21 @@ internal static class CancellationTokenSourceExtensions { public static void TryDispose(this CancellationTokenSource cts) { - cts?.Dispose(); + try + { + cts?.Dispose(); + } + catch + { + //Ignore + } } public static void TryCancel(this CancellationTokenSource cts) { if (cts != null && !cts.IsCancellationRequested) { - cts.Cancel(); + cts.TryCancel(); } } } diff --git a/ElectronSharp.API/SocketIO/Extensions/EventHandlerExtensions.cs b/ElectronSharp.API/SocketIO/Extensions/EventHandlerExtensions.cs index 73ce4ffa..aa8fe236 100644 --- a/ElectronSharp.API/SocketIO/Extensions/EventHandlerExtensions.cs +++ b/ElectronSharp.API/SocketIO/Extensions/EventHandlerExtensions.cs @@ -26,7 +26,13 @@ public static async Task TryInvokeAsync(this Func func, T arg1) { return; } - await func(arg1); + + var task = func(arg1); + + if(task is not null) + { + await task; + } } } } diff --git a/ElectronSharp.API/SocketIO/Messages/ConnectedMessage.cs b/ElectronSharp.API/SocketIO/Messages/ConnectedMessage.cs index d23bca6a..5b0f4658 100644 --- a/ElectronSharp.API/SocketIO/Messages/ConnectedMessage.cs +++ b/ElectronSharp.API/SocketIO/Messages/ConnectedMessage.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Text; using System.Text.Json; +using Microsoft.Extensions.ObjectPool; namespace SocketIOClient.Messages { @@ -63,15 +64,23 @@ private void Eio4Read(string msg) Sid = JsonDocument.Parse(msg).RootElement.GetProperty("sid").GetString(); } + private static ObjectPool _sbPool = new DefaultObjectPool(new StringBuilderPooledObjectPolicy()); + private string Eio4Write() { - var builder = new StringBuilder("40"); + var builder = _sbPool.Get(); + builder.Append("40"); + if (!string.IsNullOrEmpty(Namespace)) { builder.Append(Namespace).Append(','); } + builder.Append(AuthJsonStr); - return builder.ToString(); + + var final = builder.ToString(); + _sbPool.Return(builder); + return final; } private void Eio3Read(string msg) @@ -102,8 +111,12 @@ private string Eio3Write() { return string.Empty; } - var builder = new StringBuilder("40"); + + var builder = _sbPool.Get(); + builder.Append("40"); + builder.Append(Namespace); + if (Query != null) { int i = -1; @@ -122,7 +135,9 @@ private string Eio3Write() } } builder.Append(','); - return builder.ToString(); + var final = builder.ToString(); + _sbPool.Return(builder); + return final; } } } diff --git a/ElectronSharp.API/SocketIO/SocketIO.cs b/ElectronSharp.API/SocketIO/SocketIO.cs index 41396e57..e20977e0 100644 --- a/ElectronSharp.API/SocketIO/SocketIO.cs +++ b/ElectronSharp.API/SocketIO/SocketIO.cs @@ -796,12 +796,12 @@ public void AddExpectedException(Type type) public void Dispose() { - _connCts.TryCancel(); - _connCts.TryDispose(); - Transport.TryDispose(); - _ackHandlers.Clear(); - _onAnyHandlers.Clear(); - _eventHandlers.Clear(); + _connCts?.TryCancel(); + _connCts?.TryDispose(); + Transport?.TryDispose(); + _ackHandlers?.Clear(); + _onAnyHandlers?.Clear(); + _eventHandlers?.Clear(); } } } \ No newline at end of file diff --git a/ElectronSharp.SampleApp/Properties/launchSettings.json b/ElectronSharp.SampleApp/Properties/launchSettings.json index 761243d1..a99e1966 100644 --- a/ElectronSharp.SampleApp/Properties/launchSettings.json +++ b/ElectronSharp.SampleApp/Properties/launchSettings.json @@ -11,9 +11,9 @@ }, "run with electronize": { "commandName": "Executable", - "executablePath": "$(SolutionDir)ElectronNET.CLI\\bin\\Debug\\net6.0\\dotnet-electron-sharp.exe", - "commandLineArgs": "start /from-build-output $(SolutionDir)ElectronNET.WebApp\\bin\\$(Configuration)\\net6.0", - "workingDirectory": "$(SolutionDir)ElectronNET.WebApp" + "executablePath": "$(SolutionDir)ElectronSharp.CLI\\bin\\Debug\\net7.0\\dotnet-electron-sharp.exe", + "commandLineArgs": "start /from-build-output $(SolutionDir)ElectronSharp.SampleApp\\bin\\$(Configuration)\\net6.0", + "workingDirectory": "$(SolutionDir)ElectronSharp.SampleApp" }, "run from csharp": { "commandName": "Project",