2using System.Collections.Generic;
4using System.Reflection;
6using System.Threading.Tasks;
29 private readonly
static Dictionary<string, INode> nodesById =
new Dictionary<string, INode>();
30 private static IEnumerable<INode> nodes =
null;
32 private static WebServer webServer =
null;
34 private static Ports ports =
null;
36 private static DateTime writeTime = DateTime.MinValue;
38 internal static DateTime nodesTimestamp = DateTime.MinValue;
51 this.LoadConfiguration();
61 private void CheckLoadConfiguration()
63 if (nodes is
null || nodesTimestamp < this.
LastChanged)
64 this.LoadConfiguration();
67 private void LoadConfiguration()
72 if (!File.Exists(GatewayConfigFileName))
75 if (!File.Exists(GatewayConfigFileName))
76 throw new Exception(
"Gateway.config not found.");
79 XmlDocument Config =
new XmlDocument();
80 Config.Load(GatewayConfigFileName);
85 generalInformation =
new GeneralInformation(
this);
86 webServer =
new WebServer(
this);
87 databaseDefinition =
new DatabaseDefinition(
this);
88 ports =
new Ports(
this);
91 List<INode> Nodes =
new List<INode>()
99 Dictionary<string, bool> ContentEncodingsFound =
new Dictionary<string, bool>();
101 foreach (XmlNode N
in Config.DocumentElement.ChildNodes)
103 if (N is XmlElement E)
107 case "ApplicationName":
108 generalInformation.ApplicationName = E.InnerText;
112 DefaultPage DefaultPage =
new DefaultPage(
this, webServer,
XML.
Attribute(E,
"host"), E.InnerText);
113 webServer.AddInternal(DefaultPage);
118 webServer.TrustClientCertificates =
XML.
Attribute(E,
"trustCertificates",
false);
120 foreach (XmlNode N2
in E.ChildNodes)
122 if (N2.LocalName ==
"Port" &&
int.TryParse(N2.InnerText, out
int PortNumber))
124 XmlElement E2 = (XmlElement)N2;
128 webServer.AddInternal(
new MTlsPort(
this, webServer, PortNumber, ClientCertificatesPort, TrustClientCertificatesPort));
133 case "ContentEncodings":
134 foreach (XmlNode N2
in E.ChildNodes)
136 if (N2.LocalName ==
"ContentEncoding")
138 XmlElement E2 = (XmlElement)N2;
146 ContentEncodingsFound[Method] =
true;
148 webServer.AddInternal(
new ContentEncoding(
this, webServer, Method, Dynamic, Static));
155 case "ExportExceptions":
156 generalInformation.ExceptionFolder =
XML.
Attribute(E,
"folder",
"Exceptions");
161 databaseDefinition.DefaultCollectionName =
XML.
Attribute(E,
"defaultCollectionName",
string.Empty);
162 databaseDefinition.BlockSize =
XML.
Attribute(E,
"blockSize", 0);
163 databaseDefinition.BlocksInCache =
XML.
Attribute(E,
"blocksInCache", 0);
164 databaseDefinition.BlobBlockSize =
XML.
Attribute(E,
"blobBlockSize", 0);
165 databaseDefinition.TimeoutMs =
XML.
Attribute(E,
"timeoutMs", 0);
166 databaseDefinition.Encrypted =
XML.
Attribute(E,
"encrypted",
false);
167 databaseDefinition.Compiled =
XML.
Attribute(E,
"compiled",
false);
171 foreach (XmlNode N2
in E.ChildNodes)
173 if (N2.LocalName ==
"Port")
175 XmlElement E2 = (XmlElement)N2;
177 if (!
string.IsNullOrEmpty(Protocol) &&
int.TryParse(E2.InnerText, out
int PortNumber))
178 ports.AddInternal(
new Port(
this, ports, Protocol, PortNumber));
183 case "DefaultHttpResponseHeaders":
184 foreach (XmlNode N2
in E.ChildNodes)
186 if (N2.LocalName ==
"DefaultHttpResponseHeader")
188 XmlElement E2 = (XmlElement)N2;
191 webServer.AddInternal(
new DefaultHttpResponseHeader(
this, webServer, Name, Value));
197 foreach (XmlNode N2
in E.ChildNodes)
199 if (N2.LocalName ==
"FileFolder")
201 XmlElement E2 = (XmlElement)N2;
204 FileFolder Folder =
new FileFolder(
this, webServer, WebFolder, FolderPath);
205 webServer.AddInternal(Folder);
207 foreach (XmlNode N3
in E2.ChildNodes)
209 if (N3.LocalName ==
"DefaultHttpResponseHeader")
211 XmlElement E3 = (XmlElement)N3;
214 Folder.AddInternal(
new DefaultHttpResponseHeader(
this, Folder, Name, Value));
221 case "VanityResources":
222 foreach (XmlNode N2
in E.ChildNodes)
224 if (N2.LocalName ==
"VanityResource")
226 XmlElement E2 = (XmlElement)N2;
229 webServer.AddInternal(
new VanityResource(
this, webServer, Regex, Url));
235 foreach (XmlNode N2
in E.ChildNodes)
237 if (N2.LocalName ==
"Redirection")
239 XmlElement E2 = (XmlElement)N2;
242 bool IncludeSubPaths =
XML.
Attribute(E2,
"includeSubPaths",
false);
244 webServer.AddInternal(
new Redirection(
this, webServer, Resource, Location, IncludeSubPaths, Permanent));
250 foreach (XmlNode N2
in E.ChildNodes)
252 if (N2.LocalName ==
"ProxyResource")
254 XmlElement E2 = (XmlElement)N2;
255 string LocalResource =
XML.
Attribute(E2,
"localResource");
256 string RemoteDomain =
XML.
Attribute(E2,
"remoteDomain");
257 string RemoteFolder =
XML.
Attribute(E2,
"remoteFolder");
260 bool UseSession =
XML.
Attribute(E2,
"useSession",
false);
262 webServer.AddInternal(
new ProxyResource(
this, webServer, LocalResource, RemoteDomain, RemoteFolder,
263 RemotePort, Encrypted, UseSession, TimeoutMs));
269 if (loginAuditor is
null)
271 loginAuditor =
new LoginAuditorNode(
this);
272 Nodes.Add(loginAuditor);
274 foreach (XmlNode N2
in E.ChildNodes)
276 if (N2.LocalName ==
"Interval")
278 XmlElement E2 = (XmlElement)N2;
284 loginAuditor.AddInternal(
new IntervalNode(
this, NrAttempts, Interval));
289 Log.
Error(
"Only one LoginAuditor element permitted.", GatewayConfigFileName);
295 if (loginAuditor is
null)
297 loginAuditor =
new LoginAuditorNode(
this);
298 Nodes.Add(loginAuditor);
318 if (ContentEncodingsFound.ContainsKey(Encoding.
Label))
321 ContentEncodingsFound[Encoding.
Label] =
true;
329 nodes = Nodes.ToArray();
334 foreach (
INode Node
in nodes)
336 nodesById[Node.
NodeId] = Node;
338 if (Node is ConfigurationNode ConfigurationNode && ConfigurationNode.HasChildren)
340 foreach (
INode Child
in ConfigurationNode.Children)
341 nodesById[Child.
NodeId] = Child;
379 this.CheckLoadConfiguration();
387 public event EventHandlerAsync<SourceEvent>
OnEvent;
416 if (NodeRef.
SourceId !=
this.SourceID || !
string.IsNullOrEmpty(NodeRef.
Partition))
417 return Task.FromResult<
INode>(
null);
421 if (nodesById.TryGetValue(NodeRef.
NodeId, out
INode Node))
422 return Task.FromResult(Node);
424 return Task.FromResult<
INode>(
null);
430 return this.OnEvent.Raise(
this,
Event);
443 nodesById[Node.
NodeId] = Node;
457 if (nodesById.TryGetValue(Node.
NodeId, out
INode Node2) && Node == Node2)
460 foreach (KeyValuePair<string, INode> P
in nodesById)
464 nodesById.Remove(P.Key);
469 nodesById[Node.
NodeId] = Node;
473 internal async Task NodeDeleted(
INode Node,
bool External)
482 if (nodesById.TryGetValue(Node.
NodeId, out
INode Node2) && Node == Node2)
483 nodesById.Remove(Node.
NodeId);
487 private static void ScheduleSave()
489 if (writeTime > DateTime.MinValue)
492 writeTime = DateTime.MinValue;
498 private static async Task SaveFile(
object _)
505 if (File.Exists(BakFileName))
506 File.Delete(BakFileName);
508 if (File.Exists(ConfigFileName))
512 Log.
Notice(
"Gateway.config file updated.\r\n\r\nOld Config file:\r\n\r\n```\r\n" + OldConfig +
"\r\n```",
513 ConfigFileName,
string.Empty,
"GatewayConfigUpdated");
515 File.Move(ConfigFileName, BakFileName);
518 using (FileStream fs = File.Create(ConfigFileName))
520 using (XmlWriter Xml = XmlWriter.Create(fs,
XML.
WriterSettings(
true,
false, Encoding.UTF8)))
522 Xml.WriteStartDocument();
525 Xml.WriteComment(
"The configuration file in the program data folder, will have precedence over the configuration file in the installation folder.");
526 Xml.WriteComment(
"When upgrading, the configuration file in the installation folder will be updated, but the configuration file in the program data");
527 Xml.WriteComment(
"folder will be maintained. If you make changes to the configuration file, make a copy and place it in the program data folder, and");
528 Xml.WriteComment(
"edit it there. This will make sure you don't lose any changes when you update the software.");
530 Xml.WriteElementString(
"ApplicationName", generalInformation.
ApplicationName);
536 if (Node is DefaultPage DefaultPage)
538 Xml.WriteStartElement(
"DefaultPage");
540 if (!
string.IsNullOrEmpty(DefaultPage.Host))
541 Xml.WriteAttributeString(
"host", DefaultPage.Host);
543 Xml.WriteValue(DefaultPage.Page);
544 Xml.WriteEndElement();
549 Xml.WriteStartElement(
"MutualTls");
550 Xml.WriteAttributeString(
"clientCertificates", webServer.
ClientCertificates.ToString());
555 if (Node is MTlsPort MTlsPort)
557 Xml.WriteStartElement(
"Port");
558 Xml.WriteAttributeString(
"clientCertificates", MTlsPort.ClientCertificates.ToString());
559 Xml.WriteAttributeString(
"trustCertificates",
CommonTypes.
Encode(MTlsPort.TrustClientCertificates));
560 Xml.WriteValue(MTlsPort.PortNumber.ToString());
561 Xml.WriteEndElement();
565 Xml.WriteEndElement();
569 Dictionary<string, bool> Found =
new Dictionary<string, bool>();
571 Xml.WriteStartElement(
"ContentEncodings");
575 if (Node is ContentEncoding ContentEncoding)
577 Xml.WriteStartElement(
"ContentEncoding");
578 Xml.WriteAttributeString(
"method", ContentEncoding.Method);
579 Xml.WriteAttributeString(
"dynamic",
CommonTypes.
Encode(ContentEncoding.Dynamic));
581 Xml.WriteEndElement();
583 Found[ContentEncoding.Method] =
true;
604 if (Found.ContainsKey(Encoding.
Label))
607 Found[Encoding.
Label] =
true;
609 Xml.WriteStartElement(
"ContentEncoding");
610 Xml.WriteAttributeString(
"method", Encoding.
Label);
613 Xml.WriteEndElement();
616 Xml.WriteEndElement();
619 Xml.WriteStartElement(
"Database");
620 Xml.WriteAttributeString(
"folder", databaseDefinition.
Folder);
622 Xml.WriteAttributeString(
"blockSize", databaseDefinition.
BlockSize.ToString());
623 Xml.WriteAttributeString(
"blocksInCache", databaseDefinition.
BlocksInCache.ToString());
624 Xml.WriteAttributeString(
"blobBlockSize", databaseDefinition.
BlobBlockSize.ToString());
625 Xml.WriteAttributeString(
"timeoutMs", databaseDefinition.
TimeoutMs.ToString());
628 Xml.WriteEndElement();
630 Xml.WriteStartElement(
"Ports");
636 if (Node is Port Port)
638 Xml.WriteStartElement(
"Port");
639 Xml.WriteAttributeString(
"protocol", Port.Protocol);
640 Xml.WriteValue(Port.PortNumber.ToString());
641 Xml.WriteEndElement();
646 Xml.WriteEndElement();
650 Xml.WriteStartElement(
"DefaultHttpResponseHeaders");
654 if (Node is DefaultHttpResponseHeader DefaultHttpResponseHeader)
656 Xml.WriteStartElement(
"DefaultHttpResponseHeader");
657 Xml.WriteAttributeString(
"key", DefaultHttpResponseHeader.Name);
658 Xml.WriteAttributeString(
"value", DefaultHttpResponseHeader.Value);
659 Xml.WriteEndElement();
663 Xml.WriteEndElement();
664 Xml.WriteStartElement(
"FileFolders");
666 Xml.WriteComment(
"Add a sequence of FileFolder elements. Each FileFolder element creates a web folder defined by the webFolder attribute. These folder resources are absolute");
667 Xml.WriteComment(
"resources. Each web folder will be mapped to a corresponding folder on the local machine or in the network, defined by the folderPath attribute. ");
668 Xml.WriteComment(
"");
669 Xml.WriteComment(
"Example:");
670 Xml.WriteComment(
"");
671 Xml.WriteComment(
"<FileFolder webFolder=\"/Folder\" folderPath=\"\\\\Server\\Path\"/>");
675 if (Node is FileFolder FileFolder)
677 Xml.WriteStartElement(
"FileFolder");
678 Xml.WriteAttributeString(
"webFolder", FileFolder.WebFolder);
679 Xml.WriteAttributeString(
"folderPath", FileFolder.FolderPath);
680 Xml.WriteEndElement();
684 Xml.WriteEndElement();
689 Xml.WriteStartElement(
"VanityResources");
693 if (Node is VanityResource VanityResource)
695 Xml.WriteStartElement(
"VanityResource");
696 Xml.WriteAttributeString(
"regex", VanityResource.Regex);
697 Xml.WriteAttributeString(
"url", VanityResource.Url);
698 Xml.WriteEndElement();
702 Xml.WriteEndElement();
704 Xml.WriteStartElement(
"Redirections");
708 if (Node is Redirection Redirection)
710 Xml.WriteStartElement(
"Redirection");
711 Xml.WriteAttributeString(
"resource", Redirection.Resource);
712 Xml.WriteAttributeString(
"location", Redirection.Location);
713 Xml.WriteAttributeString(
"includeSubPaths",
CommonTypes.
Encode(Redirection.IncludeSubPaths));
714 Xml.WriteAttributeString(
"permanent",
CommonTypes.
Encode(Redirection.Permanent));
715 Xml.WriteEndElement();
719 Xml.WriteEndElement();
721 Xml.WriteStartElement(
"ReverseProxy");
725 if (Node is ProxyResource ProxyResource)
727 Xml.WriteStartElement(
"ProxyResource");
728 Xml.WriteAttributeString(
"localResource", ProxyResource.LocalResource);
729 Xml.WriteAttributeString(
"remoteDomain", ProxyResource.RemoteDomain);
730 Xml.WriteAttributeString(
"remoteFolder", ProxyResource.RemoteFolder);
731 Xml.WriteAttributeString(
"remotePort", ProxyResource.RemotePort.ToString());
732 Xml.WriteAttributeString(
"encrypted",
CommonTypes.
Encode(ProxyResource.Encrypted));
733 Xml.WriteAttributeString(
"useSession",
CommonTypes.
Encode(ProxyResource.UseSession));
734 Xml.WriteAttributeString(
"timeoutMs", ProxyResource.TimeoutMs.ToString());
735 Xml.WriteEndElement();
739 Xml.WriteEndElement();
744 Xml.WriteStartElement(
"ExportExceptions");
745 Xml.WriteAttributeString(
"folder", generalInformation.
ExceptionFolder);
746 Xml.WriteEndElement();
749 if (!(loginAuditor is
null))
751 IEnumerable<INode> Intervals = await loginAuditor.
ChildNodes;
754 foreach (
INode _2
in Intervals)
762 Xml.WriteStartElement(
"LoginAuditor");
764 foreach (
INode Node
in Intervals)
766 if (Node is IntervalNode Interval)
768 Xml.WriteStartElement(
"Interval");
769 Xml.WriteAttributeString(
"nrAttempts", Interval.NrAttempts.ToString());
771 if (Interval.Interval.HasValue)
772 Xml.WriteAttributeString(
"interval", Interval.Interval.Value.ToString());
774 Xml.WriteEndElement();
778 Xml.WriteEndElement();
782 Xml.WriteEndElement();
783 Xml.WriteEndDocument();
789 await Task.Delay(5000);
797 writeTime = DateTime.MinValue;
805 internal static async Task FileUpdated()
809 if (writeTime > DateTime.MinValue)
812 Log.
Notice(
"Gateway.config changed outside of the system. Loading configuration.");
814 Dictionary<string, INode> OldNodes =
new Dictionary<string, INode>();
815 List<INode> Updated =
new List<INode>();
816 List<INode> Added =
new List<INode>();
820 foreach (KeyValuePair<string, INode> P
in nodesById)
821 OldNodes[P.Key] = P.Value;
824 instance.LoadConfiguration();
828 foreach (KeyValuePair<string, INode> P
in nodesById)
830 if (OldNodes.Remove(P.Key))
831 Updated.Add(P.Value);
837 foreach (
INode Node
in OldNodes.Values)
838 await instance.NodeDeleted(Node,
true);
840 foreach (
INode Node
in Added)
841 await instance.NodeAdded(Node,
true);
843 foreach (
INode Node
in Updated)
844 await instance.NodeUpdated(Node,
true);
Helps with parsing of commong data types.
static string Encode(bool x)
Encodes a Boolean for use in XML and other formats.
Static class managing loading of resources stored as embedded resources or in content files.
static async Task< string > ReadAllTextAsync(string FileName)
Reads a text file asynchronously.
Helps with common XML-related tasks.
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
static XmlWriterSettings WriterSettings(bool Indent, bool OmitXmlDeclaration)
Gets an XML writer settings object.
Static class managing loading of XSL resources stored as embedded resources or in content files.
static XmlSchema LoadSchema(string ResourceName)
Loads an XML schema from an embedded resource.
static void Validate(string ObjectID, XmlDocument Xml, params XmlSchema[] Schemas)
Validates an XML document given a set of XML schemas.
Class representing an event.
Static class managing the application event log. Applications and services log events on this static ...
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.
static void Error(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an error event.
static void Notice(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a notice event.
Static class managing the runtime environment of the IoT Gateway.
static string ConfigFilePath
Full path to Gateway.config file.
const string GatewayConfigNamespace
http://waher.se/Schema/GatewayConfiguration.xsd
static string AppDataFolder
Application data folder.
const string GatewayConfigLocalName
GatewayConfiguration
static DateTime ScheduleEvent(ScheduledEventCallback Callback, DateTime When, object State)
Schedules a one-time event.
static bool CancelScheduledEvent(DateTime When)
Cancels a scheduled event.
const string GatewayConfigLocalFileName
Gateway.config
Implements an HTTP server.
const int DefaultHttpPort
Default HTTP Port (80).
const int DefaultHttpsPort
Default HTTPS port (443).
Static class that dynamically manages types and interfaces available in the runtime environment.
static object[] NoParameters
Contains an empty array of parameter values.
static Type[] GetTypesImplementingInterface(string InterfaceFullName)
Gets all types implementing a given interface.
static ConstructorInfo GetDefaultConstructor(Type Type)
Gets the default constructor of a type, if one exists.
Contains information about a language.
Task< string > GetStringAsync(Type Type, int Id, string Default)
Gets the string value of a string ID. If no such string exists, a string is created with the default ...
Contains information about a namespace in a language.
Basic access point for runtime language localization.
static async Task< Language > GetDefaultLanguageAsync()
Gets the default language.
virtual Task< IEnumerable< INode > > ChildNodes
Child nodes. If no child nodes are available, null is returned.
virtual bool HasChildren
If the source has any child sources.
Contains the local database definition.
int TimeoutMs
Timeout of database operations, in milliseconds.
int BlockSize
Number of bytes of each B-Tree block in the database.
int BlocksInCache
Number of blocks to maintain in internal memory.
string Folder
Folder, relative to the application data folder, where object database files will be stored.
bool Compiled
If object serializers should be compiled or not.
bool Encrypted
If the database is encrypted.
int BlobBlockSize
Number of bytes of each BLOB block in the database.
string DefaultCollectionName
Name of the collection to use, if the class definition lacks a collection definition.
Data source mirroring the Gateway.config file.
DateTime LastChanged
When the source was last updated.
IEnumerable< IDataSource > ChildSources
Child sources. If no child sources are available, null is returned.
Task< string > GetNameAsync(Language Language)
Gets the name of data source.
string SourceID
ID of data source.
const string GatewayConfigSourceID
Data source mirroring the Gateway.config file.
Task< bool > CanViewAsync(RequestOrigin Caller)
If the data source is visible to the caller.
bool HasChildren
If the source has any child sources.
GatewayConfigSource()
Data source mirroring the Gateway.config file.
Task< INode > GetNodeAsync(IThingReference NodeRef)
Gets the node, given a reference to it.
EventHandlerAsync< SourceEvent > OnEvent
Event raised when a data source event has been raised.
IEnumerable< INode > RootNodes
Root node references. If no root nodes are available, null is returned.
void Dispose()
IDisposable.Dispose
Contains configuration of the Login Auditor.
Root node of port numbers to use.
Settings for the Web Server.
ClientCertificates ClientCertificates
mTLS-configuration
bool TrustClientCertificates
If certificates are to be trusted, or if they are required to be valid.
Service Module hosting the XMPP broker and its components.
Tokens available in request.
static readonly RequestOrigin Empty
Empty request origin.
string From
Address of caller.
static NodeRemoved FromNode(INode Node)
Creates an event object from a node object.
Abstract base class for all data source events.
Interface for content encodings in HTTP transfers.
bool SupportsStaticEncoding
If encoding can be used for static encoding.
void ConfigureSupport(bool Dynamic, bool Static)
Configures support for the algorithm.
bool SupportsDynamicEncoding
If encoding can be used for dynamic encoding.
string Label
Label identifying the Content-Encoding
Interface for datasources that are published through the concentrator interface.
Interface for nodes that are published through the concentrator interface.
Interface for thing references.
string Partition
Optional partition in which the Node ID is unique.
string SourceId
Optional ID of source containing node.
ClientCertificates
Client Certificate Options
Represents a duration value, as defined by the xsd:duration data type: http://www....
static readonly Duration Zero
Zero value