diff --git a/Documentation/de/relayserver.md b/Documentation/de/relayserver.md index 76df9e54..92a5a59d 100644 --- a/Documentation/de/relayserver.md +++ b/Documentation/de/relayserver.md @@ -7,12 +7,12 @@ # Release Notes -## Version 2.3.0-rc3 +## Version 2.3.0-rc4 -* RabbitMq Verbesserungen +* RabbitMQ Verbesserungen * Beendete Verbindungen werden nun explizit auch auf dem Rabbit-Client beendet. - * Die Funktion, verlorene Verbindungen zum RabbitMq Server von dessen Client automatisch wiederherstellen zu lassen, wird nun standardmäßig aktiviert. + * Die Funktion, verlorene Verbindungen zum RabbitMQ Server von dessen Client automatisch wiederherstellen zu lassen, wird nun standardmäßig aktiviert. * Wenn eine Verbindung geschlossen wird, wird sichergestellt dass alle Nachrichten die noch nicht acknowledged wurden, wieder zugestellt werden. * Um den Durchsatz zu erhöhen werden nun separate Channels (via RoutingKeys) für Requests, Responses und Acknowledgements verwendet. @@ -34,12 +34,23 @@ * Es stehen nun alle benötigen Informationen für manuelles Acknowlegment einfacher zur Verfügung. * Das Request-logging führt nun auch die RequestId mit. * Registrierungen der On-Premise Connector-Typen für Autofac ist nun möglich. + * Der On-Premise Connector service initialisiert nun die HttpConnection properties. + * Relative Pfade in der Konfiguration werden nun einheitlich relativ zur RelayServer .exe-Datei ausgewertet und nicht mehr zum Ausführungsverzeichnis. + * Es wird jetzt per default JSON statt XML über die eigenen Endpunkte zurück gegeben. + * Eine eigene Implementation (oder von der Standardklasse abgeleiteten) eines IOnPremiseConnectionContext kann in der DI registriert werden. + * Um eine einfachere Migration von geänderten IOnPremiseTargetRequest Implementierungen durchführen zu können, wurde eine Property mit dem Namen "Properties" eingeführt, welche in den Root des JSON serialisiert wird, um im On-Premise Connector in die jeweiligen Properties deserialisiert zu werden. * Fehlerbehebungen - * Der OnPremiseConnector Demo-Service konnte ein Framwork-Assembly unter bestimmten Voraussetzungen nicht korrekt laden. - * Der OnPremise-Connector wird seinen `HttpClient` mit dem er Antworten an den RelayServer sendet nun erneuern, falls dort Fehler auftreten. + * Der On-Premise Connector Demo-Service konnte ein Framwork-Assembly unter bestimmten Voraussetzungen nicht korrekt laden. + * Der On-Premise Connector wird seinen `HttpClient` mit dem er Antworten an den RelayServer sendet nun erneuern, falls dort Fehler auftreten. * HttpConfig muss unter bestimmten Umständen explizit initialisiert werden. + * Ein neu erzeugter `HttpClient` erhält nun auch die Authentication-Header seines Vorgängers. + * Auch der Request Interceptor hat nun Zugriff auf den Stream der Daten. + * Der Zugriff auf leere Inhalte in intercepted Requests und Responses wirft nun keine NullReferenceException mehr. + * Das Acknowledgement wurde nicht auf dem korrekten RabbitMQ-Model durchgeführt. + * Eine bereits deaktivierter On-Premise Connector wurde wiederholt deaktiviert. + * Eine Konfigurationsnachricht wurde auch an On-Premise Connectoren geschickt, welche diese gar nicht unterstützt haben. ## Version 2.2.0 @@ -62,7 +73,7 @@ * Fehlerbehebungen - * Der automatische Disconnect stand in individuellen OnPremiseConnector-Implementationen nicht korrekt zur Verfügung. + * Der automatische Disconnect stand in individuellen On-Premise Connector-Implementationen nicht korrekt zur Verfügung. ## Version 2.1.0 @@ -84,7 +95,7 @@ * Fehlerbehebungen * Wenn eine weiterzuleitende Anfrage einen Query-Parameter namens 'path' enthielt, führte das zu unerwartetem Verhalten. - * Die konfigurierbare Filterung des Inhaltes von OnPremise-seitigen Fehler-Antworten wurde korrigiert. + * Die konfigurierbare Filterung des Inhaltes von on-premise-seitigen Fehler-Antworten wurde korrigiert. * Eine genauere Fehlermeldung wird angezeigt wenn die Konfigurationsdatei des RelayServers fehlt. ## Version 2.0.0 @@ -93,9 +104,9 @@ * Mehrere RelayServer können zur Lastverteilung parallel betrieben werden. Die Server müssen hierzu Zugriff auf einen gemeinsamen Netzwerk-Ordner haben, in dem zu übertragende Daten zwischen den Servern ausgetauscht werden. -* Verbesserte Verbindungsstabilität mit neueren OnPremiseConnectoren +* Verbesserte Verbindungsstabilität mit neueren On-Premise Connectoren - * Der RelayServer wird einen OnPremiseConnector mit Version 2.x oder neuer nun regelmässig mit einem Heartbeat anfragen. Bleibt dieser Heartbeat aus, so wird der OnPremiseConnector versuchen die Verbindung zum RelayServer neu aufzubauen. + * Der RelayServer wird einen On-Premise Connector mit Version 2.x oder neuer nun regelmässig mit einem Heartbeat anfragen. Bleibt dieser Heartbeat aus, so wird der On-Premise Connector versuchen die Verbindung zum RelayServer neu aufzubauen. * Eigenen Code ausführen diff --git a/Documentation/en/relayserver.md b/Documentation/en/relayserver.md index 4f2fbd44..bf1986ca 100644 --- a/Documentation/en/relayserver.md +++ b/Documentation/en/relayserver.md @@ -17,9 +17,9 @@ The goal of this list is to highlight companies who pay back to this open source # Version history -## Version 2.3.0-rc3 +## Version 2.3.0-rc4 -* RabbitMq Improvements +* RabbitMQ Improvements * Closed Rabbit connections will now be automatically unbound at the client. * The automatic recovery feature of the rabbit client will now be enabled by default. @@ -28,7 +28,7 @@ The goal of this list is to highlight companies who pay back to this open source * On-Premise Interceptors - * It is now possible to add custom code into the On-Premise connector that is able to intercept and modifiy requests and responses. + * It is now possible to add custom code into the On-Premise Connector that is able to intercept and modifiy requests and responses. * Modify content streams @@ -42,13 +42,24 @@ The goal of this list is to highlight companies who pay back to this open source * Logging of sensitive data is now configurable and enabled by default. * Now all information required for manual acknowledgment is provided for easier handling. * Request logging now also tracks the request id. - * It is now possible to register the OnPremise Connector types with Autofac. + * It is now possible to register the On-Premise Connector types with Autofac. + * The On-Premise Connector service now initializes http connection properties. + * Relative paths in configuration are now consistently evaluated against the exe directory of the RelayServer and not against the execution dir anymore. + * The new default for our own responses now is JSON and not XML. + * An own implementation (or inherited from the default one) of an IOnPremiseConnectionContext can be registered in the DI. + * For an easier migration scenario of changed IOnPremiseTargetRequest implementations a property called "Properties" was introduced, which will be serialized onto the root JSON object to be deserialized as properties on the on-premise side. * Bugfixes - * Under certain circumstances the on-premise connector demo service wasn't able to load a framework assembly. - * The OnPremise-Connector is now able to recreate the `HttpClient` that is used to send responses to the RelayServer in case there are errors when posting. + * Under certain circumstances the On-Premise Connector demo service wasn't able to load a framework assembly. + * The On-Premise Connector is now able to recreate the `HttpClient` that is used to send responses to the RelayServer in case there are errors when posting. * HttpConfig needs to be explicitely initialized under certain circumstances. + * A newly created HttpClient now also receives the authentication header values of its predecessor. + * The request interceptor now also has access to the request body. + * Accessing empty bodies on intercepted requests and responses does not throw a NullReferenceException anymore. + * The acknowledgement was not done on the correct RabbitMQ model. + * An already deactivated connection was repeatingly deactivated again. + * The config message was accidentally sent to On-Premise Connectors not supporting it. ## Version 2.2.0 @@ -71,7 +82,7 @@ The goal of this list is to highlight companies who pay back to this open source * Bugfixes - * The automatic disconnect feature was not correctly made available for custom implementations of the On-Premise connector service. + * The automatic disconnect feature was not correctly made available for custom implementations of the On-Premise Connector service. ## Version 2.1.0 @@ -79,15 +90,15 @@ The goal of this list is to highlight companies who pay back to this open source * It is now possible to configure link-specific settings on the RelayServer itself. -* Automatic disconnect of On-Premises connectors +* Automatic disconnect of On-Premises Connectors - * If required, it is possible to have an On-Premises connector auto-disconnect itself after a maximum absolute connection time and/or after a maximum idle time. + * If required, it is possible to have an On-Premises Connector auto-disconnect itself after a maximum absolute connection time and/or after a maximum idle time. * General improvements - * The On-Premises connectors settings for reconnect timeouts (maximum and minimum) are now configurable, to be able to prevent accidental DDoS detections i.e. when the server restarts and all connectors want to reconnect in the same 30 second window. + * The On-Premises Connectors settings for reconnect timeouts (maximum and minimum) are now configurable, to be able to prevent accidental DDoS detections i.e. when the server restarts and all Connectors want to reconnect in the same 30 second window. * Interceptors now can read the local uri that the client requested, i.e. to set forwarded headers. - * It is now possible to configure whether the On-Premises connector automatically follows an http redirect response from a On-Premises target, or if the redirect will be relayed too. + * It is now possible to configure whether the On-Premises Connector automatically follows an http redirect response from a On-Premises target, or if the redirect will be relayed too. * It is now possible to use a custom implementation of an `IPasswordComplexityValidator` by registering that in an Autofac module within a custom code assembly. * Bugfixes @@ -102,9 +113,9 @@ The goal of this list is to highlight companies who pay back to this open source * It is now possible to operate multiple RelayServers in parallel for better load distribution. All servers need to have access to a shared network folder in order to exchange binary files with request or response payloads. -* Improved connection stability with new On-Premises connectors +* Improved connection stability with new On-Premises Connectors - * The RelayServer is now capable of heart beating On-Premises connectors of version 2.x or newer. A connector that does not receive this heartbeat will automatically try to reconnect to the server. + * The RelayServer is now capable of heart beating On-Premises Connectors of version 2.x or newer. A Connector that does not receive this heartbeat will automatically try to reconnect to the server. * Implementation of custom code @@ -133,7 +144,7 @@ The goal of this list is to highlight companies who pay back to this open source * Optimizations - * Memory consumption of the RelayServer and On-Premises connectors has been reduced. Additionally we optimized general performance to make the system more efficient. + * Memory consumption of the RelayServer and On-Premises Connectors has been reduced. Additionally we optimized general performance to make the system more efficient. * Security improvements diff --git a/Shared/AssemblyInfo.shared.cs b/Shared/AssemblyInfo.shared.cs index b3524108..25463dcd 100644 --- a/Shared/AssemblyInfo.shared.cs +++ b/Shared/AssemblyInfo.shared.cs @@ -9,4 +9,4 @@ [assembly: AssemblyVersion("2.3.0.0")] [assembly: AssemblyFileVersion("2.3.0.0")] -[assembly: AssemblyInformationalVersion("2.3.0.0-rc3")] +[assembly: AssemblyInformationalVersion("2.3.0.0-rc4")] diff --git a/Shared/ProjectProperties.shared.props b/Shared/ProjectProperties.shared.props index 57a6ab4b..e3a81137 100644 --- a/Shared/ProjectProperties.shared.props +++ b/Shared/ProjectProperties.shared.props @@ -2,7 +2,7 @@ 2.3.0 - 2.3.0-rc3 + 2.3.0-rc4 Copyright © Thinktecture AG 2015 - 2020. All rights reserved. thinktecture;relayserver diff --git a/Thinktecture.Relay.CustomCodeDemo/CustomCodeModule.cs b/Thinktecture.Relay.CustomCodeDemo/CustomCodeModule.cs index f4a06643..1aecf57e 100644 --- a/Thinktecture.Relay.CustomCodeDemo/CustomCodeModule.cs +++ b/Thinktecture.Relay.CustomCodeDemo/CustomCodeModule.cs @@ -30,6 +30,9 @@ protected override void Load(ContainerBuilder builder) // Example: Override request logger with a custom version builder.RegisterType().AsImplementedInterfaces(); + // Example: Override the on-premise connector context with an own implementation + builder.RegisterType().AsImplementedInterfaces().InstancePerDependency(); + base.Load(builder); } } diff --git a/Thinktecture.Relay.CustomCodeDemo/DemoOnPremiseConnectorContext.cs b/Thinktecture.Relay.CustomCodeDemo/DemoOnPremiseConnectorContext.cs new file mode 100644 index 00000000..03423220 --- /dev/null +++ b/Thinktecture.Relay.CustomCodeDemo/DemoOnPremiseConnectorContext.cs @@ -0,0 +1,15 @@ +using Serilog; +using Thinktecture.Relay.Server.Communication; + +namespace Thinktecture.Relay.CustomCodeDemo +{ + internal class DemoOnPremiseConnectorContext : OnPremiseConnectionContext + { + public DemoOnPremiseConnectorContext(ILogger logger) + { + logger?.Information("Creating own connector context"); + } + + public override bool SupportsConfiguration => false; + } +} diff --git a/Thinktecture.Relay.CustomCodeDemo/DemoRequestInterceptor.cs b/Thinktecture.Relay.CustomCodeDemo/DemoRequestInterceptor.cs index 3465156a..2ec58bb7 100644 --- a/Thinktecture.Relay.CustomCodeDemo/DemoRequestInterceptor.cs +++ b/Thinktecture.Relay.CustomCodeDemo/DemoRequestInterceptor.cs @@ -1,9 +1,10 @@ using System; +using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; -using System.Net.Http.Formatting; using System.Text; +using System.Web.Http.Controllers; using Serilog; using Thinktecture.Relay.Server; using Thinktecture.Relay.Server.Interceptor; @@ -17,16 +18,19 @@ namespace Thinktecture.Relay.CustomCodeDemo public class DemoRequestInterceptor : IOnPremiseRequestInterceptor { private readonly ILogger _logger; + private readonly HttpRequestContext _context; /// /// Creates a new instance of the /// /// An instance of an , that will be injected by Autofac when this interceptor is created - public DemoRequestInterceptor(ILogger logger) + /// An instance of an for access to request specifics. + public DemoRequestInterceptor(ILogger logger, HttpRequestContext context) { // You can also have the DI inject different custom dependencies, as long as // they are all registered in your interceptors Autofac module _logger = logger; + _context = context; } /// @@ -38,6 +42,21 @@ public HttpResponseMessage OnRequestReceived(IInterceptedRequest request) { _logger?.Debug($"{nameof(DemoRequestInterceptor)}.{nameof(OnRequestReceived)} is called."); + if (_context.IsLocal) + { + _logger?.Debug("This request comes from localhost."); + } + + // Example: Move query parameters into own JSON property + if (request.Url.Contains("?")) + { + var parts = request.Url.Split('?'); + request.Url = parts[0]; + request.Properties = new Dictionary() { + { "Parameter", parts[1] } + }; + } + // Example: Modify content if (request.HttpHeaders.TryGetValue("Content-Type", out var contentType) && contentType == "application/json") { diff --git a/Thinktecture.Relay.CustomCodeDemo/Thinktecture.Relay.CustomCodeDemo.csproj b/Thinktecture.Relay.CustomCodeDemo/Thinktecture.Relay.CustomCodeDemo.csproj index 7cb34702..cd87df82 100644 --- a/Thinktecture.Relay.CustomCodeDemo/Thinktecture.Relay.CustomCodeDemo.csproj +++ b/Thinktecture.Relay.CustomCodeDemo/Thinktecture.Relay.CustomCodeDemo.csproj @@ -76,6 +76,7 @@ + diff --git a/Thinktecture.Relay.OnPremiseConnector/IdentityModel/HttpClientExtensions.cs b/Thinktecture.Relay.OnPremiseConnector/IdentityModel/HttpClientExtensions.cs index ec8a105c..b3dc7909 100644 --- a/Thinktecture.Relay.OnPremiseConnector/IdentityModel/HttpClientExtensions.cs +++ b/Thinktecture.Relay.OnPremiseConnector/IdentityModel/HttpClientExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Net.Http; using System.Net.Http.Headers; @@ -16,6 +17,10 @@ public static class HttpClientExtensions /// The password to set. public static void SetBasicAuthentication(this HttpClient client, string userName, string password) { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (userName == null) throw new ArgumentNullException(nameof(userName)); + if (password == null) throw new ArgumentNullException(nameof(password)); + client.DefaultRequestHeaders.Authorization = new BasicAuthenticationHeaderValue(userName, password); } @@ -27,9 +32,24 @@ public static void SetBasicAuthentication(this HttpClient client, string userNam /// The token to set. public static void SetToken(this HttpClient client, string scheme, string token) { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (scheme == null) throw new ArgumentNullException(nameof(scheme)); + if (token == null) throw new ArgumentNullException(nameof(token)); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(scheme, token); } + /// + /// Gets header value for token authentication. + /// + /// The to get the header value from. + public static string GetToken(this HttpClient client) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + + return client.DefaultRequestHeaders?.Authorization?.Parameter; + } + /// /// Sets headers for bearer token authentication. /// @@ -37,6 +57,9 @@ public static void SetToken(this HttpClient client, string scheme, string token) /// The token to set as a bearer token. public static void SetBearerToken(this HttpClient client, string token) { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (token == null) throw new ArgumentNullException(nameof(token)); + client.SetToken("Bearer", token); } } diff --git a/Thinktecture.Relay.OnPremiseConnector/SignalR/RelayServerHttpConnection.cs b/Thinktecture.Relay.OnPremiseConnector/SignalR/RelayServerHttpConnection.cs index c984f78d..8e88954d 100644 --- a/Thinktecture.Relay.OnPremiseConnector/SignalR/RelayServerHttpConnection.cs +++ b/Thinktecture.Relay.OnPremiseConnector/SignalR/RelayServerHttpConnection.cs @@ -26,8 +26,12 @@ public RelayServerHttpConnection(ILogger logger, Uri relayServerUri, TimeSpan re _requestTimeout = requestTimeout; #if NETSTANDARD2_0 - ServicePointManager.FindServicePoint(relayServerUri).ConnectionLeaseTimeout = requestTimeout.Milliseconds; - ServicePointManager.DnsRefreshTimeout = requestTimeout.Milliseconds; + ServicePointManager.FindServicePoint(relayServerUri).ConnectionLeaseTimeout = (int)requestTimeout.TotalMilliseconds; + + if (requestTimeout.Milliseconds < ServicePointManager.DnsRefreshTimeout) + { + ServicePointManager.DnsRefreshTimeout = (int)requestTimeout.TotalMilliseconds; + } #endif CreateHttpClient(); @@ -78,9 +82,11 @@ public void SetBearerToken(string accessToken) private void RecreateHttpClient() { + var token = _httpClient.GetToken(); var oldClient = _httpClient; CreateHttpClient(); + _httpClient.SetBearerToken(token); oldClient?.Dispose(); } diff --git a/Thinktecture.Relay.OnPremiseConnectorService/OnPremisesService.cs b/Thinktecture.Relay.OnPremiseConnectorService/OnPremisesService.cs index 692d45c9..58f2370f 100644 --- a/Thinktecture.Relay.OnPremiseConnectorService/OnPremisesService.cs +++ b/Thinktecture.Relay.OnPremiseConnectorService/OnPremisesService.cs @@ -94,6 +94,13 @@ public async Task StartAsync() _connector.RegisterOnPremiseTarget(onPremiseTarget.Key, handlerType); } + var timeout = (int)section.RequestTimeout.TotalMilliseconds; + ServicePointManager.FindServicePoint(new Uri(section.BaseUrl)).ConnectionLeaseTimeout = timeout; + if (timeout < ServicePointManager.DnsRefreshTimeout) + { + ServicePointManager.DnsRefreshTimeout = timeout; + } + await _connector.ConnectAsync().ConfigureAwait(false); } catch (Exception ex) diff --git a/Thinktecture.Relay.Server.Test/Communication/BackendCommunicationTests/Prepare.cs b/Thinktecture.Relay.Server.Test/Communication/BackendCommunicationTests/Prepare.cs index f69e8ca9..39427e80 100644 --- a/Thinktecture.Relay.Server.Test/Communication/BackendCommunicationTests/Prepare.cs +++ b/Thinktecture.Relay.Server.Test/Communication/BackendCommunicationTests/Prepare.cs @@ -1,6 +1,7 @@ using System.Reactive.Subjects; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Thinktecture.Relay.Server.Communication.RabbitMq; using Thinktecture.Relay.Server.OnPremise; namespace Thinktecture.Relay.Server.Communication.BackendCommunicationTests @@ -15,7 +16,7 @@ public Prepare() var responseSubject = new Subject(); MessageDispatcherMock.Setup(d => d.OnResponseReceived()).Returns(responseSubject); - var acknowledgeSubject = new Subject(); + var acknowledgeSubject = new Subject(); MessageDispatcherMock.Setup(d => d.OnAcknowledgeReceived()).Returns(acknowledgeSubject); } diff --git a/Thinktecture.Relay.Server/Communication/BackendCommunication.cs b/Thinktecture.Relay.Server/Communication/BackendCommunication.cs index f89e6f69..d52bd064 100644 --- a/Thinktecture.Relay.Server/Communication/BackendCommunication.cs +++ b/Thinktecture.Relay.Server/Communication/BackendCommunication.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Newtonsoft.Json; using Serilog; +using Thinktecture.Relay.Server.Communication.RabbitMq; using Thinktecture.Relay.Server.Config; using Thinktecture.Relay.Server.OnPremise; using Thinktecture.Relay.Server.Repository; @@ -85,7 +86,10 @@ public async Task RegisterOnPremiseAsync(IOnPremiseConnectionContext onPremiseCo _connectionContexts.TryAdd(onPremiseConnectionContext.ConnectionId, onPremiseConnectionContext); - await ProvideLinkConfigurationAsync(onPremiseConnectionContext).ConfigureAwait(false); + if (onPremiseConnectionContext.SupportsConfiguration) + { + await ProvideLinkConfigurationAsync(onPremiseConnectionContext).ConfigureAwait(false); + } } public async Task UnregisterOnPremiseConnectionAsync(string connectionId) @@ -145,14 +149,14 @@ public void SendOnPremiseConnectorRequest(Guid linkId, IOnPremiseConnectorReques _messageDispatcher.DispatchRequest(linkId, request); } - public async Task AcknowledgeOnPremiseConnectorRequestAsync(Guid originId, string acknowledgeId, string connectionId) + public async Task AcknowledgeOnPremiseConnectorRequestAsync(Guid originId, string connectionId, string acknowledgeId) { CheckDisposed(); if (originId != Guid.Empty) { - _logger?.Debug("Dispatching acknowledge. origin-id={OriginId}, acknowledge-id={AcknowledgeId}, connection-id={ConnectionId}", originId, acknowledgeId, connectionId); - _messageDispatcher.DispatchAcknowledge(originId, acknowledgeId); + _logger?.Debug("Dispatching acknowledge. origin-id={OriginId}, connection-id={ConnectionId}, acknowledge-id={AcknowledgeId}", originId, connectionId, acknowledgeId); + _messageDispatcher.DispatchAcknowledge(originId, connectionId, acknowledgeId); } if (connectionId != null) @@ -245,7 +249,15 @@ private IDisposable StartReceivingAcknowledges() { _logger?.Debug("Start receiving acknowledges from dispatcher. origin-id={OriginId}", OriginId); - return _messageDispatcher.OnAcknowledgeReceived().Subscribe(acknowledgeId => _messageDispatcher.AcknowledgeRequest(acknowledgeId)); + return _messageDispatcher.OnAcknowledgeReceived().Subscribe(AcknowledgeRequest); + } + + private void AcknowledgeRequest(IAcknowledgeRequest acknowledgeRequest) + { + if (_connectionContexts.TryGetValue(acknowledgeRequest.ConnectionId, out var connectionContext)) + { + _messageDispatcher.AcknowledgeRequest(connectionContext.LinkId, acknowledgeRequest.AcknowledgeId); + } } private async Task GetOnPremiseTargetResponseAsync(IOnPremiseConnectorCallback callback, TimeSpan requestTimeout, CancellationToken cancellationToken) diff --git a/Thinktecture.Relay.Server/Communication/IBackendCommunication.cs b/Thinktecture.Relay.Server/Communication/IBackendCommunication.cs index cea1f7ce..71083375 100644 --- a/Thinktecture.Relay.Server/Communication/IBackendCommunication.cs +++ b/Thinktecture.Relay.Server/Communication/IBackendCommunication.cs @@ -10,7 +10,7 @@ public interface IBackendCommunication Guid OriginId { get; } Task GetResponseAsync(string requestId, TimeSpan? requestTimeout = null); void SendOnPremiseConnectorRequest(Guid linkId, IOnPremiseConnectorRequest request); - Task AcknowledgeOnPremiseConnectorRequestAsync(Guid originId, string acknowledgeId, string connectionId); + Task AcknowledgeOnPremiseConnectorRequestAsync(Guid originId, string connectionId, string acknowledgeId); Task RenewLastActivityAsync(string connectionId); Task RegisterOnPremiseAsync(IOnPremiseConnectionContext onPremiseConnectionContext); Task UnregisterOnPremiseConnectionAsync(string connectionId); diff --git a/Thinktecture.Relay.Server/Communication/IMessageDispatcher.cs b/Thinktecture.Relay.Server/Communication/IMessageDispatcher.cs index 096a4f90..4138df1f 100644 --- a/Thinktecture.Relay.Server/Communication/IMessageDispatcher.cs +++ b/Thinktecture.Relay.Server/Communication/IMessageDispatcher.cs @@ -1,4 +1,5 @@ using System; +using Thinktecture.Relay.Server.Communication.RabbitMq; using Thinktecture.Relay.Server.OnPremise; namespace Thinktecture.Relay.Server.Communication @@ -7,12 +8,12 @@ public interface IMessageDispatcher : IDisposable { IObservable OnRequestReceived(Guid linkId, string connectionId, bool autoAck); IObservable OnResponseReceived(); - IObservable OnAcknowledgeReceived(); + IObservable OnAcknowledgeReceived(); - void AcknowledgeRequest(string acknowledgeId); + void AcknowledgeRequest(Guid linkId, string acknowledgeId); void DispatchRequest(Guid linkId, IOnPremiseConnectorRequest request); void DispatchResponse(Guid originId, IOnPremiseConnectorResponse response); - void DispatchAcknowledge(Guid originId, string acknowledgeId); + void DispatchAcknowledge(Guid originId, string connectionId, string acknowledgeId); } } diff --git a/Thinktecture.Relay.Server/Communication/IOnPremiseConnectionContext.cs b/Thinktecture.Relay.Server/Communication/IOnPremiseConnectionContext.cs deleted file mode 100644 index 6a1d1b97..00000000 --- a/Thinktecture.Relay.Server/Communication/IOnPremiseConnectionContext.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Thinktecture.Relay.Server.OnPremise; - -namespace Thinktecture.Relay.Server.Communication -{ - public interface IOnPremiseConnectionContext - { - string ConnectionId { get; set; } - Guid LinkId { get; set; } - bool IsActive { get; set; } - DateTime LastLocalActivity { get; set; } - Func RequestAction { get; set; } - string IpAddress { get; set; } - string UserName { get; set; } - string Role { get; set; } - int ConnectorVersion { get; set; } - string ConnectorAssemblyVersion { get; set; } - bool SupportsAck { get; } - bool SupportsHeartbeat { get; } - bool SupportsConfiguration { get; } - DateTime NextHeartbeat { get; set; } - } -} diff --git a/Thinktecture.Relay.Server/Communication/InProcess/InProcessMessageDispatcher.cs b/Thinktecture.Relay.Server/Communication/InProcess/InProcessMessageDispatcher.cs deleted file mode 100644 index 34fd4a9c..00000000 --- a/Thinktecture.Relay.Server/Communication/InProcess/InProcessMessageDispatcher.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading.Tasks; -using Serilog; -using Thinktecture.Relay.Server.Config; -using Thinktecture.Relay.Server.OnPremise; - -namespace Thinktecture.Relay.Server.Communication.InProcess -{ - internal class InProcessMessageDispatcher : IMessageDispatcher, IDisposable - { - private readonly ILogger _logger; - private readonly Dictionary _requestSubjectLookup; - private readonly ConcurrentDictionary> _responseSubjectLookup; - private readonly Guid _originId; - - private bool _disposed; - - public InProcessMessageDispatcher(ILogger logger, IPersistedSettings persistedSettings) - { - _logger = logger; - _requestSubjectLookup = new Dictionary(); - _responseSubjectLookup = new ConcurrentDictionary>(); - _originId = persistedSettings?.OriginId ?? throw new ArgumentNullException(nameof(persistedSettings)); - } - - public IObservable OnRequestReceived(Guid linkId, string connectionId, bool autoAck) - { - if (connectionId == null) - throw new ArgumentNullException(nameof(connectionId)); - - CheckDisposed(); - _logger?.Information("Creating request subscription for link {LinkId} and connection {ConnectionId}", linkId, connectionId); - - return Observable.Create(observer => - { - var ctx = GetRequestSubjectContext(linkId, connectionId); - var subscription = ctx.Subject.Subscribe(observer.OnNext); - - return new DelegatingDisposable(_logger, () => - { - subscription.Dispose(); - - lock (_requestSubjectLookup) - { - ctx.RemoveConnection(connectionId); - - if (ctx.ConnectionCount == 0) - _requestSubjectLookup.Remove(linkId); - } - }); - }); - } - - public IObservable OnResponseReceived() - { - CheckDisposed(); - _logger?.Information("Creating response subscription"); - - return Observable.Create(observer => - { - var subject = GetResponseSubject(_originId); - var subscription = subject.Subscribe(observer.OnNext); - - return new DelegatingDisposable(_logger, () => - { - subscription.Dispose(); - _responseSubjectLookup.TryRemove(_originId, out var sub); - }); - }); - } - - public IObservable OnAcknowledgeReceived() - { - return Observable.Empty(); - } - - public void AcknowledgeRequest(string acknowledgeId) - { - // no ack here - } - - public void DispatchRequest(Guid linkId, IOnPremiseConnectorRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - CheckDisposed(); - _logger?.Debug("Dispatching request. link-id={LinkId}, request-id={RequestId}, method={HttpMethod}, url={RequestUrl}", linkId, request.RequestId, request.HttpMethod, request.Url); - - TryGetRequestSubject(linkId)?.OnNext(request); - } - - public void DispatchResponse(Guid originId, IOnPremiseConnectorResponse response) - { - if (response == null) - throw new ArgumentNullException(nameof(response)); - - CheckDisposed(); - _logger?.Debug("Dispatching response. origin-id={OriginId}, request-id={RequestId}, status-code={ResponseStatusCode}", originId, response.RequestId, response.StatusCode); - - GetResponseSubject(originId).OnNext(response); - } - - public void DispatchAcknowledge(Guid originId, string acknowledgeId) - { - } - - private Subject TryGetRequestSubject(Guid linkId) - { - lock (_requestSubjectLookup) - { - if (_requestSubjectLookup.TryGetValue(linkId, out var ctx)) - return ctx.Subject; - } - - return null; - } - - private RequestSubjectContext GetRequestSubjectContext(Guid linkId, string connectionId) - { - lock (_requestSubjectLookup) - { - if (!_requestSubjectLookup.TryGetValue(linkId, out var ctx)) - { - ctx = new RequestSubjectContext(); - _requestSubjectLookup.Add(linkId, ctx); - } - - ctx.AddConnection(connectionId); - - return ctx; - } - } - - private Subject GetResponseSubject(Guid originId) - { - return _responseSubjectLookup.GetOrAdd(originId, id => new Subject()); - } - - private void CheckDisposed() - { - if (_disposed) - throw new ObjectDisposedException(GetType().Name); - } - - public void Dispose() - { - if (!_disposed) - { - _disposed = true; - - IEnumerable disposables; - lock (_requestSubjectLookup) - { - disposables = _requestSubjectLookup.Values.ToArray(); - } - - DisposeSubjects(disposables); - DisposeSubjects(_responseSubjectLookup.Values); - } - } - - private void DisposeSubjects(IEnumerable subjects) - { - try - { - foreach (var subject in subjects) - { - subject.Dispose(); - } - } - catch (Exception ex) - { - _logger?.Error(ex, "Error during disposing of a subject"); - } - } - } -} diff --git a/Thinktecture.Relay.Server/Communication/InProcess/RequestSubjectContext.cs b/Thinktecture.Relay.Server/Communication/InProcess/RequestSubjectContext.cs deleted file mode 100644 index c2375920..00000000 --- a/Thinktecture.Relay.Server/Communication/InProcess/RequestSubjectContext.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Reactive.Subjects; -using Thinktecture.Relay.Server.OnPremise; - -namespace Thinktecture.Relay.Server.Communication.InProcess -{ - public class RequestSubjectContext : IDisposable - { - public Subject Subject { get; } - public int ConnectionCount => _connectionIds.Count; - - private readonly ConcurrentDictionary _connectionIds; - - public RequestSubjectContext() - { - Subject = new Subject(); - _connectionIds = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - } - - public void AddConnection(string connectionId) - { - _connectionIds.TryAdd(connectionId, connectionId); - } - - public void RemoveConnection(string connectionId) - { - _connectionIds.TryRemove(connectionId, out var id); - } - - public void Dispose() - { - Subject.Dispose(); - } - } -} diff --git a/Thinktecture.Relay.Server/Communication/OnPremiseConnectionHeartbeater.cs b/Thinktecture.Relay.Server/Communication/OnPremiseConnectionHeartbeater.cs index 113e80ab..4aaa2876 100644 --- a/Thinktecture.Relay.Server/Communication/OnPremiseConnectionHeartbeater.cs +++ b/Thinktecture.Relay.Server/Communication/OnPremiseConnectionHeartbeater.cs @@ -100,7 +100,7 @@ private async Task SendHeartbeatAsync(IOnPremiseConnectionContext connectionCont private async Task MarkConnectionInactiveIfTimedOut(IOnPremiseConnectionContext connectionContext) { - if (connectionContext.LastLocalActivity + _configuration.ActiveConnectionTimeout < DateTime.UtcNow) + if (connectionContext.IsActive && connectionContext.LastLocalActivity + _configuration.ActiveConnectionTimeout < DateTime.UtcNow) { await _backendCommunication.DeactivateOnPremiseConnectionAsync(connectionContext.ConnectionId); } diff --git a/Thinktecture.Relay.Server/Communication/RabbitMq/AcknowledgeRequest.cs b/Thinktecture.Relay.Server/Communication/RabbitMq/AcknowledgeRequest.cs new file mode 100644 index 00000000..78aaae2b --- /dev/null +++ b/Thinktecture.Relay.Server/Communication/RabbitMq/AcknowledgeRequest.cs @@ -0,0 +1,8 @@ +namespace Thinktecture.Relay.Server.Communication.RabbitMq +{ + internal class AcknowledgeRequest : IAcknowledgeRequest + { + public string ConnectionId { get; set; } + public string AcknowledgeId { get; set; } + } +} diff --git a/Thinktecture.Relay.Server/Communication/RabbitMq/IAcknowledgeRequest.cs b/Thinktecture.Relay.Server/Communication/RabbitMq/IAcknowledgeRequest.cs new file mode 100644 index 00000000..b7a8116d --- /dev/null +++ b/Thinktecture.Relay.Server/Communication/RabbitMq/IAcknowledgeRequest.cs @@ -0,0 +1,8 @@ +namespace Thinktecture.Relay.Server.Communication.RabbitMq +{ + public interface IAcknowledgeRequest + { + string AcknowledgeId { get; } + string ConnectionId { get; } + } +} diff --git a/Thinktecture.Relay.Server/Communication/RabbitMq/IRabbitMqAcknowledgeableChannel.cs b/Thinktecture.Relay.Server/Communication/RabbitMq/IRabbitMqAcknowledgeableChannel.cs deleted file mode 100644 index 9b476a39..00000000 --- a/Thinktecture.Relay.Server/Communication/RabbitMq/IRabbitMqAcknowledgeableChannel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Thinktecture.Relay.Server.Communication.RabbitMq -{ - internal interface IRabbitMqAcknowledgeableChannel : IRabbitMqChannel - where TMessage : class - { - IObservable OnReceived(bool autoAck); - void Acknowledge(string acknowledgeId); - } -} diff --git a/Thinktecture.Relay.Server/Communication/RabbitMq/IRabbitMqRequestChannel.cs b/Thinktecture.Relay.Server/Communication/RabbitMq/IRabbitMqRequestChannel.cs new file mode 100644 index 00000000..26f49e0f --- /dev/null +++ b/Thinktecture.Relay.Server/Communication/RabbitMq/IRabbitMqRequestChannel.cs @@ -0,0 +1,11 @@ +using System; +using Thinktecture.Relay.Server.OnPremise; + +namespace Thinktecture.Relay.Server.Communication.RabbitMq +{ + internal interface IRabbitMqRequestChannel : IRabbitMqChannel + { + void Acknowledge(string acknowledgeId); + IObservable OnReceived(bool autoAck); + } +} diff --git a/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqAcknowledgeChannel.cs b/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqAcknowledgeChannel.cs index f6194bcb..2f893e68 100644 --- a/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqAcknowledgeChannel.cs +++ b/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqAcknowledgeChannel.cs @@ -1,42 +1,14 @@ -using System; -using System.Threading.Tasks; using RabbitMQ.Client; using Serilog; using Thinktecture.Relay.Server.Config; namespace Thinktecture.Relay.Server.Communication.RabbitMq { - internal class RabbitMqAcknowledgeChannel : RabbitMqChannelBase, IRabbitMqAcknowledgeableChannel + internal class RabbitMqAcknowledgeChannel : RabbitMqChannelBase { public RabbitMqAcknowledgeChannel(ILogger logger, IConnection connection, IConfiguration configuration, string exchange, string channelId, string queuePrefix) : base(logger, connection, configuration, exchange, channelId, queuePrefix) { } - - public override IObservable OnReceived() - { - return CreateObservable(); - } - - public override Task Dispatch(string message) - { - var data = Serialize(message, out var properties); - Send(data, properties); - - return Task.CompletedTask; - } - - public IObservable OnReceived(bool autoAck) - { - return CreateObservable(); - } - - public void Acknowledge(string acknowledgeId) - { - if (UInt64.TryParse(acknowledgeId, out var deliveryTag)) - { - Acknowledge(deliveryTag); - } - } } } diff --git a/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqChannelBase.cs b/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqChannelBase.cs index 18c6ad05..0a665fba 100644 --- a/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqChannelBase.cs +++ b/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqChannelBase.cs @@ -22,8 +22,7 @@ internal abstract class RabbitMqChannelBase : IRabbitMqChannel OnReceived(); - public abstract Task Dispatch(TMessage message); + + public virtual Task Dispatch(TMessage request) + { + var data = Serialize(request, out var properties); + Send(data, properties); + + return Task.CompletedTask; + } protected void DeclareExchange() { @@ -109,7 +115,7 @@ protected void Unbind() } protected IObservable CreateObservable(bool autoAck = true, Action callback = null) - where TMessageType : TMessage + where TMessageType : class, TMessage { return Observable.Create(observer => { @@ -186,4 +192,19 @@ public void Dispose() } } } + + internal class RabbitMqChannelBase : RabbitMqChannelBase + where TMessage : class + where TInstance : class, TMessage + { + protected RabbitMqChannelBase(ILogger logger, IConnection connection, IConfiguration configuration, string exchange, string channelId, string queuePrefix) + : base(logger, connection, configuration, exchange, channelId, queuePrefix) + { + } + + public override IObservable OnReceived() + { + return CreateObservable(); + } + } } diff --git a/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqMessageDispatcherHandler.cs b/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqMessageDispatcher.cs similarity index 68% rename from Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqMessageDispatcherHandler.cs rename to Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqMessageDispatcher.cs index d1ac5d85..213ff55e 100644 --- a/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqMessageDispatcherHandler.cs +++ b/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqMessageDispatcher.cs @@ -7,7 +7,7 @@ namespace Thinktecture.Relay.Server.Communication.RabbitMq { - public class RabbitMqMessageDispatcherHandler : IMessageDispatcher + internal class RabbitMqMessageDispatcher : IMessageDispatcher { private const string _EXCHANGE_NAME = "RelayServer"; private const string _REQUEST_QUEUE_PREFIX = "Request"; @@ -18,11 +18,11 @@ public class RabbitMqMessageDispatcherHandler : IMessageDispatcher private readonly IConnection _connection; private readonly IConfiguration _configuration; private readonly Guid _originId; - private readonly ConcurrentDictionary _rabbitMqRequestChannels = new ConcurrentDictionary(); - private readonly ConcurrentDictionary _rabbitMqResponseChannels = new ConcurrentDictionary(); - private readonly ConcurrentDictionary _rabbitMqAcknowledgeChannels = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _rabbitMqRequestChannels = new ConcurrentDictionary(); + private readonly ConcurrentDictionary> _rabbitMqResponseChannels = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _rabbitMqAcknowledgeChannels = new ConcurrentDictionary>(); - public RabbitMqMessageDispatcherHandler(ILogger logger, IConnection connection, IConfiguration configuration, IPersistedSettings persistedSettings) + public RabbitMqMessageDispatcher(ILogger logger, IConnection connection, IConfiguration configuration, IPersistedSettings persistedSettings) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _connection = connection ?? throw new ArgumentNullException(nameof(connection)); @@ -41,14 +41,14 @@ public IObservable OnResponseReceived() return EnsureResponseChannel(_originId.ToString()).OnReceived(); } - public IObservable OnAcknowledgeReceived() + public IObservable OnAcknowledgeReceived() { return EnsureAcknowledgeChannel(_originId.ToString()).OnReceived(); } - public void AcknowledgeRequest(string acknowledgeId) + public void AcknowledgeRequest(Guid linkId, string acknowledgeId) { - if (_rabbitMqAcknowledgeChannels.TryGetValue(_originId.ToString(), out var rabbitMqChannel)) + if (_rabbitMqRequestChannels.TryGetValue(linkId.ToString(), out var rabbitMqChannel)) { rabbitMqChannel.Acknowledge(acknowledgeId); } @@ -64,23 +64,23 @@ public void DispatchResponse(Guid originId, IOnPremiseConnectorResponse response EnsureResponseChannel(originId.ToString()).Dispatch(response); } - public void DispatchAcknowledge(Guid originId, string acknowledgeId) + public void DispatchAcknowledge(Guid originId, string connectionId, string acknowledgeId) { - EnsureAcknowledgeChannel(originId.ToString()).Dispatch(acknowledgeId); + EnsureAcknowledgeChannel(originId.ToString()).Dispatch(new AcknowledgeRequest() { ConnectionId = connectionId, AcknowledgeId = acknowledgeId }); } - private RabbitMqRequestChannel EnsureRequestChannel(string channelId) + private IRabbitMqRequestChannel EnsureRequestChannel(string channelId) { if (_rabbitMqRequestChannels.TryGetValue(channelId, out var rabbitMqChannel)) return rabbitMqChannel; - rabbitMqChannel = new RabbitMqRequestChannel(_logger.ForContext(), _connection, _configuration, _EXCHANGE_NAME, channelId, _REQUEST_QUEUE_PREFIX); + rabbitMqChannel = new RabbitMqRequestChannel(_logger.ForContext(), _connection, _configuration, _EXCHANGE_NAME, channelId, _REQUEST_QUEUE_PREFIX, _originId); _rabbitMqRequestChannels[channelId] = rabbitMqChannel; return rabbitMqChannel; } - private RabbitMqResponseChannel EnsureResponseChannel(string channelId) + private IRabbitMqChannel EnsureResponseChannel(string channelId) { if (_rabbitMqResponseChannels.TryGetValue(channelId, out var rabbitMqChannel)) return rabbitMqChannel; @@ -91,7 +91,7 @@ private RabbitMqResponseChannel EnsureResponseChannel(string channelId) return rabbitMqChannel; } - private RabbitMqAcknowledgeChannel EnsureAcknowledgeChannel(string channelId) + private IRabbitMqChannel EnsureAcknowledgeChannel(string channelId) { if (_rabbitMqAcknowledgeChannels.TryGetValue(channelId, out var rabbitMqChannel)) return rabbitMqChannel; diff --git a/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqRequestChannel.cs b/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqRequestChannel.cs index b6f6453f..8ce002fc 100644 --- a/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqRequestChannel.cs +++ b/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqRequestChannel.cs @@ -8,11 +8,19 @@ namespace Thinktecture.Relay.Server.Communication.RabbitMq { - internal class RabbitMqRequestChannel : RabbitMqChannelBase, IRabbitMqAcknowledgeableChannel + internal class RabbitMqRequestChannel : RabbitMqChannelBase, IRabbitMqRequestChannel { - public RabbitMqRequestChannel(ILogger logger, IConnection connection, IConfiguration configuration, string exchange, string channelId, string queuePrefix) + private readonly Guid _originId; + + public RabbitMqRequestChannel(ILogger logger, IConnection connection, IConfiguration configuration, string exchange, string channelId, string queuePrefix, Guid originId) : base(logger, connection, configuration, exchange, channelId, queuePrefix) { + _originId = originId; + } + + public override IObservable OnReceived() + { + throw new NotSupportedException(); } public override Task Dispatch(IOnPremiseConnectorRequest message) @@ -30,12 +38,6 @@ public override Task Dispatch(IOnPremiseConnectorRequest message) return Task.CompletedTask; } - - public override IObservable OnReceived() - { - return OnReceived(true); - } - public IObservable OnReceived(bool autoAck) { return CreateObservable(autoAck, (request, deliveryTag) => @@ -55,7 +57,7 @@ public IObservable OnReceived(bool autoAck) case AcknowledgmentMode.Default: case AcknowledgmentMode.Manual: request.AcknowledgeId = deliveryTag.ToString(); - request.AcknowledgeOriginId = new Guid(ChannelId); + request.AcknowledgeOriginId = _originId; Logger?.Verbose("Request acknowledge id was set. request-id={RequestId}, acknowledge-id={AcknowledgeId}", request.RequestId, request.AcknowledgeId); break; } diff --git a/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqResponseChannel.cs b/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqResponseChannel.cs index 9ea84b8f..b055d02f 100644 --- a/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqResponseChannel.cs +++ b/Thinktecture.Relay.Server/Communication/RabbitMq/RabbitMqResponseChannel.cs @@ -1,5 +1,3 @@ -using System; -using System.Threading.Tasks; using RabbitMQ.Client; using Serilog; using Thinktecture.Relay.Server.Config; @@ -7,24 +5,11 @@ namespace Thinktecture.Relay.Server.Communication.RabbitMq { - internal class RabbitMqResponseChannel : RabbitMqChannelBase + internal class RabbitMqResponseChannel : RabbitMqChannelBase { public RabbitMqResponseChannel(ILogger logger, IConnection connection, IConfiguration configuration, string exchange, string channelId, string queuePrefix) : base(logger, connection, configuration, exchange, channelId, queuePrefix) { } - - public override IObservable OnReceived() - { - return CreateObservable(); - } - - public override Task Dispatch(IOnPremiseConnectorResponse message) - { - var data = Serialize(message, out var properties); - Send(data, properties); - - return Task.CompletedTask; - } } } diff --git a/Thinktecture.Relay.Server/Config/Configuration.cs b/Thinktecture.Relay.Server/Config/Configuration.cs index f0201137..3517b78b 100644 --- a/Thinktecture.Relay.Server/Config/Configuration.cs +++ b/Thinktecture.Relay.Server/Config/Configuration.cs @@ -1,6 +1,7 @@ using System; using System.Configuration; using System.IO; +using System.Reflection; using System.Web.Http; using Serilog; @@ -90,7 +91,7 @@ public Configuration(ILogger logger) OnPremiseConnectorCallbackTimeout = tmpTimeSpan; } - TraceFileDirectory = GetValue(nameof(TraceFileDirectory)) ?? "tracefiles"; + TraceFileDirectory = GetPathValue(nameof(TraceFileDirectory), logger) ?? "tracefiles"; LinkPasswordLength = 100; if (Int32.TryParse(GetValue(nameof(LinkPasswordLength)), out var tmpInt)) @@ -148,13 +149,13 @@ public Configuration(ILogger logger) Port = tmpInt; } - ManagementWebLocation = GetValue(nameof(ManagementWebLocation)); + ManagementWebLocation = GetPathValue(nameof(ManagementWebLocation), logger); if (String.IsNullOrWhiteSpace(ManagementWebLocation)) { ManagementWebLocation = "ManagementWeb"; } - TemporaryRequestStoragePath = GetValue(nameof(TemporaryRequestStoragePath)); + TemporaryRequestStoragePath = GetPathValue(nameof(TemporaryRequestStoragePath), logger); if (String.IsNullOrWhiteSpace(TemporaryRequestStoragePath)) { TemporaryRequestStoragePath = null; @@ -172,7 +173,7 @@ public Configuration(ILogger logger) ActiveConnectionTimeout = tmpTimeSpan; } - CustomCodeAssemblyPath = GetValue(nameof(CustomCodeAssemblyPath)); + CustomCodeAssemblyPath = GetPathValue(nameof(CustomCodeAssemblyPath), logger); if (String.IsNullOrWhiteSpace(CustomCodeAssemblyPath)) { CustomCodeAssemblyPath = null; @@ -188,7 +189,7 @@ public Configuration(ILogger logger) if (String.IsNullOrEmpty(SharedSecret) && String.IsNullOrEmpty(OAuthCertificate)) { - if (String.IsNullOrEmpty(TemporaryRequestStoragePath)) + if (String.IsNullOrEmpty(TemporaryRequestStoragePath)) // assume Multi-Server operation mode when this folder is configured { logger?.Warning("No SharedSecret or OAuthCertificate is configured. Please configure one of them. Continuing with a random value which will make all tokens invalid on restart."); SharedSecret = Convert.ToBase64String(Guid.NewGuid().ToByteArray()); @@ -294,6 +295,33 @@ private string GetValue(string settingName) ?? ConfigurationManager.AppSettings[settingName]; } + private string GetPathValue(string settingName, ILogger logger) + { + var value = GetValue(settingName); + + if (String.IsNullOrEmpty(value)) + { + return null; + } + + if (!Path.IsPathRooted(value)) + { + var basePath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + + // we have a relative path, so we need to combine it with current execution directory + logger?.Verbose($"Configured path for {{SettingName}} is relative ({{{settingName}}}) to base path {{BasePath}} " , settingName, value, basePath); + + value = Path.GetFullPath(Path.Combine(basePath, value)); + logger?.Verbose($"Converted path for {{SettingName}} is absolute: {{{settingName}}}" , settingName, value); + } + else + { + logger?.Verbose($"Configured path for {{SettingName}} is absolute: {{{settingName}}}" , settingName, value); + } + + return value; + } + private void LogSettings(ILogger logger) { logger?.Verbose("Setting {ConfigurationProperty}: {ConfigurationValue}", nameof(RabbitMqConnectionString), RabbitMqConnectionString); diff --git a/Thinktecture.Relay.Server/Controller/ClientController.cs b/Thinktecture.Relay.Server/Controller/ClientController.cs index e46b0102..cf298a8e 100644 --- a/Thinktecture.Relay.Server/Controller/ClientController.cs +++ b/Thinktecture.Relay.Server/Controller/ClientController.cs @@ -73,6 +73,7 @@ public async Task Relay(string fullPathToOnPremiseEndpoint) } var request = await _onPremiseRequestBuilder.BuildFromHttpRequest(Request, _backendCommunication.OriginId, pathInformation.PathWithoutUserName).ConfigureAwait(false); + PrepareRequestBodyForRelaying((OnPremiseConnectorRequest)request); var statusCode = HttpStatusCode.GatewayTimeout; IOnPremiseConnectorResponse response = null; @@ -121,8 +122,6 @@ public async Task Relay(string fullPathToOnPremiseEndpoint) private void SendOnPremiseConnectorRequest(Guid linkId, IOnPremiseConnectorRequest request) { - PrepareRequestBodyForRelaying((OnPremiseConnectorRequest)request); - _logger?.Verbose("Sending on premise connector request. request-id={RequestId}, link-id={LinkId}", request.RequestId, linkId); _backendCommunication.SendOnPremiseConnectorRequest(linkId, request); } diff --git a/Thinktecture.Relay.Server/Controller/RequestController.cs b/Thinktecture.Relay.Server/Controller/RequestController.cs index 36fc8acf..3ef600cf 100644 --- a/Thinktecture.Relay.Server/Controller/RequestController.cs +++ b/Thinktecture.Relay.Server/Controller/RequestController.cs @@ -42,8 +42,8 @@ public IHttpActionResult Get(string requestId) [HttpGet] public IHttpActionResult Acknowledge([FromUri(Name = "oid")] Guid originId, [FromUri(Name = "aid")] string acknowledgeId, [FromUri(Name = "cid")] string connectionId = null) { - _logger?.Verbose("Received acknowledge. origin-id={OriginId}, acknowledge-id={AcknowledgeId}, connection-id={ConnectionId}", originId, acknowledgeId, connectionId); - _backendCommunication.AcknowledgeOnPremiseConnectorRequestAsync(originId, acknowledgeId, connectionId); + _logger?.Verbose("Received acknowledge. origin-id={OriginId}, connection-id={ConnectionId}, acknowledge-id={AcknowledgeId}", originId, connectionId, acknowledgeId); + _backendCommunication.AcknowledgeOnPremiseConnectorRequestAsync(originId, connectionId, acknowledgeId); return Ok(); } diff --git a/Thinktecture.Relay.Server/DependencyInjection/RelayServerModule.cs b/Thinktecture.Relay.Server/DependencyInjection/RelayServerModule.cs index 59039ef7..2d04f476 100644 --- a/Thinktecture.Relay.Server/DependencyInjection/RelayServerModule.cs +++ b/Thinktecture.Relay.Server/DependencyInjection/RelayServerModule.cs @@ -47,7 +47,7 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().AsImplementedInterfaces().SingleInstance(); builder.RegisterType().AsImplementedInterfaces().SingleInstance(); builder.Register(ctx => ctx.Resolve().CreateConnection()).AsImplementedInterfaces().SingleInstance(); - builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); builder.RegisterType().AsImplementedInterfaces().SingleInstance() .OnActivated(args => args.Instance.Prepare()); @@ -84,6 +84,7 @@ protected override void Load(ContainerBuilder builder) } builder.RegisterType().AsImplementedInterfaces(); + builder.RegisterType().AsImplementedInterfaces().InstancePerDependency(); _customCodeAssemblyLoader.RegisterModule(builder); diff --git a/Thinktecture.Relay.Server/Interceptor/IInterceptedStream.cs b/Thinktecture.Relay.Server/Interceptor/IInterceptedStream.cs new file mode 100644 index 00000000..abff11cd --- /dev/null +++ b/Thinktecture.Relay.Server/Interceptor/IInterceptedStream.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.IO; + +namespace Thinktecture.Relay.Server.Interceptor +{ + internal interface IInterceptedStream + { + Stream Stream { get; set; } + + byte[] Body { get; set; } + + long ContentLength { get; set; } + + IReadOnlyDictionary HttpHeaders { get; set; } + } +} diff --git a/Thinktecture.Relay.Server/Interceptor/InterceptedRequest.cs b/Thinktecture.Relay.Server/Interceptor/InterceptedRequest.cs index f0f7d677..2b04167b 100644 --- a/Thinktecture.Relay.Server/Interceptor/InterceptedRequest.cs +++ b/Thinktecture.Relay.Server/Interceptor/InterceptedRequest.cs @@ -10,7 +10,7 @@ namespace Thinktecture.Relay.Server.Interceptor { - internal class InterceptedRequest : OnPremiseConnectorRequest, IInterceptedRequest + internal class InterceptedRequest : OnPremiseConnectorRequest, IInterceptedRequest, IInterceptedStream { private readonly ILogger _logger; @@ -26,17 +26,13 @@ internal class InterceptedRequest : OnPremiseConnectorRequest, IInterceptedReque [JsonIgnore] public Stream Content { - get => GetContent(); - set - { - Stream = value; - SetContentLength(Stream); - } + get => this.GetContentStream(_logger); + set => this.SetContentStream(value, _logger); } public InterceptedRequest(ILogger logger, IOnPremiseConnectorRequest other) { - _logger = logger ?? throw new ArgumentNullException(nameof(logger));; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); RequestId = other.RequestId; OriginId = other.OriginId; @@ -53,37 +49,12 @@ public InterceptedRequest(ILogger logger, IOnPremiseConnectorRequest other) AlwaysSendToOnPremiseConnector = other.AlwaysSendToOnPremiseConnector; Expiration = other.Expiration; AcknowledgeOriginId = other.AcknowledgeOriginId; + Properties = other.Properties; } public Dictionary CloneHttpHeaders() { return HttpHeaders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); } - - private Stream GetContent() - { - if (Body == null) - { - _logger.Information("Interceptor accessed the content of the request. Creating a COPY of the content stream to prevent multiple reads of the actual request stream. This might cause additional memory overhead."); - - Body = new byte[ContentLength]; - Stream.Read(Body, 0, (int)ContentLength); - } - - return new MemoryStream(Body); - } - - private void SetContentLength(Stream stream) - { - ContentLength = stream.Length; - - if (HttpHeaders.ContainsKey("Content-Length")) - { - var headers = CloneHttpHeaders(); - headers["Content-Length"] = ContentLength.ToString(); - - HttpHeaders = headers; - } - } } } diff --git a/Thinktecture.Relay.Server/Interceptor/InterceptedResponse.cs b/Thinktecture.Relay.Server/Interceptor/InterceptedResponse.cs index 9ccd788e..02d17bed 100644 --- a/Thinktecture.Relay.Server/Interceptor/InterceptedResponse.cs +++ b/Thinktecture.Relay.Server/Interceptor/InterceptedResponse.cs @@ -7,20 +7,14 @@ namespace Thinktecture.Relay.Server.Interceptor { - internal class InterceptedResponse : OnPremiseConnectorResponse, IInterceptedResponse + internal class InterceptedResponse : OnPremiseConnectorResponse, IInterceptedResponse, IInterceptedStream { private readonly ILogger _logger; public Stream Content { - get => GetContent(); - - set - { - Stream = value; - Body = null; - SetContentLength(Stream); - } + get => this.GetContentStream(_logger); + set => this.SetContentStream(value, _logger); } public InterceptedResponse(ILogger logger, IOnPremiseConnectorResponse other) @@ -42,41 +36,5 @@ public Dictionary CloneHttpHeaders() { return HttpHeaders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); } - - private Stream GetContent() - { - if (Stream == null && Body != null) - { - // This means we have a legacy-type Content, so simply assign a new stream - Stream = new MemoryStream(Body); - } - - if (Stream != null && Body == null) - { - // This means we have the loaded response body available - _logger.Information("Interceptor accessed the content of the response. Creating a COPY of the content stream to prevent multiple reads of the actual response stream."); - - Body = new byte[ContentLength]; - Stream.Read(Body, 0, (int)ContentLength); - - // switch the actual stream to a new memory stream on the content to allow for multiple reads - Stream = new MemoryStream(Body); - } - - return new MemoryStream(Body); - } - - private void SetContentLength(Stream stream) - { - ContentLength = stream.Length; - - if (HttpHeaders.ContainsKey("Content-Length")) - { - var headers = CloneHttpHeaders(); - headers["Content-Length"] = ContentLength.ToString(); - - HttpHeaders = headers; - } - } } } diff --git a/Thinktecture.Relay.Server/Interceptor/InterceptedStreamExtensions.cs b/Thinktecture.Relay.Server/Interceptor/InterceptedStreamExtensions.cs new file mode 100644 index 00000000..d14c1246 --- /dev/null +++ b/Thinktecture.Relay.Server/Interceptor/InterceptedStreamExtensions.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using System.Linq; +using Serilog; + +namespace Thinktecture.Relay.Server.Interceptor +{ + internal static class InterceptedStreamExtensions + { + public static Stream GetContentStream(this IInterceptedStream intercepted, ILogger logger) + { + intercepted.Stream = CreateStream(intercepted, logger); + return intercepted.Stream; + } + + private static Stream CreateStream(IInterceptedStream intercepted, ILogger logger) + { + if (intercepted.Stream == null) + { + return new MemoryStream(intercepted.Body ?? Array.Empty()); + } + + if (intercepted.Stream.CanSeek) + { + return intercepted.Stream; + } + + logger.Information("Interceptor accessed the content of the response. Creating a COPY of the content stream to allow multiple reads of the response stream."); + + var stream = new MemoryStream(); + intercepted.Stream.CopyTo(stream); + stream.Position = 0; + + return stream; + } + + public static void SetContentStream(this IInterceptedStream intercepted, Stream stream, ILogger logger) + { + intercepted.Body = null; + intercepted.Stream = stream; + intercepted.ContentLength = stream.Length; + + if (intercepted.HttpHeaders.ContainsKey("Content-Length")) + { + var headers = intercepted.HttpHeaders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + headers["Content-Length"] = intercepted.ContentLength.ToString(); + intercepted.HttpHeaders = headers; + } + } + } +} diff --git a/Thinktecture.Relay.Server/OnPremise/OnPremiseConnectorRequest.cs b/Thinktecture.Relay.Server/OnPremise/OnPremiseConnectorRequest.cs index 8cf803f2..657ddde2 100644 --- a/Thinktecture.Relay.Server/OnPremise/OnPremiseConnectorRequest.cs +++ b/Thinktecture.Relay.Server/OnPremise/OnPremiseConnectorRequest.cs @@ -31,5 +31,7 @@ internal class OnPremiseConnectorRequest : IOnPremiseConnectorRequest public TimeSpan Expiration { get; set; } public Guid AcknowledgeOriginId { get; set; } + + public IReadOnlyDictionary Properties { get; set; } } } diff --git a/Thinktecture.Relay.Server/SignalR/OnPremisesConnection.cs b/Thinktecture.Relay.Server/SignalR/OnPremisesConnection.cs index 3c5477b0..a9308395 100644 --- a/Thinktecture.Relay.Server/SignalR/OnPremisesConnection.cs +++ b/Thinktecture.Relay.Server/SignalR/OnPremisesConnection.cs @@ -2,7 +2,10 @@ using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; +using Autofac; using Microsoft.AspNet.SignalR; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Serilog; using Thinktecture.Relay.Server.Communication; using Thinktecture.Relay.Server.Config; @@ -15,12 +18,14 @@ internal class OnPremisesConnection : PersistentConnection private readonly ILogger _logger; private readonly IBackendCommunication _backendCommunication; private readonly IConfiguration _configuration; + private readonly ILifetimeScope _lifetimeScope; - public OnPremisesConnection(ILogger logger, IBackendCommunication backendCommunication, IConfiguration configuration) + public OnPremisesConnection(ILogger logger, IBackendCommunication backendCommunication, IConfiguration configuration, ILifetimeScope lifetimeScope) { _logger = logger; _backendCommunication = backendCommunication ?? throw new ArgumentNullException(nameof(backendCommunication)); _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + _lifetimeScope = lifetimeScope ?? throw new ArgumentNullException(nameof(lifetimeScope)); } protected override bool AuthorizeRequest(IRequest request) @@ -71,7 +76,18 @@ private async Task ForwardClientRequestAsync(string connectionId, IOnPremiseConn _logger?.Verbose("Forwarding client request to connection. connection-id={ConnectionId}, request-id={RequestId}, http-method={RequestMethod}, url={RequestUrl}, origin-id={OriginId}, body-length={RequestContentLength}", connectionId, request.RequestId, request.HttpMethod, _configuration.LogSensitiveData ? uri.PathAndQuery : uri.AbsolutePath, request.OriginId, request.ContentLength); - await Connection.Send(connectionId, request).ConfigureAwait(false); + var json = JObject.FromObject(request); + if (request.Properties != null) + { + json.Remove(nameof(IOnPremiseConnectorRequest.Properties)); + + foreach (var kvp in request.Properties) + { + json[kvp.Key] = JToken.FromObject(kvp.Value); + } + } + + await Connection.Send(connectionId, json).ConfigureAwait(false); } private static OnPremiseClaims GetOnPremiseClaims(IRequest request) @@ -87,17 +103,18 @@ private static OnPremiseClaims GetOnPremiseClaims(IRequest request) private async Task RegisterOnPremiseAsync(IRequest request, string connectionId, OnPremiseClaims claims) { - await _backendCommunication.RegisterOnPremiseAsync(new OnPremiseConnectionContext() - { - ConnectionId = connectionId, - LinkId = claims.OnPremiseId, - UserName = claims.UserName, - Role = claims.Role, - RequestAction = (cr, cancellationToken) => ForwardClientRequestAsync(connectionId, cr), - IpAddress = GetIpAddressFromOwinEnvironment(request.Environment), - ConnectorVersion = GetConnectorVersionFromRequest(request), - ConnectorAssemblyVersion = GetConnectorAssemblyVersionFromRequest(request), - }).ConfigureAwait(false); + var context = _lifetimeScope.Resolve(); + + context.ConnectionId = connectionId; + context.LinkId = claims.OnPremiseId; + context.UserName = claims.UserName; + context.Role = claims.Role; + context.RequestAction = (cr, cancellationToken) => ForwardClientRequestAsync(connectionId, cr); + context.IpAddress = GetIpAddressFromOwinEnvironment(request.Environment); + context.ConnectorVersion = GetConnectorVersionFromRequest(request); + context.ConnectorAssemblyVersion = GetConnectorAssemblyVersionFromRequest(request); + + await _backendCommunication.RegisterOnPremiseAsync(context).ConfigureAwait(false); } // Adopted from http://stackoverflow.com/questions/11044361/signalr-get-caller-ip-address diff --git a/Thinktecture.Relay.Server/Startup.cs b/Thinktecture.Relay.Server/Startup.cs index 32e72110..08c7886b 100644 --- a/Thinktecture.Relay.Server/Startup.cs +++ b/Thinktecture.Relay.Server/Startup.cs @@ -4,9 +4,11 @@ using System.Data.Entity.Migrations; using System.IO; using System.Linq; +using System.Net.Http; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.Web.Http; +using System.Web.Http.Controllers; using System.Web.Http.Cors; using System.Web.Http.ExceptionHandling; using Autofac; @@ -95,6 +97,13 @@ private ILifetimeScope RegisterAdditionalServices(ILifetimeScope container, Http // This enables property injection into ASP.NET MVC filter attributes builder.RegisterWebApiFilterProvider(httpConfig); RegisterApiControllers(builder); + + // Make the current resolvable through the dependency scope. + // This is required to extract the current HttpContext from its properties. + builder.RegisterHttpRequestMessage(httpConfig); + builder.Register(c => c.Resolve().Properties["MS_RequestContext"]) + .As() + .InstancePerRequest(); }); } @@ -252,6 +261,7 @@ private HttpConfiguration CreateHttpConfiguration() httpConfig.Routes.MapHttpRoute("ManagementWeb", "api/managementweb/{controller}/{action}"); } + httpConfig.Formatters.Remove(httpConfig.Formatters.XmlFormatter); httpConfig.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); return httpConfig; diff --git a/Thinktecture.Relay.Server/Thinktecture.Relay.Server.csproj b/Thinktecture.Relay.Server/Thinktecture.Relay.Server.csproj index d793a93e..3d55c249 100644 --- a/Thinktecture.Relay.Server/Thinktecture.Relay.Server.csproj +++ b/Thinktecture.Relay.Server/Thinktecture.Relay.Server.csproj @@ -288,17 +288,20 @@ - - + + + - + + + @@ -339,13 +342,10 @@ - - - diff --git a/Thinktecture.Relay/Server/Communication/IOnPremiseConnectionContext.cs b/Thinktecture.Relay/Server/Communication/IOnPremiseConnectionContext.cs new file mode 100644 index 00000000..843a36ed --- /dev/null +++ b/Thinktecture.Relay/Server/Communication/IOnPremiseConnectionContext.cs @@ -0,0 +1,83 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Thinktecture.Relay.Server.OnPremise; + +namespace Thinktecture.Relay.Server.Communication +{ + /// + /// Represents the context for an on-premise connection. + /// + public interface IOnPremiseConnectionContext + { + /// + /// The unique id of the connection. + /// + string ConnectionId { get; set; } + + /// + /// The unique id of the link. + /// + Guid LinkId { get; set; } + + /// + /// Marks the connection active or inactive. + /// + bool IsActive { get; set; } + + /// + /// The of the last activity (e.g. a receiption of a heartbeat result). + /// + DateTime LastLocalActivity { get; set; } + + /// + /// The request action called when the connection should handle an incoming relay request. + /// + Func RequestAction { get; set; } + + /// + /// The IP address of the on-premise connector. + /// + string IpAddress { get; set; } + + /// + /// The user name used to authenticate. + /// + string UserName { get; set; } + + /// + /// The role assigned to the authenticated user. + /// + string Role { get; set; } + + /// + /// The version of the on-premise connector. + /// + int ConnectorVersion { get; set; } + + /// + /// The assembly version of the on-premise connector. + /// + string ConnectorAssemblyVersion { get; set; } + + /// + /// Indicates if the on-premise connector supports acknowledging. + /// + bool SupportsAck { get; } + + /// + /// Indicates if the on-premise connector supports heartbeating. + /// + bool SupportsHeartbeat { get; } + + /// + /// Indicates if the on-premise connector supports server-side configuring. + /// + bool SupportsConfiguration { get; } + + /// + /// The when the next heartbeat should be sent. + /// + DateTime NextHeartbeat { get; set; } + } +} diff --git a/Thinktecture.Relay.Server/Communication/OnPremiseConnectionContext.cs b/Thinktecture.Relay/Server/Communication/OnPremiseConnectionContext.cs similarity index 60% rename from Thinktecture.Relay.Server/Communication/OnPremiseConnectionContext.cs rename to Thinktecture.Relay/Server/Communication/OnPremiseConnectionContext.cs index c5fcf8e9..50584894 100644 --- a/Thinktecture.Relay.Server/Communication/OnPremiseConnectionContext.cs +++ b/Thinktecture.Relay/Server/Communication/OnPremiseConnectionContext.cs @@ -5,21 +5,49 @@ namespace Thinktecture.Relay.Server.Communication { + /// public class OnPremiseConnectionContext : IOnPremiseConnectionContext { + /// public string ConnectionId { get; set; } + + /// public Guid LinkId { get; set; } + + /// public bool IsActive { get; set; } = true; + + /// public DateTime LastLocalActivity { get; set; } = DateTime.UtcNow; + + /// public Func RequestAction { get; set; } + + /// public string IpAddress { get; set; } + + /// public string UserName { get; set; } + + /// public string Role { get; set; } + + /// public int ConnectorVersion { get; set; } + + /// public string ConnectorAssemblyVersion { get; set; } - public bool SupportsAck => ConnectorVersion >= 1; - public bool SupportsHeartbeat => ConnectorVersion >= 2; - public bool SupportsConfiguration => ConnectorVersion >= 3; + + /// + public virtual bool SupportsAck => ConnectorVersion >= 1; + + /// + public virtual bool SupportsHeartbeat => ConnectorVersion >= 2; + + /// + public virtual bool SupportsConfiguration => ConnectorVersion >= 3; + + /// public DateTime NextHeartbeat { get; set; } } } diff --git a/Thinktecture.Relay/Server/Interceptor/IInterceptedRequest.cs b/Thinktecture.Relay/Server/Interceptor/IInterceptedRequest.cs index f33d3669..8a1bb837 100644 --- a/Thinktecture.Relay/Server/Interceptor/IInterceptedRequest.cs +++ b/Thinktecture.Relay/Server/Interceptor/IInterceptedRequest.cs @@ -57,5 +57,10 @@ public interface IInterceptedRequest : IReadOnlyInterceptedRequest /// /// A changeable dictionary containing all HttpHeaders of the intercepted request. Dictionary CloneHttpHeaders(); + + /// + /// Gets the additional properties which will be serialized onto the root object + /// + IReadOnlyDictionary Properties { get; set; } } } diff --git a/Thinktecture.Relay/Server/OnPremise/IOnPremiseConnectorRequest.cs b/Thinktecture.Relay/Server/OnPremise/IOnPremiseConnectorRequest.cs index 63e766e6..d9f9fc7f 100644 --- a/Thinktecture.Relay/Server/OnPremise/IOnPremiseConnectorRequest.cs +++ b/Thinktecture.Relay/Server/OnPremise/IOnPremiseConnectorRequest.cs @@ -85,5 +85,10 @@ public interface IOnPremiseConnectorRequest /// Gets the id of the RelayServer this request may acknowledged to /// Guid AcknowledgeOriginId { get; } + + /// + /// Gets the additional properties which will be serialized onto the root object + /// + IReadOnlyDictionary Properties { get; } } }