#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