PollingTransport.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  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. }
  130. string result = sendBuilder.ToString();
  131. var length = System.Text.Encoding.UTF8.GetByteCount(result);
  132. var buffer = BufferPool.Get(length, true);
  133. System.Text.Encoding.UTF8.GetBytes(result, 0, result.Length, buffer, 0);
  134. var stream = new BufferSegmentStream();
  135. stream.Write(new BufferSegment(buffer, 0, length));
  136. request.UploadStream = stream;
  137. request.SetHeader("Content-Type", "text/plain; charset=UTF-8");
  138. }
  139. private void OnRequestFinished(HTTPRequest req, HTTPResponse resp)
  140. {
  141. // Clear out the LastRequest variable, so we can start sending out new packets
  142. LastRequest = null;
  143. if (State == TransportStates.Closed)
  144. return;
  145. string errorString = null;
  146. switch (req.State)
  147. {
  148. // The request finished without any problem.
  149. case HTTPRequestStates.Finished:
  150. if (HTTPManager.Logger.Level <= BestHTTP.Logger.Loglevels.All)
  151. HTTPManager.Logger.Verbose("PollingTransport", "OnRequestFinished: " + resp.DataAsText, this.Manager.Context);
  152. if (resp.IsSuccess)
  153. {
  154. // When we are sending data, the response is an 'ok' string
  155. if (req.MethodType != HTTPMethods.Post)
  156. ParseResponse(resp);
  157. }
  158. else
  159. errorString = string.Format("Polling - Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2} Uri: {3}",
  160. resp.StatusCode,
  161. resp.Message,
  162. resp.DataAsText,
  163. req.CurrentUri);
  164. break;
  165. // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
  166. case HTTPRequestStates.Error:
  167. errorString = (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception");
  168. break;
  169. // The request aborted, initiated by the user.
  170. case HTTPRequestStates.Aborted:
  171. errorString = string.Format("Polling - Request({0}) Aborted!", req.CurrentUri);
  172. break;
  173. // Connecting to the server is timed out.
  174. case HTTPRequestStates.ConnectionTimedOut:
  175. errorString = string.Format("Polling - Connection Timed Out! Uri: {0}", req.CurrentUri);
  176. break;
  177. // The request didn't finished in the given time.
  178. case HTTPRequestStates.TimedOut:
  179. errorString = string.Format("Polling - Processing the request({0}) Timed Out!", req.CurrentUri);
  180. break;
  181. }
  182. if (!string.IsNullOrEmpty(errorString))
  183. (Manager as IManager).OnTransportError(this, errorString);
  184. }
  185. #endregion
  186. #region Polling Implementation
  187. public void Poll()
  188. {
  189. if (PollRequest != null || State == TransportStates.Paused)
  190. return;
  191. PollRequest = new HTTPRequest(new Uri(string.Format("{0}?EIO={1}&transport=polling&t={2}-{3}&sid={4}{5}",
  192. Manager.Uri.ToString(),
  193. Manager.ProtocolVersion,
  194. Manager.Timestamp.ToString(),
  195. Manager.RequestCounter++.ToString(),
  196. Manager.Handshake.Sid,
  197. !Manager.Options.QueryParamsOnlyForHandshake ? Manager.Options.BuildQueryParams() : string.Empty)),
  198. HTTPMethods.Get,
  199. OnPollRequestFinished);
  200. #if !BESTHTTP_DISABLE_CACHING
  201. // Don't even try to cache it
  202. PollRequest.DisableCache = true;
  203. #endif
  204. PollRequest.MaxRetries = 0;
  205. if (this.Manager.Options.HTTPRequestCustomizationCallback != null)
  206. this.Manager.Options.HTTPRequestCustomizationCallback(this.Manager, PollRequest);
  207. PollRequest.Send();
  208. }
  209. private void OnPollRequestFinished(HTTPRequest req, HTTPResponse resp)
  210. {
  211. // Clear the PollRequest variable, so we can start a new poll.
  212. PollRequest = null;
  213. if (State == TransportStates.Closed)
  214. return;
  215. string errorString = null;
  216. switch (req.State)
  217. {
  218. // The request finished without any problem.
  219. case HTTPRequestStates.Finished:
  220. if (HTTPManager.Logger.Level <= BestHTTP.Logger.Loglevels.All)
  221. HTTPManager.Logger.Verbose("PollingTransport", "OnPollRequestFinished: " + resp.DataAsText, this.Manager.Context);
  222. if (resp.IsSuccess)
  223. ParseResponse(resp);
  224. else
  225. errorString = string.Format("Polling - Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2} Uri: {3}",
  226. resp.StatusCode,
  227. resp.Message,
  228. resp.DataAsText,
  229. req.CurrentUri);
  230. break;
  231. // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
  232. case HTTPRequestStates.Error:
  233. errorString = req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception";
  234. break;
  235. // The request aborted, initiated by the user.
  236. case HTTPRequestStates.Aborted:
  237. errorString = string.Format("Polling - Request({0}) Aborted!", req.CurrentUri);
  238. break;
  239. // Connecting to the server is timed out.
  240. case HTTPRequestStates.ConnectionTimedOut:
  241. errorString = string.Format("Polling - Connection Timed Out! Uri: {0}", req.CurrentUri);
  242. break;
  243. // The request didn't finished in the given time.
  244. case HTTPRequestStates.TimedOut:
  245. errorString = string.Format("Polling - Processing the request({0}) Timed Out!", req.CurrentUri);
  246. break;
  247. }
  248. if (!string.IsNullOrEmpty(errorString))
  249. (Manager as IManager).OnTransportError(this, errorString);
  250. }
  251. #endregion
  252. #region Packet Parsing and Handling
  253. /// <summary>
  254. /// Preprocessing and sending out packets to the manager.
  255. /// </summary>
  256. private void OnPacket(IncomingPacket packet)
  257. {
  258. switch (packet.TransportEvent)
  259. {
  260. case TransportEventTypes.Open:
  261. if (this.State != TransportStates.Opening)
  262. HTTPManager.Logger.Warning("PollingTransport", "Received 'Open' packet while state is '" + State.ToString() + "'", this.Manager.Context);
  263. else
  264. State = TransportStates.Open;
  265. goto default;
  266. case TransportEventTypes.Message:
  267. if (packet.SocketIOEvent == SocketIOEventTypes.Connect) //2:40
  268. this.State = TransportStates.Open;
  269. goto default;
  270. default:
  271. (Manager as IManager).OnPacket(packet);
  272. break;
  273. }
  274. }
  275. private void ParseResponse(HTTPResponse resp)
  276. {
  277. try
  278. {
  279. if (resp == null || resp.Data == null || resp.Data.Length < 1)
  280. return;
  281. int idx = 0;
  282. while (idx < resp.Data.Length)
  283. {
  284. int endIdx = FindNextRecordSeparator(resp.Data, idx);
  285. int length = endIdx - idx;
  286. if (length <= 0)
  287. break;
  288. IncomingPacket packet = IncomingPacket.Empty;
  289. if (resp.Data[idx] == 'b')
  290. {
  291. // First byte is the binary indicator('b'). We must skip it, so we advance our idx and also have to decrease length
  292. idx++;
  293. length--;
  294. var base64Encoded = System.Text.Encoding.UTF8.GetString(resp.Data, idx, length);
  295. var byteData = Convert.FromBase64String(base64Encoded);
  296. packet = this.Manager.Parser.Parse(this.Manager, new BufferSegment(byteData, 0, byteData.Length));
  297. }
  298. else
  299. {
  300. // It's the handshake data?
  301. if (this.State == TransportStates.Opening)
  302. {
  303. TransportEventTypes transportEvent = (TransportEventTypes)(resp.Data[idx] - '0');
  304. if (transportEvent == TransportEventTypes.Open)
  305. {
  306. var handshake = BestHTTP.JSON.LitJson.JsonMapper.ToObject<HandshakeData>(Encoding.UTF8.GetString(resp.Data, idx + 1, length - 1));
  307. packet = new IncomingPacket(TransportEventTypes.Open, SocketIOEventTypes.Unknown, "/", -1);
  308. packet.DecodedArg = handshake;
  309. }
  310. else
  311. {
  312. // TODO: error?
  313. }
  314. }
  315. else
  316. {
  317. packet = this.Manager.Parser.Parse(this.Manager, System.Text.Encoding.UTF8.GetString(resp.Data, idx, length));
  318. }
  319. }
  320. if (!packet.Equals(IncomingPacket.Empty))
  321. {
  322. try
  323. {
  324. OnPacket(packet);
  325. }
  326. catch (Exception ex)
  327. {
  328. HTTPManager.Logger.Exception("PollingTransport", "ParseResponse - OnPacket", ex, this.Manager.Context);
  329. (Manager as IManager).EmitError(ex.Message + " " + ex.StackTrace);
  330. }
  331. }
  332. idx = endIdx + 1;
  333. }
  334. }
  335. catch (Exception ex)
  336. {
  337. (Manager as IManager).EmitError(ex.Message + " " + ex.StackTrace);
  338. HTTPManager.Logger.Exception("PollingTransport", "ParseResponse", ex, this.Manager.Context);
  339. }
  340. }
  341. private int FindNextRecordSeparator(byte[] data, int startIdx)
  342. {
  343. for (int i = startIdx; i < data.Length; ++i)
  344. {
  345. if (data[i] == 0x1E)
  346. return i;
  347. }
  348. return data.Length;
  349. }
  350. #endregion
  351. }
  352. }
  353. #endif