EventSource.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906
  1. #if !BESTHTTP_DISABLE_SERVERSENT_EVENTS
  2. using System;
  3. using System.Collections.Concurrent;
  4. using System.Collections.Generic;
  5. using System.Text;
  6. using BestHTTP.Core;
  7. using BestHTTP.Extensions;
  8. using BestHTTP.Logger;
  9. using BestHTTP.PlatformSupport.Memory;
  10. #if UNITY_WEBGL && !UNITY_EDITOR
  11. using System.Runtime.InteropServices;
  12. #endif
  13. namespace BestHTTP.ServerSentEvents
  14. {
  15. /// <summary>
  16. /// Possible states of an EventSource object.
  17. /// </summary>
  18. public enum States
  19. {
  20. Initial,
  21. Connecting,
  22. Open,
  23. Retrying,
  24. Closing,
  25. Closed
  26. }
  27. public delegate void OnGeneralEventDelegate(EventSource eventSource);
  28. public delegate void OnMessageDelegate(EventSource eventSource, BestHTTP.ServerSentEvents.Message message);
  29. public delegate void OnErrorDelegate(EventSource eventSource, string error);
  30. public delegate bool OnRetryDelegate(EventSource eventSource);
  31. public delegate void OnEventDelegate(EventSource eventSource, BestHTTP.ServerSentEvents.Message message);
  32. public delegate void OnStateChangedDelegate(EventSource eventSource, States oldState, States newState);
  33. #if !UNITY_WEBGL || UNITY_EDITOR
  34. public delegate void OnCommentDelegate(EventSource eventSource, string comment);
  35. #endif
  36. #if UNITY_WEBGL && !UNITY_EDITOR
  37. delegate void OnWebGLEventSourceOpenDelegate(uint id);
  38. delegate void OnWebGLEventSourceMessageDelegate(uint id, string eventStr, string data, string eventId, int retry);
  39. delegate void OnWebGLEventSourceErrorDelegate(uint id, string reason);
  40. #endif
  41. /// <summary>
  42. /// http://www.w3.org/TR/eventsource/
  43. /// </summary>
  44. public class EventSource : IProtocol
  45. #if !UNITY_WEBGL || UNITY_EDITOR
  46. , IHeartbeat
  47. #endif
  48. {
  49. #region Public Properties
  50. /// <summary>
  51. /// Uri of the remote endpoint.
  52. /// </summary>
  53. public Uri Uri { get; private set; }
  54. /// <summary>
  55. /// Current state of the EventSource object.
  56. /// </summary>
  57. public States State
  58. {
  59. get
  60. {
  61. return _state;
  62. }
  63. private set
  64. {
  65. States oldState = _state;
  66. _state = value;
  67. if (OnStateChanged != null)
  68. {
  69. try
  70. {
  71. OnStateChanged(this, oldState, _state);
  72. }
  73. catch(Exception ex)
  74. {
  75. HTTPManager.Logger.Exception("EventSource", "OnStateChanged", ex);
  76. }
  77. }
  78. }
  79. }
  80. private States _state;
  81. /// <summary>
  82. /// Time to wait to do a reconnect attempt. Default to 2 sec. The server can overwrite this setting.
  83. /// </summary>
  84. public TimeSpan ReconnectionTime { get; set; }
  85. /// <summary>
  86. /// The last successfully received event's id.
  87. /// </summary>
  88. public string LastEventId { get; private set; }
  89. public HostConnectionKey ConnectionKey { get; private set; }
  90. public bool IsClosed { get { return this.State == States.Closed; } }
  91. public LoggingContext LoggingContext { get; private set; }
  92. #if !UNITY_WEBGL || UNITY_EDITOR
  93. /// <summary>
  94. /// The internal request object of the EventSource.
  95. /// </summary>
  96. public HTTPRequest InternalRequest { get; private set; }
  97. #else
  98. public bool WithCredentials { get; set; }
  99. #endif
  100. #endregion
  101. #region Public Events
  102. /// <summary>
  103. /// Called when successfully connected to the server.
  104. /// </summary>
  105. public event OnGeneralEventDelegate OnOpen;
  106. /// <summary>
  107. /// Called on every message received from the server.
  108. /// </summary>
  109. public event OnMessageDelegate OnMessage;
  110. /// <summary>
  111. /// Called when an error occurs.
  112. /// </summary>
  113. public event OnErrorDelegate OnError;
  114. #if !UNITY_WEBGL || UNITY_EDITOR
  115. /// <summary>
  116. /// Called when the EventSource will try to do a retry attempt. If this function returns with false, it will cancel the attempt.
  117. /// </summary>
  118. public event OnRetryDelegate OnRetry;
  119. /// <summary>
  120. /// This event is called for comments received from the server.
  121. /// </summary>
  122. public event OnCommentDelegate OnComment;
  123. #endif
  124. /// <summary>
  125. /// Called when the EventSource object closed.
  126. /// </summary>
  127. public event OnGeneralEventDelegate OnClosed;
  128. /// <summary>
  129. /// Called every time when the State property changed.
  130. /// </summary>
  131. public event OnStateChangedDelegate OnStateChanged;
  132. #endregion
  133. #region Privates
  134. /// <summary>
  135. /// A dictionary to store eventName => delegate mapping.
  136. /// </summary>
  137. private Dictionary<string, OnEventDelegate> EventTable;
  138. #if !UNITY_WEBGL || UNITY_EDITOR
  139. /// <summary>
  140. /// Number of retry attempts made.
  141. /// </summary>
  142. private byte RetryCount;
  143. /// <summary>
  144. /// When we called the Retry function. We will delay the Open call from here.
  145. /// </summary>
  146. private DateTime RetryCalled;
  147. /// <summary>
  148. /// Buffer for the read data.
  149. /// </summary>
  150. private byte[] LineBuffer;
  151. /// <summary>
  152. /// Buffer position.
  153. /// </summary>
  154. private int LineBufferPos = 0;
  155. /// <summary>
  156. /// The currently receiving and parsing message
  157. /// </summary>
  158. private BestHTTP.ServerSentEvents.Message CurrentMessage;
  159. /// <summary>
  160. /// Completed messages that waiting to be dispatched
  161. /// </summary>
  162. //private List<BestHTTP.ServerSentEvents.Message> CompletedMessages = new List<BestHTTP.ServerSentEvents.Message>();
  163. private ConcurrentQueue<BestHTTP.ServerSentEvents.Message> CompletedMessages = new ConcurrentQueue<Message>();
  164. #else
  165. private static Dictionary<uint, EventSource> EventSources = new Dictionary<uint, EventSource>();
  166. private uint Id;
  167. #endif
  168. #endregion
  169. public EventSource(Uri uri, int readBufferSizeOverride = 0)
  170. {
  171. this.Uri = uri;
  172. this.LoggingContext = new LoggingContext(this);
  173. this.ReconnectionTime = TimeSpan.FromMilliseconds(2000);
  174. this.ConnectionKey = new HostConnectionKey(this.Uri.Host, HostDefinition.GetKeyFor(this.Uri
  175. #if !BESTHTTP_DISABLE_PROXY
  176. , HTTPManager.Proxy
  177. #endif
  178. ));
  179. #if !UNITY_WEBGL || UNITY_EDITOR
  180. this.InternalRequest = new HTTPRequest(Uri, HTTPMethods.Get, true, true, OnRequestFinished);
  181. // Set headers
  182. this.InternalRequest.SetHeader("Accept", "text/event-stream");
  183. this.InternalRequest.SetHeader("Cache-Control", "no-cache");
  184. this.InternalRequest.SetHeader("Accept-Encoding", "identity");
  185. this.InternalRequest.StreamChunksImmediately = true;
  186. this.InternalRequest.ReadBufferSizeOverride = readBufferSizeOverride;
  187. this.InternalRequest.OnStreamingData = OnData;
  188. // Disable internal retry
  189. this.InternalRequest.MaxRetries = 0;
  190. this.InternalRequest.Context.Add("EventSource", this.LoggingContext);
  191. #else
  192. if (!ES_IsSupported())
  193. throw new NotSupportedException("This browser isn't support the EventSource protocol!");
  194. this.Id = ES_Create(this.Uri.ToString(), WithCredentials, OnOpenCallback, OnMessageCallback, OnErrorCallback);
  195. EventSources.Add(this.Id, this);
  196. #endif
  197. }
  198. #region Public Functions
  199. /// <summary>
  200. /// Start to connect to the remote server.
  201. /// </summary>
  202. public void Open()
  203. {
  204. if (this.State != States.Initial &&
  205. this.State != States.Retrying &&
  206. this.State != States.Closed)
  207. return;
  208. this.State = States.Connecting;
  209. #if !UNITY_WEBGL || UNITY_EDITOR
  210. if (!string.IsNullOrEmpty(this.LastEventId))
  211. this.InternalRequest.SetHeader("Last-Event-ID", this.LastEventId);
  212. this.InternalRequest.Send();
  213. #endif
  214. }
  215. /// <summary>
  216. /// Start to close the connection.
  217. /// </summary>
  218. public void Close()
  219. {
  220. if (this.State == States.Closing ||
  221. this.State == States.Closed)
  222. return;
  223. this.State = States.Closing;
  224. #if !UNITY_WEBGL || UNITY_EDITOR
  225. if (this.InternalRequest != null)
  226. this.CancellationRequested();
  227. else
  228. this.State = States.Closed;
  229. #else
  230. ES_Close(this.Id);
  231. SetClosed("Close");
  232. EventSources.Remove(this.Id);
  233. ES_Release(this.Id);
  234. #endif
  235. }
  236. /// <summary>
  237. /// With this function an event handler can be subscribed for an event name.
  238. /// </summary>
  239. public void On(string eventName, OnEventDelegate action)
  240. {
  241. if (EventTable == null)
  242. EventTable = new Dictionary<string, OnEventDelegate>();
  243. EventTable[eventName] = action;
  244. #if UNITY_WEBGL && !UNITY_EDITOR
  245. ES_AddEventHandler(this.Id, eventName);
  246. #endif
  247. }
  248. /// <summary>
  249. /// With this function the event handler can be removed for the given event name.
  250. /// </summary>
  251. /// <param name="eventName"></param>
  252. public void Off(string eventName)
  253. {
  254. if (eventName == null || EventTable == null)
  255. return;
  256. EventTable.Remove(eventName);
  257. }
  258. #endregion
  259. #region Private Helper Functions
  260. private void CallOnError(string error, string msg)
  261. {
  262. if (OnError != null)
  263. {
  264. try
  265. {
  266. OnError(this, error);
  267. }
  268. catch (Exception ex)
  269. {
  270. HTTPManager.Logger.Exception("EventSource", msg + " - OnError", ex, this.LoggingContext);
  271. }
  272. }
  273. }
  274. #if !UNITY_WEBGL || UNITY_EDITOR
  275. private bool CallOnRetry()
  276. {
  277. if (OnRetry != null)
  278. {
  279. try
  280. {
  281. return OnRetry(this);
  282. }
  283. catch(Exception ex)
  284. {
  285. HTTPManager.Logger.Exception("EventSource", "CallOnRetry", ex, this.LoggingContext);
  286. }
  287. }
  288. return true;
  289. }
  290. #endif
  291. private void SetClosed(string msg)
  292. {
  293. this.State = States.Closed;
  294. if (OnClosed != null)
  295. {
  296. try
  297. {
  298. OnClosed(this);
  299. }
  300. catch (Exception ex)
  301. {
  302. HTTPManager.Logger.Exception("EventSource", msg + " - OnClosed", ex, this.LoggingContext);
  303. }
  304. }
  305. }
  306. #if !UNITY_WEBGL || UNITY_EDITOR
  307. private void Retry()
  308. {
  309. if (RetryCount > 0 ||
  310. !CallOnRetry())
  311. {
  312. SetClosed("Retry");
  313. return;
  314. }
  315. RetryCount++;
  316. RetryCalled = DateTime.UtcNow;
  317. HTTPManager.Heartbeats.Subscribe(this);
  318. this.State = States.Retrying;
  319. }
  320. #endif
  321. #endregion
  322. #region HTTP Request Implementation
  323. #if !UNITY_WEBGL || UNITY_EDITOR
  324. private void OnRequestFinished(HTTPRequest req, HTTPResponse resp)
  325. {
  326. HTTPManager.Logger.Information("EventSource", string.Format("OnRequestFinished - State: {0}, StatusCode: {1}", this.State, resp != null ? resp.StatusCode : 0), req.Context);
  327. if (this.State == States.Closed)
  328. return;
  329. if (this.State == States.Closing || req.IsCancellationRequested)
  330. {
  331. SetClosed("OnRequestFinished");
  332. return;
  333. }
  334. string reason = string.Empty;
  335. // In some cases retry is prohibited
  336. bool canRetry = true;
  337. switch (req.State)
  338. {
  339. // The request finished without any problem.
  340. case HTTPRequestStates.Finished:
  341. // HTTP 200 OK responses that have a Content-Type specifying an unsupported type, or that have no Content-Type at all, must cause the user agent to fail the connection.
  342. if (resp.StatusCode == 200 && !resp.HasHeaderWithValue("content-type", "text/event-stream"))
  343. {
  344. reason = "No Content-Type header with value 'text/event-stream' present.";
  345. canRetry = false;
  346. }
  347. // HTTP 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, and 504 Gateway Timeout responses, and any network error that prevents the connection
  348. // from being established in the first place (e.g. DNS errors), must cause the user agent to asynchronously reestablish the connection.
  349. // Any other HTTP response code not listed here must cause the user agent to fail the connection.
  350. if (canRetry &&
  351. resp.StatusCode != 500 &&
  352. resp.StatusCode != 502 &&
  353. resp.StatusCode != 503 &&
  354. resp.StatusCode != 504)
  355. {
  356. canRetry = false;
  357. reason = string.Format("Request Finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
  358. resp.StatusCode,
  359. resp.Message,
  360. resp.DataAsText);
  361. }
  362. break;
  363. // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
  364. case HTTPRequestStates.Error:
  365. reason = "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception");
  366. break;
  367. // The request aborted, initiated by the user.
  368. case HTTPRequestStates.Aborted:
  369. // If the state is Closing, then it's a normal behaviour, and we close the EventSource
  370. reason = "OnRequestFinished - Aborted without request. EventSource's State: " + this.State;
  371. break;
  372. // Connecting to the server is timed out.
  373. case HTTPRequestStates.ConnectionTimedOut:
  374. reason = "Connection Timed Out!";
  375. break;
  376. // The request didn't finished in the given time.
  377. case HTTPRequestStates.TimedOut:
  378. reason = "Processing the request Timed Out!";
  379. break;
  380. }
  381. // If we are not closing the EventSource, then we will try to reconnect.
  382. if (this.State < States.Closing)
  383. {
  384. if (!string.IsNullOrEmpty(reason))
  385. CallOnError(reason, "OnRequestFinished");
  386. if (canRetry)
  387. Retry();
  388. else
  389. SetClosed("OnRequestFinished");
  390. }
  391. else
  392. SetClosed("OnRequestFinished");
  393. }
  394. private bool OnData(HTTPRequest request, HTTPResponse response, byte[] dataFragment, int dataFragmentLength)
  395. {
  396. if (this.State == States.Connecting)
  397. {
  398. string contentType = response.GetFirstHeaderValue("content-type");
  399. bool IsUpgraded = response.StatusCode == 200 &&
  400. !string.IsNullOrEmpty(contentType) &&
  401. contentType.ToLower().StartsWith("text/event-stream");
  402. if (IsUpgraded)
  403. {
  404. ProtocolEventHelper.AddProtocol(this);
  405. if (this.OnOpen != null)
  406. {
  407. try
  408. {
  409. this.OnOpen(this);
  410. }
  411. catch (Exception ex)
  412. {
  413. HTTPManager.Logger.Exception("EventSource", "OnOpen", ex, request.Context);
  414. }
  415. }
  416. this.RetryCount = 0;
  417. this.State = States.Open;
  418. }
  419. else
  420. {
  421. this.State = States.Closing;
  422. request.Abort();
  423. }
  424. }
  425. if (this.State == States.Closing)
  426. return true;
  427. if (FeedData(dataFragment, dataFragmentLength))
  428. ProtocolEventHelper.EnqueueProtocolEvent(new ProtocolEventInfo(this));
  429. return true;
  430. }
  431. #region Data Parsing
  432. public bool FeedData(byte[] buffer, int count)
  433. {
  434. if (count == -1)
  435. count = buffer.Length;
  436. if (count == 0)
  437. return false;
  438. if (LineBuffer == null)
  439. LineBuffer = BufferPool.Get(1024, true);
  440. int newlineIdx;
  441. int pos = 0;
  442. bool hasMessageToSend = false;
  443. do
  444. {
  445. newlineIdx = -1;
  446. int skipCount = 1; // to skip CR and/or LF
  447. for (int i = pos; i < count && newlineIdx == -1; ++i)
  448. {
  449. // Lines must be separated by either a U+000D CARRIAGE RETURN U+000A LINE FEED (CRLF) character pair, a single U+000A LINE FEED (LF) character, or a single U+000D CARRIAGE RETURN (CR) character.
  450. if (buffer[i] == HTTPResponse.CR)
  451. {
  452. if (i + 1 < count && buffer[i + 1] == HTTPResponse.LF)
  453. skipCount = 2;
  454. newlineIdx = i;
  455. }
  456. else if (buffer[i] == HTTPResponse.LF)
  457. newlineIdx = i;
  458. }
  459. int copyIndex = newlineIdx == -1 ? count : newlineIdx;
  460. if (LineBuffer.Length < LineBufferPos + (copyIndex - pos))
  461. {
  462. int newSize = LineBufferPos + (copyIndex - pos);
  463. BufferPool.Resize(ref LineBuffer, newSize, true, false);
  464. }
  465. Array.Copy(buffer, pos, LineBuffer, LineBufferPos, copyIndex - pos);
  466. LineBufferPos += copyIndex - pos;
  467. if (newlineIdx == -1)
  468. return hasMessageToSend;
  469. hasMessageToSend |= ParseLine(LineBuffer, LineBufferPos);
  470. LineBufferPos = 0;
  471. //pos += newlineIdx + skipCount;
  472. pos = newlineIdx + skipCount;
  473. } while (newlineIdx != -1 && pos < count);
  474. return hasMessageToSend;
  475. }
  476. bool ParseLine(byte[] buffer, int count)
  477. {
  478. // If the line is empty (a blank line) => Dispatch the event
  479. if (count == 0)
  480. {
  481. if (CurrentMessage != null)
  482. {
  483. CompletedMessages.Enqueue(CurrentMessage);
  484. CurrentMessage = null;
  485. return true;
  486. }
  487. return false;
  488. }
  489. // If the line starts with a U+003A COLON character (:) => Ignore the line.
  490. if (buffer[0] == 0x3A)
  491. {
  492. this.CompletedMessages.Enqueue(new Message() { IsComment = true, Data = Encoding.UTF8.GetString(buffer, 1, count - 1) });
  493. return true;
  494. }
  495. //If the line contains a U+003A COLON character (:)
  496. int colonIdx = -1;
  497. for (int i = 0; i < count && colonIdx == -1; ++i)
  498. if (buffer[i] == 0x3A)
  499. colonIdx = i;
  500. string field;
  501. string value;
  502. if (colonIdx != -1)
  503. {
  504. // Collect the characters on the line before the first U+003A COLON character (:), and let field be that string.
  505. field = Encoding.UTF8.GetString(buffer, 0, colonIdx);
  506. //Collect the characters on the line after the first U+003A COLON character (:), and let value be that string. If value starts with a U+0020 SPACE character, remove it from value.
  507. if (colonIdx + 1 < count && buffer[colonIdx + 1] == 0x20)
  508. colonIdx++;
  509. colonIdx++;
  510. // discarded because it is not followed by a blank line
  511. if (colonIdx >= count)
  512. return false;
  513. value = Encoding.UTF8.GetString(buffer, colonIdx, count - colonIdx);
  514. }
  515. else
  516. {
  517. // Otherwise, the string is not empty but does not contain a U+003A COLON character (:) =>
  518. // Process the field using the whole line as the field name, and the empty string as the field value.
  519. field = Encoding.UTF8.GetString(buffer, 0, count);
  520. value = string.Empty;
  521. }
  522. if (CurrentMessage == null)
  523. CurrentMessage = new BestHTTP.ServerSentEvents.Message();
  524. switch (field)
  525. {
  526. // If the field name is "id" => Set the last event ID buffer to the field value.
  527. case "id":
  528. CurrentMessage.Id = value;
  529. break;
  530. // If the field name is "event" => Set the event type buffer to field value.
  531. case "event":
  532. CurrentMessage.Event = value;
  533. break;
  534. // If the field name is "data" => Append the field value to the data buffer, then append a single U+000A LINE FEED (LF) character to the data buffer.
  535. case "data":
  536. // Append a new line if we already have some data. This way we can skip step 3.) in the EventSource's OnMessageReceived.
  537. // We do only null check, because empty string can be valid payload
  538. if (CurrentMessage.Data != null)
  539. CurrentMessage.Data += Environment.NewLine;
  540. CurrentMessage.Data += value;
  541. break;
  542. // If the field name is "retry" => If the field value consists of only ASCII digits, then interpret the field value as an integer in base ten,
  543. // and set the event stream's reconnection time to that integer. Otherwise, ignore the field.
  544. case "retry":
  545. int result;
  546. if (int.TryParse(value, out result))
  547. CurrentMessage.Retry = TimeSpan.FromMilliseconds(result);
  548. break;
  549. // Otherwise: The field is ignored.
  550. default:
  551. break;
  552. }
  553. return false;
  554. }
  555. #endregion
  556. #endif
  557. #endregion
  558. #region EventStreamResponse Event Handlers
  559. private void OnMessageReceived(BestHTTP.ServerSentEvents.Message message)
  560. {
  561. if (this.State >= States.Closing)
  562. return;
  563. // 1.) Set the last event ID string of the event source to value of the last event ID buffer.
  564. // The buffer does not get reset, so the last event ID string of the event source remains set to this value until the next time it is set by the server.
  565. // We check here only for null, because it can be a non-null but empty string.
  566. if (message.Id != null)
  567. this.LastEventId = message.Id;
  568. if (message.Retry.TotalMilliseconds > 0)
  569. this.ReconnectionTime = message.Retry;
  570. // 2.) If the data buffer is an empty string, set the data buffer and the event type buffer to the empty string and abort these steps.
  571. if (string.IsNullOrEmpty(message.Data))
  572. return;
  573. // 3.) If the data buffer's last character is a U+000A LINE FEED (LF) character, then remove the last character from the data buffer.
  574. // This step can be ignored. We constructed the string to be able to skip this step.
  575. if (OnMessage != null && !message.IsComment)
  576. {
  577. try
  578. {
  579. OnMessage(this, message);
  580. }
  581. catch (Exception ex)
  582. {
  583. HTTPManager.Logger.Exception("EventSource", "OnMessageReceived - OnMessage", ex, this.LoggingContext);
  584. }
  585. }
  586. #if !UNITY_WEBGL || UNITY_EDITOR
  587. else if (message.IsComment && this.OnComment != null)
  588. {
  589. try
  590. {
  591. this.OnComment(this, message.Data);
  592. }
  593. catch (Exception ex)
  594. {
  595. HTTPManager.Logger.Exception("EventSource", "OnMessageReceived - OnComment", ex, this.LoggingContext);
  596. }
  597. }
  598. #endif
  599. if (EventTable != null && !string.IsNullOrEmpty(message.Event))
  600. {
  601. OnEventDelegate action;
  602. if (EventTable.TryGetValue(message.Event, out action))
  603. {
  604. if (action != null)
  605. {
  606. try
  607. {
  608. action(this, message);
  609. }
  610. catch(Exception ex)
  611. {
  612. HTTPManager.Logger.Exception("EventSource", "OnMessageReceived - action", ex, this.LoggingContext);
  613. }
  614. }
  615. }
  616. }
  617. }
  618. public void HandleEvents()
  619. {
  620. #if !UNITY_WEBGL || UNITY_EDITOR
  621. if (this.State == States.Open)
  622. {
  623. BestHTTP.ServerSentEvents.Message message;
  624. while (this.CompletedMessages.TryDequeue(out message))
  625. OnMessageReceived(message);
  626. }
  627. #endif
  628. }
  629. public void CancellationRequested()
  630. {
  631. #if !UNITY_WEBGL || UNITY_EDITOR
  632. if (this.InternalRequest != null)
  633. this.InternalRequest.Abort();
  634. #else
  635. Close();
  636. #endif
  637. }
  638. public void Dispose()
  639. {
  640. }
  641. #endregion
  642. #region IHeartbeat Implementation
  643. #if !UNITY_WEBGL || UNITY_EDITOR
  644. void IHeartbeat.OnHeartbeatUpdate(TimeSpan dif)
  645. {
  646. if (this.State != States.Retrying)
  647. {
  648. HTTPManager.Heartbeats.Unsubscribe(this);
  649. return;
  650. }
  651. if (DateTime.UtcNow - RetryCalled >= ReconnectionTime)
  652. {
  653. Open();
  654. if (this.State != States.Connecting)
  655. SetClosed("OnHeartbeatUpdate");
  656. HTTPManager.Heartbeats.Unsubscribe(this);
  657. }
  658. }
  659. #endif
  660. #endregion
  661. #region WebGL Static Callbacks
  662. #if UNITY_WEBGL && !UNITY_EDITOR
  663. [AOT.MonoPInvokeCallback(typeof(OnWebGLEventSourceOpenDelegate))]
  664. static void OnOpenCallback(uint id)
  665. {
  666. EventSource es;
  667. if (EventSources.TryGetValue(id, out es))
  668. {
  669. if (es.OnOpen != null)
  670. {
  671. try
  672. {
  673. es.OnOpen(es);
  674. }
  675. catch(Exception ex)
  676. {
  677. HTTPManager.Logger.Exception("EventSource", "OnOpen", ex, es.LoggingContext);
  678. }
  679. }
  680. es.State = States.Open;
  681. }
  682. else
  683. HTTPManager.Logger.Warning("EventSource", "OnOpenCallback - No EventSource found for id: " + id.ToString());
  684. }
  685. [AOT.MonoPInvokeCallback(typeof(OnWebGLEventSourceMessageDelegate))]
  686. static void OnMessageCallback(uint id, string eventStr, string data, string eventId, int retry)
  687. {
  688. EventSource es;
  689. if (EventSources.TryGetValue(id, out es))
  690. {
  691. var msg = new BestHTTP.ServerSentEvents.Message();
  692. msg.Id = eventId;
  693. msg.Data = data;
  694. msg.Event = eventStr;
  695. msg.Retry = TimeSpan.FromSeconds(retry);
  696. es.OnMessageReceived(msg);
  697. }
  698. }
  699. [AOT.MonoPInvokeCallback(typeof(OnWebGLEventSourceErrorDelegate))]
  700. static void OnErrorCallback(uint id, string reason)
  701. {
  702. EventSource es;
  703. if (EventSources.TryGetValue(id, out es))
  704. {
  705. es.CallOnError(reason, "OnErrorCallback");
  706. es.SetClosed("OnError");
  707. EventSources.Remove(id);
  708. }
  709. try
  710. {
  711. ES_Release(id);
  712. }
  713. catch (Exception ex)
  714. {
  715. HTTPManager.Logger.Exception("EventSource", "ES_Release", ex);
  716. }
  717. }
  718. #endif
  719. #endregion
  720. #region WebGL Interface
  721. #if UNITY_WEBGL && !UNITY_EDITOR
  722. [DllImport("__Internal")]
  723. static extern bool ES_IsSupported();
  724. [DllImport("__Internal")]
  725. static extern uint ES_Create(string url, bool withCred, OnWebGLEventSourceOpenDelegate onOpen, OnWebGLEventSourceMessageDelegate onMessage, OnWebGLEventSourceErrorDelegate onError);
  726. [DllImport("__Internal")]
  727. static extern void ES_AddEventHandler(uint id, string eventName);
  728. [DllImport("__Internal")]
  729. static extern void ES_Close(uint id);
  730. [DllImport("__Internal")]
  731. static extern void ES_Release(uint id);
  732. #endif
  733. #endregion
  734. }
  735. }
  736. #endif