Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ClientEvents.cs
1using System;
3using System.Text;
4using System.Threading.Tasks;
5using Waher.Content;
8using Waher.Events;
14using Waher.Script;
16using Waher.Security;
17
18namespace Waher.IoTGateway
19{
51 {
55 public const int TabIdCacheTimeoutSeconds = 60;
56
61
65 public ClientEvents()
66 : base("/ClientEvents")
67 {
68 }
69
73 public override bool HandlesSubPaths => true;
74
78 public override bool UserSessions => true;
79
83 public bool AllowsGET => true;
84
88 public bool AllowsPOST => true;
89
96 public async Task GET(HttpRequest Request, HttpResponse Response)
97 {
98 if (string.IsNullOrEmpty(Request.SubPath))
99 {
100 await Response.SendResponse(new BadRequestException("Sub-path missing."));
101 return;
102 }
103
104 string Id = Request.SubPath[1..];
105 string ContentType;
106 byte[] Content;
107 bool ConstantBuffer;
108 bool More;
109
110 lock (requestsByContentID)
111 {
112 if (requestsByContentID.TryGetValue(Id, out ContentQueue Queue))
113 {
114 if (Queue.Content is null)
115 {
116 Queue.Response = Response;
117 return;
118 }
119
120 More = Queue.More;
121 ConstantBuffer = Queue.ConstantBuffer;
122 Content = Queue.Content;
123 ContentType = Queue.ContentType;
124
125 if (More)
126 {
127 Queue.More = false;
128 Queue.ConstantBuffer = false;
129 Queue.Content = null;
130 Queue.ContentType = null;
131 }
132 else
133 requestsByContentID.Remove(Id);
134 }
135 else
136 {
137 requestsByContentID[Id] = new ContentQueue(Id)
138 {
139 Response = Response
140 };
141
142 return;
143 }
144 }
145
146 SetTransparentCorsHeaders(this, Request, Response);
147
148 Response.SetHeader("X-More", More ? "1" : "0");
149 Response.ContentType = ContentType;
150 await Response.Write(ConstantBuffer, Content, 0, Content.Length);
151 await Response.SendResponse();
152 }
153
160 public async Task POST(HttpRequest Request, HttpResponse Response)
161 {
162 if (!Request.HasData || Request.Session is null)
163 {
164 await Response.SendResponse(new BadRequestException("POST request missing data."));
165 return;
166 }
167
168 // TODO: Check User authenticated
169
170 ContentResponse Content = await Request.DecodeDataAsync();
171 if (Content.HasError || !(Content.Decoded is string Location))
172 {
173 await Response.SendResponse(new BadRequestException("Expected location."));
174 return;
175 }
176
177 string TabID = Request.Header["X-TabID"];
178 if (string.IsNullOrEmpty(TabID))
179 {
180 await Response.SendResponse(new BadRequestException("Expected X-TabID header."));
181 return;
182 }
183
184 TabQueue Queue = Register(Request, Response, null, Location, TabID);
185 StringBuilder Json = null;
186
187 SetTransparentCorsHeaders(this, Request, Response);
188
189 Response.ContentType = JsonCodec.DefaultContentType;
190
191 if (!await Queue.SyncObj.TryBeginWrite(10000))
192 {
193 await Response.SendResponse(new InternalServerErrorException("Unable to get access to queue."));
194 return;
195 }
196
197 try
198 {
199 if (!(Queue.Queue.First is null))
200 {
201 foreach (string Event in Queue.Queue)
202 {
203 if (Json is null)
204 Json = new StringBuilder("[");
205 else
206 Json.Append(',');
207
208 Json.Append(Event);
209 }
210
211 Queue.Queue.Clear();
212 Queue.Response = null;
213 }
214 else
215 Queue.Response = Response;
216 }
217 finally
218 {
219 await Queue.SyncObj.EndWrite();
220 }
221
222 if (!(Json is null))
223 {
224 timeoutByTabID.Remove(TabID);
225
226 Json.Append(']');
227 await Response.Write(Json.ToString());
228 await Response.SendResponse();
229 await Response.DisposeAsync();
230 }
231 else
232 timeoutByTabID[TabID] = Queue;
233 }
234
235 private static TabQueue Register(HttpRequest Request, HttpResponse Response, WebSocket Socket, string Location, string TabID)
236 {
237 Uri Uri = new Uri(Location);
238 string Resource = Uri.LocalPath;
239 (string, string, string)[] Query = null;
240 SessionVariables Session = Request.Session;
241 string s;
242
243 if (!string.IsNullOrEmpty(Uri.Query))
244 {
245 s = Uri.Query;
246 if (s.StartsWith("?"))
247 s = s[1..];
248
249 string[] Parts = s.Split('&');
250 Query = new (string, string, string)[Parts.Length];
251 int i, j = 0;
252
253 foreach (string Part in Parts)
254 {
255 i = Part.IndexOf('=');
256 if (i < 0)
257 Query[j++] = (Part, string.Empty, string.Empty);
258 else
259 {
260 string s2 = Part[(i + 1)..];
261 Query[j++] = (Part[..i], s2, System.Net.WebUtility.UrlDecode(s2));
262 }
263 }
264 }
265
266 if (eventsByTabID.TryGetValue(TabID, out TabQueue Queue) &&
267 !string.IsNullOrEmpty(Queue.SessionID) &&
268 !(Queue.SyncObj is null))
269 {
270 Queue.WebSocket = Socket;
271 Queue.Uri = Uri;
272 Queue.Query = Query;
273 }
274 else
275 {
276 string HttpSessionID = GetSessionId(Request, Response);
277
278 TabQueue Queue2 = new TabQueue(TabID, HttpSessionID, Session, false)
279 {
280 WebSocket = Socket,
281 Uri = Uri,
282 Query = Query
283 };
284
285 if (!(Queue is null))
286 {
287 while (!(Queue.Queue.First is null))
288 {
289 Queue2.Queue.AddLast(Queue.Queue.First.Value);
290 Queue.Queue.RemoveFirst();
291 }
292 }
293
294 Queue = Queue2;
295 eventsByTabID[TabID] = Queue;
296 }
297
298 lock (locationByTabID)
299 {
300 if (!locationByTabID.TryGetValue(TabID, out s) || s != Resource)
301 locationByTabID[TabID] = Resource;
302 }
303
304 lock (tabIdsByLocation)
305 {
306 if (!tabIdsByLocation.TryGetValue(Resource, out Dictionary<string, (string, string, string)[]> TabIds))
307 {
308 TabIds = new Dictionary<string, (string, string, string)[]>();
309 tabIdsByLocation[Resource] = TabIds;
310 }
311
312 TabIds[TabID] = Query;
313 }
314
315 Type UserType;
316 object UserObject;
317
318 if (!(Session is null) &&
319 Session.TryGetVariable(" User ", out Variable UserVariable) &&
320 !((UserObject = UserId(UserVariable.ValueObject)) is null))
321 {
322 lock (usersByTabID)
323 {
324 if (!usersByTabID.TryGetValue(TabID, out object Obj2) || !Obj2.Equals(UserObject))
325 usersByTabID[TabID] = UserObject;
326 }
327
328 UserType = UserObject.GetType();
329
330 lock (tabIdsByUser)
331 {
332 if (!tabIdsByUser.TryGetValue(UserType, out Dictionary<object, Dictionary<string, (string, string, string)[]>> UserObjects))
333 {
334 UserObjects = new Dictionary<object, Dictionary<string, (string, string, string)[]>>();
335 tabIdsByUser[UserType] = UserObjects;
336 }
337
338 if (!UserObjects.TryGetValue(UserObject, out Dictionary<string, (string, string, string)[]> TabIds))
339 {
340 TabIds = new Dictionary<string, (string, string, string)[]>();
341 UserObjects[UserObject] = TabIds;
342 }
343
344 TabIds[TabID] = Query;
345 }
346 }
347 else
348 {
349 lock (usersByTabID)
350 {
351 if (!usersByTabID.TryGetValue(TabID, out UserObject))
352 UserObject = null;
353 }
354
355 if (!(UserObject is null))
356 {
357 UserType = UserObject.GetType();
358
359 lock (tabIdsByUser)
360 {
361 if (tabIdsByUser.TryGetValue(UserType, out Dictionary<object, Dictionary<string, (string, string, string)[]>> UserObjects))
362 {
363 if (UserObjects.Remove(UserObject) && UserObjects.Count == 0)
364 tabIdsByUser.Remove(UserType);
365 }
366 }
367 }
368 }
369
370 return Queue;
371 }
372
373 private static object UserId(object User)
374 {
375 if (User is IUser User2)
376 return User2.UserName;
377 else if (User is GenericObject GenObj)
378 {
379 if (GenObj.TryGetFieldValue("UserName", out object UserName))
380 return UserName;
381 else
382 return GenObj.ObjectId;
383 }
384 else if (User is Dictionary<string, IElement> ScriptObj)
385 {
386 if (ScriptObj.TryGetValue("UserName", out IElement UserName))
387 return UserName.AssociatedObjectValue;
388 else
389 return User;
390 }
391 else if (User is Dictionary<string, object> JsonObj)
392 {
393 if (JsonObj.TryGetValue("UserName", out object UserName))
394 return UserName;
395 else
396 return User;
397 }
398 else
399 return User;
400 }
401
402 internal static async Task RegisterWebSocket(WebSocket Socket, string Location, string TabID)
403 {
404 TabQueue Queue = Register(Socket.HttpRequest, Socket.HttpResponse, Socket, Location, TabID);
405 LinkedList<string> ToSend = null;
406
407 if (!await Queue.SyncObj.TryBeginWrite(10000))
408 throw new InternalServerErrorException("Unable to get access to queue.");
409
410 try
411 {
412 if (!(Queue.Queue.First is null))
413 {
414 ToSend = new LinkedList<string>();
415
416 foreach (string s2 in Queue.Queue)
417 ToSend.AddLast(s2);
418
419 Queue.Queue.Clear();
420 }
421 }
422 finally
423 {
424 await Queue.SyncObj.EndWrite();
425 }
426
427 if (!(ToSend is null))
428 {
429 foreach (string s2 in ToSend)
430 await Socket.Send(s2, 4096);
431 }
432 }
433
434 internal static void Ping(string TabID)
435 {
436 if (eventsByTabID.TryGetValue(TabID, out TabQueue TabQueue))
437 TabQueue.Ping();
438 }
439
440 internal static async Task UnregisterWebSocket(WebSocket Socket, string Location, string TabID)
441 {
442 if (eventsByTabID.TryGetValue(TabID, out TabQueue Queue) && Queue.WebSocket == Socket)
443 {
444 if (!await Queue.SyncObj.TryBeginWrite(10000))
445 throw new InternalServerErrorException("Unable to get access to queue.");
446
447 try
448 {
449 Queue.WebSocket = null;
450 }
451 finally
452 {
453 await Queue.SyncObj.EndWrite();
454 }
455
456 if (Queue.KeepAliveUntil > DateTime.Now)
457 return;
458 }
459
460 Uri Uri = new Uri(Location);
461 Remove(TabID, Uri.LocalPath);
462 }
463
470 public static void KeepTabAlive(string TabID, DateTime KeepAliveUntil)
471 {
472 if (!eventsByTabID.TryGetValue(TabID, out TabQueue Queue))
473 {
474 Queue = new TabQueue(TabID, string.Empty, new SessionVariables(), true);
475 eventsByTabID[TabID] = Queue;
476 }
477
478 DateTime Now = DateTime.Now;
479
480 if (KeepAliveUntil >= Now.AddSeconds(TabIdCacheTimeoutSeconds))
481 {
482 DateTime CurrentKeepAliveTime = Queue.KeepAliveUntil;
483
484 if (CurrentKeepAliveTime < KeepAliveUntil)
485 {
486 Queue.KeepAliveUntil = KeepAliveUntil;
487
488 if (CurrentKeepAliveTime == DateTime.MinValue)
490 }
491 }
492 }
493
494 private static void KeepTabAlive(object State)
495 {
496 if (State is string TabID &&
497 eventsByTabID.TryGetValue(TabID, out TabQueue Queue))
498 {
499 DateTime Now = DateTime.Now;
500
501 if (Queue.KeepAliveUntil < Now)
502 Queue.KeepAliveUntil = DateTime.MinValue;
503 else
505 }
506 }
507
508 private static readonly Cache<string, TabQueue> eventsByTabID = GetTabQueueCache();
509 private static readonly Cache<string, TabQueue> timeoutByTabID = GetTabTimeoutCache();
510 private static readonly Cache<string, ContentQueue> requestsByContentID = GetContentCache();
511 private static readonly Dictionary<string, string> locationByTabID = new Dictionary<string, string>();
512 private static readonly Dictionary<string, object> usersByTabID = new Dictionary<string, object>();
513 private static readonly Dictionary<string, Dictionary<string, (string, string, string)[]>> tabIdsByLocation =
514 new Dictionary<string, Dictionary<string, (string, string, string)[]>>(StringComparer.OrdinalIgnoreCase);
515 private static readonly Dictionary<Type, Dictionary<object, Dictionary<string, (string, string, string)[]>>> tabIdsByUser =
516 new Dictionary<Type, Dictionary<object, Dictionary<string, (string, string, string)[]>>>();
517
518 private static Cache<string, TabQueue> GetTabTimeoutCache()
519 {
520 Cache<string, TabQueue> Result = new Cache<string, TabQueue>(int.MaxValue, TimeSpan.MaxValue, TimeSpan.FromSeconds(20), true);
521 Result.Removed += TimeoutCacheItem_Removed;
522 return Result;
523 }
524
525 private static async Task TimeoutCacheItem_Removed(object Sender, CacheItemEventArgs<string, TabQueue> e)
526 {
527 if (e.Reason == RemovedReason.NotUsed)
528 {
529 HttpResponse Response = e.Value.Response;
530
531 if (!(Response is null))
532 {
533 try
534 {
535 e.Value.Response = null;
536
537 await Response.Write("[{\"type\":\"NOP\"}]");
538 await Response.SendResponse();
539 }
540 catch (Exception)
541 {
542 // Ignore
543 }
544 finally
545 {
546 await Response.DisposeAsync();
547 }
548 }
549 }
550 }
551
552 private static Cache<string, TabQueue> GetTabQueueCache()
553 {
554 Cache<string, TabQueue> Result = new Cache<string, TabQueue>(int.MaxValue, TimeSpan.MaxValue, TimeSpan.FromSeconds(TabIdCacheTimeoutSeconds), true);
555 Result.Removed += QueueCacheItem_Removed;
556 return Result;
557 }
558
559 private static async Task QueueCacheItem_Removed(object Sender, CacheItemEventArgs<string, TabQueue> e)
560 {
561 TabQueue Queue = e.Value;
562 string TabID = Queue.TabID;
563
564 Remove(TabID, null);
565 await Queue.DisposeAsync();
566 }
567
568 private static void Remove(string TabID, string Resource)
569 {
570 string Location;
571 object User;
572
573 lock (locationByTabID)
574 {
575 if (locationByTabID.TryGetValue(TabID, out Location) && (Resource is null || Location == Resource))
576 locationByTabID.Remove(TabID);
577 else
578 Location = null;
579 }
580
581 if (!(Location is null))
582 {
583 lock (tabIdsByLocation)
584 {
585 if (tabIdsByLocation.TryGetValue(Location, out Dictionary<string, (string, string, string)[]> TabIDs))
586 {
587 if (TabIDs.Remove(TabID) && TabIDs.Count == 0)
588 tabIdsByLocation.Remove(Location);
589 }
590 }
591 }
592
593 lock (usersByTabID)
594 {
595 if (usersByTabID.TryGetValue(TabID, out User))
596 usersByTabID.Remove(TabID);
597 else
598 User = null;
599 }
600
601 if (!(User is null))
602 {
603 Type UserType = User.GetType();
604
605 lock (tabIdsByUser)
606 {
607 if (tabIdsByUser.TryGetValue(UserType, out Dictionary<object, Dictionary<string, (string, string, string)[]>> UserObjects) &&
608 UserObjects.TryGetValue(User, out Dictionary<string, (string, string, string)[]> TabIDs))
609 {
610 if (TabIDs.Remove(TabID) && TabIDs.Count == 0 &&
611 UserObjects.Remove(User) && UserObjects.Count == 0)
612 {
613 tabIdsByUser.Remove(UserType);
614 }
615 }
616 }
617 }
618 }
619
620 private static Cache<string, ContentQueue> GetContentCache()
621 {
622 Cache<string, ContentQueue> Result = new Cache<string, ContentQueue>(int.MaxValue, TimeSpan.MaxValue, TimeSpan.FromSeconds(90), true);
623 Result.Removed += ContentCacheItem_Removed;
624 return Result;
625 }
626
627 private static async Task ContentCacheItem_Removed(object Sender, CacheItemEventArgs<string, ContentQueue> e)
628 {
629 if (e.Reason != RemovedReason.Manual)
630 {
631 try
632 {
633 HttpResponse Response = e.Value.Response;
634
635 if (!(Response is null))
636 {
637 Response.ContentType = PlainTextCodec.DefaultContentType;
638 await Response.Write("Request took too long to complete.");
639 await Response.SendResponse();
640 }
641 }
642 catch (Exception)
643 {
644 // Ignore
645 }
646 }
647 }
648
657 public static Task ReportAsynchronousResult(string Id, string ContentType,
658 bool ConstantBuffer, byte[] Result)
659 {
660 return ReportAsynchronousResult(Id, ContentType, ConstantBuffer, Result, false);
661 }
662
672 public static async Task ReportAsynchronousResult(string Id, string ContentType,
673 bool ConstantBuffer, byte[] Result, bool More)
674 {
675 try
676 {
677 HttpResponse Response;
678
679 lock (requestsByContentID)
680 {
681 if (requestsByContentID.TryGetValue(Id, out ContentQueue Queue))
682 {
683 if (Queue.Response is null)
684 {
685 Queue.ContentType = ContentType;
686 Queue.ConstantBuffer = ConstantBuffer;
687 Queue.Content = Result;
688 Queue.More = More;
689 return;
690 }
691
692 Response = Queue.Response;
693
694 if (More)
695 Queue.Response = null;
696 else
697 requestsByContentID.Remove(Id);
698 }
699 else
700 {
701 Queue = new ContentQueue(Id)
702 {
703 ContentType = ContentType,
704 ConstantBuffer = ConstantBuffer,
705 Content = Result,
706 More = More
707 };
708
709 requestsByContentID[Id] = Queue;
710 return;
711 }
712 }
713
714 Response.SetHeader("X-More", More ? "1" : "0");
715 Response.ContentType = ContentType;
716 await Response.Write(ConstantBuffer, Result, 0, Result.Length);
717 await Response.SendResponse();
718 }
719 catch (Exception)
720 {
721 // Ignore
722 }
723 }
724
729 public static string[] GetOpenLocations()
730 {
731 string[] Result;
732
733 lock (tabIdsByLocation)
734 {
735 Result = new string[tabIdsByLocation.Count];
736 tabIdsByLocation.Keys.CopyTo(Result, 0);
737 }
738
739 return Result;
740 }
741
746 public static object[] GetActiveUsers()
747 {
748 List<object> Result = new List<object>();
749
750 lock (tabIdsByUser)
751 {
752 foreach (KeyValuePair<Type, Dictionary<object, Dictionary<string, (string, string, string)[]>>> P in tabIdsByUser)
753 Result.AddRange(P.Value.Keys);
754 }
755
756 return Result.ToArray();
757 }
758
764 public static string[] GetTabIDsForLocation(string Location)
765 {
766 return GetTabIDsForLocation(Location, Array.Empty<KeyValuePair<string, string>>());
767 }
768
776 public static string[] GetTabIDsForLocation(string Location, string QueryParameter1, string QueryParameterValue1)
777 {
778 return GetTabIDsForLocation(Location, new KeyValuePair<string, string>(QueryParameter1, QueryParameterValue1));
779 }
780
790 public static string[] GetTabIDsForLocation(string Location, string QueryParameter1, string QueryParameterValue1,
791 string QueryParameter2, string QueryParameterValue2)
792 {
793 return GetTabIDsForLocation(Location,
794 new KeyValuePair<string, string>(QueryParameter1, QueryParameterValue1),
795 new KeyValuePair<string, string>(QueryParameter2, QueryParameterValue2));
796 }
797
804 public static string[] GetTabIDsForLocation(string Location, params KeyValuePair<string, string>[] QueryFilter)
805 {
806 return GetTabIDsForLocation(Location, false, QueryFilter);
807 }
808
817 public static string[] GetTabIDsForLocation(string Location, bool IgnoreCase,
818 string QueryParameter1, string QueryParameterValue1)
819 {
820 return GetTabIDsForLocation(Location, IgnoreCase, new KeyValuePair<string, string>(QueryParameter1, QueryParameterValue1));
821 }
822
833 public static string[] GetTabIDsForLocation(string Location, bool IgnoreCase,
834 string QueryParameter1, string QueryParameterValue1,
835 string QueryParameter2, string QueryParameterValue2)
836 {
837 return GetTabIDsForLocation(Location, IgnoreCase,
838 new KeyValuePair<string, string>(QueryParameter1, QueryParameterValue1),
839 new KeyValuePair<string, string>(QueryParameter2, QueryParameterValue2));
840 }
841
849 public static string[] GetTabIDsForLocation(string Location, bool IgnoreCase, params KeyValuePair<string, string>[] QueryFilter)
850 {
851 lock (tabIdsByLocation)
852 {
853 if (tabIdsByLocation.TryGetValue(Location, out Dictionary<string, (string, string, string)[]> TabIDs))
854 return ProcessQueryFilterLocked(TabIDs, QueryFilter, IgnoreCase);
855 }
856
857 if (eventsByTabID.TryGetValue(Location, out TabQueue Queue))
858 {
859 return ProcessQueryFilterLocked(new Dictionary<string, (string, string, string)[]>()
860 {
861 { Location, Queue.Query }
862 }, QueryFilter, IgnoreCase);
863 }
864 else
865 return Array.Empty<string>();
866 }
867
873 public static string[] GetTabIDsForUser(object User)
874 {
875 return GetTabIDsForUser(User, null, false, Array.Empty<KeyValuePair<string, string>>());
876 }
877
884 public static string[] GetTabIDsForUser(object User, params KeyValuePair<string, string>[] QueryFilter)
885 {
886 return GetTabIDsForUser(User, null, false, QueryFilter);
887 }
888
897 public static string[] GetTabIDsForUser(object User, bool IgnoreCase,
898 string QueryParameter1, string QueryParameterValue1)
899 {
900 return GetTabIDsForUser(User, IgnoreCase, new KeyValuePair<string, string>(QueryParameter1, QueryParameterValue1));
901 }
902
913 public static string[] GetTabIDsForUser(object User, bool IgnoreCase,
914 string QueryParameter1, string QueryParameterValue1,
915 string QueryParameter2, string QueryParameterValue2)
916 {
917 return GetTabIDsForUser(User, IgnoreCase,
918 new KeyValuePair<string, string>(QueryParameter1, QueryParameterValue1),
919 new KeyValuePair<string, string>(QueryParameter2, QueryParameterValue2));
920 }
921
929 public static string[] GetTabIDsForUser(object User, bool IgnoreCase, params KeyValuePair<string, string>[] QueryFilter)
930 {
931 return GetTabIDsForUser(User, null, IgnoreCase, QueryFilter);
932 }
933
940 public static string[] GetTabIDsForUser(object User, string Location)
941 {
942 return GetTabIDsForUser(User, Location, Array.Empty<KeyValuePair<string, string>>());
943 }
944
953 public static string[] GetTabIDsForUser(object User, string Location,
954 string QueryParameter1, string QueryParameterValue1)
955 {
956 return GetTabIDsForUser(User, Location,
957 new KeyValuePair<string, string>(QueryParameter1, QueryParameterValue1));
958 }
959
970 public static string[] GetTabIDsForUser(object User, string Location,
971 string QueryParameter1, string QueryParameterValue1,
972 string QueryParameter2, string QueryParameterValue2)
973 {
974 return GetTabIDsForUser(User, Location,
975 new KeyValuePair<string, string>(QueryParameter1, QueryParameterValue1),
976 new KeyValuePair<string, string>(QueryParameter2, QueryParameterValue2));
977 }
978
986 public static string[] GetTabIDsForUser(object User, string Location, params KeyValuePair<string, string>[] QueryFilter)
987 {
988 return GetTabIDsForUser(User, Location, false, QueryFilter);
989 }
990
1000 public static string[] GetTabIDsForUser(object User, string Location, bool IgnoreCase,
1001 string QueryParameter1, string QueryParameterValue1)
1002 {
1003 return GetTabIDsForUser(User, Location, IgnoreCase,
1004 new KeyValuePair<string, string>(QueryParameter1, QueryParameterValue1));
1005 }
1006
1018 public static string[] GetTabIDsForUser(object User, string Location, bool IgnoreCase,
1019 string QueryParameter1, string QueryParameterValue1,
1020 string QueryParameter2, string QueryParameterValue2)
1021 {
1022 return GetTabIDsForUser(User, Location, IgnoreCase,
1023 new KeyValuePair<string, string>(QueryParameter1, QueryParameterValue1),
1024 new KeyValuePair<string, string>(QueryParameter2, QueryParameterValue2));
1025 }
1026
1035 public static string[] GetTabIDsForUser(object User, string Location, bool IgnoreCase, params KeyValuePair<string, string>[] QueryFilter)
1036 {
1037 User = UserId(User);
1038 Type T = User.GetType();
1039 string[] Result;
1040
1041 lock (tabIdsByUser)
1042 {
1043 if (tabIdsByUser.TryGetValue(T, out Dictionary<object, Dictionary<string, (string, string, string)[]>> UserObjects) &&
1044 UserObjects.TryGetValue(User, out Dictionary<string, (string, string, string)[]> TabIDs))
1045 {
1046 Result = ProcessQueryFilterLocked(TabIDs, QueryFilter, IgnoreCase);
1047 }
1048 else
1049 return Array.Empty<string>();
1050 }
1051
1052 if (string.IsNullOrEmpty(Location))
1053 return Result;
1054
1055 List<string> Result2 = new List<string>();
1056
1057 lock (tabIdsByLocation)
1058 {
1059 if (tabIdsByLocation.TryGetValue(Location, out Dictionary<string, (string, string, string)[]> TabIDs))
1060 {
1061 foreach (string TabID in Result)
1062 {
1063 if (TabIDs.ContainsKey(TabID))
1064 Result2.Add(TabID);
1065 }
1066 }
1067 }
1068
1069 return Result2.ToArray();
1070 }
1071
1072 private static string[] ProcessQueryFilterLocked(Dictionary<string, (string, string, string)[]> TabIDs,
1073 KeyValuePair<string, string>[] QueryFilter, bool IgnoreCase)
1074 {
1075 string[] Result;
1076
1077 if (QueryFilter is null || QueryFilter.Length == 0)
1078 {
1079 Result = new string[TabIDs.Count];
1080 TabIDs.Keys.CopyTo(Result, 0);
1081 }
1082 else
1083 {
1084 List<string> Match = new List<string>();
1085 bool Found;
1086 bool IsMatch;
1087
1088 foreach (KeyValuePair<string, (string, string, string)[]> P in TabIDs)
1089 {
1090 IsMatch = true;
1091
1092 foreach (KeyValuePair<string, string> Q in QueryFilter)
1093 {
1094 if (Q.Value is null)
1095 {
1096 Found = true;
1097
1098 if (!(P.Value is null))
1099 {
1100 foreach ((string, string, string) Q2 in P.Value)
1101 {
1102 if (Q2.Item1 == Q.Key)
1103 {
1104 Found = false;
1105 break;
1106 }
1107 }
1108 }
1109
1110 if (!Found)
1111 {
1112 IsMatch = false;
1113 break;
1114 }
1115 }
1116 else
1117 {
1118 Found = false;
1119
1120 if (!(P.Value is null))
1121 {
1122 foreach ((string, string, string) Q2 in P.Value)
1123 {
1124 if (Q2.Item1 == Q.Key &&
1125 (string.Compare(Q2.Item2, Q.Value, IgnoreCase) == 0 ||
1126 string.Compare(Q2.Item3, Q.Value, IgnoreCase) == 0))
1127 {
1128 Found = true;
1129 break;
1130 }
1131 }
1132 }
1133
1134 if (!Found)
1135 {
1136 IsMatch = false;
1137 break;
1138 }
1139 }
1140 }
1141
1142 if (IsMatch)
1143 Match.Add(P.Key);
1144 }
1145
1146 Result = Match.ToArray();
1147 }
1148
1149 return Result;
1150 }
1151
1157 public static string[] GetTabIDsForLocations(params string[] Locations)
1158 {
1159 switch (Locations.Length)
1160 {
1161 case 0:
1162 return Array.Empty<string>();
1163
1164 case 1:
1165 return GetTabIDsForLocation(Locations[0]);
1166
1167 default:
1168 Dictionary<string, bool> Result = new Dictionary<string, bool>();
1169
1170 lock (tabIdsByLocation)
1171 {
1172 foreach (string Location in Locations)
1173 {
1174 if (tabIdsByLocation.TryGetValue(Location, out Dictionary<string, (string, string, string)[]> TabIDs))
1175 {
1176 foreach (string TabID in TabIDs.Keys)
1177 Result[TabID] = true;
1178 }
1179 }
1180 }
1181
1182 string[] Result2 = new string[Result.Count];
1183 Result.Keys.CopyTo(Result2, 0);
1184
1185 return Result2;
1186 }
1187 }
1188
1193 public static string[] GetTabIDs()
1194 {
1195 string[] Result;
1196
1197 lock (locationByTabID)
1198 {
1199 Result = new string[locationByTabID.Count];
1200 locationByTabID.Keys.CopyTo(Result, 0);
1201 }
1202
1203 return Result;
1204 }
1205
1210 public static string[] GetTabIDsForUsers()
1211 {
1212 Dictionary<string, bool> Result = new Dictionary<string, bool>();
1213
1214 lock (tabIdsByUser)
1215 {
1216 foreach (Dictionary<object, Dictionary<string, (string, string, string)[]>> P in tabIdsByUser.Values)
1217 {
1218 foreach (Dictionary<string, (string, string, string)[]> P2 in P.Values)
1219 {
1220 foreach (string TabID in P2.Keys)
1221 Result[TabID] = true;
1222 }
1223 }
1224 }
1225
1226 string[] Result2 = new string[Result.Count];
1227 Result.Keys.CopyTo(Result2, 0);
1228 return Result2;
1229 }
1230
1236 public static TabInformation GetTabIDInformation(string TabID)
1237 {
1238 if (eventsByTabID.TryGetValue(TabID, out TabQueue Queue))
1239 return new TabInformation(Queue);
1240 else
1241 return null;
1242 }
1243
1247 public class TabInformation
1248 {
1253 internal TabInformation(TabQueue Queue)
1254 {
1255 this.TabID = Queue.TabID;
1256 this.SessionID = Queue.SessionID;
1257 this.Session = Queue.Session;
1258 this.Uri = Queue.Uri;
1259
1260 this.Query = new Dictionary<string, string>();
1261
1262 if (!(Queue.Query is null))
1263 {
1264 foreach ((string, string, string) Rec in Queue.Query)
1265 this.Query[Rec.Item1] = Rec.Item3;
1266 }
1267 }
1268
1272 public string TabID { get; }
1273
1277 public string SessionID { get; }
1278
1283
1287 public Uri Uri { get; }
1288
1292 public Dictionary<string, string> Query { get; }
1293 }
1294
1295 internal class TabQueue : IDisposableAsync
1296 {
1297 private WebSocket webSocket;
1298
1299 public string TabID;
1300 public string SessionID;
1301 public SessionVariables Session;
1302 public MultiReadSingleWriteObject SyncObj;
1303 public LinkedList<string> Queue = new LinkedList<string>();
1304 public HttpResponse Response = null;
1305 public Uri Uri = null;
1306 public DateTime KeepAliveUntil = DateTime.MinValue;
1307 public (string, string, string)[] Query = null;
1308 private bool disposeSession;
1309
1310 public TabQueue(string ID, string SessionID, SessionVariables Session, bool DisposeSession)
1311 {
1312 this.SyncObj = new MultiReadSingleWriteObject(this);
1313 this.TabID = ID;
1314 this.SessionID = SessionID;
1315 this.Session = Session;
1316 this.disposeSession = DisposeSession;
1317 }
1318
1319 public WebSocket WebSocket
1320 {
1321 get => this.webSocket;
1322 set
1323 {
1324 if (this.webSocket != value)
1325 {
1326 if (!(this.webSocket is null))
1327 this.webSocket.Heartbeat -= this.Ping;
1328
1329 this.webSocket = value;
1330
1331 if (!(this.webSocket is null))
1332 this.webSocket.Heartbeat += this.Ping;
1333 }
1334 }
1335 }
1336
1337 [Obsolete("Use DisposeAsync instead.")]
1338 public void Dispose()
1339 {
1340 this.DisposeAsync().Wait();
1341 }
1342
1343 public async Task DisposeAsync()
1344 {
1345 this.WebSocket = null;
1346
1347 await this.SyncObj.TryBeginWrite(10000);
1348
1349 this.SyncObj?.Dispose();
1350 this.SyncObj = null;
1351
1352 if (this.disposeSession)
1353 {
1354 this.Session?.Dispose();
1355 this.Session = null;
1356 }
1357
1358 this.Queue?.Clear();
1359 }
1360
1361 private Task Ping(object Sender, WebSocketEventArgs e)
1362 {
1363 eventsByTabID.Ping(this.TabID);
1364 this.Ping();
1365 return Task.CompletedTask;
1366 }
1367
1368 public void Ping()
1369 {
1370 if (!string.IsNullOrEmpty(this.SessionID))
1371 Gateway.HttpServer?.GetSession(this.SessionID, false);
1372 }
1373 }
1374
1375 private class ContentQueue
1376 {
1377 public string ContentID;
1378 public string ContentType;
1379 public HttpResponse Response = null;
1380 public bool ConstantBuffer;
1381 public byte[] Content = null;
1382 public bool More = false;
1383
1384 public ContentQueue(string ID)
1385 {
1386 this.ContentID = ID;
1387 }
1388 }
1389
1397 public static Task<int> PushEvent(string[] TabIDs, string Type, object Data)
1398 {
1399 if (Data is string s)
1400 return PushEvent(TabIDs, Type, s, false, null, null);
1401 else
1402 {
1403 s = JSON.Encode(Data, false);
1404 return PushEvent(TabIDs, Type, s, true, null, null);
1405 }
1406 }
1407
1415 public static Task<int> PushEvent(string[] TabIDs, string Type, string Data)
1416 {
1417 return PushEvent(TabIDs, Type, Data, false, null, null);
1418 }
1419
1428 public static Task<int> PushEvent(string[] TabIDs, string Type, string Data, bool DataIsJson)
1429 {
1430 return PushEvent(TabIDs, Type, Data, DataIsJson, null, null);
1431 }
1432
1445 public static async Task<int> PushEvent(string[] TabIDs, string Type, string Data, bool DataIsJson, string UserVariable, params string[] Privileges)
1446 {
1447 int Result = 0;
1448
1449 try
1450 {
1451 StringBuilder Json = new StringBuilder();
1452
1453 Json.Append("{\"type\":\"");
1454 Json.Append(Type);
1455 Json.Append("\",\"data\":");
1456
1457 if (DataIsJson)
1458 Json.Append(Data);
1459 else
1460 {
1461 Json.Append('"');
1462 Json.Append(JSON.Encode(Data));
1463 Json.Append('"');
1464 }
1465
1466 Json.Append('}');
1467
1468 string s = Json.ToString();
1469
1470 TabIDs ??= eventsByTabID.GetKeys();
1471
1472 foreach (string TabID in TabIDs)
1473 {
1474 if (!(TabID is null) && eventsByTabID.TryGetValue(TabID, out TabQueue Queue))
1475 {
1476 if (!string.IsNullOrEmpty(UserVariable))
1477 {
1478 if (!Queue.Session.TryGetVariable(UserVariable, out Variable v) ||
1479 !(v.ValueObject is IUser User))
1480 {
1481 continue;
1482 }
1483
1484 if (!(Privileges is null))
1485 {
1486 bool HasPrivileges = true;
1487
1488 foreach (string Privilege in Privileges)
1489 {
1490 if (!User.HasPrivilege(Privilege))
1491 {
1492 HasPrivileges = false;
1493 break;
1494 }
1495 }
1496
1497 if (!HasPrivileges)
1498 continue;
1499 }
1500 }
1501
1502 if (await Queue.SyncObj.TryBeginWrite(10000))
1503 {
1504 try
1505 {
1506 if (!(Queue.WebSocket is null))
1507 await Queue.WebSocket.Send(Json.ToString(), 4096);
1508 else if (!(Queue.Response is null))
1509 {
1510 try
1511 {
1512 await Queue.Response.Write("[" + Json.ToString() + "]");
1513 await Queue.Response.SendResponse();
1514 await Queue.Response.DisposeAsync();
1515 Queue.Response = null;
1516 }
1517 catch (Exception)
1518 {
1519 // Ignore
1520 }
1521 }
1522 else
1523 Queue.Queue.AddLast(s);
1524 }
1525 finally
1526 {
1527 if (!(Queue.SyncObj is null))
1528 await Queue.SyncObj.EndWrite();
1529 }
1530 }
1531
1532 timeoutByTabID.Remove(TabID);
1533 Result++;
1534 }
1535 }
1536 }
1537 catch (Exception ex)
1538 {
1539 Log.Exception(ex);
1540 }
1541
1542 return Result;
1543 }
1544
1545 }
1546}
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
Helps with common JSON-related tasks.
Definition: JSON.cs:16
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:537
const string DefaultContentType
application/json
Definition: JsonCodec.cs:20
Plain text encoder/decoder.
const string DefaultContentType
text/plain
Class representing an event.
Definition: Event.cs:11
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
Dictionary< string, string > Query
Query parameters.
SessionVariables Session
Session variables
The ClientEvents class allows applications to push information asynchronously to web clients connecte...
Definition: ClientEvents.cs:51
static string[] GetTabIDsForUser(object User, params KeyValuePair< string, string >[] QueryFilter)
Gets the Tab IDs of all tabs that a specific user views.
static async Task< int > PushEvent(string[] TabIDs, string Type, string Data, bool DataIsJson, string UserVariable, params string[] Privileges)
Puses an event to a set of Tabs, given their Tab IDs.
static string[] GetTabIDsForUser(object User, string Location)
Gets the Tab IDs of all tabs that a specific user views.
static void KeepTabAlive(string TabID, DateTime KeepAliveUntil)
Keeps a Tab alive, even though it might be temporarily offline or disconnected.
static string[] GetTabIDsForUser(object User, string Location, string QueryParameter1, string QueryParameterValue1)
Gets the Tab IDs of all tabs that a specific user views.
override bool UserSessions
If the resource uses user sessions.
Definition: ClientEvents.cs:78
static string[] GetOpenLocations()
Returns a list of resources that are currently open.
static object[] GetActiveUsers()
Returns a list of active users
bool AllowsGET
If the GET method is allowed.
Definition: ClientEvents.cs:83
static string[] GetTabIDsForLocation(string Location, string QueryParameter1, string QueryParameterValue1)
Gets the Tab IDs of all tabs that display a particular resource.
static TabInformation GetTabIDInformation(string TabID)
Gets information about a Tab, given its ID.
static string[] GetTabIDsForUser(object User, bool IgnoreCase, string QueryParameter1, string QueryParameterValue1, string QueryParameter2, string QueryParameterValue2)
Gets the Tab IDs of all tabs that a specific user views.
override bool HandlesSubPaths
If the resource handles sub-paths.
Definition: ClientEvents.cs:73
static string[] GetTabIDsForLocation(string Location)
Gets the Tab IDs of all tabs that display a particular resource.
static async Task ReportAsynchronousResult(string Id, string ContentType, bool ConstantBuffer, byte[] Result, bool More)
Reports asynchronously evaluated result back to a client.
const int TabIdCacheTimeoutSeconds
Number of seconds before a Tab ID is purged, unless references or kept alive.
Definition: ClientEvents.cs:55
static Task< int > PushEvent(string[] TabIDs, string Type, object Data)
Puses an event to a set of Tabs, given their Tab IDs.
static string[] GetTabIDsForLocation(string Location, params KeyValuePair< string, string >[] QueryFilter)
Gets the Tab IDs of all tabs that display a particular resource.
static string[] GetTabIDsForUser(object User, string Location, bool IgnoreCase, string QueryParameter1, string QueryParameterValue1, string QueryParameter2, string QueryParameterValue2)
Gets the Tab IDs of all tabs that a specific user views.
static string[] GetTabIDsForUser(object User, string Location, bool IgnoreCase, string QueryParameter1, string QueryParameterValue1)
Gets the Tab IDs of all tabs that a specific user views.
async Task GET(HttpRequest Request, HttpResponse Response)
Executes the POST method on the resource.
Definition: ClientEvents.cs:96
static Task ReportAsynchronousResult(string Id, string ContentType, bool ConstantBuffer, byte[] Result)
Reports asynchronously evaluated result back to a client.
static Task< int > PushEvent(string[] TabIDs, string Type, string Data, bool DataIsJson)
Puses an event to a set of Tabs, given their Tab IDs.
static string[] GetTabIDsForLocation(string Location, bool IgnoreCase, params KeyValuePair< string, string >[] QueryFilter)
Gets the Tab IDs of all tabs that display a particular resource.
const int TabIdCacheTimeoutSecondsHalf
Half of TabIdCacheTimeoutSeconds.
Definition: ClientEvents.cs:60
static string[] GetTabIDsForLocation(string Location, bool IgnoreCase, string QueryParameter1, string QueryParameterValue1, string QueryParameter2, string QueryParameterValue2)
Gets the Tab IDs of all tabs that display a particular resource.
static string[] GetTabIDsForLocations(params string[] Locations)
Gets the Tab IDs of all tabs that display a set of resources.
static string[] GetTabIDsForLocation(string Location, bool IgnoreCase, string QueryParameter1, string QueryParameterValue1)
Gets the Tab IDs of all tabs that display a particular resource.
static string[] GetTabIDsForUser(object User, bool IgnoreCase, string QueryParameter1, string QueryParameterValue1)
Gets the Tab IDs of all tabs that a specific user views.
ClientEvents()
Resource managing asynchronous events to web clients.
Definition: ClientEvents.cs:65
static string[] GetTabIDsForUser(object User)
Gets the Tab IDs of all tabs that a specific user views.
bool AllowsPOST
If the POST method is allowed.
Definition: ClientEvents.cs:88
static string[] GetTabIDs()
Gets all open Tab IDs.
static string[] GetTabIDsForUser(object User, string Location, bool IgnoreCase, params KeyValuePair< string, string >[] QueryFilter)
Gets the Tab IDs of all tabs that a specific user views.
static string[] GetTabIDsForUser(object User, string Location, params KeyValuePair< string, string >[] QueryFilter)
Gets the Tab IDs of all tabs that a specific user views.
static Task< int > PushEvent(string[] TabIDs, string Type, string Data)
Puses an event to a set of Tabs, given their Tab IDs.
static string[] GetTabIDsForLocation(string Location, string QueryParameter1, string QueryParameterValue1, string QueryParameter2, string QueryParameterValue2)
Gets the Tab IDs of all tabs that display a particular resource.
static string[] GetTabIDsForUser(object User, string Location, string QueryParameter1, string QueryParameterValue1, string QueryParameter2, string QueryParameterValue2)
Gets the Tab IDs of all tabs that a specific user views.
async Task POST(HttpRequest Request, HttpResponse Response)
Executes the POST method on the resource.
static string[] GetTabIDsForUser(object User, bool IgnoreCase, params KeyValuePair< string, string >[] QueryFilter)
Gets the Tab IDs of all tabs that a specific user views.
static string[] GetTabIDsForUsers()
Gets all open Tab IDs for logged in users.
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:147
static DateTime ScheduleEvent(Action< object > Callback, DateTime When, object State)
Schedules a one-time event.
Definition: Gateway.cs:4253
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
Base class for all asynchronous HTTP resources. An asynchronous resource responds outside of the meth...
Represents an HTTP request.
Definition: HttpRequest.cs:22
HttpRequestHeader Header
Request header.
Definition: HttpRequest.cs:182
bool HasData
If the request has data.
Definition: HttpRequest.cs:113
SessionVariables Session
Contains session states, if the resource requires sessions, or null otherwise.
Definition: HttpRequest.cs:212
string SubPath
Sub-path. If a resource is found handling the request, this property contains the trailing sub-path o...
Definition: HttpRequest.cs:194
async Task< ContentResponse > DecodeDataAsync()
Decodes data sent in request.
Definition: HttpRequest.cs:139
static string GetSessionId(HttpRequest Request, HttpResponse Response)
Gets the session ID used for a request.
static void SetTransparentCorsHeaders(HttpResource Resource, HttpRequest Request, HttpResponse Response)
Sets CORS headers for a resource, allowing it to be embedded in other sites.
const string HttpSessionID
The Cookie Key for HTTP Session Identifiers: "HttpSessionID"
Definition: HttpResource.cs:27
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:23
async Task DisposeAsync()
Closes the connection and disposes of all resources.
async Task SendResponse()
Sends the response back to the client. If the resource is synchronous, there's no need to call this m...
Task Write(byte[] Data)
Returns binary data in the response.
void SetHeader(string FieldName, string Value)
Sets a custom header field value.
The server encountered an unexpected condition which prevented it from fulfilling the request.
Collection of session variables.
override bool TryGetVariable(string Name, out Variable Variable)
Tries to get a variable object, given its name.
Class handling a web-socket.
Definition: WebSocket.cs:17
async Task< bool > Send(string Payload, int MaxFrameLength)
Sends a text payload, possibly in multiple frames.
Definition: WebSocket.cs:607
HttpResponse HttpResponse
Original HTTP response used when connection was upgrades to a WebSocket connection.
Definition: WebSocket.cs:72
HttpRequest HttpRequest
Original HTTP request made to upgrade the connection to a WebSocket connection.
Definition: WebSocket.cs:67
Generic object. Contains a sequence of properties.
Implements an in-memory cache.
Definition: Cache.cs:17
Event arguments for cache item removal events.
ValueType Value
Value of item that was removed.
RemovedReason Reason
Reason for removing the item.
Represents an object that allows single concurrent writers but multiple concurrent readers....
virtual async Task< bool > TryBeginWrite(int Timeout)
Waits, at most Timeout milliseconds, until object ready for writing. Each successful call to TryBegi...
Contains information about a variable.
Definition: Variable.cs:10
Interface for asynchronously disposable objects.
GET Interface for HTTP resources.
POST Interface for HTTP resources.
Basic interface for all types of elements.
Definition: IElement.cs:21
Basic interface for a user.
Definition: IUser.cs:7
RemovedReason
Reason for removing the item.
ContentType
DTLS Record content type.
Definition: Enumerations.cs:11