TransportBase.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. #if !BESTHTTP_DISABLE_SIGNALR
  2. using System;
  3. using System.Collections.Generic;
  4. using BestHTTP.SignalR.Messages;
  5. using BestHTTP.SignalR.JsonEncoders;
  6. namespace BestHTTP.SignalR.Transports
  7. {
  8. public delegate void OnTransportStateChangedDelegate(TransportBase transport, TransportStates oldState, TransportStates newState);
  9. public abstract class TransportBase
  10. {
  11. private const int MaxRetryCount = 5;
  12. #region Public Properties
  13. /// <summary>
  14. /// Name of the transport.
  15. /// </summary>
  16. public string Name { get; protected set; }
  17. /// <summary>
  18. /// True if the manager has to check the last message received time and reconnect if too much time passes.
  19. /// </summary>
  20. public abstract bool SupportsKeepAlive { get; }
  21. /// <summary>
  22. /// Type of the transport. Used mainly by the manager in its BuildUri function.
  23. /// </summary>
  24. public abstract TransportTypes Type { get; }
  25. /// <summary>
  26. /// Reference to the manager.
  27. /// </summary>
  28. public IConnection Connection { get; protected set; }
  29. /// <summary>
  30. /// The current state of the transport.
  31. /// </summary>
  32. public TransportStates State
  33. {
  34. get { return _state; }
  35. protected set
  36. {
  37. TransportStates old = _state;
  38. _state = value;
  39. if (OnStateChanged != null)
  40. OnStateChanged(this, old, _state);
  41. }
  42. }
  43. public TransportStates _state;
  44. /// <summary>
  45. /// Thi event called when the transport's State set to a new value.
  46. /// </summary>
  47. public event OnTransportStateChangedDelegate OnStateChanged;
  48. #endregion
  49. public TransportBase(string name, Connection connection)
  50. {
  51. this.Name = name;
  52. this.Connection = connection;
  53. this.State = TransportStates.Initial;
  54. }
  55. #region Abstract functions
  56. /// <summary>
  57. /// Start to connect to the server
  58. /// </summary>
  59. public abstract void Connect();
  60. /// <summary>
  61. /// Stop the connection
  62. /// </summary>
  63. public abstract void Stop();
  64. /// <summary>
  65. /// The transport specific implementation to send the given json string to the server.
  66. /// </summary>
  67. protected abstract void SendImpl(string json);
  68. /// <summary>
  69. /// Called when the Start request finished successfully, or after a reconnect.
  70. /// Manager.TransportOpened(); called from the TransportBase after this call
  71. /// </summary>
  72. protected abstract void Started();
  73. /// <summary>
  74. /// Called when the abort request finished successfully.
  75. /// </summary>
  76. protected abstract void Aborted();
  77. #endregion
  78. /// <summary>
  79. /// Called after a succesful connect/reconnect. The transport implementations have to call this function.
  80. /// </summary>
  81. protected void OnConnected()
  82. {
  83. if (this.State != TransportStates.Reconnecting)
  84. {
  85. // Send the Start request
  86. Start();
  87. }
  88. else
  89. {
  90. Connection.TransportReconnected();
  91. Started();
  92. this.State = TransportStates.Started;
  93. }
  94. }
  95. #region Start Request Sending
  96. /// <summary>
  97. /// Sends out the /start request to the server.
  98. /// </summary>
  99. protected void Start()
  100. {
  101. HTTPManager.Logger.Information("Transport - " + this.Name, "Sending Start Request");
  102. this.State = TransportStates.Starting;
  103. if (this.Connection.Protocol > ProtocolVersions.Protocol_2_0)
  104. {
  105. var request = new HTTPRequest(Connection.BuildUri(RequestTypes.Start, this), HTTPMethods.Get, true, true, OnStartRequestFinished);
  106. request.Tag = 0;
  107. request.DisableRetry = true;
  108. request.Timeout = Connection.NegotiationResult.ConnectionTimeout + TimeSpan.FromSeconds(10);
  109. Connection.PrepareRequest(request, RequestTypes.Start);
  110. request.Send();
  111. }
  112. else
  113. {
  114. // The transport and the signalr protocol now started
  115. this.State = TransportStates.Started;
  116. Started();
  117. Connection.TransportStarted();
  118. }
  119. }
  120. private void OnStartRequestFinished(HTTPRequest req, HTTPResponse resp)
  121. {
  122. switch (req.State)
  123. {
  124. case HTTPRequestStates.Finished:
  125. if (resp.IsSuccess)
  126. {
  127. HTTPManager.Logger.Information("Transport - " + this.Name, "Start - Returned: " + resp.DataAsText);
  128. string response = Connection.ParseResponse(resp.DataAsText);
  129. if (response != "started")
  130. {
  131. Connection.Error(string.Format("Expected 'started' response, but '{0}' found!", response));
  132. return;
  133. }
  134. // The transport and the signalr protocol now started
  135. this.State = TransportStates.Started;
  136. Started();
  137. Connection.TransportStarted();
  138. return;
  139. }
  140. else
  141. HTTPManager.Logger.Warning("Transport - " + this.Name, string.Format("Start - request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2} Uri: {3}",
  142. resp.StatusCode,
  143. resp.Message,
  144. resp.DataAsText,
  145. req.CurrentUri));
  146. goto default;
  147. default:
  148. HTTPManager.Logger.Information("Transport - " + this.Name, "Start request state: " + req.State.ToString());
  149. // The request may not reached the server. Try it again.
  150. int retryCount = (int)req.Tag;
  151. if (retryCount++ < MaxRetryCount)
  152. {
  153. req.Tag = retryCount;
  154. req.Send();
  155. }
  156. else
  157. Connection.Error("Failed to send Start request.");
  158. break;
  159. }
  160. }
  161. #endregion
  162. #region Abort Implementation
  163. /// <summary>
  164. /// Will abort the transport. In SignalR 'Abort'ing is a graceful process, while 'Close'ing is a hard-abortion...
  165. /// </summary>
  166. public virtual void Abort()
  167. {
  168. if (this.State != TransportStates.Started)
  169. return;
  170. this.State = TransportStates.Closing;
  171. var request = new HTTPRequest(Connection.BuildUri(RequestTypes.Abort, this), HTTPMethods.Get, true, true, OnAbortRequestFinished);
  172. // Retry counter
  173. request.Tag = 0;
  174. request.DisableRetry = true;
  175. Connection.PrepareRequest(request, RequestTypes.Abort);
  176. request.Send();
  177. }
  178. protected void AbortFinished()
  179. {
  180. this.State = TransportStates.Closed;
  181. Connection.TransportAborted();
  182. this.Aborted();
  183. }
  184. private void OnAbortRequestFinished(HTTPRequest req, HTTPResponse resp)
  185. {
  186. switch (req.State)
  187. {
  188. case HTTPRequestStates.Finished:
  189. if (resp.IsSuccess)
  190. {
  191. HTTPManager.Logger.Information("Transport - " + this.Name, "Abort - Returned: " + resp.DataAsText);
  192. if (this.State == TransportStates.Closing)
  193. AbortFinished();
  194. }
  195. else
  196. {
  197. HTTPManager.Logger.Warning("Transport - " + this.Name, string.Format("Abort - Handshake request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2} Uri: {3}",
  198. resp.StatusCode,
  199. resp.Message,
  200. resp.DataAsText,
  201. req.CurrentUri));
  202. // try again
  203. goto default;
  204. }
  205. break;
  206. default:
  207. HTTPManager.Logger.Information("Transport - " + this.Name, "Abort request state: " + req.State.ToString());
  208. // The request may not reached the server. Try it again.
  209. int retryCount = (int)req.Tag;
  210. if (retryCount++ < MaxRetryCount)
  211. {
  212. req.Tag = retryCount;
  213. req.Send();
  214. }
  215. else
  216. Connection.Error("Failed to send Abort request!");
  217. break;
  218. }
  219. }
  220. #endregion
  221. #region Send Implementation
  222. /// <summary>
  223. /// Sends the given json string to the wire.
  224. /// </summary>
  225. /// <param name="jsonStr"></param>
  226. public void Send(string jsonStr)
  227. {
  228. try
  229. {
  230. HTTPManager.Logger.Information("Transport - " + this.Name, "Sending: " + jsonStr);
  231. SendImpl(jsonStr);
  232. }
  233. catch (Exception ex)
  234. {
  235. HTTPManager.Logger.Exception("Transport - " + this.Name, "Send", ex);
  236. }
  237. }
  238. #endregion
  239. #region Helper Functions
  240. /// <summary>
  241. /// Start the reconnect process
  242. /// </summary>
  243. public void Reconnect()
  244. {
  245. HTTPManager.Logger.Information("Transport - " + this.Name, "Reconnecting");
  246. Stop();
  247. this.State = TransportStates.Reconnecting;
  248. Connect();
  249. }
  250. /// <summary>
  251. /// When the json string is successfully parsed will return with an IServerMessage implementation.
  252. /// </summary>
  253. public static IServerMessage Parse(IJsonEncoder encoder, string json)
  254. {
  255. // Nothing to parse?
  256. if (string.IsNullOrEmpty(json))
  257. {
  258. HTTPManager.Logger.Error("MessageFactory", "Parse - called with empty or null string!");
  259. return null;
  260. }
  261. // We don't have to do further decoding, if it's an empty json object, then it's a KeepAlive message from the server
  262. if (json.Length == 2 && json == "{}")
  263. return new KeepAliveMessage();
  264. IDictionary<string, object> msg = null;
  265. try
  266. {
  267. // try to decode the json message with the encoder
  268. msg = encoder.DecodeMessage(json);
  269. }
  270. catch(Exception ex)
  271. {
  272. HTTPManager.Logger.Exception("MessageFactory", "Parse - encoder.DecodeMessage", ex);
  273. return null;
  274. }
  275. if (msg == null)
  276. {
  277. HTTPManager.Logger.Error("MessageFactory", "Parse - Json Decode failed for json string: \"" + json + "\"");
  278. return null;
  279. }
  280. // "C" is for message id
  281. IServerMessage result = null;
  282. if (!msg.ContainsKey("C"))
  283. {
  284. // If there are no ErrorMessage in the object, then it was a success
  285. if (!msg.ContainsKey("E"))
  286. result = new ResultMessage();
  287. else
  288. result = new FailureMessage();
  289. }
  290. else
  291. result = new MultiMessage();
  292. try
  293. {
  294. result.Parse(msg);
  295. }
  296. catch
  297. {
  298. HTTPManager.Logger.Error("MessageFactory", "Can't parse msg: " + json);
  299. throw;
  300. }
  301. return result;
  302. }
  303. #endregion
  304. }
  305. }
  306. #endif