You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
9.8 KiB
271 lines
9.8 KiB
8 months ago
|
#if !BESTHTTP_DISABLE_SIGNALR_CORE
|
||
|
|
||
|
using BestHTTP.PlatformSupport.Memory;
|
||
|
using BestHTTP.SignalRCore.Messages;
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
|
||
|
#if NETFX_CORE || NET_4_6
|
||
|
using System.Reflection;
|
||
|
#endif
|
||
|
|
||
|
namespace BestHTTP.SignalRCore
|
||
|
{
|
||
|
public interface IProtocol
|
||
|
{
|
||
|
string Name { get; }
|
||
|
|
||
|
TransferModes Type { get; }
|
||
|
|
||
|
IEncoder Encoder { get; }
|
||
|
|
||
|
HubConnection Connection { get; set; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// This function must parse binary representation of the messages into the list of Messages.
|
||
|
/// </summary>
|
||
|
void ParseMessages(BufferSegment segment, ref List<Message> messages);
|
||
|
|
||
|
/// <summary>
|
||
|
/// This function must return the encoded representation of the given message.
|
||
|
/// </summary>
|
||
|
BufferSegment EncodeMessage(Message message);
|
||
|
|
||
|
/// <summary>
|
||
|
/// This function must convert all element in the arguments array to the corresponding type from the argTypes array.
|
||
|
/// </summary>
|
||
|
object[] GetRealArguments(Type[] argTypes, object[] arguments);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Convert a value to the given type.
|
||
|
/// </summary>
|
||
|
object ConvertTo(Type toType, object obj);
|
||
|
}
|
||
|
|
||
|
public sealed class JsonProtocol : IProtocol
|
||
|
{
|
||
|
public const char Separator = (char)0x1E;
|
||
|
|
||
|
public string Name { get { return "json"; } }
|
||
|
|
||
|
public TransferModes Type { get { return TransferModes.Binary; } }
|
||
|
|
||
|
public IEncoder Encoder { get; private set; }
|
||
|
|
||
|
public HubConnection Connection { get; set; }
|
||
|
|
||
|
public JsonProtocol(IEncoder encoder)
|
||
|
{
|
||
|
if (encoder == null)
|
||
|
throw new ArgumentNullException("encoder");
|
||
|
|
||
|
this.Encoder = encoder;
|
||
|
}
|
||
|
|
||
|
public void ParseMessages(BufferSegment segment, ref List<Message> messages) {
|
||
|
if (segment.Data == null || segment.Count == 0)
|
||
|
return;
|
||
|
|
||
|
int from = segment.Offset;
|
||
|
int separatorIdx = Array.IndexOf<byte>(segment.Data, (byte)JsonProtocol.Separator, from);
|
||
|
if (separatorIdx == -1)
|
||
|
throw new Exception("Missing separator in data! Segment: " + segment.ToString());
|
||
|
|
||
|
while (separatorIdx != -1)
|
||
|
{
|
||
|
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||
|
HTTPManager.Logger.Verbose("JsonProtocol", "ParseMessages - " + System.Text.Encoding.UTF8.GetString(segment.Data, from, separatorIdx - from));
|
||
|
var message = this.Encoder.DecodeAs<Message>(new BufferSegment(segment.Data, from, separatorIdx - from));
|
||
|
|
||
|
messages.Add(message);
|
||
|
|
||
|
from = separatorIdx + 1;
|
||
|
separatorIdx = Array.IndexOf<byte>(segment.Data, (byte)JsonProtocol.Separator, from);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public BufferSegment EncodeMessage(Message message)
|
||
|
{
|
||
|
BufferSegment result = BufferSegment.Empty;
|
||
|
|
||
|
// While message contains all informations already, the spec states that no additional field are allowed in messages
|
||
|
// So we are creating 'specialized' messages here to send to the server.
|
||
|
switch (message.type)
|
||
|
{
|
||
|
case MessageTypes.StreamItem:
|
||
|
result = this.Encoder.Encode<StreamItemMessage>(new StreamItemMessage()
|
||
|
{
|
||
|
type = message.type,
|
||
|
invocationId = message.invocationId,
|
||
|
item = message.item
|
||
|
});
|
||
|
break;
|
||
|
|
||
|
case MessageTypes.Completion:
|
||
|
if (!string.IsNullOrEmpty(message.error))
|
||
|
{
|
||
|
result = this.Encoder.Encode<CompletionWithError>(new CompletionWithError()
|
||
|
{
|
||
|
type = MessageTypes.Completion,
|
||
|
invocationId = message.invocationId,
|
||
|
error = message.error
|
||
|
});
|
||
|
}
|
||
|
else if (message.result != null)
|
||
|
{
|
||
|
result = this.Encoder.Encode<CompletionWithResult>(new CompletionWithResult()
|
||
|
{
|
||
|
type = MessageTypes.Completion,
|
||
|
invocationId = message.invocationId,
|
||
|
result = message.result
|
||
|
});
|
||
|
}
|
||
|
else
|
||
|
result = this.Encoder.Encode<Completion>(new Completion()
|
||
|
{
|
||
|
type = MessageTypes.Completion,
|
||
|
invocationId = message.invocationId
|
||
|
});
|
||
|
break;
|
||
|
|
||
|
case MessageTypes.Invocation:
|
||
|
case MessageTypes.StreamInvocation:
|
||
|
if (message.streamIds != null)
|
||
|
{
|
||
|
result = this.Encoder.Encode<UploadInvocationMessage>(new UploadInvocationMessage()
|
||
|
{
|
||
|
type = message.type,
|
||
|
invocationId = message.invocationId,
|
||
|
nonblocking = message.nonblocking,
|
||
|
target = message.target,
|
||
|
arguments = message.arguments,
|
||
|
streamIds = message.streamIds
|
||
|
});
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result = this.Encoder.Encode<InvocationMessage>(new InvocationMessage()
|
||
|
{
|
||
|
type = message.type,
|
||
|
invocationId = message.invocationId,
|
||
|
nonblocking = message.nonblocking,
|
||
|
target = message.target,
|
||
|
arguments = message.arguments
|
||
|
});
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case MessageTypes.CancelInvocation:
|
||
|
result = this.Encoder.Encode<CancelInvocationMessage>(new CancelInvocationMessage()
|
||
|
{
|
||
|
invocationId = message.invocationId
|
||
|
});
|
||
|
break;
|
||
|
|
||
|
case MessageTypes.Ping:
|
||
|
//result = this.Encoder.Encode<PingMessage>(new PingMessage());
|
||
|
// fast path to encode a well-known json string
|
||
|
result = EncodeKnown("{\"type\":6}");
|
||
|
break;
|
||
|
|
||
|
case MessageTypes.Close:
|
||
|
if (!string.IsNullOrEmpty(message.error))
|
||
|
{
|
||
|
result = this.Encoder.Encode<CloseWithErrorMessage>(new CloseWithErrorMessage() { error = message.error });
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//result = this.Encoder.Encode<CloseMessage>(new CloseMessage());
|
||
|
// fast path to encode a well-known json string
|
||
|
result = EncodeKnown("{\"type\":7}");
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
|
||
|
HTTPManager.Logger.Verbose("JsonProtocol", "EncodeMessage - json: " + System.Text.Encoding.UTF8.GetString(result.Data, 0, result.Count - 1));
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
private BufferSegment EncodeKnown(string json)
|
||
|
{
|
||
|
int len = System.Text.Encoding.UTF8.GetByteCount(json);
|
||
|
byte[] buffer = BufferPool.Get(len + 1, true);
|
||
|
System.Text.Encoding.UTF8.GetBytes(json, 0, json.Length, buffer, 0);
|
||
|
buffer[len] = (byte)JsonProtocol.Separator;
|
||
|
return new BufferSegment(buffer, 0, len + 1);
|
||
|
}
|
||
|
|
||
|
public object[] GetRealArguments(Type[] argTypes, object[] arguments)
|
||
|
{
|
||
|
if (arguments == null || arguments.Length == 0)
|
||
|
return null;
|
||
|
|
||
|
if (argTypes.Length > arguments.Length)
|
||
|
throw new Exception(string.Format("argType.Length({0}) < arguments.length({1})", argTypes.Length, arguments.Length));
|
||
|
|
||
|
object[] realArgs = new object[arguments.Length];
|
||
|
|
||
|
for (int i = 0; i < arguments.Length; ++i)
|
||
|
realArgs[i] = ConvertTo(argTypes[i], arguments[i]);
|
||
|
|
||
|
return realArgs;
|
||
|
}
|
||
|
|
||
|
public object ConvertTo(Type toType, object obj)
|
||
|
{
|
||
|
if (obj == null)
|
||
|
return null;
|
||
|
|
||
|
#if NETFX_CORE
|
||
|
TypeInfo typeInfo = toType.GetTypeInfo();
|
||
|
#endif
|
||
|
|
||
|
#if NETFX_CORE
|
||
|
if (typeInfo.IsEnum)
|
||
|
#else
|
||
|
if (toType.IsEnum)
|
||
|
#endif
|
||
|
return Enum.Parse(toType, obj.ToString(), true);
|
||
|
|
||
|
#if NETFX_CORE
|
||
|
if (typeInfo.IsPrimitive)
|
||
|
#else
|
||
|
if (toType.IsPrimitive)
|
||
|
#endif
|
||
|
return Convert.ChangeType(obj, toType);
|
||
|
|
||
|
if (toType == typeof(string))
|
||
|
return obj.ToString();
|
||
|
|
||
|
#if NETFX_CORE
|
||
|
if (typeInfo.IsGenericType && toType.Name == "Nullable`1")
|
||
|
return Convert.ChangeType(obj, toType.GenericTypeArguments[0]);
|
||
|
#else
|
||
|
if (toType.IsGenericType && toType.Name == "Nullable`1")
|
||
|
return Convert.ChangeType(obj, toType.GetGenericArguments()[0]);
|
||
|
#endif
|
||
|
|
||
|
return this.Encoder.ConvertTo(toType, obj);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the given string parameter's bytes with the added separator(0x1E).
|
||
|
/// </summary>
|
||
|
public static BufferSegment WithSeparator(string str)
|
||
|
{
|
||
|
int len = System.Text.Encoding.UTF8.GetByteCount(str);
|
||
|
|
||
|
byte[] buffer = BufferPool.Get(len + 1, true);
|
||
|
|
||
|
System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, 0);
|
||
|
|
||
|
buffer[len] = 0x1e;
|
||
|
|
||
|
return new BufferSegment(buffer, 0, len + 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|