#if !BESTHTTP_DISABLE_SIGNALR using System; using System.Collections.Generic; using BestHTTP.JSON; namespace BestHTTP.SignalR { public sealed class NegotiationData { #region Public Negotiate data /// /// Path to the SignalR endpoint. Currently not used by the client. /// public string Url { get; private set; } /// /// WebSocket endpoint. /// public string WebSocketServerUrl { get; private set; } /// /// 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 /// public string ConnectionToken { get; private set; } /// /// The id of the connection. /// public string ConnectionId { get; private set; } /// /// 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. /// public TimeSpan? KeepAliveTimeout { get; private set; } /// /// The amount of time within which the client should try to reconnect if the connection goes away. /// public TimeSpan DisconnectTimeout { get; private set; } /// /// Timeout of poll requests. /// public TimeSpan ConnectionTimeout { get; private set; } /// /// Whether the server supports websockets. /// public bool TryWebSockets { get; private set; } /// /// The version of the protocol used for communication. /// public string ProtocolVersion { get; private set; } /// /// The maximum amount of time the client should try to connect to the server using a given transport. /// public TimeSpan TransportConnectTimeout { get; private set; } /// /// The wait time before restablishing a long poll connection after data is sent from the server. The default value is 0. /// public TimeSpan LongPollDelay { get; private set; } #endregion #region Event Handlers /// /// Event handler that called when the negotiation data received and parsed successfully. /// public Action OnReceived; /// /// Event handler that called when an error happens. /// public Action OnError; #endregion #region Private private HTTPRequest NegotiationRequest; private IConnection Connection; #endregion public NegotiationData(Connection connection) { this.Connection = connection; } /// /// Start to get the negotiation data. /// 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"); } /// /// Abort the negotiation request. /// 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 dict = Json.Decode(str, ref success) as Dictionary; 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 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 from, string key) { return Get(from, key) as string; } private static List GetStringList(Dictionary from, string key) { List value = Get(from, key) as List; List result = new List(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 from, string key) { return (int)(double)Get(from, key); } private static double GetDouble(Dictionary from, string key) { return (double)Get(from, key); } #endregion } } #endif