using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using Webserver.UrlHandlers; using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse; namespace Webserver.SSE { public enum ESseClientWriteResult { Ok, Closed, Error, } public class SseClient { private const int keepAliveIntervalSeconds = 10; private static readonly byte[] keepAliveData = Encoding.UTF8.GetBytes (": KeepAlive\n\n"); public readonly IPEndPoint RemoteEndpoint; private readonly SseHandler parent; private readonly HttpListenerResponse response; private DateTime lastMessageSent = DateTime.Now; public SseClient (SseHandler _parent, RequestContext _context) { parent = _parent; response = _context.Response; RemoteEndpoint = _context.Request.RemoteEndPoint; // Keep the request open response.SendChunked = true; response.AddHeader ("Content-Type", "text/event-stream"); response.OutputStream.Flush (); } public ESseClientWriteResult Write (byte[] _bytes, int _bytesToSend) { HttpListenerResponse resp = response; try { if (!resp.OutputStream.CanWrite) { parent.ClientClosed (this); resp.Close (); return ESseClientWriteResult.Closed; } resp.OutputStream.Write (_bytes, 0, _bytesToSend); resp.OutputStream.Flush (); lastMessageSent = DateTime.Now; return ESseClientWriteResult.Ok; } catch (IOException e) { parent.ClientClosed (this); if (e.InnerException is SocketException se) { if (se.SocketErrorCode == SocketError.ConnectionAborted || se.SocketErrorCode == SocketError.Shutdown) { return ESseClientWriteResult.Closed; } Log.Error ($"[Web] [SSE] SocketError ({se.SocketErrorCode.ToStringCached ()}) while trying to write", true); return ESseClientWriteResult.Error; } Log.Error ("[Web] [SSE] IOException while trying to write:", true); Log.Exception (e); return ESseClientWriteResult.Error; } catch (Exception e) { parent.ClientClosed (this); resp.Close (); Log.Error ("[Web] [SSE] Exception while trying to write:", true); Log.Exception (e); return ESseClientWriteResult.Error; } } public void HandleKeepAlive () { DateTime now = DateTime.Now; if (!((now - lastMessageSent).TotalSeconds >= keepAliveIntervalSeconds)) { return; } Write (keepAliveData, keepAliveData.Length); lastMessageSent = now; } } }