Skip to content

Commit

Permalink
Augment config from code #9
Browse files Browse the repository at this point in the history
  • Loading branch information
Tratcher committed Apr 27, 2020
1 parent 1a709ef commit 36da8b4
Show file tree
Hide file tree
Showing 13 changed files with 699 additions and 121 deletions.
37 changes: 36 additions & 1 deletion samples/ReverseProxy.Sample/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Net;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.ReverseProxy.Core.Abstractions;

namespace Microsoft.ReverseProxy.Sample
{
Expand All @@ -30,7 +33,39 @@ public Startup(IConfiguration configuration)
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddReverseProxy().LoadFromConfig(_configuration.GetSection("ReverseProxy"), reloadOnChange: true);
services.AddMemoryCache();
services.AddReverseProxy()
.LoadFromConfig(_configuration.GetSection("ReverseProxy"), reloadOnChange: true)
.ConfigureBackendDefaults((id, backend) =>
{
backend.HealthCheckOptions ??= new HealthCheckOptions();
// How to use custom metadata to configure backends
if (backend.Metadata?.TryGetValue("CustomHealth", out var customHealth) ?? false
&& string.Equals(customHealth, "true", StringComparison.OrdinalIgnoreCase))
{
backend.HealthCheckOptions.Enabled = true;
}
})
.ConfigureBackend("backend1", backend =>
{
backend.HealthCheckOptions.Enabled = false;
})
.ConfigureRouteDefaults(route =>
{
// Do not let config based routes take priority over code based routes.
// Lower numbers are higher priority.
if (route.Priority.HasValue && route.Priority.Value < 0)
{
route.Priority = 0;
}
})
// If I need services as part of the config:
.ConfigureRoute<IMemoryCache>("route1", (route, cache) =>
{
var value = cache.Get<int>("key");
route.Priority = value;
})
;
}

/// <summary>
Expand Down
5 changes: 4 additions & 1 deletion samples/ReverseProxy.Sample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
"LoadBalancing": {
"Mode": "Random"
},
"Metadata": {
"CustomHealth": "true"
},
"Endpoints": {
"backend1/endpoint1": {
"Address": "https://localhost:10000/"
Expand All @@ -39,7 +42,7 @@
},
"Routes": [
{
"RouteId": "backend1/route1",
"RouteId": "route1",
"BackendId": "backend1",
"Match": {
"Methods": [ "GET", "POST" ],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

namespace Microsoft.ReverseProxy.Core.Abstractions
{
/// <summary>
Expand All @@ -12,5 +14,10 @@ public interface IConfigErrorReporter
/// Reports a configuration error.
/// </summary>
void ReportError(string code, string itemId, string message);

/// <summary>
/// Reports a configuration error.
/// </summary>
void ReportError(string code, string itemId, string message, Exception ex);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public sealed class ProxyRoute : IDeepCloneable<ProxyRoute>
public ProxyMatch Match { get; private set; } = new ProxyMatch();

/// <summary>
/// Optionally, a priority value for this route. Routes with higher numbers take precedence over lower numbers.
/// Optionally, a priority value for this route. Routes with lower numbers take precedence over higher numbers.
/// </summary>
public int? Priority { get; set; }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using Microsoft.ReverseProxy.Core.Abstractions;
using Microsoft.ReverseProxy.Core.Configuration;
using Microsoft.ReverseProxy.Core.Configuration.DependencyInjection;
using Microsoft.ReverseProxy.Core.Service;

namespace Microsoft.Extensions.DependencyInjection
{
Expand Down Expand Up @@ -48,5 +52,233 @@ public static IReverseProxyBuilder LoadFromConfig(this IReverseProxyBuilder buil

return builder;
}

/// <summary>
/// Provides a configuration action that runs on all routes each time the configuration is generated. This can be called more than once and
/// the Actions are run in the given order.
/// </summary>
/// <param name="builder"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IReverseProxyBuilder ConfigureRouteDefaults(this IReverseProxyBuilder builder, Action<ProxyRoute> configure)
{
if (configure is null)
{
throw new ArgumentNullException(nameof(configure));
}

builder.Services.Configure<DynamicConfigBuilderOptions>(options =>
{
options.RouteDefaultConfigs.Add(configure);
});

return builder;
}

/// <summary>
/// Provides a configuration action that runs on all routes each time the configuration is generated. This can be called more than once and
/// the Actions are run in the given order.
/// </summary>
/// <typeparam name="TService">TService: A service resolved from the IServiceProvider for use when configuring routes. If you need multiple services
/// then specify IServiceProvider and resolve them directly.</typeparam>
/// <param name="builder"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IReverseProxyBuilder ConfigureRouteDefaults<TService>(this IReverseProxyBuilder builder, Action<ProxyRoute, TService> configure) where TService : class
{
if (configure is null)
{
throw new ArgumentNullException(nameof(configure));
}

builder.Services.AddOptions<DynamicConfigBuilderOptions>().Configure<TService>((options, service) =>
{
options.RouteDefaultConfigs.Add(route => configure(route, service));
});

return builder;
}

/// <summary>
/// Provides a configuration action that runs on the named route each time the configuration is generated. This can be called more than once
/// per route and the Actions are run in the given order. These are applied after any default configuration Actions.
/// </summary>
/// <param name="builder"></param>
/// <param name="routeId"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IReverseProxyBuilder ConfigureRoute(this IReverseProxyBuilder builder, string routeId, Action<ProxyRoute> configure)
{
if (routeId is null)
{
throw new ArgumentNullException(nameof(routeId));
}

if (configure is null)
{
throw new ArgumentNullException(nameof(configure));
}

builder.Services.Configure<DynamicConfigBuilderOptions>(options =>
{
if (!options.RouteConfigs.TryGetValue(routeId, out var configs))
{
configs = new List<Action<ProxyRoute>>(1);
options.RouteConfigs[routeId] = configs;
}
configs.Add(configure);
});

return builder;
}

/// <summary>
/// Provides a configuration action that runs on the named route each time the configuration is generated. This can be called more than once
/// per route and the Actions are run in the given order. These are applied after any default configuration Actions.
/// </summary>
/// <typeparam name="TService">TService: A service resolved from the IServiceProvider for use when configuring this route. If you need multiple
/// services then specify IServiceProvider and resolve them directly.</typeparam>
/// <param name="builder"></param>
/// <param name="routeId"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IReverseProxyBuilder ConfigureRoute<TService>(this IReverseProxyBuilder builder, string routeId, Action<ProxyRoute, TService> configure) where TService : class
{
if (routeId is null)
{
throw new ArgumentNullException(nameof(routeId));
}

if (configure is null)
{
throw new ArgumentNullException(nameof(configure));
}

builder.Services.AddOptions<DynamicConfigBuilderOptions>().Configure<TService>((options, service) =>
{
if (!options.RouteConfigs.TryGetValue(routeId, out var configs))
{
configs = new List<Action<ProxyRoute>>(1);
options.RouteConfigs[routeId] = configs;
}
configs.Add(route => configure(route, service));
});

return builder;
}

/// <summary>
/// Provides a configuration action that runs on all backends each time the configuration is generated. This can be called more than once and
/// the Actions are run in the given order.
/// </summary>
/// <param name="builder"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IReverseProxyBuilder ConfigureBackendDefaults(this IReverseProxyBuilder builder, Action<string, Backend> configure)
{
if (configure is null)
{
throw new ArgumentNullException(nameof(configure));
}

builder.Services.Configure<DynamicConfigBuilderOptions>(options =>
{
options.BackendDefaultConfigs.Add(configure);
});

return builder;
}

/// <summary>
/// Provides a configuration action that runs on all backends each time the configuration is generated. This can be called more than once and
/// the Actions are run in the given order.
/// </summary>
/// <typeparam name="TService">TService: A service resolved from the IServiceProvider for use when configuring backends. If you need multiple
/// services then specify IServiceProvider and resolve them directly.</typeparam>
/// <param name="builder"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IReverseProxyBuilder ConfigureBackendDefaults<TService>(this IReverseProxyBuilder builder, Action<string, Backend, TService> configure) where TService : class
{
if (configure is null)
{
throw new ArgumentNullException(nameof(configure));
}

builder.Services.AddOptions<DynamicConfigBuilderOptions>().Configure<TService>((options, service) =>
{
options.BackendDefaultConfigs.Add((backendId, backend) => configure(backendId, backend, service));
});

return builder;
}

/// <summary>
/// Provides a configuration action that runs on the named backend each time the configuration is generated. This can be called more than once
/// per backend and the Actions are run in the given order. These are applied after any default configuration Actions.
/// </summary>
/// <param name="builder"></param>
/// <param name="backendId"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IReverseProxyBuilder ConfigureBackend(this IReverseProxyBuilder builder, string backendId, Action<Backend> configure)
{
if (backendId is null)
{
throw new ArgumentNullException(nameof(backendId));
}

if (configure is null)
{
throw new ArgumentNullException(nameof(configure));
}

builder.Services.Configure<DynamicConfigBuilderOptions>(options =>
{
if (!options.BackendConfigs.TryGetValue(backendId, out var configs))
{
configs = new List<Action<Backend>>(1);
options.BackendConfigs[backendId] = configs;
}
configs.Add(configure);
});

return builder;
}

/// <summary>
/// Provides a configuration action that runs on the named backend each time the configuration is generated. This can be called more than once
/// per backend and the Actions are run in the given order. These are applied after any default configuration Actions.
/// </summary>
/// <typeparam name="TService">TService: A service resolved from the IServiceProvider for use when configuring this backend. If you need multiple
/// services then specify IServiceProvider and resolve them directly.</typeparam>
/// <param name="builder"></param>
/// <param name="backendId"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IReverseProxyBuilder ConfigureBackend<TService>(this IReverseProxyBuilder builder, string backendId, Action<Backend, TService> configure) where TService : class
{
if (backendId is null)
{
throw new ArgumentNullException(nameof(backendId));
}

if (configure is null)
{
throw new ArgumentNullException(nameof(configure));
}

builder.Services.AddOptions<DynamicConfigBuilderOptions>().Configure<TService>((options, service) =>
{
if (!options.BackendConfigs.TryGetValue(backendId, out var configs))
{
configs = new List<Action<Backend>>(1);
options.BackendConfigs[backendId] = configs;
}
configs.Add(backend => configure(backend, service));
});

return builder;
}
}
}
10 changes: 10 additions & 0 deletions src/ReverseProxy.Core/Configuration/ProxyConfigLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ public void ReportError(string code, string itemId, string message)
{
Log.ConfigError(_logger, code, itemId, message);
}

public void ReportError(string code, string itemId, string message, Exception ex)
{
Log.ConfigError(_logger, ex, code, itemId, message);
}
}


Expand Down Expand Up @@ -142,6 +147,11 @@ public static void ConfigError(ILogger logger, string code, string itemId, strin
_configError(logger, code, itemId, message, null);
}

public static void ConfigError(ILogger logger, Exception ex, string code, string itemId, string message)
{
_configError(logger, code, itemId, message, ex);
}

public static void ApplyProxyConfig(ILogger logger)
{
_applyProxyConfig(logger, null);
Expand Down
3 changes: 3 additions & 0 deletions src/ReverseProxy.Core/Service/Config/ConfigErrors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@ internal static class ConfigErrors
internal const string ParsedRouteRuleMultipleHostMatchers = "ParsedRoute_RuleMultipleHostMatchers";
internal const string ParsedRouteRuleMultiplePathMatchers = "ParsedRoute_RuleMultiplePathMatchers";
internal const string ParsedRouteRuleInvalidMatcher = "ParsedRoute_RuleInvalidMatcher";

internal const string ConfigBuilderBackendException = "ConfigBuilder_BackendException";
internal const string ConfigBuilderRouteException = "ConfigBuilder_RouteException";
}
}
Loading

0 comments on commit 36da8b4

Please sign in to comment.