From 7aec7020515c0c9744e6e38ca519ab3748118d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B0=D0=BD=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A2=D0=B5=D1=80=D0=B5=D1=89=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2?= Date: Fri, 12 Apr 2024 15:54:26 +0300 Subject: [PATCH 01/15] add http client handlers --- .../ATI.Services.Consul.csproj | 2 +- .../Http/HttpClientBuilderExtensions.cs | 20 +++++ ATI.Services.Consul/Http/HttpConsulHandler.cs | 47 +++++++++++ .../ServiceCollectionHttpClientExtensions.cs | 81 +++++++++++++++++++ 4 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 ATI.Services.Consul/Http/HttpClientBuilderExtensions.cs create mode 100644 ATI.Services.Consul/Http/HttpConsulHandler.cs create mode 100644 ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs diff --git a/ATI.Services.Consul/ATI.Services.Consul.csproj b/ATI.Services.Consul/ATI.Services.Consul.csproj index 3b8f353..60e93ce 100644 --- a/ATI.Services.Consul/ATI.Services.Consul.csproj +++ b/ATI.Services.Consul/ATI.Services.Consul.csproj @@ -17,7 +17,7 @@ - + \ No newline at end of file diff --git a/ATI.Services.Consul/Http/HttpClientBuilderExtensions.cs b/ATI.Services.Consul/Http/HttpClientBuilderExtensions.cs new file mode 100644 index 0000000..f37da98 --- /dev/null +++ b/ATI.Services.Consul/Http/HttpClientBuilderExtensions.cs @@ -0,0 +1,20 @@ +using ATI.Services.Common.Options; +using ATI.Services.Consul.Http; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace PassportVerification.test; + +[PublicAPI] +public static class HttpClientBuilderExtensions +{ + public static IHttpClientBuilder WithConsul(this IHttpClientBuilder httpClientBuilder) + where TServiceOptions : BaseServiceOptions + { + httpClientBuilder.Services.AddSingleton>(); + + return httpClientBuilder + .AddHttpMessageHandler>(); + } + +} \ No newline at end of file diff --git a/ATI.Services.Consul/Http/HttpConsulHandler.cs b/ATI.Services.Consul/Http/HttpConsulHandler.cs new file mode 100644 index 0000000..158f07a --- /dev/null +++ b/ATI.Services.Consul/Http/HttpConsulHandler.cs @@ -0,0 +1,47 @@ +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using ATI.Services.Common.Metrics; +using ATI.Services.Common.Options; +using Microsoft.Extensions.Options; + +namespace ATI.Services.Consul.Http; + +public class HttpConsulHandler : HttpConsulHandler where T : BaseServiceOptions +{ + public HttpConsulHandler(MetricsFactory metricsFactory, IOptions serviceOptions) + : base(metricsFactory, serviceOptions.Value) + { + } +} + +public class HttpConsulHandler : DelegatingHandler +{ + private readonly ConsulServiceAddress _serviceAddress; + + protected HttpConsulHandler(MetricsFactory metricsFactory, BaseServiceOptions serviceOptions) + { + _serviceAddress = + new ConsulServiceAddress(metricsFactory, serviceOptions.ServiceName, serviceOptions.Environment); + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken ct) + { + var url = await _serviceAddress.ToHttpAsync(); + var relativeUrl = request.RequestUri?.PathAndQuery; + request.RequestUri = new Uri(new Uri(url), relativeUrl); + + return await base.SendAsync(request, ct); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _serviceAddress?.Dispose(); + } + + base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs b/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs new file mode 100644 index 0000000..99985a2 --- /dev/null +++ b/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs @@ -0,0 +1,81 @@ +using System; +using System.Linq; +using System.Net.Http; +using ATI.Services.Common.Http.Extensions; +using ATI.Services.Common.Logging; +using ATI.Services.Common.Options; +using ATI.Services.Common.Variables; +using JetBrains.Annotations; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NLog; +using PassportVerification.test; +using ConfigurationManager = ATI.Services.Common.Behaviors.ConfigurationManager; + +namespace ATI.Services.Common.Http; + +[PublicAPI] +public static class ServiceCollectionHttpClientExtensions +{ + /// + /// Dynamically add all inheritors of BaseServiceOptions as AddConsulHttpClient + /// + /// + /// + public static IServiceCollection AddConsulHttpClients(this IServiceCollection services) + { + var servicesOptionsTypes = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type.IsSubclassOf(typeof(BaseServiceOptions))); + + foreach (var serviceOptionType in servicesOptionsTypes) + { + var method = typeof(ServiceCollectionHttpClientExtensions) + .GetMethod(nameof(AddConsulHttpClient), new[] { typeof(IServiceCollection) }); + var generic = method.MakeGenericMethod(serviceOptionType); + generic.Invoke(null, new[] { services }); + } + + return services; + } + + /// + /// Add HttpClient to HttpClientFactory with retry/cb/timeout policy + /// Will add only if UseHttpClientFactory == true + /// + /// + /// + /// s + public static IServiceCollection AddConsulHttpClient(this IServiceCollection services, Action additionalActions) where T : BaseServiceOptions + { + var className = typeof(T).Name; + var settings = ConfigurationManager.GetSection(className).Get(); + var logger = LogManager.GetLogger(settings.ServiceName); + + if (!settings.UseHttpClientFactory || string.IsNullOrEmpty(settings.ConsulName)) + { + logger.WarnWithObject($"Class ${className} has UseHttpClientFactory == false while AddCustomHttpClient"); + return services; + } + + var serviceVariablesOptions = ConfigurationManager.GetSection(nameof(ServiceVariablesOptions)).Get(); + + services.AddHttpClient(settings.ServiceName, httpClient => + { + // We will override this url by consul, but we need to set it, otherwise we will get exception because HttpRequestMessage doesn't have baseUrl (only relative) + httpClient.BaseAddress = new Uri("http://localhost"); + httpClient.SetBaseFields(serviceVariablesOptions.GetServiceAsClientName(), serviceVariablesOptions.GetServiceAsClientHeaderName(), settings.AdditionalHeaders); + additionalActions(httpClient); + }) + .WithLogging() + .WithProxyFields() + .AddRetryPolicy(settings, logger) + // Get new instance url for each retry (because 1 instance can be down) + .WithConsul() + .AddHostSpecificCircuitBreakerPolicy(settings, logger) + .AddTimeoutPolicy(settings.TimeOut) + .WithMetrics(); + + return services; + } +} \ No newline at end of file From 0de7a600f2dc1628dc3511fb1607d82403955dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B0=D0=BD=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A2=D0=B5=D1=80=D0=B5=D1=89=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2?= Date: Fri, 12 Apr 2024 16:35:48 +0300 Subject: [PATCH 02/15] fix exception --- ATI.Services.Consul/ATI.Services.Consul.csproj | 2 +- ATI.Services.Consul/ConsulMetricsHttpClientWrapper.cs | 1 + ...onsulMetricsHttpClientWrapperServiceCollectionExtensions.cs | 2 ++ .../Http/ServiceCollectionHttpClientExtensions.cs | 3 +-- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ATI.Services.Consul/ATI.Services.Consul.csproj b/ATI.Services.Consul/ATI.Services.Consul.csproj index 60e93ce..a2a29e8 100644 --- a/ATI.Services.Consul/ATI.Services.Consul.csproj +++ b/ATI.Services.Consul/ATI.Services.Consul.csproj @@ -17,7 +17,7 @@ - + \ No newline at end of file diff --git a/ATI.Services.Consul/ConsulMetricsHttpClientWrapper.cs b/ATI.Services.Consul/ConsulMetricsHttpClientWrapper.cs index 66bdce9..6c5e5bb 100644 --- a/ATI.Services.Consul/ConsulMetricsHttpClientWrapper.cs +++ b/ATI.Services.Consul/ConsulMetricsHttpClientWrapper.cs @@ -20,6 +20,7 @@ namespace ATI.Services.Consul /// Обертка, включающая в себя ConsulServiceAddress, MetricsHttpClientWrapper и MetricsTracingFactory /// [PublicAPI] + [Obsolete("Use HttpClientFactory instead")] public class ConsulMetricsHttpClientWrapper : IDisposable { private readonly BaseServiceOptions _serviceOptions; diff --git a/ATI.Services.Consul/ConsulMetricsHttpClientWrapperServiceCollectionExtensions.cs b/ATI.Services.Consul/ConsulMetricsHttpClientWrapperServiceCollectionExtensions.cs index c71ae73..fb2bd61 100644 --- a/ATI.Services.Consul/ConsulMetricsHttpClientWrapperServiceCollectionExtensions.cs +++ b/ATI.Services.Consul/ConsulMetricsHttpClientWrapperServiceCollectionExtensions.cs @@ -1,3 +1,4 @@ +using System; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; @@ -6,6 +7,7 @@ namespace ATI.Services.Consul; [PublicAPI] public static class ConsulMetricsHttpClientWrapperServiceCollectionExtensions { + [Obsolete("Use HttpClientFactory and ServiceCollection.AddConsulHttpClients instead")] public static IServiceCollection AddConsulMetricsHttpClientWrappers(this IServiceCollection services) { // Add IHttpClientFactory for ConsulMetricsHttpClientWrapper<> diff --git a/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs b/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs index 99985a2..4280a8a 100644 --- a/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs +++ b/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs @@ -46,7 +46,7 @@ public static IServiceCollection AddConsulHttpClients(this IServiceCollection se /// /// /// s - public static IServiceCollection AddConsulHttpClient(this IServiceCollection services, Action additionalActions) where T : BaseServiceOptions + public static IServiceCollection AddConsulHttpClient(this IServiceCollection services) where T : BaseServiceOptions { var className = typeof(T).Name; var settings = ConfigurationManager.GetSection(className).Get(); @@ -65,7 +65,6 @@ public static IServiceCollection AddConsulHttpClient(this IServiceCollection // We will override this url by consul, but we need to set it, otherwise we will get exception because HttpRequestMessage doesn't have baseUrl (only relative) httpClient.BaseAddress = new Uri("http://localhost"); httpClient.SetBaseFields(serviceVariablesOptions.GetServiceAsClientName(), serviceVariablesOptions.GetServiceAsClientHeaderName(), settings.AdditionalHeaders); - additionalActions(httpClient); }) .WithLogging() .WithProxyFields() From 220700ccbbab54f8d5eefdb5c0717e2f22d69fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B0=D0=BD=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A2=D0=B5=D1=80=D0=B5=D1=89=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2?= Date: Fri, 12 Apr 2024 16:57:46 +0300 Subject: [PATCH 03/15] fix bug --- ATI.Services.Consul/Http/HttpConsulHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ATI.Services.Consul/Http/HttpConsulHandler.cs b/ATI.Services.Consul/Http/HttpConsulHandler.cs index 158f07a..82f78c8 100644 --- a/ATI.Services.Consul/Http/HttpConsulHandler.cs +++ b/ATI.Services.Consul/Http/HttpConsulHandler.cs @@ -23,7 +23,7 @@ public class HttpConsulHandler : DelegatingHandler protected HttpConsulHandler(MetricsFactory metricsFactory, BaseServiceOptions serviceOptions) { _serviceAddress = - new ConsulServiceAddress(metricsFactory, serviceOptions.ServiceName, serviceOptions.Environment); + new ConsulServiceAddress(metricsFactory, serviceOptions.ConsulName, serviceOptions.Environment); } protected override async Task SendAsync(HttpRequestMessage request, CancellationToken ct) From c3cdcd87486386112d50412b46226eb24962c226 Mon Sep 17 00:00:00 2001 From: Stanislav Tereschenkov Date: Mon, 15 Apr 2024 10:59:22 +0300 Subject: [PATCH 04/15] Update ci.yml --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1be0ac1..6574d1b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,6 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v1 with: - dotnet-version: '7.0.x' # SDK Version to use; x will use the latest version of the 6.0 channel + dotnet-version: '8.0.x' # SDK Version to use; x will use the latest version of the 6.0 channel - name: Build - run: dotnet build --configuration Release \ No newline at end of file + run: dotnet build --configuration Release From 4a3dcf2374b3e554ec73a294bcd6e6939be8b044 Mon Sep 17 00:00:00 2001 From: Stanislav Tereschenkov Date: Mon, 15 Apr 2024 10:59:33 +0300 Subject: [PATCH 05/15] Update release-preview.yml --- .github/workflows/release-preview.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-preview.yml b/.github/workflows/release-preview.yml index ddfcf2d..a39d23f 100644 --- a/.github/workflows/release-preview.yml +++ b/.github/workflows/release-preview.yml @@ -14,10 +14,10 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v1 with: - dotnet-version: '7.0.x' # SDK Version to use; x will use the latest version of the 6.0 channel + dotnet-version: '8.0.x' # SDK Version to use; x will use the latest version of the 6.0 channel - name: Pack run: dotnet pack --configuration Release /p:Version=${VERSION} --output . - name: Push run: dotnet nuget push atisu.services.consul.${VERSION}.nupkg --source https://api.nuget.org/v3/index.json --api-key ${ATISERVICES_NUGET_APIKEY} env: - ATISERVICES_NUGET_APIKEY: ${{ secrets.ATISERVICES_NUGET_APIKEY }} \ No newline at end of file + ATISERVICES_NUGET_APIKEY: ${{ secrets.ATISERVICES_NUGET_APIKEY }} From 7fde167ad402a5a3db080960cd2a79e7b524b3fd Mon Sep 17 00:00:00 2001 From: Stanislav Tereschenkov Date: Mon, 15 Apr 2024 10:59:44 +0300 Subject: [PATCH 06/15] Update release.yml --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 06d798c..62f5cad 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v1 with: - dotnet-version: '7.0.x' # SDK Version to use; x will use the latest version of the 6.0 channel + dotnet-version: '8.0.x' # SDK Version to use; x will use the latest version of the 6.0 channel - name: Build run: dotnet build --configuration Release /p:Version=${VERSION} - name: Pack @@ -26,4 +26,4 @@ jobs: - name: Push run: dotnet nuget push atisu.services.consul.${VERSION}.nupkg --source https://api.nuget.org/v3/index.json --api-key ${ATISERVICES_NUGET_APIKEY} env: - ATISERVICES_NUGET_APIKEY: ${{ secrets.ATISERVICES_NUGET_APIKEY }} \ No newline at end of file + ATISERVICES_NUGET_APIKEY: ${{ secrets.ATISERVICES_NUGET_APIKEY }} From 9992dabb546aebc2c44277fa743085c6eae6a212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B0=D0=BD=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A2=D0=B5=D1=80=D0=B5=D1=89=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2?= Date: Mon, 15 Apr 2024 11:01:11 +0300 Subject: [PATCH 07/15] .net 8 --- ATI.Services.Consul/ATI.Services.Consul.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ATI.Services.Consul/ATI.Services.Consul.csproj b/ATI.Services.Consul/ATI.Services.Consul.csproj index a2a29e8..e096467 100644 --- a/ATI.Services.Consul/ATI.Services.Consul.csproj +++ b/ATI.Services.Consul/ATI.Services.Consul.csproj @@ -1,6 +1,6 @@  - net7.0 + net8.0 linux-x64 Team Services ATI @@ -17,7 +17,7 @@ - + \ No newline at end of file From af8ea08a68a13e77d915a6483e6580dd7557d047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B0=D0=BD=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A2=D0=B5=D1=80=D0=B5=D1=89=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2?= Date: Mon, 15 Apr 2024 12:45:50 +0300 Subject: [PATCH 08/15] infinite httpconsulhandler --- ATI.Services.Consul/ATI.Services.Consul.csproj | 2 +- ATI.Services.Consul/Http/HttpClientBuilderExtensions.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ATI.Services.Consul/ATI.Services.Consul.csproj b/ATI.Services.Consul/ATI.Services.Consul.csproj index e096467..d401418 100644 --- a/ATI.Services.Consul/ATI.Services.Consul.csproj +++ b/ATI.Services.Consul/ATI.Services.Consul.csproj @@ -17,7 +17,7 @@ - + \ No newline at end of file diff --git a/ATI.Services.Consul/Http/HttpClientBuilderExtensions.cs b/ATI.Services.Consul/Http/HttpClientBuilderExtensions.cs index f37da98..c35ca9d 100644 --- a/ATI.Services.Consul/Http/HttpClientBuilderExtensions.cs +++ b/ATI.Services.Consul/Http/HttpClientBuilderExtensions.cs @@ -1,3 +1,4 @@ +using System.Threading; using ATI.Services.Common.Options; using ATI.Services.Consul.Http; using JetBrains.Annotations; @@ -14,7 +15,9 @@ public static IHttpClientBuilder WithConsul(this IHttpClientBui httpClientBuilder.Services.AddSingleton>(); return httpClientBuilder - .AddHttpMessageHandler>(); + .AddHttpMessageHandler>() + // infinite handler because we don't want to wait until ConsulServiceAddress will recreated and cache objects every 2 minutes (default) + .SetHandlerLifetime(Timeout.InfiniteTimeSpan); } } \ No newline at end of file From 426ca18311caf6e28957ccde80323d0387e7fb53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B0=D0=BD=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A2=D0=B5=D1=80=D0=B5=D1=89=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2?= Date: Mon, 15 Apr 2024 14:07:50 +0300 Subject: [PATCH 09/15] add exception if settings are null --- .../Http/ServiceCollectionHttpClientExtensions.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs b/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs index 4280a8a..fbb2841 100644 --- a/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs +++ b/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs @@ -50,6 +50,11 @@ public static IServiceCollection AddConsulHttpClient(this IServiceCollection { var className = typeof(T).Name; var settings = ConfigurationManager.GetSection(className).Get(); + if (settings == null) + { + throw new Exception($"Cannot find section for {className}"); + } + var logger = LogManager.GetLogger(settings.ServiceName); if (!settings.UseHttpClientFactory || string.IsNullOrEmpty(settings.ConsulName)) From 58a63e5f7e7a4a1d1b774f399ad4c3ea9151900b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B0=D0=BD=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A2=D0=B5=D1=80=D0=B5=D1=89=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2?= Date: Mon, 15 Apr 2024 17:27:48 +0300 Subject: [PATCH 10/15] add readme --- .../ATI.Services.Consul.csproj | 2 +- ATI.Services.Consul/README.md | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 ATI.Services.Consul/README.md diff --git a/ATI.Services.Consul/ATI.Services.Consul.csproj b/ATI.Services.Consul/ATI.Services.Consul.csproj index d401418..09ef097 100644 --- a/ATI.Services.Consul/ATI.Services.Consul.csproj +++ b/ATI.Services.Consul/ATI.Services.Consul.csproj @@ -17,7 +17,7 @@ - + \ No newline at end of file diff --git a/ATI.Services.Consul/README.md b/ATI.Services.Consul/README.md new file mode 100644 index 0000000..fb4b725 --- /dev/null +++ b/ATI.Services.Consul/README.md @@ -0,0 +1,52 @@ +# ATI.Services.Consul +## Деплой + + +### Теги + +Выкладка в nuget происходит на основе триггера на тег определённого формата: [как повышать версию](https://learn.microsoft.com/en-us/nuget/concepts/package-versioning) +##### ВАЖНО: +1. Все теги должны начинаться с `v` +2. Для тестовой версии тег должен быть с постфиксом, например `v1.0.1-rc` +3. Релизный тег должен состоять только из цифр версии, например `v1.0.0` + +* Создание тега через git(нужно запушить его в origin) [создание тега и пуш в remote](https://git-scm.com/book/en/v2/Git-Basics-Tagging) + * Команды для тестового: + 1. `git checkout <название ветки>` + 2. `git tag -a <название тега> -m "<описание тега>" ` + 3. `git push --tags` + * Команды для релизного: + 1. `git tag -a <название тега> -m "<описание тега>" ` + 2. `git push --tags` +* Через раздел [releases](https://github.com/atidev/ATI.Services.Consul/releases)(альфа версии нужно помечать соответсвующей галкой). +* При пуше, в некоторых IDE, необходимо отметить чекбокс об отправке тегов + + +#### Разработка теперь выглядит вот так: +1. Создаем ветку, пушим изменения, создаем pull request. +2. Добавляем на ветку тег с версией изменения +3. Срабатывает workflow билдит и пушит версию(берёт из названия тега) в nuget. +4. По готовности мерджим ветку в master. +5. Тегаем нужный коммит мастера. +Нужно обязательно описать изменения внесённые этим релизом в release notes +Здесь лучше воспользоваться интерфейсом гитхаба, там удобнее редактировать текст. +6. Срабатывает релизный workflow билдит и пушит в нугет релизную версию. +7. В разделе [Releases](https://github.com/atidev/ATI.Services.Consul/releases) появляется информация о нашем релиз и release notes. + +--- +## Документация + +### Http + +За основу взята работа с `HttClientFactory` из `atisu.services.common`, но добавлены следующие extensions: +1. `services.AddConsulHttpClient` +2. `services.AddConsulHttpClients()` - он автоматически соберет из проекта всех наследников `BaseServiceOptions`, где `ConsulName не NULL и UseHttpClientFactory = true` и для них сделает вызов `services.AddConsulHttpClient<>()` + +Методы делают почти все то же самое, что и `services.AddCustomHttpClient<>`, за исключением: +1. Добавляется `ServiceAsClientName` хедер во все запросы +2. Добавляется `HttpConsulHandler`, который на каждый запрос (retry) получает ip+port инстанса из `ConsulServiceAddress` + +--- + + + From 581aebc064226620711ef4a676c0de8f9ea1ceaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B0=D0=BD=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A2=D0=B5=D1=80=D0=B5=D1=89=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2?= Date: Sat, 27 Apr 2024 12:16:17 +0300 Subject: [PATCH 11/15] add checks --- .../ATI.Services.Consul.csproj | 2 +- ATI.Services.Consul/Http/HttpConsulHandler.cs | 5 ++++ .../ServiceCollectionHttpClientExtensions.cs | 24 +++++++++++++++---- ATI.Services.Consul/README.md | 2 +- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/ATI.Services.Consul/ATI.Services.Consul.csproj b/ATI.Services.Consul/ATI.Services.Consul.csproj index 09ef097..2584fcc 100644 --- a/ATI.Services.Consul/ATI.Services.Consul.csproj +++ b/ATI.Services.Consul/ATI.Services.Consul.csproj @@ -17,7 +17,7 @@ - + \ No newline at end of file diff --git a/ATI.Services.Consul/Http/HttpConsulHandler.cs b/ATI.Services.Consul/Http/HttpConsulHandler.cs index 82f78c8..e1c1b5b 100644 --- a/ATI.Services.Consul/Http/HttpConsulHandler.cs +++ b/ATI.Services.Consul/Http/HttpConsulHandler.cs @@ -2,9 +2,11 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using ATI.Services.Common.Logging; using ATI.Services.Common.Metrics; using ATI.Services.Common.Options; using Microsoft.Extensions.Options; +using NLog; namespace ATI.Services.Consul.Http; @@ -19,11 +21,14 @@ public HttpConsulHandler(MetricsFactory metricsFactory, IOptions serviceOptio public class HttpConsulHandler : DelegatingHandler { private readonly ConsulServiceAddress _serviceAddress; + private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); protected HttpConsulHandler(MetricsFactory metricsFactory, BaseServiceOptions serviceOptions) { _serviceAddress = new ConsulServiceAddress(metricsFactory, serviceOptions.ConsulName, serviceOptions.Environment); + + Logger.WarnWithObject("HttpConsulHandler constructor", new { serviceOptions.ServiceName }); } protected override async Task SendAsync(HttpRequestMessage request, CancellationToken ct) diff --git a/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs b/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs index fbb2841..23e5441 100644 --- a/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs +++ b/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs @@ -1,6 +1,6 @@ using System; +using System.Collections.Generic; using System.Linq; -using System.Net.Http; using ATI.Services.Common.Http.Extensions; using ATI.Services.Common.Logging; using ATI.Services.Common.Options; @@ -17,6 +17,8 @@ namespace ATI.Services.Common.Http; [PublicAPI] public static class ServiceCollectionHttpClientExtensions { + private static readonly HashSet RegisteredServiceNames = new (); + /// /// Dynamically add all inheritors of BaseServiceOptions as AddConsulHttpClient /// @@ -54,18 +56,30 @@ public static IServiceCollection AddConsulHttpClient(this IServiceCollection { throw new Exception($"Cannot find section for {className}"); } + + var serviceName = settings.ServiceName; - var logger = LogManager.GetLogger(settings.ServiceName); + var logger = LogManager.GetLogger(serviceName); if (!settings.UseHttpClientFactory || string.IsNullOrEmpty(settings.ConsulName)) { - logger.WarnWithObject($"Class ${className} has UseHttpClientFactory == false while AddCustomHttpClient"); + logger.WarnWithObject($"Class ${className} has UseHttpClientFactory == false OR ConsulName == null while AddCustomHttpClient"); + return services; + } + + // Each HttpClient must be added only once, otherwise we will get exceptions like System.InvalidOperationException: The 'InnerHandler' property must be null. 'DelegatingHandler' instances provided to 'HttpMessageHandlerBuilder' must not be reused or cached. + // Handler: 'ATI.Services.Consul.Http.HttpConsulHandler + // Possible reason - because HttpConsulHandler is singleton (not transient) + // https://stackoverflow.com/questions/77542613/the-innerhandler-property-must-be-null-delegatinghandler-instances-provided + if (RegisteredServiceNames.Contains(serviceName)) + { + logger.WarnWithObject($"Class ${className} is already registered"); return services; } var serviceVariablesOptions = ConfigurationManager.GetSection(nameof(ServiceVariablesOptions)).Get(); - services.AddHttpClient(settings.ServiceName, httpClient => + services.AddHttpClient(serviceName, httpClient => { // We will override this url by consul, but we need to set it, otherwise we will get exception because HttpRequestMessage doesn't have baseUrl (only relative) httpClient.BaseAddress = new Uri("http://localhost"); @@ -79,6 +93,8 @@ public static IServiceCollection AddConsulHttpClient(this IServiceCollection .AddHostSpecificCircuitBreakerPolicy(settings, logger) .AddTimeoutPolicy(settings.TimeOut) .WithMetrics(); + + RegisteredServiceNames.Add(serviceName); return services; } diff --git a/ATI.Services.Consul/README.md b/ATI.Services.Consul/README.md index fb4b725..89b9dbb 100644 --- a/ATI.Services.Consul/README.md +++ b/ATI.Services.Consul/README.md @@ -42,7 +42,7 @@ 1. `services.AddConsulHttpClient` 2. `services.AddConsulHttpClients()` - он автоматически соберет из проекта всех наследников `BaseServiceOptions`, где `ConsulName не NULL и UseHttpClientFactory = true` и для них сделает вызов `services.AddConsulHttpClient<>()` -Методы делают почти все то же самое, что и `services.AddCustomHttpClient<>`, за исключением: +Методы делают почти все то же самое, что и `services.AddCustomHttpClient<>`, но дополнительно: 1. Добавляется `ServiceAsClientName` хедер во все запросы 2. Добавляется `HttpConsulHandler`, который на каждый запрос (retry) получает ip+port инстанса из `ConsulServiceAddress` From c44d7c7e3ea9ee8b2d9ad44deb17a9c832dde528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B0=D0=BD=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A2=D0=B5=D1=80=D0=B5=D1=89=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2?= Date: Thu, 2 May 2024 15:57:35 +0300 Subject: [PATCH 12/15] fix extensions --- .../ATI.Services.Consul.csproj | 2 +- .../Http/HttpClientBuilderExtensions.cs | 7 +- .../ServiceCollectionHttpClientExtensions.cs | 78 +++++-------------- ATI.Services.Consul/README.md | 3 +- 4 files changed, 27 insertions(+), 63 deletions(-) diff --git a/ATI.Services.Consul/ATI.Services.Consul.csproj b/ATI.Services.Consul/ATI.Services.Consul.csproj index 2584fcc..a485f8a 100644 --- a/ATI.Services.Consul/ATI.Services.Consul.csproj +++ b/ATI.Services.Consul/ATI.Services.Consul.csproj @@ -17,7 +17,7 @@ - + \ No newline at end of file diff --git a/ATI.Services.Consul/Http/HttpClientBuilderExtensions.cs b/ATI.Services.Consul/Http/HttpClientBuilderExtensions.cs index c35ca9d..d97d288 100644 --- a/ATI.Services.Consul/Http/HttpClientBuilderExtensions.cs +++ b/ATI.Services.Consul/Http/HttpClientBuilderExtensions.cs @@ -1,10 +1,9 @@ using System.Threading; using ATI.Services.Common.Options; -using ATI.Services.Consul.Http; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; -namespace PassportVerification.test; +namespace ATI.Services.Consul.Http; [PublicAPI] public static class HttpClientBuilderExtensions @@ -16,7 +15,9 @@ public static IHttpClientBuilder WithConsul(this IHttpClientBui return httpClientBuilder .AddHttpMessageHandler>() - // infinite handler because we don't want to wait until ConsulServiceAddress will recreated and cache objects every 2 minutes (default) + // By default, handlers are alive for 2 minutes + // If we don't set InfiniteTimeSpan, every 2 minutes HttpConsulHandler will be recreated + // And it will lead to new ConsulServiceAddress instances, which constructor is pretty expensive and will stop http requests for some time .SetHandlerLifetime(Timeout.InfiniteTimeSpan); } diff --git a/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs b/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs index 23e5441..c997840 100644 --- a/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs +++ b/ATI.Services.Consul/Http/ServiceCollectionHttpClientExtensions.cs @@ -1,100 +1,64 @@ using System; -using System.Collections.Generic; -using System.Linq; using ATI.Services.Common.Http.Extensions; -using ATI.Services.Common.Logging; using ATI.Services.Common.Options; using ATI.Services.Common.Variables; using JetBrains.Annotations; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using NLog; -using PassportVerification.test; using ConfigurationManager = ATI.Services.Common.Behaviors.ConfigurationManager; -namespace ATI.Services.Common.Http; +namespace ATI.Services.Consul.Http; [PublicAPI] public static class ServiceCollectionHttpClientExtensions { - private static readonly HashSet RegisteredServiceNames = new (); - - /// - /// Dynamically add all inheritors of BaseServiceOptions as AddConsulHttpClient - /// - /// - /// - public static IServiceCollection AddConsulHttpClients(this IServiceCollection services) - { - var servicesOptionsTypes = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(assembly => assembly.GetTypes()) - .Where(type => type.IsSubclassOf(typeof(BaseServiceOptions))); - - foreach (var serviceOptionType in servicesOptionsTypes) - { - var method = typeof(ServiceCollectionHttpClientExtensions) - .GetMethod(nameof(AddConsulHttpClient), new[] { typeof(IServiceCollection) }); - var generic = method.MakeGenericMethod(serviceOptionType); - generic.Invoke(null, new[] { services }); - } - - return services; - } - /// /// Add HttpClient to HttpClientFactory with retry/cb/timeout policy /// Will add only if UseHttpClientFactory == true /// /// - /// + /// Type of the http adapter for typed HttpClient + /// /// s - public static IServiceCollection AddConsulHttpClient(this IServiceCollection services) where T : BaseServiceOptions + public static IServiceCollection AddConsulHttpClient(this IServiceCollection services) + where TAdapter : class + where TServiceOptions : BaseServiceOptions { - var className = typeof(T).Name; - var settings = ConfigurationManager.GetSection(className).Get(); + var className = typeof(TServiceOptions).Name; + var settings = ConfigurationManager.GetSection(className).Get(); if (settings == null) { throw new Exception($"Cannot find section for {className}"); } - - var serviceName = settings.ServiceName; - - var logger = LogManager.GetLogger(serviceName); - if (!settings.UseHttpClientFactory || string.IsNullOrEmpty(settings.ConsulName)) + if (string.IsNullOrEmpty(settings.ConsulName)) { - logger.WarnWithObject($"Class ${className} has UseHttpClientFactory == false OR ConsulName == null while AddCustomHttpClient"); - return services; + throw new Exception($"Class {className} has ConsulName == null while AddConsulHttpClient"); } - // Each HttpClient must be added only once, otherwise we will get exceptions like System.InvalidOperationException: The 'InnerHandler' property must be null. 'DelegatingHandler' instances provided to 'HttpMessageHandlerBuilder' must not be reused or cached. - // Handler: 'ATI.Services.Consul.Http.HttpConsulHandler - // Possible reason - because HttpConsulHandler is singleton (not transient) - // https://stackoverflow.com/questions/77542613/the-innerhandler-property-must-be-null-delegatinghandler-instances-provided - if (RegisteredServiceNames.Contains(serviceName)) - { - logger.WarnWithObject($"Class ${className} is already registered"); - return services; - } - + var serviceName = settings.ServiceName; + var logger = LogManager.GetLogger(serviceName); + var serviceVariablesOptions = ConfigurationManager.GetSection(nameof(ServiceVariablesOptions)).Get(); - services.AddHttpClient(serviceName, httpClient => + services.AddHttpClient(httpClient => { // We will override this url by consul, but we need to set it, otherwise we will get exception because HttpRequestMessage doesn't have baseUrl (only relative) httpClient.BaseAddress = new Uri("http://localhost"); httpClient.SetBaseFields(serviceVariablesOptions.GetServiceAsClientName(), serviceVariablesOptions.GetServiceAsClientHeaderName(), settings.AdditionalHeaders); }) - .WithLogging() - .WithProxyFields() + .WithLogging() + .WithProxyFields() .AddRetryPolicy(settings, logger) // Get new instance url for each retry (because 1 instance can be down) - .WithConsul() + .WithConsul() .AddHostSpecificCircuitBreakerPolicy(settings, logger) .AddTimeoutPolicy(settings.TimeOut) - .WithMetrics(); - - RegisteredServiceNames.Add(serviceName); + .WithMetrics(); + // we don't override PooledConnectionLifetime even we use HttpClient in static TAdapter + // because we are getting new host from consul for each request + // https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines return services; } diff --git a/ATI.Services.Consul/README.md b/ATI.Services.Consul/README.md index 89b9dbb..4a42d02 100644 --- a/ATI.Services.Consul/README.md +++ b/ATI.Services.Consul/README.md @@ -39,8 +39,7 @@ ### Http За основу взята работа с `HttClientFactory` из `atisu.services.common`, но добавлены следующие extensions: -1. `services.AddConsulHttpClient` -2. `services.AddConsulHttpClients()` - он автоматически соберет из проекта всех наследников `BaseServiceOptions`, где `ConsulName не NULL и UseHttpClientFactory = true` и для них сделает вызов `services.AddConsulHttpClient<>()` +1. `services.AddConsulHttpClient` Методы делают почти все то же самое, что и `services.AddCustomHttpClient<>`, но дополнительно: 1. Добавляется `ServiceAsClientName` хедер во все запросы From 74f7cdbcb00e944a1f69e7762d80f45cb8e162af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B0=D0=BD=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A2=D0=B5=D1=80=D0=B5=D1=89=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2?= Date: Fri, 17 May 2024 17:03:31 +0300 Subject: [PATCH 13/15] update common lib --- ATI.Services.Consul/ATI.Services.Consul.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ATI.Services.Consul/ATI.Services.Consul.csproj b/ATI.Services.Consul/ATI.Services.Consul.csproj index a485f8a..1e6625e 100644 --- a/ATI.Services.Consul/ATI.Services.Consul.csproj +++ b/ATI.Services.Consul/ATI.Services.Consul.csproj @@ -17,7 +17,7 @@ - + \ No newline at end of file From 5597afaf0044813472f771b68d28b752a3cccf75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B0=D0=BD=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A2=D0=B5=D1=80=D0=B5=D1=89=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2?= Date: Tue, 28 May 2024 12:06:41 +0300 Subject: [PATCH 14/15] common release version --- ATI.Services.Consul/ATI.Services.Consul.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ATI.Services.Consul/ATI.Services.Consul.csproj b/ATI.Services.Consul/ATI.Services.Consul.csproj index 1e6625e..b4a38b5 100644 --- a/ATI.Services.Consul/ATI.Services.Consul.csproj +++ b/ATI.Services.Consul/ATI.Services.Consul.csproj @@ -17,7 +17,7 @@ - + \ No newline at end of file From 5cfb5c2c5e61093e93e59b3ec87b74de0801d8c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B0=D0=BD=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A2=D0=B5=D1=80=D0=B5=D1=89=D0=B5=D0=BD=D0=BA=D0=BE=D0=B2?= Date: Tue, 28 May 2024 12:18:42 +0300 Subject: [PATCH 15/15] fix readme --- ATI.Services.Consul/README.md | 51 ----------------------------------- README.md | 25 ++++++++++++----- 2 files changed, 19 insertions(+), 57 deletions(-) delete mode 100644 ATI.Services.Consul/README.md diff --git a/ATI.Services.Consul/README.md b/ATI.Services.Consul/README.md deleted file mode 100644 index 4a42d02..0000000 --- a/ATI.Services.Consul/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# ATI.Services.Consul -## Деплой - - -### Теги - -Выкладка в nuget происходит на основе триггера на тег определённого формата: [как повышать версию](https://learn.microsoft.com/en-us/nuget/concepts/package-versioning) -##### ВАЖНО: -1. Все теги должны начинаться с `v` -2. Для тестовой версии тег должен быть с постфиксом, например `v1.0.1-rc` -3. Релизный тег должен состоять только из цифр версии, например `v1.0.0` - -* Создание тега через git(нужно запушить его в origin) [создание тега и пуш в remote](https://git-scm.com/book/en/v2/Git-Basics-Tagging) - * Команды для тестового: - 1. `git checkout <название ветки>` - 2. `git tag -a <название тега> -m "<описание тега>" ` - 3. `git push --tags` - * Команды для релизного: - 1. `git tag -a <название тега> -m "<описание тега>" ` - 2. `git push --tags` -* Через раздел [releases](https://github.com/atidev/ATI.Services.Consul/releases)(альфа версии нужно помечать соответсвующей галкой). -* При пуше, в некоторых IDE, необходимо отметить чекбокс об отправке тегов - - -#### Разработка теперь выглядит вот так: -1. Создаем ветку, пушим изменения, создаем pull request. -2. Добавляем на ветку тег с версией изменения -3. Срабатывает workflow билдит и пушит версию(берёт из названия тега) в nuget. -4. По готовности мерджим ветку в master. -5. Тегаем нужный коммит мастера. -Нужно обязательно описать изменения внесённые этим релизом в release notes -Здесь лучше воспользоваться интерфейсом гитхаба, там удобнее редактировать текст. -6. Срабатывает релизный workflow билдит и пушит в нугет релизную версию. -7. В разделе [Releases](https://github.com/atidev/ATI.Services.Consul/releases) появляется информация о нашем релиз и release notes. - ---- -## Документация - -### Http - -За основу взята работа с `HttClientFactory` из `atisu.services.common`, но добавлены следующие extensions: -1. `services.AddConsulHttpClient` - -Методы делают почти все то же самое, что и `services.AddCustomHttpClient<>`, но дополнительно: -1. Добавляется `ServiceAsClientName` хедер во все запросы -2. Добавляется `HttpConsulHandler`, который на каждый запрос (retry) получает ip+port инстанса из `ConsulServiceAddress` - ---- - - - diff --git a/README.md b/README.md index eaece9f..9a2941a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # ATI.Services.Consul ## Деплой -Выкладка в nuget происходит на основе триггера на тег определённого формата -- `v1.0.0` - формат релизная версия на ветке master -- `v1.0.0-rc1` - формат тестовой/альфа/бета версии на любой ветке +Выкладка в nuget происходит на основе триггера на тег определённого формата +- `v1.0.0` - формат релизная версия на ветке master +- `v1.0.0-rc1` - формат тестовой/альфа/бета версии на любой ветке -Тег можно создать через git(нужно запушить его в origin) [создание тега и пуш в remote](https://git-scm.com/book/en/v2/Git-Basics-Tagging) +Тег можно создать через git(нужно запушить его в origin) [создание тега и пуш в remote](https://git-scm.com/book/en/v2/Git-Basics-Tagging) или через раздел [releses](https://github.com/atidev/ATI.Services.Consul/releases)(альфа версии нужно помечать соответсвующей галкой). #### Разработка теперь выглядит вот так: @@ -22,7 +22,7 @@ ### Consul Чтобы зарегистрировать сервис в консуле нужно: -`в appsettings.json` поместить блок +`в appsettings.json` поместить блок ```json "ConsulRegistratorOptions": { "ProvideEnvironment": "env", @@ -40,8 +40,21 @@ ] } ``` -> Да, там массив. Да, можно зарегать один сервак под разными тегами, именами, всем. +> Да, там массив. Да, можно зарегать один сервак под разными тегами, именами, всем. > Это необходимо в сервиса [нотификаций](http://stash.ri.domain:7990/projects/AS/repos/ati.notifications.core/browse/ATI.Notifications.Core.API/appsettings.json), для версионирования старых мобильных приложений, которые ходят без прокси и не выживают после смены контрактов. Далее осталось только добавить в `Startup.cs` `services.AddConsul()` +--- + +### Http + +За основу взята работа с `HttClientFactory` из `atisu.services.common`, но добавлены следующие extensions: +1. `services.AddConsulHttpClient` + +Методы делают почти все то же самое, что и `services.AddCustomHttpClient<>`, но дополнительно: +1. Добавляется `ServiceAsClientName` хедер во все запросы +2. Добавляется `HttpConsulHandler`, который на каждый запрос (retry) получает ip+port инстанса из `ConsulServiceAddress` + +--- +