#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 /// /// Name of the transport. /// public string Name { get; protected set; } /// /// True if the manager has to check the last message received time and reconnect if too much time passes. /// public abstract bool SupportsKeepAlive { get; } /// /// Type of the transport. Used mainly by the manager in its BuildUri function. /// public abstract TransportTypes Type { get; } /// /// Reference to the manager. /// public IConnection Connection { get; protected set; } /// /// The current state of the transport. /// public TransportStates State { get { return _state; } protected set { TransportStates old = _state; _state = value; if (OnStateChanged != null) OnStateChanged(this, old, _state); } } public TransportStates _state; /// /// Thi event called when the transport's State set to a new value. /// public event OnTransportStateChangedDelegate OnStateChanged; #endregion public TransportBase(string name, Connection connection) { this.Name = name; this.Connection = connection; this.State = TransportStates.Initial; } #region Abstract functions /// /// Start to connect to the server /// public abstract void Connect(); /// /// Stop the connection /// public abstract void Stop(); /// /// The transport specific implementation to send the given json string to the server. /// protected abstract void SendImpl(string json); /// /// Called when the Start request finished successfully, or after a reconnect. /// Manager.TransportOpened(); called from the TransportBase after this call /// protected abstract void Started(); /// /// Called when the abort request finished successfully. /// protected abstract void Aborted(); #endregion /// /// Called after a succesful connect/reconnect. The transport implementations have to call this function. /// 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 /// /// Sends out the /start request to the server. /// protected void Start() { HTTPManager.Logger.Information("Transport - " + this.Name, "Sending Start Request"); this.State = TransportStates.Starting; if (this.Connection.Protocol > ProtocolVersions.Protocol_2_0) { var request = new HTTPRequest(Connection.BuildUri(RequestTypes.Start, this), HTTPMethods.Get, true, true, OnStartRequestFinished); request.Tag = 0; request.DisableRetry = true; request.Timeout = Connection.NegotiationResult.ConnectionTimeout + TimeSpan.FromSeconds(10); Connection.PrepareRequest(request, RequestTypes.Start); request.Send(); } else { // The transport and the signalr protocol now started this.State = TransportStates.Started; Started(); Connection.TransportStarted(); } } private void OnStartRequestFinished(HTTPRequest req, HTTPResponse resp) { switch (req.State) { case HTTPRequestStates.Finished: if (resp.IsSuccess) { HTTPManager.Logger.Information("Transport - " + this.Name, "Start - Returned: " + resp.DataAsText); string response = Connection.ParseResponse(resp.DataAsText); if (response != "started") { Connection.Error(string.Format("Expected 'started' response, but '{0}' found!", response)); return; } // The transport and the signalr protocol now started this.State = TransportStates.Started; Started(); Connection.TransportStarted(); return; } else HTTPManager.Logger.Warning("Transport - " + this.Name, string.Format("Start - request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2} Uri: {3}", resp.StatusCode, resp.Message, resp.DataAsText, req.CurrentUri)); goto default; default: HTTPManager.Logger.Information("Transport - " + this.Name, "Start request state: " + req.State.ToString()); // The request may not reached the server. Try it again. int retryCount = (int)req.Tag; if (retryCount++ < MaxRetryCount) { req.Tag = retryCount; req.Send(); } else Connection.Error("Failed to send Start request."); break; } } #endregion #region Abort Implementation /// /// Will abort the transport. In SignalR 'Abort'ing is a graceful process, while 'Close'ing is a hard-abortion... /// public virtual void Abort() { if (this.State != TransportStates.Started) return; this.State = TransportStates.Closing; var request = new HTTPRequest(Connection.BuildUri(RequestTypes.Abort, this), HTTPMethods.Get, true, true, OnAbortRequestFinished); // Retry counter request.Tag = 0; request.DisableRetry = true; Connection.PrepareRequest(request, RequestTypes.Abort); request.Send(); } protected void AbortFinished() { this.State = TransportStates.Closed; Connection.TransportAborted(); this.Aborted(); } private void OnAbortRequestFinished(HTTPRequest req, HTTPResponse resp) { switch (req.State) { case HTTPRequestStates.Finished: if (resp.IsSuccess) { HTTPManager.Logger.Information("Transport - " + this.Name, "Abort - Returned: " + resp.DataAsText); if (this.State == TransportStates.Closing) AbortFinished(); } else { HTTPManager.Logger.Warning("Transport - " + this.Name, string.Format("Abort - Handshake request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2} Uri: {3}", resp.StatusCode, resp.Message, resp.DataAsText, req.CurrentUri)); // try again goto default; } break; default: HTTPManager.Logger.Information("Transport - " + this.Name, "Abort request state: " + req.State.ToString()); // The request may not reached the server. Try it again. int retryCount = (int)req.Tag; if (retryCount++ < MaxRetryCount) { req.Tag = retryCount; req.Send(); } else Connection.Error("Failed to send Abort request!"); break; } } #endregion #region Send Implementation /// /// Sends the given json string to the wire. /// /// 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 /// /// Start the reconnect process /// public void Reconnect() { HTTPManager.Logger.Information("Transport - " + this.Name, "Reconnecting"); Stop(); this.State = TransportStates.Reconnecting; Connect(); } /// /// When the json string is successfully parsed will return with an IServerMessage implementation. /// 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 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