Skip to content

Commit

Permalink
add proxy to code config
Browse files Browse the repository at this point in the history
  • Loading branch information
cecilphillip committed Sep 20, 2020
1 parent aad905a commit 95d3f4b
Show file tree
Hide file tree
Showing 14 changed files with 505 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/CodeConfigSample/CCProxy/CCProxy.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Consul" Version="1.6.1.1" />
<PackageReference Include="Microsoft.ReverseProxy" Version="1.0.0-preview.4.20424.2" />
<PackageReference Include="System.Interactive" Version="4.1.1" />
</ItemGroup>

</Project>
111 changes: 111 additions & 0 deletions src/CodeConfigSample/CCProxy/ConsulMonitorWorker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Consul;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.ReverseProxy.Abstractions;
using Microsoft.ReverseProxy.Service;

namespace CCProxy
{
public class ConsulMonitorWorker : BackgroundService
{
private readonly IConsulClient _consulClient;
private readonly IProxyConfigProvider _proxyConfigProvider;
private readonly IConfigValidator _proxyConfigValidator;
private readonly ILogger<ConsulMonitorWorker> _logger;
private const int DEFAULT_CONSUL_POLL_INTERVAL_MINS = 2;

public ConsulMonitorWorker(IConsulClient consulClient, IProxyConfigProvider proxyConfigProvider,
IConfigValidator proxyConfigValidator, ILogger<ConsulMonitorWorker> logger)
{
_consulClient = consulClient;
_proxyConfigProvider = proxyConfigProvider;
_proxyConfigValidator = proxyConfigValidator;
_logger = logger;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var serviceResult = await _consulClient.Agent.Services(stoppingToken);

if (serviceResult.StatusCode == HttpStatusCode.OK)
{
var clusters = await ExtractClusters(serviceResult);
var routes = await ExtractRoutes(serviceResult);

(_proxyConfigProvider as ConsulProxyConfigProvider)?.Update(routes, clusters);
}

await Task.Delay(TimeSpan.FromMinutes(DEFAULT_CONSUL_POLL_INTERVAL_MINS), stoppingToken);
}
}

private async Task<List<Cluster>> ExtractClusters(QueryResult<Dictionary<string, AgentService>> serviceResult)
{
var clusters = new Dictionary<string, Cluster>();
var serviceMapping = serviceResult.Response;
foreach (var (key, svc) in serviceMapping)
{
var cluster = clusters.ContainsKey(svc.Service)
? clusters[svc.Service]
: new Cluster {Id = svc.Service};

cluster.Destinations.Add(svc.ID, new Destination {Address = $"{svc.Address}:{svc.Port}"});

var clusterErrs = await _proxyConfigValidator.ValidateClusterAsync(cluster);
if (clusterErrs.Any())
{
_logger.LogError("Errors found when creating clusters for {Service}", svc.Service);
clusterErrs.ForEach(err => _logger.LogError(err, $"{svc.Service} cluster validation error"));
continue;
}

clusters[svc.Service] = cluster;
}

return clusters.Values.ToList();
}

private async Task<List<ProxyRoute>> ExtractRoutes(QueryResult<Dictionary<string, AgentService>> serviceResult)
{
var serviceMapping = serviceResult.Response;
List<ProxyRoute> routes = new List<ProxyRoute>();
foreach (var (key, svc) in serviceMapping)
{
if (svc.Meta.TryGetValue("yarp", out string enableYarp) &&
enableYarp.Equals("on", StringComparison.InvariantCulture))
{
if (routes.Any(r => r.ClusterId == svc.Service)) continue;

ProxyRoute route = new ProxyRoute
{
ClusterId = svc.Service,
RouteId = $"{svc.Service}-route",
Match =
{
Path = svc.Meta.ContainsKey("yarp_path")?svc.Meta["yarp_path"] : default,
Hosts = svc.Meta.ContainsKey("yarp_hosts")? svc.Meta["yarp_hosts"].Split(',') : default
}
};

var routeErrs = await _proxyConfigValidator.ValidateRouteAsync(route);
if (routeErrs.Any())
{
_logger.LogError("Errors found when trying to generate routes for {Service}", svc.Service);
routeErrs.ForEach(err => _logger.LogError(err, $"{svc.Service} route validation error"));
continue;
}
routes.Add(route);
}
}
return routes;
}
}
}
50 changes: 50 additions & 0 deletions src/CodeConfigSample/CCProxy/ConsulProxyConfigProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Threading;
using Consul;
using Microsoft.Extensions.Primitives;
using Microsoft.ReverseProxy.Abstractions;
using Microsoft.ReverseProxy.Service;

namespace CCProxy
{
public class ConsulProxyConfigProvider : IProxyConfigProvider
{
private readonly IConsulClient _consulClient;
private volatile ConsulProxyConfig _config;

public ConsulProxyConfigProvider(IConsulClient consulClient)
{
_consulClient = consulClient;
_config = new ConsulProxyConfig(null, null);
}

public IProxyConfig GetConfig() => _config;

public virtual void Update(IReadOnlyList<ProxyRoute> routes, IReadOnlyList<Cluster> clusters)
{
var oldConfig = _config;
_config = new ConsulProxyConfig(routes, clusters);
oldConfig.SignalChange();
}

private class ConsulProxyConfig : IProxyConfig
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
public IReadOnlyList<ProxyRoute> Routes { get; }
public IReadOnlyList<Cluster> Clusters { get; }
public IChangeToken ChangeToken { get; }

public ConsulProxyConfig(IReadOnlyList<ProxyRoute> routes, IReadOnlyList<Cluster> clusters)
{
Routes = routes;
Clusters = clusters;
ChangeToken = new CancellationChangeToken(_cts.Token);
}

internal void SignalChange()
{
_cts.Cancel();
}
}
}
}
52 changes: 52 additions & 0 deletions src/CodeConfigSample/CCProxy/Controllers/YarpController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.ReverseProxy.Service;

namespace CCProxy.Controllers
{
[Route("/yarp")]
[ApiController]
public class YarpController : Controller
{
private readonly IProxyConfigProvider _proxyConfigProvider;

public YarpController(IProxyConfigProvider proxyConfigProvider)
{
_proxyConfigProvider = proxyConfigProvider;
}

[HttpGet("routes")]
public ActionResult DumpRoutes()
{
var proxyConfig = _proxyConfigProvider.GetConfig();
return Ok(proxyConfig.Routes);
}

[HttpGet("clusters")]
public ActionResult DumpClusters()
{
var proxyConfig = _proxyConfigProvider.GetConfig();
return Ok(proxyConfig.Clusters);
}

[HttpGet("incoming")]
public IActionResult Dump()
{
var result = new
{
Request.Method,
Request.Protocol,
Request.Scheme,
Host = Request.Host.Value,
PathBase = Request.PathBase.Value,
Path = Request.Path.Value,
Query = Request.QueryString.Value,
Headers = Request.Headers.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToArray()),
Time = DateTimeOffset.UtcNow
};

return Ok(result);
}
}
}
19 changes: 19 additions & 0 deletions src/CodeConfigSample/CCProxy/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY ["CCProxy.csproj", "./"]
RUN dotnet restore "CCProxy.csproj"
COPY . .
WORKDIR "/src"
RUN dotnet build "CCProxy.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "CCProxy.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "CCProxy.dll"]
57 changes: 57 additions & 0 deletions src/CodeConfigSample/CCProxy/Middleware/HttpInspectorMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.ReverseProxy.Middleware;

namespace CCProxy.Middleware
{
public class HttpInspectorMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;

public HttpInspectorMiddleware(RequestDelegate next,
ILogger<HttpInspectorMiddleware> logger)
{
_next = next;
_logger = logger;
}

public async Task Invoke(HttpContext context)
{
LogRequest(context);
LogDestinations(context);
await _next(context);
}

private void LogDestinations(HttpContext context)
{
_logger.LogDebug("Available Destinations: ");
var proxyFeature = context.Features.Get<IReverseProxyFeature>();

proxyFeature.AvailableDestinations.ForEach(dest =>
{
_logger.LogDebug("Destination {ID} {Address}", dest.DestinationId, dest.Config.Address);
});
}

private void LogRequest(HttpContext context)
{
var buffer = new StringBuilder();

context.Request.Headers
.ForEach(kvp => buffer.Append($"{kvp.Key}: {kvp.Value}{Environment.NewLine}"));

_logger.LogDebug($"Http Request Information:{Environment.NewLine}" +
$"Schema: {context.Request.Scheme}{Environment.NewLine}" +
$"Host: {context.Request.Host}{Environment.NewLine}" +
$"Path: {context.Request.Path}{Environment.NewLine}" +
$"QueryString: {context.Request.QueryString}{Environment.NewLine}" +
$"Headers: {Environment.NewLine}{buffer}{Environment.NewLine}");
}
}
}
26 changes: 26 additions & 0 deletions src/CodeConfigSample/CCProxy/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace CCProxy
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
14 changes: 14 additions & 0 deletions src/CodeConfigSample/CCProxy/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"CCProxy": {
"commandName": "Project",
"dotnetRunMessages": "true",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS" : "http://0.0.0.0:5000"
}
}
}
}
31 changes: 31 additions & 0 deletions src/CodeConfigSample/CCProxy/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using Consul;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace CCProxy
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddConsulClient(this IServiceCollection services)
{
return services.AddConsulClient(options => { });
}

public static IServiceCollection AddConsulClient(
this IServiceCollection services,
Action<ConsulClientConfiguration> options)
{
/*
* CONSUL_HTTP_ADDR
* CONSUL_HTTP_SSL
* CONSUL_HTTP_SSL_VERIFY
* CONSUL_HTTP_AUTH
* CONSUL_HTTP_TOKEN
*/
services.TryAddSingleton<IConsulClient>(sp => new ConsulClient(options));

return services;
}
}
}
Loading

0 comments on commit 95d3f4b

Please sign in to comment.