started refactoring request builder

This commit is contained in:
TomPallister 2016-10-30 18:35:05 +00:00
parent 56bf4014bd
commit 5fc04a0514
13 changed files with 248 additions and 134 deletions

View File

@ -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<T> as such you can use any logging provider you like such as default, nlog, serilog.
Ocelot uses the standard logging interfaces ILoggerFactory / ILogger<T> 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 :(

View File

@ -46,7 +46,7 @@ namespace Ocelot.DependencyInjection
services.AddLogging();
// ocelot services.
services.AddSingleton<IRemoveHeaders, RemoveHeaders>();
services.AddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
services.AddSingleton<IOcelotConfigurationProvider, OcelotConfigurationProvider>();
services.AddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
services.AddSingleton<IAuthoriser, ClaimsAuthoriser>();
@ -60,7 +60,7 @@ namespace Ocelot.DependencyInjection
services.AddSingleton<IDownstreamRouteFinder, DownstreamRouteFinder.Finder.DownstreamRouteFinder>();
services.AddSingleton<IHttpRequester, HttpClientHttpRequester>();
services.AddSingleton<IHttpResponder, HttpContextResponder>();
services.AddSingleton<IRequestBuilder, HttpRequestBuilder>();
services.AddSingleton<IRequestCreator, HttpRequestCreator>();
services.AddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>();
services.AddSingleton<IAuthenticationHandlerFactory, AuthenticationHandlerFactory>();
services.AddSingleton<IAuthenticationHandlerCreator, AuthenticationHandlerCreator>();

View File

@ -3,7 +3,7 @@ using Ocelot.Responses;
namespace Ocelot.Headers
{
public interface IRemoveHeaders
public interface IRemoveOutputHeaders
{
Response Remove(HttpResponseHeaders headers);
}

View File

@ -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
{
/// <summary>
/// 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
/// </summary>
private readonly List<string> _unsupportedHeaders = new List<string>
private readonly string[] _unsupportedHeaders =
{
"Transfer-Encoding"
};
public Response Remove(HttpResponseHeaders headers)
{
foreach (var unsupported in _unsupportedHeaders)

View File

@ -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<Response<Request>> 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<Request>(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<string> 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<byte[]> ToByteArray(Stream stream)
{
using (stream)
{
using (var memStream = new MemoryStream())
{
await stream.CopyToAsync(memStream);
return memStream.ToArray();
}
}
}
}
}

View File

@ -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<Response<Request>> 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>(request);
}
}
}

View File

@ -5,7 +5,7 @@ using Ocelot.Responses;
namespace Ocelot.Request.Builder
{
public interface IRequestBuilder
public interface IRequestCreator
{
Task<Response<Request>> Build(string httpMethod,
string downstreamUrl,

View File

@ -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<Request> 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<string, StringValues> 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<string> 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<byte[]> ToByteArray(Stream stream)
{
using (stream)
{
using (var memStream = new MemoryStream())
{
await stream.CopyToAsync(memStream);
return memStream.ToArray();
}
}
}
}
}

View File

@ -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));

View File

@ -15,16 +15,16 @@ namespace Ocelot.Responder
/// </summary>
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<Response> SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response)
{
_removeHeaders.Remove(response.Headers);
_removeOutputHeaders.Remove(response.Headers);
foreach (var httpResponseHeader in response.Headers)
{

View File

@ -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()

View File

@ -22,7 +22,7 @@ namespace Ocelot.UnitTests.Request
{
public class HttpRequestBuilderMiddlewareTests : IDisposable
{
private readonly Mock<IRequestBuilder> _requestBuilder;
private readonly Mock<IRequestCreator> _requestBuilder;
private readonly Mock<IRequestScopedDataRepository> _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<IRequestBuilder>();
_requestBuilder = new Mock<IRequestCreator>();
_scopedRepository = new Mock<IRequestScopedDataRepository>();
var builder = new WebHostBuilder()

View File

@ -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<Ocelot.Request.Request> _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;
}