diff --git a/src/Ocelot.Library/Infrastructure/Services/CannotAddDataError.cs b/src/Ocelot.Library/Infrastructure/Services/CannotAddDataError.cs new file mode 100644 index 00000000..6a05b321 --- /dev/null +++ b/src/Ocelot.Library/Infrastructure/Services/CannotAddDataError.cs @@ -0,0 +1,11 @@ +using Ocelot.Library.Infrastructure.Responses; + +namespace Ocelot.Library.Infrastructure.Services +{ + public class CannotAddDataError : Error + { + public CannotAddDataError(string message) : base(message) + { + } + } +} diff --git a/src/Ocelot.Library/Infrastructure/Services/CannotFindDataError.cs b/src/Ocelot.Library/Infrastructure/Services/CannotFindDataError.cs new file mode 100644 index 00000000..660df5b3 --- /dev/null +++ b/src/Ocelot.Library/Infrastructure/Services/CannotFindDataError.cs @@ -0,0 +1,11 @@ +using Ocelot.Library.Infrastructure.Responses; + +namespace Ocelot.Library.Infrastructure.Services +{ + public class CannotFindDataError : Error + { + public CannotFindDataError(string message) : base(message) + { + } + } +} diff --git a/src/Ocelot.Library/Infrastructure/Services/IRequestDataService.cs b/src/Ocelot.Library/Infrastructure/Services/IRequestDataService.cs new file mode 100644 index 00000000..5af72678 --- /dev/null +++ b/src/Ocelot.Library/Infrastructure/Services/IRequestDataService.cs @@ -0,0 +1,10 @@ +using Ocelot.Library.Infrastructure.Responses; + +namespace Ocelot.Library.Infrastructure.Services +{ + public interface IRequestDataService + { + Response Add(string key, T value); + Response Get(string key); + } +} \ No newline at end of file diff --git a/src/Ocelot.Library/Infrastructure/Services/RequestDataService.cs b/src/Ocelot.Library/Infrastructure/Services/RequestDataService.cs new file mode 100644 index 00000000..28cd07e3 --- /dev/null +++ b/src/Ocelot.Library/Infrastructure/Services/RequestDataService.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Ocelot.Library.Infrastructure.Responses; + +namespace Ocelot.Library.Infrastructure.Services +{ + public class RequestDataService : IRequestDataService + { + private readonly IHttpContextAccessor _httpContextAccessor; + + public RequestDataService(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public Response Add(string key, T value) + { + try + { + _httpContextAccessor.HttpContext.Items.Add(key, value); + return new OkResponse(); + } + catch (Exception exception) + { + return new ErrorResponse(new List + { + new CannotAddDataError(string.Format($"Unable to add data for key: {key}, exception: {exception.Message}")) + }); + } + } + + public Response Get(string key) + { + object obj; + + if(_httpContextAccessor.HttpContext.Items.TryGetValue(key, out obj)) + { + var data = (T) obj; + return new OkResponse(data); + } + + return new ErrorResponse(new List + { + new CannotFindDataError($"Unable to find data for key: {key}") + }); + } + } +} diff --git a/src/Ocelot.Library/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot.Library/Middleware/DownstreamRouteFinderMiddleware.cs index a4c4738a..1b17c124 100644 --- a/src/Ocelot.Library/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot.Library/Middleware/DownstreamRouteFinderMiddleware.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Ocelot.Library.Infrastructure.DownstreamRouteFinder; using Ocelot.Library.Infrastructure.Responder; +using Ocelot.Library.Infrastructure.Services; namespace Ocelot.Library.Middleware { @@ -10,14 +11,17 @@ namespace Ocelot.Library.Middleware private readonly RequestDelegate _next; private readonly IDownstreamRouteFinder _downstreamRouteFinder; private readonly IHttpResponder _responder; + private readonly IRequestDataService _requestDataService; public DownstreamRouteFinderMiddleware(RequestDelegate next, IDownstreamRouteFinder downstreamRouteFinder, - IHttpResponder responder) + IHttpResponder responder, + IRequestDataService requestDataService) { _next = next; _downstreamRouteFinder = downstreamRouteFinder; _responder = responder; + _requestDataService = requestDataService; } public async Task Invoke(HttpContext context) @@ -32,7 +36,7 @@ namespace Ocelot.Library.Middleware return; } - context.Items.Add("DownstreamRoute", downstreamRoute.Data); + _requestDataService.Add("DownstreamRoute", downstreamRoute.Data); await _next.Invoke(context); } diff --git a/src/Ocelot.Library/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot.Library/Middleware/DownstreamUrlCreatorMiddleware.cs index 30e41802..b8e04ef7 100644 --- a/src/Ocelot.Library/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot.Library/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -1,6 +1,8 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Ocelot.Library.Infrastructure.DownstreamRouteFinder; +using Ocelot.Library.Infrastructure.Responder; +using Ocelot.Library.Infrastructure.Services; using Ocelot.Library.Infrastructure.UrlTemplateReplacer; namespace Ocelot.Library.Middleware @@ -9,34 +11,35 @@ namespace Ocelot.Library.Middleware { private readonly RequestDelegate _next; private readonly IDownstreamUrlTemplateVariableReplacer _urlReplacer; + private readonly IRequestDataService _requestDataService; + private readonly IHttpResponder _responder; public DownstreamUrlCreatorMiddleware(RequestDelegate next, - IDownstreamUrlTemplateVariableReplacer urlReplacer) + IDownstreamUrlTemplateVariableReplacer urlReplacer, + IRequestDataService requestDataService, + IHttpResponder responder) { _next = next; _urlReplacer = urlReplacer; + _requestDataService = requestDataService; + _responder = responder; } public async Task Invoke(HttpContext context) { - var downstreamRoute = GetDownstreamRouteFromOwinItems(context); + var downstreamRoute = _requestDataService.Get("DownstreamRoute"); - var downstreamUrl = _urlReplacer.ReplaceTemplateVariables(downstreamRoute); - - context.Items.Add("DownstreamUrl", downstreamUrl); - - await _next.Invoke(context); - } - - private DownstreamRoute GetDownstreamRouteFromOwinItems(HttpContext context) - { - object obj; - DownstreamRoute downstreamRoute = null; - if (context.Items.TryGetValue("DownstreamRoute", out obj)) + if (downstreamRoute.IsError) { - downstreamRoute = (DownstreamRoute) obj; + await _responder.CreateNotFoundResponse(context); + return; } - return downstreamRoute; + + var downstreamUrl = _urlReplacer.ReplaceTemplateVariables(downstreamRoute.Data); + + _requestDataService.Add("DownstreamUrl", downstreamUrl); + + await _next.Invoke(context); } } } \ No newline at end of file diff --git a/src/Ocelot.Library/Middleware/HttpRequesterMiddleware.cs b/src/Ocelot.Library/Middleware/HttpRequesterMiddleware.cs index d2b4b1e1..f564bca1 100644 --- a/src/Ocelot.Library/Middleware/HttpRequesterMiddleware.cs +++ b/src/Ocelot.Library/Middleware/HttpRequesterMiddleware.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Ocelot.Library.Infrastructure.Requester; using Ocelot.Library.Infrastructure.Responder; +using Ocelot.Library.Infrastructure.Services; namespace Ocelot.Library.Middleware { @@ -10,38 +11,36 @@ namespace Ocelot.Library.Middleware private readonly RequestDelegate _next; private readonly IHttpRequester _requester; private readonly IHttpResponder _responder; + private readonly IRequestDataService _requestDataService; public HttpRequesterMiddleware(RequestDelegate next, IHttpRequester requester, - IHttpResponder responder) + IHttpResponder responder, + IRequestDataService requestDataService) { _next = next; _requester = requester; _responder = responder; + _requestDataService = requestDataService; } public async Task Invoke(HttpContext context) { - var downstreamUrl = GetDownstreamUrlFromOwinItems(context); + var downstreamUrl = _requestDataService.Get("DownstreamUrl"); + + if (downstreamUrl.IsError) + { + await _responder.CreateNotFoundResponse(context); + return; + } var response = await _requester - .GetResponse(context.Request.Method, downstreamUrl, context.Request.Body, + .GetResponse(context.Request.Method, downstreamUrl.Data, context.Request.Body, context.Request.Headers, context.Request.Cookies, context.Request.Query, context.Request.ContentType); await _responder.CreateResponse(context, response); await _next.Invoke(context); } - - private string GetDownstreamUrlFromOwinItems(HttpContext context) - { - object obj; - string downstreamUrl = null; - if (context.Items.TryGetValue("DownstreamUrl", out obj)) - { - downstreamUrl = (string) obj; - } - return downstreamUrl; - } } } \ No newline at end of file diff --git a/src/Ocelot/Startup.cs b/src/Ocelot/Startup.cs index 1142231f..bd592588 100644 --- a/src/Ocelot/Startup.cs +++ b/src/Ocelot/Startup.cs @@ -1,11 +1,13 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Ocelot.Library.Infrastructure.DownstreamRouteFinder; using Ocelot.Library.Infrastructure.Requester; using Ocelot.Library.Infrastructure.Responder; +using Ocelot.Library.Infrastructure.Services; using Ocelot.Library.Middleware; namespace Ocelot @@ -42,6 +44,10 @@ namespace Ocelot services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + + // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc + services.AddSingleton(); + services.AddScoped(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/test/Ocelot.UnitTests/Services/RequestDataServiceTests.cs b/test/Ocelot.UnitTests/Services/RequestDataServiceTests.cs new file mode 100644 index 00000000..84921c34 --- /dev/null +++ b/test/Ocelot.UnitTests/Services/RequestDataServiceTests.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Moq; +using Ocelot.Library.Infrastructure.Responses; +using Ocelot.Library.Infrastructure.Services; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Services +{ + public class RequestDataServiceTests + { + private IRequestDataService _requestDataService; + private IHttpContextAccessor _httpContextAccesor; + private string _key; + private object _toAdd; + private Response _result; + + public RequestDataServiceTests() + { + _httpContextAccesor = new HttpContextAccessor(); + _httpContextAccesor.HttpContext = new DefaultHttpContext(); + _requestDataService = new RequestDataService(_httpContextAccesor); + } + + [Fact] + public void should_add_item() + { + this.Given(x => x.GivenIHaveAnItemToAdd("blahh", new [] {1,2,3,4})) + .When(x => x.WhenIAddTheItem()) + .Then(x => x.ThenTheItemIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_get_item() + { + this.Given(x => x.GivenThereIsAnItemInTheContext("chest")) + .When(x => x.WhenIGetTheItem()) + .Then(x => x.ThenTheItemIsReturned()) + .BDDfy(); + } + + private void ThenTheItemIsReturned() + { + _result.IsError.ShouldBeFalse(); + _result.Data.ShouldNotBeNull(); + } + + private void WhenIGetTheItem() + { + _result = _requestDataService.Get(_key); + } + + private void GivenThereIsAnItemInTheContext(string key) + { + _key = key; + var data = new[] {5435345}; + _httpContextAccesor.HttpContext.Items.Add(key, data); + } + + private void GivenIHaveAnItemToAdd(string key, object toAdd) + { + _key = key; + _toAdd = toAdd; + } + + private void WhenIAddTheItem() + { + _requestDataService.Add(_key, _toAdd); + } + + private void ThenTheItemIsAdded() + { + object obj; + _httpContextAccesor.HttpContext.Items.TryGetValue(_key, out obj).ShouldBeTrue(); + } + } +}