PollingTransport.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. #if !BESTHTTP_DISABLE_SIGNALR
  2. using System;
  3. using BestHTTP.Extensions;
  4. using BestHTTP.SignalR.Messages;
  5. namespace BestHTTP.SignalR.Transports
  6. {
  7. public sealed class PollingTransport : PostSendTransportBase, IHeartbeat
  8. {
  9. #region Overridden Properties
  10. public override bool SupportsKeepAlive { get { return false; } }
  11. public override TransportTypes Type { get { return TransportTypes.LongPoll; } }
  12. #endregion
  13. #region Privates
  14. /// <summary>
  15. /// When we received the last poll.
  16. /// </summary>
  17. private DateTime LastPoll;
  18. /// <summary>
  19. /// How much time we have to wait before we can send out a new poll request. This value sent by the server.
  20. /// </summary>
  21. private TimeSpan PollDelay;
  22. /// <summary>
  23. /// How much time we wait to a poll request to finish. It's value is the server sent negotiation's ConnectionTimeout + 10sec.
  24. /// </summary>
  25. private TimeSpan PollTimeout;
  26. /// <summary>
  27. /// Reference to the the current poll request.
  28. /// </summary>
  29. private HTTPRequest pollRequest;
  30. #endregion
  31. public PollingTransport(Connection connection)
  32. : base("longPolling", connection)
  33. {
  34. this.LastPoll = DateTime.MinValue;
  35. this.PollTimeout = connection.NegotiationResult.ConnectionTimeout + TimeSpan.FromSeconds(10);
  36. }
  37. #region Overrides from TransportBase
  38. /// <summary>
  39. /// Polling transport specific connection logic. It's a regular GET request to the /connect path.
  40. /// </summary>
  41. public override void Connect()
  42. {
  43. HTTPManager.Logger.Information("Transport - " + this.Name, "Sending Open Request");
  44. // Skip the Connecting state if we are reconnecting. If the connect succeeds, we will set the Started state directly
  45. if (this.State != TransportStates.Reconnecting)
  46. this.State = TransportStates.Connecting;
  47. RequestTypes requestType = this.State == TransportStates.Reconnecting ? RequestTypes.Reconnect : RequestTypes.Connect;
  48. var request = new HTTPRequest(Connection.BuildUri(requestType, this), HTTPMethods.Get, true, true, OnConnectRequestFinished);
  49. Connection.PrepareRequest(request, requestType);
  50. request.Send();
  51. }
  52. public override void Stop()
  53. {
  54. HTTPManager.Heartbeats.Unsubscribe(this);
  55. if (pollRequest != null)
  56. {
  57. pollRequest.Abort();
  58. pollRequest = null;
  59. }
  60. // Should we abort the send requests in the sendRequestQueue?
  61. }
  62. protected override void Started()
  63. {
  64. LastPoll = DateTime.UtcNow;
  65. HTTPManager.Heartbeats.Subscribe(this);
  66. }
  67. protected override void Aborted()
  68. {
  69. HTTPManager.Heartbeats.Unsubscribe(this);
  70. }
  71. #endregion
  72. #region Request Handlers
  73. void OnConnectRequestFinished(HTTPRequest req, HTTPResponse resp)
  74. {
  75. // error reason if there is any. We will call the manager's Error function if it's not empty.
  76. string reason = string.Empty;
  77. switch (req.State)
  78. {
  79. // The request finished without any problem.
  80. case HTTPRequestStates.Finished:
  81. if (resp.IsSuccess)
  82. {
  83. HTTPManager.Logger.Information("Transport - " + this.Name, "Connect - Request Finished Successfully! " + resp.DataAsText);
  84. OnConnected();
  85. IServerMessage msg = TransportBase.Parse(Connection.JsonEncoder, resp.DataAsText);
  86. if (msg != null)
  87. {
  88. Connection.OnMessage(msg);
  89. MultiMessage multiple = msg as MultiMessage;
  90. if (multiple != null && multiple.PollDelay.HasValue)
  91. PollDelay = multiple.PollDelay.Value;
  92. }
  93. }
  94. else
  95. reason = string.Format("Connect - Request Finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
  96. resp.StatusCode,
  97. resp.Message,
  98. resp.DataAsText);
  99. break;
  100. // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
  101. case HTTPRequestStates.Error:
  102. reason = "Connect - Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception");
  103. break;
  104. // The request aborted, initiated by the user.
  105. case HTTPRequestStates.Aborted:
  106. reason = "Connect - Request Aborted!";
  107. break;
  108. // Ceonnecting to the server is timed out.
  109. case HTTPRequestStates.ConnectionTimedOut:
  110. reason = "Connect - Connection Timed Out!";
  111. break;
  112. // The request didn't finished in the given time.
  113. case HTTPRequestStates.TimedOut:
  114. reason = "Connect - Processing the request Timed Out!";
  115. break;
  116. }
  117. if (!string.IsNullOrEmpty(reason))
  118. Connection.Error(reason);
  119. }
  120. void OnPollRequestFinished(HTTPRequest req, HTTPResponse resp)
  121. {
  122. // When Stop() called on the transport.
  123. // In Stop() we set the pollRequest to null, but a new poll request can be made after a quick reconnection, and there is a chanse that
  124. // in this handler function we can null out the new request. So we return early here.
  125. if (req.State == HTTPRequestStates.Aborted)
  126. {
  127. HTTPManager.Logger.Warning("Transport - " + this.Name, "Poll - Request Aborted!");
  128. return;
  129. }
  130. // SaveLocal the pollRequest to null, now we can send out a new one
  131. pollRequest = null;
  132. // error reason if there is any. We will call the manager's Error function if it's not empty.
  133. string reason = string.Empty;
  134. switch (req.State)
  135. {
  136. // The request finished without any problem.
  137. case HTTPRequestStates.Finished:
  138. if (resp.IsSuccess)
  139. {
  140. HTTPManager.Logger.Information("Transport - " + this.Name, "Poll - Request Finished Successfully! " + resp.DataAsText);
  141. IServerMessage msg = TransportBase.Parse(Connection.JsonEncoder, resp.DataAsText);
  142. if (msg != null)
  143. {
  144. Connection.OnMessage(msg);
  145. MultiMessage multiple = msg as MultiMessage;
  146. if (multiple != null && multiple.PollDelay.HasValue)
  147. PollDelay = multiple.PollDelay.Value;
  148. LastPoll = DateTime.UtcNow;
  149. }
  150. }
  151. else
  152. reason = string.Format("Poll - Request Finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
  153. resp.StatusCode,
  154. resp.Message,
  155. resp.DataAsText);
  156. break;
  157. // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
  158. case HTTPRequestStates.Error:
  159. reason = "Poll - Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception");
  160. break;
  161. // Ceonnecting to the server is timed out.
  162. case HTTPRequestStates.ConnectionTimedOut:
  163. reason = "Poll - Connection Timed Out!";
  164. break;
  165. // The request didn't finished in the given time.
  166. case HTTPRequestStates.TimedOut:
  167. reason = "Poll - Processing the request Timed Out!";
  168. break;
  169. }
  170. if (!string.IsNullOrEmpty(reason))
  171. Connection.Error(reason);
  172. }
  173. #endregion
  174. /// <summary>
  175. /// Polling transport speficic function. Sends a GET request to the /poll path to receive messages.
  176. /// </summary>
  177. private void Poll()
  178. {
  179. pollRequest = new HTTPRequest(Connection.BuildUri(RequestTypes.Poll, this), HTTPMethods.Get, true, true, OnPollRequestFinished);
  180. Connection.PrepareRequest(pollRequest, RequestTypes.Poll);
  181. pollRequest.Timeout = this.PollTimeout;
  182. pollRequest.Send();
  183. }
  184. #region IHeartbeat Implementation
  185. void IHeartbeat.OnHeartbeatUpdate(TimeSpan dif)
  186. {
  187. switch(State)
  188. {
  189. case TransportStates.Started:
  190. if (pollRequest == null && DateTime.UtcNow >= (LastPoll + PollDelay + Connection.NegotiationResult.LongPollDelay))
  191. Poll();
  192. break;
  193. }
  194. }
  195. #endregion
  196. }
  197. }
  198. #endif