source: binary-improvements/MapRendering/Web/SSE/SseHandler.cs@ 383

Last change on this file since 383 was 367, checked in by alloc, 3 years ago

Web:

  • Added SSE (ServerSentEvents) subsystem
  • Added log endpoint to SSE. Less heavy weight and more responsive way of watching the server log
  • Bunch of refactoring
File size: 3.6 KB
Line 
1using System;
2using System.Net;
3using System.Collections.Generic;
4using System.Reflection;
5using System.Threading;
6using AllocsFixes.NetConnections.Servers.Web.Handlers;
7
8// Implemented following HTML spec
9// https://html.spec.whatwg.org/multipage/server-sent-events.html
10
11namespace AllocsFixes.NetConnections.Servers.Web.SSE {
12 public class SseHandler : PathHandler {
13 private readonly Dictionary<string, EventBase> events = new CaseInsensitiveStringDictionary<EventBase> ();
14
15 private ThreadManager.ThreadInfo queueThead;
16 private readonly AutoResetEvent evSendRequest = new AutoResetEvent (false);
17 private bool shutdown;
18
19 public SseHandler (string _moduleName = null) : base (_moduleName) {
20 Type[] ctorTypes = {typeof (SseHandler)};
21 object[] ctorParams = {this};
22
23 foreach (Type t in Assembly.GetExecutingAssembly ().GetTypes ()) {
24 if (!t.IsAbstract && t.IsSubclassOf (typeof (EventBase))) {
25 ConstructorInfo ctor = t.GetConstructor (ctorTypes);
26 if (ctor != null) {
27 EventBase apiInstance = (EventBase) ctor.Invoke (ctorParams);
28 addEvent (apiInstance.Name, apiInstance);
29 }
30 }
31 }
32 }
33
34 public override void SetBasePathAndParent (Web _parent, string _relativePath) {
35 base.SetBasePathAndParent (_parent, _relativePath);
36
37 queueThead = ThreadManager.StartThread ("SSE-Processing_" + urlBasePath, QueueProcessThread, ThreadPriority.BelowNormal,
38 _useRealThread: true);
39 }
40
41 public override void Shutdown () {
42 base.Shutdown ();
43 shutdown = true;
44 SignalSendQueue ();
45 }
46
47 private void addEvent (string _eventName, EventBase _eventInstance) {
48 events.Add (_eventName, _eventInstance);
49 WebPermissions.Instance.AddKnownModule ("webevent." + _eventName, _eventInstance.DefaultPermissionLevel ());
50 }
51
52 public override void HandleRequest (HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _user,
53 int _permissionLevel) {
54 string eventName = _req.Url.AbsolutePath.Remove (0, urlBasePath.Length);
55
56 if (!events.TryGetValue (eventName, out EventBase eventInstance)) {
57 Log.Out ($"Error in {nameof (SseHandler)}.HandleRequest(): No handler found for event \"{eventName}\"");
58 _resp.StatusCode = (int) HttpStatusCode.NotFound;
59 return;
60 }
61
62 if (!AuthorizeForEvent (eventName, _permissionLevel)) {
63 _resp.StatusCode = (int) HttpStatusCode.Forbidden;
64 if (_user != null) {
65 //Log.Out ($"{nameof(SseHandler)}: user '{user.SteamID}' not allowed to access '{eventName}'");
66 }
67
68 return;
69 }
70
71 try {
72 eventInstance.AddListener (_resp);
73
74 // Keep the request open
75 _resp.SendChunked = true;
76
77 _resp.AddHeader ("Content-Type", "text/event-stream");
78 _resp.OutputStream.Flush ();
79 } catch (Exception e) {
80 Log.Error ($"Error in {nameof (SseHandler)}.HandleRequest(): Handler {eventInstance.Name} threw an exception:");
81 Log.Exception (e);
82 _resp.StatusCode = (int) HttpStatusCode.InternalServerError;
83 }
84 }
85
86 private bool AuthorizeForEvent (string _eventName, int _permissionLevel) {
87 return WebPermissions.Instance.ModuleAllowedWithLevel ("webevent." + _eventName, _permissionLevel);
88 }
89
90 private void QueueProcessThread (ThreadManager.ThreadInfo _threadInfo) {
91 try {
92 while (!shutdown && !_threadInfo.TerminationRequested ()) {
93 evSendRequest.WaitOne (500);
94
95 foreach (KeyValuePair<string, EventBase> kvp in events) {
96 kvp.Value.ProcessSendQueue ();
97 }
98 }
99 } catch (Exception e) {
100 Log.Error ("SSE: Error processing send queue");
101 Log.Exception (e);
102 }
103 }
104
105 public void SignalSendQueue () {
106 evSendRequest.Set ();
107 }
108 }
109}
Note: See TracBrowser for help on using the repository browser.