You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
387 lines
14 KiB
387 lines
14 KiB
8 months ago
|
#if !BESTHTTP_DISABLE_SOCKETIO
|
||
|
#if !BESTHTTP_DISABLE_WEBSOCKET
|
||
|
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
|
||
|
namespace BestHTTP.SocketIO.Transports
|
||
|
{
|
||
|
using BestHTTP.Connections;
|
||
|
using BestHTTP.WebSocket;
|
||
|
using Extensions;
|
||
|
|
||
|
/// <summary>
|
||
|
/// A transport implementation that can communicate with a SocketIO server.
|
||
|
/// </summary>
|
||
|
internal sealed class WebSocketTransport : ITransport
|
||
|
{
|
||
|
public TransportTypes Type { get { return TransportTypes.WebSocket; } }
|
||
|
public TransportStates State { get; private set; }
|
||
|
public SocketManager Manager { get; private set; }
|
||
|
public bool IsRequestInProgress { get { return false; } }
|
||
|
public bool IsPollingInProgress { get { return false; } }
|
||
|
public WebSocket Implementation { get; private set; }
|
||
|
|
||
|
private Packet PacketWithAttachment;
|
||
|
private byte[] Buffer;
|
||
|
|
||
|
public WebSocketTransport(SocketManager manager)
|
||
|
{
|
||
|
State = TransportStates.Closed;
|
||
|
Manager = manager;
|
||
|
}
|
||
|
|
||
|
#region Some ITransport Implementation
|
||
|
|
||
|
public void Open()
|
||
|
{
|
||
|
if (State != TransportStates.Closed)
|
||
|
return;
|
||
|
|
||
|
Uri uri = null;
|
||
|
string baseUrl = new UriBuilder(HTTPProtocolFactory.IsSecureProtocol(Manager.Uri) ? "wss" : "ws",
|
||
|
Manager.Uri.Host,
|
||
|
Manager.Uri.Port,
|
||
|
Manager.Uri.GetRequestPathAndQueryURL()).Uri.ToString();
|
||
|
string format = "{0}?EIO={1}&transport=websocket{3}";
|
||
|
if (Manager.Handshake != null)
|
||
|
format += "&sid={2}";
|
||
|
|
||
|
bool sendAdditionalQueryParams = !Manager.Options.QueryParamsOnlyForHandshake || (Manager.Options.QueryParamsOnlyForHandshake && Manager.Handshake == null);
|
||
|
|
||
|
uri = new Uri(string.Format(format,
|
||
|
baseUrl,
|
||
|
Manager.ProtocolVersion,
|
||
|
Manager.Handshake != null ? Manager.Handshake.Sid : string.Empty,
|
||
|
sendAdditionalQueryParams ? Manager.Options.BuildQueryParams() : string.Empty));
|
||
|
|
||
|
Implementation = new WebSocket(uri);
|
||
|
|
||
|
#if !UNITY_WEBGL || UNITY_EDITOR
|
||
|
Implementation.StartPingThread = true;
|
||
|
|
||
|
if (this.Manager.Options.HTTPRequestCustomizationCallback != null)
|
||
|
Implementation.OnInternalRequestCreated = (ws, internalRequest) => this.Manager.Options.HTTPRequestCustomizationCallback(this.Manager, internalRequest);
|
||
|
#endif
|
||
|
|
||
|
Implementation.OnOpen = OnOpen;
|
||
|
Implementation.OnMessage = OnMessage;
|
||
|
Implementation.OnBinary = OnBinary;
|
||
|
Implementation.OnError = OnError;
|
||
|
Implementation.OnClosed = OnClosed;
|
||
|
|
||
|
Implementation.Open();
|
||
|
|
||
|
State = TransportStates.Connecting;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Closes the transport and cleans up resources.
|
||
|
/// </summary>
|
||
|
public void Close()
|
||
|
{
|
||
|
if (State == TransportStates.Closed)
|
||
|
return;
|
||
|
|
||
|
State = TransportStates.Closed;
|
||
|
|
||
|
if (Implementation != null)
|
||
|
Implementation.Close();
|
||
|
else
|
||
|
HTTPManager.Logger.Warning("WebSocketTransport", "Close - WebSocket Implementation already null!");
|
||
|
Implementation = null;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Polling implementation. With WebSocket it's just a skeleton.
|
||
|
/// </summary>
|
||
|
public void Poll()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region WebSocket Events
|
||
|
|
||
|
/// <summary>
|
||
|
/// WebSocket implementation OnOpen event handler.
|
||
|
/// </summary>
|
||
|
private void OnOpen(WebSocket ws)
|
||
|
{
|
||
|
if (ws != Implementation)
|
||
|
return;
|
||
|
|
||
|
HTTPManager.Logger.Information("WebSocketTransport", "OnOpen");
|
||
|
|
||
|
State = TransportStates.Opening;
|
||
|
|
||
|
// Send a Probe packet to test the transport. If we receive back a pong with the same payload we can upgrade
|
||
|
if (Manager.UpgradingTransport == this)
|
||
|
Send(new Packet(TransportEventTypes.Ping, SocketIOEventTypes.Unknown, "/", "probe"));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// WebSocket implementation OnMessage event handler.
|
||
|
/// </summary>
|
||
|
private void OnMessage(WebSocket ws, string message)
|
||
|
{
|
||
|
if (ws != Implementation)
|
||
|
return;
|
||
|
|
||
|
if (HTTPManager.Logger.Level <= BestHTTP.Logger.Loglevels.All)
|
||
|
HTTPManager.Logger.Verbose("WebSocketTransport", "OnMessage: " + message);
|
||
|
|
||
|
Packet packet = null;
|
||
|
try
|
||
|
{
|
||
|
packet = new Packet(message);
|
||
|
}
|
||
|
catch (Exception ex)
|
||
|
{
|
||
|
HTTPManager.Logger.Exception("WebSocketTransport", "OnMessage Packet parsing", ex);
|
||
|
}
|
||
|
|
||
|
if (packet == null)
|
||
|
{
|
||
|
HTTPManager.Logger.Error("WebSocketTransport", "Message parsing failed. Message: " + message);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
|
||
|
if (packet.AttachmentCount == 0)
|
||
|
OnPacket(packet);
|
||
|
else
|
||
|
PacketWithAttachment = packet;
|
||
|
}
|
||
|
catch (Exception ex)
|
||
|
{
|
||
|
HTTPManager.Logger.Exception("WebSocketTransport", "OnMessage OnPacket", ex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// WebSocket implementation OnBinary event handler.
|
||
|
/// </summary>
|
||
|
private void OnBinary(WebSocket ws, byte[] data)
|
||
|
{
|
||
|
if (ws != Implementation)
|
||
|
return;
|
||
|
|
||
|
if (HTTPManager.Logger.Level <= BestHTTP.Logger.Loglevels.All)
|
||
|
HTTPManager.Logger.Verbose("WebSocketTransport", "OnBinary");
|
||
|
|
||
|
if (PacketWithAttachment != null)
|
||
|
{
|
||
|
switch(this.Manager.Options.ServerVersion)
|
||
|
{
|
||
|
case SupportedSocketIOVersions.v2: PacketWithAttachment.AddAttachmentFromServer(data, false); break;
|
||
|
case SupportedSocketIOVersions.v3: PacketWithAttachment.AddAttachmentFromServer(data, true); break;
|
||
|
default:
|
||
|
HTTPManager.Logger.Warning("WebSocketTransport", "Binary packet received while the server's version is Unknown. Set SocketOption's ServerVersion to the correct value to avoid packet mishandling!");
|
||
|
|
||
|
// Fall back to V2 by default.
|
||
|
this.Manager.Options.ServerVersion = SupportedSocketIOVersions.v2;
|
||
|
goto case SupportedSocketIOVersions.v2;
|
||
|
}
|
||
|
|
||
|
if (PacketWithAttachment.HasAllAttachment)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
OnPacket(PacketWithAttachment);
|
||
|
}
|
||
|
catch (Exception ex)
|
||
|
{
|
||
|
HTTPManager.Logger.Exception("WebSocketTransport", "OnBinary", ex);
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
PacketWithAttachment = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Room for improvement: we received an unwanted binary message?
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// WebSocket implementation OnError event handler.
|
||
|
/// </summary>
|
||
|
private void OnError(WebSocket ws, string error)
|
||
|
{
|
||
|
if (ws != Implementation)
|
||
|
return;
|
||
|
|
||
|
#if !UNITY_WEBGL || UNITY_EDITOR
|
||
|
if (string.IsNullOrEmpty(error))
|
||
|
{
|
||
|
switch (ws.InternalRequest.State)
|
||
|
{
|
||
|
// The request finished without any problem.
|
||
|
case HTTPRequestStates.Finished:
|
||
|
if (ws.InternalRequest.Response.IsSuccess || ws.InternalRequest.Response.StatusCode == 101)
|
||
|
error = string.Format("Request finished. Status Code: {0} Message: {1}", ws.InternalRequest.Response.StatusCode.ToString(), ws.InternalRequest.Response.Message);
|
||
|
else
|
||
|
error = string.Format("Request Finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
|
||
|
ws.InternalRequest.Response.StatusCode,
|
||
|
ws.InternalRequest.Response.Message,
|
||
|
ws.InternalRequest.Response.DataAsText);
|
||
|
break;
|
||
|
|
||
|
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
|
||
|
case HTTPRequestStates.Error:
|
||
|
error = "Request Finished with Error! : " + ws.InternalRequest.Exception != null ? (ws.InternalRequest.Exception.Message + " " + ws.InternalRequest.Exception.StackTrace) : string.Empty;
|
||
|
break;
|
||
|
|
||
|
// The request aborted, initiated by the user.
|
||
|
case HTTPRequestStates.Aborted:
|
||
|
error = "Request Aborted!";
|
||
|
break;
|
||
|
|
||
|
// Connecting to the server is timed out.
|
||
|
case HTTPRequestStates.ConnectionTimedOut:
|
||
|
error = "Connection Timed Out!";
|
||
|
break;
|
||
|
|
||
|
// The request didn't finished in the given time.
|
||
|
case HTTPRequestStates.TimedOut:
|
||
|
error = "Processing the request Timed Out!";
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if (Manager.UpgradingTransport != this)
|
||
|
(Manager as IManager).OnTransportError(this, error);
|
||
|
else
|
||
|
Manager.UpgradingTransport = null;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// WebSocket implementation OnClosed event handler.
|
||
|
/// </summary>
|
||
|
private void OnClosed(WebSocket ws, ushort code, string message)
|
||
|
{
|
||
|
if (ws != Implementation)
|
||
|
return;
|
||
|
|
||
|
HTTPManager.Logger.Information("WebSocketTransport", "OnClosed");
|
||
|
|
||
|
Close();
|
||
|
|
||
|
if (Manager.UpgradingTransport != this)
|
||
|
(Manager as IManager).TryToReconnect();
|
||
|
else
|
||
|
Manager.UpgradingTransport = null;
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Packet Sending Implementation
|
||
|
|
||
|
/// <summary>
|
||
|
/// A WebSocket implementation of the packet sending.
|
||
|
/// </summary>
|
||
|
public void Send(Packet packet)
|
||
|
{
|
||
|
if (State == TransportStates.Closed ||
|
||
|
State == TransportStates.Paused)
|
||
|
{
|
||
|
HTTPManager.Logger.Information("WebSocketTransport", string.Format("Send - State == {0}, skipping packet sending!", State));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
string encoded = packet.Encode();
|
||
|
|
||
|
if (HTTPManager.Logger.Level <= BestHTTP.Logger.Loglevels.All)
|
||
|
HTTPManager.Logger.Verbose("WebSocketTransport", "Send: " + encoded);
|
||
|
|
||
|
if (packet.AttachmentCount != 0 || (packet.Attachments != null && packet.Attachments.Count != 0))
|
||
|
{
|
||
|
if (packet.Attachments == null)
|
||
|
throw new ArgumentException("packet.Attachments are null!");
|
||
|
|
||
|
if (packet.AttachmentCount != packet.Attachments.Count)
|
||
|
throw new ArgumentException("packet.AttachmentCount != packet.Attachments.Count. Use the packet.AddAttachment function to add data to a packet!");
|
||
|
}
|
||
|
|
||
|
Implementation.Send(encoded);
|
||
|
|
||
|
if (packet.AttachmentCount != 0)
|
||
|
{
|
||
|
int maxLength = packet.Attachments[0].Length + 1;
|
||
|
for (int cv = 1; cv < packet.Attachments.Count; ++cv)
|
||
|
if ((packet.Attachments[cv].Length + 1) > maxLength)
|
||
|
maxLength = packet.Attachments[cv].Length + 1;
|
||
|
|
||
|
if (Buffer == null || Buffer.Length < maxLength)
|
||
|
Array.Resize(ref Buffer, maxLength);
|
||
|
|
||
|
for (int i = 0; i < packet.AttachmentCount; i++)
|
||
|
{
|
||
|
Buffer[0] = (byte)TransportEventTypes.Message;
|
||
|
|
||
|
Array.Copy(packet.Attachments[i], 0, Buffer, 1, packet.Attachments[i].Length);
|
||
|
|
||
|
Implementation.Send(Buffer, 0, (ulong)packet.Attachments[i].Length + 1UL);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// A WebSocket implementation of the packet sending.
|
||
|
/// </summary>
|
||
|
public void Send(List<Packet> packets)
|
||
|
{
|
||
|
for (int i = 0; i < packets.Count; ++i)
|
||
|
Send(packets[i]);
|
||
|
|
||
|
packets.Clear();
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Packet Handling
|
||
|
|
||
|
/// <summary>
|
||
|
/// Will only process packets that need to upgrade. All other packets are passed to the Manager.
|
||
|
/// </summary>
|
||
|
private void OnPacket(Packet packet)
|
||
|
{
|
||
|
switch (packet.TransportEvent)
|
||
|
{
|
||
|
case TransportEventTypes.Open:
|
||
|
if (this.State != TransportStates.Opening)
|
||
|
HTTPManager.Logger.Warning("WebSocketTransport", "Received 'Open' packet while state is '" + State.ToString() + "'");
|
||
|
else
|
||
|
State = TransportStates.Open;
|
||
|
goto default;
|
||
|
|
||
|
case TransportEventTypes.Pong:
|
||
|
// Answer for a Ping Probe.
|
||
|
if (packet.Payload == "probe")
|
||
|
{
|
||
|
State = TransportStates.Open;
|
||
|
(Manager as IManager).OnTransportProbed(this);
|
||
|
}
|
||
|
|
||
|
goto default;
|
||
|
|
||
|
default:
|
||
|
if (Manager.UpgradingTransport != this)
|
||
|
(Manager as IManager).OnPacket(packet);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
#endif
|