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

Last change on this file since 397 was 391, checked in by alloc, 2 years ago

Major refactoring/cleanup

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 public void AddEvent (string _eventName, AbsEvent _eventInstance) {
52 events.Add (_eventName, _eventInstance);
53 WebPermissions.Instance.AddKnownModule ("webevent." + _eventName, _eventInstance.DefaultPermissionLevel ());
54 }
55
56 public override void HandleRequest (RequestContext _context) {
57 string eventName = _context.RequestPath.Remove (0, urlBasePath.Length);
58
59 if (!events.TryGetValue (eventName, out AbsEvent eventInstance)) {
60 Log.Out ($"Error in {nameof (SseHandler)}.HandleRequest(): No handler found for event \"{eventName}\"");
61 _context.Response.StatusCode = (int)HttpStatusCode.NotFound;
62 return;
63 }
64
65 if (!IsAuthorizedForEvent (eventName, _context.PermissionLevel)) {
66 _context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
67 if (_context.Connection != null) {
68 //Log.Out ($"{nameof(SseHandler)}: user '{user.SteamID}' not allowed to access '{eventName}'");
69 }
70
71 return;
72 }
73
74 try {
75 eventInstance.AddListener (_context.Response);
76
77 // Keep the request open
78 _context.Response.SendChunked = true;
79
80 _context.Response.AddHeader ("Content-Type", "text/event-stream");
81 _context.Response.OutputStream.Flush ();
82 } catch (Exception e) {
83 Log.Error ($"Error in {nameof (SseHandler)}.HandleRequest(): Handler {eventInstance.Name} threw an exception:");
84 Log.Exception (e);
85 _context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
86 }
87 }
88
89 private bool IsAuthorizedForEvent (string _eventName, int _permissionLevel) {
90 return WebPermissions.Instance.ModuleAllowedWithLevel ("webevent." + _eventName, _permissionLevel);
91 }
92
93 private void QueueProcessThread (ThreadManager.ThreadInfo _threadInfo) {
94 while (!shutdown && !_threadInfo.TerminationRequested ()) {
95 evSendRequest.WaitOne (500);
96
97 foreach ((string eventName, AbsEvent eventHandler) in events) {
98 try {
99 eventHandler.ProcessSendQueue ();
100 } catch (Exception e) {
101 Log.Error ($"SSE ({eventName}): Error processing send queue");
102 Log.Exception (e);
103 }
104 }
105 }
106 }
107
108 public void SignalSendQueue () {
109 evSendRequest.Set ();
110 }
111 }
112}
Note: See TracBrowser for help on using the repository browser.