123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649 |
- #if !BESTHTTP_DISABLE_SERVERSENT_EVENTS
- using System;
- using System.Collections.Generic;
- using BestHTTP.Extensions;
- #if UNITY_WEBGL && !UNITY_EDITOR
- using System.Runtime.InteropServices;
- #endif
- namespace BestHTTP.ServerSentEvents
- {
-
-
-
- public enum States
- {
- Initial,
- Connecting,
- Open,
- Retrying,
- Closing,
- Closed
- }
- public delegate void OnGeneralEventDelegate(EventSource eventSource);
- public delegate void OnMessageDelegate(EventSource eventSource, BestHTTP.ServerSentEvents.Message message);
- public delegate void OnErrorDelegate(EventSource eventSource, string error);
- public delegate bool OnRetryDelegate(EventSource eventSource);
- public delegate void OnEventDelegate(EventSource eventSource, BestHTTP.ServerSentEvents.Message message);
- public delegate void OnStateChangedDelegate(EventSource eventSource, States oldState, States newState);
- #if UNITY_WEBGL && !UNITY_EDITOR
- delegate void OnWebGLEventSourceOpenDelegate(uint id);
- delegate void OnWebGLEventSourceMessageDelegate(uint id, string eventStr, string data, string eventId, int retry);
- delegate void OnWebGLEventSourceErrorDelegate(uint id, string reason);
- #endif
-
-
-
- public class EventSource
- #if !UNITY_WEBGL || UNITY_EDITOR
- : IHeartbeat
- #endif
- {
- #region Public Properties
-
-
-
- public Uri Uri { get; private set; }
-
-
-
- public States State
- {
- get
- {
- return _state;
- }
- private set
- {
- States oldState = _state;
- _state = value;
- if (OnStateChanged != null)
- {
- try
- {
- OnStateChanged(this, oldState, _state);
- }
- catch(Exception ex)
- {
- HTTPManager.Logger.Exception("EventSource", "OnStateChanged", ex);
- }
- }
- }
- }
- private States _state;
-
-
-
- public TimeSpan ReconnectionTime { get; set; }
-
-
-
- public string LastEventId { get; private set; }
- #if !UNITY_WEBGL || UNITY_EDITOR
-
-
-
- public HTTPRequest InternalRequest { get; private set; }
- #else
- public bool WithCredentials { get; set; }
- #endif
- #endregion
- #region Public Events
-
-
-
- public event OnGeneralEventDelegate OnOpen;
-
-
-
- public event OnMessageDelegate OnMessage;
-
-
-
- public event OnErrorDelegate OnError;
- #if !UNITY_WEBGL || UNITY_EDITOR
-
-
-
- public event OnRetryDelegate OnRetry;
- #endif
-
-
-
- public event OnGeneralEventDelegate OnClosed;
-
-
-
- public event OnStateChangedDelegate OnStateChanged;
- #endregion
- #region Privates
-
-
-
- private Dictionary<string, OnEventDelegate> EventTable;
- #if !UNITY_WEBGL || UNITY_EDITOR
-
-
-
- private byte RetryCount;
-
-
-
- private DateTime RetryCalled;
- #else
- private static Dictionary<uint, EventSource> EventSources = new Dictionary<uint, EventSource>();
- private uint Id;
- #endif
- #endregion
- public EventSource(Uri uri)
- {
- this.Uri = uri;
- this.ReconnectionTime = TimeSpan.FromMilliseconds(2000);
- #if !UNITY_WEBGL || UNITY_EDITOR
- this.InternalRequest = new HTTPRequest(Uri, HTTPMethods.Get, true, true, OnRequestFinished);
-
- this.InternalRequest.SetHeader("Accept", "text/event-stream");
- this.InternalRequest.SetHeader("Cache-Control", "no-cache");
- this.InternalRequest.SetHeader("Accept-Encoding", "identity");
-
- this.InternalRequest.ProtocolHandler = SupportedProtocols.ServerSentEvents;
- this.InternalRequest.OnUpgraded = OnUpgraded;
-
- this.InternalRequest.DisableRetry = true;
- #else
- if (!ES_IsSupported())
- throw new NotSupportedException("This browser isn't support the EventSource protocol!");
- this.Id = ES_Create(this.Uri.ToString(), WithCredentials, OnOpenCallback, OnMessageCallback, OnErrorCallback);
- EventSources.Add(this.Id, this);
- #endif
- }
- #region Public Functions
-
-
-
- public void Open()
- {
- if (this.State != States.Initial &&
- this.State != States.Retrying &&
- this.State != States.Closed)
- return;
- this.State = States.Connecting;
- #if !UNITY_WEBGL || UNITY_EDITOR
- if (!string.IsNullOrEmpty(this.LastEventId))
- this.InternalRequest.SetHeader("Last-Event-ID", this.LastEventId);
- this.InternalRequest.Send();
- #endif
- }
-
-
-
- public void Close()
- {
- if (this.State == States.Closing ||
- this.State == States.Closed)
- return;
- this.State = States.Closing;
- #if !UNITY_WEBGL || UNITY_EDITOR
- if (this.InternalRequest != null)
- this.InternalRequest.Abort();
- else
- this.State = States.Closed;
- #else
- ES_Close(this.Id);
- SetClosed("Close");
- EventSources.Remove(this.Id);
- ES_Release(this.Id);
- #endif
- }
-
-
-
- public void On(string eventName, OnEventDelegate action)
- {
- if (EventTable == null)
- EventTable = new Dictionary<string, OnEventDelegate>();
- EventTable[eventName] = action;
- #if UNITY_WEBGL && !UNITY_EDITOR
- ES_AddEventHandler(this.Id, eventName);
- #endif
- }
-
-
-
-
- public void Off(string eventName)
- {
- if (eventName == null || EventTable == null)
- return;
- EventTable.Remove(eventName);
- }
- #endregion
- #region Private Helper Functions
- private void CallOnError(string error, string msg)
- {
- if (OnError != null)
- {
- try
- {
- OnError(this, error);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("EventSource", msg + " - OnError", ex);
- }
- }
- }
- #if !UNITY_WEBGL || UNITY_EDITOR
- private bool CallOnRetry()
- {
- if (OnRetry != null)
- {
- try
- {
- return OnRetry(this);
- }
- catch(Exception ex)
- {
- HTTPManager.Logger.Exception("EventSource", "CallOnRetry", ex);
- }
- }
- return true;
- }
- #endif
- private void SetClosed(string msg)
- {
- this.State = States.Closed;
- if (OnClosed != null)
- {
- try
- {
- OnClosed(this);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("EventSource", msg + " - OnClosed", ex);
- }
- }
- }
- #if !UNITY_WEBGL || UNITY_EDITOR
- private void Retry()
- {
- if (RetryCount > 0 ||
- !CallOnRetry())
- {
- SetClosed("Retry");
- return;
- }
- RetryCount++;
- RetryCalled = DateTime.UtcNow;
- HTTPManager.Heartbeats.Subscribe(this);
- this.State = States.Retrying;
- }
- #endif
- #endregion
- #region HTTP Request Implementation
- #if !UNITY_WEBGL || UNITY_EDITOR
-
-
-
- private void OnUpgraded(HTTPRequest originalRequest, HTTPResponse response)
- {
- EventSourceResponse esResponse = response as EventSourceResponse;
- if (esResponse == null)
- {
- CallOnError("Not an EventSourceResponse!", "OnUpgraded");
- return;
- }
- if (OnOpen != null)
- {
- try
- {
- OnOpen(this);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("EventSource", "OnOpen", ex);
- }
- }
- esResponse.OnMessage += OnMessageReceived;
- esResponse.StartReceive();
- this.RetryCount = 0;
- this.State = States.Open;
- }
- private void OnRequestFinished(HTTPRequest req, HTTPResponse resp)
- {
- if (this.State == States.Closed)
- return;
- if (this.State == States.Closing ||
- req.State == HTTPRequestStates.Aborted)
- {
- SetClosed("OnRequestFinished");
- return;
- }
- string reason = string.Empty;
-
- bool canRetry = true;
- switch (req.State)
- {
-
- case HTTPRequestStates.Processing:
- canRetry = !resp.HasHeader("content-length");
- break;
-
- case HTTPRequestStates.Finished:
-
- if (resp.StatusCode == 200 && !resp.HasHeaderWithValue("content-type", "text/event-stream"))
- {
- reason = "No Content-Type header with value 'text/event-stream' present.";
- canRetry = false;
- }
-
-
-
- if (canRetry &&
- resp.StatusCode != 500 &&
- resp.StatusCode != 502 &&
- resp.StatusCode != 503 &&
- resp.StatusCode != 504)
- {
- canRetry = false;
- reason = string.Format("Request Finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
- resp.StatusCode,
- resp.Message,
- resp.DataAsText);
- }
- break;
-
- case HTTPRequestStates.Error:
- reason = "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception");
- break;
-
- case HTTPRequestStates.Aborted:
-
- reason = "OnRequestFinished - Aborted without request. EventSource's State: " + this.State;
- break;
-
- case HTTPRequestStates.ConnectionTimedOut:
- reason = "Connection Timed Out!";
- break;
-
- case HTTPRequestStates.TimedOut:
- reason = "Processing the request Timed Out!";
- break;
- }
-
- if (this.State < States.Closing)
- {
- if (!string.IsNullOrEmpty(reason))
- CallOnError(reason, "OnRequestFinished");
- if (canRetry)
- Retry();
- else
- SetClosed("OnRequestFinished");
- }
- else
- SetClosed("OnRequestFinished");
- }
- #endif
- #endregion
- #region EventStreamResponse Event Handlers
- private void OnMessageReceived(
- #if !UNITY_WEBGL || UNITY_EDITOR
- EventSourceResponse resp,
- #endif
- BestHTTP.ServerSentEvents.Message message)
- {
- if (this.State >= States.Closing)
- return;
-
-
-
- if (message.Id != null)
- this.LastEventId = message.Id;
- if (message.Retry.TotalMilliseconds > 0)
- this.ReconnectionTime = message.Retry;
-
- if (string.IsNullOrEmpty(message.Data))
- return;
-
-
- if (OnMessage != null)
- {
- try
- {
- OnMessage(this, message);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("EventSource", "OnMessageReceived - OnMessage", ex);
- }
- }
- if (EventTable != null && !string.IsNullOrEmpty(message.Event))
- {
- OnEventDelegate action;
- if (EventTable.TryGetValue(message.Event, out action))
- {
- if (action != null)
- {
- try
- {
- action(this, message);
- }
- catch(Exception ex)
- {
- HTTPManager.Logger.Exception("EventSource", "OnMessageReceived - action", ex);
- }
- }
- }
- }
- }
- #endregion
- #region IHeartbeat Implementation
- #if !UNITY_WEBGL || UNITY_EDITOR
- void IHeartbeat.OnHeartbeatUpdate(TimeSpan dif)
- {
- if (this.State != States.Retrying)
- {
- HTTPManager.Heartbeats.Unsubscribe(this);
- return;
- }
- if (DateTime.UtcNow - RetryCalled >= ReconnectionTime)
- {
- Open();
- if (this.State != States.Connecting)
- SetClosed("OnHeartbeatUpdate");
- HTTPManager.Heartbeats.Unsubscribe(this);
- }
- }
- #endif
- #endregion
- #region WebGL Static Callbacks
- #if UNITY_WEBGL && !UNITY_EDITOR
- [AOT.MonoPInvokeCallback(typeof(OnWebGLEventSourceOpenDelegate))]
- static void OnOpenCallback(uint id)
- {
- EventSource es;
- if (EventSources.TryGetValue(id, out es))
- {
- if (es.OnOpen != null)
- {
- try
- {
- es.OnOpen(es);
- }
- catch(Exception ex)
- {
- HTTPManager.Logger.Exception("EventSource", "OnOpen", ex);
- }
- }
- es.State = States.Open;
- }
- else
- HTTPManager.Logger.Warning("EventSource", "OnOpenCallback - No EventSource found for id: " + id.ToString());
- }
- [AOT.MonoPInvokeCallback(typeof(OnWebGLEventSourceMessageDelegate))]
- static void OnMessageCallback(uint id, string eventStr, string data, string eventId, int retry)
- {
- EventSource es;
- if (EventSources.TryGetValue(id, out es))
- {
- var msg = new BestHTTP.ServerSentEvents.Message();
- msg.Id = eventId;
- msg.Data = data;
- msg.Event = eventStr;
- msg.Retry = TimeSpan.FromSeconds(retry);
- es.OnMessageReceived(msg);
- }
- }
- [AOT.MonoPInvokeCallback(typeof(OnWebGLEventSourceErrorDelegate))]
- static void OnErrorCallback(uint id, string reason)
- {
- EventSource es;
- if (EventSources.TryGetValue(id, out es))
- {
- es.CallOnError(reason, "OnErrorCallback");
- es.SetClosed("OnError");
- EventSources.Remove(id);
- }
- try
- {
- ES_Release(id);
- }
- catch (Exception ex)
- {
- HTTPManager.Logger.Exception("EventSource", "ES_Release", ex);
- }
- }
- #endif
- #endregion
- #region WebGL Interface
- #if UNITY_WEBGL && !UNITY_EDITOR
- [DllImport("__Internal")]
- static extern bool ES_IsSupported();
- [DllImport("__Internal")]
- static extern uint ES_Create(string url, bool withCred, OnWebGLEventSourceOpenDelegate onOpen, OnWebGLEventSourceMessageDelegate onMessage, OnWebGLEventSourceErrorDelegate onError);
- [DllImport("__Internal")]
- static extern void ES_AddEventHandler(uint id, string eventName);
- [DllImport("__Internal")]
- static extern void ES_Close(uint id);
- [DllImport("__Internal")]
- static extern void ES_Release(uint id);
- #endif
- #endregion
- }
- }
- #endif
|