Connection.cs 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299
  1. #if !BESTHTTP_DISABLE_SIGNALR
  2. using System;
  3. using System.Text;
  4. using System.Collections.Generic;
  5. using BestHTTP.Extensions;
  6. using BestHTTP.SignalR.Hubs;
  7. using BestHTTP.SignalR.Messages;
  8. using BestHTTP.SignalR.Transports;
  9. using BestHTTP.SignalR.JsonEncoders;
  10. using BestHTTP.SignalR.Authentication;
  11. using PlatformSupport.Collections.ObjectModel;
  12. using BestHTTP.Connections;
  13. using BestHTTP.PlatformSupport.Text;
  14. #if !NETFX_CORE
  15. using PlatformSupport.Collections.Specialized;
  16. #else
  17. using System.Collections.Specialized;
  18. #endif
  19. namespace BestHTTP.SignalR
  20. {
  21. public delegate void OnNonHubMessageDelegate(Connection connection, object data);
  22. public delegate void OnConnectedDelegate(Connection connection);
  23. public delegate void OnClosedDelegate(Connection connection);
  24. public delegate void OnErrorDelegate(Connection connection, string error);
  25. public delegate void OnStateChanged(Connection connection, ConnectionStates oldState, ConnectionStates newState);
  26. public delegate void OnPrepareRequestDelegate(Connection connection, HTTPRequest req, RequestTypes type);
  27. /// <summary>
  28. /// Interface to be able to hide internally used functions and properties.
  29. /// </summary>
  30. public interface IConnection
  31. {
  32. ProtocolVersions Protocol { get; }
  33. NegotiationData NegotiationResult { get; }
  34. IJsonEncoder JsonEncoder { get; set; }
  35. void OnMessage(IServerMessage msg);
  36. void TransportStarted();
  37. void TransportReconnected();
  38. void TransportAborted();
  39. void Error(string reason);
  40. Uri BuildUri(RequestTypes type);
  41. Uri BuildUri(RequestTypes type, TransportBase transport);
  42. HTTPRequest PrepareRequest(HTTPRequest req, RequestTypes type);
  43. string ParseResponse(string responseStr);
  44. }
  45. /// <summary>
  46. /// Supported versions of the SignalR protocol.
  47. /// </summary>
  48. public enum ProtocolVersions : byte
  49. {
  50. Protocol_2_0,
  51. Protocol_2_1,
  52. Protocol_2_2
  53. }
  54. /// <summary>
  55. /// The main SignalR class. This is the entry point to connect to a SignalR service.
  56. /// </summary>
  57. public sealed class Connection : IHeartbeat, IConnection
  58. {
  59. #region Public Properties
  60. /// <summary>
  61. /// The default Json encode/decoder that will be used to encode/decode the event arguments.
  62. /// </summary>
  63. public static IJsonEncoder DefaultEncoder =
  64. #if BESTHTTP_SIGNALR_WITH_JSONDOTNET
  65. new JSonDotnetEncoder();
  66. #else
  67. new DefaultJsonEncoder();
  68. #endif
  69. /// <summary>
  70. /// The base url endpoint where the SignalR service can be found.
  71. /// </summary>
  72. public Uri Uri { get; private set; }
  73. /// <summary>
  74. /// Current State of the SignalR connection.
  75. /// </summary>
  76. public ConnectionStates State
  77. {
  78. get { return _state; }
  79. private set
  80. {
  81. ConnectionStates old = _state;
  82. _state = value;
  83. if (OnStateChanged != null)
  84. OnStateChanged(this, old, _state);
  85. }
  86. }
  87. private ConnectionStates _state;
  88. /// <summary>
  89. /// Result of the negotiation request from the server.
  90. /// </summary>
  91. public NegotiationData NegotiationResult { get; private set; }
  92. /// <summary>
  93. /// The hubs that the client is connected to.
  94. /// </summary>
  95. public Hub[] Hubs { get; private set; }
  96. /// <summary>
  97. /// The transport that is used to send and receive messages.
  98. /// </summary>
  99. public TransportBase Transport { get; private set; }
  100. /// <summary>
  101. /// Current client protocol in use.
  102. /// </summary>
  103. public ProtocolVersions Protocol { get; private set; }
  104. /// <summary>
  105. /// Additional query parameters that will be passed for the handshake uri. If the value is null, or an empty string it will be not appended to the query only the key.
  106. /// <remarks>The keys and values must be escaped properly, as the plugin will not escape these. </remarks>
  107. /// </summary>
  108. public ObservableDictionary<string, string> AdditionalQueryParams
  109. {
  110. get { return additionalQueryParams; }
  111. set
  112. {
  113. // Unsubscribe from previous dictionary's events
  114. if (additionalQueryParams != null)
  115. additionalQueryParams.CollectionChanged -= AdditionalQueryParams_CollectionChanged;
  116. additionalQueryParams = value;
  117. // Clear out the cached value
  118. BuiltQueryParams = null;
  119. // Subscribe to the collection changed event
  120. if (value != null)
  121. value.CollectionChanged += AdditionalQueryParams_CollectionChanged;
  122. }
  123. }
  124. private ObservableDictionary<string, string> additionalQueryParams;
  125. /// <summary>
  126. /// If it's false, the parameters in the AdditionalQueryParams will be passed for all http requests. Its default value is true.
  127. /// </summary>
  128. public bool QueryParamsOnlyForHandshake { get; set; }
  129. /// <summary>
  130. /// The Json encoder that will be used by the connection and the transport.
  131. /// </summary>
  132. public IJsonEncoder JsonEncoder { get; set; }
  133. /// <summary>
  134. /// An IAuthenticationProvider implementation that will be used to authenticate the connection.
  135. /// </summary>
  136. public IAuthenticationProvider AuthenticationProvider { get; set; }
  137. /// <summary>
  138. /// How much time we have to wait between two pings.
  139. /// </summary>
  140. public TimeSpan PingInterval { get; set; }
  141. /// <summary>
  142. /// Wait time before the plugin should do a reconnect attempt. Its default value is 5 seconds.
  143. /// </summary>
  144. public TimeSpan ReconnectDelay { get; set; }
  145. #endregion
  146. #region Public Events
  147. /// <summary>
  148. /// Called when the protocol is open for communication.
  149. /// </summary>
  150. public event OnConnectedDelegate OnConnected;
  151. /// <summary>
  152. /// Called when the connection is closed, and no further messages are sent or received.
  153. /// </summary>
  154. public event OnClosedDelegate OnClosed;
  155. /// <summary>
  156. /// Called when an error occures. If the connection is already Started, it will try to do a reconnect, otherwise it will close the connection.
  157. /// </summary>
  158. public event OnErrorDelegate OnError;
  159. /// <summary>
  160. /// This event called when a reconnection attempt are started. If fails to reconnect an OnError and OnClosed events are called.
  161. /// </summary>
  162. public event OnConnectedDelegate OnReconnecting;
  163. /// <summary>
  164. /// This event called when the reconnection attempt succeded.
  165. /// </summary>
  166. public event OnConnectedDelegate OnReconnected;
  167. /// <summary>
  168. /// Called every time when the connection's state changes.
  169. /// </summary>
  170. public event OnStateChanged OnStateChanged;
  171. /// <summary>
  172. /// It's called when a non-Hub message received. The data can be anything from primitive types to array of complex objects.
  173. /// </summary>
  174. public event OnNonHubMessageDelegate OnNonHubMessage;
  175. /// <summary>
  176. /// With this delegate all requests can be further customized.
  177. /// </summary>
  178. public OnPrepareRequestDelegate RequestPreparator { get; set; }
  179. #endregion
  180. #region Indexers
  181. /// <summary>
  182. /// Indexer property the access hubs by index.
  183. /// </summary>
  184. public Hub this[int idx] { get { return Hubs[idx] as Hub; } }
  185. /// <summary>
  186. /// Indexer property the access hubs by name.
  187. /// </summary>
  188. public Hub this[string hubName]
  189. {
  190. get
  191. {
  192. for (int i = 0; i < Hubs.Length; ++i)
  193. {
  194. Hub hub = Hubs[i] as Hub;
  195. if (hub.Name.Equals(hubName, StringComparison.OrdinalIgnoreCase))
  196. return hub;
  197. }
  198. return null;
  199. }
  200. }
  201. #endregion
  202. #region Internals
  203. /// <summary>
  204. /// Unique ID for all message sent by the client.
  205. /// </summary>
  206. internal long ClientMessageCounter;
  207. #endregion
  208. #region Privates
  209. /// <summary>
  210. /// Supported client protocol versions.
  211. /// </summary>
  212. private readonly string[] ClientProtocols = new string[] { "1.3", "1.4", "1.5" };
  213. /// <summary>
  214. /// A timestamp that will be sent with all request for easier debugging.
  215. /// </summary>
  216. private UInt32 Timestamp { get { return (UInt32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).Ticks; } }
  217. /// <summary>
  218. /// Request counter sent with all request for easier debugging.
  219. /// </summary>
  220. private long RequestCounter;
  221. /// <summary>
  222. /// Instance of the last received message. Used for its MessageId.
  223. /// </summary>
  224. private MultiMessage LastReceivedMessage;
  225. /// <summary>
  226. /// The GroupsToken sent by the server that stores what groups we are joined to.
  227. /// We will send it with the reconnect request.
  228. /// </summary>
  229. private string GroupsToken;
  230. /// <summary>
  231. /// Received messages before the Start request finishes.
  232. /// </summary>
  233. private List<IServerMessage> BufferedMessages;
  234. /// <summary>
  235. /// When the last message received from the server. Used for reconnecting.
  236. /// </summary>
  237. private DateTime LastMessageReceivedAt;
  238. /// <summary>
  239. /// When we started to reconnect. When too much time passes without a successful reconnect, we will close the connection.
  240. /// </summary>
  241. private DateTime ReconnectStartedAt;
  242. private DateTime ReconnectDelayStartedAt;
  243. /// <summary>
  244. /// True, if the reconnect process started.
  245. /// </summary>
  246. private bool ReconnectStarted;
  247. /// <summary>
  248. /// When the last ping request sent out.
  249. /// </summary>
  250. private DateTime LastPingSentAt;
  251. /// <summary>
  252. /// Reference to the ping request.
  253. /// </summary>
  254. private HTTPRequest PingRequest;
  255. /// <summary>
  256. /// When the transport started the connection process
  257. /// </summary>
  258. private DateTime? TransportConnectionStartedAt;
  259. /// <summary>
  260. /// Cached StringBuilder instance used in BuildUri
  261. /// </summary>
  262. private StringBuilder queryBuilder = new StringBuilder();
  263. /// <summary>
  264. /// Builds and returns with the connection data made from the hub names.
  265. /// </summary>
  266. private string ConnectionData
  267. {
  268. get
  269. {
  270. if (!string.IsNullOrEmpty(BuiltConnectionData))
  271. return BuiltConnectionData;
  272. StringBuilder sb = new StringBuilder("[", Hubs.Length * 4);
  273. if (Hubs != null)
  274. for (int i = 0; i < Hubs.Length; ++i)
  275. {
  276. sb.Append(@"{""Name"":""");
  277. sb.Append(Hubs[i].Name);
  278. sb.Append(@"""}");
  279. if (i < Hubs.Length - 1)
  280. sb.Append(",");
  281. }
  282. sb.Append("]");
  283. return BuiltConnectionData = Uri.EscapeUriString(sb.ToString());
  284. }
  285. }
  286. /// <summary>
  287. /// The cached value of the result of the ConnectionData property call.
  288. /// </summary>
  289. private string BuiltConnectionData;
  290. /// <summary>
  291. /// Builds the keys and values from the AdditionalQueryParams to an key=value form. If AdditionalQueryParams is null or empty, it will return an empty string.
  292. /// </summary>
  293. private string QueryParams
  294. {
  295. get
  296. {
  297. if (AdditionalQueryParams == null || AdditionalQueryParams.Count == 0)
  298. return string.Empty;
  299. if (!string.IsNullOrEmpty(BuiltQueryParams))
  300. return BuiltQueryParams;
  301. StringBuilder sb = StringBuilderPool.Get(AdditionalQueryParams.Count * 4); //new StringBuilder(AdditionalQueryParams.Count * 4);
  302. foreach (var kvp in AdditionalQueryParams)
  303. {
  304. sb.Append("&");
  305. sb.Append(kvp.Key);
  306. if (!string.IsNullOrEmpty(kvp.Value))
  307. {
  308. sb.Append("=");
  309. sb.Append(Uri.EscapeDataString(kvp.Value));
  310. }
  311. }
  312. return BuiltQueryParams = StringBuilderPool.ReleaseAndGrab(sb);
  313. }
  314. }
  315. /// <summary>
  316. /// The cached value of the result of the QueryParams property call.
  317. /// </summary>
  318. private string BuiltQueryParams;
  319. private SupportedProtocols NextProtocolToTry;
  320. #endregion
  321. #region Constructors
  322. public Connection(Uri uri, params string[] hubNames)
  323. : this(uri)
  324. {
  325. if (hubNames != null && hubNames.Length > 0)
  326. {
  327. this.Hubs = new Hub[hubNames.Length];
  328. for (int i = 0; i < hubNames.Length; ++i)
  329. this.Hubs[i] = new Hub(hubNames[i], this);
  330. }
  331. }
  332. public Connection(Uri uri, params Hub[] hubs)
  333. :this(uri)
  334. {
  335. this.Hubs = hubs;
  336. if (hubs != null)
  337. for (int i = 0; i < hubs.Length; ++i)
  338. (hubs[i] as IHub).Connection = this;
  339. }
  340. public Connection(Uri uri)
  341. {
  342. this.State = ConnectionStates.Initial;
  343. this.Uri = uri;
  344. this.JsonEncoder = Connection.DefaultEncoder;
  345. this.PingInterval = TimeSpan.FromMinutes(5);
  346. // Expected protocol
  347. this.Protocol = ProtocolVersions.Protocol_2_2;
  348. this.ReconnectDelay = TimeSpan.FromSeconds(5);
  349. }
  350. #endregion
  351. #region Starting the protocol
  352. /// <summary>
  353. /// This function will start to authenticate if required, and the SignalR protocol negotiation.
  354. /// </summary>
  355. public void Open()
  356. {
  357. if (State != ConnectionStates.Initial && State != ConnectionStates.Closed)
  358. return;
  359. if (AuthenticationProvider != null && AuthenticationProvider.IsPreAuthRequired)
  360. {
  361. this.State = ConnectionStates.Authenticating;
  362. AuthenticationProvider.OnAuthenticationSucceded += OnAuthenticationSucceded;
  363. AuthenticationProvider.OnAuthenticationFailed += OnAuthenticationFailed;
  364. // Start the authentication process
  365. AuthenticationProvider.StartAuthentication();
  366. }
  367. else
  368. StartImpl();
  369. }
  370. /// <summary>
  371. /// Called when the authentication succeeded.
  372. /// </summary>
  373. /// <param name="provider"></param>
  374. private void OnAuthenticationSucceded(IAuthenticationProvider provider)
  375. {
  376. provider.OnAuthenticationSucceded -= OnAuthenticationSucceded;
  377. provider.OnAuthenticationFailed -= OnAuthenticationFailed;
  378. StartImpl();
  379. }
  380. /// <summary>
  381. /// Called when the authentication failed.
  382. /// </summary>
  383. private void OnAuthenticationFailed(IAuthenticationProvider provider, string reason)
  384. {
  385. provider.OnAuthenticationSucceded -= OnAuthenticationSucceded;
  386. provider.OnAuthenticationFailed -= OnAuthenticationFailed;
  387. (this as IConnection).Error(reason);
  388. }
  389. /// <summary>
  390. /// It's the real Start implementation. It will start the negotiation
  391. /// </summary>
  392. private void StartImpl()
  393. {
  394. this.State = ConnectionStates.Negotiating;
  395. NegotiationResult = new NegotiationData(this);
  396. NegotiationResult.OnReceived = OnNegotiationDataReceived;
  397. NegotiationResult.OnError = OnNegotiationError;
  398. NegotiationResult.Start();
  399. }
  400. #region Negotiation Event Handlers
  401. /// <summary>
  402. /// Protocol negotiation finished successfully.
  403. /// </summary>
  404. private void OnNegotiationDataReceived(NegotiationData data)
  405. {
  406. // Find out what supported protocol the server speak
  407. int protocolIdx = -1;
  408. for (int i = 0; i < ClientProtocols.Length && protocolIdx == -1; ++i)
  409. if (data.ProtocolVersion == ClientProtocols[i])
  410. protocolIdx = i;
  411. // No supported protocol found? Try using the latest one.
  412. if (protocolIdx == -1)
  413. {
  414. protocolIdx = (byte)ProtocolVersions.Protocol_2_2;
  415. HTTPManager.Logger.Warning("SignalR Connection", "Unknown protocol version: " + data.ProtocolVersion);
  416. }
  417. this.Protocol = (ProtocolVersions)protocolIdx;
  418. #if !BESTHTTP_DISABLE_WEBSOCKET
  419. if (data.TryWebSockets)
  420. {
  421. Transport = new WebSocketTransport(this);
  422. #if !BESTHTTP_DISABLE_SERVERSENT_EVENTS
  423. NextProtocolToTry = SupportedProtocols.ServerSentEvents;
  424. #else
  425. NextProtocolToTry = SupportedProtocols.HTTP;
  426. #endif
  427. }
  428. else
  429. #endif
  430. {
  431. #if !BESTHTTP_DISABLE_SERVERSENT_EVENTS
  432. Transport = new ServerSentEventsTransport(this);
  433. // Long-Poll
  434. NextProtocolToTry = SupportedProtocols.HTTP;
  435. #else
  436. Transport = new PollingTransport(this);
  437. NextProtocolToTry = SupportedProtocols.Unknown;
  438. #endif
  439. }
  440. this.State = ConnectionStates.Connecting;
  441. TransportConnectionStartedAt = DateTime.UtcNow;
  442. Transport.Connect();
  443. }
  444. /// <summary>
  445. /// Protocol negotiation failed.
  446. /// </summary>
  447. private void OnNegotiationError(NegotiationData data, string error)
  448. {
  449. (this as IConnection).Error(error);
  450. }
  451. #endregion
  452. #endregion
  453. #region Public Interface
  454. /// <summary>
  455. /// Closes the connection and shuts down the transport.
  456. /// </summary>
  457. public void Close()
  458. {
  459. if (this.State == ConnectionStates.Closed)
  460. return;
  461. this.State = ConnectionStates.Closed;
  462. //ReconnectStartedAt = null;
  463. ReconnectStarted = false;
  464. TransportConnectionStartedAt = null;
  465. if (Transport != null)
  466. {
  467. Transport.Abort();
  468. Transport = null;
  469. }
  470. NegotiationResult = null;
  471. HTTPManager.Heartbeats.Unsubscribe(this);
  472. LastReceivedMessage = null;
  473. if (Hubs != null)
  474. for (int i = 0; i < Hubs.Length; ++i)
  475. (Hubs[i] as IHub).Close();
  476. if (BufferedMessages != null)
  477. {
  478. BufferedMessages.Clear();
  479. BufferedMessages = null;
  480. }
  481. if (OnClosed != null)
  482. {
  483. try
  484. {
  485. OnClosed(this);
  486. }
  487. catch (Exception ex)
  488. {
  489. HTTPManager.Logger.Exception("SignalR Connection", "OnClosed", ex);
  490. }
  491. }
  492. }
  493. /// <summary>
  494. /// Initiates a reconnect to the SignalR server.
  495. /// </summary>
  496. public void Reconnect()
  497. {
  498. // Return if reconnect process already started.
  499. if (ReconnectStarted)
  500. return;
  501. ReconnectStarted = true;
  502. // Set ReconnectStartedAt only when the previous State is not Reconnecting,
  503. // so we keep the first date&time when we started reconnecting
  504. if (this.State != ConnectionStates.Reconnecting)
  505. ReconnectStartedAt = DateTime.UtcNow;
  506. this.State = ConnectionStates.Reconnecting;
  507. HTTPManager.Logger.Warning("SignalR Connection", "Reconnecting");
  508. Transport.Reconnect();
  509. if (PingRequest != null)
  510. PingRequest.Abort();
  511. if (OnReconnecting != null)
  512. {
  513. try
  514. {
  515. OnReconnecting(this);
  516. }
  517. catch (Exception ex)
  518. {
  519. HTTPManager.Logger.Exception("SignalR Connection", "OnReconnecting", ex);
  520. }
  521. }
  522. }
  523. /// <summary>
  524. /// Will encode the argument to a Json string using the Connection's JsonEncoder, then will send it to the server.
  525. /// </summary>
  526. /// <returns>True if the plugin was able to send out the message</returns>
  527. public bool Send(object arg)
  528. {
  529. if (arg == null)
  530. throw new ArgumentNullException("arg");
  531. if (this.State != ConnectionStates.Connected)
  532. return false;
  533. string json = JsonEncoder.Encode(arg);
  534. if (string.IsNullOrEmpty(json))
  535. HTTPManager.Logger.Error("SignalR Connection", "Failed to JSon encode the given argument. Please try to use an advanced JSon encoder(check the documentation how you can do it).");
  536. else
  537. Transport.Send(json);
  538. return true;
  539. }
  540. /// <summary>
  541. /// Sends the given json string to the server.
  542. /// </summary>
  543. /// <returns>True if the plugin was able to send out the message</returns>
  544. public bool SendJson(string json)
  545. {
  546. if (json == null)
  547. throw new ArgumentNullException("json");
  548. if (this.State != ConnectionStates.Connected)
  549. return false;
  550. Transport.Send(json);
  551. return true;
  552. }
  553. #endregion
  554. #region IManager Functions
  555. /// <summary>
  556. /// Called when we receive a message from the server
  557. /// </summary>
  558. void IConnection.OnMessage(IServerMessage msg)
  559. {
  560. if (this.State == ConnectionStates.Closed)
  561. return;
  562. // Store messages that we receive while we are connecting
  563. if (this.State == ConnectionStates.Connecting)
  564. {
  565. if (BufferedMessages == null)
  566. BufferedMessages = new List<IServerMessage>();
  567. BufferedMessages.Add(msg);
  568. return;
  569. }
  570. LastMessageReceivedAt = DateTime.UtcNow;
  571. switch(msg.Type)
  572. {
  573. case MessageTypes.Multiple:
  574. LastReceivedMessage = msg as MultiMessage;
  575. // Not received in the reconnect process, so we can't rely on it
  576. if (LastReceivedMessage.IsInitialization)
  577. HTTPManager.Logger.Information("SignalR Connection", "OnMessage - Init");
  578. if (LastReceivedMessage.GroupsToken != null)
  579. GroupsToken = LastReceivedMessage.GroupsToken;
  580. if (LastReceivedMessage.ShouldReconnect)
  581. {
  582. HTTPManager.Logger.Information("SignalR Connection", "OnMessage - Should Reconnect");
  583. Reconnect();
  584. // Should we return here not processing the messages that may come with it?
  585. //return;
  586. }
  587. if (LastReceivedMessage.Data != null)
  588. for (int i = 0; i < LastReceivedMessage.Data.Count; ++i)
  589. (this as IConnection).OnMessage(LastReceivedMessage.Data[i]);
  590. break;
  591. case MessageTypes.MethodCall:
  592. MethodCallMessage methodCall = msg as MethodCallMessage;
  593. Hub hub = this[methodCall.Hub];
  594. if (hub != null)
  595. (hub as IHub).OnMethod(methodCall);
  596. else
  597. HTTPManager.Logger.Warning("SignalR Connection", string.Format("Hub \"{0}\" not found!", methodCall.Hub));
  598. break;
  599. case MessageTypes.Result:
  600. case MessageTypes.Failure:
  601. case MessageTypes.Progress:
  602. UInt64 id = (msg as IHubMessage).InvocationId;
  603. hub = FindHub(id);
  604. if (hub != null)
  605. (hub as IHub).OnMessage(msg);
  606. else
  607. HTTPManager.Logger.Warning("SignalR Connection", string.Format("No Hub found for Progress message! Id: {0}", id.ToString()));
  608. break;
  609. case MessageTypes.Data:
  610. if (OnNonHubMessage != null)
  611. OnNonHubMessage(this, (msg as DataMessage).Data);
  612. break;
  613. case MessageTypes.KeepAlive:
  614. break;
  615. default:
  616. HTTPManager.Logger.Warning("SignalR Connection", "Unknown message type received: " + msg.Type.ToString());
  617. break;
  618. }
  619. }
  620. /// <summary>
  621. /// Called from the transport implementations when the Start request finishes successfully.
  622. /// </summary>
  623. void IConnection.TransportStarted()
  624. {
  625. if (this.State != ConnectionStates.Connecting)
  626. return;
  627. InitOnStart();
  628. if (OnConnected != null)
  629. {
  630. try
  631. {
  632. OnConnected(this);
  633. }
  634. catch (Exception ex)
  635. {
  636. HTTPManager.Logger.Exception("SignalR Connection", "OnOpened", ex);
  637. }
  638. }
  639. // Deliver messages that we received before the /start request returned.
  640. // This must be after the OnStarted call, to let the clients to subrscribe to these events.
  641. if (BufferedMessages != null)
  642. {
  643. for (int i = 0; i < BufferedMessages.Count; ++i)
  644. (this as IConnection).OnMessage(BufferedMessages[i]);
  645. BufferedMessages.Clear();
  646. BufferedMessages = null;
  647. }
  648. }
  649. /// <summary>
  650. /// Called when the transport sucessfully reconnected to the server.
  651. /// </summary>
  652. void IConnection.TransportReconnected()
  653. {
  654. if (this.State != ConnectionStates.Reconnecting)
  655. return;
  656. HTTPManager.Logger.Information("SignalR Connection", "Transport Reconnected");
  657. InitOnStart();
  658. if (OnReconnected != null)
  659. {
  660. try
  661. {
  662. OnReconnected(this);
  663. }
  664. catch (Exception ex)
  665. {
  666. HTTPManager.Logger.Exception("SignalR Connection", "OnReconnected", ex);
  667. }
  668. }
  669. }
  670. /// <summary>
  671. /// Called from the transport implementation when the Abort request finishes successfully.
  672. /// </summary>
  673. void IConnection.TransportAborted()
  674. {
  675. Close();
  676. }
  677. /// <summary>
  678. /// Called when an error occures. If the connection is in the Connected state, it will start the reconnect process, otherwise it will close the connection.
  679. /// </summary>
  680. void IConnection.Error(string reason)
  681. {
  682. // Not interested about errors we received after we already closed
  683. if (this.State == ConnectionStates.Closed)
  684. return;
  685. // If we are just quitting, don't try to reconnect.
  686. if (HTTPManager.IsQuitting)
  687. {
  688. Close();
  689. return;
  690. }
  691. HTTPManager.Logger.Error("SignalR Connection", reason);
  692. ReconnectStarted = false;
  693. if (OnError != null)
  694. OnError(this, reason);
  695. if (this.State == ConnectionStates.Connected || this.State == ConnectionStates.Reconnecting)
  696. {
  697. this.ReconnectDelayStartedAt = DateTime.UtcNow;
  698. if (this.State != ConnectionStates.Reconnecting)
  699. this.ReconnectStartedAt = DateTime.UtcNow;
  700. //Reconnect();
  701. }
  702. else
  703. {
  704. // Fall back if possible
  705. if (this.State != ConnectionStates.Connecting || !TryFallbackTransport())
  706. Close();
  707. }
  708. }
  709. /// <summary>
  710. /// Creates an Uri instance for the given request type.
  711. /// </summary>
  712. Uri IConnection.BuildUri(RequestTypes type)
  713. {
  714. return (this as IConnection).BuildUri(type, null);
  715. }
  716. /// <summary>
  717. /// Creates an Uri instance from the given parameters.
  718. /// </summary>
  719. Uri IConnection.BuildUri(RequestTypes type, TransportBase transport)
  720. {
  721. // make sure that the queryBuilder is reseted
  722. queryBuilder.Length = 0;
  723. UriBuilder uriBuilder = new UriBuilder(Uri);
  724. if (!uriBuilder.Path.EndsWith("/"))
  725. uriBuilder.Path += "/";
  726. long newValue, originalValue;
  727. do
  728. {
  729. originalValue = this.RequestCounter;
  730. newValue = originalValue % long.MaxValue;
  731. } while (System.Threading.Interlocked.CompareExchange(ref this.RequestCounter, newValue, originalValue) != originalValue);
  732. switch (type)
  733. {
  734. case RequestTypes.Negotiate:
  735. uriBuilder.Path += "negotiate";
  736. goto default;
  737. case RequestTypes.Connect:
  738. #if !BESTHTTP_DISABLE_WEBSOCKET
  739. if (transport != null && transport.Type == TransportTypes.WebSocket)
  740. uriBuilder.Scheme = HTTPProtocolFactory.IsSecureProtocol(Uri) ? "wss" : "ws";
  741. #endif
  742. uriBuilder.Path += "connect";
  743. goto default;
  744. case RequestTypes.Start:
  745. uriBuilder.Path += "start";
  746. goto default;
  747. case RequestTypes.Poll:
  748. uriBuilder.Path += "poll";
  749. if (this.LastReceivedMessage != null)
  750. {
  751. queryBuilder.Append("messageId=");
  752. queryBuilder.Append(this.LastReceivedMessage.MessageId);
  753. }
  754. if (!string.IsNullOrEmpty(GroupsToken))
  755. {
  756. if (queryBuilder.Length > 0)
  757. queryBuilder.Append("&");
  758. queryBuilder.Append("groupsToken=");
  759. queryBuilder.Append(GroupsToken);
  760. }
  761. goto default;
  762. case RequestTypes.Send:
  763. uriBuilder.Path += "send";
  764. goto default;
  765. case RequestTypes.Reconnect:
  766. #if !BESTHTTP_DISABLE_WEBSOCKET
  767. if (transport != null && transport.Type == TransportTypes.WebSocket)
  768. uriBuilder.Scheme = HTTPProtocolFactory.IsSecureProtocol(Uri) ? "wss" : "ws";
  769. #endif
  770. uriBuilder.Path += "reconnect";
  771. if (this.LastReceivedMessage != null)
  772. {
  773. queryBuilder.Append("messageId=");
  774. queryBuilder.Append(this.LastReceivedMessage.MessageId);
  775. }
  776. if (!string.IsNullOrEmpty(GroupsToken))
  777. {
  778. if (queryBuilder.Length > 0)
  779. queryBuilder.Append("&");
  780. queryBuilder.Append("groupsToken=");
  781. queryBuilder.Append(GroupsToken);
  782. }
  783. goto default;
  784. case RequestTypes.Abort:
  785. uriBuilder.Path += "abort";
  786. goto default;
  787. case RequestTypes.Ping:
  788. uriBuilder.Path += "ping";
  789. queryBuilder.Append("&tid=");
  790. queryBuilder.Append(System.Threading.Interlocked.Increment(ref this.RequestCounter).ToString());
  791. queryBuilder.Append("&_=");
  792. queryBuilder.Append(Timestamp.ToString());
  793. break;
  794. default:
  795. if (queryBuilder.Length > 0)
  796. queryBuilder.Append("&");
  797. queryBuilder.Append("tid=");
  798. queryBuilder.Append(System.Threading.Interlocked.Increment(ref this.RequestCounter).ToString());
  799. queryBuilder.Append("&_=");
  800. queryBuilder.Append(Timestamp.ToString());
  801. if (transport != null)
  802. {
  803. queryBuilder.Append("&transport=");
  804. queryBuilder.Append(transport.Name);
  805. }
  806. queryBuilder.Append("&clientProtocol=");
  807. queryBuilder.Append(ClientProtocols[(byte)Protocol]);
  808. if (NegotiationResult != null && !string.IsNullOrEmpty(this.NegotiationResult.ConnectionToken))
  809. {
  810. queryBuilder.Append("&connectionToken=");
  811. queryBuilder.Append(this.NegotiationResult.ConnectionToken);
  812. }
  813. if (this.Hubs != null && this.Hubs.Length > 0)
  814. {
  815. queryBuilder.Append("&connectionData=");
  816. queryBuilder.Append(this.ConnectionData);
  817. }
  818. break;
  819. }
  820. // Query params are added to all uri
  821. if (this.AdditionalQueryParams != null && this.AdditionalQueryParams.Count > 0)
  822. queryBuilder.Append(this.QueryParams);
  823. uriBuilder.Query = queryBuilder.ToString();
  824. // reset the string builder
  825. queryBuilder.Length = 0;
  826. return uriBuilder.Uri;
  827. }
  828. /// <summary>
  829. /// It's called on every request before sending it out to the server.
  830. /// </summary>
  831. HTTPRequest IConnection.PrepareRequest(HTTPRequest req, RequestTypes type)
  832. {
  833. if (req != null && AuthenticationProvider != null)
  834. AuthenticationProvider.PrepareRequest(req, type);
  835. if (RequestPreparator != null)
  836. RequestPreparator(this, req, type);
  837. return req;
  838. }
  839. /// <summary>
  840. /// Will parse a "{ 'Response': 'xyz' }" object and returns with 'xyz'. If it fails to parse, or getting the 'Response' key, it will call the Error function.
  841. /// </summary>
  842. string IConnection.ParseResponse(string responseStr)
  843. {
  844. Dictionary<string, object> dic = JSON.Json.Decode(responseStr) as Dictionary<string, object>;
  845. if (dic == null)
  846. {
  847. (this as IConnection).Error("Failed to parse Start response: " + responseStr);
  848. return string.Empty;
  849. }
  850. object value;
  851. if (!dic.TryGetValue("Response", out value) || value == null)
  852. {
  853. (this as IConnection).Error("No 'Response' key found in response: " + responseStr);
  854. return string.Empty;
  855. }
  856. return value.ToString();
  857. }
  858. #endregion
  859. #region IHeartbeat Implementation
  860. /// <summary>
  861. /// IHeartbeat implementation to manage timeouts.
  862. /// </summary>
  863. void IHeartbeat.OnHeartbeatUpdate(TimeSpan dif)
  864. {
  865. switch(this.State)
  866. {
  867. case ConnectionStates.Connected:
  868. if (Transport.SupportsKeepAlive && NegotiationResult.KeepAliveTimeout != null && DateTime.UtcNow - LastMessageReceivedAt >= NegotiationResult.KeepAliveTimeout)
  869. Reconnect();
  870. if (PingRequest == null && DateTime.UtcNow - LastPingSentAt >= PingInterval)
  871. Ping();
  872. break;
  873. case ConnectionStates.Reconnecting:
  874. if ( DateTime.UtcNow - ReconnectStartedAt >= NegotiationResult.DisconnectTimeout)
  875. {
  876. HTTPManager.Logger.Warning("SignalR Connection", "OnHeartbeatUpdate - Failed to reconnect in the given time!");
  877. Close();
  878. }
  879. else if (DateTime.UtcNow - ReconnectDelayStartedAt >= ReconnectDelay)
  880. {
  881. if (HTTPManager.Logger.Level <= Logger.Loglevels.Warning)
  882. HTTPManager.Logger.Warning("SignalR Connection", this.ReconnectStarted.ToString() + " " + this.ReconnectStartedAt.ToString() + " " + NegotiationResult.DisconnectTimeout.ToString());
  883. Reconnect();
  884. }
  885. break;
  886. default:
  887. if (TransportConnectionStartedAt != null && DateTime.UtcNow - TransportConnectionStartedAt >= NegotiationResult.TransportConnectTimeout)
  888. {
  889. HTTPManager.Logger.Warning("SignalR Connection", "OnHeartbeatUpdate - Transport failed to connect in the given time!");
  890. // Using the Error function here instead of Close() will enable us to try to do a transport fallback.
  891. (this as IConnection).Error("Transport failed to connect in the given time!");
  892. }
  893. break;
  894. }
  895. }
  896. #endregion
  897. #region Private Helper Functions
  898. /// <summary>
  899. /// Init function to set the connected states and set up other variables.
  900. /// </summary>
  901. private void InitOnStart()
  902. {
  903. this.State = ConnectionStates.Connected;
  904. //ReconnectStartedAt = null;
  905. ReconnectStarted = false;
  906. TransportConnectionStartedAt = null;
  907. LastPingSentAt = DateTime.UtcNow;
  908. LastMessageReceivedAt = DateTime.UtcNow;
  909. HTTPManager.Heartbeats.Subscribe(this);
  910. }
  911. /// <summary>
  912. /// Find and return with a Hub that has the message id.
  913. /// </summary>
  914. private Hub FindHub(UInt64 msgId)
  915. {
  916. if (Hubs != null)
  917. for (int i = 0; i < Hubs.Length; ++i)
  918. if ((Hubs[i] as IHub).HasSentMessageId(msgId))
  919. return Hubs[i];
  920. return null;
  921. }
  922. /// <summary>
  923. /// Try to fall back to next transport. If no more transport to try, it will return false.
  924. /// </summary>
  925. private bool TryFallbackTransport()
  926. {
  927. if (this.State == ConnectionStates.Connecting)
  928. {
  929. if (BufferedMessages != null)
  930. BufferedMessages.Clear();
  931. // stop the current transport
  932. Transport.Stop();
  933. Transport = null;
  934. switch(NextProtocolToTry)
  935. {
  936. #if !BESTHTTP_DISABLE_WEBSOCKET
  937. case SupportedProtocols.WebSocket:
  938. Transport = new WebSocketTransport(this);
  939. break;
  940. #endif
  941. #if !BESTHTTP_DISABLE_SERVERSENT_EVENTS
  942. case SupportedProtocols.ServerSentEvents:
  943. Transport = new ServerSentEventsTransport(this);
  944. NextProtocolToTry = SupportedProtocols.HTTP;
  945. break;
  946. #endif
  947. case SupportedProtocols.HTTP:
  948. Transport = new PollingTransport(this);
  949. NextProtocolToTry = SupportedProtocols.Unknown;
  950. break;
  951. case SupportedProtocols.Unknown:
  952. return false;
  953. }
  954. TransportConnectionStartedAt = DateTime.UtcNow;
  955. Transport.Connect();
  956. if (PingRequest != null)
  957. PingRequest.Abort();
  958. return true;
  959. }
  960. return false;
  961. }
  962. /// <summary>
  963. /// This event will be called when the AdditonalQueryPrams dictionary changed. We have to reset the cached values.
  964. /// </summary>
  965. private void AdditionalQueryParams_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  966. {
  967. BuiltQueryParams = null;
  968. }
  969. #endregion
  970. #region Ping Implementation
  971. /// <summary>
  972. /// Sends a Ping request to the SignalR server.
  973. /// </summary>
  974. private void Ping()
  975. {
  976. HTTPManager.Logger.Information("SignalR Connection", "Sending Ping request.");
  977. PingRequest = new HTTPRequest((this as IConnection).BuildUri(RequestTypes.Ping), OnPingRequestFinished);
  978. PingRequest.ConnectTimeout = PingInterval;
  979. (this as IConnection).PrepareRequest(PingRequest, RequestTypes.Ping);
  980. PingRequest.Send();
  981. LastPingSentAt = DateTime.UtcNow;
  982. }
  983. /// <summary>
  984. /// Called when the Ping request finished.
  985. /// </summary>
  986. void OnPingRequestFinished(HTTPRequest req, HTTPResponse resp)
  987. {
  988. PingRequest = null;
  989. string reason = string.Empty;
  990. switch (req.State)
  991. {
  992. // The request finished without any problem.
  993. case HTTPRequestStates.Finished:
  994. if (resp.IsSuccess)
  995. {
  996. // Parse the response, and do nothing when we receive the "pong" response
  997. string response = (this as IConnection).ParseResponse(resp.DataAsText);
  998. if (response != "pong")
  999. reason = "Wrong answer for ping request: " + response;
  1000. else
  1001. HTTPManager.Logger.Information("SignalR Connection", "Pong received.");
  1002. }
  1003. else
  1004. reason = string.Format("Ping - Request Finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
  1005. resp.StatusCode,
  1006. resp.Message,
  1007. resp.DataAsText);
  1008. break;
  1009. // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
  1010. case HTTPRequestStates.Error:
  1011. reason = "Ping - Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception");
  1012. break;
  1013. // Connecting to the server is timed out.
  1014. case HTTPRequestStates.ConnectionTimedOut:
  1015. reason = "Ping - Connection Timed Out!";
  1016. break;
  1017. // The request didn't finished in the given time.
  1018. case HTTPRequestStates.TimedOut:
  1019. reason = "Ping - Processing the request Timed Out!";
  1020. break;
  1021. }
  1022. if (!string.IsNullOrEmpty(reason))
  1023. (this as IConnection).Error(reason);
  1024. }
  1025. #endregion
  1026. }
  1027. }
  1028. #endif