Connection.cs 44 KB

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