source: binary-improvements2/WebServer/src/UrlHandlers/SseHandler.cs@ 402

Last change on this file since 402 was 402, checked in by alloc, 22 months ago
  • Major refactoring
  • Using Utf8Json for (de)serialization
  • Moving APIs to REST
  • Removing dependencies from WebServer and MapRenderer to ServerFixes
File size: 3.7 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Net;
4using System.Reflection;
5using System.Threading;
6using Webserver.SSE;
7
8// Implemented following HTML spec
9// https://html.spec.whatwg.org/multipage/server-sent-events.html
10
11namespace Webserver.UrlHandlers {
12 public class SseHandler : AbsHandler {
13 private readonly Dictionary<string, AbsEvent> events = new CaseInsensitiveStringDictionary<AbsEvent> ();
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 (AbsEvent))) {
25 continue;
26 }
27
28 ConstructorInfo ctor = t.GetConstructor (ctorTypes);
29 if (ctor == null) {
30 continue;
31 }
32
33 AbsEvent apiInstance = (AbsEvent)ctor.Invoke (ctorParams);
34 AddEvent (apiInstance.Name, apiInstance);
35 }
36 }
37
38 public override void SetBasePathAndParent (Web _parent, string _relativePath) {
39 base.SetBasePathAndParent (_parent, _relativePath);
40
41 queueThead = ThreadManager.StartThread ($"SSE-Processing_{urlBasePath}", QueueProcessThread, ThreadPriority.BelowNormal,
42 _useRealThread: true);
43 }
44
45 public override void Shutdown () {
46 base.Shutdown ();
47 shutdown = true;
48 SignalSendQueue ();
49 }
50
51 // ReSharper disable once MemberCanBePrivate.Global
52 public void AddEvent (string _eventName, AbsEvent _eventInstance) {
53 events.Add (_eventName, _eventInstance);
54 WebPermissions.Instance.AddKnownModule ($"webevent.{_eventName}", _eventInstance.DefaultPermissionLevel ());
55 }
56
57 public override void HandleRequest (RequestContext _context) {
58 string eventName = _context.RequestPath.Remove (0, urlBasePath.Length);
59
60 if (!events.TryGetValue (eventName, out AbsEvent eventInstance)) {
61 Log.Warning ($"[Web] [SSE] In {nameof (SseHandler)}.HandleRequest(): No handler found for event \"{eventName}\"");
62 _context.Response.StatusCode = (int)HttpStatusCode.NotFound;
63 return;
64 }
65
66 if (!IsAuthorizedForEvent (eventName, _context.PermissionLevel)) {
67 _context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
68 if (_context.Connection != null) {
69 //Log.Out ($"{nameof(SseHandler)}: user '{user.SteamID}' not allowed to access '{eventName}'");
70 }
71
72 return;
73 }
74
75 try {
76 eventInstance.AddListener (_context.Response);
77
78 // Keep the request open
79 _context.Response.SendChunked = true;
80
81 _context.Response.AddHeader ("Content-Type", "text/event-stream");
82 _context.Response.OutputStream.Flush ();
83 } catch (Exception e) {
84 Log.Error ($"[Web] [SSE] In {nameof (SseHandler)}.HandleRequest(): Handler {eventInstance.Name} threw an exception:");
85 Log.Exception (e);
86 _context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
87 }
88 }
89
90 private bool IsAuthorizedForEvent (string _eventName, int _permissionLevel) {
91 return WebPermissions.Instance.ModuleAllowedWithLevel ($"webevent.{_eventName}", _permissionLevel);
92 }
93
94 private void QueueProcessThread (ThreadManager.ThreadInfo _threadInfo) {
95 while (!shutdown && !_threadInfo.TerminationRequested ()) {
96 evSendRequest.WaitOne (500);
97
98 foreach ((string eventName, AbsEvent eventHandler) in events) {
99 try {
100 eventHandler.ProcessSendQueue ();
101 } catch (Exception e) {
102 Log.Error ($"[Web] [SSE] '{eventName}': Error processing send queue");
103 Log.Exception (e);
104 }
105 }
106 }
107 }
108
109 public void SignalSendQueue () {
110 evSendRequest.Set ();
111 }
112 }
113}
Note: See TracBrowser for help on using the repository browser.