This commit is contained in:
2020-07-09 08:50:24 +08:00
parent 13d25f4707
commit c523462b82
1818 changed files with 174940 additions and 582 deletions

View File

@@ -0,0 +1,635 @@
#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
{
/// <summary>
/// Possible states of an EventSource object.
/// </summary>
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
/// <summary>
/// http://www.w3.org/TR/eventsource/
/// </summary>
public class EventSource
#if !UNITY_WEBGL || UNITY_EDITOR
: IHeartbeat
#endif
{
#region Public Properties
/// <summary>
/// Uri of the remote endpoint.
/// </summary>
public Uri Uri { get; private set; }
/// <summary>
/// Current state of the EventSource object.
/// </summary>
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;
/// <summary>
/// Time to wait to do a reconnect attempt. Default to 2 sec. The server can overwrite this setting.
/// </summary>
public TimeSpan ReconnectionTime { get; set; }
/// <summary>
/// The last successfully received event's id.
/// </summary>
public string LastEventId { get; private set; }
#if !UNITY_WEBGL || UNITY_EDITOR
/// <summary>
/// The internal request object of the EventSource.
/// </summary>
public HTTPRequest InternalRequest { get; private set; }
#endif
#endregion
#region Public Events
/// <summary>
/// Called when successfully connected to the server.
/// </summary>
public event OnGeneralEventDelegate OnOpen;
/// <summary>
/// Called on every message received from the server.
/// </summary>
public event OnMessageDelegate OnMessage;
/// <summary>
/// Called when an error occures.
/// </summary>
public event OnErrorDelegate OnError;
#if !UNITY_WEBGL || UNITY_EDITOR
/// <summary>
/// Called when the EventSource will try to do a retry attempt. If this function returns with false, it will cancel the attempt.
/// </summary>
public event OnRetryDelegate OnRetry;
#endif
/// <summary>
/// Called when the EventSource object closed.
/// </summary>
public event OnGeneralEventDelegate OnClosed;
/// <summary>
/// Called every time when the State property changed.
/// </summary>
public event OnStateChangedDelegate OnStateChanged;
#endregion
#region Privates
/// <summary>
/// A dictionary to store eventName => delegate mapping.
/// </summary>
private Dictionary<string, OnEventDelegate> EventTable;
#if !UNITY_WEBGL || UNITY_EDITOR
/// <summary>
/// Number of retry attempts made.
/// </summary>
private byte RetryCount;
/// <summary>
/// When we called the Retry function. We will delay the Open call from here.
/// </summary>
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);
// Set headers
this.InternalRequest.SetHeader("Accept", "text/event-stream");
this.InternalRequest.SetHeader("Cache-Control", "no-cache");
this.InternalRequest.SetHeader("Accept-Encoding", "identity");
// Set protocol stuff
this.InternalRequest.ProtocolHandler = SupportedProtocols.ServerSentEvents;
this.InternalRequest.OnUpgraded = OnUpgraded;
// Disable internal retry
this.InternalRequest.DisableRetry = true;
#endif
}
#region Public Functions
/// <summary>
/// Start to connect to the remote servr.
/// </summary>
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();
#else
this.Id = ES_Create(this.Uri.ToString(), true, OnOpenCallback, OnMessageCallback, OnErrorCallback);
EventSources.Add(this.Id, this);
#endif
}
/// <summary>
/// Start to close the connection.
/// </summary>
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
}
/// <summary>
/// With this function an event handler can be subscribed for an event name.
/// </summary>
public void On(string eventName, OnEventDelegate action)
{
if (EventTable == null)
EventTable = new Dictionary<string, OnEventDelegate>();
EventTable[eventName] = action;
}
/// <summary>
/// With this function the event handler can be removed for the given event name.
/// </summary>
/// <param name="eventName"></param>
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
/// <summary>
/// We are successfully upgraded to the EventSource protocol, we can start to receive and parse the incoming data.
/// </summary>
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;
// In some cases retry is prohibited
bool canRetry = true;
switch (req.State)
{
// The server sent all the data it's wanted.
case HTTPRequestStates.Processing:
canRetry = !resp.HasHeader("content-length");
break;
// The request finished without any problem.
case HTTPRequestStates.Finished:
// 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.
if (resp.StatusCode == 200 && !resp.HasHeaderWithValue("content-type", "text/event-stream"))
{
reason = "No Content-Type header with value 'text/event-stream' present.";
canRetry = false;
}
// HTTP 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, and 504 Gateway Timeout responses, and any network error that prevents the connection
// from being established in the first place (e.g. DNS errors), must cause the user agent to asynchronously reestablish the connection.
// Any other HTTP response code not listed here must cause the user agent to fail the connection.
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;
// The request finished with an unexpected error. The request's Exception property may contain more info about the error.
case HTTPRequestStates.Error:
reason = "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception");
break;
// The request aborted, initiated by the user.
case HTTPRequestStates.Aborted:
// If the state is Closing, then it's a normal behaviour, and we close the EventSource
reason = "OnRequestFinished - Aborted without request. EventSource's State: " + this.State;
break;
// Connecting to the server is timed out.
case HTTPRequestStates.ConnectionTimedOut:
reason = "Connection Timed Out!";
break;
// The request didn't finished in the given time.
case HTTPRequestStates.TimedOut:
reason = "Processing the request Timed Out!";
break;
}
// If we are not closing the EventSource, then we will try to reconnect.
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;
// 1.) Set the last event ID string of the event source to value of the last event ID buffer.
// 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.
// We check here only for null, because it can be a non-null but empty string.
if (message.Id != null)
this.LastEventId = message.Id;
if (message.Retry.TotalMilliseconds > 0)
this.ReconnectionTime = message.Retry;
// 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.
if (string.IsNullOrEmpty(message.Data))
return;
// 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.
// This step can be ignored. We constructed the string to be able to skip this step.
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 uint ES_Create(string url, bool withCred, OnWebGLEventSourceOpenDelegate onOpen, OnWebGLEventSourceMessageDelegate onMessage, OnWebGLEventSourceErrorDelegate onError);
[DllImport("__Internal")]
static extern void ES_Close(uint id);
[DllImport("__Internal")]
static extern void ES_Release(uint id);
#endif
#endregion
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3082cccc0555e44fd8b5604394dfe0e2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,380 @@
#if !BESTHTTP_DISABLE_SERVERSENT_EVENTS && (!UNITY_WEBGL || UNITY_EDITOR)
using System;
using System.IO;
using System.Threading;
using System.Text;
using System.Collections.Generic;
namespace BestHTTP.ServerSentEvents
{
/// <summary>
/// A low-level class to receive and parse an EventSource(http://www.w3.org/TR/eventsource/) stream.
/// Higher level protocol representation is implemented in the EventSource class.
/// </summary>
public sealed class EventSourceResponse : HTTPResponse, IProtocol
{
public bool IsClosed { get; private set; }
#region Public Events
public Action<EventSourceResponse, BestHTTP.ServerSentEvents.Message> OnMessage;
public Action<EventSourceResponse> OnClosed;
#endregion
#region Privates
/// <summary>
/// Thread sync object
/// </summary>
private object FrameLock = new object();
/// <summary>
/// Buffer for the read data.
/// </summary>
private byte[] LineBuffer = new byte[1024];
/// <summary>
/// Buffer position.
/// </summary>
private int LineBufferPos = 0;
/// <summary>
/// The currently receiving and parsing message
/// </summary>
private BestHTTP.ServerSentEvents.Message CurrentMessage;
/// <summary>
/// Completed messages that waiting to be dispatched
/// </summary>
private List<BestHTTP.ServerSentEvents.Message> CompletedMessages = new List<BestHTTP.ServerSentEvents.Message>();
#endregion
public EventSourceResponse(HTTPRequest request, Stream stream, bool isStreamed, bool isFromCache)
:base(request, stream, isStreamed, isFromCache)
{
base.IsClosedManually = true;
}
internal override bool Receive(int forceReadRawContentLength = -1, bool readPayloadData = true)
{
bool received = base.Receive(forceReadRawContentLength, false);
string contentType = this.GetFirstHeaderValue("content-type");
base.IsUpgraded = received &&
this.StatusCode == 200 &&
!string.IsNullOrEmpty(contentType) &&
contentType.ToLower().StartsWith("text/event-stream");
// If we didn't upgraded to the protocol we have to read all the sent payload because
// next requests may read these datas as HTTP headers and will fail
if (!IsUpgraded)
ReadPayload(forceReadRawContentLength);
return received;
}
internal void StartReceive()
{
if (IsUpgraded)
{
#if NETFX_CORE
#pragma warning disable 4014
Windows.System.Threading.ThreadPool.RunAsync(ReceiveThreadFunc);
#pragma warning restore 4014
#else
ThreadPool.QueueUserWorkItem(ReceiveThreadFunc);
//new Thread(ReceiveThreadFunc)
// .Start();
#endif
}
}
#region Private Threading Functions
private void ReceiveThreadFunc(object param)
{
try
{
if (HasHeaderWithValue("transfer-encoding", "chunked"))
ReadChunked(Stream);
else
ReadRaw(Stream, -1);
}
#if !NETFX_CORE
catch (ThreadAbortException)
{
this.baseRequest.State = HTTPRequestStates.Aborted;
}
#endif
catch (Exception e)
{
if (HTTPUpdateDelegator.IsCreated)
{
this.baseRequest.Exception = e;
this.baseRequest.State = HTTPRequestStates.Error;
}
else
this.baseRequest.State = HTTPRequestStates.Aborted;
}
finally
{
IsClosed = true;
}
}
#endregion
#region Read Implementations
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
private new void ReadChunked(Stream stream)
{
int chunkLength = ReadChunkLength(stream);
byte[] buffer = new byte[chunkLength];
while (chunkLength != 0)
{
// To avoid more GC garbage we use only one buffer, and resize only if the next chunk doesn't fit.
if (buffer.Length < chunkLength)
Array.Resize<byte>(ref buffer, chunkLength);
int readBytes = 0;
// Fill up the buffer
do
{
int bytes = stream.Read(buffer, readBytes, chunkLength - readBytes);
if (bytes == 0)
throw new Exception("The remote server closed the connection unexpectedly!");
readBytes += bytes;
} while (readBytes < chunkLength);
FeedData(buffer, readBytes);
// Every chunk data has a trailing CRLF
ReadTo(stream, LF);
// read the next chunk's length
chunkLength = ReadChunkLength(stream);
}
// Read the trailing headers or the CRLF
ReadHeaders(stream);
}
private new void ReadRaw(Stream stream, int contentLength)
{
byte[] buffer = new byte[1024];
int bytes;
do
{
bytes = stream.Read(buffer, 0, buffer.Length);
FeedData(buffer, bytes);
} while(bytes > 0);
}
#endregion
#region Data Parsing
public void FeedData(byte[] buffer, int count)
{
if (count == -1)
count = buffer.Length;
if (count == 0)
return;
int newlineIdx;
int pos = 0;
do {
newlineIdx = -1;
int skipCount = 1; // to skip CR and/or LF
for (int i = pos; i < count && newlineIdx == -1; ++i)
{
// 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.
if (buffer[i] == HTTPResponse.CR)
{
if (i + 1 < count && buffer[i + 1] == HTTPResponse.LF)
skipCount = 2;
newlineIdx = i;
}
else if (buffer[i] == HTTPResponse.LF)
newlineIdx = i;
}
int copyIndex = newlineIdx == -1 ? count : newlineIdx;
if (LineBuffer.Length < LineBufferPos + (copyIndex - pos))
Array.Resize<byte>(ref LineBuffer, LineBufferPos + (copyIndex - pos));
Array.Copy(buffer, pos, LineBuffer, LineBufferPos, copyIndex - pos);
LineBufferPos += copyIndex - pos;
if (newlineIdx == -1)
return;
ParseLine(LineBuffer, LineBufferPos);
LineBufferPos = 0;
//pos += newlineIdx + skipCount;
pos = newlineIdx + skipCount;
}while(newlineIdx != -1 && pos < count);
}
void ParseLine(byte[] buffer, int count)
{
// If the line is empty (a blank line) => Dispatch the event
if (count == 0)
{
if (CurrentMessage != null)
{
lock (FrameLock)
CompletedMessages.Add(CurrentMessage);
CurrentMessage = null;
}
return;
}
// If the line starts with a U+003A COLON character (:) => Ignore the line.
if (buffer[0] == 0x3A)
return;
//If the line contains a U+003A COLON character (:)
int colonIdx = -1;
for (int i = 0; i < count && colonIdx == -1; ++i)
if (buffer[i] == 0x3A)
colonIdx = i;
string field;
string value;
if (colonIdx != -1)
{
// Collect the characters on the line before the first U+003A COLON character (:), and let field be that string.
field = Encoding.UTF8.GetString(buffer, 0, colonIdx);
//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.
if (colonIdx + 1 < count && buffer[colonIdx + 1] == 0x20)
colonIdx++;
colonIdx++;
// discarded because it is not followed by a blank line
if (colonIdx >= count)
return;
value = Encoding.UTF8.GetString(buffer, colonIdx, count - colonIdx);
}
else
{
// Otherwise, the string is not empty but does not contain a U+003A COLON character (:) =>
// Process the field using the whole line as the field name, and the empty string as the field value.
field = Encoding.UTF8.GetString(buffer, 0, count);
value = string.Empty;
}
if (CurrentMessage == null)
CurrentMessage = new BestHTTP.ServerSentEvents.Message();
switch(field)
{
// If the field name is "id" => Set the last event ID buffer to the field value.
case "id":
CurrentMessage.Id = value;
break;
// If the field name is "event" => Set the event type buffer to field value.
case "event":
CurrentMessage.Event = value;
break;
// 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.
case "data":
// Append a new line if we already have some data. This way we can skip step 3.) in the EventSource's OnMessageReceived.
// We do only null check, because empty string can be valid payload
if (CurrentMessage.Data != null)
CurrentMessage.Data += Environment.NewLine;
CurrentMessage.Data += value;
break;
// 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,
// and set the event stream's reconnection time to that integer. Otherwise, ignore the field.
case "retry":
int result;
if (int.TryParse(value, out result))
CurrentMessage.Retry = TimeSpan.FromMilliseconds(result);
break;
// Otherwise: The field is ignored.
default:
break;
}
}
#endregion
void IProtocol.HandleEvents()
{
lock(FrameLock)
{
// Send out messages.
if (CompletedMessages.Count > 0)
{
if (OnMessage != null)
for (int i = 0; i < CompletedMessages.Count; ++i)
{
try
{
OnMessage(this, CompletedMessages[i]);
}
catch(Exception ex)
{
HTTPManager.Logger.Exception("EventSourceMessage", "HandleEvents - OnMessage", ex);
}
}
CompletedMessages.Clear();
}
}
// We are closed
if (IsClosed)
{
CompletedMessages.Clear();
if (OnClosed != null)
{
try
{
OnClosed(this);
}
catch (Exception ex)
{
HTTPManager.Logger.Exception("EventSourceMessage", "HandleEvents - OnClosed", ex);
}
finally
{
OnClosed = null;
}
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a598cad2dba4e4979b2289f7ef953421
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
#if !BESTHTTP_DISABLE_SERVERSENT_EVENTS
using System;
namespace BestHTTP.ServerSentEvents
{
public sealed class Message
{
/// <summary>
/// Event Id of the message. If it's null, then it's not present.
/// </summary>
public string Id { get; internal set; }
/// <summary>
/// Name of the event, or an empty string.
/// </summary>
public string Event { get; internal set; }
/// <summary>
/// The actual payload of the message.
/// </summary>
public string Data { get; internal set; }
/// <summary>
/// A reconnection time, in milliseconds. This must initially be a user-agent-defined value, probably in the region of a few seconds.
/// </summary>
public TimeSpan Retry { get; internal set; }
public override string ToString()
{
return string.Format("\"{0}\": \"{1}\"", Event, Data);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e741973262f5e4edf9880e1df1d0f530
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: