├── .config └── dotnet-tools.json ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .editorconfig ├── .github └── workflows │ └── build-and-publish-on-push.yaml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── assets │ ├── en-architecture-overview.png │ ├── logo_sponsor_abraxas.png │ ├── logo_sponsor_cmi.svg │ └── logo_sponsor_kms_ag.png ├── concepts.md ├── configuration.md ├── deployment.md ├── development-getting-started.md ├── glossary.md ├── migration.md ├── modules.md └── release-notes.md ├── global.json ├── package-lock.json ├── package.json └── src ├── .dockerignore ├── .idea └── .idea.Thinktecture.Relay │ └── .idea │ ├── .name │ ├── prettier.xml │ └── runConfigurations │ └── Relay_Server.xml ├── Directory.Build.props ├── Thinktecture.Relay.Abstractions ├── Acknowledgement │ ├── AcknowledgeMode.cs │ ├── AcknowledgeRequest.cs │ └── IAcknowledgeRequest.cs ├── DiscoveryDocument.cs ├── Extensions │ ├── ClientRequestExtensions.cs │ ├── EventExtensions.cs │ ├── StreamExtensions.cs │ ├── TargetResponseExtensions.cs │ └── TypeExtensions.cs ├── InlineMemoryStreamJsonConverter.cs ├── README.md ├── Thinktecture.Relay.Abstractions.csproj ├── TimeSpanJsonConverter.cs ├── Transport │ ├── ClientRequest.cs │ ├── IClientRequest.cs │ ├── IConnectorTransportLimit.cs │ ├── ITargetResponse.cs │ ├── ITenantConfig.cs │ └── TargetResponse.cs └── TransportException.cs ├── Thinktecture.Relay.Connector.Abstractions ├── Authentication │ └── IAccessTokenProvider.cs ├── Constants.cs ├── DependencyInjection │ └── IRelayConnectorBuilder.cs ├── IConnectorConnection.cs ├── README.md ├── RelayConnectorBuilderExtensions.cs ├── RelayConnectorOptions.cs ├── RelayTargetOptions.cs ├── Targets │ ├── IClientRequestHandler.cs │ ├── IClientRequestWorker.cs │ ├── IRelayTarget.cs │ └── RelayWebTargetOptions.cs ├── Thinktecture.Relay.Connector.Abstractions.csproj └── Transport │ ├── IAcknowledgeTransport.cs │ └── IResponseTransport.cs ├── Thinktecture.Relay.Connector.Protocols.SignalR ├── AcknowledgeTransport.Log.cs ├── AcknowledgeTransport.cs ├── ConnectorConnection.Log.cs ├── ConnectorConnection.cs ├── DiscoveryDocumentRetryPolicy.Log.cs ├── DiscoveryDocumentRetryPolicy.cs ├── HubConnectionExtensions.cs ├── HubConnectionFactory.Log.cs ├── HubConnectionFactory.cs ├── LoggingEventIds.cs ├── README.md ├── RelayConnectorBuilderExtensions.cs ├── ResponseTransport.Log.cs ├── ResponseTransport.cs └── Thinktecture.Relay.Connector.Protocols.SignalR.csproj ├── Thinktecture.Relay.Connector ├── Authentication │ ├── AccessTokenProvider.Log.cs │ └── AccessTokenProvider.cs ├── DependencyInjection │ ├── RelayConnectorBuilder.cs │ └── RelayConnectorBuilderExtensions.cs ├── LoggingEventIds.cs ├── Options │ ├── ClientCredentialsClientConfigureOptions.Log.cs │ ├── ClientCredentialsClientConfigureOptions.cs │ ├── RelayConnectorConfigureOptions.cs │ ├── RelayConnectorPostConfigureOptions.Log.cs │ ├── RelayConnectorPostConfigureOptions.cs │ ├── RelayConnectorValidateOptions.cs │ └── RelayServerConfigurationRetriever.cs ├── README.md ├── RelayConnector.cs ├── ServiceCollectionExtensions.cs ├── Targets │ ├── ClientRequestHandler.Log.cs │ ├── ClientRequestHandler.cs │ ├── ClientRequestWorker.Log.cs │ ├── ClientRequestWorker.cs │ ├── EchoTarget.cs │ ├── PingTarget.cs │ ├── RelayTargetRegistry.Log.cs │ ├── RelayTargetRegistry.cs │ ├── RelayWebTarget.Log.cs │ └── RelayWebTarget.cs └── Thinktecture.Relay.Connector.csproj ├── Thinktecture.Relay.Server.Abstractions ├── Constants.cs ├── DependencyInjection │ ├── IApplicationBuilderPart.cs │ ├── IRelayServerBuilder.cs │ └── RelayServerBuilderExtensions.cs ├── Diagnostics │ ├── IRelayRequestLogger.cs │ └── RelayRequestLoggerLevel.cs ├── DisposeAction.cs ├── Extensions │ ├── ClaimsPrincipalExtensions.cs │ └── HttpRequestExtensions.cs ├── IBodyStore.cs ├── Interceptor │ ├── IClientRequestInterceptor.cs │ └── ITargetResponseInterceptor.cs ├── LoggingEventIds.cs ├── Maintenance │ └── IMaintenanceJob.cs ├── Persistence │ ├── DataTransferObjects │ │ └── Page.cs │ ├── IConnectionService.cs │ ├── IConnectionStatisticsWriter.cs │ ├── IOriginStatisticsWriter.cs │ ├── IRequestService.cs │ ├── IStatisticsService.cs │ ├── ITenantService.cs │ └── Models │ │ ├── ClientSecret.cs │ │ ├── Config.cs │ │ ├── Connection.cs │ │ ├── Origin.cs │ │ ├── PersistenceModelExtensions.cs │ │ ├── Request.cs │ │ └── Tenant.cs ├── README.md ├── RelayServerContext.cs ├── RelayServerOptions.cs ├── Thinktecture.Relay.Server.Abstractions.csproj └── Transport │ ├── ConnectorRegistry.Log.cs │ ├── ConnectorRegistry.cs │ ├── IAcknowledgeCoordinator.cs │ ├── IAcknowledgeDispatcher.cs │ ├── IConnectorTransport.cs │ ├── IConnectorTransportFactory.cs │ ├── IRelayClientRequestFactory.cs │ ├── IRelayContext.cs │ ├── IRelayTargetResponseWriter.cs │ ├── IRequestCoordinator.cs │ ├── IResponseContext.cs │ ├── IResponseCoordinator.cs │ ├── IResponseDispatcher.cs │ ├── IServerTransport.cs │ ├── ITenantHandler.cs │ ├── ITenantHandlerFactory.cs │ └── ITenantTransport.cs ├── Thinktecture.Relay.Server.Interceptors ├── ForwardedHeaderInterceptor.cs ├── README.md ├── RelayServerBuilderExtensions.cs └── Thinktecture.Relay.Server.Interceptors.csproj ├── Thinktecture.Relay.Server.Management ├── DataTransferObjects │ ├── IdResult.cs │ ├── Tenant.cs │ └── TenantCredential.cs ├── Endpoints │ ├── DeleteTenantEndpoint.cs │ ├── GetTenantEndpoint.cs │ ├── GetTenantsPagedEndpoint.cs │ ├── PostTenantEndpoint.cs │ └── PutTenantEndpoint.cs ├── Extensions │ ├── EndpointRouteBuilderExtensions.cs │ └── PersistenceModelsExtensions.cs ├── ManagementApiPolicyNames.cs ├── README.md └── Thinktecture.Relay.Server.Management.csproj ├── Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.PostgreSql ├── Migrations │ └── ConfigurationDb │ │ ├── 20231109135209_Initial.Designer.cs │ │ ├── 20231109135209_Initial.cs │ │ ├── 20231109143956_Add_RequireAuthentication.Designer.cs │ │ ├── 20231109143956_Add_RequireAuthentication.cs │ │ ├── 20231109154430_Add_MaximumConcurrentConnectorRequests.Designer.cs │ │ ├── 20231109154430_Add_MaximumConcurrentConnectorRequests.cs │ │ ├── 20240410083448_Add_ConfigForeignKey.Designer.cs │ │ ├── 20240410083448_Add_ConfigForeignKey.cs │ │ └── RelayDbContextModelSnapshot.cs ├── README.md ├── ServiceCollectionExtensions.cs └── Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.PostgreSql.csproj ├── Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.SqlServer ├── Migrations │ └── ConfigurationDb │ │ ├── 20231109135212_Initial.Designer.cs │ │ ├── 20231109135212_Initial.cs │ │ ├── 20231109143959_Add_RequireAuthentication.Designer.cs │ │ ├── 20231109143959_Add_RequireAuthentication.cs │ │ ├── 20231109154433_Add_MaximumConcurrentConnectorRequests.Designer.cs │ │ ├── 20231109154433_Add_MaximumConcurrentConnectorRequests.cs │ │ ├── 20240410083451_Add_ConfigForeignKey.Designer.cs │ │ ├── 20240410083451_Add_ConfigForeignKey.cs │ │ └── RelayDbContextModelSnapshot.cs ├── README.md ├── ServiceCollectionExtensions.cs └── Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.SqlServer.csproj ├── Thinktecture.Relay.Server.Persistence.EntityFrameworkCore ├── ConnectionService.cs ├── LoggingEventIds.cs ├── ModelBuilderExtensions.cs ├── OrderedQueryableExtensions.cs ├── README.md ├── RelayDbContext.cs ├── RequestService.Log.cs ├── RequestService.cs ├── ServiceCollectionExtensions.cs ├── ServiceProviderExtensions.Log.cs ├── ServiceProviderExtensions.cs ├── StatisticsService.Log.cs ├── StatisticsService.cs ├── TenantService.cs └── Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.csproj ├── Thinktecture.Relay.Server.Protocols.RabbitMq ├── Constants.cs ├── DisposableConsumer.Log.cs ├── DisposableConsumer.cs ├── LoggingEventIds.cs ├── ModelExtensions.cs ├── ModelFactory.Log.cs ├── ModelFactory.cs ├── README.md ├── RabbitMqOptions.cs ├── RelayServerBuilderExtensions.cs ├── RoundRobinEndpointResolver.cs ├── ServerTransport.Log.cs ├── ServerTransport.cs ├── TenantHandler.Log.cs ├── TenantHandler.cs ├── TenantHandlerFactory.cs ├── TenantTransport.Log.cs ├── TenantTransport.cs └── Thinktecture.Relay.Server.Protocols.RabbitMq.csproj ├── Thinktecture.Relay.Server.Protocols.SignalR ├── ApplicationBuilderPart.cs ├── ConnectorHub.Log.cs ├── ConnectorHub.cs ├── ConnectorTransport.Log.cs ├── ConnectorTransport.cs ├── ConnectorTransportFactory.cs ├── LoggingEventIds.cs ├── Options │ ├── HubOptionsPostConfigureOptions.cs │ └── JwtBearerPostConfigureOptions.cs ├── README.md ├── RelayServerBuilderExtensions.cs └── Thinktecture.Relay.Server.Protocols.SignalR.csproj ├── Thinktecture.Relay.Server ├── ApplicationBuilderExtensions.cs ├── DependencyInjection │ ├── RelayServerBuilder.cs │ └── RelayServerBuilderExtensions.cs ├── Diagnostics │ ├── RelayRequestLogger.Log.cs │ └── RelayRequestLogger.cs ├── Endpoints │ ├── AcknowledgeEndpoint.Log.cs │ ├── AcknowledgeEndpoint.cs │ ├── BodyContentEndpoint.Log.cs │ ├── BodyContentEndpoint.cs │ ├── DiscoveryDocumentEndpoint.Log.cs │ └── DiscoveryDocumentEndpoint.cs ├── LoggingEventIds.cs ├── Maintenance │ ├── MaintenanceJobRunner.Log.cs │ ├── MaintenanceJobRunner.cs │ └── StatisticsCleanupJob.cs ├── MaintenanceOptions.cs ├── Middleware │ ├── RelayContext.cs │ ├── RelayMiddleware.Log.cs │ └── RelayMiddleware.cs ├── README.md ├── ServiceCollectionExtensions.cs ├── Services │ ├── ConnectionStatisticsWriter.cs │ ├── DiscoveryDocumentBuilder.cs │ ├── OriginStatisticsWriter.cs │ ├── RelayTargetResponseWriter.Log.cs │ ├── RelayTargetResponseWriter.cs │ └── ServerStatisticsWriter.cs ├── StatisticsOptions.cs ├── Thinktecture.Relay.Server.csproj └── Transport │ ├── AcknowledgeCoordinator.Log.cs │ ├── AcknowledgeCoordinator.cs │ ├── AcknowledgeDispatcher.Log.cs │ ├── AcknowledgeDispatcher.cs │ ├── FileBodyStore.Log.cs │ ├── FileBodyStore.cs │ ├── InMemoryBodyStore.Log.cs │ ├── InMemoryBodyStore.cs │ ├── InMemoryServerTransport.cs │ ├── InMemoryTenantHandler.cs │ ├── InMemoryTenantHandlerFactory.cs │ ├── InMemoryTenantTransport.Log.cs │ ├── InMemoryTenantTransport.cs │ ├── RelayClientRequestFactory.cs │ ├── RequestCoordinator.Log.cs │ ├── RequestCoordinator.cs │ ├── ResponseCoordinator.Log.cs │ ├── ResponseCoordinator.cs │ ├── ResponseDispatcher.Log.cs │ └── ResponseDispatcher.cs ├── Thinktecture.Relay.sln ├── Thinktecture.Relay.sln.DotSettings ├── docker ├── Directory.Build.props ├── Thinktecture.Relay.Connector.Docker │ ├── Dockerfile │ ├── LoggingEventIds.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.Log.cs │ ├── Startup.cs │ ├── Thinktecture.Relay.Connector.Docker.csproj │ ├── appsettings.Development.json │ └── appsettings.json ├── Thinktecture.Relay.Docker │ ├── .gitignore │ ├── Authentication │ │ ├── ApiKeyAuthenticationDefaults.cs │ │ ├── ApiKeyAuthenticationHandler.cs │ │ ├── ApiKeyAuthenticationOptions.cs │ │ └── ApiKeyExtensions.cs │ ├── DockerUtils.cs │ ├── Program.cs │ ├── StreamDestructuringPolicy.cs │ └── Thinktecture.Relay.Docker.csproj ├── Thinktecture.Relay.ManagementApi.Docker │ ├── .gitignore │ ├── Dockerfile │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── ServiceCollectionExtensions.cs │ ├── Thinktecture.Relay.ManagementApi.Docker.csproj │ ├── appsettings.Development.json │ ├── appsettings.json │ └── samples │ │ ├── DELETE_tenants.http │ │ ├── GET_tenants.http │ │ ├── POST_tenants.http │ │ └── PUT_tenants.http ├── Thinktecture.Relay.Server.Docker │ ├── Dockerfile │ ├── Interceptors │ │ ├── ConnectorFinishedAcknowledgementModeInterceptor.cs │ │ ├── DemoRequestInterceptor.Log.cs │ │ ├── DemoRequestInterceptor.cs │ │ ├── DemoResponseInterceptor.Log.cs │ │ └── DemoResponseInterceptor.cs │ ├── LoggingEventIds.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── ServiceCollectionExtensions.cs │ ├── Startup.cs │ ├── Thinktecture.Relay.Server.Docker.csproj │ ├── appsettings.Development.json │ └── appsettings.json ├── docker-compose.yml └── keycloak_data │ └── relayserver-realm.json ├── full-rebuild.ps1 ├── nuget └── icon.png ├── samples └── RelayServerCore.postman_collection.json └── tools ├── Directory.Build.props ├── MigrationCreation.PostgreSql ├── DbContextFactory.cs ├── MigrationCreation.PostgreSql.csproj ├── Program.cs ├── appsettings.json ├── create-migration.ps1 └── create-migration.sh ├── MigrationCreation.SqlServer ├── DbContextFactory.cs ├── MigrationCreation.SqlServer.csproj ├── Program.cs ├── appsettings.json ├── create-migration.ps1 └── create-migration.sh ├── create-migrations.ps1 └── create-migrations.sh /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-ef": { 6 | "version": "7.0.13", 7 | "commands": [ 8 | "dotnet-ef" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/dotnet/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] .NET version: 6.0, 3.1, 6.0-bullseye, 3.1-bullseye, 6.0-focal, 3.1-focal 4 | ARG VARIANT="6.0-bullseye-slim" 5 | FROM mcr.microsoft.com/vscode/devcontainers/dotnet:0-${VARIANT} 6 | 7 | # [Choice] Node.js version: none, lts/*, 18, 16, 14 8 | ARG NODE_VERSION="none" 9 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 10 | 11 | # [Optional] Uncomment this section to install additional OS packages. 12 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 13 | # && apt-get -y install --no-install-recommends 14 | 15 | # [Optional] Uncomment this line to install global node packages. 16 | # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .config/ 2 | .github/ 3 | 4 | src/ 5 | 6 | packages/ 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "printWidth": 140, 5 | "proseWrap": "always", 6 | "semi": false, 7 | "singleQuote": true, 8 | "arrowParens": "avoid" 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The BSD 3-Clause "New" or "Revised" License 2 | 3 | Copyright (c) 2016 - 2025, Thinktecture AG 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of RelayServer nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /docs/assets/en-architecture-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinktecture/relayserver/b2f9e86d9edb00c3653d11e884dbac445199014f/docs/assets/en-architecture-overview.png -------------------------------------------------------------------------------- /docs/assets/logo_sponsor_abraxas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinktecture/relayserver/b2f9e86d9edb00c3653d11e884dbac445199014f/docs/assets/logo_sponsor_abraxas.png -------------------------------------------------------------------------------- /docs/assets/logo_sponsor_cmi.svg: -------------------------------------------------------------------------------- 1 | CMI_Logo_RGB -------------------------------------------------------------------------------- /docs/assets/logo_sponsor_kms_ag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinktecture/relayserver/b2f9e86d9edb00c3653d11e884dbac445199014f/docs/assets/logo_sponsor_kms_ag.png -------------------------------------------------------------------------------- /docs/release-notes.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | Here you can find all the releases of RelayServer 3 with their corresponding change log. 4 | 5 | * v3.1.0 6 | - Update many external packages (e.g. System.Text.Json having a vulnerability) 7 | - Replace IdentityModel.AspNetCore with Duende.AccessTokenManagement 8 | - Provide AuthorizationTokenEndpoint directly in discovery document 9 | - Deprecate AuthorizationServer in discovery document 10 | 11 | * v3.0.1 12 | - Add NuGet package readme files 13 | 14 | * v3.0.0 15 | - Rewrite of RelayServer, replaces v2. 16 | - Details see [./migration.md](Migration) 17 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.100", 4 | "rollForward": "latestFeature", 5 | "allowPrerelease": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relayserver", 3 | "version": "3.0.0", 4 | "description": "Thinktecture RelayServer enables secure communication from clients - including mobile devices, web, and native applications - to their on-premises backend applications.", 5 | "directories": { 6 | "doc": "docs" 7 | }, 8 | "scripts": { 9 | "prettier": "prettier --write ." 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/thinktecture/relayserver.git" 14 | }, 15 | "keywords": [ 16 | "relay", 17 | "connector", 18 | "tenant", 19 | "on-premise", 20 | "proxy", 21 | "gateway" 22 | ], 23 | "author": "Thinktecture AG", 24 | "license": "BSD-3-Clause", 25 | "bugs": { 26 | "url": "https://github.com/thinktecture/relayserver/issues" 27 | }, 28 | "homepage": "https://github.com/thinktecture/relayserver#readme", 29 | "devDependencies": { 30 | "husky": "^4.3.0", 31 | "lint-staged": "^10.3.0", 32 | "prettier": "^2.1.1" 33 | }, 34 | "husky": { 35 | "hooks": { 36 | "pre-commit": "lint-staged" 37 | } 38 | }, 39 | "lint-staged": { 40 | "*.md": "prettier --write" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md 26 | -------------------------------------------------------------------------------- /src/.idea/.idea.Thinktecture.Relay/.idea/.name: -------------------------------------------------------------------------------- 1 | Thinktecture.Relay -------------------------------------------------------------------------------- /src/.idea/.idea.Thinktecture.Relay/.idea/prettier.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /src/.idea/.idea.Thinktecture.Relay/.idea/runConfigurations/Relay_Server.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Abstractions/Acknowledgement/AcknowledgeMode.cs: -------------------------------------------------------------------------------- 1 | using Thinktecture.Relay.Transport; 2 | 3 | namespace Thinktecture.Relay.Acknowledgement; 4 | 5 | /// 6 | /// Used to set the acknowledge mode on a . 7 | /// 8 | public enum AcknowledgeMode 9 | { 10 | /// 11 | /// Disables acknowledging of . 12 | /// 13 | /// This is the default value. 14 | Disabled, 15 | 16 | /// 17 | /// Acknowledges when the connector has received the and also downloaded an optional body. 18 | /// 19 | ConnectorReceived, 20 | 21 | /// 22 | /// Acknowledges when the connector has finished the and is about to return the 23 | /// to the server (after uploading an optional body). 24 | /// 25 | ConnectorFinished, 26 | 27 | /// 28 | /// Acknowledgement will be done by third-party code. 29 | /// 30 | Manual, 31 | } 32 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Abstractions/Acknowledgement/AcknowledgeRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Thinktecture.Relay.Acknowledgement; 4 | 5 | /// 6 | public class AcknowledgeRequest : IAcknowledgeRequest 7 | { 8 | /// 9 | public Guid OriginId { get; set; } 10 | 11 | /// 12 | public Guid RequestId { get; set; } 13 | 14 | /// 15 | public bool RemoveRequestBodyContent { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Abstractions/Acknowledgement/IAcknowledgeRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Thinktecture.Relay.Acknowledgement; 4 | 5 | /// 6 | /// The metadata of an acknowledgement. 7 | /// 8 | public interface IAcknowledgeRequest 9 | { 10 | /// 11 | /// The unique id of the origin where the acknowledgement should be send to. 12 | /// 13 | Guid OriginId { get; set; } 14 | 15 | /// 16 | /// The unique id of the request. 17 | /// 18 | Guid RequestId { get; set; } 19 | 20 | /// 21 | /// Indicates if the request body content should be removed. 22 | /// 23 | bool RemoveRequestBodyContent { get; set; } 24 | } 25 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Abstractions/Extensions/TargetResponseExtensions.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable once CheckNamespace; (extension methods on ITargetResponse namespace) 2 | namespace Thinktecture.Relay.Transport; 3 | 4 | /// 5 | /// Extension methods for . 6 | /// 7 | public static class TargetResponseExtensions 8 | { 9 | /// 10 | /// Checks if the body content is currently outsourced. 11 | /// 12 | /// An . 13 | /// true if the body content is outsourced; otherwise, false. 14 | public static bool IsBodyContentOutsourced(this ITargetResponse response) 15 | => response.BodySize > 0 && response.BodyContent is null; 16 | } 17 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Abstractions/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | // ReSharper disable once CheckNamespace; (extension methods on Stream namespace) 4 | namespace System; 5 | 6 | /// 7 | /// Extension methods for . 8 | /// 9 | public static class TypeExtensions 10 | { 11 | /// 12 | /// Gets the simple name of the assembly. This is usually, but not necessarily, the file name of the manifest file of the 13 | /// assembly, minus its extension. 14 | /// 15 | /// The . 16 | /// The simple name of the assembly. 17 | public static string? GetAssemblySimpleName(this Type type) 18 | => type.Assembly.GetName().Name; 19 | 20 | /// 21 | /// Gets the version of the assembly either from the or 22 | /// . 23 | /// 24 | /// The . 25 | /// The version of the assembly. 26 | public static string GetAssemblyVersion(this Type type) 27 | { 28 | var assembly = type.Assembly; 29 | return assembly.GetCustomAttribute()?.InformationalVersion ?? 30 | assembly.GetCustomAttribute()?.Version ?? "Unknown"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Abstractions/InlineMemoryStreamJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace Thinktecture.Relay; 7 | 8 | internal class InlineMemoryStreamJsonConverter : JsonConverter 9 | { 10 | public override Stream Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 11 | => new MemoryStream(Convert.FromBase64String(reader.GetString() ?? string.Empty)); 12 | 13 | public override void Write(Utf8JsonWriter writer, Stream value, JsonSerializerOptions options) 14 | { 15 | if (value is MemoryStream stream) 16 | { 17 | writer.WriteBase64StringValue(stream.ToArray()); 18 | return; 19 | } 20 | 21 | writer.WriteNullValue(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Abstractions/README.md: -------------------------------------------------------------------------------- 1 | # Thinktecture.Relay.Abstractions 2 | 3 | Contains abstractions for the RelayServer 3. 4 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Abstractions/Thinktecture.Relay.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Thinktecture RelayServer Abstractions 6 | Contains abstractions for the RelayServer server and connector. 7 | 8 | This usually should not be referenced directly but provides shared types for 9 | the more specific connector and server abstractions. 10 | 11 | Thinktecture.Relay 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Abstractions/TimeSpanJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace Thinktecture.Relay; 7 | 8 | internal class TimeSpanJsonConverter : JsonConverter 9 | { 10 | public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 11 | => TimeSpan.Parse(reader.GetString() ?? TimeSpan.Zero.ToString(), CultureInfo.InvariantCulture); 12 | 13 | public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) 14 | => writer.WriteStringValue(value.ToString("c")); 15 | } 16 | 17 | internal class NullableTimeSpanJsonConverter : JsonConverter 18 | { 19 | public override TimeSpan? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 20 | => reader.TokenType == JsonTokenType.String 21 | ? TimeSpan.Parse(reader.GetString() ?? TimeSpan.Zero.ToString(), CultureInfo.InvariantCulture) 22 | : null; 23 | 24 | public override void Write(Utf8JsonWriter writer, TimeSpan? value, JsonSerializerOptions options) 25 | { 26 | if (value is null) 27 | { 28 | writer.WriteNullValue(); 29 | } 30 | else 31 | { 32 | writer.WriteStringValue(value.Value.ToString("c")); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Abstractions/Transport/ClientRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text.Json.Serialization; 5 | using Thinktecture.Relay.Acknowledgement; 6 | 7 | namespace Thinktecture.Relay.Transport; 8 | 9 | /// 10 | public class ClientRequest : IClientRequest 11 | { 12 | /// 13 | public Guid RequestId { get; set; } 14 | 15 | /// 16 | public Guid RequestOriginId { get; set; } 17 | 18 | /// 19 | public AcknowledgeMode AcknowledgeMode { get; set; } 20 | 21 | /// 22 | public Guid? AcknowledgeOriginId { get; set; } 23 | 24 | /// 25 | public string Target { get; set; } = default!; 26 | 27 | /// 28 | public string TenantName { get; set; } = default!; 29 | 30 | /// 31 | public string HttpMethod { get; set; } = default!; 32 | 33 | /// 34 | public string Url { get; set; } = default!; 35 | 36 | /// 37 | public IDictionary HttpHeaders { get; set; } = default!; 38 | 39 | /// 40 | public long? OriginalBodySize { get; set; } 41 | 42 | /// 43 | public long? BodySize { get; set; } 44 | 45 | /// 46 | [JsonConverter(typeof(InlineMemoryStreamJsonConverter))] 47 | public Stream? BodyContent { get; set; } 48 | 49 | /// 50 | public bool EnableTracing { get; set; } 51 | 52 | /// 53 | public bool DiscardConnectorResponse { get; set; } 54 | } 55 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Abstractions/Transport/IConnectorTransportLimit.cs: -------------------------------------------------------------------------------- 1 | namespace Thinktecture.Relay.Transport; 2 | 3 | /// 4 | /// An implementation of a connector transport limit between connector and RelayServer. 5 | /// 6 | public interface IConnectorTransportLimit 7 | { 8 | /// 9 | /// The maximum size of binary data the transport is capable to serialize inline, or null if there is no limit. 10 | /// 11 | int? BinarySizeThreshold { get; } 12 | } 13 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Abstractions/Transport/ITenantConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Thinktecture.Relay.Transport; 4 | 5 | /// 6 | /// The tenant configuration which can be sent to a connector. 7 | /// 8 | public interface ITenantConfig 9 | { 10 | /// 11 | /// The interval used to send keep alive pings between the server and a connector. 12 | /// 13 | public TimeSpan? KeepAliveInterval { get; set; } 14 | 15 | /// 16 | /// Enable tracing for all requests of this particular tenant. 17 | /// 18 | public bool? EnableTracing { get; set; } 19 | 20 | /// 21 | /// The minimum delay to wait for until a reconnect of a connector should be attempted again. 22 | /// 23 | public TimeSpan? ReconnectMinimumDelay { get; set; } 24 | 25 | /// 26 | /// The maximum delay to wait for until a reconnect of a connector should be attempted again. 27 | /// 28 | public TimeSpan? ReconnectMaximumDelay { get; set; } 29 | } 30 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Abstractions/Transport/TargetResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace Thinktecture.Relay.Transport; 8 | 9 | /// 10 | public class TargetResponse : ITargetResponse 11 | { 12 | /// 13 | public Guid RequestId { get; set; } 14 | 15 | /// 16 | public Guid RequestOriginId { get; set; } 17 | 18 | /// 19 | public DateTime? RequestStart { get; set; } 20 | 21 | /// 22 | [JsonConverter(typeof(NullableTimeSpanJsonConverter))] 23 | public TimeSpan? RequestDuration { get; set; } 24 | 25 | /// 26 | public string? ConnectionId { get; set; } 27 | 28 | /// 29 | public HttpStatusCode HttpStatusCode { get; set; } 30 | 31 | /// 32 | public IDictionary? HttpHeaders { get; set; } 33 | 34 | /// 35 | public long? OriginalBodySize { get; set; } 36 | 37 | /// 38 | public long? BodySize { get; set; } 39 | 40 | /// 41 | [JsonConverter(typeof(InlineMemoryStreamJsonConverter))] 42 | public Stream? BodyContent { get; set; } 43 | 44 | /// 45 | public bool RequestFailed { get; set; } 46 | } 47 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Abstractions/TransportException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Thinktecture.Relay; 4 | 5 | /// 6 | /// Thrown when the application encounters an error when reading or writing to or from a transport. 7 | /// 8 | public class TransportException : Exception 9 | { 10 | /// 11 | /// Initializes a new instance of the class with a specified error message and a reference 12 | /// to the inner exception that is the cause of this exception. 13 | /// 14 | /// 15 | /// The exception that is the cause of the current exception, or a null reference if no inner 16 | /// exception is specified. 17 | /// 18 | public TransportException(Exception innerException) 19 | : base("The transport raised an error. See inner exception for details.", innerException) 20 | { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Abstractions/Authentication/IAccessTokenProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Thinktecture.Relay.Connector.Authentication; 4 | 5 | /// 6 | /// An implementation that provides access tokens for server access. 7 | /// 8 | public interface IAccessTokenProvider 9 | { 10 | /// 11 | /// Retrieves an access token. 12 | /// 13 | /// A representing the asynchronous operation, which wraps the access token. 14 | Task GetAccessTokenAsync(); 15 | } 16 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Abstractions/DependencyInjection/IRelayConnectorBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Thinktecture.Relay.Acknowledgement; 3 | using Thinktecture.Relay.Transport; 4 | 5 | namespace Thinktecture.Relay.Connector.DependencyInjection; 6 | 7 | /// 8 | /// Connector builder interface. 9 | /// 10 | // ReSharper disable UnusedTypeParameter; (this makes method call signatures cleaner) 11 | public interface IRelayConnectorBuilder 12 | // ReSharper restore UnusedTypeParameter 13 | where TRequest : IClientRequest 14 | where TResponse : ITargetResponse 15 | where TAcknowledge : IAcknowledgeRequest 16 | { 17 | /// 18 | /// Gets the application service collection. 19 | /// 20 | IServiceCollection Services { get; } 21 | } 22 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Abstractions/README.md: -------------------------------------------------------------------------------- 1 | # Thinktecture.Relay.Connector.Abstractions 2 | 3 | Contains interfaces and base classes for a RelayServer 3 Connector. 4 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Abstractions/RelayConnectorOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | 5 | namespace Thinktecture.Relay.Connector; 6 | 7 | /// 8 | /// Options for the connector. 9 | /// 10 | public class RelayConnectorOptions 11 | { 12 | /// 13 | /// The base uri of the server. 14 | /// 15 | public Uri RelayServerBaseUri { get; set; } = default!; 16 | 17 | /// 18 | /// The tenant name to use for authentication. 19 | /// 20 | public string TenantName { get; set; } = default!; 21 | 22 | /// 23 | /// The tenant secret to use for authentication. 24 | /// 25 | public string TenantSecret { get; set; } = default!; 26 | 27 | /// 28 | /// The . 29 | /// 30 | public DiscoveryDocument DiscoveryDocument { get; set; } = default!; 31 | 32 | /// 33 | /// Indicates that the used to communicate with the server uses the HTTP keep-alive feature. 34 | /// 35 | public bool UseHttpKeepAlive { get; set; } = true; 36 | 37 | /// 38 | /// The targets keyed by id. 39 | /// 40 | public Dictionary>? Targets { get; set; } 41 | } 42 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Abstractions/RelayTargetOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Thinktecture.Relay.Connector; 5 | 6 | /// 7 | /// Options for relay target registration via dependency injection. 8 | /// 9 | public class RelayTargetOptions 10 | { 11 | /// 12 | /// The default target timeout. 13 | /// 14 | public static readonly TimeSpan DefaultTargetTimeout = TimeSpan.FromSeconds(100); 15 | 16 | /// 17 | /// The of . 18 | /// 19 | public List Targets { get; } = new List(); 20 | 21 | /// 22 | /// A single target registration. 23 | /// 24 | public class RelayTargetRegistration 25 | { 26 | /// 27 | /// The unique id of the target. 28 | /// 29 | public string Id { get; set; } = default!; 30 | 31 | /// 32 | /// The of the target handling requests. 33 | /// 34 | public Type Type { get; set; } = default!; 35 | 36 | /// 37 | /// An optional when the target times out. 38 | /// 39 | public TimeSpan? Timeout { get; set; } 40 | 41 | /// 42 | /// Constructor arguments not provided by the . 43 | /// 44 | public object[] Parameters { get; set; } = default!; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Abstractions/Targets/IClientRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Thinktecture.Relay.Transport; 4 | 5 | namespace Thinktecture.Relay.Connector.Targets; 6 | 7 | /// 8 | /// An implementation of a client request handler for handling the request in the first step. 9 | /// 10 | /// The type of request. 11 | /// 12 | public interface IClientRequestHandler 13 | where T : IClientRequest 14 | { 15 | /// 16 | /// Handles the request. 17 | /// 18 | /// The client request. 19 | /// 20 | /// The token to monitor for cancellation requests. The default value is 21 | /// . 22 | /// A representing the asynchronous operation. 23 | Task HandleAsync(T request, CancellationToken cancellationToken = default); 24 | 25 | /// 26 | /// Acknowledges the request. 27 | /// 28 | /// The client request. 29 | /// Indicates if the request body content should be removed. 30 | /// 31 | Task AcknowledgeRequestAsync(T request, bool removeRequestBodyContent); 32 | } 33 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Abstractions/Targets/IClientRequestWorker.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Thinktecture.Relay.Transport; 4 | 5 | namespace Thinktecture.Relay.Connector.Targets; 6 | 7 | /// 8 | /// An implementation of a worker for handling a request. 9 | /// 10 | /// The type of request. 11 | /// The type of response. 12 | /// 13 | public interface IClientRequestWorker 14 | where TRequest : IClientRequest 15 | where TResponse : ITargetResponse 16 | { 17 | /// 18 | /// Handles the request. 19 | /// 20 | /// The client request. 21 | /// 22 | /// The token to monitor for cancellation requests. The default value is 23 | /// . 24 | /// 25 | /// A representing the asynchronous operation, which wraps the target response. 26 | Task HandleAsync(TRequest request, CancellationToken cancellationToken = default); 27 | } 28 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Abstractions/Targets/RelayWebTargetOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Thinktecture.Relay.Connector.Targets; 4 | 5 | /// 6 | /// Used to set additional web target options. 7 | /// 8 | [Flags] 9 | public enum RelayWebTargetOptions 10 | { 11 | /// 12 | /// No additional options. 13 | /// 14 | /// This is the default value. 15 | None = 0, 16 | 17 | /// 18 | /// Enables following redirects. 19 | /// 20 | FollowRedirect = 1, 21 | } 22 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Abstractions/Thinktecture.Relay.Connector.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Thinktecture RelayServer Connector Abstractions 6 | Contains abstractions for the connector. 7 | Thinktecture.Relay.Connector 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Abstractions/Transport/IAcknowledgeTransport.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Thinktecture.Relay.Acknowledgement; 4 | 5 | namespace Thinktecture.Relay.Connector.Transport; 6 | 7 | /// 8 | /// An implementation of a transport for acknowledge requests. 9 | /// 10 | /// The type of acknowledge. 11 | public interface IAcknowledgeTransport 12 | where T : IAcknowledgeRequest 13 | { 14 | /// 15 | /// Transports the acknowledge request. 16 | /// 17 | /// The acknowledge request. 18 | /// 19 | /// The token to monitor for cancellation requests. The default value is 20 | /// . 21 | /// 22 | /// A representing the asynchronous operation. 23 | Task TransportAsync(T request, CancellationToken cancellationToken = default); 24 | } 25 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Abstractions/Transport/IResponseTransport.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Thinktecture.Relay.Transport; 4 | 5 | namespace Thinktecture.Relay.Connector.Transport; 6 | 7 | /// 8 | /// An implementation of a transport for responses. 9 | /// 10 | /// The type of response. 11 | public interface IResponseTransport 12 | where T : ITargetResponse 13 | { 14 | /// 15 | /// Transports the target response. 16 | /// 17 | /// The target response. 18 | /// 19 | /// The token to monitor for cancellation requests. The default value is 20 | /// . 21 | /// 22 | /// A representing the asynchronous operation. 23 | Task TransportAsync(T response, CancellationToken cancellationToken = default); 24 | } 25 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Protocols.SignalR/AcknowledgeTransport.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | using Thinktecture.Relay.Acknowledgement; 4 | 5 | namespace Thinktecture.Relay.Connector.Protocols.SignalR; 6 | 7 | public partial class AcknowledgeTransport 8 | { 9 | private partial class Log 10 | { 11 | [LoggerMessage(LoggingEventIds.AcknowledgeTransportTransportingAck, LogLevel.Trace, 12 | "Transporting acknowledge request {@AcknowledgeRequest} for request {RelayRequestId} on connection {TransportConnectionId}")] 13 | public static partial void TransportingAck(ILogger logger, IAcknowledgeRequest acknowledgeRequest, 14 | Guid relayRequestId, string? transportConnectionId); 15 | 16 | [LoggerMessage(LoggingEventIds.AcknowledgeTransportErrorTransportingAck, LogLevel.Error, 17 | "An error occured while transporting acknowledge for request {RelayRequestId} on connection {TransportConnectionId}")] 18 | public static partial void ErrorTransportingAck(ILogger logger, Exception? ex, Guid relayRequestId, 19 | string? transportConnectionId); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Protocols.SignalR/AcknowledgeTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.SignalR.Client; 5 | using Microsoft.Extensions.Logging; 6 | using Thinktecture.Relay.Acknowledgement; 7 | using Thinktecture.Relay.Connector.Transport; 8 | 9 | namespace Thinktecture.Relay.Connector.Protocols.SignalR; 10 | 11 | /// 12 | public partial class AcknowledgeTransport : IAcknowledgeTransport 13 | where T : IAcknowledgeRequest 14 | { 15 | private readonly HubConnection _hubConnection; 16 | private readonly ILogger _logger; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// An . 22 | /// The . 23 | public AcknowledgeTransport(ILogger> logger, HubConnection hubConnection) 24 | { 25 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 26 | _hubConnection = hubConnection ?? throw new ArgumentNullException(nameof(hubConnection)); 27 | } 28 | 29 | /// 30 | public async Task TransportAsync(T request, CancellationToken cancellationToken = default) 31 | { 32 | try 33 | { 34 | Log.TransportingAck(_logger, request, request.RequestId, _hubConnection.ConnectionId); 35 | await _hubConnection.InvokeAsync("Acknowledge", request, cancellationToken); 36 | } 37 | catch (Exception ex) 38 | { 39 | Log.ErrorTransportingAck(_logger, ex, request.RequestId, _hubConnection.ConnectionId); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Protocols.SignalR/DiscoveryDocumentRetryPolicy.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Connector.Protocols.SignalR; 5 | 6 | public partial class DiscoveryDocumentRetryPolicy 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.DiscoveryDocumentRetryPolicyLogRetry, LogLevel.Information, 11 | "Connecting attempt {ConnectionAttempt} failed and will be tried again in {ReconnectDelay} seconds")] 12 | public static partial void LogRetry(ILogger logger, long connectionAttempt, int reconnectDelay); 13 | 14 | [LoggerMessage(LoggingEventIds.DiscoveryDocumentKeepingDefaults, LogLevel.Warning, 15 | "Keeping (default) reconnect delays because minimum ({ReconnectMinimumDelay}) cannot be greater than maximum ({ReconnectMaximumDelay})")] 16 | public static partial void KeepingDefaults(ILogger logger, TimeSpan? reconnectMinimumDelay, 17 | TimeSpan? reconnectMaximumDelay); 18 | 19 | [LoggerMessage(LoggingEventIds.DiscoveryDocumentUsingDelays, LogLevel.Debug, 20 | "Using a minimum of {ReconnectMinimumDelay} and a maximum of {ReconnectMaximumDelay} for reconnecting")] 21 | public static partial void UsingDelays(ILogger logger, TimeSpan? reconnectMinimumDelay, 22 | TimeSpan? reconnectMaximumDelay); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Protocols.SignalR/HubConnectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.SignalR.Client; 3 | 4 | namespace Thinktecture.Relay.Connector.Protocols.SignalR; 5 | 6 | internal static class HubConnectionExtensions 7 | { 8 | public static void SetKeepAliveInterval(this HubConnection connection, TimeSpan? keepAliveInterval) 9 | { 10 | if (keepAliveInterval is null) return; 11 | 12 | connection.KeepAliveInterval = keepAliveInterval.Value; 13 | connection.ServerTimeout = 14 | TimeSpan.FromSeconds(keepAliveInterval.Value.TotalSeconds * 2); // should always be twice of keep-alive 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Protocols.SignalR/HubConnectionFactory.Log.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace Thinktecture.Relay.Connector.Protocols.SignalR; 4 | 5 | public partial class HubConnectionFactory 6 | { 7 | private static partial class Log 8 | { 9 | [LoggerMessage(LoggingEventIds.HubConnectionFactoryCreatingConnection, LogLevel.Information, 10 | "Creating connection to {ConnectorEndpoint}")] 11 | public static partial void CreatingConnection(ILogger logger, string connectorEndpoint); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Protocols.SignalR/LoggingEventIds.cs: -------------------------------------------------------------------------------- 1 | namespace Thinktecture.Relay.Connector.Protocols.SignalR; 2 | 3 | internal static class LoggingEventIds 4 | { 5 | public const int AcknowledgeTransportTransportingAck = 10001; 6 | public const int AcknowledgeTransportErrorTransportingAck = 10002; 7 | 8 | public const int ConnectorConnectionDisconnecting = 10101; 9 | public const int ConnectorConnectionDisconnected = 10102; 10 | public const int ConnectorConnectionGracefullyClosed = 10103; 11 | public const int ConnectorConnectionReconnectingAfterLoss = 10104; 12 | public const int ConnectorConnectionReconnectingAfterError = 10105; 13 | public const int ConnectorConnectionReconnectedWithoutId = 10106; 14 | public const int ConnectorConnectionReconnected = 10107; 15 | public const int ConnectorConnectionReconnectedWithNewId = 10108; 16 | public const int ConnectorConnectionHandlingRequestDetailed = 10109; 17 | public const int ConnectorConnectionHandlingRequestSimple = 10110; 18 | public const int ConnectorConnectionReceivedTenantConfig = 10111; 19 | public const int ConnectorConnectionLogConnected = 10112; 20 | public const int ConnectorConnectionConnectError = 10113; 21 | 22 | public const int DiscoveryDocumentRetryPolicyLogRetry = 10201; 23 | public const int DiscoveryDocumentKeepingDefaults = 10202; 24 | public const int DiscoveryDocumentUsingDelays = 10203; 25 | 26 | public const int HubConnectionFactoryCreatingConnection = 10301; 27 | 28 | public const int ResponseTransportTransportingResponse = 10401; 29 | public const int ResponseTransportErrorTransportingResponse = 10402; 30 | } 31 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Protocols.SignalR/README.md: -------------------------------------------------------------------------------- 1 | # Thinktecture.Relay.Connector.Protocols.SignalR 2 | 3 | Contains the SignalR transport implementation used by the RelayServer 3 Connector to connect to the RelayServer. 4 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Protocols.SignalR/ResponseTransport.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | using Thinktecture.Relay.Transport; 4 | 5 | namespace Thinktecture.Relay.Connector.Protocols.SignalR; 6 | 7 | public partial class ResponseTransport 8 | { 9 | private partial class Log 10 | { 11 | [LoggerMessage(LoggingEventIds.ResponseTransportTransportingResponse, LogLevel.Trace, 12 | "Transporting response {@Response} for request {RelayRequestId} on connection {TransportConnectionId}")] 13 | public static partial void TransportingResponse(ILogger logger, ITargetResponse response, Guid relayRequestId, 14 | string? transportConnectionId); 15 | 16 | [LoggerMessage(LoggingEventIds.ResponseTransportErrorTransportingResponse, LogLevel.Error, 17 | "An error occured while transporting response for request {RelayRequestId} on connection {TransportConnectionId}")] 18 | public static partial void ErrorTransportingResponse(ILogger logger, Exception exception, 19 | Guid relayRequestId, string? transportConnectionId); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Protocols.SignalR/ResponseTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.SignalR.Client; 5 | using Microsoft.Extensions.Logging; 6 | using Thinktecture.Relay.Connector.Transport; 7 | using Thinktecture.Relay.Transport; 8 | 9 | namespace Thinktecture.Relay.Connector.Protocols.SignalR; 10 | 11 | /// 12 | public partial class ResponseTransport : IResponseTransport 13 | where T : ITargetResponse 14 | { 15 | private readonly HubConnection _hubConnection; 16 | private readonly ILogger _logger; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// An . 22 | /// The . 23 | public ResponseTransport(ILogger> logger, HubConnection hubConnection) 24 | { 25 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 26 | _hubConnection = hubConnection ?? throw new ArgumentNullException(nameof(hubConnection)); 27 | } 28 | 29 | /// 30 | public async Task TransportAsync(T response, CancellationToken cancellationToken = default) 31 | { 32 | Log.TransportingResponse(_logger, response, response.RequestId, _hubConnection.ConnectionId); 33 | 34 | try 35 | { 36 | await _hubConnection.InvokeAsync("Deliver", response, cancellationToken); 37 | } 38 | catch (Exception ex) 39 | { 40 | Log.ErrorTransportingResponse(_logger, ex, response.RequestId, _hubConnection.ConnectionId); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector.Protocols.SignalR/Thinktecture.Relay.Connector.Protocols.SignalR.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Thinktecture RelayServer Connector SignalR Protocol 6 | Contains the SignalR connector transport implementation for the RelayServer. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/Authentication/AccessTokenProvider.Log.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace Thinktecture.Relay.Connector.Authentication; 4 | 5 | internal partial class AccessTokenProvider 6 | { 7 | private static partial class Log 8 | { 9 | [LoggerMessage(LoggingEventIds.AccessTokenProviderRequestingAccessToken, LogLevel.Debug, "Requesting access token")] 10 | public static partial void RequestingAccessToken(ILogger logger); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/Authentication/AccessTokenProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Duende.AccessTokenManagement; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Thinktecture.Relay.Connector.Authentication; 7 | 8 | internal partial class AccessTokenProvider : IAccessTokenProvider 9 | { 10 | private readonly IClientCredentialsTokenManagementService _clientCredentialsTokenManagementService; 11 | private readonly ILogger _logger; 12 | 13 | public AccessTokenProvider(ILogger logger, 14 | IClientCredentialsTokenManagementService clientCredentialsTokenManagementService) 15 | { 16 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 17 | _clientCredentialsTokenManagementService = 18 | clientCredentialsTokenManagementService ?? 19 | throw new ArgumentNullException(nameof(clientCredentialsTokenManagementService)); 20 | } 21 | 22 | public async Task GetAccessTokenAsync() 23 | { 24 | Log.RequestingAccessToken(_logger); 25 | var token = 26 | await _clientCredentialsTokenManagementService.GetAccessTokenAsync(Constants.RelayServerClientName); 27 | return token.AccessToken; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/DependencyInjection/RelayConnectorBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Thinktecture.Relay.Acknowledgement; 4 | using Thinktecture.Relay.Transport; 5 | 6 | namespace Thinktecture.Relay.Connector.DependencyInjection; 7 | 8 | internal class 9 | RelayConnectorBuilder : IRelayConnectorBuilder 10 | where TRequest : IClientRequest 11 | where TResponse : ITargetResponse 12 | where TAcknowledge : IAcknowledgeRequest 13 | { 14 | public IServiceCollection Services { get; } 15 | 16 | public RelayConnectorBuilder(IServiceCollection services) 17 | => Services = services ?? throw new ArgumentNullException(nameof(services)); 18 | } 19 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/Options/ClientCredentialsClientConfigureOptions.Log.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace Thinktecture.Relay.Connector.Options; 4 | 5 | internal partial class ClientCredentialsClientConfigureOptions 6 | { 7 | private static partial class Log 8 | { 9 | [LoggerMessage(LoggingEventIds.ClientCredentialsClientConfigureOptionsSetTokenEndpoint, LogLevel.Trace, 10 | "Using token endpoint {TokenEndpoint}")] 11 | public static partial void UseTokenEndpoint(ILogger logger, string tokenEndpoint); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/Options/ClientCredentialsClientConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Duende.AccessTokenManagement; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.Options; 5 | 6 | namespace Thinktecture.Relay.Connector.Options; 7 | 8 | internal partial class ClientCredentialsClientConfigureOptions : IConfigureNamedOptions 9 | { 10 | private readonly ILogger _logger; 11 | private readonly RelayConnectorOptions _relayConnectorOptions; 12 | 13 | public ClientCredentialsClientConfigureOptions(ILogger logger, 14 | IOptions relayConnectorOptions) 15 | { 16 | if (relayConnectorOptions is null) throw new ArgumentNullException(nameof(relayConnectorOptions)); 17 | 18 | _logger = logger; 19 | _relayConnectorOptions = relayConnectorOptions.Value; 20 | } 21 | 22 | public void Configure(string? name, ClientCredentialsClient options) 23 | { 24 | if (name != Constants.RelayServerClientName) 25 | { 26 | return; 27 | } 28 | 29 | Log.UseTokenEndpoint(_logger, _relayConnectorOptions.DiscoveryDocument.AuthorizationTokenEndpoint); 30 | 31 | options.TokenEndpoint = _relayConnectorOptions.DiscoveryDocument.AuthorizationTokenEndpoint; 32 | options.ClientId = _relayConnectorOptions.TenantName; 33 | options.ClientSecret = _relayConnectorOptions.TenantSecret; 34 | options.Scope = Constants.RelayServerScopes; 35 | } 36 | 37 | public void Configure(ClientCredentialsClient options) 38 | { 39 | Configure(null, options); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/Options/RelayConnectorConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using Thinktecture.Relay.Connector.Targets; 3 | 4 | namespace Thinktecture.Relay.Connector.Options; 5 | 6 | internal class RelayConnectorConfigureOptions : IConfigureOptions 7 | { 8 | public void Configure(RelayConnectorOptions options) 9 | { 10 | if (options.Targets is null || options.Targets.Count == 0) return; 11 | 12 | foreach (var (_, value) in options.Targets) 13 | { 14 | if (value.TryGetValue(Constants.RelayConnectorOptionsTargetType, out var type) && !type.Contains(".")) 15 | { 16 | value[Constants.RelayConnectorOptionsTargetType] = $"{typeof(RelayWebTarget).Namespace}.{type}"; 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/Options/RelayConnectorPostConfigureOptions.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Connector.Options; 5 | 6 | internal partial class RelayConnectorPostConfigureOptions 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.RelayConnectorPostConfigureOptionsGotDiscoveryDocument, LogLevel.Trace, 11 | "Got discovery document from {DiscoveryDocumentUrl} ({@DiscoveryDocument})")] 12 | public static partial void GotDiscoveryDocument(ILogger logger, Uri discoveryDocumentUrl, 13 | DiscoveryDocument discoveryDocument); 14 | 15 | [LoggerMessage(LoggingEventIds.RelayConnectorPostConfigureOptionsErrorRetrievingDiscoveryDocument, 16 | LogLevel.Error, 17 | "An error occured while retrieving the discovery document from {DiscoveryDocumentUrl}")] 18 | public static partial void ErrorRetrievingDiscoveryDocument(ILogger logger, Exception exception, 19 | string discoveryDocumentUrl); 20 | 21 | [LoggerMessage(LoggingEventIds.RelayConnectorPostConfigureOptionsErrorTargetTypeNotFound, LogLevel.Error, 22 | "Could not find target type {TargetType} for target {Target}")] 23 | public static partial void ErrorTargetTypeNotFound(ILogger logger, string targetType, string target); 24 | 25 | [LoggerMessage(LoggingEventIds.RelayConnectorPostConfigureOptionsCouldNotParseTimeout, LogLevel.Warning, 26 | "Could not parse timeout \"{TargetTimeout}\" for target {Target}")] 27 | public static partial void CouldNotParseTimeout(ILogger logger, string targetTimeout, string target); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/Options/RelayConnectorValidateOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.Extensions.Options; 4 | 5 | namespace Thinktecture.Relay.Connector.Options; 6 | 7 | internal class RelayConnectorValidateOptions : IValidateOptions 8 | { 9 | public ValidateOptionsResult Validate(string? name, RelayConnectorOptions options) 10 | { 11 | if (options.Targets is null || options.Targets.Count == 0) return ValidateOptionsResult.Success; 12 | 13 | var missingType = options.Targets.Where(kvp => !kvp.Value.ContainsKey(Constants.RelayConnectorOptionsTargetType)) 14 | .Select(kvp => kvp.Key).ToArray(); 15 | if (missingType.Length != 0) 16 | { 17 | var missingTypes = string.Join("\", \"", missingType); 18 | return ValidateOptionsResult.Fail( 19 | $"The following targets have no \"{Constants.RelayConnectorOptionsTargetType}\" provided: \"{missingTypes}\""); 20 | } 21 | 22 | var unknownType = options.Targets 23 | .Where(kvp => Type.GetType(kvp.Value[Constants.RelayConnectorOptionsTargetType]) is null) 24 | .Select(kvp => kvp.Key).ToArray(); 25 | if (unknownType.Length != 0) 26 | { 27 | var unknownTypes = string.Join("\", \"", unknownType); 28 | return ValidateOptionsResult.Fail( 29 | $"The following targets have an invalid type provided: \"{unknownTypes}\""); 30 | } 31 | 32 | return ValidateOptionsResult.Success; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/Options/RelayServerConfigurationRetriever.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.IdentityModel.Protocols; 6 | 7 | namespace Thinktecture.Relay.Connector.Options; 8 | 9 | internal class RelayServerConfigurationRetriever : IConfigurationRetriever 10 | { 11 | public async Task GetConfigurationAsync(string address, IDocumentRetriever retriever, 12 | CancellationToken cancellationToken = default) 13 | { 14 | var document = await retriever.GetDocumentAsync(address, cancellationToken); 15 | 16 | var result = JsonSerializer.Deserialize(document, new JsonSerializerOptions() 17 | { 18 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 19 | }); 20 | 21 | return result ?? throw new Exception("Could not deserialize discovery document."); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/README.md: -------------------------------------------------------------------------------- 1 | # Thinktecture.Relay.Connector 2 | 3 | Contains the implementation to include a RelayServer 3 Connector into your projects. 4 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/RelayConnector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Thinktecture.Relay.Connector; 6 | 7 | /// 8 | /// A connection to a RelayServer. 9 | /// 10 | /// This is just a convenient class for holding the transient . 11 | public class RelayConnector 12 | { 13 | private readonly IConnectorConnection _connection; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// An . 19 | public RelayConnector(IConnectorConnection connection) 20 | => _connection = connection ?? throw new ArgumentNullException(nameof(connection)); 21 | 22 | /// 23 | /// Opens a connection to the server. 24 | /// 25 | /// 26 | /// The token to monitor for cancellation requests. The default value is 27 | /// . 28 | /// 29 | /// A representing the asynchronous operation. 30 | public Task ConnectAsync(CancellationToken cancellationToken = default) 31 | => _connection.ConnectAsync(cancellationToken); 32 | 33 | /// 34 | /// Closes the connection to the server. 35 | /// 36 | /// 37 | /// The token to monitor for cancellation requests. The default value is 38 | /// . 39 | /// 40 | /// A representing the asynchronous operation. 41 | public Task DisconnectAsync(CancellationToken cancellationToken = default) 42 | => _connection.DisconnectAsync(cancellationToken); 43 | } 44 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/Targets/ClientRequestHandler.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Connector.Targets; 5 | 6 | public partial class ClientRequestHandler 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.ClientRequestHandlerAcknowledgeRequest, LogLevel.Debug, 11 | "Acknowledging request {RelayRequestId} on origin {OriginId}")] 12 | public static partial void AcknowledgeRequest(ILogger logger, Guid relayRequestId, Guid? originId); 13 | 14 | [LoggerMessage(LoggingEventIds.ClientRequestHandlerErrorHandlingRequest, LogLevel.Error, 15 | "An error occured during handling of request {RelayRequestId}")] 16 | public static partial void ErrorHandlingRequest(ILogger logger, Exception exception, Guid relayRequestId); 17 | 18 | [LoggerMessage(LoggingEventIds.ClientRequestHandlerDeliverResponse, LogLevel.Debug, 19 | "Delivering response for request {RelayRequestId}")] 20 | public static partial void DeliverResponse(ILogger logger, Guid relayRequestId); 21 | 22 | [LoggerMessage(LoggingEventIds.ClientRequestHandlerDiscardResponse, LogLevel.Debug, 23 | "Discarding response for request {RelayRequestId}")] 24 | public static partial void DiscardResponse(ILogger logger, Guid relayRequestId); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/Targets/PingTarget.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Thinktecture.Relay.Transport; 8 | 9 | namespace Thinktecture.Relay.Connector.Targets; 10 | 11 | /// 12 | public class PingTarget : IRelayTargetFunc 13 | where TRequest : IClientRequest 14 | where TResponse : ITargetResponse, new() 15 | { 16 | /// 17 | public Task HandleAsync(TRequest request, CancellationToken cancellationToken = default) 18 | { 19 | if (!request.HttpMethod.Equals("GET", StringComparison.OrdinalIgnoreCase)) 20 | return Task.FromResult(request.CreateResponse(HttpStatusCode.NotFound)); 21 | 22 | var result = request.CreateResponse(HttpStatusCode.OK); 23 | 24 | result.HttpHeaders = new Dictionary() 25 | { 26 | { "Content-Type", ["text/plain"] }, 27 | }; 28 | result.BodyContent = new MemoryStream("PONG"u8.ToArray()); 29 | result.BodySize = result.BodyContent.Length; 30 | 31 | return Task.FromResult(result); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/Targets/RelayTargetRegistry.Log.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace Thinktecture.Relay.Connector.Targets; 4 | 5 | public partial class RelayTargetRegistry 6 | { 7 | private static partial class Log 8 | { 9 | [LoggerMessage(LoggingEventIds.RelayTargetRegistryRegisteredTarget, LogLevel.Debug, 10 | "Registered relay target {Target} as type {TargetType}")] 11 | public static partial void RegisteredTarget(ILogger logger, string target, string? targetType); 12 | 13 | [LoggerMessage(LoggingEventIds.RelayTargetRegistryUnregisteredTarget, LogLevel.Debug, 14 | "Unregistered relay target {Target}")] 15 | public static partial void UnregisteredTarget(ILogger logger, string target); 16 | 17 | [LoggerMessage(LoggingEventIds.RelayTargetRegistryCouldNotUnregisterTarget, LogLevel.Warning, 18 | "Could not unregister relay target {Target}")] 19 | public static partial void CouldNotUnregisterTarget(ILogger logger, string target); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/Targets/RelayWebTarget.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Thinktecture.Relay.Connector.Targets; 6 | 7 | public partial class RelayWebTarget 8 | { 9 | private static partial class Log 10 | { 11 | [LoggerMessage(LoggingEventIds.RelayWebTargetRequestingTarget, LogLevel.Trace, 12 | "Requesting target for request {RelayRequestId} at {BaseAddress} for {Url}")] 13 | public static partial void RequestingTarget(ILogger logger, Guid relayRequestId, Uri? baseAddress, string url); 14 | 15 | [LoggerMessage(LoggingEventIds.RelayWebTargetRequestedTarget, LogLevel.Debug, 16 | "Requested target for request {RelayRequestId} returned {HttpStatusCode}")] 17 | public static partial void RequestedTarget(ILogger logger, Guid relayRequestId, HttpStatusCode httpStatusCode); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Connector/Thinktecture.Relay.Connector.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Thinktecture RelayServer Connector 6 | Contains the connector implementations for the RelayServer. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/DependencyInjection/IApplicationBuilderPart.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | 3 | namespace Thinktecture.Relay.Server.DependencyInjection; 4 | 5 | /// 6 | /// An implementation of an part to extend the application's request pipeline. 7 | /// 8 | public interface IApplicationBuilderPart 9 | { 10 | /// 11 | /// Adds the to the application's request pipeline. 12 | /// 13 | void Use(IApplicationBuilder builder); 14 | } 15 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/DependencyInjection/IRelayServerBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Thinktecture.Relay.Acknowledgement; 3 | using Thinktecture.Relay.Transport; 4 | 5 | namespace Thinktecture.Relay.Server.DependencyInjection; 6 | 7 | /// 8 | /// Server builder interface. 9 | /// 10 | /// The type of request. 11 | /// The type of response. 12 | /// The type of acknowledge. 13 | // ReSharper disable UnusedTypeParameter; (this makes method call signatures cleaner) 14 | public interface IRelayServerBuilder 15 | // ReSharper restore UnusedTypeParameter 16 | where TRequest : IClientRequest 17 | where TResponse : ITargetResponse 18 | where TAcknowledge : IAcknowledgeRequest 19 | { 20 | /// 21 | /// Gets the application service collection. 22 | /// 23 | IServiceCollection Services { get; } 24 | } 25 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/DisposeAction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Thinktecture.Relay.Server; 5 | 6 | /// 7 | /// An implementation of a dispose action which will be executed when this instance is disposed. 8 | /// 9 | public class DisposeAction : IAsyncDisposable 10 | { 11 | private readonly Func _dispose; 12 | 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The asynchronous action to execute when disposing. 17 | public DisposeAction(Func dispose) 18 | => _dispose = dispose ?? throw new ArgumentNullException(nameof(dispose)); 19 | 20 | /// 21 | public async ValueTask DisposeAsync() 22 | => await _dispose(); 23 | } 24 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Extensions/HttpRequestExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace Thinktecture.Relay.Server.Extensions; 6 | 7 | /// 8 | /// A struct containing the parsed request url. 9 | /// 10 | /// The requested mode. 11 | /// The unique name of the tenant. 12 | /// The name of the target. 13 | /// The url for the target. 14 | public record struct RelayRequest(string Mode, string TenantName, string Target, string Url); 15 | 16 | /// 17 | /// Extension methods for the . 18 | /// 19 | public static class HttpRequestExtensions 20 | { 21 | /// 22 | /// Parses the request url into a . 23 | /// 24 | /// A to be used for parsing. 25 | /// The . 26 | public static RelayRequest GetRelayRequest(this HttpRequest httpRequest) 27 | { 28 | // /mode/tenant/target/(url) 29 | var parts = httpRequest.Path.Value?.Split('/').Skip(1).ToArray() ?? Array.Empty(); 30 | var mode = parts.ElementAt(0).ToLower(); 31 | var tenantName = parts.ElementAtOrDefault(1) ?? string.Empty; 32 | var target = parts.ElementAtOrDefault(2) ?? string.Empty; 33 | var url = $"{string.Join("/", parts.Skip(3))}{httpRequest.QueryString}"; 34 | 35 | return new RelayRequest(mode, tenantName, target, url); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Interceptor/IClientRequestInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Thinktecture.Relay.Server.Transport; 4 | using Thinktecture.Relay.Transport; 5 | 6 | namespace Thinktecture.Relay.Server.Interceptor; 7 | 8 | /// 9 | /// An implementation of an interceptor dealing with the request of the client. 10 | /// 11 | /// The type of request. 12 | /// The type of response. 13 | public interface IClientRequestInterceptor 14 | where TRequest : IClientRequest 15 | where TResponse : class, ITargetResponse 16 | { 17 | /// 18 | /// Called when a request was received. 19 | /// 20 | /// The context of the relay task. 21 | /// 22 | /// The token to monitor for cancellation requests. The default value is 23 | /// . 24 | /// 25 | /// A representing the asynchronous operation. 26 | Task OnRequestReceivedAsync(IRelayContext context, 27 | CancellationToken cancellationToken = default); 28 | } 29 | 30 | /// 31 | /// An implementation of an interceptor dealing with the request of the client. 32 | /// 33 | public interface IClientRequestInterceptor : IClientRequestInterceptor; 34 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Interceptor/ITargetResponseInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Thinktecture.Relay.Server.Transport; 4 | using Thinktecture.Relay.Transport; 5 | 6 | namespace Thinktecture.Relay.Server.Interceptor; 7 | 8 | /// 9 | /// An implementation of an interceptor dealing with the response of a target. 10 | /// 11 | /// The type of request. 12 | /// The type of response. 13 | public interface ITargetResponseInterceptor 14 | where TRequest : IClientRequest 15 | where TResponse : class, ITargetResponse 16 | { 17 | /// 18 | /// Called when a response was received. 19 | /// 20 | /// The context of the relay task. 21 | /// 22 | /// The token to monitor for cancellation requests. The default value is 23 | /// . 24 | /// 25 | /// A representing the asynchronous operation. 26 | Task OnResponseReceivedAsync(IRelayContext context, 27 | CancellationToken cancellationToken = default); 28 | } 29 | 30 | /// 31 | /// An implementation of an interceptor dealing with the response of a target. 32 | /// 33 | public interface ITargetResponseInterceptor : ITargetResponseInterceptor; 34 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/LoggingEventIds.cs: -------------------------------------------------------------------------------- 1 | namespace Thinktecture.Relay.Server; 2 | 3 | internal static class LoggingEventIds 4 | { 5 | public const int ConnectorRegistryRegisteringConnection = 10001; 6 | public const int ConnectorRegistryUnregisteringConnection = 10002; 7 | public const int ConnectorRegistryCouldNotUnregisterConnection = 10003; 8 | public const int ConnectorRegistryUnknownRequestConnection = 10004; 9 | public const int ConnectorRegistryUnknownAcknowledgeConnection = 10005; 10 | public const int ConnectorRegistryDeliveringRequest = 10006; 11 | } 12 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Maintenance/IMaintenanceJob.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Thinktecture.Relay.Server.Maintenance; 5 | 6 | /// 7 | /// An implementation of a job that will be instantiated and called from a background task in a configured maintenance 8 | /// interval. 9 | /// 10 | public interface IMaintenanceJob 11 | { 12 | /// 13 | /// Called when the maintenance interval 14 | /// 15 | /// 16 | /// The token to monitor for cancellation requests. The default value is 17 | /// . 18 | /// 19 | /// A representing the asynchronous operation. 20 | Task DoMaintenanceAsync(CancellationToken cancellationToken = default); 21 | } 22 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Persistence/DataTransferObjects/Page.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Thinktecture.Relay.Server.Persistence.DataTransferObjects; 4 | 5 | /// 6 | /// An object that represents a single page of a paginated result. 7 | /// 8 | /// The type of the data transfer object in this page. 9 | public class Page 10 | { 11 | /// 12 | /// Gets of sets an array of the actual results for this page. 13 | /// 14 | public T[] Results { get; set; } = Array.Empty(); 15 | 16 | /// 17 | /// Gets or sets the total amount of data entries available. 18 | /// 19 | public int TotalCount { get; set; } 20 | 21 | /// 22 | /// Gets or sets the starting index of the array within all available entries. 23 | /// 24 | public int Offset { get; set; } 25 | 26 | /// 27 | /// Gets or sets the requested maximum size of the array. 28 | /// 29 | public int PageSize { get; set; } 30 | } 31 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Persistence/IConnectionService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Thinktecture.Relay.Server.Persistence; 4 | 5 | /// 6 | /// Represents a way to access connection data in the persistence layer. 7 | /// 8 | public interface IConnectionService 9 | { 10 | /// 11 | /// Returns true when a connection for the tenant is available; otherwise, false. 12 | /// 13 | /// The unique name of the tenant. 14 | /// A representing the asynchronous operation, which wraps the result. 15 | Task IsConnectionAvailableAsync(string tenantName); 16 | } 17 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Persistence/IRequestService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Thinktecture.Relay.Server.Persistence.Models; 3 | 4 | namespace Thinktecture.Relay.Server.Persistence; 5 | 6 | /// 7 | /// Represents a way to access request data in the persistence layer. 8 | /// 9 | public interface IRequestService 10 | { 11 | /// 12 | /// Stores the request. 13 | /// 14 | /// The . 15 | /// A representing the asynchronous operation. 16 | Task StoreRequestAsync(Request request); 17 | } 18 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Persistence/Models/ClientSecret.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Thinktecture.Relay.Server.Persistence.Models; 4 | 5 | /// 6 | /// Represents a client secret which a connector for a needs to use for authentication. 7 | /// 8 | public class ClientSecret 9 | { 10 | /// 11 | /// The unique id of this client secret. 12 | /// 13 | public Guid Id { get; set; } 14 | 15 | /// 16 | /// The unique name of the tenant. 17 | /// 18 | public string TenantName { get; set; } = default!; 19 | 20 | /// 21 | /// A SHA256 or SHA512 of the actual secret string. 22 | /// 23 | /// The maximum length is 4000 unicode characters. 24 | public string Value { get; set; } = default!; 25 | 26 | /// 27 | /// Indicates the point in time when this secret was created. 28 | /// 29 | public DateTime Created { get; set; } 30 | 31 | /// 32 | /// Defines an optional point in time when this secret automatically will become invalid. 33 | /// 34 | public DateTime? Expiration { get; set; } 35 | } 36 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Persistence/Models/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Thinktecture.Relay.Transport; 3 | 4 | namespace Thinktecture.Relay.Server.Persistence.Models; 5 | 6 | /// 7 | /// Represents a tenant config. 8 | /// 9 | public class Config : ITenantConfig 10 | { 11 | /// 12 | /// The unique name of the tenant. 13 | /// 14 | public string TenantName { get; set; } = default!; 15 | 16 | /// 17 | public TimeSpan? KeepAliveInterval { get; set; } 18 | 19 | /// 20 | public bool? EnableTracing { get; set; } 21 | 22 | /// 23 | public TimeSpan? ReconnectMinimumDelay { get; set; } 24 | 25 | /// 26 | public TimeSpan? ReconnectMaximumDelay { get; set; } 27 | } 28 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Persistence/Models/Connection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Thinktecture.Relay.Server.Persistence.Models; 4 | 5 | /// 6 | /// Represents a single connection of tenants' on-premises installation to a relay server. 7 | /// 8 | public class Connection 9 | { 10 | /// 11 | /// The transport-specific connection id. 12 | /// 13 | /// The maximum length is 100 unicode characters. 14 | public string Id { get; set; } = default!; 15 | 16 | /// 17 | /// The unique name of the tenant. 18 | /// 19 | public string TenantName { get; set; } = default!; 20 | 21 | /// 22 | /// The unique id of the relay server instance this connection is held to. 23 | /// 24 | public Guid OriginId { get; set; } 25 | 26 | /// 27 | /// The time when this connection was opened. 28 | /// 29 | public DateTimeOffset ConnectTime { get; set; } 30 | 31 | /// 32 | /// The time when this connection was closed. 33 | /// 34 | public DateTimeOffset? DisconnectTime { get; set; } 35 | 36 | /// 37 | /// The last time when the last message through this connection was recorded. 38 | /// 39 | public DateTimeOffset? LastSeenTime { get; set; } 40 | 41 | /// 42 | /// The remote IP address of the connector. 43 | /// 44 | public string? RemoteIpAddress { get; set; } 45 | } 46 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Persistence/Models/Origin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace Thinktecture.Relay.Server.Persistence.Models; 6 | 7 | /// 8 | /// Represents a relay server instance. 9 | /// 10 | public class Origin 11 | { 12 | /// 13 | /// The unique id of the running instance. 14 | /// 15 | public Guid Id { get; set; } 16 | 17 | /// 18 | /// The time when this instance started. 19 | /// 20 | public DateTimeOffset StartupTime { get; set; } 21 | 22 | /// 23 | /// The time when this instance shut down. 24 | /// 25 | public DateTimeOffset? ShutdownTime { get; set; } 26 | 27 | /// 28 | /// The last time when this instance said it is still alive. 29 | /// 30 | public DateTimeOffset LastSeenTime { get; set; } 31 | 32 | /// 33 | /// All connections that are currently registered for this origin. 34 | /// 35 | [JsonIgnore] 36 | public List? Connections { get; set; } 37 | } 38 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/README.md: -------------------------------------------------------------------------------- 1 | # Thinktecture.Relay.Server.Abstractions 2 | 3 | Contains interfaces and base classes for the server implementation of RelayServer 3. 4 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/RelayServerContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Thinktecture.Relay.Server; 4 | 5 | /// 6 | /// Provides contextual information. 7 | /// 8 | public class RelayServerContext 9 | { 10 | /// 11 | /// The unique id of this instance. 12 | /// 13 | /// This changes during every startup. 14 | public Guid OriginId { get; } = Guid.NewGuid(); 15 | } 16 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Thinktecture.Relay.Server.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Thinktecture RelayServer Server Abstractions 6 | Contains abstractions for the server. 7 | Thinktecture.Relay.Server 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Transport/IAcknowledgeCoordinator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Thinktecture.Relay.Acknowledgement; 5 | 6 | namespace Thinktecture.Relay.Server.Transport; 7 | 8 | /// 9 | /// An implementation of a coordinator for acknowledge requests. 10 | /// 11 | /// The type of acknowledge. 12 | public interface IAcknowledgeCoordinator 13 | where T : IAcknowledgeRequest 14 | { 15 | /// 16 | /// Registers an acknowledge state. 17 | /// 18 | /// The unique id of the request. 19 | /// The unique id of the connection. 20 | /// The id to acknowledge. 21 | /// The request body content is outsourced. 22 | void RegisterRequest(Guid requestId, string connectionId, string acknowledgeId, bool outsourcedRequestBodyContent); 23 | 24 | /// 25 | /// Processes the acknowledge request. 26 | /// 27 | /// The acknowledge request. 28 | /// 29 | /// The token to monitor for cancellation requests. The default value is 30 | /// . 31 | /// 32 | /// A representing the asynchronous operation. 33 | Task ProcessAcknowledgeAsync(T request, CancellationToken cancellationToken = default); 34 | 35 | /// 36 | /// Prunes all outstanding acknowledge ids. 37 | /// 38 | void PruneOutstandingAcknowledgeIds(); 39 | } 40 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Transport/IAcknowledgeDispatcher.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Thinktecture.Relay.Acknowledgement; 4 | 5 | namespace Thinktecture.Relay.Server.Transport; 6 | 7 | /// 8 | /// An implementation of a dispatcher for acknowledge requests. 9 | /// 10 | /// The type of acknowledge. 11 | public interface IAcknowledgeDispatcher 12 | where T : IAcknowledgeRequest 13 | { 14 | /// 15 | /// Dispatches an acknowledge request. 16 | /// 17 | /// The acknowledge request. 18 | /// 19 | /// The token to monitor for cancellation requests. The default value is 20 | /// . 21 | /// 22 | /// A representing the asynchronous operation. 23 | Task DispatchAsync(T request, CancellationToken cancellationToken = default); 24 | } 25 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Transport/IConnectorTransport.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Thinktecture.Relay.Transport; 4 | 5 | namespace Thinktecture.Relay.Server.Transport; 6 | 7 | /// 8 | /// An implementation of a transport for requests. 9 | /// 10 | /// The type of request. 11 | public interface IConnectorTransport 12 | where T : IClientRequest 13 | { 14 | /// 15 | /// Transports the client request. 16 | /// 17 | /// The client request. 18 | /// 19 | /// The token to monitor for cancellation requests. The default value is 20 | /// . 21 | /// 22 | /// A representing the asynchronous operation. 23 | Task TransportAsync(T request, CancellationToken cancellationToken = default); 24 | } 25 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Transport/IConnectorTransportFactory.cs: -------------------------------------------------------------------------------- 1 | using Thinktecture.Relay.Transport; 2 | 3 | namespace Thinktecture.Relay.Server.Transport; 4 | 5 | /// 6 | /// An implementation of a factory to create an instance of a class implementing . 7 | /// 8 | /// The type of request. 9 | public interface IConnectorTransportFactory 10 | where T : IClientRequest 11 | { 12 | /// 13 | /// Creates an instance of a class implementing . 14 | /// 15 | /// The unique connection id. 16 | /// An . 17 | IConnectorTransport Create(string connectionId); 18 | } 19 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Transport/IRelayClientRequestFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using Thinktecture.Relay.Transport; 6 | 7 | namespace Thinktecture.Relay.Server.Transport; 8 | 9 | /// 10 | /// An implementation of a factory to create an instance of a class implementing . 11 | /// 12 | public interface IRelayClientRequestFactory 13 | where TRequest : IClientRequest 14 | { 15 | /// 16 | /// Creates an instance of a class implementing . 17 | /// 18 | /// The unique name of the tenant. 19 | /// The unique id of the request. 20 | /// The . 21 | /// 22 | /// The token to monitor for cancellation requests. The default value is 23 | /// . 24 | /// 25 | /// 26 | /// A representing the asynchronous operation, which wraps the creation of an instance 27 | /// implementing . 28 | /// 29 | Task CreateAsync(string tenantName, Guid requestId, HttpRequest httpRequest, 30 | CancellationToken cancellationToken = default); 31 | } 32 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Transport/IRelayTargetResponseWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | using Thinktecture.Relay.Transport; 5 | 6 | namespace Thinktecture.Relay.Server.Transport; 7 | 8 | /// 9 | /// An implementation of a writer for to . 10 | /// 11 | /// The type of request. 12 | /// The type of response. 13 | public interface IRelayTargetResponseWriter 14 | where TRequest : IClientRequest 15 | where TResponse : class, ITargetResponse 16 | { 17 | /// 18 | /// Writes the target response to the . 19 | /// 20 | /// An . 21 | /// An . 22 | /// The . 23 | /// 24 | /// The token to monitor for cancellation requests. The default value is 25 | /// . 26 | /// 27 | /// A representing the asynchronous operation. 28 | Task WriteAsync(TRequest clientRequest, TResponse? targetResponse, HttpResponse httpResponse, 29 | CancellationToken cancellationToken = default); 30 | } 31 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Transport/IRequestCoordinator.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Thinktecture.Relay.Transport; 4 | 5 | namespace Thinktecture.Relay.Server.Transport; 6 | 7 | /// 8 | /// An implementation of a coordinator for requests. 9 | /// 10 | /// The type of request. 11 | public interface IRequestCoordinator 12 | where TRequest : IClientRequest 13 | { 14 | /// 15 | /// Processes the client request. 16 | /// 17 | /// The client request. 18 | /// 19 | /// The token to monitor for cancellation requests. The default value is 20 | /// . 21 | /// 22 | /// A representing the asynchronous operation. 23 | Task ProcessRequestAsync(TRequest request, CancellationToken cancellationToken = default); 24 | } 25 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Transport/IResponseContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Thinktecture.Relay.Transport; 3 | 4 | namespace Thinktecture.Relay.Server.Transport; 5 | 6 | /// 7 | /// An implementation of a response context containing the target response and an optional disposable. 8 | /// 9 | /// The type of response. 10 | public interface IResponseContext 11 | where T : ITargetResponse 12 | { 13 | /// 14 | /// The target response. 15 | /// 16 | T Response { get; } 17 | 18 | /// 19 | /// An which should be disposed when the response body content is no longer needed. 20 | /// 21 | IAsyncDisposable? Disposable { get; } 22 | } 23 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Transport/IResponseDispatcher.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Thinktecture.Relay.Transport; 4 | 5 | namespace Thinktecture.Relay.Server.Transport; 6 | 7 | /// 8 | /// An implementation of a dispatcher for target responses. 9 | /// 10 | /// The type of response. 11 | public interface IResponseDispatcher 12 | where T : ITargetResponse 13 | { 14 | /// 15 | /// Dispatches the target response. 16 | /// 17 | /// The target response. 18 | /// 19 | /// The token to monitor for cancellation requests. The default value is 20 | /// . 21 | /// 22 | /// A representing the asynchronous operation. 23 | Task DispatchAsync(T response, CancellationToken cancellationToken = default); 24 | } 25 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Transport/IServerTransport.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Thinktecture.Relay.Acknowledgement; 3 | using Thinktecture.Relay.Transport; 4 | 5 | namespace Thinktecture.Relay.Server.Transport; 6 | 7 | /// 8 | /// An implementation of a dispatcher for server to server messages. 9 | /// 10 | /// The type of response. 11 | /// The type of acknowledge. 12 | public interface IServerTransport 13 | where TResponse : ITargetResponse 14 | where TAcknowledge : IAcknowledgeRequest 15 | { 16 | /// 17 | /// The maximum size of binary data the protocol is capable to serialize inline, or null if there is no limit. 18 | /// 19 | int? BinarySizeThreshold { get; } 20 | 21 | /// 22 | /// Dispatches the target response to the origin server. 23 | /// 24 | /// The target response. 25 | /// A representing the asynchronous operation. 26 | Task DispatchResponseAsync(TResponse response); 27 | 28 | /// 29 | /// Dispatches the acknowledge request to the origin server. 30 | /// 31 | /// The acknowledge request. 32 | /// A representing the asynchronous operation. 33 | Task DispatchAcknowledgeAsync(TAcknowledge request); 34 | } 35 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Transport/ITenantHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Thinktecture.Relay.Server.Transport; 5 | 6 | /// 7 | /// An implementation of a handler processing request messages from the transport. 8 | /// 9 | public interface ITenantHandler 10 | { 11 | /// 12 | /// Acknowledges a client request. 13 | /// 14 | /// The unique id of the acknowledge. 15 | /// 16 | /// The token to monitor for cancellation requests. The default value is 17 | /// . 18 | /// 19 | /// A representing the asynchronous operation. 20 | Task AcknowledgeAsync(string acknowledgeId, CancellationToken cancellationToken = default); 21 | } 22 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Transport/ITenantHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Thinktecture.Relay.Server.Transport; 2 | 3 | /// 4 | /// An implementation of a factory to create an instance if a class implementing . 5 | /// 6 | public interface ITenantHandlerFactory 7 | { 8 | /// 9 | /// Creates an instance of a class implementing . 10 | /// 11 | /// The unique name of the tenant. 12 | /// The unique id of the connection. 13 | /// The amount of maximum concurrent requests. 14 | /// An . 15 | ITenantHandler Create(string tenantName, string connectionId, int maximumConcurrentRequests); 16 | } 17 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Abstractions/Transport/ITenantTransport.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Thinktecture.Relay.Transport; 3 | 4 | namespace Thinktecture.Relay.Server.Transport; 5 | 6 | /// 7 | /// An implementation of a dispatcher for client requests to a tenant. 8 | /// 9 | /// The type of request. 10 | public interface ITenantTransport 11 | where T : IClientRequest 12 | { 13 | /// 14 | /// The maximum size of binary data the protocol is capable to serialize inline, or null if there is no limit. 15 | /// 16 | int? BinarySizeThreshold { get; } 17 | 18 | /// 19 | /// Transports the request to a tenant. 20 | /// 21 | /// The client request. 22 | /// A representing the asynchronous operation. 23 | Task TransportAsync(T request); 24 | } 25 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Interceptors/ForwardedHeaderInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Thinktecture.Relay.Server.Interceptor; 4 | using Thinktecture.Relay.Server.Transport; 5 | using Thinktecture.Relay.Transport; 6 | 7 | namespace Thinktecture.Relay.Server.Interceptors; 8 | 9 | // ReSharper disable once ClassNeverInstantiated.Global 10 | internal class ForwardedHeaderInterceptor : IClientRequestInterceptor 11 | where TRequest : IClientRequest 12 | where TResponse : class, ITargetResponse 13 | { 14 | public Task OnRequestReceivedAsync(IRelayContext context, 15 | CancellationToken cancellationToken = default) 16 | { 17 | context.ClientRequest.HttpHeaders.Add("X-Forwarded-For", 18 | new[] { context.HttpContext.Connection.RemoteIpAddress.ToString() }); 19 | context.ClientRequest.HttpHeaders.Add("X-Forwarded-Host", new[] { context.HttpContext.Request.Host.Host }); 20 | context.ClientRequest.HttpHeaders.Add("X-Forwarded-Proto", new[] { context.HttpContext.Request.Scheme }); 21 | return Task.CompletedTask; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Interceptors/README.md: -------------------------------------------------------------------------------- 1 | # Thinktecture.Relay.Server.Interceptors 2 | 3 | Contains optional interceptor implementations for the RelayServer 3. 4 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Interceptors/RelayServerBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Thinktecture.Relay.Acknowledgement; 2 | using Thinktecture.Relay.Server.Interceptors; 3 | using Thinktecture.Relay.Server.DependencyInjection; 4 | using Thinktecture.Relay.Transport; 5 | 6 | // ReSharper disable once CheckNamespace; (extension methods on IServiceCollection namespace) 7 | namespace Microsoft.Extensions.DependencyInjection; 8 | 9 | /// 10 | /// Extension methods for the . 11 | /// 12 | public static class RelayServerBuilderExtensions 13 | { 14 | /// 15 | /// Adds the X-Forwarded header interceptor. 16 | /// 17 | /// The instance. 18 | /// The type of request. 19 | /// The type of response. 20 | /// The type of acknowledge. 21 | /// The instance. 22 | public static IRelayServerBuilder AddForwardedHeaderInterceptor( 24 | this IRelayServerBuilder builder) 25 | where TRequest : IClientRequest 26 | where TResponse : class, ITargetResponse 27 | where TAcknowledge : IAcknowledgeRequest 28 | { 29 | builder 30 | .AddClientRequestInterceptor>(); 32 | 33 | return builder; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Interceptors/Thinktecture.Relay.Server.Interceptors.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Thinktecture RelayServer Interceptors 6 | Contains interceptors for the RelayServer server. 7 | Thinktecture.Relay.Server.Interceptors 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Management/DataTransferObjects/IdResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Thinktecture.Relay.Server.Management.DataTransferObjects; 4 | 5 | /// 6 | /// Represents a result that carries an Id. 7 | /// 8 | public class IdResult 9 | { 10 | /// 11 | /// Gets or sets an Id. 12 | /// 13 | public Guid Id { get; set; } 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public IdResult() { } 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// An initial value for the property. 24 | public IdResult(Guid id) 25 | { 26 | Id = id; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Management/DataTransferObjects/TenantCredential.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Thinktecture.Relay.Server.Management.DataTransferObjects; 4 | 5 | /// 6 | /// Represents a stored credential for a tenant. 7 | /// 8 | public class TenantCredential 9 | { 10 | /// 11 | /// Gets or sets a unique identifier for this specific credential. 12 | /// 13 | public Guid Id { get; set; } 14 | 15 | /// 16 | /// Gets or sets a SHA256 or SHA512 value that represents the value of this credential. 17 | /// 18 | public string? Value { get; set; } 19 | 20 | /// 21 | /// Gets or sets a plain text value. To be used only for creating a new credential. 22 | /// 23 | public string? PlainTextValue { get; set; } 24 | 25 | /// 26 | /// Gets or sets the date and time when this credential was created at. 27 | /// 28 | public DateTime Created { get; set; } 29 | 30 | /// 31 | /// Gets or sets the date and time when this credential expires. 32 | /// 33 | public DateTime? Expiration { get; set; } 34 | } 35 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Management/Extensions/EndpointRouteBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Routing; 2 | using Thinktecture.Relay.Server.Management.Endpoints; 3 | 4 | namespace Thinktecture.Relay.Server.Management.Extensions; 5 | 6 | /// 7 | /// Provides extensions methods for the . 8 | /// 9 | public static class EndpointRouteBuilderExtensions 10 | { 11 | /// 12 | /// Adds the RelayServer management api endpoints with an optional path. 13 | /// 14 | /// The web application to register the endpoints with. 15 | /// The base path under which the endpoints should be mapped (defaults to /management). 16 | public static void UseRelayServerManagementEndpoints(this IEndpointRouteBuilder app, string basePath = "/management") 17 | { 18 | var endpointPath = $"{basePath}/tenants"; 19 | 20 | app.MapGetTenantsPaged(endpointPath, ManagementApiPolicyNames.Read); 21 | app.MapGetTenant(endpointPath, ManagementApiPolicyNames.Read); 22 | app.MapPostTenant(endpointPath, ManagementApiPolicyNames.Write); 23 | app.MapPutTenant(endpointPath, ManagementApiPolicyNames.Write); 24 | app.MapDeleteTenant(endpointPath, ManagementApiPolicyNames.Write); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Management/ManagementApiPolicyNames.cs: -------------------------------------------------------------------------------- 1 | namespace Thinktecture.Relay.Server.Management; 2 | 3 | /// 4 | /// Provides constant values for the management api defaults. 5 | /// 6 | public static class ManagementApiPolicyNames 7 | { 8 | 9 | /// 10 | /// The default policy name for the read permissions. 11 | /// 12 | public const string Read = "managementapi:read"; 13 | 14 | /// 15 | /// The default policy name for the write permissions. 16 | /// 17 | public const string Write = "managementapi:write"; 18 | } 19 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Management/README.md: -------------------------------------------------------------------------------- 1 | # Thinktecture.Relay.Server.Management 2 | 3 | Contains endpoints for an optional RelayServer 3 management API. 4 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Management/Thinktecture.Relay.Server.Management.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Thinktecture RelayServer Server Management 6 | Contains management support for the Thinktecture Relay Server. 7 | Thinktecture.Relay.Server.Management 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.PostgreSql/Migrations/ConfigurationDb/20231109143956_Add_RequireAuthentication.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.PostgreSql.Migrations.ConfigurationDb 6 | { 7 | public partial class Add_RequireAuthentication : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.AddColumn( 12 | name: "RequireAuthentication", 13 | table: "Tenants", 14 | type: "boolean", 15 | nullable: false, 16 | defaultValue: false); 17 | } 18 | 19 | protected override void Down(MigrationBuilder migrationBuilder) 20 | { 21 | migrationBuilder.DropColumn( 22 | name: "RequireAuthentication", 23 | table: "Tenants"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.PostgreSql/Migrations/ConfigurationDb/20231109154430_Add_MaximumConcurrentConnectorRequests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.PostgreSql.Migrations.ConfigurationDb 6 | { 7 | public partial class Add_MaximumConcurrentConnectorRequests : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.AddColumn( 12 | name: "MaximumConcurrentConnectorRequests", 13 | table: "Tenants", 14 | type: "integer", 15 | nullable: false, 16 | defaultValue: 0); 17 | } 18 | 19 | protected override void Down(MigrationBuilder migrationBuilder) 20 | { 21 | migrationBuilder.DropColumn( 22 | name: "MaximumConcurrentConnectorRequests", 23 | table: "Tenants"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.PostgreSql/README.md: -------------------------------------------------------------------------------- 1 | # Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.PostgreSql 2 | 3 | Contains the PostgreSQL specific migrations for the RelayServer 3 EF Core persistence. 4 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.PostgreSql/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.PostgreSql.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Thinktecture RelayServer EntityFramework PostgreSQL 6 | Contains the EF Core migrations and extension methods for using PostgreSQL as persistence. 7 | CS1591 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.SqlServer/Migrations/ConfigurationDb/20231109143959_Add_RequireAuthentication.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.SqlServer.Migrations.ConfigurationDb 6 | { 7 | public partial class Add_RequireAuthentication : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.AddColumn( 12 | name: "RequireAuthentication", 13 | table: "Tenants", 14 | type: "bit", 15 | nullable: false, 16 | defaultValue: false); 17 | } 18 | 19 | protected override void Down(MigrationBuilder migrationBuilder) 20 | { 21 | migrationBuilder.DropColumn( 22 | name: "RequireAuthentication", 23 | table: "Tenants"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.SqlServer/Migrations/ConfigurationDb/20231109154433_Add_MaximumConcurrentConnectorRequests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.SqlServer.Migrations.ConfigurationDb 6 | { 7 | public partial class Add_MaximumConcurrentConnectorRequests : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.AddColumn( 12 | name: "MaximumConcurrentConnectorRequests", 13 | table: "Tenants", 14 | type: "int", 15 | nullable: false, 16 | defaultValue: 0); 17 | } 18 | 19 | protected override void Down(MigrationBuilder migrationBuilder) 20 | { 21 | migrationBuilder.DropColumn( 22 | name: "MaximumConcurrentConnectorRequests", 23 | table: "Tenants"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.SqlServer/README.md: -------------------------------------------------------------------------------- 1 | # Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.SqlServer 2 | 3 | Contains the SQL Server specific migrations for the RelayServer 3 EF Core persistence. 4 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.SqlServer/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.SqlServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Thinktecture RelayServer EntityFramework SqlServer 6 | Contains the EF Core migrations and extension methods for using Microsoft SQL Server as persistence. 7 | CS1591 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore/ConnectionService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace Thinktecture.Relay.Server.Persistence.EntityFrameworkCore; 6 | 7 | /// 8 | public class ConnectionService : IConnectionService 9 | { 10 | private readonly RelayDbContext _dbContext; 11 | 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The Entity Framework Core database context. 16 | public ConnectionService(RelayDbContext dbContext) 17 | => _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); 18 | 19 | /// 20 | public Task IsConnectionAvailableAsync(string tenantName) 21 | => _dbContext.Connections.AnyAsync(c => c.TenantName == tenantName); 22 | } 23 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore/OrderedQueryableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.EntityFrameworkCore; 4 | using Thinktecture.Relay.Server.Persistence.DataTransferObjects; 5 | 6 | // ReSharper disable once CheckNamespace; (extension methods on OrderedQueryables namespace) 7 | namespace System.Linq; 8 | 9 | /// 10 | /// Provides extension methods for the type. 11 | /// 12 | public static class OrderedQueryableExtensions 13 | { 14 | /// 15 | /// Creates a paged result from a query in an asynchronous fashion. 16 | /// 17 | /// The to query for a page. 18 | /// The amount of items to skip. 19 | /// The amount of items to take. 20 | /// The token to monitor for cancellation requests. The default value is 21 | /// . 22 | /// 23 | /// The type of the . 24 | /// A paged result containing the corresponding items. 25 | public static async Task> ToPagedResultAsync(this IOrderedQueryable source, int skip, int take, 26 | CancellationToken cancellationToken = default) 27 | => new Page() 28 | { 29 | TotalCount = await source.CountAsync(cancellationToken), 30 | Results = await source.Skip(skip).Take(take).ToArrayAsync(cancellationToken), 31 | Offset = skip, 32 | PageSize = take, 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore/README.md: -------------------------------------------------------------------------------- 1 | # Thinktecture.Relay.Server.Persistence.EntityFrameworkCore 2 | 3 | Contains the EF Core context and entities for persisting RelayServer 3 information in a relational database. 4 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore/RelayDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Thinktecture.Relay.Server.Persistence.Models; 3 | 4 | namespace Thinktecture.Relay.Server.Persistence.EntityFrameworkCore; 5 | 6 | /// 7 | /// Provides EntityFrameworkCore data access. 8 | /// 9 | public class RelayDbContext : DbContext 10 | { 11 | /// 12 | /// The tenants that can connect to the server with their connectors. 13 | /// 14 | /// Tenants were formerly known als Links in previous versions. 15 | public DbSet Tenants { get; set; } = default!; 16 | 17 | /// 18 | /// The client secrets a connector needs for authentication when connecting to the server. 19 | /// 20 | public DbSet ClientSecrets { get; set; } = default!; 21 | 22 | /// 23 | /// The relay server instances. 24 | /// 25 | public DbSet Origins { get; set; } = default!; 26 | 27 | /// 28 | /// The connections. 29 | /// 30 | public DbSet Connections { get; set; } = default!; 31 | 32 | /// 33 | /// The tenant configs. 34 | /// 35 | public DbSet Configs { get; set; } = default!; 36 | 37 | /// 38 | /// The requests. 39 | /// 40 | public DbSet Requests { get; set; } = default!; 41 | 42 | /// 43 | public RelayDbContext(DbContextOptions options) 44 | : base(options) 45 | { 46 | } 47 | 48 | /// 49 | protected override void OnModelCreating(ModelBuilder modelBuilder) 50 | { 51 | base.OnModelCreating(modelBuilder); 52 | 53 | modelBuilder.ConfigureEntities(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore/RequestService.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | using Thinktecture.Relay.Server.Persistence.Models; 4 | 5 | namespace Thinktecture.Relay.Server.Persistence.EntityFrameworkCore; 6 | 7 | public partial class RequestService 8 | { 9 | private static partial class Log 10 | { 11 | [LoggerMessage(LoggingEventIds.RequestServiceStoringRequest, LogLevel.Trace, "Storing request {@Request}")] 12 | public static partial void StoringRequest(ILogger logger, Request request); 13 | 14 | [LoggerMessage(LoggingEventIds.RequestServiceErrorStoringRequest, LogLevel.Error, 15 | "An error occured while storing request {RelayRequestId}")] 16 | public static partial void ErrorStoringRequest(ILogger logger, Exception exception, Guid relayRequestId); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore/RequestService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Logging; 4 | using Thinktecture.Relay.Server.Persistence.Models; 5 | 6 | namespace Thinktecture.Relay.Server.Persistence.EntityFrameworkCore; 7 | 8 | /// 9 | public partial class RequestService : IRequestService 10 | { 11 | private readonly RelayDbContext _dbContext; 12 | private readonly ILogger _logger; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// An . 18 | /// The Entity Framework Core database context. 19 | public RequestService(ILogger logger, RelayDbContext dbContext) 20 | { 21 | _logger = logger; 22 | _dbContext = dbContext; 23 | } 24 | 25 | /// 26 | public async Task StoreRequestAsync(Request request) 27 | { 28 | Log.StoringRequest(_logger, request); 29 | 30 | try 31 | { 32 | _dbContext.Add(request); 33 | await _dbContext.SaveChangesAsync(); 34 | } 35 | catch (OperationCanceledException) 36 | { 37 | // Ignore this, as this will be thrown when the service shuts down gracefully 38 | } 39 | catch (Exception ex) 40 | { 41 | Log.ErrorStoringRequest(_logger, ex, request.RequestId); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.DependencyInjection.Extensions; 3 | 4 | namespace Thinktecture.Relay.Server.Persistence.EntityFrameworkCore; 5 | 6 | /// 7 | /// Extension methods for the . 8 | /// 9 | public static class ServiceCollectionExtensions 10 | { 11 | /// 12 | /// Adds the EntityFrameworkCore repositories to the . 13 | /// 14 | /// The . 15 | /// The . 16 | public static IServiceCollection AddRelayServerEntityFrameworkCoreRepositories(this IServiceCollection services) 17 | { 18 | services.TryAddScoped(); 19 | services.TryAddScoped(); 20 | services.TryAddScoped(); 21 | services.TryAddScoped(); 22 | 23 | return services; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore/ServiceProviderExtensions.Log.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace Thinktecture.Relay.Server.Persistence.EntityFrameworkCore; 4 | 5 | public static partial class ServiceProviderExtensions 6 | { 7 | private static partial class Log 8 | { 9 | [LoggerMessage(LoggingEventIds.ServiceProviderExtensionsApplyingPendingMigrations, LogLevel.Information, 10 | "Applying {MigrationCount} pending migration(s)")] 11 | public static partial void ApplyingPendingMigrations(ILogger logger, int migrationCount); 12 | 13 | [LoggerMessage(LoggingEventIds.ServiceProviderExtensionsNoMigrationsPending, LogLevel.Debug, 14 | "No migrations pending")] 15 | public static partial void NoMigrationsPending(ILogger logger); 16 | 17 | [LoggerMessage(LoggingEventIds.ServiceProviderExtensionsCannotRollback, LogLevel.Error, 18 | "Cannot rollback any migrations on a non-migrated database")] 19 | public static partial void CannotRollback(ILogger logger); 20 | 21 | [LoggerMessage(LoggingEventIds.ServiceProviderExtensionsMigrationNotFound, LogLevel.Warning, 22 | "The provided target migration \"{TargetMigration}\" was not found in the already applied migrations (\"{Migrations}\")")] 23 | public static partial void MigrationNotFound(ILogger logger, string targetMigration, string migrations); 24 | 25 | [LoggerMessage(LoggingEventIds.ServiceProviderExtensionsRollingBack, LogLevel.Warning, 26 | "Rolling back to migration {TargetMigration}")] 27 | public static partial void RollingBack(ILogger logger, string targetMigration); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore/Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Thinktecture RelayServer EntityFramework 6 | Contains the EF Core context for data persistence. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.RabbitMq/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Thinktecture.Relay.Server.Protocols.RabbitMq; 2 | 3 | internal static class Constants 4 | { 5 | public const string ExchangeName = "RelayServer"; 6 | 7 | /// 8 | /// The prefix for request queues. 9 | /// 10 | /// Should be followed by a space and the tenant name. These queues won't be removed after creation. 11 | public const string RequestQueuePrefix = "Requests"; 12 | 13 | /// 14 | /// The prefix for response queues. 15 | /// 16 | /// Should be followed by a space and the origin id. These queues will be removed when the owning origin is shut down. 17 | public const string ResponseQueuePrefix = "Responses"; 18 | 19 | /// 20 | /// The prefix for acknowledge queues. 21 | /// 22 | /// Should be followed by a space and the origin id. These queues will be removed when the owning origin is shut down. 23 | public const string AcknowledgeQueuePrefix = "Acknowledgements"; 24 | } 25 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.RabbitMq/DisposableConsumer.Log.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace Thinktecture.Relay.Server.Protocols.RabbitMq; 4 | 5 | internal partial class DisposableConsumer 6 | { 7 | private static partial class Log 8 | { 9 | [LoggerMessage(LoggingEventIds.ModelExtensionsConsumingConsumer, LogLevel.Trace, 10 | "Consuming {QueueName} with consumer {ConsumerTag}")] 11 | public static partial void ConsumingConsumer(ILogger logger, string queueName, string? consumerTag); 12 | 13 | [LoggerMessage(LoggingEventIds.ModelExtensionsLostConsumer, LogLevel.Warning, 14 | "Lost consumer {ConsumerTag} on queue {QueueName}")] 15 | public static partial void LostConsumer(ILogger logger, string consumerTag, string queueName); 16 | 17 | [LoggerMessage(LoggingEventIds.ModelExtensionsRestoredConsumer, LogLevel.Information, 18 | "Restored consumer {ConsumerTag} on queue {QueueName} (was {OldConsumerTag})")] 19 | public static partial void RestoredConsumer(ILogger logger, string consumerTag, string queueName, 20 | string oldConsumerTag); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.RabbitMq/LoggingEventIds.cs: -------------------------------------------------------------------------------- 1 | namespace Thinktecture.Relay.Server.Protocols.RabbitMq; 2 | 3 | internal static class LoggingEventIds 4 | { 5 | public const int ModelExtensionsConsumingConsumer = 10001; 6 | public const int ModelExtensionsLostConsumer = 10002; 7 | public const int ModelExtensionsRestoredConsumer = 10003; 8 | 9 | public const int ModelFactoryConnectionRecovered = 10101; 10 | public const int ModelFactoryConnectionClosed = 10102; 11 | public const int ModelFactoryModelCreated = 10103; 12 | public const int ModelFactoryModelCallbackError = 10104; 13 | public const int ModelFactoryModelClosed = 10105; 14 | 15 | public const int ServerTransportDispatchingAcknowledge = 10201; 16 | public const int ServerTransportDispatchedResponse = 10202; 17 | public const int ServerTransportDispatchedAcknowledge = 10203; 18 | public const int ServerTransportResponseConsumed = 10204; 19 | public const int ServerTransportAcknowledgeConsumed = 10205; 20 | 21 | public const int TenantHandlerAcknowledge = 10301; 22 | public const int TenantHandlerCouldNotParseAcknowledge = 10302; 23 | public const int TenantHandlerReceivedRequest = 10303; 24 | 25 | public const int TenantTransportPublishedRequest = 10401; 26 | public const int TenantTransportErrorDispatchingRequest = 10402; 27 | public const int TenantTransportLoadedTenantNames = 10403; 28 | public const int TenantTransportEnsuredTenantQueue = 10404; 29 | } 30 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.RabbitMq/ModelFactory.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Protocols.RabbitMq; 5 | 6 | public partial class ModelFactory 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.ModelFactoryConnectionRecovered, LogLevel.Information, 11 | "Connection successful recovered")] 12 | public static partial void ConnectionRecovered(ILogger logger); 13 | 14 | [LoggerMessage(LoggingEventIds.ModelFactoryConnectionClosed, LogLevel.Debug, 15 | "Connection closed ({ShutdownReason})")] 16 | public static partial void ConnectionClosed(ILogger logger, string shutdownReason); 17 | 18 | [LoggerMessage(LoggingEventIds.ModelFactoryModelCreated, LogLevel.Trace, 19 | "Model created for {ModelContext} with channel {ModelChannel}")] 20 | public static partial void ModelCreated(ILogger logger, string modelContext, int modelChannel); 21 | 22 | [LoggerMessage(LoggingEventIds.ModelFactoryModelCallbackError, LogLevel.Error, 23 | "An error occured in a model callback for {ModelContext} with channel {ModelChannel}")] 24 | public static partial void ModelCallbackError(ILogger logger, Exception ex, 25 | string modelContext, int modelChannel); 26 | 27 | [LoggerMessage(LoggingEventIds.ModelFactoryModelClosed, LogLevel.Trace, 28 | "Model for {ModelContext} with channel {ModelChannel} closed ({ShutdownReason})")] 29 | public static partial void ModelClosed(ILogger logger, string modelContext, int modelChannel, 30 | string shutdownReason); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.RabbitMq/README.md: -------------------------------------------------------------------------------- 1 | # Thinktecture.Relay.Server.Protocols.RabbitMq 2 | 3 | Contains the RabbitMQ specific implementation of the server to server transport in RelayServer 3. 4 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.RabbitMq/RabbitMqOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Thinktecture.Relay.Server.Protocols.RabbitMq; 2 | 3 | /// 4 | /// Configuration options for Rabbit MQ. 5 | /// 6 | public class RabbitMqOptions 7 | { 8 | /// 9 | /// The Uri to connect to. 10 | /// 11 | public string Uri { get; set; } = default!; 12 | 13 | /// 14 | /// The comma-separated hosts of a Rabbit MQ cluster. 15 | /// 16 | public string? ClusterHosts { get; set; } 17 | 18 | /// 19 | /// The maximum size for inline binary data in a Rabbit MQ message. 20 | /// 21 | /// The default value is 1 megabyte. 22 | public int MaximumBinarySize { get; set; } = 1024 * 1024; // 1mb 23 | } 24 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.RabbitMq/RoundRobinEndpointResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using RabbitMQ.Client; 5 | 6 | namespace Thinktecture.Relay.Server.Protocols.RabbitMq; 7 | 8 | /// 9 | public class RoundRobinEndpointResolver : IEndpointResolver 10 | { 11 | private readonly AmqpTcpEndpoint[] _endpoints; 12 | 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The s to use. 17 | public RoundRobinEndpointResolver(IEnumerable endpoints) 18 | { 19 | if (endpoints is null) throw new ArgumentNullException(nameof(endpoints)); 20 | 21 | _endpoints = endpoints.ToArray(); 22 | } 23 | 24 | /// 25 | public IEnumerable All() 26 | => _endpoints; 27 | } 28 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.RabbitMq/ServerTransport.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Protocols.RabbitMq; 5 | 6 | public partial class ServerTransport 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.ServerTransportDispatchingAcknowledge, LogLevel.Trace, 11 | "Dispatching acknowledge {@AcknowledgeRequest}")] 12 | public static partial void DispatchingAcknowledge(ILogger logger, TAcknowledge acknowledgeRequest); 13 | 14 | [LoggerMessage(LoggingEventIds.ServerTransportDispatchedResponse, LogLevel.Trace, 15 | "Dispatched response for request {RelayRequestId} to origin {OriginId}")] 16 | public static partial void DispatchedResponse(ILogger logger, Guid relayRequestId, Guid originId); 17 | 18 | [LoggerMessage(LoggingEventIds.ServerTransportDispatchedAcknowledge, LogLevel.Trace, 19 | "Dispatched acknowledgement for request {RelayRequestId} to origin {OriginId}")] 20 | public static partial void DispatchedAcknowledge(ILogger logger, Guid relayRequestId, Guid originId); 21 | 22 | [LoggerMessage(LoggingEventIds.ServerTransportResponseConsumed, LogLevel.Trace, 23 | "Received response for request {RelayRequestId} from queue {QueueName} by consumer {ConsumerTag}")] 24 | public static partial void ResponseConsumed(ILogger logger, Guid relayRequestId, string queueName, 25 | string consumerTag); 26 | 27 | [LoggerMessage(LoggingEventIds.ServerTransportAcknowledgeConsumed, LogLevel.Trace, 28 | "Received acknowledge for request {RelayRequestId} from queue {QueueName} by consumer {ConsumerTag}")] 29 | public static partial void AcknowledgeConsumed(ILogger logger, Guid relayRequestId, string queueName, 30 | string consumerTag); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.RabbitMq/TenantHandler.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Protocols.RabbitMq; 5 | 6 | public partial class TenantHandler 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.TenantHandlerAcknowledge, LogLevel.Trace, "Acknowledging {AcknowledgeId}")] 11 | public static partial void Acknowledge(ILogger logger, string acknowledgeId); 12 | 13 | [LoggerMessage(LoggingEventIds.TenantHandlerCouldNotParseAcknowledge, LogLevel.Warning, 14 | "Could not parse acknowledge id {AcknowledgeId}")] 15 | public static partial void CouldNotParseAcknowledge(ILogger logger, string acknowledgeId); 16 | 17 | [LoggerMessage(LoggingEventIds.TenantHandlerReceivedRequest, LogLevel.Trace, 18 | "Received request {RelayRequestId} from queue {QueueName} by consumer {ConsumerTag}")] 19 | public static partial void ReceivedRequest(ILogger logger, Guid relayRequestId, string queueName, 20 | string consumerTag); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.RabbitMq/TenantHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Thinktecture.Relay.Acknowledgement; 4 | using Thinktecture.Relay.Server.Transport; 5 | using Thinktecture.Relay.Transport; 6 | 7 | namespace Thinktecture.Relay.Server.Protocols.RabbitMq; 8 | 9 | /// 10 | public class TenantHandlerFactory : ITenantHandlerFactory 11 | where TRequest : IClientRequest 12 | where TAcknowledge : IAcknowledgeRequest 13 | { 14 | private readonly IServiceProvider _serviceProvider; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// An . 20 | public TenantHandlerFactory(IServiceProvider serviceProvider) 21 | => _serviceProvider = serviceProvider; 22 | 23 | /// 24 | public ITenantHandler Create(string tenantName, string connectionId, int maximumConcurrentRequests) 25 | => ActivatorUtilities.CreateInstance>(_serviceProvider, tenantName, 26 | connectionId, maximumConcurrentRequests); 27 | } 28 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.RabbitMq/TenantTransport.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Protocols.RabbitMq; 5 | 6 | public partial class TenantTransport 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.TenantTransportPublishedRequest, LogLevel.Trace, 11 | "Published request {RelayRequestId} to tenant {TenantName}")] 12 | public static partial void PublishedRequest(ILogger logger, Guid relayRequestId, string tenantName); 13 | 14 | [LoggerMessage(LoggingEventIds.TenantTransportErrorDispatchingRequest, LogLevel.Error, 15 | "An error occured while dispatching request {RelayRequestId} to tenant {TenantName} queue")] 16 | public static partial void ErrorDispatchingRequest(ILogger logger, Exception ex, Guid relayRequestId, 17 | string tenantName); 18 | 19 | [LoggerMessage(LoggingEventIds.TenantTransportLoadedTenantNames, LogLevel.Information, 20 | "Loaded {TenantCount} tenant names")] 21 | public static partial void LoadedTenantNames(ILogger logger, int tenantCount); 22 | 23 | [LoggerMessage(LoggingEventIds.TenantTransportEnsuredTenantQueue, LogLevel.Trace, 24 | "Ensured queue {QueueName} for tenant {TenantName}")] 25 | public static partial void EnsuredTenantQueue(ILogger logger, string queueName, string tenantName); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.RabbitMq/Thinktecture.Relay.Server.Protocols.RabbitMq.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Thinktecture RelayServer Server RabbitMQ Protocol 6 | Contains the RabbitMQ server transport implementation for the RelayServer. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.SignalR/ApplicationBuilderPart.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Thinktecture.Relay.Acknowledgement; 3 | using Thinktecture.Relay.Server.DependencyInjection; 4 | using Thinktecture.Relay.Transport; 5 | 6 | namespace Thinktecture.Relay.Server.Protocols.SignalR; 7 | 8 | /// 9 | public class ApplicationBuilderPart : IApplicationBuilderPart 10 | where TRequest : IClientRequest 11 | where TResponse : class, ITargetResponse 12 | where TAcknowledge : IAcknowledgeRequest 13 | { 14 | /// 15 | public void Use(IApplicationBuilder builder) 16 | => builder.UseEndpoints(endpoints => 17 | { 18 | endpoints.MapHub>("/connector"); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.SignalR/ConnectorTransport.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | using Thinktecture.Relay.Transport; 4 | 5 | namespace Thinktecture.Relay.Server.Protocols.SignalR; 6 | 7 | public partial class ConnectorTransport 8 | { 9 | private static partial class Log 10 | { 11 | [LoggerMessage(LoggingEventIds.ConnectorTransportTransportingRequest, LogLevel.Trace, 12 | "Transporting request {@Request} for request {RelayRequestId} on connection {TransportConnectionId}")] 13 | public static partial void TransportingRequest(ILogger logger, IClientRequest request, Guid relayRequestId, 14 | string transportConnectionId); 15 | 16 | [LoggerMessage(LoggingEventIds.ConnectorTransportErrorTransportingRequest, LogLevel.Error, 17 | "An error occured while transporting request {RelayRequestId} on connection {TransportConnectionId}")] 18 | public static partial void ErrorTransportingRequest(ILogger logger, Exception ex, Guid relayRequestId, 19 | string transportConnectionId); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.SignalR/ConnectorTransportFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Thinktecture.Relay.Acknowledgement; 4 | using Thinktecture.Relay.Server.Transport; 5 | using Thinktecture.Relay.Transport; 6 | 7 | namespace Thinktecture.Relay.Server.Protocols.SignalR; 8 | 9 | /// 10 | public class ConnectorTransportFactory : IConnectorTransportFactory 11 | where TRequest : IClientRequest 12 | where TResponse : ITargetResponse 13 | where TAcknowledge : IAcknowledgeRequest 14 | { 15 | private readonly IServiceProvider _serviceProvider; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// An . 21 | public ConnectorTransportFactory(IServiceProvider serviceProvider) 22 | => _serviceProvider = serviceProvider; 23 | 24 | /// 25 | public IConnectorTransport Create(string connectionId) 26 | => ActivatorUtilities.CreateInstance>(_serviceProvider, 27 | connectionId); 28 | } 29 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.SignalR/LoggingEventIds.cs: -------------------------------------------------------------------------------- 1 | namespace Thinktecture.Relay.Server.Protocols.SignalR; 2 | 3 | internal static class LoggingEventIds 4 | { 5 | public const int ConnectorHubErrorNoTenantName = 10001; 6 | public const int ConnectorHubIncomingConnectionCreatedTenant = 10002; 7 | public const int ConnectorHubRejectingUnknownTenant = 10003; 8 | public const int ConnectorHubIncomingConnectionUpdatedTenant = 10004; 9 | public const int ConnectorHubIncomingConnection = 10005; 10 | public const int ConnectorHubDisconnectedError = 10006; 11 | public const int ConnectorHubDisconnected = 10007; 12 | public const int ConnectorHubReceivedResponse = 10008; 13 | public const int ConnectorHubReceivedAcknowledge = 10009; 14 | 15 | public const int ConnectorTransportTransportingRequest = 10101; 16 | public const int ConnectorTransportErrorTransportingRequest = 10102; 17 | } 18 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.SignalR/Options/HubOptionsPostConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.SignalR; 3 | using Microsoft.Extensions.Options; 4 | using Thinktecture.Relay.Acknowledgement; 5 | using Thinktecture.Relay.Transport; 6 | 7 | namespace Thinktecture.Relay.Server.Protocols.SignalR.Options; 8 | 9 | internal class HubOptionsPostConfigureOptions 10 | : IPostConfigureOptions>> 11 | where TRequest : IClientRequest 12 | where TResponse : class, ITargetResponse 13 | where TAcknowledge : IAcknowledgeRequest 14 | { 15 | private readonly RelayServerOptions _relayServerOptions; 16 | 17 | public HubOptionsPostConfigureOptions(IOptions relayServerOptions) 18 | { 19 | if (relayServerOptions is null) throw new ArgumentNullException(nameof(relayServerOptions)); 20 | 21 | _relayServerOptions = relayServerOptions.Value; 22 | } 23 | 24 | public void PostConfigure(string? name, HubOptions> options) 25 | { 26 | options.HandshakeTimeout = _relayServerOptions.HandshakeTimeout; 27 | options.KeepAliveInterval = _relayServerOptions.KeepAliveInterval; 28 | // should always be twice of keep-alive 29 | options.ClientTimeoutInterval = _relayServerOptions.KeepAliveInterval * 2; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.SignalR/Options/JwtBearerPostConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Authentication.JwtBearer; 3 | using Microsoft.Extensions.Options; 4 | 5 | namespace Thinktecture.Relay.Server.Protocols.SignalR.Options; 6 | 7 | internal class JwtBearerPostConfigureOptions : IPostConfigureOptions 8 | { 9 | public void PostConfigure(string? name, JwtBearerOptions options) 10 | { 11 | // We have to hook the OnMessageReceived event in order to 12 | // allow the JWT authentication handler to read the access 13 | // token from the query string when a WebSocket or 14 | // Server-Sent Events request comes in. 15 | 16 | // Sending the access token in the query string is required due to 17 | // a limitation in Browser APIs. We restrict it to only calls to the 18 | // SignalR hub in this code. 19 | // See https://docs.microsoft.com/aspnet/core/signalr/security#access-token-logging 20 | // for more information about security considerations when using 21 | // the query string to transmit the access token. 22 | options.Events = new JwtBearerEvents() 23 | { 24 | OnMessageReceived = context => 25 | { 26 | var accessToken = context.Request.Query["access_token"]; 27 | 28 | // If the request is for our hub... 29 | var path = context.HttpContext.Request.Path; 30 | if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/connector")) 31 | { 32 | // Read the token out of the query string 33 | context.Token = accessToken; 34 | } 35 | 36 | return Task.CompletedTask; 37 | }, 38 | }; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.SignalR/README.md: -------------------------------------------------------------------------------- 1 | # Thinktecture.Relay.Server.Protocols.SignalR 2 | 3 | Contains the SignalR hub implementation in the server part of RelayServer 3. 4 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server.Protocols.SignalR/Thinktecture.Relay.Server.Protocols.SignalR.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Thinktecture RelayServer Server SignalR Protocol 6 | Contains the SignalR server transport implementation for the RelayServer. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/DependencyInjection/RelayServerBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Thinktecture.Relay.Acknowledgement; 4 | using Thinktecture.Relay.Transport; 5 | 6 | namespace Thinktecture.Relay.Server.DependencyInjection; 7 | 8 | internal class 9 | RelayServerBuilder : IRelayServerBuilder 10 | where TRequest : IClientRequest 11 | where TResponse : ITargetResponse 12 | where TAcknowledge : IAcknowledgeRequest 13 | { 14 | public IServiceCollection Services { get; } 15 | 16 | public RelayServerBuilder(IServiceCollection services) 17 | => Services = services ?? throw new ArgumentNullException(nameof(services)); 18 | } 19 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Diagnostics/RelayRequestLogger.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Diagnostics; 5 | 6 | public partial class RelayRequestLogger 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(20400, LogLevel.Trace, "Writing request log for successful request {RelayRequestId}")] 11 | public static partial void Success(ILogger logger, Guid relayRequestId); 12 | 13 | [LoggerMessage(20401, LogLevel.Trace, "Writing request log for aborted request {RelayRequestId}")] 14 | public static partial void Abort(ILogger logger, Guid relayRequestId); 15 | 16 | [LoggerMessage(20402, LogLevel.Trace, "Writing request log for failed request {RelayRequestId}")] 17 | public static partial void Fail(ILogger logger, Guid relayRequestId); 18 | 19 | [LoggerMessage(20403, LogLevel.Trace, "Writing request log for expired request {RelayRequestId}")] 20 | public static partial void Expired(ILogger logger, Guid relayRequestId); 21 | 22 | [LoggerMessage(20404, LogLevel.Trace, "Writing request log for error on request {RelayRequestId}")] 23 | public static partial void Error(ILogger logger, Guid relayRequestId); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Endpoints/AcknowledgeEndpoint.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Endpoints; 5 | 6 | internal partial class AcknowledgeEndpoint 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.AcknowledgeEndpointAcknowledgementReceived, LogLevel.Debug, 11 | "Received acknowledgement for request {RelayRequestId} on origin {OriginId}")] 12 | public static partial void AcknowledgementReceived(ILogger logger, Guid relayRequestId, Guid originId); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Endpoints/AcknowledgeEndpoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Routing; 8 | using Microsoft.Extensions.Logging; 9 | using Thinktecture.Relay.Acknowledgement; 10 | using Thinktecture.Relay.Server.Transport; 11 | 12 | namespace Thinktecture.Relay.Server.Endpoints; 13 | 14 | internal static partial class EndpointRouteBuilderExtensions 15 | { 16 | public static IEndpointRouteBuilder MapAcknowledge(this IEndpointRouteBuilder app, string pattern) 17 | { 18 | app.MapPost($"{pattern}/{{originId:guid}}/{{requestId:guid}}", AcknowledgeEndpoint.HandleRequestAsync) 19 | .WithName("Acknowledge") 20 | .WithDisplayName("Acknowledge") 21 | .Produces(StatusCodes.Status204NoContent); 22 | 23 | return app; 24 | } 25 | } 26 | 27 | internal partial class AcknowledgeEndpoint 28 | { 29 | public static async Task HandleRequestAsync([FromServices] ILogger logger, 30 | [FromRoute] Guid originId, [FromRoute] Guid requestId, 31 | [FromServices] IAcknowledgeDispatcher acknowledgeDispatcher, 32 | CancellationToken cancellationToken = default) 33 | { 34 | Log.AcknowledgementReceived(logger, requestId, originId); 35 | 36 | await acknowledgeDispatcher.DispatchAsync(new AcknowledgeRequest 37 | { 38 | OriginId = originId, 39 | RequestId = requestId, 40 | RemoveRequestBodyContent = true 41 | }, cancellationToken); 42 | 43 | return Results.NoContent(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Endpoints/BodyContentEndpoint.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Endpoints; 5 | 6 | internal partial class BodyContentEndpoint 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.BodyContentEndpointDeliverBody, LogLevel.Debug, 11 | "Delivering request body content for request {RelayRequestId}, should delete: {DeleteBody}")] 12 | public static partial void DeliverBody(ILogger logger, Guid relayRequestId, bool deleteBody); 13 | 14 | [LoggerMessage(LoggingEventIds.BodyContentEndpointStoreBody, LogLevel.Debug, 15 | "Storing response body content for request {RelayRequestId}")] 16 | public static partial void StoreBody(ILogger logger, Guid relayRequestId); 17 | 18 | [LoggerMessage(LoggingEventIds.BodyContentEndpointResponseAborted, LogLevel.Warning, 19 | "Connector for {TenantName} aborted the response body upload for request {RelayRequestId}")] 20 | public static partial void ResponseAborted(ILogger logger, string tenantName, Guid relayRequestId); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Endpoints/DiscoveryDocumentEndpoint.Log.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace Thinktecture.Relay.Server.Endpoints; 4 | 5 | internal partial class DiscoveryDocumentEndpoint 6 | { 7 | private static partial class Log 8 | { 9 | [LoggerMessage(LoggingEventIds.DiscoveryDocumentEndpointReturnDiscoveryDocument, LogLevel.Debug, 10 | "Returning discovery document")] 11 | public static partial void ReturnDiscoveryDocument(ILogger logger); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Endpoints/DiscoveryDocumentEndpoint.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.AspNetCore.Routing; 6 | using Microsoft.Extensions.Logging; 7 | using Thinktecture.Relay.Server.Services; 8 | 9 | namespace Thinktecture.Relay.Server.Endpoints; 10 | 11 | internal static partial class EndpointRouteBuilderExtensions 12 | { 13 | public static IEndpointRouteBuilder MapDiscoveryDocument(this IEndpointRouteBuilder app, string pattern) 14 | { 15 | app.MapGet(pattern, DiscoveryDocumentEndpoint.HandleRequestAsync) 16 | .WithName("GetDiscoveryDocument") 17 | .WithDisplayName("Get discovery document") 18 | .Produces(StatusCodes.Status200OK); 19 | 20 | return app; 21 | } 22 | } 23 | 24 | internal partial class DiscoveryDocumentEndpoint 25 | { 26 | public static async Task HandleRequestAsync([FromServices] ILogger logger, 27 | [FromServices] DiscoveryDocumentBuilder documentBuilder, [FromServices] IHttpContextAccessor httpContextAccessor) 28 | { 29 | Log.ReturnDiscoveryDocument(logger); 30 | 31 | var document = await documentBuilder.BuildAsync(httpContextAccessor.HttpContext!.Request, httpContextAccessor.HttpContext!.RequestAborted); 32 | 33 | return Results.Ok(document); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Maintenance/MaintenanceJobRunner.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Maintenance; 5 | 6 | public partial class MaintenanceJobRunner 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.MaintenanceJobRunnerRunMaintenanceJob, LogLevel.Trace, 11 | "Running maintenance job {MaintenanceJob}")] 12 | public static partial void RunMaintenanceJob(ILogger logger, string? maintenanceJob); 13 | 14 | [LoggerMessage(LoggingEventIds.MaintenanceJobRunnerRunMaintenanceJobFailed, LogLevel.Error, 15 | "An error occured while running maintenance job {MaintenanceJob}")] 16 | public static partial void RunMaintenanceJobFailed(ILogger logger, Exception ex, string? maintenanceJob); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Maintenance/StatisticsCleanupJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Options; 5 | using Thinktecture.Relay.Server.Persistence; 6 | 7 | namespace Thinktecture.Relay.Server.Maintenance; 8 | 9 | /// 10 | public class StatisticsCleanupJob : IMaintenanceJob 11 | { 12 | private readonly StatisticsOptions _statisticsOptions; 13 | private readonly IStatisticsService _statisticsService; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// An . 19 | /// An . 20 | public StatisticsCleanupJob(IStatisticsService statisticsService, 21 | IOptions statisticsOptions) 22 | { 23 | if (statisticsOptions is null) throw new ArgumentNullException(nameof(statisticsOptions)); 24 | 25 | _statisticsService = statisticsService ?? throw new ArgumentNullException(nameof(statisticsService)); 26 | _statisticsOptions = statisticsOptions.Value; 27 | } 28 | 29 | /// 30 | public async Task DoMaintenanceAsync(CancellationToken cancellationToken = default) 31 | { 32 | await _statisticsService.CleanUpOriginsAsync(_statisticsOptions.EntryMaxAge, cancellationToken); 33 | 34 | if (_statisticsOptions.EnableConnectionCleanup) 35 | { 36 | await _statisticsService.CleanUpConnectionsAsync(_statisticsOptions.EntryMaxAge, cancellationToken); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/MaintenanceOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Thinktecture.Relay.Server; 4 | 5 | /// 6 | /// Configuration options for the maintenance system. 7 | /// 8 | public class MaintenanceOptions 9 | { 10 | /// 11 | /// The default run interval. 12 | /// 13 | public static readonly TimeSpan DefaultRunInterval = TimeSpan.FromMinutes(15); 14 | 15 | /// 16 | /// The interval in which maintenance jobs will be run. 17 | /// 18 | public TimeSpan RunInterval { get; set; } = DefaultRunInterval; 19 | } 20 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/README.md: -------------------------------------------------------------------------------- 1 | # Thinktecture.Relay.Server 2 | 3 | Contains the server implementation of RelayServer 3. 4 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Services/RelayTargetResponseWriter.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Thinktecture.Relay.Server.Services; 6 | 7 | public partial class RelayTargetResponseWriter 8 | { 9 | private static partial class Log 10 | { 11 | [LoggerMessage(LoggingEventIds.RelayTargetResponseWriterFailedRequest, LogLevel.Warning, 12 | "The request {RelayRequestId} failed internally with {HttpStatusCode}")] 13 | public static partial void FailedRequest(ILogger logger, Guid relayRequestId, HttpStatusCode httpStatusCode); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/StatisticsOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Thinktecture.Relay.Server; 4 | 5 | /// 6 | /// Configuration options for the statistics system. 7 | /// 8 | public class StatisticsOptions 9 | { 10 | /// 11 | /// The default entry max age. 12 | /// 13 | public static readonly TimeSpan DefaultEntryMaxAge = TimeSpan.FromMinutes(15); 14 | 15 | /// 16 | /// The default origin last seen update interval. 17 | /// 18 | public static readonly TimeSpan DefaultOriginLastSeenUpdateInterval = TimeSpan.FromMinutes(5); 19 | 20 | /// 21 | /// The default connection last seen update interval. 22 | /// 23 | public static readonly TimeSpan DefaultConnectionLastSeenUpdateInterval = TimeSpan.FromMinutes(1); 24 | 25 | /// 26 | /// The time span to keep stale or closed connection and origin entries in the statistics store. 27 | /// 28 | public TimeSpan EntryMaxAge { get; set; } = DefaultEntryMaxAge; 29 | 30 | /// 31 | /// The time interval in which the origin's last seen will be updated. 32 | /// 33 | public TimeSpan OriginLastSeenUpdateInterval { get; set; } = DefaultOriginLastSeenUpdateInterval; 34 | 35 | /// 36 | /// Indicates whether to clean up stale or closed connections or not. 37 | /// 38 | public bool EnableConnectionCleanup { get; set; } 39 | 40 | /// 41 | /// The time interval in which the connection's last seen time will be updated. 42 | /// 43 | public TimeSpan ConnectionLastSeenUpdateInterval { get; set; } = DefaultConnectionLastSeenUpdateInterval; 44 | } 45 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Thinktecture.Relay.Server.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Thinktecture RelayServer Server 6 | Contains the server implementations for the RelayServer. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Transport/AcknowledgeCoordinator.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Transport; 5 | 6 | public partial class AcknowledgeCoordinator 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.AcknowledgeCoordinatorRegisterAcknowledgeState, LogLevel.Trace, 11 | "Registering acknowledge state of request {RelayRequestId} from connection {TransportConnectionId} for id {AcknowledgeId}")] 12 | public static partial void RegisterAcknowledgeState(ILogger logger, Guid relayRequestId, 13 | string transportConnectionId, string acknowledgeId); 14 | 15 | [LoggerMessage(LoggingEventIds.AcknowledgeCoordinatorReRegisterAcknowledgeState, LogLevel.Warning, 16 | "Re-registering an already existing request {RelayRequestId} from connection {TransportConnectionId} for id {AcknowledgeId} – this can happen when a request got queued again and will lead to a re-execution of the request but only the first result be acknowledged and the others will be discarded")] 17 | public static partial void ReRegisterAcknowledgeState(ILogger logger, Guid relayRequestId, 18 | string transportConnectionId, string acknowledgeId); 19 | 20 | [LoggerMessage(LoggingEventIds.AcknowledgeCoordinatorUnknownRequest, LogLevel.Warning, 21 | "Unknown request {RelayRequestId} to acknowledge received")] 22 | public static partial void UnknownRequest(ILogger logger, Guid relayRequestId); 23 | 24 | [LoggerMessage(LoggingEventIds.AcknowledgeCoordinatorPrunedRequest, LogLevel.Debug, 25 | "Request {RelayRequestId} was already pruned and will not be acknowledged – this happens after an auto-recovery of the message queue transport")] 26 | public static partial void PrunedRequest(ILogger logger, Guid relayRequestId); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Transport/AcknowledgeDispatcher.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Transport; 5 | 6 | public partial class AcknowledgeDispatcher 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.AcknowledgeDispatcherLocalAcknowledge, LogLevel.Trace, 11 | "Locally dispatching acknowledge for request {RelayRequestId}")] 12 | public static partial void LocalAcknowledge(ILogger logger, Guid relayRequestId); 13 | 14 | [LoggerMessage(LoggingEventIds.AcknowledgeDispatcherRedirectAcknowledge, LogLevel.Trace, 15 | "Remotely dispatching acknowledge for request {RelayRequestId} to origin {OriginId}")] 16 | public static partial void RedirectAcknowledge(ILogger logger, Guid relayRequestId, Guid originId); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Transport/InMemoryBodyStore.Log.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace Thinktecture.Relay.Server.Transport; 4 | 5 | internal partial class InMemoryBodyStore 6 | { 7 | private static partial class Log 8 | { 9 | [LoggerMessage(LoggingEventIds.InMemoryBodyStoreUsingStorage, LogLevel.Debug, 10 | "Using {StorageType} as body store")] 11 | public static partial void UsingStorage(ILogger logger, string storageType); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Transport/InMemoryServerTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Thinktecture.Relay.Acknowledgement; 4 | using Thinktecture.Relay.Transport; 5 | 6 | namespace Thinktecture.Relay.Server.Transport; 7 | 8 | internal class InMemoryServerTransport : IServerTransport 9 | where TResponse : ITargetResponse 10 | where TAcknowledge : IAcknowledgeRequest 11 | { 12 | private readonly IAcknowledgeCoordinator _acknowledgeCoordinator; 13 | private readonly IResponseCoordinator _responseCoordinator; 14 | 15 | public int? BinarySizeThreshold { get; } = null; // no limit 16 | 17 | public InMemoryServerTransport(IResponseCoordinator responseCoordinator, 18 | IAcknowledgeCoordinator acknowledgeCoordinator) 19 | { 20 | _responseCoordinator = responseCoordinator ?? throw new ArgumentNullException(nameof(responseCoordinator)); 21 | _acknowledgeCoordinator = 22 | acknowledgeCoordinator ?? throw new ArgumentNullException(nameof(acknowledgeCoordinator)); 23 | } 24 | 25 | public Task DispatchResponseAsync(TResponse response) 26 | => _responseCoordinator.ProcessResponseAsync(response); 27 | 28 | public Task DispatchAcknowledgeAsync(TAcknowledge request) 29 | => _acknowledgeCoordinator.ProcessAcknowledgeAsync(request); 30 | } 31 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Transport/InMemoryTenantHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Thinktecture.Relay.Server.Transport; 5 | 6 | internal class InMemoryTenantHandler : ITenantHandler 7 | { 8 | public static readonly ITenantHandler Noop = new InMemoryTenantHandler(); 9 | 10 | public Task AcknowledgeAsync(string acknowledgeId, CancellationToken cancellationToken = default) 11 | => Task.CompletedTask; 12 | } 13 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Transport/InMemoryTenantHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Thinktecture.Relay.Server.Transport; 2 | 3 | internal class InMemoryTenantHandlerFactory : ITenantHandlerFactory 4 | { 5 | public ITenantHandler Create(string tenantName, string connectionId, int maximumConcurrentRequests) 6 | => InMemoryTenantHandler.Noop; 7 | } 8 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Transport/InMemoryTenantTransport.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Transport; 5 | 6 | internal partial class InMemoryTenantTransport 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.InMemoryTenantTransportErrorDeliveringRequest, LogLevel.Error, 11 | "Could not deliver request {RelayRequestId} to a connection")] 12 | public static partial void ErrorDeliveringRequest(ILogger logger, Guid relayRequestId); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Transport/InMemoryTenantTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Logging; 5 | using Thinktecture.Relay.Transport; 6 | 7 | namespace Thinktecture.Relay.Server.Transport; 8 | 9 | internal partial class InMemoryTenantTransport : ITenantTransport 10 | where T : IClientRequest 11 | { 12 | private readonly ConnectorRegistry _connectorRegistry; 13 | private readonly ILogger _logger; 14 | 15 | public int? BinarySizeThreshold { get; } = null; 16 | 17 | public InMemoryTenantTransport(ILogger> logger, ConnectorRegistry connectorRegistry) 18 | { 19 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 20 | _connectorRegistry = connectorRegistry ?? throw new ArgumentNullException(nameof(connectorRegistry)); 21 | } 22 | 23 | public Task EnsureKnownTransports(CancellationToken cancellationToken = default) 24 | => Task.CompletedTask; 25 | 26 | public async Task TransportAsync(T request) 27 | { 28 | if (!await _connectorRegistry.TryDeliverRequestAsync(request)) 29 | { 30 | Log.ErrorDeliveringRequest(_logger, request.RequestId); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Transport/RequestCoordinator.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Transport; 5 | 6 | public partial class RequestCoordinator 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.RequestCoordinatorRedirect, LogLevel.Debug, 11 | "Redirecting request {RelayRequestId} to transport for tenant {TenantName}")] 12 | public static partial void Redirect(ILogger logger, Guid relayRequestId, string tenantName); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Transport/RequestCoordinator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Logging; 5 | using Thinktecture.Relay.Transport; 6 | 7 | namespace Thinktecture.Relay.Server.Transport; 8 | 9 | /// 10 | public partial class RequestCoordinator : IRequestCoordinator 11 | where T : IClientRequest 12 | { 13 | private readonly ILogger _logger; 14 | private readonly ITenantTransport _tenantTransport; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// An . 20 | /// An . 21 | public RequestCoordinator(ILogger> logger, ITenantTransport tenantTransport) 22 | { 23 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 24 | _tenantTransport = tenantTransport ?? throw new ArgumentNullException(nameof(tenantTransport)); 25 | } 26 | 27 | /// 28 | public async Task ProcessRequestAsync(T request, CancellationToken cancellationToken = default) 29 | { 30 | Log.Redirect(_logger, request.RequestId, request.TenantName); 31 | await _tenantTransport.TransportAsync(request); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Thinktecture.Relay.Server/Transport/ResponseDispatcher.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Transport; 5 | 6 | public partial class ResponseDispatcher 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.ResponseDispatcherLocalDispatch, LogLevel.Trace, 11 | "Locally dispatching response for request {RelayRequestId}")] 12 | public static partial void LocalDispatch(ILogger logger, Guid relayRequestId); 13 | 14 | [LoggerMessage(LoggingEventIds.ResponseDispatcherRedirectDispatch, LogLevel.Trace, 15 | "Remotely dispatching response for request {RelayRequestId} to origin {OriginId}")] 16 | public static partial void RedirectDispatch(ILogger logger, Guid relayRequestId, Guid originId); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/docker/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | false 8 | false 9 | false 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Connector.Docker/LoggingEventIds.cs: -------------------------------------------------------------------------------- 1 | namespace Thinktecture.Relay.Connector.Docker; 2 | 3 | internal static class LoggingEventIds 4 | { 5 | public const int InProcFuncExecuting = 10001; 6 | public const int InProcFuncBodySize = 10002; 7 | public const int InProcFuncEcho = 10003; 8 | 9 | public const int ConnectorServiceStarting = 10101; 10 | public const int ConnectorServiceStopping = 10102; 11 | } 12 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Connector.Docker/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Hosting; 4 | using Serilog; 5 | using Thinktecture.Relay.Docker; 6 | 7 | namespace Thinktecture.Relay.Connector.Docker; 8 | 9 | public class Program 10 | { 11 | public static async Task Main(string[] args) 12 | { 13 | try 14 | { 15 | var host = CreateHostBuilder(args).Build(); 16 | 17 | await host.RunAsync(); 18 | } 19 | catch (Exception ex) 20 | { 21 | Console.WriteLine("A fatal error cause service crash: {0}", ex); 22 | Log.Fatal(ex, "A fatal error cause service crash"); 23 | return 1; 24 | } 25 | finally 26 | { 27 | await Log.CloseAndFlushAsync(); 28 | } 29 | 30 | return 0; 31 | } 32 | 33 | public static IHostBuilder CreateHostBuilder(string[] args) 34 | => Host 35 | .CreateDefaultBuilder(args) 36 | .UseConsoleLifetime() 37 | .UseSystemd() 38 | .UseWindowsService() 39 | .UseSerilog((context, loggerConfiguration) => 40 | { 41 | loggerConfiguration 42 | .MinimumLevel.Information() 43 | .Destructure.With() 44 | .Enrich.FromLogContext() 45 | .Enrich.WithProperty("Application", "Connector") 46 | .ReadFrom.Configuration(context.Configuration) 47 | .WriteTo.Console(); 48 | }) 49 | .ConfigureServices(Startup.ConfigureServices); 50 | } 51 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Connector.Docker/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "Thinktecture.Relay.Connector.Docker": { 5 | "commandName": "Project", 6 | "environmentVariables": { 7 | "DOTNET_ENVIRONMENT": "Development" 8 | } 9 | }, 10 | "Docker": { 11 | "commandName": "Docker", 12 | "publishAllPorts": true, 13 | "useSSL": true 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Connector.Docker/Startup.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Connector.Docker; 5 | 6 | internal partial class InProcFunc 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.InProcFuncExecuting, LogLevel.Information, 11 | "Executing demo in proc target for request {RequestId}")] 12 | public static partial void Executing(ILogger logger, Guid requestId); 13 | 14 | [LoggerMessage(LoggingEventIds.InProcFuncBodySize, LogLevel.Information, 15 | "Request {RequestId} provided {BodySize} bytes in body")] 16 | public static partial void BodySize(ILogger logger, Guid requestId, long? bodySize); 17 | 18 | [LoggerMessage(LoggingEventIds.InProcFuncEcho, LogLevel.Information, 19 | "Demo in proc target is ECHOING received request body")] 20 | public static partial void Echo(ILogger logger); 21 | } 22 | } 23 | 24 | internal partial class ConnectorService 25 | { 26 | private static partial class Log 27 | { 28 | [LoggerMessage(LoggingEventIds.ConnectorServiceStarting, LogLevel.Information, "Starting connector")] 29 | public static partial void Starting(ILogger logger); 30 | 31 | [LoggerMessage(LoggingEventIds.ConnectorServiceStopping, LogLevel.Information, "Gracefully stopping connector")] 32 | public static partial void Stopping(ILogger logger); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Connector.Docker/Thinktecture.Relay.Connector.Docker.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Always 24 | 25 | 26 | Always 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Connector.Docker/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": { 4 | "Default": "Verbose", 5 | "Override": { 6 | "Microsoft": "Information", 7 | "System": "Information" 8 | } 9 | }, 10 | "WriteTo": [ 11 | { 12 | "Name": "Seq", 13 | "Args": { 14 | "ServerUrl": "http://localhost:5341" 15 | } 16 | } 17 | ] 18 | }, 19 | "RelayConnector": { 20 | "RelayServerBaseUri": "http://localhost:5000", 21 | "TenantName": "TestTenant1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Connector.Docker/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "RelayConnector": { 3 | "TenantSecret": "", 4 | "Targets": { 5 | "status": { 6 | "Type": "RelayWebTarget", 7 | "Timeout": "00:00:02", 8 | "Comment": "returns HTTP status by appended code (followed by '?sleep=#' to simulate a long running request delayed by # msec)", 9 | "Url": "https://httpstat.us/" 10 | }, 11 | "swapi": { 12 | "Type": "RelayWebTarget", 13 | "Comment": "returns more complex JSON (e.g. '/api/people/1/')", 14 | "Url": "https://swapi.dev/", 15 | "Options": "FollowRedirect" 16 | }, 17 | "picsum": { 18 | "Type": "RelayWebTarget", 19 | "Comment": "returns a random 4k image", 20 | "Url": "https://picsum.photos/3840/2160", 21 | "Options": "FollowRedirect" 22 | }, 23 | "smallpdf": { 24 | "Type": "RelayWebTarget", 25 | "Timeout": "01:00:00", 26 | "Comment": "returns a small pdf (around 10 Mb)", 27 | "Url": "https://link.testfile.org/PDF10MB", 28 | "Options": "FollowRedirect" 29 | }, 30 | "bigpdf": { 31 | "Type": "RelayWebTarget", 32 | "Timeout": "01:00:00", 33 | "Comment": "returns a really big pdf (around 100 Mb)", 34 | "Url": "https://link.testfile.org/PDF100MB", 35 | "Options": "FollowRedirect" 36 | }, 37 | "httpbin": { 38 | "Type": "RelayWebTarget", 39 | "Url": "http://localhost:8080" 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Docker/.gitignore: -------------------------------------------------------------------------------- 1 | Properties/launchSettings.json 2 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Docker/Authentication/ApiKeyAuthenticationDefaults.cs: -------------------------------------------------------------------------------- 1 | namespace Thinktecture.Relay.Docker.Authentication; 2 | 3 | /// 4 | /// Default values related to api key - based authentication handler 5 | /// 6 | public static class ApiKeyAuthenticationDefaults 7 | { 8 | /// 9 | /// The default value used for ApiKeyAuthenticationOptions.AuthenticationScheme. 10 | /// 11 | public const string AuthenticationScheme = "ApiKey"; 12 | 13 | /// 14 | /// The default header name used for the api key authentication. 15 | /// 16 | public const string HeaderName = "Api-Key"; 17 | } 18 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Docker/Authentication/ApiKeyAuthenticationHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Claims; 5 | using System.Text.Encodings.Web; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Authentication; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.Extensions.Options; 10 | 11 | namespace Thinktecture.Relay.Docker.Authentication; 12 | 13 | /// 14 | /// Example handler that allows "securing" an api with configurable api keys. 15 | /// 16 | public class ApiKeyAuthenticationHandler : AuthenticationHandler 17 | { 18 | /// 19 | public ApiKeyAuthenticationHandler(IOptionsMonitor options, 20 | ILoggerFactory logger, UrlEncoder encoder) 21 | : base(options, logger, encoder) 22 | { 23 | } 24 | 25 | protected override Task HandleAuthenticateAsync() 26 | { 27 | if (Context.Request.Headers.TryGetValue(Options.HeaderName, out var apiKeys) && 28 | !String.IsNullOrEmpty(apiKeys.First()) && 29 | Options.ApiKeys.TryGetValue(apiKeys.First()!, out var claims)) 30 | { 31 | return Task.FromResult(AuthenticateResult.Success(BuildTicket(claims))); 32 | } 33 | 34 | return Task.FromResult(AuthenticateResult.NoResult()); 35 | } 36 | 37 | private AuthenticationTicket BuildTicket(Dictionary claims) 38 | => new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity( 39 | claims.Select(c => new Claim(c.Key, c.Value)))), Scheme.Name); 40 | } 41 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Docker/Authentication/ApiKeyAuthenticationOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Authentication; 3 | 4 | namespace Thinktecture.Relay.Docker.Authentication; 5 | 6 | /// 7 | /// Class that represents the authentication configuration for api key security. 8 | /// 9 | public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions 10 | { 11 | /// 12 | /// Gets or sets the header name used for checking for an api key. 13 | /// 14 | public string HeaderName { get; set; } = ApiKeyAuthenticationDefaults.HeaderName; 15 | 16 | /// 17 | /// Gets or sets the configured api keys together with the corresponding claims 18 | /// that will be assumed when the corresponding key is being provided to the api call. 19 | /// 20 | public Dictionary> ApiKeys { get; set; } = new Dictionary>(); 21 | } 22 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Docker/Authentication/ApiKeyExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Authentication; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Thinktecture.Relay.Docker.Authentication; 6 | 7 | /// 8 | /// Extension methods to configure api key authentication. 9 | /// 10 | public static class ApiKeyExtensions 11 | { 12 | /// 13 | /// Adds api key authentication to using the specified scheme. 14 | /// 15 | /// Api key authentication uses a HTTP header to perform authentication. 16 | /// 17 | /// 18 | /// The . 19 | /// The authentication scheme. 20 | /// A display name for the authentication handler. 21 | /// A delegate to configure . 22 | /// A reference to after the operation has completed. 23 | public static AuthenticationBuilder AddApiKey(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action configureOptions) 24 | { 25 | builder.Services.AddOptions(authenticationScheme); 26 | return builder.AddScheme(authenticationScheme, displayName, configureOptions); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Docker/DockerUtils.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.AspNetCore.Server.Kestrel.Core; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | using Serilog; 6 | 7 | namespace Thinktecture.Relay.Docker; 8 | 9 | /// 10 | /// Provides utility functions for the Docker environment 11 | /// 12 | public static class DockerUtils 13 | { 14 | /// 15 | /// Creates a holst builder preconfigured for the Docker environment. 16 | /// 17 | /// The name of the application. 18 | /// The command-line arguments provided to the application. 19 | /// The startup class to use. 20 | /// An instance of an . 21 | public static IHostBuilder CreateHostBuilder(string applicationName, string[] args) 22 | where TStartup : class 23 | => Host 24 | .CreateDefaultBuilder(args) 25 | .ConfigureServices((context, services) 26 | => services.Configure(context.Configuration.GetSection("Kestrel"))) 27 | .UseSerilog((context, loggerConfiguration) => 28 | { 29 | loggerConfiguration 30 | .MinimumLevel.Error() 31 | .Destructure.With() 32 | .Enrich.FromLogContext() 33 | .Enrich.WithProperty("Application", applicationName) 34 | .ReadFrom.Configuration(context.Configuration) 35 | .WriteTo.Console(); 36 | }) 37 | .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); 38 | } 39 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Docker/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Thinktecture.Relay.Docker; 4 | 5 | /// 6 | /// Dummy entry class for the console application. 7 | /// 8 | public class Program 9 | { 10 | /// 11 | /// Dummy entry point for the console application. 12 | /// 13 | /// An application error code. 14 | public static int Main() 15 | { 16 | Console.WriteLine("This cannot be run."); 17 | return 1; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Docker/StreamDestructuringPolicy.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.IO; 4 | using Serilog.Core; 5 | using Serilog.Events; 6 | 7 | namespace Thinktecture.Relay.Docker; 8 | 9 | /// 10 | public class StreamDestructuringPolicy : IDestructuringPolicy 11 | { 12 | /// 13 | public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, 14 | [MaybeNullWhen(false)] out LogEventPropertyValue result) 15 | { 16 | if (value is Stream stream) 17 | { 18 | var destructed = new Dictionary() 19 | { 20 | { new ScalarValue("CanRead"), new ScalarValue(stream.CanRead) }, 21 | { new ScalarValue("CanWrite"), new ScalarValue(stream.CanWrite) }, 22 | { new ScalarValue("CanSeek"), new ScalarValue(stream.CanSeek) }, 23 | { new ScalarValue("CanTimeout"), new ScalarValue(stream.CanTimeout) }, 24 | }; 25 | 26 | if (stream.CanTimeout) 27 | { 28 | destructed.Add(new ScalarValue("ReadTimeout"), new ScalarValue(stream.ReadTimeout)); 29 | destructed.Add(new ScalarValue("WriteTimeout"), new ScalarValue(stream.WriteTimeout)); 30 | } 31 | 32 | if (stream.CanSeek) 33 | { 34 | destructed.Add(new ScalarValue("Length"), new ScalarValue(stream.Length)); 35 | destructed.Add(new ScalarValue("Position"), new ScalarValue(stream.Position)); 36 | } 37 | 38 | result = new DictionaryValue(destructed); 39 | return true; 40 | } 41 | 42 | result = null; 43 | return false; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Docker/Thinktecture.Relay.Docker.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.ManagementApi.Docker/.gitignore: -------------------------------------------------------------------------------- 1 | ManagementApiDocumentation.xml 2 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.ManagementApi.Docker/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Thinktecture.Relay.ManagementApi.Docker": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "launchUrl": "", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | }, 10 | "applicationUrl": "https://localhost:5005;http://localhost:5004" 11 | }, 12 | "Docker": { 13 | "commandName": "Docker", 14 | "launchBrowser": false, 15 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", 16 | "publishAllPorts": true, 17 | "useSSL": true 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.ManagementApi.Docker/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Thinktecture.Relay.ManagementApi.Docker; 6 | 7 | public static class ServiceCollectionExtensions 8 | { 9 | public static IServiceCollection AddRelayServerDbContext(this IServiceCollection services, 10 | IConfiguration configuration) 11 | { 12 | if ("SqlServer".Equals(configuration.GetValue("DatabaseType"), 13 | StringComparison.InvariantCultureIgnoreCase)) 14 | return Server.Persistence.EntityFrameworkCore.SqlServer.ServiceCollectionExtensions 15 | .AddRelayServerDbContext(services, configuration.GetConnectionString("SqlServer") 16 | ?? throw new InvalidOperationException("No 'SqlServer' connection string found.")); 17 | 18 | return Server.Persistence.EntityFrameworkCore.PostgreSql.ServiceCollectionExtensions 19 | .AddRelayServerDbContext(services, configuration.GetConnectionString("PostgreSql") 20 | ?? throw new InvalidOperationException("No 'PostgreSql' connection string found.")); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.ManagementApi.Docker/Thinktecture.Relay.ManagementApi.Docker.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | 7 | 8 | 9 | ManagementApiDocumentation.xml 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.ManagementApi.Docker/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": { 4 | "Default": "Verbose", 5 | "Override": { 6 | "Microsoft": "Information", 7 | "System": "Information" 8 | } 9 | }, 10 | "WriteTo": [ 11 | { 12 | "Name": "Seq", 13 | "Args": { 14 | "ServerUrl": "http://localhost:5341" 15 | } 16 | } 17 | ] 18 | }, 19 | "ConnectionStrings": { 20 | "PostgreSql": "host=localhost;database=relayserver;username=relayserver;password=" 21 | }, 22 | "Authentication": { 23 | "ApiKey": { 24 | "ApiKeys": { 25 | "read-key": { "managementapi": "read" }, 26 | "write-key": { "managementapi": "write" }, 27 | "readwrite-key": { "managementapi": "readwrite" } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.ManagementApi.Docker/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.ManagementApi.Docker/samples/DELETE_tenants.http: -------------------------------------------------------------------------------- 1 | DELETE http://localhost:5004/api/management/tenants/ffffffff-ffff-ffff-ffff-ffffffffffff HTTP/1.1 2 | Accept: application/json 3 | TT-Api-Key: write-key 4 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.ManagementApi.Docker/samples/GET_tenants.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:5004/api/management/tenants HTTP/1.1 2 | Accept: application/json 3 | TT-Api-Key: read-key 4 | 5 | ### 6 | 7 | GET http://localhost:5004/api/management/tenants/TestTenant1 HTTP/1.1 8 | Accept: application/json 9 | TT-Api-Key: read-key 10 | 11 | ### 12 | 13 | GET http://localhost:5004/api/management/tenants/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa HTTP/1.1 14 | Accept: application/json 15 | TT-Api-Key: read-key 16 | 17 | ### 18 | 19 | GET http://localhost:5004/api/management/tenants/ffffffff-ffff-ffff-ffff-ffffffffffff HTTP/1.1 20 | Accept: application/json 21 | TT-Api-Key: read-key 22 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.ManagementApi.Docker/samples/POST_tenants.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:5004/api/management/tenants HTTP/1.1 2 | Accept: application/json 3 | TT-Api-Key: write-key 4 | Content-Type: application/json 5 | 6 | { 7 | "id": "ffffffff-ffff-ffff-ffff-ffffffffffff", 8 | "name": "TestTenantX", 9 | "credentials": [ 10 | { 11 | "id": "ffffffff-ffff-ffff-ffff-fffffffffff1", 12 | "plainTextValue": "" 13 | }, 14 | { 15 | "id": "ffffffff-ffff-ffff-ffff-fffffffffff2", 16 | "plainTextValue": "" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.ManagementApi.Docker/samples/PUT_tenants.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:5004/api/management/tenants/ffffffff-ffff-ffff-ffff-ffffffffffff HTTP/1.1 2 | Accept: application/json 3 | TT-Api-Key: write-key 4 | Content-Type: application/json 5 | 6 | { 7 | "displayName": "Test tenant X", 8 | "description": "A tenant for testing", 9 | } 10 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Server.Docker/Interceptors/ConnectorFinishedAcknowledgementModeInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Thinktecture.Relay.Acknowledgement; 4 | using Thinktecture.Relay.Server.Interceptor; 5 | using Thinktecture.Relay.Server.Transport; 6 | using Thinktecture.Relay.Transport; 7 | 8 | namespace Thinktecture.Relay.Server.Docker.Interceptors; 9 | 10 | public class ConnectorFinishedAcknowledgementModeInterceptor : IClientRequestInterceptor 11 | { 12 | public Task OnRequestReceivedAsync(IRelayContext context, 13 | CancellationToken cancellationToken = default) 14 | { 15 | context.ClientRequest.AcknowledgeMode = AcknowledgeMode.Manual; 16 | return Task.CompletedTask; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Server.Docker/Interceptors/DemoRequestInterceptor.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Docker.Interceptors; 5 | 6 | public partial class DemoRequestInterceptor 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.DemoRequestInterceptorRemovingOutput, LogLevel.Information, 11 | "Request stream interceptor enabled for request {RequestId}, input was {OriginalRequestSize}, output will be NULL")] 12 | public static partial void RemovingOutput(ILogger logger, Guid requestId, long? originalRequestSize); 13 | 14 | [LoggerMessage(LoggingEventIds.DemoRequestInterceptorEnabled, LogLevel.Information, 15 | "Request stream interceptor enabled for request {RequestId}, input was {OriginalRequestSize}, output will be {RequestSize} bytes")] 16 | public static partial void Enabled(ILogger logger, Guid requestId, long? originalRequestSize, int requestSize); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Server.Docker/Interceptors/DemoResponseInterceptor.Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Thinktecture.Relay.Server.Docker.Interceptors; 5 | 6 | public partial class DemoResponseInterceptor 7 | { 8 | private static partial class Log 9 | { 10 | [LoggerMessage(LoggingEventIds.DemoResponseInterceptorRemovingOutput, LogLevel.Information, 11 | "Response stream interceptor enabled for request {RequestId}, input was {OriginalResponseSize}, output will be NULL")] 12 | public static partial void RemovingOutput(ILogger logger, Guid requestId, long? originalResponseSize); 13 | 14 | [LoggerMessage(LoggingEventIds.DemoResponseInterceptorEnabled, LogLevel.Information, 15 | "Response stream interceptor enabled for request {RequestId}, input was {OriginalResponseSize}, output will be {ResponseSize} bytes")] 16 | public static partial void Enabled(ILogger logger, Guid requestId, long? originalResponseSize, 17 | long? responseSize); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Server.Docker/LoggingEventIds.cs: -------------------------------------------------------------------------------- 1 | namespace Thinktecture.Relay.Server.Docker; 2 | 3 | internal static class LoggingEventIds 4 | { 5 | public const int DemoRequestInterceptorRemovingOutput = 10001; 6 | public const int DemoRequestInterceptorEnabled = 10002; 7 | 8 | public const int DemoResponseInterceptorRemovingOutput = 10101; 9 | public const int DemoResponseInterceptorEnabled = 10102; 10 | } 11 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Server.Docker/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | using Serilog; 7 | using Thinktecture.Relay.Docker; 8 | using Thinktecture.Relay.Server.Persistence.EntityFrameworkCore; 9 | 10 | namespace Thinktecture.Relay.Server.Docker; 11 | 12 | public class Program 13 | { 14 | public static async Task Main(string[] args) 15 | { 16 | try 17 | { 18 | var host = CreateHostBuilder(args).Build(); 19 | 20 | var config = host.Services.GetRequiredService(); 21 | if (config.GetValue("migrate") || config.GetValue("migrate-only")) 22 | { 23 | var rollback = config.GetValue("rollback"); 24 | if (rollback is null) 25 | { 26 | await host.Services.ApplyPendingMigrationsAsync(); 27 | } 28 | else 29 | { 30 | await host.Services.RollbackMigrationsAsync(rollback); 31 | } 32 | 33 | if (config.GetValue("migrate-only")) 34 | { 35 | return 0; 36 | } 37 | } 38 | 39 | await host.RunAsync(); 40 | } 41 | catch (Exception ex) 42 | { 43 | Console.WriteLine("A fatal error cause service crash: {0}", ex); 44 | Log.Fatal(ex, "A fatal error cause service crash"); 45 | return 1; 46 | } 47 | finally 48 | { 49 | await Log.CloseAndFlushAsync(); 50 | } 51 | 52 | return 0; 53 | } 54 | 55 | public static IHostBuilder CreateHostBuilder(string[] args) 56 | => DockerUtils.CreateHostBuilder("RelayServer", args); 57 | } 58 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Server.Docker/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "Thinktecture.Relay.Server.Docker": { 5 | "commandName": "Project", 6 | "launchBrowser": false, 7 | "launchUrl": "", 8 | "commandLineArgs": "migrate=true", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development" 11 | }, 12 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 13 | }, 14 | "Docker": { 15 | "commandName": "Docker", 16 | "launchBrowser": false, 17 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", 18 | "publishAllPorts": true, 19 | "useSSL": true 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Server.Docker/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Thinktecture.Relay.Server.Docker; 6 | 7 | public static class ServiceCollectionExtensions 8 | { 9 | public static IServiceCollection AddRelayServerDbContext(this IServiceCollection services, 10 | IConfiguration configuration) 11 | { 12 | if ("SqlServer".Equals(configuration.GetValue("DatabaseType"), 13 | StringComparison.InvariantCultureIgnoreCase)) 14 | return Persistence.EntityFrameworkCore.SqlServer.ServiceCollectionExtensions 15 | .AddRelayServerDbContext(services, configuration.GetConnectionString("SqlServer") 16 | ?? throw new InvalidOperationException("No 'SqlServer' connection string found.")); 17 | 18 | return Persistence.EntityFrameworkCore.PostgreSql.ServiceCollectionExtensions 19 | .AddRelayServerDbContext(services, configuration.GetConnectionString("PostgreSql") 20 | ?? throw new InvalidOperationException("No 'PostgreSql' connection string found.")); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Server.Docker/Thinktecture.Relay.Server.Docker.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Server.Docker/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": { 4 | "Default": "Verbose", 5 | "Override": { 6 | "Microsoft": "Information", 7 | "System": "Information" 8 | } 9 | }, 10 | "WriteTo": [ 11 | { 12 | "Name": "Seq", 13 | "Args": { 14 | "ServerUrl": "http://localhost:5341" 15 | } 16 | } 17 | ] 18 | }, 19 | "ConnectionStrings": { 20 | "PostgreSql": "host=localhost;database=relayserver;username=relayserver;password=" 21 | }, 22 | "Kestrel": { 23 | "Limits": { 24 | "MaxRequestBodySize": 115343360 25 | } 26 | }, 27 | "Authentication": { 28 | "Authority": "http://localhost:5002/realms/relayserver" 29 | }, 30 | "RabbitMq": { 31 | "Uri": "amqp://guest:guest@localhost", 32 | "ClusterHosts": "localhost:5672,localhost:5673" 33 | }, 34 | "RelayServer": { 35 | "ReconnectMinimumDelay": "00:00:10", 36 | "ReconnectMaximumDelay": "00:00:20" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/docker/Thinktecture.Relay.Server.Docker/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Kestrel": { 3 | "Limits": { 4 | "MaxRequestBodySize": 115343360 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/full-rebuild.ps1: -------------------------------------------------------------------------------- 1 | dotnet restore 2 | dotnet build --configuration Release --no-restore --no-incremental 3 | dotnet pack --configuration Release --output ./packages --no-build 4 | # cd packages 5 | # dotnet nuget push *.nupkg --source nuget.org --api-key {API_KEY_HERE} 6 | -------------------------------------------------------------------------------- /src/nuget/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinktecture/relayserver/b2f9e86d9edb00c3653d11e884dbac445199014f/src/nuget/icon.png -------------------------------------------------------------------------------- /src/tools/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | false 8 | false 9 | false 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/tools/MigrationCreation.PostgreSql/DbContextFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Design; 5 | using Microsoft.Extensions.Configuration; 6 | using Thinktecture.Relay.Server.Persistence.EntityFrameworkCore; 7 | using ServiceCollectionExtensions = 8 | Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.PostgreSql.ServiceCollectionExtensions; 9 | 10 | namespace MigrationCreation.PostgreSql; 11 | 12 | public class DbContextFactory : IDesignTimeDbContextFactory 13 | { 14 | public RelayDbContext CreateDbContext(string[] args) 15 | { 16 | var configuration = new ConfigurationBuilder() 17 | .SetBasePath(Directory.GetCurrentDirectory()) 18 | .AddJsonFile("appsettings.json") 19 | .Build(); 20 | 21 | var builder = new DbContextOptionsBuilder(); 22 | 23 | var assemblyName = typeof(ServiceCollectionExtensions) 24 | .GetAssemblySimpleName(); 25 | 26 | builder.UseNpgsql(configuration.GetConnectionString("PostgreSql"), 27 | optionsBuilder => optionsBuilder.MigrationsAssembly(assemblyName)); 28 | 29 | return new RelayDbContext(builder.Options); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/tools/MigrationCreation.PostgreSql/MigrationCreation.PostgreSql.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Creates EF Core migrations for PostgreSQL 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/tools/MigrationCreation.PostgreSql/Program.cs: -------------------------------------------------------------------------------- 1 | namespace MigrationCreation.PostgreSql; 2 | 3 | public class Program 4 | { 5 | public static void Main(string[] args) 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/tools/MigrationCreation.PostgreSql/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "PostgreSql": "host=localhost;database=relayserver;username=relayserver;password=" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/tools/MigrationCreation.PostgreSql/create-migration.ps1: -------------------------------------------------------------------------------- 1 | dotnet ef migrations add $args[0] -v -o Migrations/ConfigurationDb -c RelayDbContext -p ../../Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.PostgreSql -s . 2 | -------------------------------------------------------------------------------- /src/tools/MigrationCreation.PostgreSql/create-migration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | dotnet ef migrations add "$1" -v -o Migrations/ConfigurationDb -c RelayDbContext -p ../../Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.PostgreSql -s . 3 | -------------------------------------------------------------------------------- /src/tools/MigrationCreation.SqlServer/DbContextFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Design; 5 | using Microsoft.Extensions.Configuration; 6 | using Thinktecture.Relay.Server.Persistence.EntityFrameworkCore; 7 | using ServiceCollectionExtensions = 8 | Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.SqlServer.ServiceCollectionExtensions; 9 | 10 | namespace MigrationCreation.SqlServer; 11 | 12 | public class DbContextFactory : IDesignTimeDbContextFactory 13 | { 14 | public RelayDbContext CreateDbContext(string[] args) 15 | { 16 | var configuration = new ConfigurationBuilder() 17 | .SetBasePath(Directory.GetCurrentDirectory()) 18 | .AddJsonFile("appsettings.json") 19 | .Build(); 20 | 21 | var builder = new DbContextOptionsBuilder(); 22 | 23 | var assemblyName = typeof(ServiceCollectionExtensions) 24 | .GetAssemblySimpleName(); 25 | 26 | builder.UseSqlServer(configuration.GetConnectionString("SqlServer"), 27 | optionsBuilder => optionsBuilder.MigrationsAssembly(assemblyName)); 28 | 29 | return new RelayDbContext(builder.Options); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/tools/MigrationCreation.SqlServer/MigrationCreation.SqlServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Creates EF Core migrations for Microsoft SQL Server 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/tools/MigrationCreation.SqlServer/Program.cs: -------------------------------------------------------------------------------- 1 | namespace MigrationCreation.SqlServer; 2 | 3 | public class Program 4 | { 5 | public static void Main(string[] args) 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/tools/MigrationCreation.SqlServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "SqlServer": "server=localhost;initial catalog=relayserver;user id=relay;password=" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/tools/MigrationCreation.SqlServer/create-migration.ps1: -------------------------------------------------------------------------------- 1 | dotnet ef migrations add $args[0] -v -o Migrations/ConfigurationDb -c RelayDbContext -p ../../Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.SqlServer -s . 2 | -------------------------------------------------------------------------------- /src/tools/MigrationCreation.SqlServer/create-migration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | dotnet ef migrations add "$1" -v -o Migrations/ConfigurationDb -c RelayDbContext -p ../../Thinktecture.Relay.Server.Persistence.EntityFrameworkCore.SqlServer -s . 3 | -------------------------------------------------------------------------------- /src/tools/create-migrations.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory = $true)] 3 | [string] $MigrationName 4 | ) 5 | 6 | Push-Location 7 | 8 | Get-ChildItem MigrationCreation.* ` 9 | | ForEach-Object ` 10 | { 11 | Set-Location $_ 12 | ./create-migration.ps1 $MigrationName 13 | } 14 | 15 | Pop-Location 16 | -------------------------------------------------------------------------------- /src/tools/create-migrations.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | MIGRATION_NAME=$1 3 | 4 | usage() 5 | { 6 | echo "Usage: $0 [MigrationName]" 7 | exit; 8 | } 9 | 10 | if [ -z "$MIGRATION_NAME" ]; then 11 | usage 12 | fi 13 | 14 | for d in */ ; do 15 | (cd "$d" && ./create-migration.sh "$MIGRATION_NAME") 16 | done 17 | --------------------------------------------------------------------------------