Feature/websockets (#273)

* #212 - hacked websockets proxy together

* faffing around

* #212 hacking away :(

* #212 websockets proxy middleware working

* #212 map when for webockets working

* #212 some test refactor

* #212 temp commit

* #212 websockets proxy working, tests passing...need to do some tidying and write docs

* #212 more code coverage

* #212 docs for websockets

* #212 updated readme

* #212 tidying up after websockets refactoring

* #212 tidying up after websockets refactoring

* #212 tidying up after websockets refactoring

* stuck a warning in about logging levels into docs!
This commit is contained in:
Tom Pallister
2018-03-23 18:01:02 +00:00
committed by GitHub
parent 4493b22d0d
commit 463a7bdab4
80 changed files with 1539 additions and 369 deletions

View File

@ -37,7 +37,7 @@ namespace Ocelot.Cache.Middleware
return;
}
var downstreamUrlKey = $"{context.DownstreamRequest.Method.Method}-{context.DownstreamRequest.RequestUri.OriginalString}";
var downstreamUrlKey = $"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}";
_logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey);

View File

@ -0,0 +1,14 @@
namespace Ocelot.Configuration.Creator
{
public class AddHeader
{
public AddHeader(string key, string value)
{
Key = key;
Value = value;
}
public string Key { get; }
public string Value { get; }
}
}

View File

@ -10,12 +10,12 @@ namespace Ocelot.Configuration.Creator
{
public class HeaderFindAndReplaceCreator : IHeaderFindAndReplaceCreator
{
private IPlaceholders _placeholders;
private IOcelotLogger _logger;
private readonly IPlaceholders _placeholders;
private readonly IOcelotLogger _logger;
public HeaderFindAndReplaceCreator(IPlaceholders placeholders, IOcelotLoggerFactory factory)
{
_logger = factory.CreateLogger<HeaderFindAndReplaceCreator>();;
_logger = factory.CreateLogger<HeaderFindAndReplaceCreator>();
_placeholders = placeholders;
}

View File

@ -14,21 +14,10 @@ namespace Ocelot.Configuration.Creator
Downstream = downstream;
}
public List<HeaderFindAndReplace> Upstream { get; private set; }
public List<HeaderFindAndReplace> Upstream { get; }
public List<HeaderFindAndReplace> Downstream { get; private set; }
public List<AddHeader> AddHeadersToDownstream {get;private set;}
}
public List<HeaderFindAndReplace> Downstream { get; }
public class AddHeader
{
public AddHeader(string key, string value)
{
this.Key = key;
this.Value = value;
}
public string Key { get; private set; }
public string Value { get; private set; }
public List<AddHeader> AddHeadersToDownstream { get; }
}
}

View File

@ -6,6 +6,7 @@ using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Ocelot.Responses;
using Ocelot.ServiceDiscovery;
using Ocelot.ServiceDiscovery.Configuration;
namespace Ocelot.Configuration.Repository
{

View File

@ -4,5 +4,4 @@
{
int Delay { get; }
}
}
}

View File

@ -1,12 +0,0 @@
using Ocelot.Errors;
namespace Ocelot.DownstreamUrlCreator
{
public class DownstreamHostNullOrEmptyError : Error
{
public DownstreamHostNullOrEmptyError()
: base("downstream host was null or empty", OcelotErrorCode.DownstreamHostNullOrEmptyError)
{
}
}
}

View File

@ -1,12 +0,0 @@
using Ocelot.Errors;
namespace Ocelot.DownstreamUrlCreator
{
public class DownstreamPathNullOrEmptyError : Error
{
public DownstreamPathNullOrEmptyError()
: base("downstream path was null or empty", OcelotErrorCode.DownstreamPathNullOrEmptyError)
{
}
}
}

View File

@ -1,12 +0,0 @@
using Ocelot.Errors;
namespace Ocelot.DownstreamUrlCreator
{
public class DownstreamSchemeNullOrEmptyError : Error
{
public DownstreamSchemeNullOrEmptyError()
: base("downstream scheme was null or empty", OcelotErrorCode.DownstreamSchemeNullOrEmptyError)
{
}
}
}

View File

@ -40,49 +40,36 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
return;
}
UriBuilder uriBuilder;
context.DownstreamRequest.Scheme = context.DownstreamReRoute.DownstreamScheme;
if (ServiceFabricRequest(context))
{
uriBuilder = CreateServiceFabricUri(context, dsPath);
var pathAndQuery = CreateServiceFabricUri(context, dsPath);
context.DownstreamRequest.AbsolutePath = pathAndQuery.path;
context.DownstreamRequest.Query = pathAndQuery.query;
}
else
{
uriBuilder = new UriBuilder(context.DownstreamRequest.RequestUri)
{
Path = dsPath.Data.Value,
Scheme = context.DownstreamReRoute.DownstreamScheme
};
context.DownstreamRequest.AbsolutePath = dsPath.Data.Value;
}
context.DownstreamRequest.RequestUri = uriBuilder.Uri;
_logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", context.DownstreamRequest.RequestUri);
_logger.LogDebug("downstream url is {context.DownstreamRequest}", context.DownstreamRequest);
await _next.Invoke(context);
}
private UriBuilder CreateServiceFabricUri(DownstreamContext context, Response<DownstreamPath> dsPath)
private (string path, string query) CreateServiceFabricUri(DownstreamContext context, Response<DownstreamPath> dsPath)
{
var query = context.DownstreamRequest.RequestUri.Query;
var scheme = context.DownstreamReRoute.DownstreamScheme;
var host = context.DownstreamRequest.RequestUri.Host;
var port = context.DownstreamRequest.RequestUri.Port;
var query = context.DownstreamRequest.Query;
var serviceFabricPath = $"/{context.DownstreamReRoute.ServiceName + dsPath.Data.Value}";
Uri uri;
if (RequestForStatefullService(query))
{
uri = new Uri($"{scheme}://{host}:{port}{serviceFabricPath}{query}");
}
else
{
var split = string.IsNullOrEmpty(query) ? "?" : "&";
uri = new Uri($"{scheme}://{host}:{port}{serviceFabricPath}{query}{split}cmd=instance");
return (serviceFabricPath, query);
}
return new UriBuilder(uri);
var split = string.IsNullOrEmpty(query) ? "?" : "&";
return (serviceFabricPath, $"{query}{split}cmd=instance");
}
private static bool ServiceFabricRequest(DownstreamContext context)

View File

@ -5,6 +5,7 @@ using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Responses;
using System.Net.Http;
using Ocelot.Configuration.Creator;
using Ocelot.Request.Middleware;
namespace Ocelot.Headers
{
@ -17,7 +18,7 @@ namespace Ocelot.Headers
_claimsParser = claimsParser;
}
public Response SetHeadersOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<System.Security.Claims.Claim> claims, HttpRequestMessage downstreamRequest)
public Response SetHeadersOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<System.Security.Claims.Claim> claims, DownstreamRequest downstreamRequest)
{
foreach (var config in claimsToThings)
{

View File

@ -1,23 +1,22 @@
namespace Ocelot.Headers
{
using System;
using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Configuration.Creator;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging;
public class AddHeadersToResponse : IAddHeadersToResponse
{
private IPlaceholders _placeholders;
private IOcelotLogger _logger;
private readonly IPlaceholders _placeholders;
private readonly IOcelotLogger _logger;
public AddHeadersToResponse(IPlaceholders placeholders, IOcelotLoggerFactory factory)
{
_logger = factory.CreateLogger<AddHeadersToResponse>();
_placeholders = placeholders;
}
public void Add(List<AddHeader> addHeaders, HttpResponseMessage response)
{
foreach(var add in addHeaders)

View File

@ -5,6 +5,7 @@ using System.Net.Http;
using Ocelot.Configuration;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Request.Middleware;
using Ocelot.Responses;
namespace Ocelot.Headers
@ -18,7 +19,7 @@ namespace Ocelot.Headers
_placeholders = placeholders;
}
public Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs, HttpRequestMessage request)
public Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs, DownstreamRequest request)
{
foreach (var f in fAndRs)
{

View File

@ -6,10 +6,11 @@
using Ocelot.Configuration;
using Ocelot.Configuration.Creator;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Request.Middleware;
using Ocelot.Responses;
public interface IAddHeadersToRequest
{
Response SetHeadersOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<System.Security.Claims.Claim> claims, HttpRequestMessage downstreamRequest);
Response SetHeadersOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<System.Security.Claims.Claim> claims, DownstreamRequest downstreamRequest);
}
}

View File

@ -1,12 +1,13 @@
using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Configuration;
using Ocelot.Request.Middleware;
using Ocelot.Responses;
namespace Ocelot.Headers
{
public interface IHttpResponseHeaderReplacer
{
Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs, HttpRequestMessage httpRequestMessage);
Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs, DownstreamRequest httpRequestMessage);
}
}

View File

@ -1,4 +1,5 @@
using System.Net.Http;
using Ocelot.Request.Middleware;
using Ocelot.Responses;
namespace Ocelot.Infrastructure
@ -6,6 +7,6 @@ namespace Ocelot.Infrastructure
public interface IPlaceholders
{
Response<string> Get(string key);
Response<string> Get(string key, HttpRequestMessage request);
Response<string> Get(string key, DownstreamRequest request);
}
}

View File

@ -3,14 +3,15 @@ using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware;
using Ocelot.Request.Middleware;
using Ocelot.Responses;
namespace Ocelot.Infrastructure
{
public class Placeholders : IPlaceholders
{
private Dictionary<string, Func<Response<string>>> _placeholders;
private Dictionary<string, Func<HttpRequestMessage, string>> _requestPlaceholders;
private readonly Dictionary<string, Func<Response<string>>> _placeholders;
private readonly Dictionary<string, Func<DownstreamRequest, string>> _requestPlaceholders;
private readonly IBaseUrlFinder _finder;
private readonly IRequestScopedDataRepository _repo;
@ -30,13 +31,13 @@ namespace Ocelot.Infrastructure
return new OkResponse<string>(traceId.Data);
});
_requestPlaceholders = new Dictionary<string, Func<HttpRequestMessage, string>>();
_requestPlaceholders = new Dictionary<string, Func<DownstreamRequest, string>>();
_requestPlaceholders.Add("{DownstreamBaseUrl}", x => {
var downstreamUrl = $"{x.RequestUri.Scheme}://{x.RequestUri.Host}";
var downstreamUrl = $"{x.Scheme}://{x.Host}";
if(x.RequestUri.Port != 80 && x.RequestUri.Port != 443)
if(x.Port != 80 && x.Port != 443)
{
downstreamUrl = $"{downstreamUrl}:{x.RequestUri.Port}";
downstreamUrl = $"{downstreamUrl}:{x.Port}";
}
return $"{downstreamUrl}/";
@ -57,7 +58,7 @@ namespace Ocelot.Infrastructure
return new ErrorResponse<string>(new CouldNotFindPlaceholderError(key));
}
public Response<string> Get(string key, HttpRequestMessage request)
public Response<string> Get(string key, DownstreamRequest request)
{
if(_requestPlaceholders.ContainsKey(key))
{
@ -67,4 +68,4 @@ namespace Ocelot.Infrastructure
return new ErrorResponse<string>(new CouldNotFindPlaceholderError(key));
}
}
}
}

View File

@ -1,12 +1,8 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.Infrastructure.RequestData;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.QueryStrings.Middleware;
namespace Ocelot.LoadBalancer.Middleware
{
@ -43,17 +39,13 @@ namespace Ocelot.LoadBalancer.Middleware
return;
}
var uriBuilder = new UriBuilder(context.DownstreamRequest.RequestUri);
uriBuilder.Host = hostAndPort.Data.DownstreamHost;
context.DownstreamRequest.Host = hostAndPort.Data.DownstreamHost;
if (hostAndPort.Data.DownstreamPort > 0)
{
uriBuilder.Port = hostAndPort.Data.DownstreamPort;
context.DownstreamRequest.Port = hostAndPort.Data.DownstreamPort;
}
context.DownstreamRequest.RequestUri = uriBuilder.Uri;
try
{
await _next.Invoke(context);

View File

@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Errors;
using Ocelot.Request.Middleware;
namespace Ocelot.Middleware
{
@ -19,7 +21,7 @@ namespace Ocelot.Middleware
public ServiceProviderConfiguration ServiceProviderConfiguration {get; set;}
public HttpContext HttpContext { get; private set; }
public DownstreamReRoute DownstreamReRoute { get; set; }
public HttpRequestMessage DownstreamRequest { get; set; }
public DownstreamRequest DownstreamRequest { get; set; }
public HttpResponseMessage DownstreamResponse { get; set; }
public List<Error> Errors { get;set; }
public bool IsError => Errors.Count > 0;

View File

@ -11,5 +11,6 @@ namespace Ocelot.Middleware.Pipeline
IServiceProvider ApplicationServices { get; }
OcelotPipelineBuilder Use(Func<OcelotRequestDelegate, OcelotRequestDelegate> middleware);
OcelotRequestDelegate Build();
IOcelotPipelineBuilder New();
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Threading.Tasks;
namespace Ocelot.Middleware.Pipeline
{
public class MapWhenMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly MapWhenOptions _options;
public MapWhenMiddleware(OcelotRequestDelegate next, MapWhenOptions options)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_next = next;
_options = options;
}
public async Task Invoke(DownstreamContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (_options.Predicate(context))
{
await _options.Branch(context);
}
else
{
await _next(context);
}
}
}
}

View File

@ -0,0 +1,28 @@
using System;
namespace Ocelot.Middleware.Pipeline
{
public class MapWhenOptions
{
private Func<DownstreamContext, bool> _predicate;
public Func<DownstreamContext, bool> Predicate
{
get
{
return _predicate;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_predicate = value;
}
}
public OcelotRequestDelegate Branch { get; set; }
}
}

View File

@ -19,6 +19,12 @@ namespace Ocelot.Middleware.Pipeline
_middlewares = new List<Func<OcelotRequestDelegate, OcelotRequestDelegate>>();
}
public OcelotPipelineBuilder(IOcelotPipelineBuilder builder)
{
ApplicationServices = builder.ApplicationServices;
_middlewares = new List<Func<OcelotRequestDelegate, OcelotRequestDelegate>>();
}
public IServiceProvider ApplicationServices { get; }
public OcelotPipelineBuilder Use(Func<OcelotRequestDelegate, OcelotRequestDelegate> middleware)
@ -42,5 +48,10 @@ namespace Ocelot.Middleware.Pipeline
return app;
}
public IOcelotPipelineBuilder New()
{
return new OcelotPipelineBuilder(this);
}
}
}

View File

@ -12,6 +12,8 @@ using Microsoft.Extensions.DependencyInjection;
namespace Ocelot.Middleware.Pipeline
{
using Predicate = Func<DownstreamContext, bool>;
public static class OcelotPipelineBuilderExtensions
{
internal const string InvokeMethodName = "Invoke";
@ -91,6 +93,35 @@ namespace Ocelot.Middleware.Pipeline
});
}
public static IOcelotPipelineBuilder MapWhen(this IOcelotPipelineBuilder app, Predicate predicate, Action<IOcelotPipelineBuilder> configuration)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
}
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
var branchBuilder = app.New();
configuration(branchBuilder);
var branch = branchBuilder.Build();
var options = new MapWhenOptions
{
Predicate = predicate,
Branch = branch,
};
return app.Use(next => new MapWhenMiddleware(next, options).Invoke);
}
private static Func<T, DownstreamContext, IServiceProvider, Task> Compile<T>(MethodInfo methodinfo, ParameterInfo[] parameters)
{
var middleware = typeof(T);

View File

@ -15,18 +15,30 @@ using Ocelot.Request.Middleware;
using Ocelot.Requester.Middleware;
using Ocelot.RequestId.Middleware;
using Ocelot.Responder.Middleware;
using Ocelot.WebSockets.Middleware;
namespace Ocelot.Middleware.Pipeline
{
public static class OcelotPipelineExtensions
{
public static OcelotRequestDelegate BuildOcelotPipeline(this IOcelotPipelineBuilder builder,
OcelotPipelineConfiguration pipelineConfiguration = null)
OcelotPipelineConfiguration pipelineConfiguration)
{
// This is registered to catch any global exceptions that are not handled
// It also sets the Request Id if anything is set globally
builder.UseExceptionHandlerMiddleware();
// If the request is for websockets upgrade we fork into a different pipeline
builder.MapWhen(context => context.HttpContext.WebSockets.IsWebSocketRequest,
app =>
{
app.UseDownstreamRouteFinderMiddleware();
app.UseDownstreamRequestInitialiser();
app.UseLoadBalancingMiddleware();
app.UseDownstreamUrlCreatorMiddleware();
app.UseWebSocketsProxyMiddleware();
});
// Allow the user to respond with absolutely anything they want.
builder.UseIfNotNull(pipelineConfiguration.PreErrorResponderMiddleware);

View File

@ -7,6 +7,7 @@ using Ocelot.Responses;
using System.Security.Claims;
using System.Net.Http;
using System;
using Ocelot.Request.Middleware;
using Microsoft.Extensions.Primitives;
using System.Text;
@ -21,9 +22,9 @@ namespace Ocelot.QueryStrings
_claimsParser = claimsParser;
}
public Response SetQueriesOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<Claim> claims, HttpRequestMessage downstreamRequest)
public Response SetQueriesOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<Claim> claims, DownstreamRequest downstreamRequest)
{
var queryDictionary = ConvertQueryStringToDictionary(downstreamRequest.RequestUri.Query);
var queryDictionary = ConvertQueryStringToDictionary(downstreamRequest.Query);
foreach (var config in claimsToThings)
{
@ -46,11 +47,7 @@ namespace Ocelot.QueryStrings
}
}
var uriBuilder = new UriBuilder(downstreamRequest.RequestUri);
uriBuilder.Query = ConvertDictionaryToQueryString(queryDictionary);
downstreamRequest.RequestUri = uriBuilder.Uri;
downstreamRequest.Query = ConvertDictionaryToQueryString(queryDictionary);
return new OkResponse();
}
@ -94,4 +91,4 @@ namespace Ocelot.QueryStrings
return builder.ToString();
}
}
}
}

View File

@ -4,11 +4,12 @@ using Ocelot.Configuration;
using Ocelot.Responses;
using System.Net.Http;
using System.Security.Claims;
using Ocelot.Request.Middleware;
namespace Ocelot.QueryStrings
{
public interface IAddQueriesToRequest
{
Response SetQueriesOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<Claim> claims, HttpRequestMessage downstreamRequest);
Response SetQueriesOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<Claim> claims, DownstreamRequest downstreamRequest);
}
}

View File

@ -0,0 +1,69 @@
namespace Ocelot.Request.Middleware
{
using System;
using System.Net.Http;
using System.Net.Http.Headers;
public class DownstreamRequest
{
private readonly HttpRequestMessage _request;
public DownstreamRequest(HttpRequestMessage request)
{
_request = request;
Method = _request.Method.Method;
OriginalString = _request.RequestUri.OriginalString;
Scheme = _request.RequestUri.Scheme;
Host = _request.RequestUri.Host;
Port = _request.RequestUri.Port;
Headers = _request.Headers;
AbsolutePath = _request.RequestUri.AbsolutePath;
Query = _request.RequestUri.Query;
}
public HttpRequestHeaders Headers { get; }
public string Method { get; }
public string OriginalString { get; }
public string Scheme { get; set; }
public string Host { get; set; }
public int Port { get; set; }
public string AbsolutePath { get; set; }
public string Query { get; set; }
public HttpRequestMessage ToHttpRequestMessage()
{
var uriBuilder = new UriBuilder
{
Port = Port,
Host = Host,
Path = AbsolutePath,
Query = Query,
Scheme = Scheme
};
_request.RequestUri = uriBuilder.Uri;
return _request;
}
public string ToUri()
{
var uriBuilder = new UriBuilder
{
Port = Port,
Host = Host,
Path = AbsolutePath,
Query = Query,
Scheme = Scheme
};
return uriBuilder.Uri.AbsoluteUri;
}
}
}

View File

@ -1,5 +1,6 @@
namespace Ocelot.Request.Middleware
{
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
@ -31,9 +32,9 @@ namespace Ocelot.Request.Middleware
return;
}
context.DownstreamRequest = downstreamRequest.Data;
context.DownstreamRequest = new DownstreamRequest(downstreamRequest.Data);
await _next.Invoke(context);
}
}
}
}

View File

@ -9,6 +9,7 @@ using System.Net.Http;
using System.Net.Http.Headers;
using System.Collections.Generic;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.Request.Middleware;
namespace Ocelot.RequestId.Middleware
{
@ -82,7 +83,7 @@ namespace Ocelot.RequestId.Middleware
return headers.TryGetValues(requestId.RequestIdKey, out value);
}
private void AddRequestIdHeader(RequestId requestId, HttpRequestMessage httpRequestMessage)
private void AddRequestIdHeader(RequestId requestId, DownstreamRequest httpRequestMessage)
{
httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue);
}

View File

@ -78,9 +78,7 @@ namespace Ocelot.Requester
private string GetCacheKey(DownstreamContext request)
{
var baseUrl = $"{request.DownstreamRequest.RequestUri.Scheme}://{request.DownstreamRequest.RequestUri.Authority}{request.DownstreamRequest.RequestUri.AbsolutePath}";
return baseUrl;
return request.DownstreamRequest.OriginalString;
}
}
}

View File

@ -32,7 +32,7 @@ namespace Ocelot.Requester
try
{
var response = await httpClient.SendAsync(context.DownstreamRequest);
var response = await httpClient.SendAsync(context.DownstreamRequest.ToHttpRequestMessage());
return new OkResponse<HttpResponseMessage>(response);
}
catch (TimeoutRejectedException exception)

View File

@ -1,16 +1,16 @@
namespace Ocelot.ServiceDiscovery
{
public class ConsulRegistryConfiguration
{
public ConsulRegistryConfiguration(string hostName, int port, string keyOfServiceInConsul)
{
HostName = hostName;
Port = port;
KeyOfServiceInConsul = keyOfServiceInConsul;
}
public string KeyOfServiceInConsul { get; private set; }
public string HostName { get; private set; }
public int Port { get; private set; }
}
}
namespace Ocelot.ServiceDiscovery.Configuration
{
public class ConsulRegistryConfiguration
{
public ConsulRegistryConfiguration(string hostName, int port, string keyOfServiceInConsul)
{
HostName = hostName;
Port = port;
KeyOfServiceInConsul = keyOfServiceInConsul;
}
public string KeyOfServiceInConsul { get; private set; }
public string HostName { get; private set; }
public int Port { get; private set; }
}
}

View File

@ -1,4 +1,4 @@
namespace Ocelot.ServiceDiscovery
namespace Ocelot.ServiceDiscovery.Configuration
{
public class ServiceFabricConfiguration
{
@ -9,8 +9,10 @@
ServiceName = serviceName;
}
public string ServiceName { get; private set; }
public string HostName { get; private set; }
public int Port { get; private set; }
public string ServiceName { get; }
public string HostName { get; }
public int Port { get; }
}
}

View File

@ -1,12 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.ServiceDiscovery
{
public class UnableToFindServiceDiscoveryProviderError : Error
{
public UnableToFindServiceDiscoveryProviderError(string message)
: base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError)
{
}
}
}
using Ocelot.Errors;
namespace Ocelot.ServiceDiscovery.Errors
{
public class UnableToFindServiceDiscoveryProviderError : Error
{
public UnableToFindServiceDiscoveryProviderError(string message)
: base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError)
{
}
}
}

View File

@ -1,4 +1,5 @@
using Ocelot.Configuration;
using Ocelot.ServiceDiscovery.Providers;
namespace Ocelot.ServiceDiscovery
{
@ -6,4 +7,4 @@ namespace Ocelot.ServiceDiscovery
{
IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute);
}
}
}

View File

@ -1,21 +1,21 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ocelot.Values;
namespace Ocelot.ServiceDiscovery
{
public class ConfigurationServiceProvider : IServiceDiscoveryProvider
{
private readonly List<Service> _services;
public ConfigurationServiceProvider(List<Service> services)
{
_services = services;
}
public async Task<List<Service>> Get()
{
return await Task.FromResult(_services);
}
}
}
using System.Collections.Generic;
using System.Threading.Tasks;
using Ocelot.Values;
namespace Ocelot.ServiceDiscovery.Providers
{
public class ConfigurationServiceProvider : IServiceDiscoveryProvider
{
private readonly List<Service> _services;
public ConfigurationServiceProvider(List<Service> services)
{
_services = services;
}
public async Task<List<Service>> Get()
{
return await Task.FromResult(_services);
}
}
}

View File

@ -1,83 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Consul;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Logging;
using Ocelot.Values;
namespace Ocelot.ServiceDiscovery
{
public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider
{
private readonly ConsulRegistryConfiguration _consulConfig;
private readonly IOcelotLogger _logger;
private readonly ConsulClient _consul;
private const string VersionPrefix = "version-";
public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration consulRegistryConfiguration, IOcelotLoggerFactory factory)
{;
_logger = factory.CreateLogger<ConsulServiceDiscoveryProvider>();
var consulHost = string.IsNullOrEmpty(consulRegistryConfiguration?.HostName) ? "localhost" : consulRegistryConfiguration.HostName;
var consulPort = consulRegistryConfiguration?.Port ?? 8500;
_consulConfig = new ConsulRegistryConfiguration(consulHost, consulPort, consulRegistryConfiguration?.KeyOfServiceInConsul);
_consul = new ConsulClient(config =>
{
config.Address = new Uri($"http://{_consulConfig.HostName}:{_consulConfig.Port}");
});
}
public async Task<List<Service>> Get()
{
var queryResult = await _consul.Health.Service(_consulConfig.KeyOfServiceInConsul, string.Empty, true);
var services = new List<Service>();
foreach (var serviceEntry in queryResult.Response)
{
if (IsValid(serviceEntry))
{
services.Add(BuildService(serviceEntry));
}
else
{
_logger.LogError($"Unable to use service Address: {serviceEntry.Service.Address} and Port: {serviceEntry.Service.Port} as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0");
}
}
return services.ToList();
}
private Service BuildService(ServiceEntry serviceEntry)
{
return new Service(
serviceEntry.Service.Service,
new ServiceHostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port),
serviceEntry.Service.ID,
GetVersionFromStrings(serviceEntry.Service.Tags),
serviceEntry.Service.Tags ?? Enumerable.Empty<string>());
}
private bool IsValid(ServiceEntry serviceEntry)
{
if (serviceEntry.Service.Address.Contains("http://") || serviceEntry.Service.Address.Contains("https://") || serviceEntry.Service.Port <= 0)
{
return false;
}
return true;
}
private string GetVersionFromStrings(IEnumerable<string> strings)
{
return strings
?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal))
.TrimStart(VersionPrefix);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Consul;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Logging;
using Ocelot.ServiceDiscovery.Configuration;
using Ocelot.Values;
namespace Ocelot.ServiceDiscovery.Providers
{
public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider
{
private readonly ConsulRegistryConfiguration _consulConfig;
private readonly IOcelotLogger _logger;
private readonly ConsulClient _consul;
private const string VersionPrefix = "version-";
public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration consulRegistryConfiguration, IOcelotLoggerFactory factory)
{;
_logger = factory.CreateLogger<ConsulServiceDiscoveryProvider>();
var consulHost = string.IsNullOrEmpty(consulRegistryConfiguration?.HostName) ? "localhost" : consulRegistryConfiguration.HostName;
var consulPort = consulRegistryConfiguration?.Port ?? 8500;
_consulConfig = new ConsulRegistryConfiguration(consulHost, consulPort, consulRegistryConfiguration?.KeyOfServiceInConsul);
_consul = new ConsulClient(config =>
{
config.Address = new Uri($"http://{_consulConfig.HostName}:{_consulConfig.Port}");
});
}
public async Task<List<Service>> Get()
{
var queryResult = await _consul.Health.Service(_consulConfig.KeyOfServiceInConsul, string.Empty, true);
var services = new List<Service>();
foreach (var serviceEntry in queryResult.Response)
{
if (IsValid(serviceEntry))
{
services.Add(BuildService(serviceEntry));
}
else
{
_logger.LogError($"Unable to use service Address: {serviceEntry.Service.Address} and Port: {serviceEntry.Service.Port} as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0");
}
}
return services.ToList();
}
private Service BuildService(ServiceEntry serviceEntry)
{
return new Service(
serviceEntry.Service.Service,
new ServiceHostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port),
serviceEntry.Service.ID,
GetVersionFromStrings(serviceEntry.Service.Tags),
serviceEntry.Service.Tags ?? Enumerable.Empty<string>());
}
private bool IsValid(ServiceEntry serviceEntry)
{
if (serviceEntry.Service.Address.Contains("http://") || serviceEntry.Service.Address.Contains("https://") || serviceEntry.Service.Port <= 0)
{
return false;
}
return true;
}
private string GetVersionFromStrings(IEnumerable<string> strings)
{
return strings
?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal))
.TrimStart(VersionPrefix);
}
}
}

View File

@ -1,11 +1,11 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ocelot.Values;
namespace Ocelot.ServiceDiscovery
{
public interface IServiceDiscoveryProvider
{
Task<List<Service>> Get();
}
}
using System.Collections.Generic;
using System.Threading.Tasks;
using Ocelot.Values;
namespace Ocelot.ServiceDiscovery.Providers
{
public interface IServiceDiscoveryProvider
{
Task<List<Service>> Get();
}
}

View File

@ -1,8 +1,9 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ocelot.ServiceDiscovery.Configuration;
using Ocelot.Values;
namespace Ocelot.ServiceDiscovery
namespace Ocelot.ServiceDiscovery.Providers
{
public class ServiceFabricServiceDiscoveryProvider : IServiceDiscoveryProvider
{

View File

@ -1,6 +1,8 @@
using System.Collections.Generic;
using Ocelot.Configuration;
using Ocelot.Logging;
using Ocelot.ServiceDiscovery.Configuration;
using Ocelot.ServiceDiscovery.Providers;
using Ocelot.Values;
namespace Ocelot.ServiceDiscovery

View File

@ -7,6 +7,6 @@
Value = value;
}
public string Value { get; private set; }
public string Value { get; }
}
}

View File

@ -1,12 +0,0 @@
namespace Ocelot.Values
{
public class DownstreamUrl
{
public DownstreamUrl(string value)
{
Value = value;
}
public string Value { get; private set; }
}
}

View File

@ -7,6 +7,6 @@
Value = value;
}
public string Value { get; private set; }
public string Value { get; }
}
}

View File

@ -5,9 +5,9 @@ namespace Ocelot.Values
public class Service
{
public Service(string name,
ServiceHostAndPort hostAndPort,
string id,
string version,
ServiceHostAndPort hostAndPort,
string id,
string version,
IEnumerable<string> tags)
{
Name = name;
@ -17,14 +17,14 @@ namespace Ocelot.Values
Tags = tags;
}
public string Id { get; private set; }
public string Id { get; }
public string Name { get; private set; }
public string Name { get; }
public string Version { get; private set; }
public string Version { get; }
public IEnumerable<string> Tags { get; private set; }
public IEnumerable<string> Tags { get; }
public ServiceHostAndPort HostAndPort { get; private set; }
public ServiceHostAndPort HostAndPort { get; }
}
}

View File

@ -8,7 +8,8 @@
DownstreamPort = downstreamPort;
}
public string DownstreamHost { get; private set; }
public int DownstreamPort { get; private set; }
public string DownstreamHost { get; }
public int DownstreamPort { get; }
}
}

View File

@ -8,7 +8,8 @@ namespace Ocelot.Values
Priority = priority;
}
public string Template {get;}
public int Priority {get;}
public string Template { get; }
public int Priority { get; }
}
}
}

View File

@ -0,0 +1,103 @@
using System;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.Logging;
using Ocelot.Middleware;
namespace Ocelot.WebSockets.Middleware
{
public class WebSocketsProxyMiddleware : OcelotMiddleware
{
private OcelotRequestDelegate _next;
private IOcelotLogger _logger;
public WebSocketsProxyMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory)
{
_next = next;
_logger = loggerFactory.CreateLogger<WebSocketsProxyMiddleware>();
}
public async Task Invoke(DownstreamContext context)
{
await Proxy(context.HttpContext, context.DownstreamRequest.ToUri());
}
private async Task Proxy(HttpContext context, string serverEndpoint)
{
var wsToUpstreamClient = await context.WebSockets.AcceptWebSocketAsync();
var wsToDownstreamService = new ClientWebSocket();
var uri = new Uri(serverEndpoint);
await wsToDownstreamService.ConnectAsync(uri, CancellationToken.None);
var receiveFromUpstreamSendToDownstream = Task.Run(async () =>
{
var buffer = new byte[1024 * 4];
var receiveSegment = new ArraySegment<byte>(buffer);
while (wsToUpstreamClient.State == WebSocketState.Open || wsToUpstreamClient.State == WebSocketState.CloseSent)
{
var result = await wsToUpstreamClient.ReceiveAsync(receiveSegment, CancellationToken.None);
var sendSegment = new ArraySegment<byte>(buffer, 0, result.Count);
if(result.MessageType == WebSocketMessageType.Close)
{
await wsToUpstreamClient.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "",
CancellationToken.None);
await wsToDownstreamService.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "",
CancellationToken.None);
break;
}
await wsToDownstreamService.SendAsync(sendSegment, result.MessageType, result.EndOfMessage,
CancellationToken.None);
if (wsToUpstreamClient.State != WebSocketState.Open)
{
await wsToDownstreamService.CloseAsync(WebSocketCloseStatus.Empty, "",
CancellationToken.None);
break;
}
}
});
var receiveFromDownstreamAndSendToUpstream = Task.Run(async () =>
{
var buffer = new byte[1024 * 4];
while (wsToDownstreamService.State == WebSocketState.Open || wsToDownstreamService.State == WebSocketState.CloseSent)
{
if (wsToUpstreamClient.State != WebSocketState.Open)
{
break;
}
else
{
var receiveSegment = new ArraySegment<byte>(buffer);
var result = await wsToDownstreamService.ReceiveAsync(receiveSegment, CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
break;
}
var sendSegment = new ArraySegment<byte>(buffer, 0, result.Count);
//send to upstream client
await wsToUpstreamClient.SendAsync(sendSegment, result.MessageType, result.EndOfMessage,
CancellationToken.None);
}
}
});
await Task.WhenAll(receiveFromDownstreamAndSendToUpstream, receiveFromUpstreamSendToDownstream);
}
}
}

View File

@ -0,0 +1,12 @@
using Ocelot.Middleware.Pipeline;
namespace Ocelot.WebSockets.Middleware
{
public static class WebSocketsProxyMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseWebSocketsProxyMiddleware(this IOcelotPipelineBuilder builder)
{
return builder.UseMiddleware<WebSocketsProxyMiddleware>();
}
}
}