Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
JsonRpcWebService.cs
1using System;
3using System.Diagnostics.CodeAnalysis;
4using System.Numerics;
5using System.Reflection;
6using System.Text;
7using System.Threading.Tasks;
8using Waher.Content;
13using Waher.Events;
19using Waher.Script;
22
24{
33 {
34 private static readonly JsonCodec jsonCodec = new JsonCodec();
35
36 private readonly Dictionary<string, IJsonRpcClientRequest> requests = new Dictionary<string, IJsonRpcClientRequest>();
37 private readonly SortedDictionary<string, JsonRpcMethodInfo> methods;
38 private readonly bool userSessions;
39 private readonly bool caseSensitive;
40 private HttpAuthenticationScheme[]? authenticationSchemes = null;
41 private ProtectedResourceMetaData? resourceMetaData = null;
42 private ProtectedResourceMetaData? metaDataResource = null;
43 private JwtFactory? jwtFactory = null;
44 private string? domain = null;
45 private bool hasMetaDataResource = false;
46 private bool hasDomain = false;
47 private bool hasJwtFactory = false;
48
55 : this(ResourceName, UserSessions, true)
56 {
57 }
58
65 public JsonRpcWebService(string ResourceName, bool UserSessions, bool CaseSensitive)
66 : base(ResourceName)
67 {
68 this.userSessions = UserSessions;
69 this.caseSensitive = CaseSensitive;
70
71 if (CaseSensitive)
72 this.methods = new SortedDictionary<string, JsonRpcMethodInfo>(StringComparer.InvariantCulture);
73 else
74 this.methods = new SortedDictionary<string, JsonRpcMethodInfo>(StringComparer.InvariantCultureIgnoreCase);
75
76 foreach (MethodInfo Method in this.GetType().GetMethods(BindingFlags.Instance |
77 BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
78 {
79 if (!Method.IsDefined(typeof(JsonRpcMethodAttribute), true))
80 continue;
81
82 this.RegisterMethod(Method, GetRequiredPrivileges(Method));
83 }
84 }
85
92 public static string[]? GetRequiredPrivileges(MethodInfo Method)
93 {
94 ChunkedList<string>? RequiredPrivileges = null;
95
96 foreach (RequiredPrivilegeAttribute Attribute in
97 Method.GetCustomAttributes<RequiredPrivilegeAttribute>(true))
98 {
99 RequiredPrivileges ??= new ChunkedList<string>();
100 RequiredPrivileges.Add(Attribute.Privilege);
101 }
102
103 return RequiredPrivileges?.ToArray();
104 }
105
110 public override bool Synchronous => false;
111
115 public bool AllowsGET => true;
116
120 public bool AllowsPOST => true;
121
125 public override bool HandlesSubPaths => false;
126
130 public override bool UserSessions => this.userSessions;
131
135 public virtual bool SupportsServerSentEvents => false;
136
141 public virtual bool SendSseWelcomeMessage => false;
142
146 public virtual string SseWelcomeMessage => string.Empty;
147
151 public ProtectedResourceMetaData? MetaDataResource => this.metaDataResource;
152
156 public string? Domain => this.domain;
157
161 public bool HasMetaDataResource => this.hasMetaDataResource;
162
166 public bool HasDomain => this.hasDomain;
167
171 protected JwtFactory? JwtFactory => this.jwtFactory;
172
176 protected bool HasJwtFactory => this.hasJwtFactory;
177
181 public HttpAuthenticationScheme[]? AuthenticationSchemes => this.authenticationSchemes;
182
188 public void RegisterMethod(MethodInfo Method, params string[]? RequiredPrivileges)
189 {
190 JsonRpcMethodInfo MethodInfo;
191
192 lock (this.methods)
193 {
194 string Name = Method.Name;
195
196 if (this.methods.ContainsKey(Name))
197 throw new Exception("Method already registered: " + Name);
198
199 this.methods[Name] = MethodInfo = new JsonRpcMethodInfo(Method,
200 this.caseSensitive, RequiredPrivileges);
201 }
202
203 if (!(this.FirstServer is null))
204 this.AddAuthenticationMechanisms(MethodInfo);
205 }
206
212 public bool Unregister(MethodInfo Method)
213 {
214 lock (this.methods)
215 {
216 string Name = Method.Name;
217
218 if (this.methods.TryGetValue(Name, out JsonRpcMethodInfo Prev) &&
219 Prev.Method == Method)
220 {
221 return this.methods.Remove(Name);
222 }
223 else
224 return false;
225 }
226 }
227
235 [NotNullWhen(true)] out ProtectedResourceMetaData? Resource)
236 {
237 if (this.resourceMetaData is null)
238 {
240
241 if (Server.TryGetResource(ref s, out HttpResource HttpResource, out _) &&
243 {
244 this.resourceMetaData = MetaDataResource;
245 }
246 }
247
248 Resource = this.resourceMetaData;
249 return !(Resource is null);
250 }
251
256 public override void AddReference(HttpServer Server)
257 {
258 base.AddReference(Server);
259
260 this.hasMetaDataResource = this.TryGetResourceMetaDataResource(Server,
261 out this.metaDataResource);
262 this.hasDomain = Types.TryGetModuleParameter("Domain", out this.domain);
263
266 {
267 this.jwtFactory = JwtFactory;
268 this.hasJwtFactory = true;
269 }
270 else
271 {
272 this.jwtFactory = null;
273 this.hasJwtFactory = false;
274 }
275
276 JsonRpcMethodInfo[] Methods;
277 int c;
278
279 lock (this.methods)
280 {
281 c = this.methods.Count;
282 Methods = new JsonRpcMethodInfo[c];
283 this.methods.Values.CopyTo(Methods, 0);
284 }
285
286 foreach (JsonRpcMethodInfo MethodInfo in Methods)
287 this.AddAuthenticationMechanisms(MethodInfo);
288
289 if (this.HasMetaDataResource)
290 {
291 string ResourceMetaData = this.metaDataResource!.GetResourceMetaDataUri(
292 this.hasDomain, this.domain, this.ResourceName);
293
294 this.authenticationSchemes = HttpModule.GetAuthenticationSchemes(
295 new Uri(ResourceMetaData));
296 }
297 else
298 this.authenticationSchemes = HttpModule.GetAuthenticationSchemes();
299 }
300
306 {
307 if (Method.RequiresAuthentication)
308 {
309 if (this.hasMetaDataResource && this.hasDomain)
310 {
312 this.metaDataResource!.GetResourceMetaDataUri(true, this.domain, this.ResourceName));
313 }
314 else
316 }
317 }
318
319
325 public Task<int> SendEvent(IDictionary<string, object> Fields)
326 {
327 return this.SendEvent(All, null, Fields);
328 }
329
335 public Task<int> SendEvent(params KeyValuePair<string, object>[] Fields)
336 {
337 return this.SendEvent(All, null, Fields);
338 }
339
345 public Task<int> SendEvent(IEnumerable<KeyValuePair<string, object>> Fields)
346 {
347 return this.SendEvent(All, null, Fields);
348 }
349
356 public Task<int> SendEvent(string? Comment, IDictionary<string, object> Fields)
357 {
358 return this.SendEvent(All, Comment,
359 (IEnumerable<KeyValuePair<string, object>>)Fields);
360 }
361
368 public Task<int> SendEvent(string? Comment, params KeyValuePair<string, object>[] Fields)
369 {
370 return this.SendEvent(All, Comment, (IEnumerable<KeyValuePair<string, object>>)Fields);
371 }
372
379 public Task<int> SendEvent(string? Comment, IEnumerable<KeyValuePair<string, object>> Fields)
380 {
381 return this.SendEvent(All, Comment, Fields);
382 }
383
389 public static bool All(IJsonRpcSession? Session)
390 {
391 return true;
392 }
393
401 public Task<int> SendEvent(Predicate<IJsonRpcSession?> Filter,
402 IDictionary<string, object> Fields)
403 {
404 return this.SendEvent(Filter, null, Fields);
405 }
406
414 public Task<int> SendEvent(Predicate<IJsonRpcSession?> Filter,
415 params KeyValuePair<string, object>[] Fields)
416 {
417 return this.SendEvent(Filter, null, Fields);
418 }
419
427 public Task<int> SendEvent(Predicate<IJsonRpcSession?> Filter,
428 IEnumerable<KeyValuePair<string, object>> Fields)
429 {
430 return this.SendEvent(Filter, null, Fields);
431 }
432
441 public Task<int> SendEvent(Predicate<IJsonRpcSession?> Filter, string? Comment,
442 IDictionary<string, object> Fields)
443 {
444 return this.SendEvent(Filter, Comment,
445 (IEnumerable<KeyValuePair<string, object>>)Fields);
446 }
447
456 public Task<int> SendEvent(Predicate<IJsonRpcSession?> Filter, string? Comment,
457 params KeyValuePair<string, object>[] Fields)
458 {
459 return this.SendEvent(Filter, Comment, (IEnumerable<KeyValuePair<string, object>>)Fields);
460 }
461
470 public Task<int> SendEvent(Predicate<IJsonRpcSession?> Filter, string? Comment,
471 IEnumerable<KeyValuePair<string, object>> Fields)
472 {
473 if (!this.SupportsServerSentEvents)
474 throw new InvalidOperationException("Server-Sent Events (SSE) not supported by this resource.");
475
476 StringBuilder sb = new StringBuilder();
477 bool Empty = true;
478
479 if (!string.IsNullOrEmpty(Comment))
480 {
481 Empty = false;
482 sb.Append(Comment);
483 if (Comment.IndexOfAny(CommonTypes.CRLF) >= 0)
484 {
485 foreach (string Line in Comment.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n'))
486 {
487 sb.Append(": ");
488 sb.Append(Line);
489 sb.Append("\r\n");
490 }
491 }
492 else
493 {
494 sb.Append(": ");
495 sb.Append(Comment);
496 sb.Append("\r\n");
497 }
498 }
499
500 if (!(Fields is null))
501 {
502 foreach (KeyValuePair<string, object> P in Fields)
503 {
504 Empty = false;
505
506 if (!(P.Value is string s))
507 s = JSON.Encode(P.Value, false);
508
509 if (s.IndexOfAny(CommonTypes.CRLF) >= 0)
510 {
511 foreach (string Line in s.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n'))
512 {
513 sb.Append(P.Key);
514 sb.Append(": ");
515 sb.Append(Line);
516 sb.Append("\r\n");
517 }
518 }
519 else
520 {
521 sb.Append(P.Key);
522 sb.Append(": ");
523 sb.Append(s);
524 sb.Append("\r\n");
525 }
526 }
527 }
528
529 if (Empty)
530 sb.Append(":\r\n");
531
532 sb.Append("\r\n");
533
534 return this.SendEvent(Filter, sb.ToString());
535 }
536
537 private async Task<int> SendEvent(Predicate<IJsonRpcSession?> Filter, string Event)
538 {
539 int Count = 0;
540
541 foreach (Subscription Subscription in this.eventSubscriptionsStatic)
542 {
543 try
544 {
545 if (Filter(Subscription.Session))
546 {
547 await Subscription.Response.Write(Event);
548 await Subscription.Response.Flush(false);
549 Count++;
550 }
551 }
552 catch (Exception)
553 {
554 lock (this.eventSubscriptions)
555 {
556 this.eventSubscriptions.Remove(Subscription);
557 this.eventSubscriptionsStatic = this.eventSubscriptions.ToArray();
558 }
559 }
560 }
561
562 return Count;
563 }
564
565 private readonly ChunkedList<Subscription> eventSubscriptions = new ChunkedList<Subscription>();
566 private Subscription[] eventSubscriptionsStatic = Array.Empty<Subscription>();
567 private bool eventSubscriptionsKeepAliveRunning = false;
568
569 private class Subscription
570 {
571 public readonly HttpResponse Response;
572 public readonly IJsonRpcSession? Session;
573
574 public Subscription(HttpResponse Response, IJsonRpcSession? Session)
575 {
576 this.Response = Response;
577 this.Session = Session;
578 }
579 }
580
581 private async void KeepEventSubscrptionsAlive()
582 {
583 try
584 {
585 do
586 {
587 await Task.Delay(15000); // Keep alive every 15 seconds.
588 }
589 while (await this.SendEvent(string.Empty) > 0);
590 }
591 catch (Exception ex)
592 {
593 Log.Exception(ex);
594 }
595 finally
596 {
597 this.eventSubscriptionsKeepAliveRunning = false;
598 }
599 }
600
611 public JsonRpcClientRequest<T> CreateRequest<T>(string Message, string Method,
612 object? Parameters, IJsonRpcSession Session, Func<object?, Task<T>> ParseResult,
614 {
616 string Id;
617
618 lock (this.requests)
619 {
620 do
621 {
623 }
624 while (this.requests.ContainsKey(Id));
625
626 Request = new JsonRpcClientRequest<T>(Message, Id, Method, Parameters,
627 Session, ParseResult, this, HttpRequest);
628
629 this.requests[Id] = Request;
630 }
631
632 return Request;
633 }
634
641 public bool TryGetRequest(string Id,
642 [NotNullWhen(true)] out IJsonRpcClientRequest? Request)
643 {
644 lock (this.requests)
645 {
646 return this.requests.TryGetValue(Id, out Request);
647 }
648 }
649
655 internal bool RemoveClientRequest(string Id)
656 {
657 lock (this.requests)
658 {
659 return this.requests.Remove(Id);
660 }
661 }
662
668 internal IJsonRpcClientRequest? PopClientRequest(string Id)
669 {
670 lock (this.requests)
671 {
672 if (this.requests.TryGetValue(Id, out IJsonRpcClientRequest? Request))
673 {
674 this.requests.Remove(Id);
675 return Request;
676 }
677 else
678 return null;
679 }
680 }
681
688 public virtual async Task GET(HttpRequest Request, HttpResponse Response)
689 {
691 {
692 await this.GenerateDocumentation(Request, Response);
693 return;
694 }
695 else if (Request.Header.IsAcceptable("text/event-stream"))
696 {
697 if (!this.SupportsServerSentEvents)
698 {
699 await Response.SendResponse(new NotAcceptableException("Server-Sent Events (SSE) not supported by this resource."));
700 return;
701 }
702
703 IJsonRpcSession? Session = await this.TryGetSession(Request, Response);
704 if (Response.ResponseSent)
705 return;
706
707 Response.StatusCode = 200;
708 Response.StatusMessage = "OK";
709 Response.ContentType = "text/event-stream";
710 Response.EnableDirectTransfer();
711
712 if (this.SendSseWelcomeMessage)
713 {
714 string s = this.SseWelcomeMessage;
715
716 if (string.IsNullOrEmpty(s))
717 await Response.Write(":\r\n");
718 else
719 {
720 StringBuilder sb = new StringBuilder();
721
722 foreach (string Line in s.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n'))
723 {
724 sb.Append(": ");
725 sb.Append(Line);
726 sb.Append("\r\n");
727 }
728
729 sb.Append("\r\n");
730
731 await Response.Write(sb.ToString());
732 }
733 }
734 else
735 await Response.Write(":\r\n");
736
737 await Response.Flush(false);
738
739 lock (this.eventSubscriptions)
740 {
741 foreach (Subscription Subscription in this.eventSubscriptions)
742 {
743 if (!(Subscription.Session is null) &&
744 !(Session is null) &&
745 Subscription.Session.SessionId == Session.SessionId)
746 {
747 this.eventSubscriptions.Remove(Subscription);
748 break;
749 }
750 }
751
752 this.eventSubscriptions.Add(new Subscription(Response, Session));
753 this.eventSubscriptionsStatic = this.eventSubscriptions.ToArray();
754
755 if (!this.eventSubscriptionsKeepAliveRunning)
756 {
757 this.eventSubscriptionsKeepAliveRunning = true;
758 this.KeepEventSubscrptionsAlive();
759 }
760 }
761
762 return;
763 }
764
765 using JsonRpcServerRequest JsonRpcRequest = new JsonRpcServerRequest();
766
767 if (!(Request.Header.QueryParameters is null))
768 {
769 foreach (KeyValuePair<string, string> P in Request.Header.QueryParameters)
770 {
771 string s = P.Value;
772 object? Value;
773
774 if (P.Key == "params")
775 {
776 try
777 {
778 Value = JSON.Parse(s);
779 }
780 catch (Exception ex)
781 {
782 JsonRpcRequest.SetError(-32700, "Unable to parse parameter: " +
783 P.Key + ": " + Log.UnnestException(ex).Message,
785 continue;
786 }
787 }
788 else
789 Value = P.Value;
790
791 this.ProcessQueryParameter(JsonRpcRequest, P.Key, Value);
792 }
793 }
794
795 if (!await JsonRpcRequest.BuildResponse(this, Request, Response))
796 await this.SendResponse(Request, JsonRpcRequest, Response);
797 }
798
804 protected virtual async Task GenerateDocumentation(HttpRequest Request, HttpResponse Response)
805 {
806 StringBuilder Markdown = new StringBuilder();
808 HashSet<Type> TypesToDocument = new HashSet<Type>();
809
810 await this.GenerateDocumentationHeader(Request, Markdown);
811 await this.GenerateDocumentationIntroduction(Request, Markdown);
812 await this.GenerateDocumentationApiDescription(Notes, TypesToDocument, Request, Markdown);
813 await this.GenerateTypeDocumentation(Notes, TypesToDocument, Request, Markdown);
814
815 int i = 0;
816
817 foreach (string Note in Notes)
818 {
819 Markdown.Append("[^n");
820 Markdown.Append(i++);
821 Markdown.Append("]: ");
822 Markdown.AppendLine(Note);
823 Markdown.AppendLine();
824 }
825
826 MarkdownSettings Settings = new MarkdownSettings(null, true, new Variables());
827 MarkdownDocument Doc = await MarkdownDocument.CreateAsync(Markdown.ToString(), Settings);
828 string Html = await Doc.GenerateHTML();
829
830 await Response.Return(new HtmlDocument(Html));
831 }
832
838 protected virtual Task GenerateDocumentationHeader(HttpRequest Request, StringBuilder Markdown)
839 {
840 Markdown.Append("Title: ");
841 Markdown.AppendLine(this.Title);
842 Markdown.Append("Description: ");
843 Markdown.AppendLine(this.ShortDescription);
844
845 if (Types.TryGetModuleParameter<OAuth2Environment>("OAUTH2", out OAuth2Environment? Environment) &&
846 Environment.HasLoginMasterFileName)
847 {
848 Markdown.Append("Master: ");
849 Markdown.AppendLine(Environment.LoginMasterFileName);
850 }
851
852 Markdown.Append("Date: ");
853 Markdown.AppendLine(CommonTypes.EncodeRfc822(DateTime.UtcNow));
854 Markdown.AppendLine();
855 Markdown.AppendLine(new string('=', 40));
856 Markdown.AppendLine();
857
858 return Task.CompletedTask;
859 }
860
866 protected virtual Task GenerateDocumentationIntroduction(HttpRequest Request, StringBuilder Markdown)
867 {
868 Markdown.AppendLine(new string('=', 80));
869 Markdown.AppendLine();
870 Markdown.AppendLine(this.Title);
871 Markdown.AppendLine("========");
872 Markdown.AppendLine();
873 Markdown.AppendLine(this.MarkdownDescription);
874 Markdown.AppendLine();
875 Markdown.AppendLine("![Table of Contents](ToC)");
876 Markdown.AppendLine();
877
878 return Task.CompletedTask;
879 }
880
889 ChunkedList<string> Notes, HashSet<Type> TypesToDocument,
890 HttpRequest Request, StringBuilder Markdown)
891 {
892 Markdown.AppendLine(new string('=', 80));
893 Markdown.AppendLine();
894 Markdown.AppendLine("JSON-RPC Interface");
895 Markdown.AppendLine("---------------------");
896 Markdown.AppendLine();
897 Markdown.Append("This [JSON-RPC](https://www.jsonrpc.org/specification) ");
898 Markdown.AppendLine("Web Service is accessible on this endpoint: `");
899 Markdown.Append(Request.Header.GetURL(false, false));
900 Markdown.AppendLine("`");
901 Markdown.AppendLine();
902
903 Markdown.Append("The following subsections list JSON-RPC methods that are ");
904 Markdown.AppendLine("available on this resource.");
905 Markdown.AppendLine();
906
907 JsonRpcMethodInfo[] Methods;
908
909 lock (this.methods)
910 {
911 Methods = new JsonRpcMethodInfo[this.methods.Count];
912 this.methods.Values.CopyTo(Methods, 0);
913 }
914
915 foreach (JsonRpcMethodInfo Method in Methods)
916 {
917 Markdown.AppendLine("<section>");
918 Markdown.AppendLine();
919
920 Markdown.Append("### ");
921 this.AppendDocumentation(Notes, TypesToDocument, Method, Markdown);
922 }
923
924 return Task.CompletedTask;
925 }
926
935 protected virtual Task GenerateTypeDocumentation(ChunkedList<string> Notes,
936 HashSet<Type> TypesToDocument, HttpRequest Request, StringBuilder Markdown)
937 {
938 if (TypesToDocument.Count == 0)
939 return Task.CompletedTask;
940
941 Markdown.AppendLine(new string('=', 80));
942 Markdown.AppendLine();
943 Markdown.AppendLine("Types");
944 Markdown.AppendLine("--------");
945 Markdown.AppendLine();
946 Markdown.Append("This Web Service encodes named types as JSON dictionary ");
947 Markdown.Append("objects. The following subsections list the named typed ");
948 Markdown.AppendLine("and their corresponding properties.");
949 Markdown.AppendLine();
950
951 HashSet<Type> TypesToDocument2 = TypesToDocument;
952 TypesToDocument = new HashSet<Type>();
953
954 while (TypesToDocument2.Count > 0)
955 {
956 foreach (Type T in TypesToDocument2)
957 {
958 Markdown.AppendLine("<section>");
959 Markdown.AppendLine();
960 Markdown.Append("### `");
961 Markdown.Append(T.Name);
962 Markdown.AppendLine("`");
963 Markdown.AppendLine();
964
965 Markdown.AppendLine("| >>Properties<< |||");
966 Markdown.AppendLine("| Name | Type | Description |");
967 Markdown.AppendLine("|:-----|:-----|:------------|");
968
969 foreach (MemberInfo Member in T.GetMembers(BindingFlags.Instance | BindingFlags.Public))
970 {
971 Type MemberType;
972
973 if (Member is PropertyInfo Property)
974 MemberType = Property.PropertyType;
975 else if (Member is FieldInfo Field)
976 MemberType = Field.FieldType;
977 else
978 continue;
979
980 Markdown.Append("| `");
981 Markdown.Append(Member.Name);
982 Markdown.Append("` | ");
983 AppendType(MemberType, Markdown, TypesToDocument);
984 Markdown.Append(" | ");
985 AppendCell(Notes, this.GetMemberDocumentation(Member), Markdown);
986 Markdown.AppendLine(" |");
987 }
988
989 Markdown.AppendLine();
990 Markdown.AppendLine("</section>");
991 Markdown.AppendLine();
992 }
993
994 TypesToDocument2 = TypesToDocument;
995 TypesToDocument = new HashSet<Type>();
996 }
997
998 return Task.CompletedTask;
999 }
1000
1008 protected virtual void AppendDocumentation(ChunkedList<string> Notes,
1009 HashSet<Type> TypesToDocument, ProtectedMethod Method, StringBuilder Markdown)
1010 {
1011 Markdown.Append("`");
1012 Markdown.Append(Method.Name);
1013 Markdown.Append('(');
1014
1015 bool First = true;
1016 int NrArguments = 0;
1017
1018 foreach (ProtectedMethodArgumentInfo Parameter in Method.Arguments)
1019 {
1020 if (Parameter.IsSpecialArgument)
1021 continue;
1022
1023 if (First)
1024 First = false;
1025 else
1026 Markdown.Append(", ");
1027
1028 Markdown.Append(Parameter.Parameter.Name);
1029 NrArguments++;
1030 }
1031
1032 Markdown.AppendLine(")`");
1033 Markdown.AppendLine();
1034
1035 AppendDocumentation(Method.Documentation, Markdown);
1036
1037 Markdown.AppendLine("| >>Authentication<< ||");
1038 Markdown.AppendLine("|:-------|:-------|");
1039 Markdown.Append("| Required: | ");
1040 Markdown.Append(YesNo(Method.RequiresAuthentication));
1041 Markdown.AppendLine(" |");
1042
1043 if (Method.RequiresAuthentication && Method.RequiredPrivileges.Length > 0)
1044 {
1045 Markdown.Append("| Privileges Required: | ");
1046 First = true;
1047
1048 foreach (string Privilege in Method.RequiredPrivileges)
1049 {
1050 if (First)
1051 First = false;
1052 else
1053 Markdown.Append(", ");
1054
1055 Markdown.Append('`');
1056 Markdown.Append(Privilege);
1057 Markdown.Append('`');
1058 }
1059
1060 Markdown.AppendLine(" |");
1061 }
1062
1064 if ((Schemes?.Length ?? 0) > 0)
1065 {
1066 Markdown.Append("| Authentication Mechanisms: | ");
1067 First = true;
1068
1069 foreach (HttpAuthenticationScheme Mechanism in Schemes!)
1070 {
1071 if (First)
1072 First = false;
1073 else
1074 Markdown.Append(", ");
1075
1076 Markdown.Append(Mechanism.DisplayName);
1077 }
1078
1079 Markdown.AppendLine(" |");
1080 }
1081
1082 Markdown.AppendLine();
1083
1084 if (NrArguments > 0)
1085 {
1086 Markdown.AppendLine("| >>Arguments<< |||||");
1087 Markdown.AppendLine("| Name | Type | Use | Default Value | Description |");
1088 Markdown.AppendLine("|:-----|:-----|:---:|:-------------:|:------------|");
1089
1090 foreach (ProtectedMethodArgumentInfo Parameter in Method.Arguments)
1091 {
1092 if (Parameter.IsSpecialArgument)
1093 continue;
1094
1095 Markdown.Append("| `");
1096 Markdown.Append(Parameter.Parameter.Name);
1097 Markdown.Append("` | ");
1098 AppendType(Parameter.Parameter.ParameterType, Markdown, TypesToDocument);
1099 Markdown.Append(" | ");
1100
1101 if (Parameter.HasDefaultValue)
1102 {
1103 Markdown.Append("Optional | ");
1104 AppendValue(Parameter.DefaultValue, Markdown);
1105 }
1106 else
1107 Markdown.Append("Required | -");
1108
1109 Markdown.Append(" | ");
1110 AppendCell(Notes, this.GetParameterDocumentation(Parameter), Markdown);
1111 Markdown.AppendLine(" |");
1112 }
1113
1114 Markdown.AppendLine();
1115 }
1116
1117 if (Method.HasReturnValue)
1118 {
1119 Markdown.AppendLine("| >>Return Value<< ||");
1120 Markdown.AppendLine("| Type | Description |");
1121 Markdown.AppendLine("|:-----|:------------|");
1122
1123 Markdown.Append("| ");
1124 AppendType(Method.Method.ReturnType, Markdown, TypesToDocument);
1125 Markdown.Append(" | ");
1126 AppendCell(Notes, this.GetMemberDocumentation(Method.Method.ReturnParameter), Markdown);
1127 Markdown.AppendLine(" |");
1128 Markdown.AppendLine();
1129 }
1130
1131 Markdown.AppendLine("</section>");
1132 Markdown.AppendLine();
1133 }
1134
1140 protected static string YesNo(bool Value)
1141 {
1142 return Value ? "Yes" : "No";
1143 }
1144
1150 protected virtual KeyValuePair<bool, string>[] GetParameterDocumentation(
1152 {
1153 return Parameter.Documentation.Join(Parameter.AdditionalDocumentation);
1154 }
1155
1160 protected virtual KeyValuePair<bool, string>[] GetMemberDocumentation(
1161 ICustomAttributeProvider Member)
1162 {
1163 ChunkedList<KeyValuePair<bool, string>>? PropertyDoc = null;
1164
1165 foreach (object Attribute in
1166 Member.GetCustomAttributes(typeof(JsonRpcDocumentationAttribute), true))
1167 {
1168 if (Attribute is JsonRpcDocumentationAttribute TypedAttribute)
1169 {
1170 PropertyDoc ??= new ChunkedList<KeyValuePair<bool, string>>();
1171 PropertyDoc.Add(new KeyValuePair<bool, string>(
1172 TypedAttribute.IsMarkdown, TypedAttribute.Documentation));
1173 }
1174 }
1175
1176 return PropertyDoc?.ToArray() ?? Array.Empty<KeyValuePair<bool, string>>();
1177 }
1178
1184 protected static void AppendDocumentation(KeyValuePair<bool, string>[] Documentation,
1185 StringBuilder Markdown)
1186 {
1187 foreach (KeyValuePair<bool, string> P in Documentation)
1188 {
1189 Markdown.AppendLine();
1190
1191 if (P.Key)
1192 Markdown.AppendLine(P.Value);
1193 else
1194 Markdown.AppendLine(MarkdownDocument.Encode(P.Value));
1195
1196 Markdown.AppendLine();
1197 }
1198 }
1199
1206 protected static void AppendCell(ChunkedList<string> Notes,
1207 KeyValuePair<bool, string>[] Documentation, StringBuilder Markdown)
1208 {
1209 if (IsOneRow(Documentation))
1210 {
1211 foreach (KeyValuePair<bool, string> P in Documentation)
1212 {
1213 if (P.Key)
1214 Markdown.Append(P.Value);
1215 else
1216 Markdown.Append(MarkdownDocument.Encode(P.Value));
1217 }
1218 }
1219 else
1220 {
1221 StringBuilder sb = new StringBuilder();
1222 bool First = true;
1223
1224 foreach (KeyValuePair<bool, string> P in Documentation)
1225 {
1226 foreach (string Row in P.Value.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n'))
1227 {
1228 if (First)
1229 First = false;
1230 else
1231 {
1232 sb.AppendLine();
1233 sb.Append('\t');
1234 }
1235
1236 if (P.Key)
1237 sb.Append(Row);
1238 else
1239 sb.Append(MarkdownDocument.Encode(Row));
1240 }
1241 }
1242
1243 Markdown.Append("[^n");
1244 Markdown.Append(Notes.Count);
1245 Markdown.Append(']');
1246
1247 Notes.Add(sb.ToString());
1248 }
1249 }
1250
1256 protected static void AppendValue(object? Value, StringBuilder Markdown)
1257 {
1258 Markdown.Append('`');
1259
1260 if (Value is null)
1261 Markdown.Append("null");
1262 else if (Value.GetType().IsEnum)
1263 {
1264 Markdown.Append('"');
1265 Markdown.Append(Value.ToString());
1266 Markdown.Append('"');
1267 }
1268 else if (Value is bool b)
1269 Markdown.Append(CommonTypes.Encode(b));
1270 else
1271 Markdown.Append(Expression.ToExpressionString(Value));
1272
1273 Markdown.Append('`');
1274 }
1275
1282 protected static void AppendType(Type Type, StringBuilder Markdown,
1283 HashSet<Type> TypesToDocument)
1284 {
1285 if (Type.IsGenericType)
1286 {
1287 Type GenericType = Type.GetGenericTypeDefinition();
1288 Type[] TypeArguments;
1289
1290 if (GenericType == typeof(Task<>))
1291 {
1292 TypeArguments = Type.GetGenericArguments();
1293 if (TypeArguments.Length == 1)
1294 Type = TypeArguments[0];
1295 }
1296 else if (GenericType == typeof(Nullable<>))
1297 {
1298 TypeArguments = Type.GetGenericArguments();
1299 if (TypeArguments.Length == 1)
1300 {
1301 Markdown.Append("Nullable ");
1302 Type = TypeArguments[0];
1303 }
1304 }
1305 }
1306
1307 while (Type.IsArray && Type != typeof(byte[]))
1308 {
1309 Markdown.Append("Array of ");
1310 Type = Type.GetElementType();
1311 }
1312
1313 if (Type == typeof(byte[]))
1314 Markdown.Append("BASE64-encoded binary");
1315 if (Type == typeof(Dictionary<string, object>))
1316 Markdown.Append("Dictionary");
1317 else if (Type == typeof(string))
1318 Markdown.Append("String");
1319 else if (Type == typeof(object))
1320 Markdown.Append("Object");
1321 else if (Type == typeof(Uri))
1322 Markdown.Append("URI");
1323 else if (Type == typeof(bool))
1324 Markdown.Append("Boolean");
1325 else if (Type == typeof(int))
1326 Markdown.Append("32-bit signed integer");
1327 else if (Type == typeof(long))
1328 Markdown.Append("64-bit signed integer");
1329 else if (Type == typeof(short))
1330 Markdown.Append("16-bit signed integer");
1331 else if (Type == typeof(sbyte))
1332 Markdown.Append("8-bit signed integer");
1333 else if (Type == typeof(uint))
1334 Markdown.Append("32-bit unsigned integer");
1335 else if (Type == typeof(ulong))
1336 Markdown.Append("64-bit unsigned integer");
1337 else if (Type == typeof(ushort))
1338 Markdown.Append("16-bit unsigned integer");
1339 else if (Type == typeof(byte))
1340 Markdown.Append("Byte");
1341 else if (Type == typeof(char))
1342 Markdown.Append("Character");
1343 else if (Type == typeof(double))
1344 Markdown.Append("Double-precision floating-point");
1345 else if (Type == typeof(float))
1346 Markdown.Append("Single-precision floating-point");
1347 else if (Type == typeof(decimal))
1348 Markdown.Append("Decimal-precision floating-point");
1349 else if (Type == typeof(BigInteger))
1350 Markdown.Append("Big Integer");
1351 else if (Type == typeof(DateTime))
1352 Markdown.Append("Date & Time");
1353 else if (Type == typeof(DateTimeOffset))
1354 Markdown.Append("Date & Time & Time Zone");
1355 else if (Type == typeof(TimeSpan))
1356 Markdown.Append("Time span");
1357 else if (Type == typeof(CustomEncoding))
1358 Markdown.Append("Custom Encoding");
1359 else if (Expression.IsVoid(Type))
1360 Markdown.Append("`void`");
1361 else if (Type.IsEnum)
1362 {
1363 Markdown.Append("`");
1364 Markdown.Append(Type.Name);
1365 Markdown.Append('`');
1366 }
1367 else
1368 {
1369 Markdown.Append("[`");
1370 Markdown.Append(Type.Name);
1371 Markdown.Append("`](#");
1372 Markdown.Append(Type.Name[..1].ToLowerInvariant());
1373 Markdown.Append(Type.Name[1..]);
1374 Markdown.Append(')');
1375
1376 TypesToDocument.Add(Type);
1377 }
1378 }
1379
1385 protected static bool IsOneRow(KeyValuePair<bool, string>[] Documentation)
1386 {
1387 if (Documentation.Length > 1)
1388 return false;
1389
1390 if (Documentation.Length == 0)
1391 return true;
1392
1393 if (Documentation[0].Value.IndexOfAny(CommonTypes.CRLF) >= 0)
1394 return false;
1395
1396 return true;
1397 }
1398
1402 public abstract string Title { get; }
1403
1407 public virtual string ShortDescription
1408 {
1409 get
1410 {
1411 OAuthResourceNameAttribute? Attribute = this.GetType().GetCustomAttribute<OAuthResourceNameAttribute>();
1412 return Attribute?.ResourceName ?? "JSON-RPC Web Service: " + this.ResourceName;
1413 }
1414 }
1415
1419 public abstract string MarkdownDescription { get; }
1420
1425 protected bool Unregister(IJsonRpcSession? Session)
1426 {
1427 if (Session is null)
1428 return false;
1429
1430 lock (this.eventSubscriptions)
1431 {
1432 foreach (Subscription Subscription in this.eventSubscriptions)
1433 {
1434 if (Subscription.Session == Session)
1435 {
1436 this.eventSubscriptions.Remove(Subscription);
1437 this.eventSubscriptionsStatic = this.eventSubscriptions.ToArray();
1438 return true;
1439 }
1440 }
1441 }
1442
1443 return false;
1444 }
1445
1452 protected virtual Task<IJsonRpcSession?> TryGetSession(HttpRequest Request, HttpResponse Response)
1453 {
1454 return Task.FromResult<IJsonRpcSession?>(null);
1455 }
1456
1463 public virtual async Task POST(HttpRequest Request, HttpResponse Response)
1464 {
1465 using JsonRpcServerRequest JsonRpcRequest = new JsonRpcServerRequest();
1466
1467 if (!Request.HasData)
1468 {
1469 JsonRpcRequest.SetError(-32600, "No payload.",
1471 }
1472 else
1473 {
1474 ContentResponse RequestData = await Request.DecodeDataAsync();
1475
1476 if (RequestData.HasError)
1477 {
1478 JsonRpcRequest.SetError(-32700, "Unable to parse payload.",
1480 }
1481 else if (RequestData.Decoded is Dictionary<string, object> RequestObj)
1482 {
1483 foreach (KeyValuePair<string, object> P in RequestObj)
1484 this.ProcessQueryParameter(JsonRpcRequest, P.Key, P.Value);
1485 }
1486 else if (RequestData.Decoded is Array Requests)
1487 {
1488 int i, c = Requests.Length;
1489
1490 if (c == 0)
1491 {
1492 JsonRpcRequest.SetError(-32600, "Empty request.",
1494 }
1495 else
1496 {
1497 JsonRpcRequest.BatchRequests = new JsonRpcServerRequest[c];
1498
1499 for (i = 0; i < c; i++)
1500 {
1501 JsonRpcServerRequest ItemRequest = new JsonRpcServerRequest();
1502 JsonRpcRequest.BatchRequests[i] = ItemRequest;
1503
1504 if (Requests.GetValue(i) is Dictionary<string, object> ItemRequestObj)
1505 {
1506 foreach (KeyValuePair<string, object> P in ItemRequestObj)
1507 this.ProcessQueryParameter(ItemRequest, P.Key, P.Value);
1508 }
1509 else
1510 {
1511 ItemRequest.SetError(-32600, "Expected JSON object or array of JSON objects in request.",
1513 }
1514 }
1515 }
1516 }
1517 else
1518 {
1519 JsonRpcRequest.SetError(-32600, "Expected JSON object or array of JSON objects in request.",
1521 }
1522 }
1523
1524 if (!await JsonRpcRequest.BuildResponse(this, Request, Response))
1525 await this.SendResponse(Request, JsonRpcRequest, Response);
1526 }
1527
1528 private async Task SendResponse(HttpRequest HttpRequest,
1529 JsonRpcServerRequest JsonRequest, HttpResponse Response)
1530 {
1531 if (JsonRequest.StatusCode == 204)
1532 {
1533 Response.StatusCode = JsonRequest.StatusCode;
1534 Response.StatusMessage = JsonRequest.StatusMessage;
1535 }
1536 else if (JsonRequest.IsResult)
1537 {
1538 Response.StatusCode = 200;
1539 Response.StatusMessage = "OK";
1540 }
1541 else
1542 {
1543 ContentResponse Encoded;
1544
1546 {
1547 Encoded = await jsonCodec.EncodeAsync(JsonRequest.Response,
1548 Encoding.UTF8, null, JsonCodec.JsonRpcContentType);
1549 }
1550 else
1551 {
1552 string ContentType = HttpRequest.Header.Accept.GetBestAlternative(JsonCodec.JsonContentTypes);
1553
1554 Encoded = await jsonCodec.EncodeAsync(JsonRequest.Response,
1555 Encoding.UTF8, null, ContentType);
1556 }
1557
1558 if (Encoded.HasError)
1559 {
1560 await Response.SendResponse(Encoded.Error);
1561 return;
1562 }
1563 else
1564 {
1565 Response.StatusCode = JsonRequest.StatusCode;
1566 Response.StatusMessage = JsonRequest.StatusMessage;
1567
1568 if (JsonRequest.StatusCode != 204)
1569 {
1570 Response.ContentType = Encoded.ContentType;
1571
1572 await Response.Write(true, Encoded.Encoded, 0, Encoded.Encoded.Length);
1573 }
1574 }
1575 }
1576
1577 await Response.SendResponse();
1578 }
1579
1580 private void ProcessQueryParameter(JsonRpcServerRequest Request, string Key, object Value)
1581 {
1582 switch (Key)
1583 {
1584 case "jsonrpc":
1585 Request.JsonVersion = Value?.ToString() ?? string.Empty;
1586 break;
1587
1588 case "method":
1589 if (Value is string Method)
1590 {
1591 lock (this.methods)
1592 {
1593 if (!this.methods.TryGetValue(Method.Replace('/', '_'), out Request.MethodInfo))
1594 Request.SetError(-32601, "Method not found: " + Method,
1596 }
1597 }
1598 else
1599 {
1600 Request.SetError(-32600, "Invalid method name.",
1602 }
1603 break;
1604
1605 case "result":
1606 Request.IsResult = true;
1607 Request.Result = Value;
1608 Request.StatusCode = 202;
1609 Request.StatusMessage = "Accepted";
1610 break;
1611
1612 case "params":
1613 if (Value is Dictionary<string, object?> Obj)
1614 Request.ParametersObj = Obj;
1615 else if (Value is Array A)
1616 Request.ParametersArray = A;
1617 else
1618 {
1619 Request.SetError(-32600, "Invalid parameters.",
1621 }
1622 break;
1623
1624 case "id":
1625 Request.Id = Value;
1626 break;
1627
1628 case "error":
1629 Request.IsError = true;
1630
1631 if (Value is Dictionary<string, object> Error &&
1632 Error.TryGetValue("code", out object Obj2) &&
1633 Obj2 is int ErrorCode &&
1634 Error.TryGetValue("message", out Obj2) &&
1635 Obj2 is string ErrorMessage)
1636 {
1637 Request.SetError(ErrorCode, ErrorMessage,
1640 }
1641 else
1642 {
1643 Request.SetError(-32600, Value.ToString(),
1646 }
1647 break;
1648
1649 default:
1650 Request.SetError(-32600, "Unexpected request received: Unknown property: " + Key,
1652 break;
1653 }
1654 }
1655
1656 }
1657}
A custom encoded object.
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static string Encode(bool x)
Encodes a Boolean for use in XML and other formats.
Definition: CommonTypes.cs:596
static readonly char[] CRLF
Contains the CR LF character sequence.
Definition: CommonTypes.cs:19
static string EncodeRfc822(DateTime Timestamp)
Encodes a date and time, according to RFC 822 §5.
Definition: CommonTypes.cs:669
Contains information about a response to a content request.
byte[] Encoded
Encoded object.
string ContentType
Internet Content-Type of encoded object.
bool HasError
If an error occurred.
object Decoded
Decoded object.
Exception Error
Error response.
Inline comment found in the document.
Definition: Comment.cs:11
HTML encoder/decoder.
Definition: HtmlCodec.cs:15
const string DefaultContentType
Default Content-Type for HTML: text/html
Definition: HtmlCodec.cs:26
Helps with common JSON-related tasks.
Definition: JSON.cs:16
static object Parse(string Json)
Parses a JSON string.
Definition: JSON.cs:45
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:537
static readonly string[] JsonContentTypes
JSON content types.
Definition: JsonCodec.cs:42
const string JsonRpcContentType
application/json-rpc
Definition: JsonCodec.cs:25
Contains a markdown document. This markdown document class supports original markdown,...
static string Encode(string s)
Encodes all special characters in a string so that it can be included in a markdown document without ...
async Task< string > GenerateHTML()
Generates HTML from the markdown text.
static Task< MarkdownDocument > CreateAsync(string MarkdownText, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
Contains settings that the Markdown parser uses to customize its behavior.
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
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:828
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
Base class for all HTTP authentication schemes, as defined in RFC-7235: https://datatracker....
abstract string DisplayName
Display name for authentication scheme.
bool IsAcceptable(string Alternative)
Checks if an alternative is acceptable to the client sending a request.
KeyValuePair< string, string >[] QueryParameters
All query parameters.
HttpFieldAccept Accept
Accept HTTP Field header. (RFC 2616, §14.1)
string GetURL()
Gets an absolute URL for the request.
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
async Task< ContentResponse > DecodeDataAsync()
Decodes data sent in request.
Definition: HttpRequest.cs:139
Base class for all HTTP resources.
Definition: HttpResource.cs:23
HttpServer FirstServer
First server on which the resource has been registered.
string ResourceName
Name of resource.
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:23
Task Flush(bool EndOfData)
Clears all buffers for the current writer and causes any buffered data to be written to the underlyin...
async Task SendResponse()
Sends the response back to the client. If the resource is synchronous, there's no need to call this m...
void EnableDirectTransfer()
Enables direct transfer to output of written data, with no encoding, buffering or limit.
Task Write(byte[] Data)
Returns binary data in the response.
bool ResponseSent
If the response has been sent.
Implements an HTTP server.
Definition: HttpServer.cs:41
bool TryGetResource(HttpRequest Request, out HttpResource Resource, out string SubPath)
Tries to get a resource from the server.
Definition: HttpServer.cs:1796
The server encountered an unexpected condition which prevented it from fulfilling the request.
Represents a request made to a JSON-RPC client.
Declares the method as a JSON-RPC method, to be published by the JSON-RPC web service in which it is ...
Information about a method published via a JSON-RPC web service.
Information about a JSON-RPC request.
Abstract base class for Web Services based on JSON-RPC v2.0.
Task< int > SendEvent(Predicate< IJsonRpcSession?> Filter, string? Comment, params KeyValuePair< string, object >[] Fields)
Sends an event to clients with open subscriptions.
bool TryGetRequest(string Id, [NotNullWhen(true)] out IJsonRpcClientRequest? Request)
Tries to get a pending client request, given its ID.
virtual async Task POST(HttpRequest Request, HttpResponse Response)
Executes the POST method on the resource.
override bool HandlesSubPaths
If the resource handles sub-paths.
static ? string[] GetRequiredPrivileges(MethodInfo Method)
Gets required privileges for the user calling the method, if any. If no privilege requirements are fo...
virtual Task GenerateDocumentationApiDescription(ChunkedList< string > Notes, HashSet< Type > TypesToDocument, HttpRequest Request, StringBuilder Markdown)
Generates the Markdown ApiDescription for the documentation page.
static bool All(IJsonRpcSession? Session)
Filter function that selects all sessions.
virtual Task< IJsonRpcSession?> TryGetSession(HttpRequest Request, HttpResponse Response)
Tries to get a session object for the resource, if any.
virtual string SseWelcomeMessage
Server-Sent Events (SSE) welcome message, if one should be sent.
virtual KeyValuePair< bool, string >[] GetParameterDocumentation(ProtectedMethodArgumentInfo Parameter)
Gets parameter documentation for a method parameter.
virtual Task GenerateDocumentationIntroduction(HttpRequest Request, StringBuilder Markdown)
Generates the Markdown introduction for the documentation page.
bool HasMetaDataResource
If an OAUTH resource meta-data resource is registered on the server.
Task< int > SendEvent(IEnumerable< KeyValuePair< string, object > > Fields)
Sends an event to clients with open subscriptions.
override bool Synchronous
If the resource is synchronous (i.e. returns a response in the method handler), or if it is asynchron...
bool AllowsGET
If the GET method is allowed.
Task< int > SendEvent(IDictionary< string, object > Fields)
Sends an event to clients with open subscriptions.
Task< int > SendEvent(Predicate< IJsonRpcSession?> Filter, IEnumerable< KeyValuePair< string, object > > Fields)
Sends an event to clients with open subscriptions.
ProtectedResourceMetaData? MetaDataResource
OAUTH resource meta-data resource, if any registered.
static void AppendType(Type Type, StringBuilder Markdown, HashSet< Type > TypesToDocument)
Outputs a type in Markdown format.
override bool UserSessions
If the resource uses user sessions.
abstract string MarkdownDescription
Markdown description of web service.
Task< int > SendEvent(params KeyValuePair< string, object >[] Fields)
Sends an event to clients with open subscriptions.
JsonRpcWebService(string ResourceName, bool UserSessions)
Abstract base class for Web Services based on JSON-RPC v2.0.
bool Unregister(MethodInfo Method)
Unregisters a method from the JSON-RPC interface.
bool TryGetResourceMetaDataResource(HttpServer Server, [NotNullWhen(true)] out ProtectedResourceMetaData? Resource)
Tries to get the resource meta-data resource, if any registered.
virtual string ShortDescription
Short Description of JSON-RPC web service.
Task< int > SendEvent(Predicate< IJsonRpcSession?> Filter, string? Comment, IEnumerable< KeyValuePair< string, object > > Fields)
Sends an event to clients with open subscriptions.
bool HasJwtFactory
If a JWT Factory is available.
virtual Task GenerateTypeDocumentation(ChunkedList< string > Notes, HashSet< Type > TypesToDocument, HttpRequest Request, StringBuilder Markdown)
Documents types used in the JSON-RPC interface, if any.
Task< int > SendEvent(Predicate< IJsonRpcSession?> Filter, IDictionary< string, object > Fields)
Sends an event to clients with open subscriptions.
void RegisterMethod(MethodInfo Method, params string[]? RequiredPrivileges)
Registers a method to be used in the JSON-RPC interface.
static bool IsOneRow(KeyValuePair< bool, string >[] Documentation)
Checks if a documentation array is one row or multiple rows.
void AddAuthenticationMechanisms(ProtectedMethod Method)
Adds authentication mechanisms to a method, if required.
virtual Task GenerateDocumentationHeader(HttpRequest Request, StringBuilder Markdown)
Generates the Markdown header for the documentation page.
bool AllowsPOST
If the POST method is allowed.
Task< int > SendEvent(Predicate< IJsonRpcSession?> Filter, params KeyValuePair< string, object >[] Fields)
Sends an event to clients with open subscriptions.
virtual async Task GenerateDocumentation(HttpRequest Request, HttpResponse Response)
Generates a documentation page for the resource, if supported.
Task< int > SendEvent(string? Comment, IEnumerable< KeyValuePair< string, object > > Fields)
Sends an event to clients with open subscriptions.
bool HasDomain
If a domain name is registered on the server.
JsonRpcWebService(string ResourceName, bool UserSessions, bool CaseSensitive)
Abstract base class for Web Services based on JSON-RPC v2.0.
HttpAuthenticationScheme?[] AuthenticationSchemes
Generic authentication schemes for the resource.
string? Domain
Domain name of the server, if any registered.
override void AddReference(HttpServer Server)
Method called when a resource has been registered on a server.
Task< int > SendEvent(Predicate< IJsonRpcSession?> Filter, string? Comment, IDictionary< string, object > Fields)
Sends an event to clients with open subscriptions.
abstract string Title
Title of JSON-RPC web service.
bool Unregister(IJsonRpcSession? Session)
Unregisters an existing SSE subscription for a session, if any.
virtual void AppendDocumentation(ChunkedList< string > Notes, HashSet< Type > TypesToDocument, ProtectedMethod Method, StringBuilder Markdown)
Appends Documentation to a Markdown document.
virtual bool SupportsServerSentEvents
If Server-Sent Events (SSE) are supported by the resource.
static void AppendValue(object? Value, StringBuilder Markdown)
Outputs a value in Markdown format.
virtual bool SendSseWelcomeMessage
If a Server-Sent Events (SSE) welcome message should be sent to clients with open subscriptions.
static string YesNo(bool Value)
Returns "Yes" or "No" based on the boolean value provided.
static void AppendDocumentation(KeyValuePair< bool, string >[] Documentation, StringBuilder Markdown)
Appends Documentation to a Markdown document.
JwtFactory? JwtFactory
JWT Factory, if available.
virtual async Task GET(HttpRequest Request, HttpResponse Response)
Executes the GET method on the resource.
Task< int > SendEvent(string? Comment, IDictionary< string, object > Fields)
Sends an event to clients with open subscriptions.
JsonRpcClientRequest< T > CreateRequest< T >(string Message, string Method, object? Parameters, IJsonRpcSession Session, Func< object?, Task< T > > ParseResult, HttpRequest HttpRequest)
Sends a request to the client, and waits for a response.
static void AppendCell(ChunkedList< string > Notes, KeyValuePair< bool, string >[] Documentation, StringBuilder Markdown)
Appends a cell to the Markdown table.
virtual KeyValuePair< bool, string >[] GetMemberDocumentation(ICustomAttributeProvider Member)
Gets documentation for a member.
Task< int > SendEvent(string? Comment, params KeyValuePair< string, object >[] Fields)
Sends an event to clients with open subscriptions.
Adds documentation to a JSON-RPC method, parameter, property, field, event or return value....
Information about an argument in a protected method.
KeyValuePair< bool, string >?[] AdditionalDocumentation
Additional documentation for the method argument. Value represents documentation text,...
bool IsSpecialArgument
If the argument represents a special argument.
KeyValuePair< bool, string >[] Documentation
Available documentation for the method argument. Value represents documentation text,...
object? DefaultValue
Default value of argument, if defined.
Information about a protected method.
ProtectedMethodArgumentInfo[] Arguments
Arguments
bool HasReturnValue
If the method has a return value.
HttpAuthenticationScheme?[] AuthenticationMechanisms
Authentication mechanisms available to authenticate users, if authentication is required.
bool RequiresAuthentication
If authentication of the user is required.
string[] RequiredPrivileges
Privileges required by the user that calls the method.
string Name
Name to use in documentation.
KeyValuePair< bool, string >[] Documentation
Available documentation for the method. Value represents documentation text, and Key represents if th...
void UpdateAuthenticationMechanisms()
Updates the authentication mechanisms available to authenticate users that want to access the method.
Defines a privilege that is required by the user that calls a method.
The resource identified by the request is only capable of generating response entities which have con...
The server has not found anything matching the Request-URI. No indication is given of whether the con...
Base class for all asynchronous HTTP resources. An asynchronous resource responds outside of the meth...
static string GenerateRandomCode(int NrBytes)
Generates a random unique code.
bool HasLoginMasterFileName
If a login master file name has been registered
Provides OAUTH resource meta-data, as defined in RFC 9728. https://datatracker.ietf....
string GetResourceMetaDataUri(bool Encrypted, string? Domain, string ResourceName)
Gets the resource meta-data URI for a given resource.
The server is currently unable to handle the request due to a temporary overloading or maintenance of...
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
bool Remove(T Item)
Removes an element from the collection.
Definition: ChunkedList.cs:357
int Count
Number of elements in collection.
Definition: ChunkedList.cs:68
void Add(T Item)
Adds an item to the collection.
Definition: ChunkedList.cs:272
T[] ToArray()
Returns an array containing all elements of the collection.
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:607
Class managing a script expression.
Definition: Expression.cs:41
static bool IsVoid(Type ResultType)
Checks if a result object type is equal to void (i.e. its type equal to System.Threading....
Definition: Expression.cs:4735
static string ToExpressionString(object Value)
Converts an object to a string, that can be parsed as part of an expression.
Definition: Expression.cs:5052
Collection of variables.
Definition: Variables.cs:25
A factory that can create and validate JWT tokens.
Definition: JwtFactory.cs:66
bool Disposed
If the factory has been disposed.
Definition: JwtFactory.cs:272
static HttpAuthenticationScheme[] GetAuthenticationSchemes()
Gets an array of authentication schemes available to authorize access to a web resource.
Definition: HttpModule.cs:108
GET Interface for HTTP resources.
POST Interface for HTTP resources.
Interface for JSON-RPC client request objects.
Interface for JSON-RPC session objects.
Definition: ImplTypes.g.cs:58