Skip to content

Instantly share code, notes, and snippets.

@msx752
Created February 23, 2022 12:42
Show Gist options
  • Save msx752/dae6978d783661df86d4f99356ecec5e to your computer and use it in GitHub Desktop.
Save msx752/dae6978d783661df86d4f99356ecec5e to your computer and use it in GitHub Desktop.
Ocelot WebApplicationFactory
using Ocelot.Configuration;
using Ocelot.Logging;
using Ocelot.Requester;
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
public class CustomHttpClientBuilder : Ocelot.Requester.IHttpClientBuilder
{
private readonly IHttpClientCache _cacheHandlers;
private readonly ICustomOcelotMessageHandler _customOcelotMessageHandler;
private readonly TimeSpan _defaultTimeout;
private readonly IDelegatingHandlerHandlerFactory _factory;
private readonly IOcelotLogger _logger;
private DownstreamRoute _cacheKey;
private IHttpClient _client;
private HttpClient _httpClient;
public CustomHttpClientBuilder(
IDelegatingHandlerHandlerFactory factory,
IHttpClientCache cacheHandlers,
IOcelotLogger logger,
ICustomOcelotMessageHandler customOcelotMessageHandler)
{
_factory = factory;
_cacheHandlers = cacheHandlers;
_logger = logger;
// This is hardcoded at the moment but can easily be added to configuration
// if required by a user request.
_defaultTimeout = TimeSpan.FromSeconds(90);
_customOcelotMessageHandler = customOcelotMessageHandler;
}
public IHttpClient Create(DownstreamRoute downstreamRoute)
{
_cacheKey = downstreamRoute;
var httpClient = _cacheHandlers.Get(_cacheKey);
if (httpClient != null)
{
_client = httpClient;
return httpClient;
}
var handler = CreateHandler(downstreamRoute);
if (handler is HttpClientHandler && downstreamRoute.DangerousAcceptAnyServerCertificateValidator)
{
((HttpClientHandler)handler).ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => true;
_logger
.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamRoute, UpstreamPathTemplate: {downstreamRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {downstreamRoute.DownstreamPathTemplate}");
}
var timeout = downstreamRoute.QosOptions.TimeoutValue == 0
? _defaultTimeout
: TimeSpan.FromMilliseconds(downstreamRoute.QosOptions.TimeoutValue);
_httpClient = new HttpClient(CreateHttpMessageHandler(handler, downstreamRoute))
{
Timeout = timeout
};
_client = new HttpClientWrapper(_httpClient);
return _client;
}
public void Save()
{
_cacheHandlers.Set(_cacheKey, _client, TimeSpan.FromHours(24));
}
private HttpMessageHandler CreateHandler(DownstreamRoute downstreamRoute)
{
var wafHandler = _customOcelotMessageHandler.Gethandler(downstreamRoute);
if (wafHandler != null)
return wafHandler; // waf routing, if not defined searches real ip/port below
// Dont' create the CookieContainer if UseCookies is not set or the HttpClient will complain
// under .Net Full Framework
var useCookies = downstreamRoute.HttpHandlerOptions.UseCookieContainer;
return useCookies ? UseCookiesHandler(downstreamRoute) : UseNonCookiesHandler(downstreamRoute);
}
private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler, DownstreamRoute request)
{
//todo handle error
var handlers = _factory.Get(request).Data;
handlers
.Select(handler => handler)
.Reverse()
.ToList()
.ForEach(handler =>
{
var delegatingHandler = handler();
delegatingHandler.InnerHandler = httpMessageHandler;
httpMessageHandler = delegatingHandler;
});
return httpMessageHandler;
}
private HttpClientHandler UseCookiesHandler(DownstreamRoute downstreamRoute)
{
return new HttpClientHandler
{
AllowAutoRedirect = downstreamRoute.HttpHandlerOptions.AllowAutoRedirect,
UseCookies = downstreamRoute.HttpHandlerOptions.UseCookieContainer,
UseProxy = downstreamRoute.HttpHandlerOptions.UseProxy,
MaxConnectionsPerServer = downstreamRoute.HttpHandlerOptions.MaxConnectionsPerServer,
CookieContainer = new CookieContainer(),
};
}
private HttpClientHandler UseNonCookiesHandler(DownstreamRoute downstreamRoute)
{
return new HttpClientHandler
{
AllowAutoRedirect = downstreamRoute.HttpHandlerOptions.AllowAutoRedirect,
UseCookies = downstreamRoute.HttpHandlerOptions.UseCookieContainer,
UseProxy = downstreamRoute.HttpHandlerOptions.UseProxy,
MaxConnectionsPerServer = downstreamRoute.HttpHandlerOptions.MaxConnectionsPerServer,
};
}
}
using Microsoft.AspNetCore.Http;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Requester;
using Ocelot.Responses;
using System;
using System.Net.Http;
using System.Threading.Tasks;
public class CustomHttpClientHttpRequester : IHttpRequester
{
private readonly IHttpClientCache _cacheHandlers;
private readonly ICustomOcelotMessageHandler _customOcelotMessageHandler;
private readonly IDelegatingHandlerHandlerFactory _factory;
private readonly IOcelotLogger _logger;
private readonly IExceptionToErrorMapper _mapper;
public CustomHttpClientHttpRequester(IOcelotLoggerFactory loggerFactory,
IHttpClientCache cacheHandlers,
IDelegatingHandlerHandlerFactory factory,
IExceptionToErrorMapper mapper,
ICustomOcelotMessageHandler customOcelotMessageHandler)
{
_logger = loggerFactory.CreateLogger<CustomHttpClientHttpRequester>();
_cacheHandlers = cacheHandlers;
_factory = factory;
_mapper = mapper;
_customOcelotMessageHandler = customOcelotMessageHandler;
}
public async Task<Response<HttpResponseMessage>> GetResponse(HttpContext httpContext)
{
var builder = new CustomHttpClientBuilder(_factory, _cacheHandlers, _logger, _customOcelotMessageHandler);
var downstreamRoute = httpContext.Items.DownstreamRoute();
var downstreamRequest = httpContext.Items.DownstreamRequest();
var httpClient = builder.Create(downstreamRoute);
try
{
var response = await httpClient.SendAsync(downstreamRequest.ToHttpRequestMessage(), httpContext.RequestAborted);
return new OkResponse<HttpResponseMessage>(response);
}
catch (Exception exception)
{
var error = _mapper.Map(exception);
return new ErrorResponse<HttpResponseMessage>(error);
}
finally
{
builder.Save();
}
}
}
using Ocelot.Configuration;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Reflection;
public class CustomOcelotMessageHandler : ICustomOcelotMessageHandler
{
private Dictionary<string, HttpMessageHandler> handlers = new Dictionary<string, HttpMessageHandler>();
public CustomOcelotMessageHandler(HttpClient[] httpClients)
{
foreach (var item in httpClients)
{
var kv = GetHttpClientHandler(item);
handlers.Add(kv.Key, kv.Value);
}
}
public HttpMessageHandler Gethandler(DownstreamRoute downstreamRoute)
{
foreach (var downstreamHostAndPort in downstreamRoute.DownstreamAddresses)
{
if (handlers.TryGetValue($"{downstreamHostAndPort.Host.ToLowerInvariant()}:{downstreamHostAndPort.Port}", out HttpMessageHandler handler))
return handler;
}
return null;
}
private static KeyValuePair<string, HttpMessageHandler> GetHttpClientHandler(HttpClient httpClient)
{
var handlerField = httpClient
.GetType().BaseType
.GetField("_handler", BindingFlags.NonPublic | BindingFlags.Instance);
var handler = (HttpMessageHandler)handlerField.GetValue(httpClient);
var handlerType = handler.GetType();
if (!handlerType.FullName.StartsWith("Microsoft.AspNetCore.Mvc.Testing"))
throw new ArgumentException($"selected HttpClient doesn't belongs to WebApplicationFactory, this is only for Testing purpose.");
return new KeyValuePair<string, HttpMessageHandler>($"{httpClient.BaseAddress.Host.ToLowerInvariant()}:{httpClient.BaseAddress.Port}", handler);
}
}
using Ocelot.Configuration;
using System.Net.Http;
public interface ICustomOcelotMessageHandler
{
HttpMessageHandler Gethandler(DownstreamRoute downstreamRoute);
}
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Ocelot.Requester;
using System;
using System.Net.Http;
public static class OcelotRedirectExtensions
{
public static void AddOcelotWAFRedirections(this IServiceCollection services, HttpClient[] httpClients)
{
if (httpClients == null)
return;
var _ocelotMessageHandlers = new CustomOcelotMessageHandler(httpClients);
services.AddSingleton(typeof(ICustomOcelotMessageHandler), x => _ocelotMessageHandlers);
var descriptorIHttpRequester = new ServiceDescriptor(typeof(IHttpRequester), typeof(CustomHttpClientHttpRequester),
ServiceLifetime.Singleton);
services.Replace(descriptorIHttpRequester);
}
/// <summary>
/// creates virtual HttpContext for the WebApplicationFactory for given url format.
/// </summary>
/// <typeparam name="TStartup"></typeparam>
/// <param name="webApplicationFactory"></param>
/// <param name="port">virtual port default: 80</param>
/// <param name="host">virtual hostname default: localhost</param>
/// <param name="scheme">virtual scheme default: http</param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static HttpClient CreateOcelotClient<TStartup>(this WebApplicationFactory<TStartup> webApplicationFactory,
int port,
string host = "localhost")
where TStartup : class
{
if (Uri.TryCreate(new Uri($"http://{host}:{port}"), "", out Uri generatedVirtualuri))
{
return webApplicationFactory.CreateClient(new WebApplicationFactoryClientOptions()
{
BaseAddress = generatedVirtualuri
});
}
throw new ArgumentException($"Invalid Uri Format: '{$"http://{host}:{port}"}' please use valid Uri format.");
}
}
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using System.Net.Http;
public class OcelotWebApplicationFactory : WebApplicationFactory<WAFOcelot.Startup>
{
private readonly HttpClient[] httpClients;
public OcelotWebApplicationFactory(HttpClient[] httpClients)
{
this.httpClients = httpClients;
}
public OcelotWebApplicationFactory()
{
}
protected override IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder(new string[0]).ConfigureWebHostDefaults((wb) =>
{
wb.UseEnvironment("Development");
wb.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("ocelot.json", false, true);
});
wb.UseStartup<WAFOcelot.Startup>();
wb.ConfigureServices(services =>
{
services.AddOcelotWAFRedirections(httpClients);
});
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment