Browse Source

Use AspNetCore health check.

pull/336/head
Sebastian Stehle 7 years ago
parent
commit
189cd14986
  1. 12
      src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs
  2. 11
      src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs
  3. 13
      src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoDBHealthCheck.cs
  4. 16
      src/Squidex.Infrastructure/Diagnostics/GCHealthCheck.cs
  5. 27
      src/Squidex.Infrastructure/Diagnostics/HealthCheckResult.cs
  6. 16
      src/Squidex.Infrastructure/Diagnostics/HealthCheckScopes.cs
  7. 20
      src/Squidex.Infrastructure/Diagnostics/IHealthCheck.cs
  8. 13
      src/Squidex.Infrastructure/Diagnostics/OrleansHealthCheck.cs
  9. 1
      src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  10. 1
      src/Squidex/Areas/Api/Config/Swagger/XmlTagProcessor.cs
  11. 5
      src/Squidex/Config/Domain/EntitiesServices.cs
  12. 7
      src/Squidex/Config/Domain/EventStoreServices.cs
  13. 12
      src/Squidex/Config/Domain/InfrastructureServices.cs
  14. 4
      src/Squidex/Config/Domain/StoreServices.cs
  15. 55
      src/Squidex/Config/Web/WebExtensions.cs
  16. 4
      src/Squidex/Config/Web/WebServices.cs
  17. 113
      src/Squidex/Pipeline/Diagnostics/HealthCheckMiddleware.cs
  18. 1
      src/Squidex/Squidex.csproj

12
src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs

@ -5,13 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Orleans; using Orleans;
using Squidex.Domain.Apps.Entities.Apps.Indexes; using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Diagnostics;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps.Diagnostics namespace Squidex.Domain.Apps.Entities.Apps.Diagnostics
@ -20,11 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Diagnostics
{ {
private readonly IAppsByNameIndex index; private readonly IAppsByNameIndex index;
public IEnumerable<string> Scopes
{
get { yield return HealthCheckScopes.Cluster; }
}
public OrleansAppsHealthCheck(IGrainFactory grainFactory) public OrleansAppsHealthCheck(IGrainFactory grainFactory)
{ {
Guard.NotNull(grainFactory, nameof(grainFactory)); Guard.NotNull(grainFactory, nameof(grainFactory));
@ -32,11 +26,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Diagnostics
index = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id); index = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id);
} }
public async Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)) public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
{ {
await index.CountAsync(); await index.CountAsync();
return new HealthCheckResult(true); return HealthCheckResult.Healthy("Orleans must establish communication.");
} }
} }
} }

11
src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs

@ -5,10 +5,10 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using EventStore.ClientAPI; using EventStore.ClientAPI;
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace Squidex.Infrastructure.Diagnostics namespace Squidex.Infrastructure.Diagnostics
{ {
@ -16,11 +16,6 @@ namespace Squidex.Infrastructure.Diagnostics
{ {
private readonly IEventStoreConnection connection; private readonly IEventStoreConnection connection;
public IEnumerable<string> Scopes
{
get { yield return HealthCheckScopes.Node; }
}
public GetEventStoreHealthCheck(IEventStoreConnection connection) public GetEventStoreHealthCheck(IEventStoreConnection connection)
{ {
Guard.NotNull(connection, nameof(connection)); Guard.NotNull(connection, nameof(connection));
@ -28,11 +23,11 @@ namespace Squidex.Infrastructure.Diagnostics
this.connection = connection; this.connection = connection;
} }
public async Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)) public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
{ {
await connection.ReadEventAsync("test", 1, false); await connection.ReadEventAsync("test", 1, false);
return new HealthCheckResult(true, "Querying test event from event store."); return HealthCheckResult.Healthy("Application must query data from EventStore.");
} }
} }
} }

13
src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoDBHealthCheck.cs

@ -5,9 +5,9 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using MongoDB.Driver; using MongoDB.Driver;
namespace Squidex.Infrastructure.Diagnostics namespace Squidex.Infrastructure.Diagnostics
@ -16,11 +16,6 @@ namespace Squidex.Infrastructure.Diagnostics
{ {
private readonly IMongoDatabase mongoDatabase; private readonly IMongoDatabase mongoDatabase;
public IEnumerable<string> Scopes
{
get { yield return HealthCheckScopes.Node; }
}
public MongoDBHealthCheck(IMongoDatabase mongoDatabase) public MongoDBHealthCheck(IMongoDatabase mongoDatabase)
{ {
Guard.NotNull(mongoDatabase, nameof(mongoDatabase)); Guard.NotNull(mongoDatabase, nameof(mongoDatabase));
@ -28,13 +23,15 @@ namespace Squidex.Infrastructure.Diagnostics
this.mongoDatabase = mongoDatabase; this.mongoDatabase = mongoDatabase;
} }
public async Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)) public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
{ {
var collectionNames = await mongoDatabase.ListCollectionNamesAsync(cancellationToken: cancellationToken); var collectionNames = await mongoDatabase.ListCollectionNamesAsync(cancellationToken: cancellationToken);
var result = await collectionNames.AnyAsync(cancellationToken); var result = await collectionNames.AnyAsync(cancellationToken);
return new HealthCheckResult(result); var status = result ? HealthStatus.Healthy : HealthStatus.Unhealthy;
return new HealthCheckResult(status, "Application must query data from MongoDB");
} }
} }
} }

16
src/Squidex.Infrastructure/Diagnostics/GCHealthCheck.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Squidex.Infrastructure.Diagnostics namespace Squidex.Infrastructure.Diagnostics
@ -17,11 +18,6 @@ namespace Squidex.Infrastructure.Diagnostics
{ {
private readonly long threshold; private readonly long threshold;
public IEnumerable<string> Scopes
{
get { yield return HealthCheckScopes.Node; }
}
public GCHealthCheck(IOptions<GCHealthCheckOptions> options) public GCHealthCheck(IOptions<GCHealthCheckOptions> options)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
@ -29,19 +25,21 @@ namespace Squidex.Infrastructure.Diagnostics
threshold = 1024 * 1024 * options.Value.Threshold; threshold = 1024 * 1024 * options.Value.Threshold;
} }
public Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)) public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
{ {
var allocated = GC.GetTotalMemory(false); var allocated = GC.GetTotalMemory(false);
var data = new Dictionary<string, object>() var data = new Dictionary<string, object>
{ {
{ "Allocated", allocated }, { "Allocated", allocated.ToReadableSize() },
{ "Gen0Collections", GC.CollectionCount(0) }, { "Gen0Collections", GC.CollectionCount(0) },
{ "Gen1Collections", GC.CollectionCount(1) }, { "Gen1Collections", GC.CollectionCount(1) },
{ "Gen2Collections", GC.CollectionCount(2) }, { "Gen2Collections", GC.CollectionCount(2) },
}; };
return Task.FromResult(new HealthCheckResult(allocated < threshold, $"Reports degraded status if allocated bytes >= {threshold.ToReadableSize()}", data)); var status = allocated < threshold ? HealthStatus.Healthy : HealthStatus.Unhealthy;
return Task.FromResult(new HealthCheckResult(status, $"Application must consum less than {threshold.ToReadableSize()} memory.", data: data));
} }
} }
} }

27
src/Squidex.Infrastructure/Diagnostics/HealthCheckResult.cs

@ -1,27 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
namespace Squidex.Infrastructure.Diagnostics
{
public sealed class HealthCheckResult
{
public bool IsHealthy { get; }
public string Description { get; }
public Dictionary<string, object> Data { get; }
public HealthCheckResult(bool isHealthy, string description = null, Dictionary<string, object> data = null)
{
IsHealthy = isHealthy;
Data = data;
Description = description;
}
}
}

16
src/Squidex.Infrastructure/Diagnostics/HealthCheckScopes.cs

@ -1,16 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.Diagnostics
{
public static class HealthCheckScopes
{
public const string Any = "*";
public const string Node = "node";
public const string Cluster = "cluster";
}
}

20
src/Squidex.Infrastructure/Diagnostics/IHealthCheck.cs

@ -1,20 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Squidex.Infrastructure.Diagnostics
{
public interface IHealthCheck
{
IEnumerable<string> Scopes { get; }
Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken));
}
}

13
src/Squidex.Infrastructure/Diagnostics/OrleansHealthCheck.cs

@ -5,9 +5,9 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Orleans; using Orleans;
using Orleans.Runtime; using Orleans.Runtime;
@ -17,11 +17,6 @@ namespace Squidex.Infrastructure.Diagnostics
{ {
private readonly IManagementGrain managementGrain; private readonly IManagementGrain managementGrain;
public IEnumerable<string> Scopes
{
get { yield return HealthCheckScopes.Cluster; }
}
public OrleansHealthCheck(IGrainFactory grainFactory) public OrleansHealthCheck(IGrainFactory grainFactory)
{ {
Guard.NotNull(grainFactory, nameof(grainFactory)); Guard.NotNull(grainFactory, nameof(grainFactory));
@ -29,11 +24,13 @@ namespace Squidex.Infrastructure.Diagnostics
managementGrain = grainFactory.GetGrain<IManagementGrain>(0); managementGrain = grainFactory.GetGrain<IManagementGrain>(0);
} }
public async Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)) public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
{ {
var activationCount = await managementGrain.GetTotalActivationCount(); var activationCount = await managementGrain.GetTotalActivationCount();
return new HealthCheckResult(activationCount > 0, "Orleans must have at least one activation."); var status = activationCount > 0 ? HealthStatus.Healthy : HealthStatus.Unhealthy;
return new HealthCheckResult(status, "Orleans must have at least one activation.");
} }
} }
} }

1
src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -9,6 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.2" /> <PackageReference Include="Microsoft.OData.Core" Version="7.5.2" />
<PackageReference Include="Microsoft.Orleans.CodeGenerator.MSBuild" Version="2.1.2"> <PackageReference Include="Microsoft.Orleans.CodeGenerator.MSBuild" Version="2.1.2">

1
src/Squidex/Areas/Api/Config/Swagger/XmlTagProcessor.cs

@ -10,7 +10,6 @@ using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NJsonSchema.Infrastructure; using NJsonSchema.Infrastructure;
using NSwag.Annotations;
using NSwag.SwaggerGeneration.Processors; using NSwag.SwaggerGeneration.Processors;
using NSwag.SwaggerGeneration.Processors.Contexts; using NSwag.SwaggerGeneration.Processors.Contexts;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;

5
src/Squidex/Config/Domain/EntitiesServices.cs

@ -20,7 +20,6 @@ using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Diagnostics;
using Squidex.Domain.Apps.Entities.Apps.Indexes; using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Apps.Templates; using Squidex.Domain.Apps.Entities.Apps.Templates;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
@ -42,7 +41,6 @@ using Squidex.Domain.Apps.Entities.Schemas.Indexes;
using Squidex.Domain.Apps.Entities.Tags; using Squidex.Domain.Apps.Entities.Tags;
using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Diagnostics;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Migrations;
using Squidex.Pipeline; using Squidex.Pipeline;
@ -97,9 +95,6 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<SchemaHistoryEventsCreator>() services.AddSingletonAs<SchemaHistoryEventsCreator>()
.As<IHistoryEventsCreator>(); .As<IHistoryEventsCreator>();
services.AddSingletonAs<OrleansAppsHealthCheck>()
.As<IHealthCheck>();
services.AddSingletonAs<RolePermissionsProvider>() services.AddSingletonAs<RolePermissionsProvider>()
.AsSelf(); .AsSelf();

7
src/Squidex/Config/Domain/EventStoreServices.cs

@ -48,11 +48,14 @@ namespace Squidex.Config.Domain
var connection = EventStoreConnection.Create(eventStoreConfiguration); var connection = EventStoreConnection.Create(eventStoreConfiguration);
services.AddSingletonAs(c => new GetEventStoreHealthCheck(connection)) services.AddSingletonAs(connection)
.As<IHealthCheck>(); .As<IEventStoreConnection>();
services.AddSingletonAs(c => new GetEventStore(connection, c.GetRequiredService<IJsonSerializer>(), eventStorePrefix, eventStoreProjectionHost)) services.AddSingletonAs(c => new GetEventStore(connection, c.GetRequiredService<IJsonSerializer>(), eventStorePrefix, eventStoreProjectionHost))
.As<IEventStore>(); .As<IEventStore>();
services.AddHealthChecks()
.AddCheck<GetEventStoreHealthCheck>("EventStore", tags: new[] { "node" });
} }
}); });

12
src/Squidex/Config/Domain/InfrastructureServices.cs

@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Entities.Apps.Diagnostics;
using Squidex.Domain.Users; using Squidex.Domain.Users;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Caching;
@ -27,6 +28,11 @@ namespace Squidex.Config.Domain
{ {
public static void AddMyInfrastructureServices(this IServiceCollection services) public static void AddMyInfrastructureServices(this IServiceCollection services)
{ {
services.AddHealthChecks()
.AddCheck<GCHealthCheck>("GC", tags: new[] { "node" })
.AddCheck<OrleansHealthCheck>("Orleans", tags: new[] { "cluster" })
.AddCheck<OrleansAppsHealthCheck>("Orleans App", tags: new[] { "cluster" });
services.AddSingletonAs(SystemClock.Instance) services.AddSingletonAs(SystemClock.Instance)
.As<IClock>(); .As<IClock>();
@ -39,12 +45,6 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AsyncLocalCache>() services.AddSingletonAs<AsyncLocalCache>()
.As<ILocalCache>(); .As<ILocalCache>();
services.AddSingletonAs<GCHealthCheck>()
.As<IHealthCheck>();
services.AddSingletonAs<OrleansHealthCheck>()
.As<IHealthCheck>();
services.AddSingletonAs<HttpContextAccessor>() services.AddSingletonAs<HttpContextAccessor>()
.As<IHttpContextAccessor>(); .As<IHttpContextAccessor>();

4
src/Squidex/Config/Domain/StoreServices.cs

@ -60,8 +60,8 @@ namespace Squidex.Config.Domain
services.AddSingletonAs(mongoDatabase) services.AddSingletonAs(mongoDatabase)
.As<IMongoDatabase>(); .As<IMongoDatabase>();
services.AddSingletonAs<MongoDBHealthCheck>() services.AddHealthChecks()
.As<IHealthCheck>(); .AddCheck<MongoDBHealthCheck>("MongoDB", tags: new[] { "node" });
services.AddSingletonAs<MongoMigrationStatus>() services.AddSingletonAs<MongoMigrationStatus>()
.As<IMigrationStatus>(); .As<IMigrationStatus>();

55
src/Squidex/Config/Web/WebExtensions.cs

@ -5,11 +5,18 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Squidex.Infrastructure.Diagnostics; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Squidex.Infrastructure.Json;
using Squidex.Pipeline; using Squidex.Pipeline;
using Squidex.Pipeline.Diagnostics;
using Squidex.Pipeline.Robots; using Squidex.Pipeline.Robots;
namespace Squidex.Config.Web namespace Squidex.Config.Web
@ -32,19 +39,51 @@ namespace Squidex.Config.Web
public static IApplicationBuilder UseMyHealthCheck(this IApplicationBuilder app) public static IApplicationBuilder UseMyHealthCheck(this IApplicationBuilder app)
{ {
app.Map("/readiness", builder => var serializer = app.ApplicationServices.GetRequiredService<IJsonSerializer>();
var writer = new Func<HttpContext, HealthReport, Task>((httpContext, report) =>
{
var response = new
{
Entries = report.Entries.ToDictionary(x => x.Key, x =>
{
var value = x.Value;
return new
{
Data = value.Data.Count > 0 ? new Dictionary<string, object>(value.Data) : null,
value.Description,
value.Duration,
value.Status
};
}),
report.Status,
report.TotalDuration
};
var json = serializer.Serialize(response);
httpContext.Response.Headers["Content-Types"] = "text/json";
return httpContext.Response.WriteAsync(json);
});
app.UseHealthChecks("/readiness", new HealthCheckOptions
{ {
builder.UseMiddleware<HealthCheckMiddleware>(HealthCheckScopes.Any); Predicate = check => true,
ResponseWriter = writer
}); });
app.Map("/healthz", builder => app.UseHealthChecks("/healthz", new HealthCheckOptions
{ {
builder.UseMiddleware<HealthCheckMiddleware>(HealthCheckScopes.Node); Predicate = check => check.Tags.Contains("node"),
ResponseWriter = writer
}); });
app.Map("/cluster-healthz", builder => app.UseHealthChecks("/cluster-healthz", new HealthCheckOptions
{ {
builder.UseMiddleware<HealthCheckMiddleware>(HealthCheckScopes.Cluster); Predicate = check => check.Tags.Contains("cluster"),
ResponseWriter = writer
}); });
return app; return app;

4
src/Squidex/Config/Web/WebServices.cs

@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Squidex.Config.Domain; using Squidex.Config.Domain;
using Squidex.Pipeline; using Squidex.Pipeline;
using Squidex.Pipeline.Diagnostics;
using Squidex.Pipeline.Robots; using Squidex.Pipeline.Robots;
namespace Squidex.Config.Web namespace Squidex.Config.Web
@ -27,9 +26,6 @@ namespace Squidex.Config.Web
services.AddSingletonAs<AppResolver>() services.AddSingletonAs<AppResolver>()
.AsSelf(); .AsSelf();
services.AddSingletonAs<HealthCheckMiddleware>()
.AsSelf();
services.AddSingletonAs<RobotsTxtMiddleware>() services.AddSingletonAs<RobotsTxtMiddleware>()
.AsSelf(); .AsSelf();

113
src/Squidex/Pipeline/Diagnostics/HealthCheckMiddleware.cs

@ -1,113 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Diagnostics;
using Squidex.Infrastructure.Json;
namespace Squidex.Pipeline.Diagnostics
{
public sealed class HealthCheckMiddleware
{
private const string Suffix = "HealthCheck";
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2);
private readonly Dictionary<string, IHealthCheck> healthChecks;
private readonly IJsonSerializer serializer;
private readonly RequestDelegate next;
private readonly List<string> scopes;
public HealthCheckMiddleware(IEnumerable<IHealthCheck> healthChecks, IJsonSerializer serializer, RequestDelegate next, string scopes)
{
Guard.NotNull(healthChecks, nameof(healthChecks));
Guard.NotNull(serializer, nameof(serializer));
this.healthChecks = healthChecks.ToDictionary(GetName);
this.next = next;
this.serializer = serializer;
this.scopes = SplitScopes(scopes);
}
public async Task Invoke(HttpContext context)
{
if (CanServeRequest(context.Request))
{
using (var cts = new CancellationTokenSource(Timeout))
{
var matchingChecks = healthChecks.Where(x => CanUseCheck(x.Value));
var results = await Task.WhenAll(matchingChecks.Select(x => MakeHealthCheckAsync(x.Key, x.Value, cts.Token)));
context.Response.StatusCode = 200;
context.Response.Headers.Add("Content-Type", "application/json");
if (results.Any(x => !x.Result.IsHealthy))
{
context.Response.StatusCode = 503;
}
var response = results.ToDictionary(x => x.Name, x => x.Result);
var json = serializer.Serialize(new { status = response });
await context.Response.WriteAsync(json);
}
}
else
{
await next(context);
}
}
private bool CanUseCheck(IHealthCheck check)
{
return scopes.Count == 0 || check.Scopes.Intersect(scopes).Any();
}
private bool CanServeRequest(HttpRequest request)
{
return HttpMethods.IsGet(request.Method) && (request.Path == "/" || string.IsNullOrEmpty(request.Path));
}
private static List<string> SplitScopes(string scopes)
{
return scopes.Split(",").Where(x => x != "*").ToList();
}
private static string GetName(IHealthCheck check)
{
var name = check.GetType().Name.ToCamelCase();
if (name.EndsWith(Suffix, StringComparison.OrdinalIgnoreCase))
{
name = name.Substring(0, name.Length - Suffix.Length);
}
return name;
}
private async Task<(string Name, HealthCheckResult Result)> MakeHealthCheckAsync(string name, IHealthCheck check, CancellationToken ct)
{
try
{
var result = await check.CheckHealthAsync(ct);
return (name, result);
}
catch
{
return (name, new HealthCheckResult(false));
}
}
}
}

1
src/Squidex/Squidex.csproj

@ -65,6 +65,7 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />

Loading…
Cancel
Save