up
This commit is contained in:
256
Assets/BestHTTP/SignalR/Transports/PollingTransport.cs
Normal file
256
Assets/BestHTTP/SignalR/Transports/PollingTransport.cs
Normal file
@@ -0,0 +1,256 @@
|
||||
#if !BESTHTTP_DISABLE_SIGNALR
|
||||
|
||||
using System;
|
||||
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.SignalR.Messages;
|
||||
|
||||
namespace BestHTTP.SignalR.Transports
|
||||
{
|
||||
public sealed class PollingTransport : PostSendTransportBase, IHeartbeat
|
||||
{
|
||||
#region Overridden Properties
|
||||
|
||||
public override bool SupportsKeepAlive { get { return false; } }
|
||||
public override TransportTypes Type { get { return TransportTypes.LongPoll; } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Privates
|
||||
|
||||
/// <summary>
|
||||
/// When we received the last poll.
|
||||
/// </summary>
|
||||
private DateTime LastPoll;
|
||||
|
||||
/// <summary>
|
||||
/// How much time we have to wait before we can send out a new poll request. This value sent by the server.
|
||||
/// </summary>
|
||||
private TimeSpan PollDelay;
|
||||
|
||||
/// <summary>
|
||||
/// How much time we wait to a poll request to finish. It's value is the server sent negotiation's ConnectionTimeout + 10sec.
|
||||
/// </summary>
|
||||
private TimeSpan PollTimeout;
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the the current poll request.
|
||||
/// </summary>
|
||||
private HTTPRequest pollRequest;
|
||||
|
||||
#endregion
|
||||
|
||||
public PollingTransport(Connection connection)
|
||||
: base("longPolling", connection)
|
||||
{
|
||||
this.LastPoll = DateTime.MinValue;
|
||||
this.PollTimeout = connection.NegotiationResult.ConnectionTimeout + TimeSpan.FromSeconds(10);
|
||||
}
|
||||
|
||||
#region Overrides from TransportBase
|
||||
|
||||
/// <summary>
|
||||
/// Polling transport specific connection logic. It's a regular GET request to the /connect path.
|
||||
/// </summary>
|
||||
public override void Connect()
|
||||
{
|
||||
HTTPManager.Logger.Information("Transport - " + this.Name, "Sending Open Request");
|
||||
|
||||
// Skip the Connecting state if we are reconnecting. If the connect succeeds, we will set the Started state directly
|
||||
if (this.State != TransportStates.Reconnecting)
|
||||
this.State = TransportStates.Connecting;
|
||||
|
||||
RequestTypes requestType = this.State == TransportStates.Reconnecting ? RequestTypes.Reconnect : RequestTypes.Connect;
|
||||
|
||||
var request = new HTTPRequest(Connection.BuildUri(requestType, this), HTTPMethods.Get, true, true, OnConnectRequestFinished);
|
||||
|
||||
Connection.PrepareRequest(request, requestType);
|
||||
|
||||
request.Send();
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
HTTPManager.Heartbeats.Unsubscribe(this);
|
||||
|
||||
if (pollRequest != null)
|
||||
{
|
||||
pollRequest.Abort();
|
||||
pollRequest = null;
|
||||
}
|
||||
|
||||
// Should we abort the send requests in the sendRequestQueue?
|
||||
}
|
||||
|
||||
protected override void Started()
|
||||
{
|
||||
LastPoll = DateTime.UtcNow;
|
||||
HTTPManager.Heartbeats.Subscribe(this);
|
||||
}
|
||||
|
||||
protected override void Aborted()
|
||||
{
|
||||
HTTPManager.Heartbeats.Unsubscribe(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Request Handlers
|
||||
|
||||
void OnConnectRequestFinished(HTTPRequest req, HTTPResponse resp)
|
||||
{
|
||||
// error reason if there is any. We will call the manager's Error function if it's not empty.
|
||||
string reason = string.Empty;
|
||||
|
||||
switch (req.State)
|
||||
{
|
||||
// The request finished without any problem.
|
||||
case HTTPRequestStates.Finished:
|
||||
if (resp.IsSuccess)
|
||||
{
|
||||
HTTPManager.Logger.Information("Transport - " + this.Name, "Connect - Request Finished Successfully! " + resp.DataAsText);
|
||||
|
||||
OnConnected();
|
||||
|
||||
IServerMessage msg = TransportBase.Parse(Connection.JsonEncoder, resp.DataAsText);
|
||||
|
||||
if (msg != null)
|
||||
{
|
||||
Connection.OnMessage(msg);
|
||||
|
||||
MultiMessage multiple = msg as MultiMessage;
|
||||
if (multiple != null && multiple.PollDelay.HasValue)
|
||||
PollDelay = multiple.PollDelay.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
reason = string.Format("Connect - Request Finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
|
||||
resp.StatusCode,
|
||||
resp.Message,
|
||||
resp.DataAsText);
|
||||
break;
|
||||
|
||||
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
|
||||
case HTTPRequestStates.Error:
|
||||
reason = "Connect - Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception");
|
||||
break;
|
||||
|
||||
// The request aborted, initiated by the user.
|
||||
case HTTPRequestStates.Aborted:
|
||||
reason = "Connect - Request Aborted!";
|
||||
break;
|
||||
|
||||
// Ceonnecting to the server is timed out.
|
||||
case HTTPRequestStates.ConnectionTimedOut:
|
||||
reason = "Connect - Connection Timed Out!";
|
||||
break;
|
||||
|
||||
// The request didn't finished in the given time.
|
||||
case HTTPRequestStates.TimedOut:
|
||||
reason = "Connect - Processing the request Timed Out!";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(reason))
|
||||
Connection.Error(reason);
|
||||
}
|
||||
|
||||
void OnPollRequestFinished(HTTPRequest req, HTTPResponse resp)
|
||||
{
|
||||
// When Stop() called on the transport.
|
||||
// In Stop() we set the pollRequest to null, but a new poll request can be made after a quick reconnection, and there is a chanse that
|
||||
// in this handler function we can null out the new request. So we return early here.
|
||||
if (req.State == HTTPRequestStates.Aborted)
|
||||
{
|
||||
HTTPManager.Logger.Warning("Transport - " + this.Name, "Poll - Request Aborted!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the pollRequest to null, now we can send out a new one
|
||||
pollRequest = null;
|
||||
|
||||
// error reason if there is any. We will call the manager's Error function if it's not empty.
|
||||
string reason = string.Empty;
|
||||
|
||||
switch (req.State)
|
||||
{
|
||||
// The request finished without any problem.
|
||||
case HTTPRequestStates.Finished:
|
||||
if (resp.IsSuccess)
|
||||
{
|
||||
HTTPManager.Logger.Information("Transport - " + this.Name, "Poll - Request Finished Successfully! " + resp.DataAsText);
|
||||
|
||||
IServerMessage msg = TransportBase.Parse(Connection.JsonEncoder, resp.DataAsText);
|
||||
|
||||
if (msg != null)
|
||||
{
|
||||
Connection.OnMessage(msg);
|
||||
|
||||
MultiMessage multiple = msg as MultiMessage;
|
||||
if (multiple != null && multiple.PollDelay.HasValue)
|
||||
PollDelay = multiple.PollDelay.Value;
|
||||
|
||||
LastPoll = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
else
|
||||
reason = string.Format("Poll - Request Finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
|
||||
resp.StatusCode,
|
||||
resp.Message,
|
||||
resp.DataAsText);
|
||||
break;
|
||||
|
||||
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
|
||||
case HTTPRequestStates.Error:
|
||||
reason = "Poll - Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception");
|
||||
break;
|
||||
|
||||
// Ceonnecting to the server is timed out.
|
||||
case HTTPRequestStates.ConnectionTimedOut:
|
||||
reason = "Poll - Connection Timed Out!";
|
||||
break;
|
||||
|
||||
// The request didn't finished in the given time.
|
||||
case HTTPRequestStates.TimedOut:
|
||||
reason = "Poll - Processing the request Timed Out!";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(reason))
|
||||
Connection.Error(reason);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Polling transport speficic function. Sends a GET request to the /poll path to receive messages.
|
||||
/// </summary>
|
||||
private void Poll()
|
||||
{
|
||||
pollRequest = new HTTPRequest(Connection.BuildUri(RequestTypes.Poll, this), HTTPMethods.Get, true, true, OnPollRequestFinished);
|
||||
|
||||
Connection.PrepareRequest(pollRequest, RequestTypes.Poll);
|
||||
|
||||
pollRequest.Timeout = this.PollTimeout;
|
||||
|
||||
pollRequest.Send();
|
||||
}
|
||||
|
||||
#region IHeartbeat Implementation
|
||||
|
||||
void IHeartbeat.OnHeartbeatUpdate(TimeSpan dif)
|
||||
{
|
||||
switch(State)
|
||||
{
|
||||
case TransportStates.Started:
|
||||
if (pollRequest == null && DateTime.UtcNow >= (LastPoll + PollDelay + Connection.NegotiationResult.LongPollDelay))
|
||||
Poll();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Assets/BestHTTP/SignalR/Transports/PollingTransport.cs.meta
Normal file
11
Assets/BestHTTP/SignalR/Transports/PollingTransport.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c045998de4c74642918c655313ad0a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
103
Assets/BestHTTP/SignalR/Transports/PostSendTransportBase.cs
Normal file
103
Assets/BestHTTP/SignalR/Transports/PostSendTransportBase.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
#if !BESTHTTP_DISABLE_SIGNALR
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BestHTTP.SignalR.Messages;
|
||||
|
||||
namespace BestHTTP.SignalR.Transports
|
||||
{
|
||||
/// <summary>
|
||||
/// A base class for implementations that must send the data in unique requests. These are currently the LongPolling and ServerSentEvents transports.
|
||||
/// </summary>
|
||||
public abstract class PostSendTransportBase : TransportBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Sent out send requests. Keeping a reference to them for future use.
|
||||
/// </summary>
|
||||
protected List<HTTPRequest> sendRequestQueue = new List<HTTPRequest>();
|
||||
|
||||
public PostSendTransportBase(string name, Connection con)
|
||||
: base(name, con)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#region Send Implementation
|
||||
|
||||
protected override void SendImpl(string json)
|
||||
{
|
||||
var request = new HTTPRequest(Connection.BuildUri(RequestTypes.Send, this), HTTPMethods.Post, true, true, OnSendRequestFinished);
|
||||
|
||||
request.FormUsage = Forms.HTTPFormUsage.UrlEncoded;
|
||||
request.AddField("data", json);
|
||||
|
||||
Connection.PrepareRequest(request, RequestTypes.Send);
|
||||
|
||||
// Set a lower priority then the default. This way requests that are sent out alongside with SignalR sent requests can be processed sooner.
|
||||
request.Priority = -1;
|
||||
|
||||
request.Send();
|
||||
|
||||
sendRequestQueue.Add(request);
|
||||
}
|
||||
|
||||
void OnSendRequestFinished(HTTPRequest req, HTTPResponse resp)
|
||||
{
|
||||
sendRequestQueue.Remove(req);
|
||||
|
||||
// error reason if there is any. We will call the manager's Error function if it's not empty.
|
||||
string reason = string.Empty;
|
||||
|
||||
switch (req.State)
|
||||
{
|
||||
// The request finished without any problem.
|
||||
case HTTPRequestStates.Finished:
|
||||
if (resp.IsSuccess)
|
||||
{
|
||||
HTTPManager.Logger.Information("Transport - " + this.Name, "Send - Request Finished Successfully! " + resp.DataAsText);
|
||||
|
||||
if (!string.IsNullOrEmpty(resp.DataAsText))
|
||||
{
|
||||
IServerMessage msg = TransportBase.Parse(Connection.JsonEncoder, resp.DataAsText);
|
||||
|
||||
if (msg != null)
|
||||
Connection.OnMessage(msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
reason = string.Format("Send - Request Finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
|
||||
resp.StatusCode,
|
||||
resp.Message,
|
||||
resp.DataAsText);
|
||||
break;
|
||||
|
||||
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
|
||||
case HTTPRequestStates.Error:
|
||||
reason = "Send - Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception");
|
||||
break;
|
||||
|
||||
// The request aborted, initiated by the user.
|
||||
case HTTPRequestStates.Aborted:
|
||||
reason = "Send - Request Aborted!";
|
||||
break;
|
||||
|
||||
// Connecting to the server is timed out.
|
||||
case HTTPRequestStates.ConnectionTimedOut:
|
||||
reason = "Send - Connection Timed Out!";
|
||||
break;
|
||||
|
||||
// The request didn't finished in the given time.
|
||||
case HTTPRequestStates.TimedOut:
|
||||
reason = "Send - Processing the request Timed Out!";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(reason))
|
||||
Connection.Error(reason);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dcb0508f850d744138a334e9d6004d2d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
171
Assets/BestHTTP/SignalR/Transports/ServerSentEventsTransport.cs
Normal file
171
Assets/BestHTTP/SignalR/Transports/ServerSentEventsTransport.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
#if !BESTHTTP_DISABLE_SIGNALR
|
||||
#if !BESTHTTP_DISABLE_SERVERSENT_EVENTS
|
||||
|
||||
using System;
|
||||
|
||||
using BestHTTP.ServerSentEvents;
|
||||
using BestHTTP.SignalR.Messages;
|
||||
|
||||
namespace BestHTTP.SignalR.Transports
|
||||
{
|
||||
/// <summary>
|
||||
/// A SignalR transport implementation that uses the Server-Sent Events protocol.
|
||||
/// </summary>
|
||||
public sealed class ServerSentEventsTransport : PostSendTransportBase
|
||||
{
|
||||
#region Overridden Properties
|
||||
|
||||
/// <summary>
|
||||
/// It's a persistent connection. We support the keep-alive mechanism.
|
||||
/// </summary>
|
||||
public override bool SupportsKeepAlive { get { return true; } }
|
||||
|
||||
/// <summary>
|
||||
/// Type of the transport.
|
||||
/// </summary>
|
||||
public override TransportTypes Type { get { return TransportTypes.ServerSentEvents; } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Privates
|
||||
|
||||
/// <summary>
|
||||
/// The EventSource object.
|
||||
/// </summary>
|
||||
private EventSource EventSource;
|
||||
|
||||
#endregion
|
||||
|
||||
public ServerSentEventsTransport(Connection con)
|
||||
: base("serverSentEvents", con)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#region Overrides from TransportBase
|
||||
|
||||
public override void Connect()
|
||||
{
|
||||
if (EventSource != null)
|
||||
{
|
||||
HTTPManager.Logger.Warning("ServerSentEventsTransport", "Start - EventSource already created!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip the Connecting state if we are reconnecting. If the connect succeeds, we will set the Started state directly
|
||||
if (this.State != TransportStates.Reconnecting)
|
||||
this.State = TransportStates.Connecting;
|
||||
|
||||
RequestTypes requestType = this.State == TransportStates.Reconnecting ? RequestTypes.Reconnect : RequestTypes.Connect;
|
||||
|
||||
Uri uri = Connection.BuildUri(requestType, this);
|
||||
|
||||
EventSource = new EventSource(uri);
|
||||
|
||||
EventSource.OnOpen += OnEventSourceOpen;
|
||||
EventSource.OnMessage += OnEventSourceMessage;
|
||||
EventSource.OnError += OnEventSourceError;
|
||||
EventSource.OnClosed += OnEventSourceClosed;
|
||||
|
||||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
// Disable internal retry
|
||||
EventSource.OnRetry += (es) => false;
|
||||
#endif
|
||||
|
||||
// Start connecting to the server.
|
||||
EventSource.Open();
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
EventSource.OnOpen -= OnEventSourceOpen;
|
||||
EventSource.OnMessage -= OnEventSourceMessage;
|
||||
EventSource.OnError -= OnEventSourceError;
|
||||
EventSource.OnClosed -= OnEventSourceClosed;
|
||||
|
||||
EventSource.Close();
|
||||
|
||||
EventSource = null;
|
||||
}
|
||||
|
||||
protected override void Started()
|
||||
{
|
||||
// Nothing to do here for this transport
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom Abort function where we will start to close the EventSource object.
|
||||
/// </summary>
|
||||
public override void Abort()
|
||||
{
|
||||
base.Abort();
|
||||
|
||||
EventSource.Close();
|
||||
}
|
||||
|
||||
protected override void Aborted()
|
||||
{
|
||||
if (this.State == TransportStates.Closing)
|
||||
this.State = TransportStates.Closed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region EventSource Event Handlers
|
||||
|
||||
private void OnEventSourceOpen(EventSource eventSource)
|
||||
{
|
||||
HTTPManager.Logger.Information("Transport - " + this.Name, "OnEventSourceOpen");
|
||||
}
|
||||
|
||||
private void OnEventSourceMessage(EventSource eventSource, BestHTTP.ServerSentEvents.Message message)
|
||||
{
|
||||
if (message.Data.Equals("initialized"))
|
||||
{
|
||||
base.OnConnected();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
IServerMessage msg = TransportBase.Parse(Connection.JsonEncoder, message.Data);
|
||||
|
||||
if (msg != null)
|
||||
Connection.OnMessage(msg);
|
||||
|
||||
}
|
||||
|
||||
private void OnEventSourceError(EventSource eventSource, string error)
|
||||
{
|
||||
HTTPManager.Logger.Information("Transport - " + this.Name, "OnEventSourceError");
|
||||
|
||||
// We are in a reconnecting phase, we have to connect now.
|
||||
if (this.State == TransportStates.Reconnecting)
|
||||
{
|
||||
Connect();
|
||||
return;
|
||||
}
|
||||
|
||||
// Already closed?
|
||||
if (this.State == TransportStates.Closed)
|
||||
return;
|
||||
|
||||
// Closing? Then we are closed now.
|
||||
if (this.State == TransportStates.Closing)
|
||||
this.State = TransportStates.Closed;
|
||||
else // Errored when we didn't expected it.
|
||||
Connection.Error(error);
|
||||
}
|
||||
|
||||
private void OnEventSourceClosed(ServerSentEvents.EventSource eventSource)
|
||||
{
|
||||
HTTPManager.Logger.Information("Transport - " + this.Name, "OnEventSourceClosed");
|
||||
|
||||
OnEventSourceError(eventSource, "EventSource Closed!");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 162ff5b00b4dc4dcb8a44c017590605e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
378
Assets/BestHTTP/SignalR/Transports/TransportBase.cs
Normal file
378
Assets/BestHTTP/SignalR/Transports/TransportBase.cs
Normal file
@@ -0,0 +1,378 @@
|
||||
#if !BESTHTTP_DISABLE_SIGNALR
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BestHTTP.SignalR.Messages;
|
||||
using BestHTTP.SignalR.JsonEncoders;
|
||||
|
||||
namespace BestHTTP.SignalR.Transports
|
||||
{
|
||||
public delegate void OnTransportStateChangedDelegate(TransportBase transport, TransportStates oldState, TransportStates newState);
|
||||
|
||||
public abstract class TransportBase
|
||||
{
|
||||
private const int MaxRetryCount = 5;
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Name of the transport.
|
||||
/// </summary>
|
||||
public string Name { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the manager has to check the last message received time and reconnect if too much time passes.
|
||||
/// </summary>
|
||||
public abstract bool SupportsKeepAlive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of the transport. Used mainly by the manager in its BuildUri function.
|
||||
/// </summary>
|
||||
public abstract TransportTypes Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the manager.
|
||||
/// </summary>
|
||||
public IConnection Connection { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current state of the transport.
|
||||
/// </summary>
|
||||
public TransportStates State
|
||||
{
|
||||
get { return _state; }
|
||||
protected set
|
||||
{
|
||||
TransportStates old = _state;
|
||||
_state = value;
|
||||
|
||||
if (OnStateChanged != null)
|
||||
OnStateChanged(this, old, _state);
|
||||
}
|
||||
}
|
||||
public TransportStates _state;
|
||||
|
||||
/// <summary>
|
||||
/// Thi event called when the transport's State set to a new value.
|
||||
/// </summary>
|
||||
public event OnTransportStateChangedDelegate OnStateChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
public TransportBase(string name, Connection connection)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Connection = connection;
|
||||
this.State = TransportStates.Initial;
|
||||
}
|
||||
|
||||
#region Abstract functions
|
||||
|
||||
/// <summary>
|
||||
/// Start to connect to the server
|
||||
/// </summary>
|
||||
public abstract void Connect();
|
||||
|
||||
/// <summary>
|
||||
/// Stop the connection
|
||||
/// </summary>
|
||||
public abstract void Stop();
|
||||
|
||||
/// <summary>
|
||||
/// The transport specific implementation to send the given json string to the server.
|
||||
/// </summary>
|
||||
protected abstract void SendImpl(string json);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the Start request finished successfully, or after a reconnect.
|
||||
/// Manager.TransportOpened(); called from the TransportBase after this call
|
||||
/// </summary>
|
||||
protected abstract void Started();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the abort request finished successfully.
|
||||
/// </summary>
|
||||
protected abstract void Aborted();
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Called after a succesful connect/reconnect. The transport implementations have to call this function.
|
||||
/// </summary>
|
||||
protected void OnConnected()
|
||||
{
|
||||
if (this.State != TransportStates.Reconnecting)
|
||||
{
|
||||
// Send the Start request
|
||||
Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
Connection.TransportReconnected();
|
||||
|
||||
Started();
|
||||
|
||||
this.State = TransportStates.Started;
|
||||
}
|
||||
}
|
||||
|
||||
#region Start Request Sending
|
||||
|
||||
/// <summary>
|
||||
/// Sends out the /start request to the server.
|
||||
/// </summary>
|
||||
protected void Start()
|
||||
{
|
||||
HTTPManager.Logger.Information("Transport - " + this.Name, "Sending Start Request");
|
||||
|
||||
this.State = TransportStates.Starting;
|
||||
|
||||
if (this.Connection.Protocol > ProtocolVersions.Protocol_2_0)
|
||||
{
|
||||
var request = new HTTPRequest(Connection.BuildUri(RequestTypes.Start, this), HTTPMethods.Get, true, true, OnStartRequestFinished);
|
||||
|
||||
request.Tag = 0;
|
||||
request.DisableRetry = true;
|
||||
|
||||
request.Timeout = Connection.NegotiationResult.ConnectionTimeout + TimeSpan.FromSeconds(10);
|
||||
|
||||
Connection.PrepareRequest(request, RequestTypes.Start);
|
||||
|
||||
request.Send();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The transport and the signalr protocol now started
|
||||
this.State = TransportStates.Started;
|
||||
|
||||
Started();
|
||||
|
||||
Connection.TransportStarted();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStartRequestFinished(HTTPRequest req, HTTPResponse resp)
|
||||
{
|
||||
switch (req.State)
|
||||
{
|
||||
case HTTPRequestStates.Finished:
|
||||
if (resp.IsSuccess)
|
||||
{
|
||||
HTTPManager.Logger.Information("Transport - " + this.Name, "Start - Returned: " + resp.DataAsText);
|
||||
|
||||
string response = Connection.ParseResponse(resp.DataAsText);
|
||||
|
||||
if (response != "started")
|
||||
{
|
||||
Connection.Error(string.Format("Expected 'started' response, but '{0}' found!", response));
|
||||
return;
|
||||
}
|
||||
|
||||
// The transport and the signalr protocol now started
|
||||
this.State = TransportStates.Started;
|
||||
|
||||
Started();
|
||||
|
||||
Connection.TransportStarted();
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
HTTPManager.Logger.Warning("Transport - " + this.Name, string.Format("Start - request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2} Uri: {3}",
|
||||
resp.StatusCode,
|
||||
resp.Message,
|
||||
resp.DataAsText,
|
||||
req.CurrentUri));
|
||||
goto default;
|
||||
|
||||
default:
|
||||
HTTPManager.Logger.Information("Transport - " + this.Name, "Start request state: " + req.State.ToString());
|
||||
|
||||
// The request may not reached the server. Try it again.
|
||||
int retryCount = (int)req.Tag;
|
||||
if (retryCount++ < MaxRetryCount)
|
||||
{
|
||||
req.Tag = retryCount;
|
||||
req.Send();
|
||||
}
|
||||
else
|
||||
Connection.Error("Failed to send Start request.");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Abort Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Will abort the transport. In SignalR 'Abort'ing is a graceful process, while 'Close'ing is a hard-abortion...
|
||||
/// </summary>
|
||||
public virtual void Abort()
|
||||
{
|
||||
if (this.State != TransportStates.Started)
|
||||
return;
|
||||
|
||||
this.State = TransportStates.Closing;
|
||||
|
||||
var request = new HTTPRequest(Connection.BuildUri(RequestTypes.Abort, this), HTTPMethods.Get, true, true, OnAbortRequestFinished);
|
||||
|
||||
// Retry counter
|
||||
request.Tag = 0;
|
||||
request.DisableRetry = true;
|
||||
|
||||
Connection.PrepareRequest(request, RequestTypes.Abort);
|
||||
|
||||
request.Send();
|
||||
}
|
||||
|
||||
protected void AbortFinished()
|
||||
{
|
||||
this.State = TransportStates.Closed;
|
||||
|
||||
Connection.TransportAborted();
|
||||
|
||||
this.Aborted();
|
||||
}
|
||||
|
||||
private void OnAbortRequestFinished(HTTPRequest req, HTTPResponse resp)
|
||||
{
|
||||
switch (req.State)
|
||||
{
|
||||
case HTTPRequestStates.Finished:
|
||||
if (resp.IsSuccess)
|
||||
{
|
||||
HTTPManager.Logger.Information("Transport - " + this.Name, "Abort - Returned: " + resp.DataAsText);
|
||||
|
||||
if (this.State == TransportStates.Closing)
|
||||
AbortFinished();
|
||||
}
|
||||
else
|
||||
{
|
||||
HTTPManager.Logger.Warning("Transport - " + this.Name, string.Format("Abort - Handshake request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2} Uri: {3}",
|
||||
resp.StatusCode,
|
||||
resp.Message,
|
||||
resp.DataAsText,
|
||||
req.CurrentUri));
|
||||
|
||||
// try again
|
||||
goto default;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
HTTPManager.Logger.Information("Transport - " + this.Name, "Abort request state: " + req.State.ToString());
|
||||
|
||||
// The request may not reached the server. Try it again.
|
||||
int retryCount = (int)req.Tag;
|
||||
if (retryCount++ < MaxRetryCount)
|
||||
{
|
||||
req.Tag = retryCount;
|
||||
req.Send();
|
||||
}
|
||||
else
|
||||
Connection.Error("Failed to send Abort request!");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Send Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Sends the given json string to the wire.
|
||||
/// </summary>
|
||||
/// <param name="jsonStr"></param>
|
||||
public void Send(string jsonStr)
|
||||
{
|
||||
try
|
||||
{
|
||||
HTTPManager.Logger.Information("Transport - " + this.Name, "Sending: " + jsonStr);
|
||||
|
||||
SendImpl(jsonStr);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("Transport - " + this.Name, "Send", ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Functions
|
||||
|
||||
/// <summary>
|
||||
/// Start the reconnect process
|
||||
/// </summary>
|
||||
public void Reconnect()
|
||||
{
|
||||
HTTPManager.Logger.Information("Transport - " + this.Name, "Reconnecting");
|
||||
|
||||
Stop();
|
||||
|
||||
this.State = TransportStates.Reconnecting;
|
||||
|
||||
Connect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the json string is successfully parsed will return with an IServerMessage implementation.
|
||||
/// </summary>
|
||||
public static IServerMessage Parse(IJsonEncoder encoder, string json)
|
||||
{
|
||||
// Nothing to parse?
|
||||
if (string.IsNullOrEmpty(json))
|
||||
{
|
||||
HTTPManager.Logger.Error("MessageFactory", "Parse - called with empty or null string!");
|
||||
return null;
|
||||
}
|
||||
|
||||
// We don't have to do further decoding, if it's an empty json object, then it's a KeepAlive message from the server
|
||||
if (json.Length == 2 && json == "{}")
|
||||
return new KeepAliveMessage();
|
||||
|
||||
IDictionary<string, object> msg = null;
|
||||
|
||||
try
|
||||
{
|
||||
// try to decode the json message with the encoder
|
||||
msg = encoder.DecodeMessage(json);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
HTTPManager.Logger.Exception("MessageFactory", "Parse - encoder.DecodeMessage", ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (msg == null)
|
||||
{
|
||||
HTTPManager.Logger.Error("MessageFactory", "Parse - Json Decode failed for json string: \"" + json + "\"");
|
||||
return null;
|
||||
}
|
||||
|
||||
// "C" is for message id
|
||||
IServerMessage result = null;
|
||||
if (!msg.ContainsKey("C"))
|
||||
{
|
||||
// If there are no ErrorMessage in the object, then it was a success
|
||||
if (!msg.ContainsKey("E"))
|
||||
result = new ResultMessage();
|
||||
else
|
||||
result = new FailureMessage();
|
||||
}
|
||||
else
|
||||
result = new MultiMessage();
|
||||
|
||||
result.Parse(msg);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Assets/BestHTTP/SignalR/Transports/TransportBase.cs.meta
Normal file
11
Assets/BestHTTP/SignalR/Transports/TransportBase.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e70b72ca739249898fee66e08da5bda
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
172
Assets/BestHTTP/SignalR/Transports/WebSocketTransport.cs
Normal file
172
Assets/BestHTTP/SignalR/Transports/WebSocketTransport.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
#if !BESTHTTP_DISABLE_SIGNALR
|
||||
#if !BESTHTTP_DISABLE_WEBSOCKET
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
using BestHTTP;
|
||||
using BestHTTP.JSON;
|
||||
using BestHTTP.SignalR.Hubs;
|
||||
using BestHTTP.SignalR.Messages;
|
||||
using BestHTTP.SignalR.JsonEncoders;
|
||||
|
||||
namespace BestHTTP.SignalR.Transports
|
||||
{
|
||||
public sealed class WebSocketTransport : TransportBase
|
||||
{
|
||||
#region Overridden Properties
|
||||
|
||||
public override bool SupportsKeepAlive { get { return true; } }
|
||||
public override TransportTypes Type { get { return TransportTypes.WebSocket; } }
|
||||
|
||||
#endregion
|
||||
|
||||
private WebSocket.WebSocket wSocket;
|
||||
|
||||
public WebSocketTransport(Connection connection)
|
||||
: base("webSockets", connection)
|
||||
{
|
||||
}
|
||||
|
||||
#region Overrides from TransportBase
|
||||
|
||||
/// <summary>
|
||||
/// Websocket transport specific connection logic. It will create a WebSocket instance, and starts to connect to the server.
|
||||
/// </summary>
|
||||
public override void Connect()
|
||||
{
|
||||
if (wSocket != null)
|
||||
{
|
||||
HTTPManager.Logger.Warning("WebSocketTransport", "Start - WebSocket already created!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip the Connecting state if we are reconnecting. If the connect succeeds, we will set the Started state directly
|
||||
if (this.State != TransportStates.Reconnecting)
|
||||
this.State = TransportStates.Connecting;
|
||||
|
||||
RequestTypes requestType = this.State == TransportStates.Reconnecting ? RequestTypes.Reconnect : RequestTypes.Connect;
|
||||
|
||||
Uri uri = Connection.BuildUri(requestType, this);
|
||||
|
||||
// Create the WebSocket instance
|
||||
wSocket = new WebSocket.WebSocket(uri);
|
||||
|
||||
// Set up eventhandlers
|
||||
wSocket.OnOpen += WSocket_OnOpen;
|
||||
wSocket.OnMessage += WSocket_OnMessage;
|
||||
wSocket.OnClosed += WSocket_OnClosed;
|
||||
wSocket.OnErrorDesc += WSocket_OnError;
|
||||
|
||||
#if !UNITY_WEBGL || UNITY_EDITOR
|
||||
// prepare the internal http request
|
||||
Connection.PrepareRequest(wSocket.InternalRequest, requestType);
|
||||
#endif
|
||||
|
||||
// start opening the websocket protocol
|
||||
wSocket.Open();
|
||||
}
|
||||
|
||||
protected override void SendImpl(string json)
|
||||
{
|
||||
if (wSocket != null && wSocket.IsOpen)
|
||||
wSocket.Send(json);
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
if (wSocket != null)
|
||||
{
|
||||
wSocket.OnOpen = null;
|
||||
wSocket.OnMessage = null;
|
||||
wSocket.OnClosed = null;
|
||||
wSocket.OnErrorDesc = null;
|
||||
wSocket.Close();
|
||||
wSocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Started()
|
||||
{
|
||||
// Nothing to be done here for this transport
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The /abort request successfully finished
|
||||
/// </summary>
|
||||
protected override void Aborted()
|
||||
{
|
||||
// if the websocket is still open, close it
|
||||
if (wSocket != null && wSocket.IsOpen)
|
||||
{
|
||||
wSocket.Close();
|
||||
wSocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region WebSocket Events
|
||||
|
||||
void WSocket_OnOpen(WebSocket.WebSocket webSocket)
|
||||
{
|
||||
if (webSocket != wSocket)
|
||||
return;
|
||||
|
||||
HTTPManager.Logger.Information("WebSocketTransport", "WSocket_OnOpen");
|
||||
|
||||
OnConnected();
|
||||
}
|
||||
|
||||
void WSocket_OnMessage(WebSocket.WebSocket webSocket, string message)
|
||||
{
|
||||
if (webSocket != wSocket)
|
||||
return;
|
||||
|
||||
IServerMessage msg = TransportBase.Parse(Connection.JsonEncoder, message);
|
||||
|
||||
if (msg != null)
|
||||
Connection.OnMessage(msg);
|
||||
}
|
||||
|
||||
void WSocket_OnClosed(WebSocket.WebSocket webSocket, ushort code, string message)
|
||||
{
|
||||
if (webSocket != wSocket)
|
||||
return;
|
||||
|
||||
string reason = code.ToString() + " : " + message;
|
||||
|
||||
HTTPManager.Logger.Information("WebSocketTransport", "WSocket_OnClosed " + reason);
|
||||
|
||||
if (this.State == TransportStates.Closing)
|
||||
this.State = TransportStates.Closed;
|
||||
else
|
||||
Connection.Error(reason);
|
||||
}
|
||||
|
||||
void WSocket_OnError(WebSocket.WebSocket webSocket, string reason)
|
||||
{
|
||||
if (webSocket != wSocket)
|
||||
return;
|
||||
|
||||
// On WP8.1, somehow we receive an exception that the remote server forcibly closed the connection instead of the
|
||||
// WebSocket closed packet... Also, even the /abort request didn't finished.
|
||||
if (this.State == TransportStates.Closing ||
|
||||
this.State == TransportStates.Closed)
|
||||
{
|
||||
base.AbortFinished();
|
||||
}
|
||||
else
|
||||
{
|
||||
HTTPManager.Logger.Error("WebSocketTransport", "WSocket_OnError " + reason);
|
||||
|
||||
Connection.Error(reason);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8512b91bd1b34464ba25f2edbb886b31
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user