diff --git a/README.md b/README.md index 03b10a11..ca6670a6 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,14 @@ This is pretty much all you need to get going.......more to come! ## Logging -Ocelot uses the standard logging interfaces ILoggerFactory / ILogger as such you can use any logging provider you like such as default, nlog, serilog. +Ocelot uses the standard logging interfaces ILoggerFactory / ILogger as such you can use any logging provider you like such as default, nlog, serilog or whatever you want. + +## Not supported + +Ocelot does not support... + - Chunked Encoding - Ocelot will always get the body size and return Content-Length header. Sorry + if this doesn't work for your use case! + - Fowarding a host header - The host header that you send to Ocelot will not be forwarded to + the downstream service. Obviously this would break everything :( diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index db761130..51299365 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -46,7 +46,7 @@ namespace Ocelot.DependencyInjection services.AddLogging(); // ocelot services. - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -60,7 +60,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/Headers/IRemoveHeaders.cs b/src/Ocelot/Headers/IRemoveOutputHeaders.cs similarity index 78% rename from src/Ocelot/Headers/IRemoveHeaders.cs rename to src/Ocelot/Headers/IRemoveOutputHeaders.cs index d22f4306..909d78f9 100644 --- a/src/Ocelot/Headers/IRemoveHeaders.cs +++ b/src/Ocelot/Headers/IRemoveOutputHeaders.cs @@ -3,7 +3,7 @@ using Ocelot.Responses; namespace Ocelot.Headers { - public interface IRemoveHeaders + public interface IRemoveOutputHeaders { Response Remove(HttpResponseHeaders headers); } diff --git a/src/Ocelot/Headers/RemoveHeaders.cs b/src/Ocelot/Headers/RemoveOutputHeaders.cs similarity index 80% rename from src/Ocelot/Headers/RemoveHeaders.cs rename to src/Ocelot/Headers/RemoveOutputHeaders.cs index 46ce7ba1..c683bd39 100644 --- a/src/Ocelot/Headers/RemoveHeaders.cs +++ b/src/Ocelot/Headers/RemoveOutputHeaders.cs @@ -1,22 +1,20 @@ -using System.Collections.Generic; using System.Net.Http.Headers; using Ocelot.Responses; namespace Ocelot.Headers { - public class RemoveHeaders : IRemoveHeaders + public class RemoveOutputHeaders : IRemoveOutputHeaders { /// /// Some webservers return headers that cannot be forwarded to the client /// in a given context such as transfer encoding chunked when ASP.NET is not /// returning the response in this manner /// - private readonly List _unsupportedHeaders = new List + private readonly string[] _unsupportedHeaders = { "Transfer-Encoding" }; - public Response Remove(HttpResponseHeaders headers) { foreach (var unsupported in _unsupportedHeaders) diff --git a/src/Ocelot/Request/Builder/HttpRequestBuilder.cs b/src/Ocelot/Request/Builder/HttpRequestBuilder.cs deleted file mode 100644 index 6d2f29cc..00000000 --- a/src/Ocelot/Request/Builder/HttpRequestBuilder.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.Responses; - -namespace Ocelot.Request.Builder -{ - public class HttpRequestBuilder : IRequestBuilder - { - public async Task> Build( - string httpMethod, - string downstreamUrl, - Stream content, - IHeaderDictionary headers, - IRequestCookieCollection cookies, - QueryString queryString, - string contentType, - RequestId.RequestId requestId) - { - var method = new HttpMethod(httpMethod); - - var uri = new Uri(string.Format("{0}{1}", downstreamUrl, queryString.ToUriComponent())); - - var httpRequestMessage = new HttpRequestMessage(method, uri); - - if (content != null) - { - httpRequestMessage.Content = new ByteArrayContent(await ToByteArray(content)); - } - - if (!string.IsNullOrEmpty(contentType)) - { - httpRequestMessage.Content.Headers.Remove("Content-Type"); - httpRequestMessage.Content.Headers.TryAddWithoutValidation("Content-Type", contentType); - } - - //todo get rid of if - if (headers != null) - { - headers.Remove("Content-Type"); - - foreach (var header in headers) - { - //todo get rid of if.. - if (header.Key.ToLower() != "host") - { - httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); - } - } - } - - if (RequestKeyIsNotNull(requestId) && !RequestIdInHeaders(requestId, httpRequestMessage.Headers)) - { - ForwardRequestIdToDownstreamService(requestId, httpRequestMessage); - } - - var cookieContainer = new CookieContainer(); - - //todo get rid of if - if (cookies != null) - { - foreach (var cookie in cookies) - { - cookieContainer.Add(uri, new Cookie(cookie.Key, cookie.Value)); - } - } - - return new OkResponse(new Request(httpRequestMessage, cookieContainer)); - } - - private void ForwardRequestIdToDownstreamService(RequestId.RequestId requestId, HttpRequestMessage httpRequestMessage) - { - httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue); - } - - private bool RequestIdInHeaders(RequestId.RequestId requestId, HttpRequestHeaders headers) - { - IEnumerable value; - if (headers.TryGetValues(requestId.RequestIdKey, out value)) - { - return true; - } - - return false; - } - - private bool RequestKeyIsNotNull(RequestId.RequestId requestId) - { - return !string.IsNullOrEmpty(requestId?.RequestIdKey) && !string.IsNullOrEmpty(requestId.RequestIdValue); - } - - private async Task ToByteArray(Stream stream) - { - using (stream) - { - using (var memStream = new MemoryStream()) - { - await stream.CopyToAsync(memStream); - return memStream.ToArray(); - } - } - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Request/Builder/HttpRequestCreator.cs b/src/Ocelot/Request/Builder/HttpRequestCreator.cs new file mode 100644 index 00000000..91234331 --- /dev/null +++ b/src/Ocelot/Request/Builder/HttpRequestCreator.cs @@ -0,0 +1,34 @@ +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Responses; + +namespace Ocelot.Request.Builder +{ + public sealed class HttpRequestCreator : IRequestCreator + { + public async Task> Build( + string httpMethod, + string downstreamUrl, + Stream content, + IHeaderDictionary headers, + IRequestCookieCollection cookies, + QueryString queryString, + string contentType, + RequestId.RequestId requestId) + { + var request = await new RequestBuilder() + .WithHttpMethod(httpMethod) + .WithDownstreamUrl(downstreamUrl) + .WithQueryString(queryString) + .WithContent(content) + .WithContentType(contentType) + .WithHeaders(headers) + .WithRequestId(requestId) + .WithCookies(cookies) + .Build(); + + return new OkResponse(request); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Request/Builder/IRequestBuilder.cs b/src/Ocelot/Request/Builder/IRequestCreator.cs similarity index 92% rename from src/Ocelot/Request/Builder/IRequestBuilder.cs rename to src/Ocelot/Request/Builder/IRequestCreator.cs index cefa3bcf..7641d848 100644 --- a/src/Ocelot/Request/Builder/IRequestBuilder.cs +++ b/src/Ocelot/Request/Builder/IRequestCreator.cs @@ -5,7 +5,7 @@ using Ocelot.Responses; namespace Ocelot.Request.Builder { - public interface IRequestBuilder + public interface IRequestCreator { Task> Build(string httpMethod, string downstreamUrl, diff --git a/src/Ocelot/Request/Builder/RequestBuilder.cs b/src/Ocelot/Request/Builder/RequestBuilder.cs new file mode 100644 index 00000000..e19546c0 --- /dev/null +++ b/src/Ocelot/Request/Builder/RequestBuilder.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; + +namespace Ocelot.Request.Builder +{ + internal sealed class RequestBuilder + { + private HttpMethod _method; + private string _downstreamUrl; + private QueryString _queryString; + private Stream _content; + private string _contentType; + private IHeaderDictionary _headers; + private RequestId.RequestId _requestId; + private IRequestCookieCollection _cookies; + private readonly string[] _unsupportedHeaders = {"host"}; + + public RequestBuilder WithHttpMethod(string httpMethod) + { + _method = new HttpMethod(httpMethod); + return this; + } + + public RequestBuilder WithDownstreamUrl(string downstreamUrl) + { + _downstreamUrl = downstreamUrl; + return this; + } + + public RequestBuilder WithQueryString(QueryString queryString) + { + _queryString = queryString; + return this; + } + + public RequestBuilder WithContent(Stream content) + { + _content = content; + return this; + } + + public RequestBuilder WithContentType(string contentType) + { + _contentType = contentType; + return this; + } + + public RequestBuilder WithHeaders(IHeaderDictionary headers) + { + _headers = headers; + return this; + } + + public RequestBuilder WithRequestId(RequestId.RequestId requestId) + { + _requestId = requestId; + return this; + } + + public RequestBuilder WithCookies(IRequestCookieCollection cookies) + { + _cookies = cookies; + return this; + } + + public async Task Build() + { + var uri = new Uri(string.Format("{0}{1}", _downstreamUrl, _queryString.ToUriComponent())); + + var httpRequestMessage = new HttpRequestMessage(_method, uri); + + await AddContentToRequest(httpRequestMessage); + + AddContentTypeToRequest(httpRequestMessage); + + AddHeadersToRequest(httpRequestMessage); + + if (RequestIdKeyIsNotNull(_requestId) && !RequestIdInHeaders(_requestId, httpRequestMessage.Headers)) + { + AddRequestIdHeader(_requestId, httpRequestMessage); + } + + var cookieContainer = CreateCookieContainer(uri); + + return new Request(httpRequestMessage, cookieContainer); + } + + private async Task AddContentToRequest(HttpRequestMessage httpRequestMessage) + { + if (_content != null) + { + httpRequestMessage.Content = new ByteArrayContent(await ToByteArray(_content)); + } + } + + private void AddContentTypeToRequest(HttpRequestMessage httpRequestMessage) + { + if (!string.IsNullOrEmpty(_contentType)) + { + httpRequestMessage.Content.Headers.Remove("Content-Type"); + httpRequestMessage.Content.Headers.TryAddWithoutValidation("Content-Type", _contentType); + } + } + + private void AddHeadersToRequest(HttpRequestMessage httpRequestMessage) + { + if (_headers != null) + { + _headers.Remove("Content-Type"); + + foreach (var header in _headers) + { + //todo get rid of if.. + if (IsSupportedHeader(header)) + { + httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); + } + } + } + } + + private bool IsSupportedHeader(KeyValuePair header) + { + return !_unsupportedHeaders.Contains(header.Key.ToLower()); + } + + private CookieContainer CreateCookieContainer(Uri uri) + { + var cookieContainer = new CookieContainer(); + + if (_cookies != null) + { + foreach (var cookie in _cookies) + { + cookieContainer.Add(uri, new Cookie(cookie.Key, cookie.Value)); + } + } + + return cookieContainer; + } + + private void AddRequestIdHeader(RequestId.RequestId requestId, HttpRequestMessage httpRequestMessage) + { + httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue); + } + + private bool RequestIdInHeaders(RequestId.RequestId requestId, HttpRequestHeaders headers) + { + IEnumerable value; + if (headers.TryGetValues(requestId.RequestIdKey, out value)) + { + return true; + } + + return false; + } + + private bool RequestIdKeyIsNotNull(RequestId.RequestId requestId) + { + return !string.IsNullOrEmpty(requestId?.RequestIdKey) && !string.IsNullOrEmpty(requestId.RequestIdValue); + } + + private async Task ToByteArray(Stream stream) + { + using (stream) + { + using (var memStream = new MemoryStream()) + { + await stream.CopyToAsync(memStream); + return memStream.ToArray(); + } + } + } + } +} diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs index 82f01452..1f2fea61 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs @@ -9,20 +9,20 @@ namespace Ocelot.Request.Middleware public class HttpRequestBuilderMiddleware : OcelotMiddleware { private readonly RequestDelegate _next; - private readonly IRequestBuilder _requestBuilder; + private readonly IRequestCreator _requestCreator; public HttpRequestBuilderMiddleware(RequestDelegate next, IRequestScopedDataRepository requestScopedDataRepository, - IRequestBuilder requestBuilder) + IRequestCreator requestCreator) :base(requestScopedDataRepository) { _next = next; - _requestBuilder = requestBuilder; + _requestCreator = requestCreator; } public async Task Invoke(HttpContext context) { - var buildResult = await _requestBuilder + var buildResult = await _requestCreator .Build(context.Request.Method, DownstreamUrl, context.Request.Body, context.Request.Headers, context.Request.Cookies, context.Request.QueryString, context.Request.ContentType, new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier)); diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 5c81225f..abc9e834 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -15,16 +15,16 @@ namespace Ocelot.Responder /// public class HttpContextResponder : IHttpResponder { - private readonly IRemoveHeaders _removeHeaders; + private readonly IRemoveOutputHeaders _removeOutputHeaders; - public HttpContextResponder(IRemoveHeaders removeHeaders) + public HttpContextResponder(IRemoveOutputHeaders removeOutputHeaders) { - _removeHeaders = removeHeaders; + _removeOutputHeaders = removeOutputHeaders; } public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response) { - _removeHeaders.Remove(response.Headers); + _removeOutputHeaders.Remove(response.Headers); foreach (var httpResponseHeader in response.Headers) { diff --git a/test/Ocelot.UnitTests/Headers/RemoveHeaders.cs b/test/Ocelot.UnitTests/Headers/RemoveHeaders.cs index 774dd56b..784f89aa 100644 --- a/test/Ocelot.UnitTests/Headers/RemoveHeaders.cs +++ b/test/Ocelot.UnitTests/Headers/RemoveHeaders.cs @@ -10,12 +10,12 @@ namespace Ocelot.UnitTests.Headers public class RemoveHeaders { private HttpResponseHeaders _headers; - private readonly Ocelot.Headers.RemoveHeaders _removeHeaders; + private readonly Ocelot.Headers.RemoveOutputHeaders _removeOutputHeaders; private Response _result; public RemoveHeaders() { - _removeHeaders = new Ocelot.Headers.RemoveHeaders(); + _removeOutputHeaders = new Ocelot.Headers.RemoveOutputHeaders(); } [Fact] @@ -39,7 +39,7 @@ namespace Ocelot.UnitTests.Headers private void WhenIRemoveTheHeaders() { - _result = _removeHeaders.Remove(_headers); + _result = _removeOutputHeaders.Remove(_headers); } private void TheHeaderIsNoLongerInTheContext() diff --git a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs index ccc55e9e..a4797bcb 100644 --- a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs @@ -22,7 +22,7 @@ namespace Ocelot.UnitTests.Request { public class HttpRequestBuilderMiddlewareTests : IDisposable { - private readonly Mock _requestBuilder; + private readonly Mock _requestBuilder; private readonly Mock _scopedRepository; private readonly string _url; private readonly TestServer _server; @@ -35,7 +35,7 @@ namespace Ocelot.UnitTests.Request public HttpRequestBuilderMiddlewareTests() { _url = "http://localhost:51879"; - _requestBuilder = new Mock(); + _requestBuilder = new Mock(); _scopedRepository = new Mock(); var builder = new WebHostBuilder() diff --git a/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs b/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs index 5e82b86c..3a07532e 100644 --- a/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs +++ b/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs @@ -22,14 +22,14 @@ namespace Ocelot.UnitTests.Request private IRequestCookieCollection _cookies; private QueryString _query; private string _contentType; - private readonly IRequestBuilder _requestBuilder; + private readonly IRequestCreator _requestCreator; private Response _result; private Ocelot.RequestId.RequestId _requestId; public RequestBuilderTests() { _content = new StringContent(string.Empty); - _requestBuilder = new HttpRequestBuilder(); + _requestCreator = new HttpRequestCreator(); } [Fact] @@ -280,7 +280,7 @@ namespace Ocelot.UnitTests.Request private void WhenICreateARequest() { - _result = _requestBuilder.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers, + _result = _requestCreator.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers, _cookies, _query, _contentType, _requestId).Result; }