PollingTransport.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. #if !BESTHTTP_DISABLE_SOCKETIO
  2. using System;
  3. using System.Text;
  4. using BestHTTP.Extensions;
  5. using BestHTTP.PlatformSupport.Memory;
  6. namespace BestHTTP.SocketIO3.Transports
  7. {
  8. public sealed class PollingTransport : ITransport
  9. {
  10. #region Public (ITransport) Properties
  11. public TransportTypes Type { get { return TransportTypes.Polling; } }
  12. public TransportStates State { get; private set; }
  13. public SocketManager Manager { get; private set; }
  14. public bool IsRequestInProgress { get { return LastRequest != null; } }
  15. public bool IsPollingInProgress { get { return PollRequest != null; } }
  16. #endregion
  17. #region Private Fields
  18. /// <summary>
  19. /// The last POST request we sent to the server.
  20. /// </summary>
  21. private HTTPRequest LastRequest;
  22. /// <summary>
  23. /// Last GET request we sent to the server.
  24. /// </summary>
  25. private HTTPRequest PollRequest;
  26. #endregion
  27. public PollingTransport(SocketManager manager)
  28. {
  29. Manager = manager;
  30. }
  31. public void Open()
  32. {
  33. string format = "{0}?EIO={1}&transport=polling&t={2}-{3}{5}";
  34. if (Manager.Handshake != null)
  35. format += "&sid={4}";
  36. bool sendAdditionalQueryParams = !Manager.Options.QueryParamsOnlyForHandshake || (Manager.Options.QueryParamsOnlyForHandshake && Manager.Handshake == null);
  37. HTTPRequest request = new HTTPRequest(new Uri(string.Format(format,
  38. Manager.Uri.ToString(),
  39. Manager.ProtocolVersion,
  40. Manager.Timestamp.ToString(),
  41. Manager.RequestCounter++.ToString(),
  42. Manager.Handshake != null ? Manager.Handshake.Sid : string.Empty,
  43. sendAdditionalQueryParams ? Manager.Options.BuildQueryParams() : string.Empty)),
  44. OnRequestFinished);
  45. #if !BESTHTTP_DISABLE_CACHING
  46. // Don't even try to cache it
  47. request.DisableCache = true;
  48. #endif
  49. request.MaxRetries = 0;
  50. if (this.Manager.Options.HTTPRequestCustomizationCallback != null)
  51. this.Manager.Options.HTTPRequestCustomizationCallback(this.Manager, request);
  52. request.Send();
  53. State = TransportStates.Opening;
  54. }
  55. /// <summary>
  56. /// Closes the transport and cleans up resources.
  57. /// </summary>
  58. public void Close()
  59. {
  60. if (State == TransportStates.Closed)
  61. return;
  62. State = TransportStates.Closed;
  63. }
  64. #region Packet Sending Implementation
  65. private System.Collections.Generic.List<OutgoingPacket> lonelyPacketList = new System.Collections.Generic.List<OutgoingPacket>(1);
  66. public void Send(OutgoingPacket packet)
  67. {
  68. try
  69. {
  70. lonelyPacketList.Add(packet);
  71. Send(lonelyPacketList);
  72. }
  73. finally
  74. {
  75. lonelyPacketList.Clear();
  76. }
  77. }
  78. public void Send(System.Collections.Generic.List<OutgoingPacket> packets)
  79. {
  80. if (State != TransportStates.Opening && State != TransportStates.Open)
  81. return;
  82. if (IsRequestInProgress)
  83. throw new Exception("Sending packets are still in progress!");
  84. LastRequest = new HTTPRequest(new Uri(string.Format("{0}?EIO={1}&transport=polling&t={2}-{3}&sid={4}{5}",
  85. Manager.Uri.ToString(),
  86. Manager.ProtocolVersion,
  87. Manager.Timestamp.ToString(),
  88. Manager.RequestCounter++.ToString(),
  89. Manager.Handshake.Sid,
  90. !Manager.Options.QueryParamsOnlyForHandshake ? Manager.Options.BuildQueryParams() : string.Empty)),
  91. HTTPMethods.Post,
  92. OnRequestFinished);
  93. #if !BESTHTTP_DISABLE_CACHING
  94. // Don't even try to cache it
  95. LastRequest.DisableCache = true;
  96. #endif
  97. EncodePackets(packets, LastRequest);
  98. if (this.Manager.Options.HTTPRequestCustomizationCallback != null)
  99. this.Manager.Options.HTTPRequestCustomizationCallback(this.Manager, LastRequest);
  100. LastRequest.Send();
  101. }
  102. StringBuilder sendBuilder = new StringBuilder();
  103. private void EncodePackets(System.Collections.Generic.List<OutgoingPacket> packets, HTTPRequest request)
  104. {
  105. sendBuilder.Length = 0;
  106. for (int i = 0; i < packets.Count; ++i)
  107. {
  108. var packet = packets[i];
  109. if (packet.IsBinary)
  110. {
  111. sendBuilder.Append('b');
  112. sendBuilder.Append(Convert.ToBase64String(packet.PayloadData.Data, packet.PayloadData.Offset, packet.PayloadData.Count));
  113. }
  114. else
  115. {
  116. sendBuilder.Append(packet.Payload);
  117. }
  118. if (packet.Attachements != null)
  119. {
  120. for (int cv = 0; cv < packet.Attachements.Count; ++cv)
  121. {
  122. sendBuilder.Append((char)0x1E);
  123. sendBuilder.Append('b');
  124. sendBuilder.Append(Convert.ToBase64String(packet.Attachements[cv]));
  125. }
  126. }
  127. if (i < packets.Count - 1)
  128. sendBuilder.Append((char)0x1E);
  129. BufferPool.Release(packet.PayloadData);
  130. }
  131. string result = sendBuilder.ToString();
  132. var length = System.Text.Encoding.UTF8.GetByteCount(result);
  133. var buffer = BufferPool.Get(length, true);
  134. System.Text.Encoding.UTF8.GetBytes(result, 0, result.Length, buffer, 0);
  135. var stream = new BufferSegmentStream();
  136. stream.Write(new BufferSegment(buffer, 0, length));
  137. request.UploadStream = stream;
  138. request.SetHeader("Content-Type", "text/plain; charset=UTF-8");
  139. }
  140. private void OnRequestFinished(HTTPRequest req, HTTPResponse resp)
  141. {
  142. // Clear out the LastRequest variable, so we can start sending out new packets
  143. LastRequest = null;
  144. if (State == TransportStates.Closed)
  145. return;
  146. string errorString = null;
  147. switch (req.State)
  148. {
  149. // The request finished without any problem.
  150. case HTTPRequestStates.Finished:
  151. if (HTTPManager.Logger.Level <= BestHTTP.Logger.Loglevels.All)
  152. HTTPManager.Logger.Verbose("PollingTransport", "OnRequestFinished: " + resp.DataAsText, this.Manager.Context);
  153. if (resp.IsSuccess)
  154. {
  155. // When we are sending data, the response is an 'ok' string
  156. if (req.MethodType != HTTPMethods.Post)
  157. ParseResponse(resp);
  158. }
  159. else
  160. errorString = string.Format("Polling - Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2} Uri: {3}",
  161. resp.StatusCode,
  162. resp.Message,
  163. resp.DataAsText,
  164. req.CurrentUri);
  165. break;
  166. // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
  167. case HTTPRequestStates.Error:
  168. errorString = (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception");
  169. break;
  170. // The request aborted, initiated by the user.
  171. case HTTPRequestStates.Aborted:
  172. errorString = string.Format("Polling - Request({0}) Aborted!", req.CurrentUri);
  173. break;
  174. // Connecting to the server is timed out.
  175. case HTTPRequestStates.ConnectionTimedOut:
  176. errorString = string.Format("Polling - Connection Timed Out! Uri: {0}", req.CurrentUri);
  177. break;
  178. // The request didn't finished in the given time.
  179. case HTTPRequestStates.TimedOut:
  180. errorString = string.Format("Polling - Processing the request({0}) Timed Out!", req.CurrentUri);
  181. break;
  182. }
  183. if (!string.IsNullOrEmpty(errorString))
  184. (Manager as IManager).OnTransportError(this, errorString);
  185. }
  186. #endregion
  187. #region Polling Implementation
  188. public void Poll()
  189. {
  190. if (PollRequest != null || State == TransportStates.Paused)
  191. return;
  192. PollRequest = new HTTPRequest(new Uri(string.Format("{0}?EIO={1}&transport=polling&t={2}-{3}&sid={4}{5}",
  193. Manager.Uri.ToString(),
  194. Manager.ProtocolVersion,
  195. Manager.Timestamp.ToString(),
  196. Manager.RequestCounter++.ToString(),
  197. Manager.Handshake.Sid,
  198. !Manager.Options.QueryParamsOnlyForHandshake ? Manager.Options.BuildQueryParams() : string.Empty)),
  199. HTTPMethods.Get,
  200. OnPollRequestFinished);
  201. #if !BESTHTTP_DISABLE_CACHING
  202. // Don't even try to cache it
  203. PollRequest.DisableCache = true;
  204. #endif
  205. PollRequest.MaxRetries = 0;
  206. if (this.Manager.Options.HTTPRequestCustomizationCallback != null)
  207. this.Manager.Options.HTTPRequestCustomizationCallback(this.Manager, PollRequest);
  208. PollRequest.Send();
  209. }
  210. private void OnPollRequestFinished(HTTPRequest req, HTTPResponse resp)
  211. {
  212. // Clear the PollRequest variable, so we can start a new poll.
  213. PollRequest = null;
  214. if (State == TransportStates.Closed)
  215. return;
  216. string errorString = null;
  217. switch (req.State)
  218. {
  219. // The request finished without any problem.
  220. case HTTPRequestStates.Finished:
  221. if (HTTPManager.Logger.Level <= BestHTTP.Logger.Loglevels.All)
  222. HTTPManager.Logger.Verbose("PollingTransport", "OnPollRequestFinished: " + resp.DataAsText, this.Manager.Context);
  223. if (resp.IsSuccess)
  224. ParseResponse(resp);
  225. else
  226. errorString = string.Format("Polling - Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2} Uri: {3}",
  227. resp.StatusCode,
  228. resp.Message,
  229. resp.DataAsText,
  230. req.CurrentUri);
  231. break;
  232. // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
  233. case HTTPRequestStates.Error:
  234. errorString = req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception";
  235. break;
  236. // The request aborted, initiated by the user.
  237. case HTTPRequestStates.Aborted:
  238. errorString = string.Format("Polling - Request({0}) Aborted!", req.CurrentUri);
  239. break;
  240. // Connecting to the server is timed out.
  241. case HTTPRequestStates.ConnectionTimedOut:
  242. errorString = string.Format("Polling - Connection Timed Out! Uri: {0}", req.CurrentUri);
  243. break;
  244. // The request didn't finished in the given time.
  245. case HTTPRequestStates.TimedOut:
  246. errorString = string.Format("Polling - Processing the request({0}) Timed Out!", req.CurrentUri);
  247. break;
  248. }
  249. if (!string.IsNullOrEmpty(errorString))
  250. (Manager as IManager).OnTransportError(this, errorString);
  251. }
  252. #endregion
  253. #region Packet Parsing and Handling
  254. /// <summary>
  255. /// Preprocessing and sending out packets to the manager.
  256. /// </summary>
  257. private void OnPacket(IncomingPacket packet)
  258. {
  259. switch (packet.TransportEvent)
  260. {
  261. case TransportEventTypes.Open:
  262. if (this.State != TransportStates.Opening)
  263. HTTPManager.Logger.Warning("PollingTransport", "Received 'Open' packet while state is '" + State.ToString() + "'", this.Manager.Context);
  264. else
  265. State = TransportStates.Open;
  266. goto default;
  267. case TransportEventTypes.Message:
  268. if (packet.SocketIOEvent == SocketIOEventTypes.Connect) //2:40
  269. this.State = TransportStates.Open;
  270. goto default;
  271. default:
  272. (Manager as IManager).OnPacket(packet);
  273. break;
  274. }
  275. }
  276. private void ParseResponse(HTTPResponse resp)
  277. {
  278. try
  279. {
  280. if (resp == null || resp.Data == null || resp.Data.Length < 1)
  281. return;
  282. int idx = 0;
  283. while (idx < resp.Data.Length)
  284. {
  285. int endIdx = FindNextRecordSeparator(resp.Data, idx);
  286. int length = endIdx - idx;
  287. if (length <= 0)
  288. break;
  289. IncomingPacket packet = IncomingPacket.Empty;
  290. if (resp.Data[idx] == 'b')
  291. {
  292. // First byte is the binary indicator('b'). We must skip it, so we advance our idx and also have to decrease length
  293. idx++;
  294. length--;
  295. var base64Encoded = System.Text.Encoding.UTF8.GetString(resp.Data, idx, length);
  296. var byteData = Convert.FromBase64String(base64Encoded);
  297. packet = this.Manager.Parser.Parse(this.Manager, new BufferSegment(byteData, 0, byteData.Length));
  298. }
  299. else
  300. {
  301. // It's the handshake data?
  302. if (this.State == TransportStates.Opening)
  303. {
  304. TransportEventTypes transportEvent = (TransportEventTypes)(resp.Data[idx] - '0');
  305. if (transportEvent == TransportEventTypes.Open)
  306. {
  307. var handshake = BestHTTP.JSON.LitJson.JsonMapper.ToObject<HandshakeData>(Encoding.UTF8.GetString(resp.Data, idx + 1, length - 1));
  308. packet = new IncomingPacket(TransportEventTypes.Open, SocketIOEventTypes.Unknown, "/", -1);
  309. packet.DecodedArg = handshake;
  310. }
  311. else
  312. {
  313. // TODO: error?
  314. }
  315. }
  316. else
  317. {
  318. packet = this.Manager.Parser.Parse(this.Manager, System.Text.Encoding.UTF8.GetString(resp.Data, idx, length));
  319. }
  320. }
  321. if (!packet.Equals(IncomingPacket.Empty))
  322. {
  323. try
  324. {
  325. OnPacket(packet);
  326. }
  327. catch (Exception ex)
  328. {
  329. HTTPManager.Logger.Exception("PollingTransport", "ParseResponse - OnPacket", ex, this.Manager.Context);
  330. (Manager as IManager).EmitError(ex.Message + " " + ex.StackTrace);
  331. }
  332. }
  333. idx = endIdx + 1;
  334. }
  335. }
  336. catch (Exception ex)
  337. {
  338. (Manager as IManager).EmitError(ex.Message + " " + ex.StackTrace);
  339. HTTPManager.Logger.Exception("PollingTransport", "ParseResponse", ex, this.Manager.Context);
  340. }
  341. }
  342. private int FindNextRecordSeparator(byte[] data, int startIdx)
  343. {
  344. for (int i = startIdx; i < data.Length; ++i)
  345. {
  346. if (data[i] == 0x1E)
  347. return i;
  348. }
  349. return data.Length;
  350. }
  351. #endregion
  352. }
  353. }
  354. #endif