using System; using System.Collections.Generic; using System.Text; using Webserver.UrlHandlers; namespace Webserver.SSE { public abstract class AbsEvent { private const int encodingBufferSize = 1024 * 1024; private readonly SseHandler parent; public readonly string Name; private readonly byte[] encodingBuffer; private readonly StringBuilder stringBuilder = new StringBuilder (); private readonly List openClients = new List (); private readonly BlockingQueue<(string _eventName, string _data)> sendQueue = new BlockingQueue<(string _eventName, string _data)> (); private int currentlyOpen; private int totalOpened; private int totalClosed; protected AbsEvent (SseHandler _parent, bool _reuseEncodingBuffer = true, string _name = null) { Name = _name ?? GetType ().Name; parent = _parent; if (_reuseEncodingBuffer) { encodingBuffer = new byte[encodingBufferSize]; } } public void AddListener (SseClient _client) { totalOpened++; currentlyOpen++; openClients.Add (_client); logConnectionState ("Connection opened", _client); } protected void SendData (string _eventName, string _data) { sendQueue.Enqueue ((_eventName, _data)); parent.SignalSendQueue (); } public void ProcessSendQueue () { while (sendQueue.HasData ()) { (string eventName, string data) = sendQueue.Dequeue (); stringBuilder.Append ("event: "); stringBuilder.AppendLine (eventName); stringBuilder.Append ("data: "); stringBuilder.AppendLine (data); stringBuilder.AppendLine (""); string output = stringBuilder.ToString (); stringBuilder.Clear (); byte[] buf; int bytesToSend; if (encodingBuffer != null) { buf = encodingBuffer; try { bytesToSend = Encoding.UTF8.GetBytes (output, 0, output.Length, buf, 0); } catch (ArgumentException e) { Log.Error ($"[Web] [SSE] '{Name}': Exception while encoding data for output, most likely exceeding buffer size:"); Log.Exception (e); return; } } else { buf = Encoding.UTF8.GetBytes (output); bytesToSend = buf.Length; } sendBufToListeners (buf, bytesToSend); } } private void sendBufToListeners (byte[] _bytes, int _bytesToSend) { for (int i = openClients.Count - 1; i >= 0; i--) { openClients [i].Write (_bytes, _bytesToSend); } } public virtual int DefaultPermissionLevel () => 0; private void logConnectionState (string _message, SseClient _client) { Log.Out ($"[Web] [SSE] '{Name}': {_message} from {_client.RemoteEndpoint} (Left open: {currentlyOpen}, total opened: {totalOpened}, closed: {totalClosed})"); } public void ClientClosed (SseClient _client) { if (openClients.Remove (_client)) { currentlyOpen--; totalClosed++; logConnectionState ("Closed connection", _client); } } } }