source: binary-improvements2/MapRendering/Web/SSE/SseHandler.cs@ 387

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

Big refactoring in Web to pass around a Context instead of a bunch of individual arguments all the time

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