diff --git a/samples/OcelotEureka/ApiGateway/Program.cs b/samples/OcelotEureka/ApiGateway/Program.cs index 28d344c92..d72d86444 100644 --- a/samples/OcelotEureka/ApiGateway/Program.cs +++ b/samples/OcelotEureka/ApiGateway/Program.cs @@ -22,7 +22,7 @@ public static IWebHost BuildWebHost(string[] args) => .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("ocelot.json") + .AddJsonFile("ocelot.json", false, false) .AddEnvironmentVariables(); }) .ConfigureServices(s => diff --git a/samples/OcelotGraphQL/Program.cs b/samples/OcelotGraphQL/Program.cs index 81938f9c8..83a48fda1 100644 --- a/samples/OcelotGraphQL/Program.cs +++ b/samples/OcelotGraphQL/Program.cs @@ -106,7 +106,7 @@ type Query { .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("ocelot.json") + .AddJsonFile("ocelot.json", false, false) .AddEnvironmentVariables(); }) .ConfigureServices(s => { diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs index 7d913cfa1..c1f4a1730 100644 --- a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs @@ -59,7 +59,6 @@ public Task OpenAsync(CancellationToken cancellationToken) { this.webHost = new WebHostBuilder() .UseKestrel() - //.UseStartup() .UseUrls(this.listeningAddress) .ConfigureAppConfiguration((hostingContext, config) => { @@ -67,7 +66,7 @@ public Task OpenAsync(CancellationToken cancellationToken) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("ocelot.json") + .AddJsonFile("ocelot.json", false, false) .AddEnvironmentVariables(); }) .ConfigureLogging((hostingContext, logging) => diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs index 2c70e3948..77f9775ea 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs @@ -1,61 +1,61 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; -using Ocelot.Configuration; -using Ocelot.Errors; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Logging; -using Ocelot.Middleware; - -namespace Ocelot.Authentication.Middleware -{ - public class AuthenticationMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - - public AuthenticationMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory) - : base(loggerFactory.CreateLogger()) - { - _next = next; - } - - public async Task Invoke(DownstreamContext context) - { - if (IsAuthenticatedRoute(context.DownstreamReRoute)) - { - Logger.LogInformation($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); - - var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey); - - context.HttpContext.User = result.Principal; - - if (context.HttpContext.User.Identity.IsAuthenticated) - { - Logger.LogInformation($"Client has been authenticated for {context.HttpContext.Request.Path}"); - await _next.Invoke(context); - } - else - { - var error = new UnauthenticatedError( - $"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated"); - - Logger.LogWarning($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error}"); - - SetPipelineError(context, error); - } - } - else - { - Logger.LogInformation($"No authentication needed for {context.HttpContext.Request.Path}"); - - await _next.Invoke(context); - } - } - - private static bool IsAuthenticatedRoute(DownstreamReRoute reRoute) - { - return reRoute.IsAuthenticated; - } - } -} +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Ocelot.Configuration; +using Ocelot.Errors; +using Ocelot.Infrastructure.Extensions; +using Ocelot.Logging; +using Ocelot.Middleware; + +namespace Ocelot.Authentication.Middleware +{ + public class AuthenticationMiddleware : OcelotMiddleware + { + private readonly OcelotRequestDelegate _next; + + public AuthenticationMiddleware(OcelotRequestDelegate next, + IOcelotLoggerFactory loggerFactory) + : base(loggerFactory.CreateLogger()) + { + _next = next; + } + + public async Task Invoke(DownstreamContext context) + { + if (IsAuthenticatedRoute(context.DownstreamReRoute)) + { + Logger.LogInformation($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); + + var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey); + + context.HttpContext.User = result.Principal; + + if (context.HttpContext.User.Identity.IsAuthenticated) + { + Logger.LogInformation($"Client has been authenticated for {context.HttpContext.Request.Path}"); + await _next.Invoke(context); + } + else + { + var error = new UnauthenticatedError( + $"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated"); + + Logger.LogWarning($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error}"); + + SetPipelineError(context, error); + } + } + else + { + Logger.LogInformation($"No authentication needed for {context.HttpContext.Request.Path}"); + + await _next.Invoke(context); + } + } + + private static bool IsAuthenticatedRoute(DownstreamReRoute reRoute) + { + return reRoute.IsAuthenticated; + } + } +} diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index 1ddf6fccd..d3409ee85 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -71,7 +71,7 @@ public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder File.WriteAllText("ocelot.json", json); - builder.AddJsonFile("ocelot.json"); + builder.AddJsonFile("ocelot.json", false, false); return builder; } diff --git a/src/Ocelot/Request/Mapper/IRequestMapper.cs b/src/Ocelot/Request/Mapper/IRequestMapper.cs index 343f1ab2d..9d34cbb22 100644 --- a/src/Ocelot/Request/Mapper/IRequestMapper.cs +++ b/src/Ocelot/Request/Mapper/IRequestMapper.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Request.Mapper -{ - using System.Net.Http; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Ocelot.Responses; - - public interface IRequestMapper - { - Task> Map(HttpRequest request); - } -} +namespace Ocelot.Request.Mapper +{ + using System.Net.Http; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.Responses; + + public interface IRequestMapper + { + Response Map(HttpRequest request); + } +} diff --git a/src/Ocelot/Request/Mapper/RequestMapper.cs b/src/Ocelot/Request/Mapper/RequestMapper.cs index 67afc53d7..f417cc016 100644 --- a/src/Ocelot/Request/Mapper/RequestMapper.cs +++ b/src/Ocelot/Request/Mapper/RequestMapper.cs @@ -1,108 +1,99 @@ -namespace Ocelot.Request.Mapper -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Net.Http; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Http.Extensions; - using Microsoft.Extensions.Primitives; - using Ocelot.Responses; - - public class RequestMapper : IRequestMapper - { - private readonly string[] _unsupportedHeaders = { "host" }; - - public async Task> Map(HttpRequest request) - { - try - { - var requestMessage = new HttpRequestMessage() - { - Content = await MapContent(request), - Method = MapMethod(request), - RequestUri = MapUri(request) - }; - - MapHeaders(request, requestMessage); - - return new OkResponse(requestMessage); - } - catch (Exception ex) - { - return new ErrorResponse(new UnmappableRequestError(ex)); - } - } - - private async Task MapContent(HttpRequest request) - { - if (request.Body == null) - { - return null; - } - - var content = new ByteArrayContent(await ToByteArray(request.Body)); - - content.Headers - .TryAddWithoutValidation("Content-Type", new[] {request.ContentType}); - - AddHeaderIfExistsOnRequest("Content-Language", content, request); - AddHeaderIfExistsOnRequest("Content-Location", content, request); - AddHeaderIfExistsOnRequest("Content-Range", content, request); - AddHeaderIfExistsOnRequest("Content-MD5", content, request); - AddHeaderIfExistsOnRequest("Content-Disposition", content, request); - AddHeaderIfExistsOnRequest("Content-Encoding", content, request); - - return content; - } - - private void AddHeaderIfExistsOnRequest(string key, HttpContent content, HttpRequest request) - { - if(request.Headers.ContainsKey(key)) - { - content.Headers - .TryAddWithoutValidation(key, request.Headers[key].ToList()); - } - } - - private HttpMethod MapMethod(HttpRequest request) - { - return new HttpMethod(request.Method); - } - - private Uri MapUri(HttpRequest request) - { - return new Uri(request.GetEncodedUrl()); - } - - private void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage) - { - foreach (var header in request.Headers) - { - if (IsSupportedHeader(header)) - { - requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); - } - } - } - - private async Task ToByteArray(Stream stream) - { - using (stream) - { - using (var memStream = new MemoryStream()) - { - await stream.CopyToAsync(memStream); - return memStream.ToArray(); - } - } - } - - private bool IsSupportedHeader(KeyValuePair header) - { - return !_unsupportedHeaders.Contains(header.Key.ToLower()); - } - } -} +namespace Ocelot.Request.Mapper +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Extensions; + using Microsoft.Extensions.Primitives; + using Ocelot.Responses; + + public class RequestMapper : IRequestMapper + { + private readonly string[] _unsupportedHeaders = { "host" }; + + public Response Map(HttpRequest request) + { + try + { + var requestMessage = new HttpRequestMessage() + { + Content = MapContent(request), + Method = MapMethod(request), + RequestUri = MapUri(request) + }; + + MapHeaders(request, requestMessage); + + return new OkResponse(requestMessage); + } + catch (Exception ex) + { + return new ErrorResponse(new UnmappableRequestError(ex)); + } + } + + private HttpContent MapContent(HttpRequest request) + { + if (request.Body == null || (request.Body.CanSeek && request.Body.Length <= 0)) + { + return null; + } + + var content = new StreamContent(request.Body); + + if(!string.IsNullOrEmpty(request.ContentType)) + { + content.Headers + .TryAddWithoutValidation("Content-Type", new[] {request.ContentType}); + } + + AddHeaderIfExistsOnRequest("Content-Language", content, request); + AddHeaderIfExistsOnRequest("Content-Location", content, request); + AddHeaderIfExistsOnRequest("Content-Range", content, request); + AddHeaderIfExistsOnRequest("Content-MD5", content, request); + AddHeaderIfExistsOnRequest("Content-Disposition", content, request); + AddHeaderIfExistsOnRequest("Content-Encoding", content, request); + + return content; + } + + private void AddHeaderIfExistsOnRequest(string key, HttpContent content, HttpRequest request) + { + if(request.Headers.ContainsKey(key)) + { + content.Headers + .TryAddWithoutValidation(key, request.Headers[key].ToList()); + } + } + + private HttpMethod MapMethod(HttpRequest request) + { + return new HttpMethod(request.Method); + } + + private Uri MapUri(HttpRequest request) + { + return new Uri(request.GetEncodedUrl()); + } + + private void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage) + { + foreach (var header in request.Headers) + { + if (IsSupportedHeader(header)) + { + requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); + } + } + } + + private bool IsSupportedHeader(KeyValuePair header) + { + return !_unsupportedHeaders.Contains(header.Key.ToLower()); + } + } +} diff --git a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs index 4f3151d97..2bdbec9f8 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs @@ -27,7 +27,7 @@ public DownstreamRequestInitialiserMiddleware(OcelotRequestDelegate next, public async Task Invoke(DownstreamContext context) { - var downstreamRequest = await _requestMapper.Map(context.HttpContext.Request); + var downstreamRequest = _requestMapper.Map(context.HttpContext.Request); if (downstreamRequest.IsError) { diff --git a/test/Ocelot.AcceptanceTests/ContentTests.cs b/test/Ocelot.AcceptanceTests/ContentTests.cs new file mode 100644 index 000000000..d545392b1 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ContentTests.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class ContentTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private string _contentType; + private long? _contentLength; + private bool _contentTypeHeaderExists; + + public ContentTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_not_add_content_type_or_content_length_headers() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51339, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51339", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => ThenTheContentTypeShouldBeEmpty()) + .And(x => ThenTheContentLengthShouldBeEmpty()) + .BDDfy(); + } + + [Fact] + public void should_add_content_type_and_content_length_headers() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51349, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + } + } + }; + + var contentType = "application/json"; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51349", "/", 201, string.Empty)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .And(x => _steps.GivenThePostHasContentType(contentType)) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) + .And(x => ThenTheContentLengthIs(11)) + .And(x => ThenTheContentTypeIsIs(contentType)) + .BDDfy(); + } + + [Fact] + public void should_add_default_content_type_header() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51359, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + } + } + }; + + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51359", "/", 201, string.Empty)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) + .And(x => ThenTheContentLengthIs(11)) + .And(x => ThenTheContentTypeIsIs("text/plain; charset=utf-8")) + .BDDfy(); + } + + private void ThenTheContentTypeIsIs(string expected) + { + _contentType.ShouldBe(expected); + } + + private void ThenTheContentLengthShouldBeEmpty() + { + _contentLength.ShouldBeNull(); + } + + private void ThenTheContentLengthIs(int expected) + { + _contentLength.ShouldBe(expected); + } + + private void ThenTheContentTypeShouldBeEmpty() + { + _contentType.ShouldBeNullOrEmpty(); + _contentTypeHeaderExists.ShouldBe(false); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + _contentType = context.Request.ContentType; + _contentLength = context.Request.ContentLength; + _contentTypeHeaderExists = context.Request.Headers.TryGetValue("Content-Type", out var value); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _builder.Start(); + } + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 62d1c8163..66fcef2ef 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -68,9 +68,9 @@ public async Task StartFakeOcelotWithWebSockets() { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + 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(); }) .ConfigureLogging((hostingContext, logging) => @@ -126,9 +126,9 @@ public void GivenOcelotIsRunning() { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + 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 => @@ -154,9 +154,9 @@ internal void GivenOcelotIsRunningUsingButterfly(string butterflyUrl) { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -197,9 +197,9 @@ public void GivenOcelotIsRunningWithMiddleareBeforePipeline(Func @@ -228,9 +228,9 @@ public void GivenOcelotIsRunningWithSpecficHandlersRegisteredInDi() { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -261,9 +261,9 @@ public void GivenOcelotIsRunningWithSpecficAggregatorsRegisteredInDi @@ -294,9 +294,9 @@ public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi() { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -326,9 +326,9 @@ public void GivenOcelotIsRunningWithGlobalHandlerRegisteredInDi() { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -357,9 +357,9 @@ public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi(FakeDepen { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -396,9 +396,9 @@ public void GivenOcelotIsRunning(Action opt { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -444,9 +444,9 @@ public void GivenOcelotIsRunningUsingJsonSerializedCache() { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + 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 => @@ -481,9 +481,9 @@ public void GivenOcelotIsRunningUsingConsulToStoreConfig() { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -509,9 +509,9 @@ public void GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache() { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -570,8 +570,8 @@ public void GivenOcelotIsRunning(OcelotPipelineConfiguration ocelotPipelineConfi { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile("ocelot.json") + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile("ocelot.json", false, false) .AddEnvironmentVariables(); var configuration = builder.Build(); @@ -823,6 +823,11 @@ public void GivenThePostHasContent(string postcontent) _postContent = new StringContent(postcontent); } + public void GivenThePostHasContentType(string postcontent) + { + _postContent.Headers.ContentType = new MediaTypeHeaderValue(postcontent); + } + public void GivenThePostHasGzipContent(object input) { var json = JsonConvert.SerializeObject(input); diff --git a/test/Ocelot.AcceptanceTests/WebSocketTests.cs b/test/Ocelot.AcceptanceTests/WebSocketTests.cs index 667fb5fda..a2ff84715 100644 --- a/test/Ocelot.AcceptanceTests/WebSocketTests.cs +++ b/test/Ocelot.AcceptanceTests/WebSocketTests.cs @@ -341,8 +341,8 @@ private async Task StartFakeDownstreamService(string url, string path) { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureLogging((hostingContext, logging) => @@ -387,8 +387,8 @@ private async Task StartSecondFakeDownstreamService(string url, string path) { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureLogging((hostingContext, logging) => diff --git a/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs b/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs index 053ac7ea9..8db90a245 100644 --- a/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs +++ b/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs @@ -1,157 +1,157 @@ -using System; -using System.Collections.Generic; -using System.IO; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Configs; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Middleware; -using Ocelot.DependencyInjection; -using System.Net.Http; -using System.Threading.Tasks; -using BenchmarkDotNet.Attributes.Jobs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Validators; - -namespace Ocelot.Benchmarks -{ - [Config(typeof(AllTheThingsBenchmarks))] - public class AllTheThingsBenchmarks : ManualConfig - { - private IWebHost _service; - private IWebHost _ocelot; - private HttpClient _httpClient; - - public AllTheThingsBenchmarks() - { - Add(StatisticColumn.AllStatistics); - Add(MemoryDiagnoser.Default); - Add(BaselineValidator.FailOnError); - } - - [GlobalSetup] - public void SetUp() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51879, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 201, string.Empty); - GivenThereIsAConfiguration(configuration); - GivenOcelotIsRunning("http://localhost:5000"); - - _httpClient = new HttpClient(); - } - - [Benchmark(Baseline = true)] - public async Task Baseline() - { - var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5000/"); - var response = await _httpClient.SendAsync(request); - response.EnsureSuccessStatusCode(); - } - -/* * Summary* - BenchmarkDotNet = v0.10.13, OS = macOS 10.12.6 (16G1212) [Darwin 16.7.0] - Intel Core i5-4278U CPU 2.60GHz(Haswell), 1 CPU, 4 logical cores and 2 physical cores - .NET Core SDK = 2.1.4 - - [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT - DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT - Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated | - --------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:| - Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB |*/ - - private void GivenOcelotIsRunning(string url) - { - _ocelot = new WebHostBuilder() - .UseKestrel() - .UseUrls(url) - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config - .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) - .AddJsonFile("appsettings.json", true, true) - .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("ocelot.json") - .AddEnvironmentVariables(); - }) - .ConfigureServices(s => { - s.AddOcelot(); - }) - .ConfigureLogging((hostingContext, logging) => - { - logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - }) - .UseIISIntegration() - .Configure(app => - { - app.UseOcelot().Wait(); - }) - .Build(); - - _ocelot.Start(); - } - - public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = Path.Combine(AppContext.BaseDirectory, "ocelot.json"); - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - } - - private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) - { - _service = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _service.Start(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Middleware; +using Ocelot.DependencyInjection; +using System.Net.Http; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes.Jobs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Validators; + +namespace Ocelot.Benchmarks +{ + [Config(typeof(AllTheThingsBenchmarks))] + public class AllTheThingsBenchmarks : ManualConfig + { + private IWebHost _service; + private IWebHost _ocelot; + private HttpClient _httpClient; + + public AllTheThingsBenchmarks() + { + Add(StatisticColumn.AllStatistics); + Add(MemoryDiagnoser.Default); + Add(BaselineValidator.FailOnError); + } + + [GlobalSetup] + public void SetUp() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 201, string.Empty); + GivenThereIsAConfiguration(configuration); + GivenOcelotIsRunning("http://localhost:5000"); + + _httpClient = new HttpClient(); + } + + [Benchmark(Baseline = true)] + public async Task Baseline() + { + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5000/"); + var response = await _httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + } + +/* * Summary* + BenchmarkDotNet = v0.10.13, OS = macOS 10.12.6 (16G1212) [Darwin 16.7.0] + Intel Core i5-4278U CPU 2.60GHz(Haswell), 1 CPU, 4 logical cores and 2 physical cores + .NET Core SDK = 2.1.4 + + [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated | + --------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:| + Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB |*/ + + private void GivenOcelotIsRunning(string url) + { + _ocelot = new WebHostBuilder() + .UseKestrel() + .UseUrls(url) + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddJsonFile("ocelot.json", false, false) + .AddEnvironmentVariables(); + }) + .ConfigureServices(s => { + s.AddOcelot(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + }) + .UseIISIntegration() + .Configure(app => + { + app.UseOcelot().Wait(); + }) + .Build(); + + _ocelot.Start(); + } + + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = Path.Combine(AppContext.BaseDirectory, "ocelot.json"); + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + { + _service = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _service.Start(); + } + } +} diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 44a89929d..94713fbf8 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -536,9 +536,9 @@ private void GivenAnotherOcelotIsRunning(string baseUrl) { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + 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(x => @@ -660,9 +660,9 @@ private void GivenOcelotIsRunningWithIdentityServerSettings(Action { @@ -693,9 +693,9 @@ private void GivenOcelotIsRunning() { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + 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(x => @@ -733,9 +733,9 @@ private void GivenOcelotIsRunningWithNoWebHostBuilder(string baseUrl) { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + 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(x => { diff --git a/test/Ocelot.IntegrationTests/RaftTests.cs b/test/Ocelot.IntegrationTests/RaftTests.cs index 7afa3ae4e..df550a0d5 100644 --- a/test/Ocelot.IntegrationTests/RaftTests.cs +++ b/test/Ocelot.IntegrationTests/RaftTests.cs @@ -446,10 +446,10 @@ private void GivenAServerIsRunning(string url) { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); - config.AddJsonFile("peers.json", optional: true, reloadOnChange: true); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", false, false); + config.AddJsonFile("peers.json", optional: true, reloadOnChange: false); #pragma warning disable CS0618 config.AddOcelotBaseUrl(url); #pragma warning restore CS0618 diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index ed125aa46..61e9c17fa 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -1,218 +1,218 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using Microsoft.AspNetCore.Http; -using System.Threading.Tasks; -using System.Collections.Concurrent; -using CacheManager.Core; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; - -namespace Ocelot.IntegrationTests -{ - public class ThreadSafeHeadersTests : IDisposable - { - private readonly HttpClient _httpClient; - private IWebHost _builder; - private IWebHostBuilder _webHostBuilder; - private readonly string _ocelotBaseUrl; - private IWebHost _downstreamBuilder; - private readonly Random _random; - private readonly ConcurrentBag _results; - - public ThreadSafeHeadersTests() - { - _results = new ConcurrentBag(); - _random = new Random(); - _httpClient = new HttpClient(); - _ocelotBaseUrl = "http://localhost:5001"; - _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); - } - - [Fact] - public void should_return_same_response_for_each_different_header_under_load_to_downsteam_service() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51879, - } - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) - .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300)) - .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string url) - { - _downstreamBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - var header = context.Request.Headers["ThreadSafeHeadersTest"]; - - context.Response.StatusCode = 200; - await context.Response.WriteAsync(header[0]); - }); - }) - .Build(); - - _downstreamBuilder.Start(); - } - - private void GivenOcelotIsRunning() - { - _webHostBuilder = new WebHostBuilder() - .UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - Action settings = (s) => - { - s.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); - }; - - x.AddOcelot() - .AddCacheManager(settings) - .AddAdministration("/administration", "secret"); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _builder = _webHostBuilder.Build(); - - _builder.Start(); - } - - private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - var text = File.ReadAllText(configurationPath); - - configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - text = File.ReadAllText(configurationPath); - } - - private void WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues(string url, int times) - { - var tasks = new Task[times]; - - for (int i = 0; i < times; i++) - { - var urlCopy = url; - var random = _random.Next(0, 50); - tasks[i] = GetForThreadSafeHeadersTest(urlCopy, random); - } - - Task.WaitAll(tasks); - } - - private async Task GetForThreadSafeHeadersTest(string url, int random) - { - var request = new HttpRequestMessage(HttpMethod.Get, url); - request.Headers.Add("ThreadSafeHeadersTest", new List { random.ToString() }); - var response = await _httpClient.SendAsync(request); - var content = await response.Content.ReadAsStringAsync(); - int result = int.Parse(content); - var tshtr = new ThreadSafeHeadersTestResult(result, random); - _results.Add(tshtr); - } - - private void ThenTheSameHeaderValuesAreReturnedByTheDownstreamService() - { - foreach(var result in _results) - { - result.Result.ShouldBe(result.Random); - } - } - - public void Dispose() - { - _builder?.Dispose(); - _httpClient?.Dispose(); - _downstreamBuilder?.Dispose(); - } - - class ThreadSafeHeadersTestResult - { - public ThreadSafeHeadersTestResult(int result, int random) - { - Result = result; - Random = random; - } - - public int Result { get; private set; } - public int Random { get; private set; } - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using Microsoft.AspNetCore.Http; +using System.Threading.Tasks; +using System.Collections.Concurrent; +using CacheManager.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; + +namespace Ocelot.IntegrationTests +{ + public class ThreadSafeHeadersTests : IDisposable + { + private readonly HttpClient _httpClient; + private IWebHost _builder; + private IWebHostBuilder _webHostBuilder; + private readonly string _ocelotBaseUrl; + private IWebHost _downstreamBuilder; + private readonly Random _random; + private readonly ConcurrentBag _results; + + public ThreadSafeHeadersTests() + { + _results = new ConcurrentBag(); + _random = new Random(); + _httpClient = new HttpClient(); + _ocelotBaseUrl = "http://localhost:5001"; + _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); + } + + [Fact] + public void should_return_same_response_for_each_different_header_under_load_to_downsteam_service() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300)) + .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url) + { + _downstreamBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + var header = context.Request.Headers["ThreadSafeHeadersTest"]; + + context.Response.StatusCode = 200; + await context.Response.WriteAsync(header[0]); + }); + }) + .Build(); + + _downstreamBuilder.Start(); + } + + private void GivenOcelotIsRunning() + { + _webHostBuilder = new WebHostBuilder() + .UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .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(x => + { + Action settings = (s) => + { + s.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithDictionaryHandle(); + }; + + x.AddOcelot() + .AddCacheManager(settings) + .AddAdministration("/administration", "secret"); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _builder = _webHostBuilder.Build(); + + _builder.Start(); + } + + private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + var text = File.ReadAllText(configurationPath); + + configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + text = File.ReadAllText(configurationPath); + } + + private void WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues(string url, int times) + { + var tasks = new Task[times]; + + for (int i = 0; i < times; i++) + { + var urlCopy = url; + var random = _random.Next(0, 50); + tasks[i] = GetForThreadSafeHeadersTest(urlCopy, random); + } + + Task.WaitAll(tasks); + } + + private async Task GetForThreadSafeHeadersTest(string url, int random) + { + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Add("ThreadSafeHeadersTest", new List { random.ToString() }); + var response = await _httpClient.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + int result = int.Parse(content); + var tshtr = new ThreadSafeHeadersTestResult(result, random); + _results.Add(tshtr); + } + + private void ThenTheSameHeaderValuesAreReturnedByTheDownstreamService() + { + foreach(var result in _results) + { + result.Result.ShouldBe(result.Random); + } + } + + public void Dispose() + { + _builder?.Dispose(); + _httpClient?.Dispose(); + _downstreamBuilder?.Dispose(); + } + + class ThreadSafeHeadersTestResult + { + public ThreadSafeHeadersTestResult(int result, int random) + { + Result = result; + Random = random; + } + + public int Result { get; private set; } + public int Random { get; private set; } + } + } +} diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index fede584f6..865c11e03 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -26,7 +26,7 @@ public static void Main(string[] args) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("ocelot.json") + .AddJsonFile("ocelot.json", false, false) .AddEnvironmentVariables(); }) .ConfigureServices(s => { diff --git a/test/Ocelot.ManualTest/ocelot.json b/test/Ocelot.ManualTest/ocelot.json index f53ef5074..322c607b1 100644 --- a/test/Ocelot.ManualTest/ocelot.json +++ b/test/Ocelot.ManualTest/ocelot.json @@ -8,7 +8,7 @@ "DownstreamHostAndPorts": [ { "Host": "localhost", - "Port": 3000 + "Port": 5001 } ], "QoSOptions": { diff --git a/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs index d571a8b72..9c5fb9777 100644 --- a/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs @@ -95,7 +95,7 @@ private void GivenTheMapperWillReturnAMappedRequest() _requestMapper .Setup(rm => rm.Map(It.IsAny())) - .ReturnsAsync(_mappedRequest); + .Returns(_mappedRequest); } private void GivenTheMapperWillReturnAnError() @@ -104,7 +104,7 @@ private void GivenTheMapperWillReturnAnError() _requestMapper .Setup(rm => rm.Map(It.IsAny())) - .ReturnsAsync(_mappedRequest); + .Returns(_mappedRequest); } private void WhenTheMiddlewareIsInvoked() diff --git a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs index 22ff9276d..17e35b644 100644 --- a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs +++ b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs @@ -1,407 +1,441 @@ -namespace Ocelot.UnitTests.Request.Mapper -{ - using System.Collections.Generic; - using System.Linq; - using System.Net.Http; - - using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Http.Internal; - using Microsoft.Extensions.Primitives; - using Ocelot.Request.Mapper; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - using Shouldly; - using System; - using System.IO; - using System.Text; - using System.Security.Cryptography; - - public class RequestMapperTests - { - readonly HttpRequest _inputRequest; - - readonly RequestMapper _requestMapper; - - Response _mappedRequest; - - List> _inputHeaders = null; - - public RequestMapperTests() - { - _inputRequest = new DefaultHttpRequest(new DefaultHttpContext()); - - _requestMapper = new RequestMapper(); - } - - [Theory] - [InlineData("https", "my.url:123", "/abc/DEF", "?a=1&b=2", "https://my.url:123/abc/DEF?a=1&b=2")] - [InlineData("http", "blah.com", "/d ef", "?abc=123", "http://blah.com/d%20ef?abc=123")] // note! the input is encoded when building the input request - [InlineData("http", "myusername:mypassword@abc.co.uk", null, null, "http://myusername:mypassword@abc.co.uk/")] - [InlineData("http", "點看.com", null, null, "http://xn--c1yn36f.com/")] - [InlineData("http", "xn--c1yn36f.com", null, null, "http://xn--c1yn36f.com/")] - public void Should_map_valid_request_uri(string scheme, string host, string path, string queryString, string expectedUri) - { - this.Given(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasScheme(scheme)) - .And(_ => GivenTheInputRequestHasHost(host)) - .And(_ => GivenTheInputRequestHasPath(path)) - .And(_ => GivenTheInputRequestHasQueryString(queryString)) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasUri(expectedUri)) - .BDDfy(); - } - - [Theory] - [InlineData("ftp", "google.com", "/abc/DEF", "?a=1&b=2")] - public void Should_error_on_unsupported_request_uri(string scheme, string host, string path, string queryString) - { - this.Given(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasScheme(scheme)) - .And(_ => GivenTheInputRequestHasHost(host)) - .And(_ => GivenTheInputRequestHasPath(path)) - .And(_ => GivenTheInputRequestHasQueryString(queryString)) - .When(_ => WhenMapped()) - .Then(_ => ThenAnErrorIsReturned()) - .And(_ => ThenTheMappedRequestIsNull()) - .BDDfy(); - } - - [Theory] - [InlineData("GET")] - [InlineData("POST")] - [InlineData("WHATEVER")] - public void Should_map_method(string method) - { - this.Given(_ => GivenTheInputRequestHasMethod(method)) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasMethod(method)) - .BDDfy(); - } - - [Fact] - public void Should_map_all_headers() - { - this.Given(_ => GivenTheInputRequestHasHeaders()) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasEachHeader()) - .BDDfy(); - } - - [Fact] - public void Should_handle_no_headers() - { - this.Given(_ => GivenTheInputRequestHasNoHeaders()) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasNoHeaders()) - .BDDfy(); - } - - [Fact] - public void Should_map_content() - { - this.Given(_ => GivenTheInputRequestHasContent("This is my content")) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasContent("This is my content")) - .BDDfy(); - } - - [Fact] - public void Should_handle_no_content() - { - this.Given(_ => GivenTheInputRequestHasNoContent()) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasNoContent()) - .BDDfy(); - } - - [Fact] - public void Should_map_content_headers() - { - byte[] md5bytes = new byte[0]; - using (var md5 = MD5.Create()) - { - md5bytes = md5.ComputeHash(Encoding.UTF8.GetBytes("some md5")); - } - - this.Given(_ => GivenTheInputRequestHasContent("This is my content")) - .And(_ => GivenTheContentTypeIs("application/json")) - .And(_ => GivenTheContentEncodingIs("gzip, compress")) - .And(_ => GivenTheContentLanguageIs("english")) - .And(_ => GivenTheContentLocationIs("/my-receipts/38")) - .And(_ => GivenTheContentRangeIs("bytes 1-2/*")) - .And(_ => GivenTheContentDispositionIs("inline")) - .And(_ => GivenTheContentMD5Is(md5bytes)) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json")) - .And(_ => ThenTheMappedRequestHasContentEncodingHeader("gzip", "compress")) - .And(_ => ThenTheMappedRequestHasContentLanguageHeader("english")) - .And(_ => ThenTheMappedRequestHasContentLocationHeader("/my-receipts/38")) - .And(_ => ThenTheMappedRequestHasContentMD5Header(md5bytes)) - .And(_ => ThenTheMappedRequestHasContentRangeHeader()) - .And(_ => ThenTheMappedRequestHasContentDispositionHeader("inline")) - .And(_ => ThenTheMappedRequestHasContentSize("This is my content".Length)) - .And(_ => ThenTheContentHeadersAreNotAddedToNonContentHeaders()) - .BDDfy(); - } - - [Fact] - public void should_not_add_content_headers() - { - this.Given(_ => GivenTheInputRequestHasContent("This is my content")) - .And(_ => GivenTheContentTypeIs("application/json")) - .And(_ => GivenTheInputRequestHasMethod("POST")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json")) - .And(_ => ThenTheMappedRequestHasContentSize("This is my content".Length)) - .And(_ => ThenTheOtherContentTypeHeadersAreNotMapped()) - .BDDfy(); - } - - private void ThenTheContentHeadersAreNotAddedToNonContentHeaders() - { - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Disposition"); - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentMD5"); - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentRange"); - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentLanguage"); - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentEncoding"); - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentLocation"); - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Length"); - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Type"); - } - - private void ThenTheOtherContentTypeHeadersAreNotMapped() - { - _mappedRequest.Data.Content.Headers.ContentDisposition.ShouldBeNull(); - _mappedRequest.Data.Content.Headers.ContentMD5.ShouldBeNull(); - _mappedRequest.Data.Content.Headers.ContentRange.ShouldBeNull(); - _mappedRequest.Data.Content.Headers.ContentLanguage.ShouldBeEmpty(); - _mappedRequest.Data.Content.Headers.ContentEncoding.ShouldBeEmpty(); - _mappedRequest.Data.Content.Headers.ContentLocation.ShouldBeNull(); - } - - private void ThenTheMappedRequestHasContentDispositionHeader(string expected) - { - _mappedRequest.Data.Content.Headers.ContentDisposition.DispositionType.ShouldBe(expected); - } - - private void GivenTheContentDispositionIs(string input) - { - _inputRequest.Headers.Add("Content-Disposition", input); - } - - private void ThenTheMappedRequestHasContentMD5Header(byte[] expected) - { - _mappedRequest.Data.Content.Headers.ContentMD5.ShouldBe(expected); - } - - private void GivenTheContentMD5Is(byte[] input) - { - var base64 = Convert.ToBase64String(input); - _inputRequest.Headers.Add("Content-MD5", base64); - } - - private void ThenTheMappedRequestHasContentRangeHeader() - { - _mappedRequest.Data.Content.Headers.ContentRange.From.ShouldBe(1); - _mappedRequest.Data.Content.Headers.ContentRange.To.ShouldBe(2); - } - - private void GivenTheContentRangeIs(string input) - { - _inputRequest.Headers.Add("Content-Range", input); - } - - private void ThenTheMappedRequestHasContentLocationHeader(string expected) - { - _mappedRequest.Data.Content.Headers.ContentLocation.OriginalString.ShouldBe(expected); - } - - private void GivenTheContentLocationIs(string input) - { - _inputRequest.Headers.Add("Content-Location", input); - } - - private void ThenTheMappedRequestHasContentLanguageHeader(string expected) - { - _mappedRequest.Data.Content.Headers.ContentLanguage.First().ShouldBe(expected); - } - - private void GivenTheContentLanguageIs(string input) - { - _inputRequest.Headers.Add("Content-Language", input); - } - - private void ThenTheMappedRequestHasContentEncodingHeader(string expected, string expectedTwo) - { - _mappedRequest.Data.Content.Headers.ContentEncoding.ToArray()[0].ShouldBe(expected); - _mappedRequest.Data.Content.Headers.ContentEncoding.ToArray()[1].ShouldBe(expectedTwo); - } - - private void GivenTheContentEncodingIs(string input) - { - _inputRequest.Headers.Add("Content-Encoding", input); - } - - private void GivenTheContentTypeIs(string contentType) - { - _inputRequest.ContentType = contentType; - } - - private void ThenTheMappedRequestHasContentTypeHeader(string expected) - { - _mappedRequest.Data.Content.Headers.ContentType.MediaType.ShouldBe(expected); - } - - private void ThenTheMappedRequestHasContentSize(long expected) - { - _mappedRequest.Data.Content.Headers.ContentLength.ShouldBe(expected); - } - - private void GivenTheInputRequestHasMethod(string method) - { - _inputRequest.Method = method; - } - - private void GivenTheInputRequestHasScheme(string scheme) - { - _inputRequest.Scheme = scheme; - } - - private void GivenTheInputRequestHasHost(string host) - { - _inputRequest.Host = new HostString(host); - } - - private void GivenTheInputRequestHasPath(string path) - { - if (path != null) - { - _inputRequest.Path = path; - } - } - - private void GivenTheInputRequestHasQueryString(string querystring) - { - if (querystring != null) - { - _inputRequest.QueryString = new QueryString(querystring); - } - } - - private void GivenTheInputRequestHasAValidUri() - { - GivenTheInputRequestHasScheme("http"); - GivenTheInputRequestHasHost("www.google.com"); - } - - private void GivenTheInputRequestHasHeaders() - { - _inputHeaders = new List>() - { - new KeyValuePair("abc", new StringValues(new string[]{"123","456" })), - new KeyValuePair("def", new StringValues(new string[]{"789","012" })), - }; - - foreach (var inputHeader in _inputHeaders) - { - _inputRequest.Headers.Add(inputHeader); - } - } - - private void GivenTheInputRequestHasNoHeaders() - { - _inputRequest.Headers.Clear(); - } - - private void GivenTheInputRequestHasContent(string content) - { - _inputRequest.Body = new MemoryStream(Encoding.UTF8.GetBytes(content)); - } - - private void GivenTheInputRequestHasNoContent() - { - _inputRequest.Body = null; - } - - private void WhenMapped() - { - _mappedRequest = _requestMapper.Map(_inputRequest).GetAwaiter().GetResult(); - } - - private void ThenNoErrorIsReturned() - { - _mappedRequest.IsError.ShouldBeFalse(); - } - - private void ThenAnErrorIsReturned() - { - _mappedRequest.IsError.ShouldBeTrue(); - } - - private void ThenTheMappedRequestHasUri(string expectedUri) - { - _mappedRequest.Data.RequestUri.OriginalString.ShouldBe(expectedUri); - } - - private void ThenTheMappedRequestHasMethod(string expectedMethod) - { - _mappedRequest.Data.Method.ToString().ShouldBe(expectedMethod); - } - - private void ThenTheMappedRequestHasEachHeader() - { - _mappedRequest.Data.Headers.Count().ShouldBe(_inputHeaders.Count); - foreach(var header in _mappedRequest.Data.Headers) - { - var inputHeader = _inputHeaders.First(h => h.Key == header.Key); - inputHeader.ShouldNotBeNull(); - inputHeader.Value.Count().ShouldBe(header.Value.Count()); - foreach(var inputHeaderValue in inputHeader.Value) - { - header.Value.Any(v => v == inputHeaderValue); - } - } - } - - private void ThenTheMappedRequestHasNoHeaders() - { - _mappedRequest.Data.Headers.Count().ShouldBe(0); - } - - private void ThenTheMappedRequestHasContent(string expectedContent) - { - _mappedRequest.Data.Content.ReadAsStringAsync().GetAwaiter().GetResult().ShouldBe(expectedContent); - } - - private void ThenTheMappedRequestHasNoContent() - { - _mappedRequest.Data.Content.ShouldBeNull(); - } - - private void ThenTheMappedRequestIsNull() - { - _mappedRequest.Data.ShouldBeNull(); - } - } -} +namespace Ocelot.UnitTests.Request.Mapper +{ + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Internal; + using Microsoft.Extensions.Primitives; + using Ocelot.Request.Mapper; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + using Shouldly; + using System; + using System.IO; + using System.Text; + using System.Security.Cryptography; + + public class RequestMapperTests + { + readonly HttpRequest _inputRequest; + + readonly RequestMapper _requestMapper; + + Response _mappedRequest; + + List> _inputHeaders = null; + + public RequestMapperTests() + { + _inputRequest = new DefaultHttpRequest(new DefaultHttpContext()); + + _requestMapper = new RequestMapper(); + } + + [Theory] + [InlineData("https", "my.url:123", "/abc/DEF", "?a=1&b=2", "https://my.url:123/abc/DEF?a=1&b=2")] + [InlineData("http", "blah.com", "/d ef", "?abc=123", "http://blah.com/d%20ef?abc=123")] // note! the input is encoded when building the input request + [InlineData("http", "myusername:mypassword@abc.co.uk", null, null, "http://myusername:mypassword@abc.co.uk/")] + [InlineData("http", "點看.com", null, null, "http://xn--c1yn36f.com/")] + [InlineData("http", "xn--c1yn36f.com", null, null, "http://xn--c1yn36f.com/")] + public void Should_map_valid_request_uri(string scheme, string host, string path, string queryString, string expectedUri) + { + this.Given(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasScheme(scheme)) + .And(_ => GivenTheInputRequestHasHost(host)) + .And(_ => GivenTheInputRequestHasPath(path)) + .And(_ => GivenTheInputRequestHasQueryString(queryString)) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasUri(expectedUri)) + .BDDfy(); + } + + [Theory] + [InlineData("ftp", "google.com", "/abc/DEF", "?a=1&b=2")] + public void Should_error_on_unsupported_request_uri(string scheme, string host, string path, string queryString) + { + this.Given(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasScheme(scheme)) + .And(_ => GivenTheInputRequestHasHost(host)) + .And(_ => GivenTheInputRequestHasPath(path)) + .And(_ => GivenTheInputRequestHasQueryString(queryString)) + .When(_ => WhenMapped()) + .Then(_ => ThenAnErrorIsReturned()) + .And(_ => ThenTheMappedRequestIsNull()) + .BDDfy(); + } + + [Theory] + [InlineData("GET")] + [InlineData("POST")] + [InlineData("WHATEVER")] + public void Should_map_method(string method) + { + this.Given(_ => GivenTheInputRequestHasMethod(method)) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasMethod(method)) + .BDDfy(); + } + + [Fact] + public void Should_map_all_headers() + { + this.Given(_ => GivenTheInputRequestHasHeaders()) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasEachHeader()) + .BDDfy(); + } + + [Fact] + public void Should_handle_no_headers() + { + this.Given(_ => GivenTheInputRequestHasNoHeaders()) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasNoHeaders()) + .BDDfy(); + } + + [Fact] + public void Should_map_content() + { + this.Given(_ => GivenTheInputRequestHasContent("This is my content")) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasContent("This is my content")) + .BDDfy(); + } + + [Fact] + public void Should_handle_no_content() + { + this.Given(_ => GivenTheInputRequestHasNullContent()) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasNoContent()) + .BDDfy(); + } + + [Fact] + public void Should_handle_no_content_type() + { + this.Given(_ => GivenTheInputRequestHasNoContentType()) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasNoContent()) + .BDDfy(); + } + + [Fact] + public void Should_handle_no_content_length() + { + this.Given(_ => GivenTheInputRequestHasNoContentLength()) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasNoContent()) + .BDDfy(); + } + + private void GivenTheInputRequestHasNoContentLength() + { + _inputRequest.ContentLength = null; + } + + private void GivenTheInputRequestHasNoContentType() + { + _inputRequest.ContentType = null; + } + + [Fact] + public void Should_map_content_headers() + { + byte[] md5bytes = new byte[0]; + using (var md5 = MD5.Create()) + { + md5bytes = md5.ComputeHash(Encoding.UTF8.GetBytes("some md5")); + } + + this.Given(_ => GivenTheInputRequestHasContent("This is my content")) + .And(_ => GivenTheContentTypeIs("application/json")) + .And(_ => GivenTheContentEncodingIs("gzip, compress")) + .And(_ => GivenTheContentLanguageIs("english")) + .And(_ => GivenTheContentLocationIs("/my-receipts/38")) + .And(_ => GivenTheContentRangeIs("bytes 1-2/*")) + .And(_ => GivenTheContentDispositionIs("inline")) + .And(_ => GivenTheContentMD5Is(md5bytes)) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json")) + .And(_ => ThenTheMappedRequestHasContentEncodingHeader("gzip", "compress")) + .And(_ => ThenTheMappedRequestHasContentLanguageHeader("english")) + .And(_ => ThenTheMappedRequestHasContentLocationHeader("/my-receipts/38")) + .And(_ => ThenTheMappedRequestHasContentMD5Header(md5bytes)) + .And(_ => ThenTheMappedRequestHasContentRangeHeader()) + .And(_ => ThenTheMappedRequestHasContentDispositionHeader("inline")) + .And(_ => ThenTheMappedRequestHasContentSize("This is my content".Length)) + .And(_ => ThenTheContentHeadersAreNotAddedToNonContentHeaders()) + .BDDfy(); + } + + [Fact] + public void should_not_add_content_headers() + { + this.Given(_ => GivenTheInputRequestHasContent("This is my content")) + .And(_ => GivenTheContentTypeIs("application/json")) + .And(_ => GivenTheInputRequestHasMethod("POST")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json")) + .And(_ => ThenTheMappedRequestHasContentSize("This is my content".Length)) + .And(_ => ThenTheOtherContentTypeHeadersAreNotMapped()) + .BDDfy(); + } + + private void ThenTheContentHeadersAreNotAddedToNonContentHeaders() + { + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Disposition"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentMD5"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentRange"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentLanguage"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentEncoding"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentLocation"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Length"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Type"); + } + + private void ThenTheOtherContentTypeHeadersAreNotMapped() + { + _mappedRequest.Data.Content.Headers.ContentDisposition.ShouldBeNull(); + _mappedRequest.Data.Content.Headers.ContentMD5.ShouldBeNull(); + _mappedRequest.Data.Content.Headers.ContentRange.ShouldBeNull(); + _mappedRequest.Data.Content.Headers.ContentLanguage.ShouldBeEmpty(); + _mappedRequest.Data.Content.Headers.ContentEncoding.ShouldBeEmpty(); + _mappedRequest.Data.Content.Headers.ContentLocation.ShouldBeNull(); + } + + private void ThenTheMappedRequestHasContentDispositionHeader(string expected) + { + _mappedRequest.Data.Content.Headers.ContentDisposition.DispositionType.ShouldBe(expected); + } + + private void GivenTheContentDispositionIs(string input) + { + _inputRequest.Headers.Add("Content-Disposition", input); + } + + private void ThenTheMappedRequestHasContentMD5Header(byte[] expected) + { + _mappedRequest.Data.Content.Headers.ContentMD5.ShouldBe(expected); + } + + private void GivenTheContentMD5Is(byte[] input) + { + var base64 = Convert.ToBase64String(input); + _inputRequest.Headers.Add("Content-MD5", base64); + } + + private void ThenTheMappedRequestHasContentRangeHeader() + { + _mappedRequest.Data.Content.Headers.ContentRange.From.ShouldBe(1); + _mappedRequest.Data.Content.Headers.ContentRange.To.ShouldBe(2); + } + + private void GivenTheContentRangeIs(string input) + { + _inputRequest.Headers.Add("Content-Range", input); + } + + private void ThenTheMappedRequestHasContentLocationHeader(string expected) + { + _mappedRequest.Data.Content.Headers.ContentLocation.OriginalString.ShouldBe(expected); + } + + private void GivenTheContentLocationIs(string input) + { + _inputRequest.Headers.Add("Content-Location", input); + } + + private void ThenTheMappedRequestHasContentLanguageHeader(string expected) + { + _mappedRequest.Data.Content.Headers.ContentLanguage.First().ShouldBe(expected); + } + + private void GivenTheContentLanguageIs(string input) + { + _inputRequest.Headers.Add("Content-Language", input); + } + + private void ThenTheMappedRequestHasContentEncodingHeader(string expected, string expectedTwo) + { + _mappedRequest.Data.Content.Headers.ContentEncoding.ToArray()[0].ShouldBe(expected); + _mappedRequest.Data.Content.Headers.ContentEncoding.ToArray()[1].ShouldBe(expectedTwo); + } + + private void GivenTheContentEncodingIs(string input) + { + _inputRequest.Headers.Add("Content-Encoding", input); + } + + private void GivenTheContentTypeIs(string contentType) + { + _inputRequest.ContentType = contentType; + } + + private void ThenTheMappedRequestHasContentTypeHeader(string expected) + { + _mappedRequest.Data.Content.Headers.ContentType.MediaType.ShouldBe(expected); + } + + private void ThenTheMappedRequestHasContentSize(long expected) + { + _mappedRequest.Data.Content.Headers.ContentLength.ShouldBe(expected); + } + + private void GivenTheInputRequestHasMethod(string method) + { + _inputRequest.Method = method; + } + + private void GivenTheInputRequestHasScheme(string scheme) + { + _inputRequest.Scheme = scheme; + } + + private void GivenTheInputRequestHasHost(string host) + { + _inputRequest.Host = new HostString(host); + } + + private void GivenTheInputRequestHasPath(string path) + { + if (path != null) + { + _inputRequest.Path = path; + } + } + + private void GivenTheInputRequestHasQueryString(string querystring) + { + if (querystring != null) + { + _inputRequest.QueryString = new QueryString(querystring); + } + } + + private void GivenTheInputRequestHasAValidUri() + { + GivenTheInputRequestHasScheme("http"); + GivenTheInputRequestHasHost("www.google.com"); + } + + private void GivenTheInputRequestHasHeaders() + { + _inputHeaders = new List>() + { + new KeyValuePair("abc", new StringValues(new string[]{"123","456" })), + new KeyValuePair("def", new StringValues(new string[]{"789","012" })), + }; + + foreach (var inputHeader in _inputHeaders) + { + _inputRequest.Headers.Add(inputHeader); + } + } + + private void GivenTheInputRequestHasNoHeaders() + { + _inputRequest.Headers.Clear(); + } + + private void GivenTheInputRequestHasContent(string content) + { + _inputRequest.Body = new MemoryStream(Encoding.UTF8.GetBytes(content)); + } + + private void GivenTheInputRequestHasNullContent() + { + _inputRequest.Body = null; + } + + private void WhenMapped() + { + _mappedRequest = _requestMapper.Map(_inputRequest); + } + + private void ThenNoErrorIsReturned() + { + _mappedRequest.IsError.ShouldBeFalse(); + } + + private void ThenAnErrorIsReturned() + { + _mappedRequest.IsError.ShouldBeTrue(); + } + + private void ThenTheMappedRequestHasUri(string expectedUri) + { + _mappedRequest.Data.RequestUri.OriginalString.ShouldBe(expectedUri); + } + + private void ThenTheMappedRequestHasMethod(string expectedMethod) + { + _mappedRequest.Data.Method.ToString().ShouldBe(expectedMethod); + } + + private void ThenTheMappedRequestHasEachHeader() + { + _mappedRequest.Data.Headers.Count().ShouldBe(_inputHeaders.Count); + foreach(var header in _mappedRequest.Data.Headers) + { + var inputHeader = _inputHeaders.First(h => h.Key == header.Key); + inputHeader.ShouldNotBeNull(); + inputHeader.Value.Count().ShouldBe(header.Value.Count()); + foreach(var inputHeaderValue in inputHeader.Value) + { + header.Value.Any(v => v == inputHeaderValue); + } + } + } + + private void ThenTheMappedRequestHasNoHeaders() + { + _mappedRequest.Data.Headers.Count().ShouldBe(0); + } + + private void ThenTheMappedRequestHasContent(string expectedContent) + { + _mappedRequest.Data.Content.ReadAsStringAsync().GetAwaiter().GetResult().ShouldBe(expectedContent); + } + + private void ThenTheMappedRequestHasNoContent() + { + _mappedRequest.Data.Content.ShouldBeNull(); + } + + private void ThenTheMappedRequestIsNull() + { + _mappedRequest.Data.ShouldBeNull(); + } + } +}