1200 lines
38 KiB
C#
1200 lines
38 KiB
C#
using System.Text;
|
|
using System.Collections.Immutable;
|
|
using Quobject.EngineIoClientDotNet.Client.Transports;
|
|
using Quobject.EngineIoClientDotNet.ComponentEmitter;
|
|
using Quobject.EngineIoClientDotNet.Modules;
|
|
using Quobject.EngineIoClientDotNet.Parser;
|
|
using Quobject.EngineIoClientDotNet.Thread;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
namespace Quobject.EngineIoClientDotNet.Client
|
|
{
|
|
public class Socket : Emitter
|
|
{
|
|
private enum ReadyStateEnum
|
|
{
|
|
OPENING,
|
|
OPEN,
|
|
CLOSING,
|
|
CLOSED
|
|
}
|
|
|
|
public static readonly string EVENT_OPEN = "open";
|
|
public static readonly string EVENT_CLOSE = "close";
|
|
public static readonly string EVENT_PACKET = "packet";
|
|
public static readonly string EVENT_DRAIN = "drain";
|
|
public static readonly string EVENT_ERROR = "error";
|
|
public static readonly string EVENT_DATA = "data";
|
|
public static readonly string EVENT_MESSAGE = "message";
|
|
public static readonly string EVENT_UPGRADE_ERROR = "upgradeError";
|
|
public static readonly string EVENT_FLUSH = "flush";
|
|
public static readonly string EVENT_HANDSHAKE = "handshake";
|
|
public static readonly string EVENT_UPGRADING = "upgrading";
|
|
public static readonly string EVENT_UPGRADE = "upgrade";
|
|
public static readonly string EVENT_PACKET_CREATE = "packetCreate";
|
|
public static readonly string EVENT_HEARTBEAT = "heartbeat";
|
|
public static readonly string EVENT_TRANSPORT = "transport";
|
|
|
|
public static readonly int Protocol = Parser.Parser.Protocol;
|
|
|
|
public static bool PriorWebsocketSuccess = false;
|
|
|
|
|
|
private bool Secure;
|
|
private bool Upgrade;
|
|
private bool TimestampRequests = true;
|
|
private bool Upgrading;
|
|
private bool RememberUpgrade;
|
|
private int Port;
|
|
private int PolicyPort;
|
|
private int PrevBufferLen;
|
|
private long PingInterval;
|
|
private long PingTimeout;
|
|
public string Id;
|
|
private string Hostname;
|
|
private string Path;
|
|
private string TimestampParam;
|
|
private ImmutableList<string> Transports;
|
|
private ImmutableList<string> Upgrades;
|
|
private Dictionary<string, string> Query;
|
|
private ImmutableList<Packet> WriteBuffer = ImmutableList<Packet>.Empty;
|
|
private ImmutableList<Action> CallbackBuffer = ImmutableList<Action>.Empty;
|
|
private Dictionary<string, string> Cookies = new Dictionary<string, string>();
|
|
/*package*/
|
|
public Transport Transport;
|
|
private EasyTimer PingTimeoutTimer;
|
|
private EasyTimer PingIntervalTimer;
|
|
|
|
private ReadyStateEnum ReadyState;
|
|
private bool Agent = false;
|
|
private bool ForceBase64 = false;
|
|
private bool ForceJsonp = false;
|
|
|
|
public Dictionary<string, string> ExtraHeaders;
|
|
|
|
|
|
//public static void SetupLog4Net()
|
|
//{
|
|
// var hierarchy = (Hierarchy)LogManager.GetRepository();
|
|
// hierarchy.Root.RemoveAllAppenders(); /*Remove any other appenders*/
|
|
|
|
// var fileAppender = new FileAppender();
|
|
// fileAppender.AppendToFile = true;
|
|
// fileAppender.LockingModel = new FileAppender.MinimalLock();
|
|
// fileAppender.File = "EngineIoClientDotNet.log";
|
|
// var pl = new PatternLayout();
|
|
// pl.ConversionPattern = "%d [%2%t] %-5p [%-10c] %m%n";
|
|
// pl.ActivateOptions();
|
|
// fileAppender.Layout = pl;
|
|
// fileAppender.ActivateOptions();
|
|
// BasicConfigurator.Configure(fileAppender);
|
|
//}
|
|
|
|
public Socket()
|
|
: this(new Options())
|
|
{
|
|
}
|
|
|
|
public Socket(string uri)
|
|
: this(uri, null)
|
|
{
|
|
}
|
|
|
|
public Socket(string uri, Options options)
|
|
: this(uri == null ? null : String2Uri(uri), options)
|
|
{
|
|
|
|
}
|
|
|
|
private static Uri String2Uri(string uri)
|
|
{
|
|
if (uri.StartsWith("http") || uri.StartsWith("ws"))
|
|
{
|
|
return new Uri(uri);
|
|
}
|
|
else
|
|
{
|
|
return new Uri("http://" + uri);
|
|
}
|
|
}
|
|
|
|
public Socket(Uri uri, Options options)
|
|
: this(uri == null ? options : Options.FromURI(uri, options))
|
|
{
|
|
}
|
|
|
|
|
|
public Socket(Options options)
|
|
{
|
|
if (options.Host != null)
|
|
{
|
|
var pieces = options.Host.Split(':');
|
|
options.Hostname = pieces[0];
|
|
if (pieces.Length > 1)
|
|
{
|
|
options.Port = int.Parse(pieces[pieces.Length - 1]);
|
|
}
|
|
}
|
|
|
|
Secure = options.Secure;
|
|
Hostname = options.Hostname;
|
|
Port = options.Port;
|
|
Query = options.QueryString != null ? ParseQS.Decode(options.QueryString) : new Dictionary<string, string>();
|
|
|
|
if (options.Query != null)
|
|
{
|
|
foreach (var item in options.Query)
|
|
{
|
|
Query.Add(item.Key,item.Value);
|
|
}
|
|
}
|
|
|
|
|
|
Upgrade = options.Upgrade;
|
|
Path = (options.Path ?? "/engine.io").Replace("/$", "") + "/";
|
|
TimestampParam = (options.TimestampParam ?? "t");
|
|
TimestampRequests = options.TimestampRequests;
|
|
Transports = options.Transports ?? ImmutableList<string>.Empty.Add(Polling.NAME).Add(WebSocket.NAME);
|
|
PolicyPort = options.PolicyPort != 0 ? options.PolicyPort : 843;
|
|
RememberUpgrade = options.RememberUpgrade;
|
|
Cookies = options.Cookies;
|
|
if (options.IgnoreServerCertificateValidation)
|
|
{
|
|
ServerCertificate.IgnoreServerCertificateValidation();
|
|
}
|
|
ExtraHeaders = options.ExtraHeaders;
|
|
|
|
}
|
|
|
|
public Socket Open()
|
|
{
|
|
string transportName;
|
|
if (RememberUpgrade && PriorWebsocketSuccess && Transports.Contains(WebSocket.NAME))
|
|
{
|
|
transportName = WebSocket.NAME;
|
|
}
|
|
else
|
|
{
|
|
transportName = Transports[0];
|
|
}
|
|
ReadyState = ReadyStateEnum.OPENING;
|
|
var transport = CreateTransport(transportName);
|
|
SetTransport(transport);
|
|
// EventTasks.Exec((n) =>
|
|
Task.Run(() =>
|
|
{
|
|
var log2 = LogManager.GetLogger(Global.CallerName());
|
|
log2.Info("Task.Run Open start");
|
|
transport.Open();
|
|
log2.Info("Task.Run Open finish");
|
|
});
|
|
return this;
|
|
}
|
|
|
|
private Transport CreateTransport(string name)
|
|
{
|
|
var query = new Dictionary<string, string>(Query);
|
|
query.Add("EIO", Parser.Parser.Protocol.ToString());
|
|
query.Add("transport", name);
|
|
if (Id != null)
|
|
{
|
|
query.Add("sid", Id);
|
|
}
|
|
var options = new Transport.Options
|
|
{
|
|
Hostname = Hostname,
|
|
Port = Port,
|
|
Secure = Secure,
|
|
Path = Path,
|
|
Query = query,
|
|
TimestampRequests = TimestampRequests,
|
|
TimestampParam = TimestampParam,
|
|
PolicyPort = PolicyPort,
|
|
Socket = this,
|
|
Agent = this.Agent,
|
|
ForceBase64 = this.ForceBase64,
|
|
ForceJsonp = this.ForceJsonp,
|
|
Cookies = this.Cookies,
|
|
ExtraHeaders = this.ExtraHeaders
|
|
};
|
|
|
|
if (name == WebSocket.NAME)
|
|
{
|
|
return new WebSocket(options);
|
|
}
|
|
else if (name == Polling.NAME)
|
|
{
|
|
return new PollingXHR(options);
|
|
}
|
|
|
|
throw new EngineIOException("CreateTransport failed");
|
|
}
|
|
|
|
private void SetTransport(Transport transport)
|
|
{
|
|
var log = LogManager.GetLogger(Global.CallerName());
|
|
log.Info(string.Format("SetTransport setting transport '{0}'", transport.Name));
|
|
|
|
if (this.Transport != null)
|
|
{
|
|
log.Info(string.Format("SetTransport clearing existing transport '{0}'", transport.Name));
|
|
this.Transport.Off();
|
|
}
|
|
|
|
Transport = transport;
|
|
|
|
Emit(EVENT_TRANSPORT, transport);
|
|
|
|
transport.On(EVENT_DRAIN, new EventDrainListener(this));
|
|
transport.On(EVENT_PACKET, new EventPacketListener(this));
|
|
transport.On(EVENT_ERROR, new EventErrorListener(this));
|
|
transport.On(EVENT_CLOSE, new EventCloseListener(this));
|
|
}
|
|
|
|
private class EventDrainListener : IListener
|
|
{
|
|
private Socket socket;
|
|
|
|
public EventDrainListener(Socket socket)
|
|
{
|
|
this.socket = socket;
|
|
}
|
|
|
|
void IListener.Call(params object[] args)
|
|
{
|
|
socket.OnDrain();
|
|
}
|
|
|
|
public int CompareTo(IListener other)
|
|
{
|
|
return this.GetId().CompareTo(other.GetId());
|
|
}
|
|
|
|
public int GetId()
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private class EventPacketListener : IListener
|
|
{
|
|
private Socket socket;
|
|
|
|
public EventPacketListener(Socket socket)
|
|
{
|
|
this.socket = socket;
|
|
}
|
|
|
|
void IListener.Call(params object[] args)
|
|
{
|
|
socket.OnPacket(args.Length > 0 ? (Packet)args[0] : null);
|
|
}
|
|
|
|
public int CompareTo(IListener other)
|
|
{
|
|
return this.GetId().CompareTo(other.GetId());
|
|
}
|
|
|
|
public int GetId()
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private class EventErrorListener : IListener
|
|
{
|
|
private Socket socket;
|
|
|
|
public EventErrorListener(Socket socket)
|
|
{
|
|
this.socket = socket;
|
|
}
|
|
|
|
public void Call(params object[] args)
|
|
{
|
|
socket.OnError(args.Length > 0 ? (Exception)args[0] : null);
|
|
}
|
|
|
|
public int CompareTo(IListener other)
|
|
{
|
|
return this.GetId().CompareTo(other.GetId());
|
|
}
|
|
|
|
public int GetId()
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private class EventCloseListener : IListener
|
|
{
|
|
private Socket socket;
|
|
|
|
public EventCloseListener(Socket socket)
|
|
{
|
|
this.socket = socket;
|
|
}
|
|
|
|
public void Call(params object[] args)
|
|
{
|
|
socket.OnClose("transport close");
|
|
}
|
|
|
|
public int CompareTo(IListener other)
|
|
{
|
|
return this.GetId().CompareTo(other.GetId());
|
|
}
|
|
|
|
public int GetId()
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
public class Options : Transport.Options
|
|
{
|
|
|
|
public ImmutableList<string> Transports;
|
|
|
|
public bool Upgrade = true;
|
|
|
|
public bool RememberUpgrade;
|
|
public string Host;
|
|
public string QueryString;
|
|
|
|
public static Options FromURI(Uri uri, Options opts)
|
|
{
|
|
if (opts == null)
|
|
{
|
|
opts = new Options();
|
|
}
|
|
|
|
opts.Host = uri.Host;
|
|
opts.Secure = uri.Scheme == "https" || uri.Scheme == "wss";
|
|
opts.Port = uri.Port;
|
|
|
|
if (!string.IsNullOrEmpty(uri.Query))
|
|
{
|
|
opts.QueryString = uri.Query;
|
|
}
|
|
|
|
return opts;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
internal void OnDrain()
|
|
{
|
|
//var log = LogManager.GetLogger(Global.CallerName());
|
|
//log.Info(string.Format("OnDrain1 PrevBufferLen={0} WriteBuffer.Count={1}", PrevBufferLen, WriteBuffer.Count));
|
|
|
|
for (int i = 0; i < this.PrevBufferLen; i++)
|
|
{
|
|
try
|
|
{
|
|
var callback = this.CallbackBuffer[i];
|
|
if (callback != null)
|
|
{
|
|
callback();
|
|
}
|
|
}
|
|
catch (ArgumentOutOfRangeException)
|
|
{
|
|
WriteBuffer = WriteBuffer.Clear();
|
|
CallbackBuffer = CallbackBuffer.Clear();
|
|
PrevBufferLen = 0;
|
|
}
|
|
}
|
|
//log.Info(string.Format("OnDrain2 PrevBufferLen={0} WriteBuffer.Count={1}", PrevBufferLen, WriteBuffer.Count));
|
|
|
|
|
|
try
|
|
{
|
|
WriteBuffer = WriteBuffer.RemoveRange(0, PrevBufferLen);
|
|
CallbackBuffer = CallbackBuffer.RemoveRange(0, PrevBufferLen);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
WriteBuffer = WriteBuffer.Clear();
|
|
CallbackBuffer = CallbackBuffer.Clear();
|
|
}
|
|
|
|
|
|
this.PrevBufferLen = 0;
|
|
//log.Info(string.Format("OnDrain3 PrevBufferLen={0} WriteBuffer.Count={1}", PrevBufferLen, WriteBuffer.Count));
|
|
|
|
if (this.WriteBuffer.Count == 0)
|
|
{
|
|
this.Emit(EVENT_DRAIN);
|
|
}
|
|
else
|
|
{
|
|
this.Flush();
|
|
}
|
|
}
|
|
|
|
private bool Flush()
|
|
{
|
|
var log = LogManager.GetLogger(Global.CallerName());
|
|
|
|
log.Info(string.Format("ReadyState={0} Transport.Writeable={1} Upgrading={2} WriteBuffer.Count={3}",ReadyState,Transport.Writable,Upgrading, WriteBuffer.Count));
|
|
if (ReadyState != ReadyStateEnum.CLOSED && ReadyState == ReadyStateEnum.OPEN && this.Transport.Writable && !Upgrading && WriteBuffer.Count != 0)
|
|
{
|
|
log.Info(string.Format("Flush {0} packets in socket", WriteBuffer.Count));
|
|
PrevBufferLen = WriteBuffer.Count;
|
|
Transport.Send(WriteBuffer);
|
|
Emit(EVENT_FLUSH);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
log.Info(string.Format("Flush Not Send"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void OnPacket(Packet packet)
|
|
{
|
|
var log = LogManager.GetLogger(Global.CallerName());
|
|
|
|
|
|
if (ReadyState == ReadyStateEnum.OPENING || ReadyState == ReadyStateEnum.OPEN)
|
|
{
|
|
log.Info(string.Format("socket received: type '{0}', data '{1}'", packet.Type, packet.Data));
|
|
|
|
Emit(EVENT_PACKET, packet);
|
|
Emit(EVENT_HEARTBEAT);
|
|
|
|
if (packet.Type == Packet.OPEN)
|
|
{
|
|
OnHandshake(new HandshakeData((string)packet.Data));
|
|
|
|
}
|
|
else if (packet.Type == Packet.PONG)
|
|
{
|
|
this.SetPing();
|
|
}
|
|
else if (packet.Type == Packet.ERROR)
|
|
{
|
|
var err = new EngineIOException("server error")
|
|
{
|
|
code = packet.Data
|
|
};
|
|
this.Emit(EVENT_ERROR, err);
|
|
}
|
|
else if (packet.Type == Packet.MESSAGE)
|
|
{
|
|
Emit(EVENT_DATA, packet.Data);
|
|
Emit(EVENT_MESSAGE, packet.Data);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log.Info(string.Format("OnPacket packet received with socket readyState '{0}'", ReadyState));
|
|
}
|
|
|
|
}
|
|
|
|
private void OnHandshake(HandshakeData handshakeData)
|
|
{
|
|
var log = LogManager.GetLogger(Global.CallerName());
|
|
log.Info(nameof(OnHandshake));
|
|
Emit(EVENT_HANDSHAKE, handshakeData);
|
|
Id = handshakeData.Sid;
|
|
Transport.Query.Add("sid", handshakeData.Sid);
|
|
Upgrades = FilterUpgrades(handshakeData.Upgrades);
|
|
PingInterval = handshakeData.PingInterval;
|
|
PingTimeout = handshakeData.PingTimeout;
|
|
OnOpen();
|
|
// In case open handler closes socket
|
|
if (ReadyStateEnum.CLOSED == this.ReadyState)
|
|
{
|
|
return;
|
|
}
|
|
this.SetPing();
|
|
|
|
this.Off(EVENT_HEARTBEAT, new OnHeartbeatAsListener(this));
|
|
this.On(EVENT_HEARTBEAT, new OnHeartbeatAsListener(this));
|
|
|
|
}
|
|
|
|
private class OnHeartbeatAsListener : IListener
|
|
{
|
|
private Socket socket;
|
|
|
|
public OnHeartbeatAsListener(Socket socket)
|
|
{
|
|
this.socket = socket;
|
|
}
|
|
|
|
void IListener.Call(params object[] args)
|
|
{
|
|
socket.OnHeartbeat(args.Length > 0 ? (long)args[0] : 0);
|
|
}
|
|
|
|
public int CompareTo(IListener other)
|
|
{
|
|
return this.GetId().CompareTo(other.GetId());
|
|
}
|
|
|
|
public int GetId()
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void SetPing()
|
|
{
|
|
//var log = LogManager.GetLogger(Global.CallerName());
|
|
|
|
if (this.PingIntervalTimer != null)
|
|
{
|
|
PingIntervalTimer.Stop();
|
|
}
|
|
var log = LogManager.GetLogger(Global.CallerName());
|
|
log.Info(string.Format("writing ping packet - expecting pong within {0}ms", PingTimeout));
|
|
|
|
PingIntervalTimer = EasyTimer.SetTimeout(() =>
|
|
{
|
|
var log2 = LogManager.GetLogger(Global.CallerName());
|
|
log2.Info("EasyTimer SetPing start");
|
|
|
|
if (Upgrading)
|
|
{
|
|
// skip this ping during upgrade
|
|
SetPing();
|
|
log2.Info("skipping Ping during upgrade");
|
|
}
|
|
else if(ReadyState == ReadyStateEnum.OPEN)
|
|
{
|
|
Ping();
|
|
OnHeartbeat(PingTimeout);
|
|
log2.Info("EasyTimer SetPing finish");
|
|
}
|
|
}, (int)PingInterval);
|
|
}
|
|
|
|
private void Ping()
|
|
{
|
|
//Send("primus::ping::" + GetJavaTime());
|
|
SendPacket(Packet.PING);
|
|
}
|
|
|
|
//private static string GetJavaTime()
|
|
//{
|
|
// var st = new DateTime(1970, 1, 1);
|
|
// var t = (DateTime.Now.ToUniversalTime() - st);
|
|
// var returnstring = t.TotalMilliseconds.ToString();
|
|
// returnstring = returnstring.Replace(".", "-");
|
|
// return returnstring;
|
|
//}
|
|
|
|
public void Write(string msg, Action fn = null)
|
|
{
|
|
Send(msg, fn);
|
|
}
|
|
|
|
public void Write(byte[] msg, Action fn = null)
|
|
{
|
|
Send(msg, fn);
|
|
}
|
|
|
|
public void Send(string msg, Action fn = null)
|
|
{
|
|
SendPacket(Packet.MESSAGE, msg, fn);
|
|
}
|
|
|
|
public void Send(byte[] msg, Action fn = null)
|
|
{
|
|
SendPacket(Packet.MESSAGE, msg, fn);
|
|
}
|
|
|
|
|
|
|
|
private void SendPacket(string type)
|
|
{
|
|
SendPacket(new Packet(type), null);
|
|
}
|
|
|
|
private void SendPacket(string type, string data, Action fn)
|
|
{
|
|
SendPacket(new Packet(type, data), fn);
|
|
}
|
|
|
|
private void SendPacket(string type, byte[] data, Action fn)
|
|
{
|
|
SendPacket(new Packet(type, data), fn);
|
|
}
|
|
|
|
private void SendPacket(Packet packet, Action fn)
|
|
{
|
|
if (fn == null)
|
|
{
|
|
fn = () => { };
|
|
}
|
|
|
|
if (Upgrading)
|
|
{
|
|
WaitForUpgradeAsync().Wait();
|
|
}
|
|
|
|
Emit(EVENT_PACKET_CREATE, packet);
|
|
//var log = LogManager.GetLogger(Global.CallerName());
|
|
//log.Info(string.Format("SendPacket WriteBuffer.Add(packet) packet ={0}",packet.Type));
|
|
WriteBuffer = WriteBuffer.Add(packet);
|
|
CallbackBuffer = CallbackBuffer.Add(fn);
|
|
Flush();
|
|
}
|
|
|
|
private async Task WaitForUpgradeAsync()
|
|
{
|
|
const int TIMEOUT = 1000;
|
|
|
|
await Task.Yield();
|
|
if (!SpinWait.SpinUntil(() => !Upgrading, TIMEOUT))
|
|
{
|
|
var log = LogManager.GetLogger(Global.CallerName());
|
|
log.Info("Wait for upgrade timeout");
|
|
}
|
|
}
|
|
|
|
private void OnOpen()
|
|
{
|
|
var log = LogManager.GetLogger(Global.CallerName());
|
|
|
|
//log.Info("socket open before call to flush()");
|
|
ReadyState = ReadyStateEnum.OPEN;
|
|
PriorWebsocketSuccess = WebSocket.NAME == Transport.Name;
|
|
|
|
Flush();
|
|
Emit(EVENT_OPEN);
|
|
|
|
|
|
if (ReadyState == ReadyStateEnum.OPEN && Upgrade && Transport is Polling)
|
|
//if (ReadyState == ReadyStateEnum.OPEN && Upgrade && this.Transport)
|
|
{
|
|
log.Info("OnOpen starting upgrade probes");
|
|
_errorCount = 0;
|
|
foreach (var upgrade in Upgrades)
|
|
{
|
|
Probe(upgrade);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Probe(string name)
|
|
{
|
|
var log = LogManager.GetLogger(Global.CallerName());
|
|
|
|
log.Info(string.Format("Probe probing transport '{0}'", name));
|
|
|
|
PriorWebsocketSuccess = false;
|
|
|
|
var transport = CreateTransport(name);
|
|
var parameters = new ProbeParameters
|
|
{
|
|
Transport = ImmutableList<Transport>.Empty.Add(transport),
|
|
Failed = ImmutableList<bool>.Empty.Add(false),
|
|
Cleanup = ImmutableList<Action>.Empty,
|
|
Socket = this
|
|
};
|
|
|
|
var onTransportOpen = new OnTransportOpenListener(parameters);
|
|
var freezeTransport = new FreezeTransportListener(parameters);
|
|
|
|
// Handle any error that happens while probing
|
|
var onError = new ProbingOnErrorListener(this, parameters.Transport, freezeTransport);
|
|
var onTransportClose = new ProbingOnTransportCloseListener(onError);
|
|
|
|
// When the socket is closed while we're probing
|
|
var onClose = new ProbingOnCloseListener(onError);
|
|
|
|
var onUpgrade = new ProbingOnUpgradeListener(freezeTransport, parameters.Transport);
|
|
|
|
|
|
|
|
parameters.Cleanup = parameters.Cleanup.Add(() =>
|
|
{
|
|
if (parameters.Transport.Count < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
parameters.Transport[0].Off(Transport.EVENT_OPEN, onTransportOpen);
|
|
parameters.Transport[0].Off(Transport.EVENT_ERROR, onError);
|
|
parameters.Transport[0].Off(Transport.EVENT_CLOSE, onTransportClose);
|
|
Off(EVENT_CLOSE, onClose);
|
|
Off(EVENT_UPGRADING, onUpgrade);
|
|
});
|
|
|
|
parameters.Transport[0].Once(Transport.EVENT_OPEN, onTransportOpen);
|
|
parameters.Transport[0].Once(Transport.EVENT_ERROR, onError);
|
|
parameters.Transport[0].Once(Transport.EVENT_CLOSE, onTransportClose);
|
|
|
|
this.Once(EVENT_CLOSE, onClose);
|
|
this.Once(EVENT_UPGRADING, onUpgrade);
|
|
|
|
parameters.Transport[0].Open();
|
|
}
|
|
|
|
private class ProbeParameters
|
|
{
|
|
public ImmutableList<Transport> Transport { get; set; }
|
|
public ImmutableList<bool> Failed { get; set; }
|
|
public ImmutableList<Action> Cleanup { get; set; }
|
|
public Socket Socket { get; set; }
|
|
}
|
|
|
|
private class OnTransportOpenListener : IListener
|
|
{
|
|
private ProbeParameters Parameters;
|
|
|
|
|
|
public OnTransportOpenListener(ProbeParameters parameters)
|
|
{
|
|
this.Parameters = parameters;
|
|
}
|
|
|
|
void IListener.Call(params object[] args)
|
|
{
|
|
if (Parameters.Failed[0])
|
|
{
|
|
return;
|
|
}
|
|
|
|
var packet = new Packet(Packet.PING, "probe");
|
|
Parameters.Transport[0].Once(Client.Transport.EVENT_PACKET, new ProbeEventPacketListener(this));
|
|
Parameters.Transport[0].Send(ImmutableList<Packet>.Empty.Add(packet));
|
|
}
|
|
|
|
private class ProbeEventPacketListener : IListener
|
|
{
|
|
private OnTransportOpenListener _onTransportOpenListener;
|
|
|
|
public ProbeEventPacketListener(OnTransportOpenListener onTransportOpenListener)
|
|
{
|
|
this._onTransportOpenListener = onTransportOpenListener;
|
|
}
|
|
|
|
|
|
void IListener.Call(params object[] args)
|
|
{
|
|
if (_onTransportOpenListener.Parameters.Failed[0])
|
|
{
|
|
return;
|
|
}
|
|
var log = LogManager.GetLogger(Global.CallerName());
|
|
|
|
var msg = (Packet) args[0];
|
|
if (Packet.PONG == msg.Type && "probe" == (string) msg.Data)
|
|
{
|
|
//log.Info(
|
|
// string.Format("probe transport '{0}' pong",
|
|
// _onTransportOpenListener.Parameters.Transport[0].Name));
|
|
|
|
_onTransportOpenListener.Parameters.Socket.Upgrading = true;
|
|
_onTransportOpenListener.Parameters.Socket.Emit(EVENT_UPGRADING,
|
|
_onTransportOpenListener.Parameters.Transport[0]);
|
|
Socket.PriorWebsocketSuccess = WebSocket.NAME ==
|
|
_onTransportOpenListener.Parameters.Transport[0].Name;
|
|
|
|
//log.Info(
|
|
// string.Format("pausing current transport '{0}'",
|
|
// _onTransportOpenListener.Parameters.Socket.Transport.Name));
|
|
((Polling) _onTransportOpenListener.Parameters.Socket.Transport).Pause(
|
|
() =>
|
|
{
|
|
if (_onTransportOpenListener.Parameters.Failed[0])
|
|
{
|
|
// reset upgrading flag and resume polling
|
|
((Polling)_onTransportOpenListener.Parameters.Socket.Transport).Resume();
|
|
_onTransportOpenListener.Parameters.Socket.Upgrading = false;
|
|
_onTransportOpenListener.Parameters.Socket.Flush();
|
|
return;
|
|
}
|
|
if (ReadyStateEnum.CLOSED == _onTransportOpenListener.Parameters.Socket.ReadyState ||
|
|
ReadyStateEnum.CLOSING == _onTransportOpenListener.Parameters.Socket.ReadyState)
|
|
{
|
|
return;
|
|
}
|
|
|
|
log.Info("changing transport and sending upgrade packet");
|
|
|
|
_onTransportOpenListener.Parameters.Cleanup[0]();
|
|
|
|
_onTransportOpenListener.Parameters.Socket.SetTransport(
|
|
_onTransportOpenListener.Parameters.Transport[0]);
|
|
var packetList =
|
|
ImmutableList<Packet>.Empty.Add(new Packet(Packet.UPGRADE));
|
|
try
|
|
{
|
|
_onTransportOpenListener.Parameters.Transport[0].Send(packetList);
|
|
|
|
_onTransportOpenListener.Parameters.Socket.Upgrading = false;
|
|
_onTransportOpenListener.Parameters.Socket.Flush();
|
|
|
|
_onTransportOpenListener.Parameters.Socket.Emit(EVENT_UPGRADE,
|
|
_onTransportOpenListener.Parameters.Transport[0]);
|
|
_onTransportOpenListener.Parameters.Transport =
|
|
_onTransportOpenListener.Parameters.Transport.RemoveAt(0);
|
|
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
log.Error("",e);
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
else
|
|
{
|
|
log.Info(string.Format("probe transport '{0}' failed",
|
|
_onTransportOpenListener.Parameters.Transport[0].Name));
|
|
|
|
var err = new EngineIOException("probe error");
|
|
_onTransportOpenListener.Parameters.Socket.Emit(EVENT_UPGRADE_ERROR, err);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public int CompareTo(IListener other)
|
|
{
|
|
return this.GetId().CompareTo(other.GetId());
|
|
}
|
|
|
|
public int GetId()
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public int CompareTo(IListener other)
|
|
{
|
|
return this.GetId().CompareTo(other.GetId());
|
|
}
|
|
|
|
public int GetId()
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private class FreezeTransportListener : IListener
|
|
{
|
|
private ProbeParameters Parameters;
|
|
|
|
public FreezeTransportListener(ProbeParameters parameters)
|
|
{
|
|
this.Parameters = parameters;
|
|
}
|
|
|
|
void IListener.Call(params object[] args)
|
|
{
|
|
if (Parameters.Failed[0])
|
|
{
|
|
return;
|
|
}
|
|
|
|
Parameters.Failed = Parameters.Failed.SetItem(0, true);
|
|
|
|
Parameters.Cleanup[0]();
|
|
|
|
if (Parameters.Transport.Count < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Parameters.Transport[0].Close();
|
|
Parameters.Transport = ImmutableList<Transport>.Empty;
|
|
}
|
|
|
|
public int CompareTo(IListener other)
|
|
{
|
|
return this.GetId().CompareTo(other.GetId());
|
|
}
|
|
|
|
public int GetId()
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private class ProbingOnErrorListener : IListener
|
|
{
|
|
private readonly Socket _socket;
|
|
private readonly ImmutableList<Transport> _transport;
|
|
private readonly IListener _freezeTransport;
|
|
|
|
public ProbingOnErrorListener(Socket socket, ImmutableList<Transport> transport, IListener freezeTransport)
|
|
{
|
|
this._socket = socket;
|
|
this._transport = transport;
|
|
this._freezeTransport = freezeTransport;
|
|
}
|
|
|
|
void IListener.Call(params object[] args)
|
|
{
|
|
var err = args[0];
|
|
EngineIOException error;
|
|
if (err is Exception)
|
|
{
|
|
error = new EngineIOException("probe error", (Exception)err);
|
|
}
|
|
else if (err is string)
|
|
{
|
|
error = new EngineIOException("probe error: " + (string)err);
|
|
}
|
|
else
|
|
{
|
|
error = new EngineIOException("probe error");
|
|
}
|
|
error.Transport = _transport[0].Name;
|
|
|
|
_freezeTransport.Call();
|
|
|
|
var log = LogManager.GetLogger(Global.CallerName());
|
|
|
|
log.Info(string.Format("probe transport \"{0}\" failed because of error: {1}", error.Transport, err));
|
|
_socket.Emit(EVENT_UPGRADE_ERROR, error);
|
|
}
|
|
|
|
public int CompareTo(IListener other)
|
|
{
|
|
return this.GetId().CompareTo(other.GetId());
|
|
}
|
|
|
|
public int GetId()
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private class ProbingOnTransportCloseListener : IListener
|
|
{
|
|
private readonly IListener _onError;
|
|
|
|
public ProbingOnTransportCloseListener(ProbingOnErrorListener onError)
|
|
{
|
|
this._onError = onError;
|
|
}
|
|
|
|
void IListener.Call(params object[] args)
|
|
{
|
|
_onError.Call("transport closed");
|
|
}
|
|
|
|
public int CompareTo(IListener other)
|
|
{
|
|
return this.GetId().CompareTo(other.GetId());
|
|
}
|
|
|
|
public int GetId()
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private class ProbingOnCloseListener : IListener
|
|
{
|
|
private IListener _onError;
|
|
|
|
public ProbingOnCloseListener(ProbingOnErrorListener onError)
|
|
{
|
|
this._onError = onError;
|
|
}
|
|
|
|
void IListener.Call(params object[] args)
|
|
{
|
|
_onError.Call("socket closed");
|
|
}
|
|
|
|
public int CompareTo(IListener other)
|
|
{
|
|
return this.GetId().CompareTo(other.GetId());
|
|
}
|
|
|
|
public int GetId()
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private class ProbingOnUpgradeListener : IListener
|
|
{
|
|
private readonly IListener _freezeTransport;
|
|
private readonly ImmutableList<Transport> _transport;
|
|
|
|
public ProbingOnUpgradeListener(FreezeTransportListener freezeTransport, ImmutableList<Transport> transport)
|
|
{
|
|
this._freezeTransport = freezeTransport;
|
|
this._transport = transport;
|
|
}
|
|
|
|
void IListener.Call(params object[] args)
|
|
{
|
|
var to = (Transport)args[0];
|
|
if (_transport[0] != null && to.Name != _transport[0].Name)
|
|
{
|
|
var log = LogManager.GetLogger(Global.CallerName());
|
|
|
|
log.Info(string.Format("'{0}' works - aborting '{1}'", to.Name, _transport[0].Name));
|
|
_freezeTransport.Call();
|
|
}
|
|
}
|
|
|
|
public int CompareTo(IListener other)
|
|
{
|
|
return this.GetId().CompareTo(other.GetId());
|
|
}
|
|
|
|
public int GetId()
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public Socket Close()
|
|
{
|
|
if (this.ReadyState == ReadyStateEnum.OPENING || this.ReadyState == ReadyStateEnum.OPEN)
|
|
{
|
|
var log = LogManager.GetLogger(Global.CallerName());
|
|
log.Info("Start");
|
|
this.OnClose("forced close");
|
|
|
|
log.Info("socket closing - telling transport to close");
|
|
Transport.Close();
|
|
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void OnClose(string reason, Exception desc = null)
|
|
{
|
|
if (this.ReadyState == ReadyStateEnum.OPENING || this.ReadyState == ReadyStateEnum.OPEN)
|
|
{
|
|
var log = LogManager.GetLogger(Global.CallerName());
|
|
|
|
log.Info(string.Format("OnClose socket close with reason: {0}", reason));
|
|
|
|
// clear timers
|
|
if (this.PingIntervalTimer != null)
|
|
{
|
|
this.PingIntervalTimer.Stop();
|
|
}
|
|
if (this.PingTimeoutTimer != null)
|
|
{
|
|
this.PingTimeoutTimer.Stop();
|
|
}
|
|
|
|
|
|
//WriteBuffer = WriteBuffer.Clear();
|
|
//CallbackBuffer = CallbackBuffer.Clear();
|
|
//PrevBufferLen = 0;
|
|
|
|
EasyTimer.SetTimeout(() =>
|
|
{
|
|
WriteBuffer = ImmutableList<Packet>.Empty;
|
|
CallbackBuffer = ImmutableList<Action>.Empty;
|
|
PrevBufferLen = 0;
|
|
}, 1);
|
|
|
|
|
|
if (this.Transport != null)
|
|
{
|
|
// stop event from firing again for transport
|
|
this.Transport.Off(EVENT_CLOSE);
|
|
|
|
// ensure transport won't stay open
|
|
this.Transport.Close();
|
|
|
|
// ignore further transport communication
|
|
this.Transport.Off();
|
|
}
|
|
|
|
// set ready state
|
|
this.ReadyState = ReadyStateEnum.CLOSED;
|
|
|
|
// clear session id
|
|
this.Id = null;
|
|
|
|
// emit close events
|
|
this.Emit(EVENT_CLOSE, reason, desc);
|
|
}
|
|
}
|
|
|
|
public ImmutableList<string> FilterUpgrades(IEnumerable<string> upgrades)
|
|
{
|
|
var filterUpgrades = ImmutableList<string>.Empty;
|
|
foreach (var upgrade in upgrades)
|
|
{
|
|
if (Transports.Contains(upgrade))
|
|
{
|
|
filterUpgrades = filterUpgrades.Add(upgrade);
|
|
}
|
|
}
|
|
return filterUpgrades;
|
|
}
|
|
|
|
|
|
|
|
internal void OnHeartbeat(long timeout)
|
|
{
|
|
if (this.PingTimeoutTimer != null)
|
|
{
|
|
PingTimeoutTimer.Stop();
|
|
PingTimeoutTimer = null;
|
|
}
|
|
|
|
if (timeout <= 0)
|
|
{
|
|
timeout = this.PingInterval + this.PingTimeout;
|
|
}
|
|
|
|
PingTimeoutTimer = EasyTimer.SetTimeout(() =>
|
|
{
|
|
var log2 = LogManager.GetLogger(Global.CallerName());
|
|
log2.Info("EasyTimer OnHeartbeat start");
|
|
if (ReadyState == ReadyStateEnum.CLOSED)
|
|
{
|
|
log2.Info("EasyTimer OnHeartbeat ReadyState == ReadyStateEnum.CLOSED finish");
|
|
return;
|
|
}
|
|
OnClose("ping timeout");
|
|
log2.Info("EasyTimer OnHeartbeat finish");
|
|
},(int) timeout);
|
|
|
|
}
|
|
|
|
private int _errorCount = 0;
|
|
|
|
internal void OnError(Exception exception)
|
|
{
|
|
var log = LogManager.GetLogger(Global.CallerName());
|
|
|
|
log.Error("socket error", exception);
|
|
PriorWebsocketSuccess = false;
|
|
|
|
//prevent endless loop
|
|
if (_errorCount == 0)
|
|
{
|
|
_errorCount++;
|
|
Emit(EVENT_ERROR, exception);
|
|
OnClose("transport error", exception);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|