Feature/#574 look at httpclient cache key (#589)

* #574 consolidate some code, man the config stuff is a mess!

* #574 just use the downstream re route and the key for caching http clients

* #574 added benchmark, i was suprised to learn using a complex type was faster than a string in benchmark .net dictionary tests, hey ho probably dont have enough data in the type...
This commit is contained in:
Tom Pallister
2018-09-01 13:10:45 +01:00
committed by GitHub
parent 55277cac45
commit 66b68fc685
40 changed files with 672 additions and 289 deletions

View File

@ -0,0 +1,163 @@
namespace Ocelot.AcceptanceTests
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using Configuration;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using Requester;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
public class HttpClientCachingTests : IDisposable
{
private readonly Steps _steps;
private string _downstreamPath;
private readonly ServiceHandler _serviceHandler;
public HttpClientCachingTests()
{
_serviceHandler = new ServiceHandler();
_steps = new Steps();
}
[Fact]
public void should_cache_one_http_client_same_re_route()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51879,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
}
}
};
var cache = new FakeHttpClientCache();
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningWithFakeHttpClientCache(cache))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => cache.Count.ShouldBe(1))
.BDDfy();
}
[Fact]
public void should_cache_two_http_client_different_re_route()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51879,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
},
new FileReRoute
{
DownstreamPathTemplate = "/two",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51879,
}
},
UpstreamPathTemplate = "/two",
UpstreamHttpMethod = new List<string> { "Get" },
}
}
};
var cache = new FakeHttpClientCache();
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningWithFakeHttpClientCache(cache))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/two"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/two"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/two"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => cache.Count.ShouldBe(2))
.BDDfy();
}
private void GivenThereIsAServiceRunningOn(string baseUrl, int statusCode, string responseBody)
{
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, async context =>
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody);
});
}
public void Dispose()
{
_serviceHandler.Dispose();
_steps.Dispose();
}
public class FakeHttpClientCache : IHttpClientCache
{
private readonly ConcurrentDictionary<DownstreamReRoute, IHttpClient> _httpClientsCache;
public FakeHttpClientCache()
{
_httpClientsCache = new ConcurrentDictionary<DownstreamReRoute, IHttpClient>();
}
public void Set(DownstreamReRoute key, IHttpClient client, TimeSpan expirationTime)
{
_httpClientsCache.AddOrUpdate(key, client, (k, oldValue) => client);
}
public IHttpClient Get(DownstreamReRoute key)
{
//todo handle error?
return _httpClientsCache.TryGetValue(key, out var client) ? client : null;
}
public int Count => _httpClientsCache.Count;
}
}
}

View File

@ -29,6 +29,7 @@
using static Ocelot.Infrastructure.Wait;
using Configuration.Repository;
using Ocelot.Configuration.Creator;
using Requester;
using CookieHeaderValue = System.Net.Http.Headers.CookieHeaderValue;
using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue;
@ -182,6 +183,35 @@
_ocelotClient = _ocelotServer.CreateClient();
}
public void GivenOcelotIsRunningWithFakeHttpClientCache(IHttpClientCache cache)
{
_webHostBuilder = new WebHostBuilder();
_webHostBuilder
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
config.AddJsonFile("ocelot.json", false, false);
config.AddEnvironmentVariables();
})
.ConfigureServices(s =>
{
s.AddSingleton<IHttpClientCache>(cache);
s.AddOcelot();
})
.Configure(app =>
{
app.UseOcelot().Wait();
});
_ocelotServer = new TestServer(_webHostBuilder);
_ocelotClient = _ocelotServer.CreateClient();
}
internal void GivenIWait(int wait)
{
Thread.Sleep(wait);