提交Unity 联机Pro

This commit is contained in:
PC-20230316NUNE\Administrator
2024-08-17 14:27:18 +08:00
parent f00193b000
commit 894100ae37
7448 changed files with 854473 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 008d5591a2cd6704fb39796bd33ff71a
folderAsset: yes
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,37 @@
#if !BESTHTTP_DISABLE_SIGNALR
namespace BestHTTP.SignalR.Authentication
{
public delegate void OnAuthenticationSuccededDelegate(IAuthenticationProvider provider);
public delegate void OnAuthenticationFailedDelegate(IAuthenticationProvider provider, string reason);
public interface IAuthenticationProvider
{
/// <summary>
/// The authentication must be run before any request made to build up the SignalR protocol
/// </summary>
bool IsPreAuthRequired { get; }
/// <summary>
/// This event must be called when the pre-authentication succeded. When IsPreAuthRequired is false, no-one will subscribe to this event.
/// </summary>
event OnAuthenticationSuccededDelegate OnAuthenticationSucceded;
/// <summary>
/// This event must be called when the pre-authentication failed. When IsPreAuthRequired is false, no-one will subscribe to this event.
/// </summary>
event OnAuthenticationFailedDelegate OnAuthenticationFailed;
/// <summary>
/// This function called once, when the before the SignalR negotiation begins. If IsPreAuthRequired is false, then this step will be skipped.
/// </summary>
void StartAuthentication();
/// <summary>
/// This function will be called for every request before sending it.
/// </summary>
void PrepareRequest(HTTPRequest request, RequestTypes type);
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: e95e3e74ee7feaf4b872a3d4d23dc0a6
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 878e20e1ec06ef542946893b5e37c3b6
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,196 @@
#if !BESTHTTP_DISABLE_SIGNALR
namespace BestHTTP.SignalR
{
/// <summary>
/// Possible transport types.
/// </summary>
public enum TransportTypes
{
/// <summary>
/// Transport using WebSockets.
/// </summary>
WebSocket,
/// <summary>
/// Transport using ServerSentEvents protocol.
/// </summary>
ServerSentEvents,
/// <summary>
/// Transport using long-polling requests.
/// </summary>
LongPoll
}
/// <summary>
/// Server sent message types
/// </summary>
public enum MessageTypes
{
/// <summary>
/// An empty json object {} sent by the server to check keep alive.
/// </summary>
KeepAlive,
/// <summary>
/// A no-hub message that contains data.
/// </summary>
Data,
/// <summary>
/// A message that can hold multiple data message alongside with other information.
/// </summary>
Multiple,
/// <summary>
/// A method call result.
/// </summary>
Result,
/// <summary>
/// A message about a failed method call.
/// </summary>
Failure,
/// <summary>
/// A message with all information to be able to call a method on the client.
/// </summary>
MethodCall,
/// <summary>
/// A long running server-method's progress.
/// </summary>
Progress
}
/// <summary>
/// Possible SignalR Connection states.
/// </summary>
public enum ConnectionStates
{
/// <summary>
/// The initial state of the connection.
/// </summary>
Initial,
/// <summary>
/// The client authenticates itself with the server. This state is skipped if no AuthenticationProvider is present.
/// </summary>
Authenticating,
/// <summary>
/// The client sent out the negotiation request to the server.
/// </summary>
Negotiating,
/// <summary>
/// The client received the negotiation data, created the transport and wait's for the transport's connection.
/// </summary>
Connecting,
/// <summary>
/// The transport connected and started successfully.
/// </summary>
Connected,
/// <summary>
/// The client started the reconnect process.
/// </summary>
Reconnecting,
/// <summary>
/// The connection is closed.
/// </summary>
Closed
}
/// <summary>
/// Possible types of SignalR requests.
/// </summary>
public enum RequestTypes
{
/// <summary>
/// Request to the /negotiate path to negotiate protocol parameters.
/// </summary>
Negotiate,
/// <summary>
/// Request to the /connect path to connect to the server. With long-polling, it's like a regular poll request.
/// </summary>
Connect,
/// <summary>
/// Request to the /start path to start the protocol.
/// </summary>
Start,
/// <summary>
/// Request to the /poll path to get new messages. Not used with the WebSocketTransport.
/// </summary>
Poll,
/// <summary>
/// Request to the /send path to send a message to the server. Not used with the WebSocketTransport.
/// </summary>
Send,
/// <summary>
/// Request to the /reconnect path to initiate a reconnection. It's used instead of the Connect type.
/// </summary>
Reconnect,
/// <summary>
/// Request to the /abort path to close the connection.
/// </summary>
Abort,
/// <summary>
/// Request to the /ping path to ping the server keeping the asp.net session alive.
/// </summary>
Ping
}
/// <summary>
/// Possible states of a transport.
/// </summary>
public enum TransportStates
{
/// <summary>
/// Initial state
/// </summary>
Initial,
/// <summary>
/// Connecting
/// </summary>
Connecting,
/// <summary>
/// Reconnecting
/// </summary>
Reconnecting,
/// <summary>
/// Sending Start request
/// </summary>
Starting,
/// <summary>
/// Start request finished successfully
/// </summary>
Started,
/// <summary>
/// Sending Abort request
/// </summary>
Closing,
/// <summary>
/// The transport closed after Abort request sent
/// </summary>
Closed
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: fafa6e4c8bf63324c82241199e91f437
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7208796729371c74c9d7c3398b19f8f3
folderAsset: yes
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,391 @@
#if !BESTHTTP_DISABLE_SIGNALR
using System;
using System.Collections.Generic;
using BestHTTP.SignalR.Messages;
using System.Text;
namespace BestHTTP.SignalR.Hubs
{
public delegate void OnMethodCallDelegate(Hub hub, string method, params object[] args);
public delegate void OnMethodCallCallbackDelegate(Hub hub, MethodCallMessage methodCall);
public delegate void OnMethodResultDelegate(Hub hub, ClientMessage originalMessage, ResultMessage result);
public delegate void OnMethodFailedDelegate(Hub hub, ClientMessage originalMessage, FailureMessage error);
public delegate void OnMethodProgressDelegate(Hub hub, ClientMessage originialMessage, ProgressMessage progress);
/// <summary>
/// Represents a clientside Hub. This class can be used as a base class to encapsulate proxy functionalities.
/// </summary>
public class Hub : IHub
{
#region Public Properties
/// <summary>
/// Name of this hub.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Server and user set state of the hub.
/// </summary>
public Dictionary<string, object> State
{
// Create only when we need to.
get
{
if (state == null)
state = new Dictionary<string, object>();
return state;
}
}
private Dictionary<string, object> state;
/// <summary>
/// Event called every time when the server sends an order to call a method on the client.
/// </summary>
public event OnMethodCallDelegate OnMethodCall;
#endregion
#region Privates
/// <summary>
/// Table of the sent messages. These messages will be removed from this table when a Result message is received from the server.
/// </summary>
private Dictionary<UInt64, ClientMessage> SentMessages = new Dictionary<ulong, ClientMessage>();
/// <summary>
/// Methodname -> callback delegate mapping. This table stores the server callable functions.
/// </summary>
private Dictionary<string, OnMethodCallCallbackDelegate> MethodTable = new Dictionary<string, OnMethodCallCallbackDelegate>();
/// <summary>
/// A reusable StringBuilder to save some GC allocs
/// </summary>
private StringBuilder builder = new StringBuilder();
#endregion
Connection IHub.Connection { get; set; }
public Hub(string name)
:this(name, null)
{
}
public Hub(string name, Connection manager)
{
this.Name = name;
(this as IHub).Connection = manager;
}
#region Public Hub Functions
/// <summary>
/// Registers a callback function to the given method.
/// </summary>
public void On(string method, OnMethodCallCallbackDelegate callback)
{
MethodTable[method] = callback;
}
/// <summary>
/// Removes callback from the given method.
/// </summary>
/// <param name="method"></param>
public void Off(string method)
{
MethodTable[method] = null;
}
/// <summary>
/// Orders the server to call a method with the given arguments.
/// </summary>
/// <returns>True if the plugin was able to send out the message</returns>
public bool Call(string method, params object[] args)
{
return Call(method, null, null, null, args);
}
/// <summary>
/// Orders the server to call a method with the given arguments.
/// The onResult callback will be called when the server successfully called the function.
/// </summary>
/// <returns>True if the plugin was able to send out the message</returns>
public bool Call(string method, OnMethodResultDelegate onResult, params object[] args)
{
return Call(method, onResult, null, null, args);
}
/// <summary>
/// Orders the server to call a method with the given arguments.
/// The onResult callback will be called when the server successfully called the function.
/// The onResultError will be called when the server can't call the function, or when the function throws an exception.
/// </summary>
/// <returns>True if the plugin was able to send out the message</returns>
public bool Call(string method, OnMethodResultDelegate onResult, OnMethodFailedDelegate onResultError, params object[] args)
{
return Call(method, onResult, onResultError, null, args);
}
/// <summary>
/// Orders the server to call a method with the given arguments.
/// The onResult callback will be called when the server successfully called the function.
/// The onProgress callback called multiple times when the method is a long running function and reports back its progress.
/// </summary>
/// <returns>True if the plugin was able to send out the message</returns>
public bool Call(string method, OnMethodResultDelegate onResult, OnMethodProgressDelegate onProgress, params object[] args)
{
return Call(method, onResult, null, onProgress, args);
}
/// <summary>
/// Orders the server to call a method with the given arguments.
/// The onResult callback will be called when the server successfully called the function.
/// The onResultError will be called when the server can't call the function, or when the function throws an exception.
/// The onProgress callback called multiple times when the method is a long running function and reports back its progress.
/// </summary>
/// <returns>True if the plugin was able to send out the message</returns>
public bool Call(string method, OnMethodResultDelegate onResult, OnMethodFailedDelegate onResultError, OnMethodProgressDelegate onProgress, params object[] args)
{
IHub thisHub = this as IHub;
// Start over the counter if we are reached the max value if the long type.
// While we are using this property only here, we don't want to make it static to avoid another thread synchronization, neither we want to make it a Hub-instance field to achieve better debuggability.
long newValue, originalValue;
do
{
originalValue = thisHub.Connection.ClientMessageCounter;
newValue = (originalValue % long.MaxValue) + 1;
} while (System.Threading.Interlocked.CompareExchange(ref thisHub.Connection.ClientMessageCounter, newValue, originalValue) != originalValue);
// Create and send the client message
return thisHub.Call(new ClientMessage(this, method, args, (ulong)thisHub.Connection.ClientMessageCounter, onResult, onResultError, onProgress));
}
#endregion
#region IHub Implementation
bool IHub.Call(ClientMessage msg)
{
IHub thisHub = this as IHub;
if (!thisHub.Connection.SendJson(BuildMessage(msg)))
return false;
SentMessages.Add(msg.CallIdx, msg);
return true;
}
/// <summary>
/// Return true if this hub sent the message with the given id.
/// </summary>
bool IHub.HasSentMessageId(UInt64 id)
{
return SentMessages.ContainsKey(id);
}
/// <summary>
/// Called on the manager's close.
/// </summary>
void IHub.Close()
{
SentMessages.Clear();
}
/// <summary>
/// Called when the client receives an order to call a hub-function.
/// </summary>
void IHub.OnMethod(MethodCallMessage msg)
{
// Merge the newly received states with the old one
MergeState(msg.State);
if (OnMethodCall != null)
{
try
{
OnMethodCall(this, msg.Method, msg.Arguments);
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("Hub - " + this.Name, "IHub.OnMethod - OnMethodCall", ex);
}
}
OnMethodCallCallbackDelegate callback;
if (MethodTable.TryGetValue(msg.Method, out callback) && callback != null)
{
try
{
callback(this, msg);
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("Hub - " + this.Name, "IHub.OnMethod - callback", ex);
}
}
else if (OnMethodCall == null)
HTTPManager.Logger.Warning("Hub - " + this.Name, string.Format("[Client] {0}.{1} (args: {2})", this.Name, msg.Method, msg.Arguments.Length));
}
/// <summary>
/// Called when the client receives back messages as a result of a server method call.
/// </summary>
void IHub.OnMessage(IServerMessage msg)
{
ClientMessage originalMsg;
UInt64 id = (msg as IHubMessage).InvocationId;
if (!SentMessages.TryGetValue(id, out originalMsg))
{
// This can happen when a result message removes the ClientMessage from the SentMessages dictionary,
// then a late come progress message tries to access it
HTTPManager.Logger.Warning("Hub - " + this.Name, "OnMessage - Sent message not found with id: " + id.ToString());
return;
}
switch(msg.Type)
{
case MessageTypes.Result:
ResultMessage result = msg as ResultMessage;
// Merge the incoming State before firing the events
MergeState(result.State);
if (originalMsg.ResultCallback != null)
{
try
{
originalMsg.ResultCallback(this, originalMsg, result);
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("Hub " + this.Name, "IHub.OnMessage - ResultCallback", ex);
}
}
SentMessages.Remove(id);
break;
case MessageTypes.Failure:
FailureMessage error = msg as FailureMessage;
// Merge the incoming State before firing the events
MergeState(error.State);
if (originalMsg.ResultErrorCallback != null)
{
try
{
originalMsg.ResultErrorCallback(this, originalMsg, error);
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("Hub " + this.Name, "IHub.OnMessage - ResultErrorCallback", ex);
}
}
SentMessages.Remove(id);
break;
case MessageTypes.Progress:
if (originalMsg.ProgressCallback != null)
{
try
{
originalMsg.ProgressCallback(this, originalMsg, msg as ProgressMessage);
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("Hub " + this.Name, "IHub.OnMessage - ProgressCallback", ex);
}
}
break;
}
}
#endregion
#region Helper Functions
/// <summary>
/// Merges the current and the new states.
/// </summary>
#if BESTHTTP_SIGNALR_WITH_JSONDOTNET
private void MergeState(IDictionary<string, Newtonsoft.Json.Linq.JToken> state)
#else
private void MergeState(IDictionary<string, object> state)
#endif
{
if (state != null && state.Count > 0)
foreach (var kvp in state)
this.State[kvp.Key] = kvp.Value;
}
/// <summary>
/// Builds a JSon string from the given message.
/// </summary>
private string BuildMessage(ClientMessage msg)
{
try
{
builder.Append("{\"H\":\"");
builder.Append(this.Name);
builder.Append("\",\"M\":\"");
builder.Append(msg.Method);
builder.Append("\",\"A\":");
string jsonEncoded = string.Empty;
// Arguments
if (msg.Args != null && msg.Args.Length > 0)
jsonEncoded = (this as IHub).Connection.JsonEncoder.Encode(msg.Args);
else
jsonEncoded = "[]";
builder.Append(jsonEncoded);
builder.Append(",\"I\":\"");
builder.Append(msg.CallIdx.ToString());
builder.Append("\"");
// State, if any
if (msg.Hub.state != null && msg.Hub.state.Count > 0)
{
builder.Append(",\"S\":");
jsonEncoded = (this as IHub).Connection.JsonEncoder.Encode(msg.Hub.state);
builder.Append(jsonEncoded);
}
builder.Append("}");
return builder.ToString();
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("Hub - " + this.Name, "Send", ex);
return null;
}
finally
{
// reset the StringBuilder instance, to reuse next time
builder.Length = 0;
}
}
#endregion
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 00b4b8fd0e14de941b796a56a1272233
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
#if !BESTHTTP_DISABLE_SIGNALR
using System;
using BestHTTP.SignalR.Messages;
namespace BestHTTP.SignalR.Hubs
{
/// <summary>
/// Interface to be able to hide internally used functions and properties.
/// </summary>
public interface IHub
{
Connection Connection { get; set; }
bool Call(ClientMessage msg);
bool HasSentMessageId(UInt64 id);
void Close();
void OnMethod(MethodCallMessage msg);
void OnMessage(IServerMessage msg);
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 1813781a232e6c74ba3e00896327ace6
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c593c51184e979a4d8e4bb2cc2a9b1d0
folderAsset: yes
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
#if !BESTHTTP_DISABLE_SIGNALR
using BestHTTP.JSON;
using System.Collections.Generic;
namespace BestHTTP.SignalR.JsonEncoders
{
public sealed class DefaultJsonEncoder : IJsonEncoder
{
public string Encode(object obj)
{
return Json.Encode(obj);
}
public IDictionary<string, object> DecodeMessage(string json)
{
bool ok = false;
IDictionary<string, object> result = Json.Decode(json, ref ok) as IDictionary<string, object>;
return ok ? result : null;
}
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 1a4bf647dcdcdbf44ab1edc744192ca0
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
#if !BESTHTTP_DISABLE_SIGNALR
using System.Collections.Generic;
namespace BestHTTP.SignalR.JsonEncoders
{
/// <summary>
/// Interface to be able to write custom Json encoders/decoders.
/// </summary>
public interface IJsonEncoder
{
/// <summary>
/// This function must create a json formatted string from the given object. If the encoding fails, it should return null.
/// </summary>
string Encode(object obj);
/// <summary>
/// This function must create a dictionary the Json formatted string parameter. If the decoding fails, it should return null.
/// </summary>
IDictionary<string, object> DecodeMessage(string json);
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: b494cd3e9ad9bd243bd68251a27c86b9
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3ea7772f76aa54748aa8b466ba3113a9
folderAsset: yes
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,71 @@
#if !BESTHTTP_DISABLE_SIGNALR
using System;
using BestHTTP.SignalR.Hubs;
namespace BestHTTP.SignalR.Messages
{
/// <summary>
/// This struct represents a message from the client.
/// It holds every data and reference needed to construct the string represented message that will be sent to the wire.
/// </summary>
public struct ClientMessage
{
/// <summary>
/// Reference to the source Hub. The Name and the State of the hub will be user.
/// </summary>
public readonly Hub Hub;
/// <summary>
/// Name of the method on the server to be called.
/// </summary>
public readonly string Method;
/// <summary>
/// Arguments of the method.
/// </summary>
public readonly object[] Args;
/// <summary>
/// Unique id on the client of this message
/// </summary>
public readonly UInt64 CallIdx;
/// <summary>
/// The delegate that will be called when the server will sends a result of this method call.
/// </summary>
public readonly OnMethodResultDelegate ResultCallback;
/// <summary>
/// The delegate that will be called when the server sends an error-result to this method call.
/// </summary>
public readonly OnMethodFailedDelegate ResultErrorCallback;
/// <summary>
/// The delegate that will be called when the server sends a progress message to this method call.
/// </summary>
public readonly OnMethodProgressDelegate ProgressCallback;
public ClientMessage(Hub hub,
string method,
object[] args,
UInt64 callIdx,
OnMethodResultDelegate resultCallback,
OnMethodFailedDelegate resultErrorCallback,
OnMethodProgressDelegate progressCallback)
{
Hub = hub;
Method = method;
Args = args;
CallIdx = callIdx;
ResultCallback = resultCallback;
ResultErrorCallback = resultErrorCallback;
ProgressCallback = progressCallback;
}
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: af18bb2021dd2f045b2d168d5769fab1
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,19 @@
#if !BESTHTTP_DISABLE_SIGNALR
using System;
namespace BestHTTP.SignalR.Messages
{
public interface IServerMessage
{
MessageTypes Type { get; }
void Parse(object data);
}
public interface IHubMessage
{
UInt64 InvocationId { get; }
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 25c60db0524552144bf03b547ae8a585
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,354 @@
#if !BESTHTTP_DISABLE_SIGNALR
#if BESTHTTP_SIGNALR_WITH_JSONDOTNET
using Newtonsoft.Json.Linq;
#endif
using System;
using System.Collections;
using System.Collections.Generic;
namespace BestHTTP.SignalR.Messages
{
/// <summary>
/// Keep-alive message sent by the server. No data sent with it.
/// </summary>
public sealed class KeepAliveMessage : IServerMessage
{
MessageTypes IServerMessage.Type { get { return MessageTypes.KeepAlive; } }
void IServerMessage.Parse(object data) { }
}
/// <summary>
/// A message that may contains multiple sub-messages and additional informations.
/// </summary>
public sealed class MultiMessage : IServerMessage
{
MessageTypes IServerMessage.Type { get { return MessageTypes.Multiple; } }
/// <summary>
/// Id of the sent message
/// </summary>
public string MessageId { get; private set; }
/// <summary>
/// True if it's an initialization message, false otherwise.
/// </summary>
public bool IsInitialization { get; private set; }
/// <summary>
/// Group token may be sent, if the group changed that the client belongs to.
/// </summary>
public string GroupsToken { get; private set; }
/// <summary>
/// The server suggests that the client should do a reconnect turn.
/// </summary>
public bool ShouldReconnect { get; private set; }
/// <summary>
/// Additional poll delay sent by the server.
/// </summary>
public TimeSpan? PollDelay { get; private set; }
/// <summary>
/// List of server messages sent inside this message.
/// </summary>
public List<IServerMessage> Data { get; private set; }
void IServerMessage.Parse(object data)
{
IDictionary<string, object> dic = data as IDictionary<string, object>;
object value;
this.MessageId = dic["C"].ToString();
if (dic.TryGetValue("S", out value))
IsInitialization = int.Parse(value.ToString()) == 1 ? true : false;
else
IsInitialization = false;
if (dic.TryGetValue("G", out value))
GroupsToken = value.ToString();
if (dic.TryGetValue("T", out value))
ShouldReconnect = int.Parse(value.ToString()) == 1 ? true : false;
else
ShouldReconnect = false;
if (dic.TryGetValue("L", out value))
PollDelay = TimeSpan.FromMilliseconds(double.Parse(value.ToString()));
IEnumerable enumerable = dic["M"] as IEnumerable;
if (enumerable != null)
{
Data = new List<IServerMessage>();
foreach (object subData in enumerable)
{
#if BESTHTTP_SIGNALR_WITH_JSONDOTNET
IDictionary<string, JToken> subObj = subData as IDictionary<string, JToken>;
#else
IDictionary<string, object> subObj = subData as IDictionary<string, object>;
#endif
IServerMessage subMsg = null;
if (subObj != null)
{
if (subObj.ContainsKey("H"))
subMsg = new MethodCallMessage();
else if (subObj.ContainsKey("I"))
subMsg = new ProgressMessage();
else
subMsg = new DataMessage();
}
else
subMsg = new DataMessage();
subMsg.Parse(subData);
Data.Add(subMsg);
}
}
}
}
/// <summary>
/// A simple non-hub data message. It holds only one Data property.
/// </summary>
public sealed class DataMessage : IServerMessage
{
MessageTypes IServerMessage.Type { get { return MessageTypes.Data; } }
public object Data { get; private set; }
void IServerMessage.Parse(object data)
{
this.Data = data;
}
}
/// <summary>
/// A Hub message that orders the client to call a method.
/// </summary>
public sealed class MethodCallMessage : IServerMessage
{
MessageTypes IServerMessage.Type { get { return MessageTypes.MethodCall; } }
/// <summary>
/// The name of the Hub that the method is called on.
/// </summary>
public string Hub { get; private set; }
/// <summary>
/// Name of the Method.
/// </summary>
public string Method { get; private set; }
/// <summary>
/// Arguments of the method call.
/// </summary>
public object[] Arguments { get; private set; }
/// <summary>
/// State changes of the hub. It's handled automatically by the Hub.
/// </summary>
#if BESTHTTP_SIGNALR_WITH_JSONDOTNET
public IDictionary<string, JToken> State { get; private set; }
#else
public IDictionary<string, object> State { get; private set; }
#endif
void IServerMessage.Parse(object data)
{
#if BESTHTTP_SIGNALR_WITH_JSONDOTNET
IDictionary<string, JToken> dic = data as IDictionary<string, JToken>;
#else
IDictionary<string, object> dic = data as IDictionary<string, object>;
#endif
Hub = dic["H"].ToString();
Method = dic["M"].ToString();
List<object> args = new List<object>();
foreach (object arg in dic["A"] as IEnumerable)
args.Add(arg);
Arguments = args.ToArray();
#if BESTHTTP_SIGNALR_WITH_JSONDOTNET
JToken value;
if (dic.TryGetValue("S", out value))
State = value as IDictionary<string, JToken>;
#else
object value;
if (dic.TryGetValue("S", out value))
State = value as IDictionary<string, object>;
#endif
}
}
/// <summary>
/// Message of a server side method invocation result.
/// </summary>
public sealed class ResultMessage : IServerMessage, IHubMessage
{
MessageTypes IServerMessage.Type { get { return MessageTypes.Result; } }
/// <summary>
/// The unique id that the client set when called the server side method. Used by the plugin to deliver this message to the good Hub.
/// </summary>
public UInt64 InvocationId { get; private set; }
/// <summary>
/// The return value of the server side method call, or null if the method's return type is void.
/// </summary>
public object ReturnValue { get; private set; }
/// <summary>
/// State changes of the hub. It's handled automatically by the Hub.
/// </summary>
#if BESTHTTP_SIGNALR_WITH_JSONDOTNET
public IDictionary<string, JToken> State { get; private set; }
#else
public IDictionary<string, object> State { get; private set; }
#endif
void IServerMessage.Parse(object data)
{
IDictionary<string, object> dic = data as IDictionary<string, object>;
InvocationId = UInt64.Parse(dic["I"].ToString());
object value;
if (dic.TryGetValue("R", out value))
ReturnValue = value;
if (dic.TryGetValue("S", out value))
{
#if BESTHTTP_SIGNALR_WITH_JSONDOTNET
State = value as IDictionary<string, JToken>;
#else
State = value as IDictionary<string, object>;
#endif
}
}
}
public sealed class FailureMessage : IServerMessage, IHubMessage
{
MessageTypes IServerMessage.Type { get { return MessageTypes.Failure; } }
/// <summary>
/// The unique id that the client set when called the server side method. Used by the plugin to deliver this message to the good Hub.
/// </summary>
public UInt64 InvocationId { get; private set; }
/// <summary>
/// True if it's a hub error.
/// </summary>
public bool IsHubError { get; private set; }
/// <summary>
/// If the method call failed, it contains the error message to detail what happened.
/// </summary>
public string ErrorMessage { get; private set; }
/// <summary>
/// A dictionary that may contain additional error data (can only be present for hub errors). It can be null.
/// </summary>
#if BESTHTTP_SIGNALR_WITH_JSONDOTNET
public IDictionary<string, JToken> AdditionalData { get; private set; }
#else
public IDictionary<string, object> AdditionalData { get; private set; }
#endif
/// <summary>
/// Stack trace of the error. It present only if detailed error reporting is turned on on the server (https://msdn.microsoft.com/en-us/library/microsoft.aspnet.signalr.hubconfiguration.enabledetailederrors%28v=vs.118%29.aspx).
/// </summary>
public string StackTrace { get; private set; }
/// <summary>
/// State changes of the hub. It's handled automatically by the Hub.
/// </summary>
#if BESTHTTP_SIGNALR_WITH_JSONDOTNET
public IDictionary<string, JToken> State { get; private set; }
#else
public IDictionary<string, object> State { get; private set; }
#endif
void IServerMessage.Parse(object data)
{
IDictionary<string, object> dic = data as IDictionary<string, object>;
InvocationId = UInt64.Parse(dic["I"].ToString());
object value;
if (dic.TryGetValue("E", out value))
ErrorMessage = value.ToString();
if (dic.TryGetValue("H", out value))
IsHubError = int.Parse(value.ToString()) == 1 ? true : false;
if (dic.TryGetValue("D", out value))
{
#if BESTHTTP_SIGNALR_WITH_JSONDOTNET
AdditionalData = value as IDictionary<string, JToken>;
#else
AdditionalData = value as IDictionary<string, object>;
#endif
}
if (dic.TryGetValue("T", out value))
StackTrace = value.ToString();
if (dic.TryGetValue("S", out value))
{
#if BESTHTTP_SIGNALR_WITH_JSONDOTNET
State = value as IDictionary<string, JToken>;
#else
State = value as IDictionary<string, object>;
#endif
}
}
}
/// <summary>
/// When a server method is a long running method the server can send the information about the progress of execution of the method to the client.
/// </summary>
public sealed class ProgressMessage : IServerMessage, IHubMessage
{
MessageTypes IServerMessage.Type { get { return MessageTypes.Progress; } }
/// <summary>
/// The unique id that the client set when called the server side method. Used by the plugin to deliver this message to the good Hub.
/// </summary>
public UInt64 InvocationId { get; private set; }
/// <summary>
/// Current progress of the long running method.
/// </summary>
public double Progress { get; private set; }
void IServerMessage.Parse(object data)
{
#if BESTHTTP_SIGNALR_WITH_JSONDOTNET
IDictionary<string, JToken> dic = data as IDictionary<string, JToken>;
#else
IDictionary<string, object> dic = data as IDictionary<string, object>;
#endif
#if BESTHTTP_SIGNALR_WITH_JSONDOTNET
IDictionary<string, JToken> P = dic["P"] as IDictionary<string, JToken>;
#else
IDictionary<string, object> P = dic["P"] as IDictionary<string, object>;
#endif
InvocationId = UInt64.Parse(P["I"].ToString());
Progress = double.Parse(P["D"].ToString());
}
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a128625937e3c5b459b3aa221061a03f
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,276 @@
#if !BESTHTTP_DISABLE_SIGNALR
using System;
using System.Collections.Generic;
using BestHTTP.JSON;
namespace BestHTTP.SignalR
{
public sealed class NegotiationData
{
#region Public Negotiate data
/// <summary>
/// Path to the SignalR endpoint. Currently not used by the client.
/// </summary>
public string Url { get; private set; }
/// <summary>
/// WebSocket endpoint.
/// </summary>
public string WebSocketServerUrl { get; private set; }
/// <summary>
/// Connection token assigned by the server. See this article for more details: http://www.asp.net/signalr/overview/security/introduction-to-security#connectiontoken.
/// This value needs to be sent in each subsequent request as the value of the connectionToken parameter
/// </summary>
public string ConnectionToken { get; private set; }
/// <summary>
/// The id of the connection.
/// </summary>
public string ConnectionId { get; private set; }
/// <summary>
/// The amount of time in seconds the client should wait before attempting to reconnect if it has not received a keep alive message.
/// If the server is configured to not send keep alive messages this value is null.
/// </summary>
public TimeSpan? KeepAliveTimeout { get; private set; }
/// <summary>
/// The amount of time within which the client should try to reconnect if the connection goes away.
/// </summary>
public TimeSpan DisconnectTimeout { get; private set; }
/// <summary>
/// Timeout of poll requests.
/// </summary>
public TimeSpan ConnectionTimeout { get; private set; }
/// <summary>
/// Whether the server supports websockets.
/// </summary>
public bool TryWebSockets { get; private set; }
/// <summary>
/// The version of the protocol used for communication.
/// </summary>
public string ProtocolVersion { get; private set; }
/// <summary>
/// The maximum amount of time the client should try to connect to the server using a given transport.
/// </summary>
public TimeSpan TransportConnectTimeout { get; private set; }
/// <summary>
/// The wait time before restablishing a long poll connection after data is sent from the server. The default value is 0.
/// </summary>
public TimeSpan LongPollDelay { get; private set; }
#endregion
#region Event Handlers
/// <summary>
/// Event handler that called when the negotiation data received and parsed successfully.
/// </summary>
public Action<NegotiationData> OnReceived;
/// <summary>
/// Event handler that called when an error happens.
/// </summary>
public Action<NegotiationData, string> OnError;
#endregion
#region Private
private HTTPRequest NegotiationRequest;
private IConnection Connection;
#endregion
public NegotiationData(Connection connection)
{
this.Connection = connection;
}
/// <summary>
/// Start to get the negotiation data.
/// </summary>
public void Start()
{
NegotiationRequest = new HTTPRequest(Connection.BuildUri(RequestTypes.Negotiate), HTTPMethods.Get, true, true, OnNegotiationRequestFinished);
Connection.PrepareRequest(NegotiationRequest, RequestTypes.Negotiate);
NegotiationRequest.Send();
HTTPManager.Logger.Information("NegotiationData", "Negotiation request sent");
}
/// <summary>
/// Abort the negotiation request.
/// </summary>
public void Abort()
{
if (NegotiationRequest != null)
{
OnReceived = null;
OnError = null;
NegotiationRequest.Abort();
}
}
#region Request Event Handler
private void OnNegotiationRequestFinished(HTTPRequest req, HTTPResponse resp)
{
NegotiationRequest = null;
switch (req.State)
{
case HTTPRequestStates.Finished:
if (resp.IsSuccess)
{
HTTPManager.Logger.Information("NegotiationData", "Negotiation data arrived: " + resp.DataAsText);
int idx = resp.DataAsText.IndexOf("{");
if (idx < 0)
{
RaiseOnError("Invalid negotiation text: " + resp.DataAsText);
return;
}
var Negotiation = Parse(resp.DataAsText.Substring(idx));
if (Negotiation == null)
{
RaiseOnError("Parsing Negotiation data failed: " + resp.DataAsText);
return;
}
if (OnReceived != null)
{
OnReceived(this);
OnReceived = null;
}
}
else
RaiseOnError(string.Format("Negotiation 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));
break;
case HTTPRequestStates.Error:
RaiseOnError(req.Exception != null ? (req.Exception.Message + " " + req.Exception.StackTrace) : string.Empty);
break;
default:
RaiseOnError(req.State.ToString());
break;
}
}
#endregion
#region Helper Methods
private void RaiseOnError(string err)
{
HTTPManager.Logger.Error("NegotiationData", "Negotiation request failed with error: " + err);
if (OnError != null)
{
OnError(this, err);
OnError = null;
}
}
private NegotiationData Parse(string str)
{
bool success = false;
Dictionary<string, object> dict = Json.Decode(str, ref success) as Dictionary<string, object>;
if (!success)
return null;
try
{
this.Url = GetString(dict, "Url");
if (dict.ContainsKey("webSocketServerUrl"))
this.WebSocketServerUrl = GetString(dict, "webSocketServerUrl");
this.ConnectionToken = Uri.EscapeDataString(GetString(dict, "ConnectionToken"));
this.ConnectionId = GetString(dict, "ConnectionId");
if (dict.ContainsKey("KeepAliveTimeout"))
this.KeepAliveTimeout = TimeSpan.FromSeconds(GetDouble(dict, "KeepAliveTimeout"));
this.DisconnectTimeout = TimeSpan.FromSeconds(GetDouble(dict, "DisconnectTimeout"));
if (dict.ContainsKey("ConnectionTimeout"))
this.ConnectionTimeout = TimeSpan.FromSeconds(GetDouble(dict, "ConnectionTimeout"));
else
this.ConnectionTimeout = TimeSpan.FromSeconds(120);
this.TryWebSockets = (bool)Get(dict, "TryWebSockets");
this.ProtocolVersion = GetString(dict, "ProtocolVersion");
this.TransportConnectTimeout = TimeSpan.FromSeconds(GetDouble(dict, "TransportConnectTimeout"));
if (dict.ContainsKey("LongPollDelay"))
this.LongPollDelay = TimeSpan.FromSeconds(GetDouble(dict, "LongPollDelay"));
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("NegotiationData", "Parse", ex);
return null;
}
return this;
}
private static object Get(Dictionary<string, object> from, string key)
{
object value;
if (!from.TryGetValue(key, out value))
throw new System.Exception(string.Format("Can't get {0} from Negotiation data!", key));
return value;
}
private static string GetString(Dictionary<string, object> from, string key)
{
return Get(from, key) as string;
}
private static List<string> GetStringList(Dictionary<string, object> from, string key)
{
List<object> value = Get(from, key) as List<object>;
List<string> result = new List<string>(value.Count);
for (int i = 0; i < value.Count; ++i)
{
string str = value[i] as string;
if (str != null)
result.Add(str);
}
return result;
}
private static int GetInt(Dictionary<string, object> from, string key)
{
return (int)(double)Get(from, key);
}
private static double GetDouble(Dictionary<string, object> from, string key)
{
return (double)Get(from, key);
}
#endregion
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: c20f605e63af7ef4eb7782aa663f0196
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c390001e221f7e1429a38eb6222413c5
folderAsset: yes
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View 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;
// Connecting 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.IsCancellationRequested)
{
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;
// Connecting 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

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 702a1cd6038bbc347a4b568983058f63
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,100 @@
#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);
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

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: f47176f2538ccf54e855235ecb7647f3
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: b8c1fc0aacfa94c4793a2e8958a34058
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,386 @@
#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.MaxRetries = 0;
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.MaxRetries = 0;
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();
try
{
result.Parse(msg);
}
catch
{
HTTPManager.Logger.Error("MessageFactory", "Can't parse msg: " + json);
throw;
}
return result;
}
#endregion
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 469a77c43ea68f14da659401301834f5
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,174 @@
#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.OnError += WSocket_OnError;
#if !UNITY_WEBGL || UNITY_EDITOR
// prepare the internal http request
wSocket.OnInternalRequestCreated = (ws, internalRequest) => Connection.PrepareRequest(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.OnError = 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
{
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
HTTPManager.Logger.Error("WebSocketTransport", "WSocket_OnError " + reason);
this.State = TransportStates.Closed;
Connection.Error(reason);
}
}
#endregion
}
}
#endif
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 5efa5f8b9a034084b87f47b2b74b8173
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: