mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 14:02:49 +08:00
started refactoring request builder
This commit is contained in:
parent
56bf4014bd
commit
5fc04a0514
10
README.md
10
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<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 :(
|
||||
|
||||
|
||||
|
@ -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>();
|
||||
|
@ -3,7 +3,7 @@ using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Headers
|
||||
{
|
||||
public interface IRemoveHeaders
|
||||
public interface IRemoveOutputHeaders
|
||||
{
|
||||
Response Remove(HttpResponseHeaders headers);
|
||||
}
|
@ -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)
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
src/Ocelot/Request/Builder/HttpRequestCreator.cs
Normal file
34
src/Ocelot/Request/Builder/HttpRequestCreator.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
183
src/Ocelot/Request/Builder/RequestBuilder.cs
Normal file
183
src/Ocelot/Request/Builder/RequestBuilder.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user