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.
342 lines
12 KiB
342 lines
12 KiB
#if !BESTHTTP_DISABLE_SOCKETIO |
|
#if !BESTHTTP_DISABLE_WEBSOCKET |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
|
|
namespace BestHTTP.SocketIO3.Transports |
|
{ |
|
using BestHTTP.Connections; |
|
using BestHTTP.PlatformSupport.Memory; |
|
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; } |
|
|
|
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.OnBinaryNoAlloc = OnBinaryNoAlloc; |
|
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!", this.Manager.Context); |
|
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", this.Manager.Context); |
|
|
|
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(this.Manager.Parser.CreateOutgoing(TransportEventTypes.Ping, "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, this.Manager.Context); |
|
|
|
IncomingPacket packet = IncomingPacket.Empty; |
|
try |
|
{ |
|
packet = this.Manager.Parser.Parse(this.Manager, message); |
|
|
|
if (packet.TransportEvent == TransportEventTypes.Open) |
|
{ |
|
packet.DecodedArg = BestHTTP.JSON.LitJson.JsonMapper.ToObject<HandshakeData>(packet.DecodedArg as string); |
|
} |
|
} |
|
catch (Exception ex) |
|
{ |
|
HTTPManager.Logger.Exception("WebSocketTransport", "OnMessage Packet parsing", ex, this.Manager.Context); |
|
} |
|
|
|
if (!packet.Equals(IncomingPacket.Empty)) |
|
{ |
|
try |
|
{ |
|
OnPacket(packet); |
|
} |
|
catch (Exception ex) |
|
{ |
|
HTTPManager.Logger.Exception("WebSocketTransport", "OnMessage OnPacket", ex, this.Manager.Context); |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// WebSocket implementation OnBinary event handler. |
|
/// </summary> |
|
private void OnBinaryNoAlloc(WebSocket ws, BufferSegment data) |
|
{ |
|
if (ws != Implementation) |
|
return; |
|
|
|
if (HTTPManager.Logger.Level <= BestHTTP.Logger.Loglevels.All) |
|
HTTPManager.Logger.Verbose("WebSocketTransport", "OnBinary", this.Manager.Context); |
|
|
|
IncomingPacket packet = IncomingPacket.Empty; |
|
try |
|
{ |
|
packet = this.Manager.Parser.Parse(this.Manager, data); |
|
} |
|
catch (Exception ex) |
|
{ |
|
HTTPManager.Logger.Exception("WebSocketTransport", "OnBinary Packet parsing", ex, this.Manager.Context); |
|
} |
|
|
|
if (!packet.Equals(IncomingPacket.Empty)) |
|
{ |
|
try |
|
{ |
|
OnPacket(packet); |
|
} |
|
catch (Exception ex) |
|
{ |
|
HTTPManager.Logger.Exception("WebSocketTransport", "OnBinary OnPacket", ex, this.Manager.Context); |
|
} |
|
} |
|
} |
|
|
|
/// <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", this.Manager.Context); |
|
|
|
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(OutgoingPacket packet) |
|
{ |
|
if (State == TransportStates.Closed || |
|
State == TransportStates.Paused) |
|
{ |
|
HTTPManager.Logger.Information("WebSocketTransport", string.Format("Send - State == {0}, skipping packet sending!", State), this.Manager.Context); |
|
return; |
|
} |
|
|
|
if (packet.IsBinary) |
|
Implementation.Send(packet.PayloadData.Data, (ulong)packet.PayloadData.Offset, (ulong)packet.PayloadData.Count); |
|
else |
|
Implementation.Send(packet.Payload); |
|
|
|
if (packet.Attachements != null) |
|
for (int i = 0; i < packet.Attachements.Count; ++i) |
|
Implementation.Send(packet.Attachements[i]); |
|
} |
|
|
|
/// <summary> |
|
/// A WebSocket implementation of the packet sending. |
|
/// </summary> |
|
public void Send(List<OutgoingPacket> 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(IncomingPacket packet) |
|
{ |
|
switch (packet.TransportEvent) |
|
{ |
|
case TransportEventTypes.Open: |
|
if (this.State != TransportStates.Opening) |
|
HTTPManager.Logger.Warning("WebSocketTransport", "Received 'Open' packet while state is '" + State.ToString() + "'", this.Manager.Context); |
|
else |
|
State = TransportStates.Open; |
|
goto default; |
|
|
|
case TransportEventTypes.Pong: |
|
// Answer for a Ping Probe. |
|
if ("probe".Equals(packet.DecodedArg)) |
|
{ |
|
State = TransportStates.Open; |
|
(Manager as IManager).OnTransportProbed(this); |
|
} |
|
|
|
goto default; |
|
|
|
default: |
|
if (Manager.UpgradingTransport != this) |
|
(Manager as IManager).OnPacket(packet); |
|
break; |
|
} |
|
} |
|
|
|
#endregion |
|
} |
|
} |
|
|
|
#endif |
|
#endif
|
|
|