Merge branch 'release-5.3.0'

This commit is contained in:
Tom Gardham-Pallister 2018-03-24 09:48:59 +00:00
commit 2c4175580f
82 changed files with 1543 additions and 373 deletions

View File

@ -5,7 +5,7 @@
[![Windows Build history](https://buildstats.info/appveyor/chart/TomPallister/ocelot-fcfpb?branch=develop&includeBuildsFromPullRequest=false)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb/history?branch=develop) [![Windows Build history](https://buildstats.info/appveyor/chart/TomPallister/ocelot-fcfpb?branch=develop&includeBuildsFromPullRequest=false)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb/history?branch=develop)
[![Coverage Status](https://coveralls.io/repos/github/TomPallister/Ocelot/badge.svg?branch=develop)](https://coveralls.io/github/TomPallister/Ocelot?branch=develop) [![Coverage Status](https://coveralls.io/repos/github/ThreeMammals/Ocelot/badge.svg?branch=develop)](https://coveralls.io/github/ThreeMammals/Ocelot?branch=develop)
# Ocelot # Ocelot
@ -41,6 +41,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio
* Request Aggregation * Request Aggregation
* Service Discovery with Consul * Service Discovery with Consul
* Service Fabric * Service Fabric
* WebSockets
* Authentication * Authentication
* Authorisation * Authorisation
* Rate Limiting * Rate Limiting

View File

@ -12,3 +12,9 @@ Finally if logging is set to trace level Ocelot will log starting, finishing and
The reason for not just using bog standard framework logging is that I could not The reason for not just using bog standard framework logging is that I could not
work out how to override the request id that get's logged when setting IncludeScopes work out how to override the request id that get's logged when setting IncludeScopes
to true for logging settings. Nicely onto the next feature. to true for logging settings. Nicely onto the next feature.
Warning
^^^^^^^
If you are logging to Console you will get terrible performance. I have had so many issues about performance issues with Ocelot
and it is always logging level Debug, logging to Console :) Make sure you are logging to something proper in production :)

View File

@ -0,0 +1,68 @@
Websockets
==========
Ocelot supports proxying websockets with some extra bits. This functionality was requested in `Issue 212 <https://github.com/ThreeMammals/Ocelot/issues/212>`_.
In order to get websocket proxying working with Ocelot you need to do the following.
In your Configure method you need to tell your application to use WebSockets.
.. code-block:: csharp
Configure(app =>
{
app.UseWebSockets();
app.UseOcelot().Wait();
})
Then in your configuration.json add the following to proxy a ReRoute using websockets.
.. code-block:: json
{
"DownstreamPathTemplate": "/ws",
"UpstreamPathTemplate": "/",
"DownstreamScheme": "ws",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5001
}
],
}
With this configuration set Ocelot will match any websocket traffic that comes in on / and proxy it to localhost:5001/ws. To make this clearer
Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and
proxy these to the upstream client.
Supported
^^^^^^^^^
1. Load Balancer
2. Routing
3. Service Discovery
This means that you can set up your downstream services running websockets and either have multiple DownstreamHostAndPorts in your ReRoute
config or hook your ReRoute into a service discovery provider and then load balance requests...Which I think is pretty cool :)
Not Supported
^^^^^^^^^^^^^
Unfortunately a lot of Ocelot's features are non websocket specific such as header and http client stuff. I've listed what won't work below.
1. Tracing
2. RequestId
3. Request Aggregation
4. Rate Limiting
5. Quality of Service
6. Middleware Injection
7. Header Transformation
8. Delegating Handlers
9. Claims Transformation
10. Caching
11. Authentication - If anyone requests it we might be able to do something with basic authentication.
12. Authorisation
I'm not 100% sure what will happen with this feature when it get's into the wild so please make sure you test thoroughly!

View File

@ -25,6 +25,7 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n
features/servicefabric features/servicefabric
features/authentication features/authentication
features/authorisation features/authorisation
features/websockets
features/administration features/administration
features/ratelimiting features/ratelimiting
features/caching features/caching

View File

@ -1,7 +1,7 @@
Big Picture Big Picture
=========== ===========
Ocleot is aimed at people using .NET running Ocelot is aimed at people using .NET running
a micro services / service orientated architecture a micro services / service orientated architecture
that need a unified point of entry into their system. that need a unified point of entry into their system.

View File

@ -9,7 +9,7 @@ built to netcoreapp2.0 `this <https://docs.microsoft.com/en-us/dotnet/articles/s
**Install NuGet package** **Install NuGet package**
Install Ocelot and it's dependecies using nuget. You will need to create a netcoreapp2.0 projct and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections Install Ocelot and it's dependecies using nuget. You will need to create a netcoreapp2.0 project and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections
to get up and running. to get up and running.
``Install-Package Ocelot`` ``Install-Package Ocelot``

View File

@ -37,7 +37,7 @@ namespace Ocelot.Cache.Middleware
return; 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); _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 public class HeaderFindAndReplaceCreator : IHeaderFindAndReplaceCreator
{ {
private IPlaceholders _placeholders; private readonly IPlaceholders _placeholders;
private IOcelotLogger _logger; private readonly IOcelotLogger _logger;
public HeaderFindAndReplaceCreator(IPlaceholders placeholders, IOcelotLoggerFactory factory) public HeaderFindAndReplaceCreator(IPlaceholders placeholders, IOcelotLoggerFactory factory)
{ {
_logger = factory.CreateLogger<HeaderFindAndReplaceCreator>();; _logger = factory.CreateLogger<HeaderFindAndReplaceCreator>();
_placeholders = placeholders; _placeholders = placeholders;
} }

View File

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

View File

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

View File

@ -4,5 +4,4 @@
{ {
int Delay { get; } 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; return;
} }
UriBuilder uriBuilder; context.DownstreamRequest.Scheme = context.DownstreamReRoute.DownstreamScheme;
if (ServiceFabricRequest(context)) if (ServiceFabricRequest(context))
{ {
uriBuilder = CreateServiceFabricUri(context, dsPath); var pathAndQuery = CreateServiceFabricUri(context, dsPath);
context.DownstreamRequest.AbsolutePath = pathAndQuery.path;
context.DownstreamRequest.Query = pathAndQuery.query;
} }
else else
{ {
uriBuilder = new UriBuilder(context.DownstreamRequest.RequestUri) context.DownstreamRequest.AbsolutePath = dsPath.Data.Value;
{
Path = dsPath.Data.Value,
Scheme = context.DownstreamReRoute.DownstreamScheme
};
} }
context.DownstreamRequest.RequestUri = uriBuilder.Uri; _logger.LogDebug("downstream url is {context.DownstreamRequest}", context.DownstreamRequest);
_logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", context.DownstreamRequest.RequestUri);
await _next.Invoke(context); 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 query = context.DownstreamRequest.Query;
var scheme = context.DownstreamReRoute.DownstreamScheme;
var host = context.DownstreamRequest.RequestUri.Host;
var port = context.DownstreamRequest.RequestUri.Port;
var serviceFabricPath = $"/{context.DownstreamReRoute.ServiceName + dsPath.Data.Value}"; var serviceFabricPath = $"/{context.DownstreamReRoute.ServiceName + dsPath.Data.Value}";
Uri uri;
if (RequestForStatefullService(query)) if (RequestForStatefullService(query))
{ {
uri = new Uri($"{scheme}://{host}:{port}{serviceFabricPath}{query}"); return (serviceFabricPath, query);
}
else
{
var split = string.IsNullOrEmpty(query) ? "?" : "&";
uri = new Uri($"{scheme}://{host}:{port}{serviceFabricPath}{query}{split}cmd=instance");
} }
return new UriBuilder(uri); var split = string.IsNullOrEmpty(query) ? "?" : "&";
return (serviceFabricPath, $"{query}{split}cmd=instance");
} }
private static bool ServiceFabricRequest(DownstreamContext context) private static bool ServiceFabricRequest(DownstreamContext context)

View File

@ -5,6 +5,7 @@ using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Responses; using Ocelot.Responses;
using System.Net.Http; using System.Net.Http;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Request.Middleware;
namespace Ocelot.Headers namespace Ocelot.Headers
{ {
@ -17,7 +18,7 @@ namespace Ocelot.Headers
_claimsParser = claimsParser; _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) foreach (var config in claimsToThings)
{ {

View File

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

View File

@ -5,6 +5,7 @@ using System.Net.Http;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Infrastructure; using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Extensions; using Ocelot.Infrastructure.Extensions;
using Ocelot.Request.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Headers namespace Ocelot.Headers
@ -18,7 +19,7 @@ namespace Ocelot.Headers
_placeholders = placeholders; _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) foreach (var f in fAndRs)
{ {

View File

@ -6,10 +6,11 @@
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Request.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
public interface IAddHeadersToRequest 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.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Request.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Headers namespace Ocelot.Headers
{ {
public interface IHttpResponseHeaderReplacer 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 System.Net.Http;
using Ocelot.Request.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Infrastructure namespace Ocelot.Infrastructure
@ -6,6 +7,6 @@ namespace Ocelot.Infrastructure
public interface IPlaceholders public interface IPlaceholders
{ {
Response<string> Get(string key); 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 System.Net.Http;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Request.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Infrastructure namespace Ocelot.Infrastructure
{ {
public class Placeholders : IPlaceholders public class Placeholders : IPlaceholders
{ {
private Dictionary<string, Func<Response<string>>> _placeholders; private readonly Dictionary<string, Func<Response<string>>> _placeholders;
private Dictionary<string, Func<HttpRequestMessage, string>> _requestPlaceholders; private readonly Dictionary<string, Func<DownstreamRequest, string>> _requestPlaceholders;
private readonly IBaseUrlFinder _finder; private readonly IBaseUrlFinder _finder;
private readonly IRequestScopedDataRepository _repo; private readonly IRequestScopedDataRepository _repo;
@ -30,13 +31,13 @@ namespace Ocelot.Infrastructure
return new OkResponse<string>(traceId.Data); return new OkResponse<string>(traceId.Data);
}); });
_requestPlaceholders = new Dictionary<string, Func<HttpRequestMessage, string>>(); _requestPlaceholders = new Dictionary<string, Func<DownstreamRequest, string>>();
_requestPlaceholders.Add("{DownstreamBaseUrl}", x => { _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}/"; return $"{downstreamUrl}/";
@ -57,7 +58,7 @@ namespace Ocelot.Infrastructure
return new ErrorResponse<string>(new CouldNotFindPlaceholderError(key)); 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)) if(_requestPlaceholders.ContainsKey(key))
{ {

View File

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

View File

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

View File

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

View File

@ -12,6 +12,8 @@ using Microsoft.Extensions.DependencyInjection;
namespace Ocelot.Middleware.Pipeline namespace Ocelot.Middleware.Pipeline
{ {
using Predicate = Func<DownstreamContext, bool>;
public static class OcelotPipelineBuilderExtensions public static class OcelotPipelineBuilderExtensions
{ {
internal const string InvokeMethodName = "Invoke"; 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) private static Func<T, DownstreamContext, IServiceProvider, Task> Compile<T>(MethodInfo methodinfo, ParameterInfo[] parameters)
{ {
var middleware = typeof(T); var middleware = typeof(T);

View File

@ -15,18 +15,30 @@ using Ocelot.Request.Middleware;
using Ocelot.Requester.Middleware; using Ocelot.Requester.Middleware;
using Ocelot.RequestId.Middleware; using Ocelot.RequestId.Middleware;
using Ocelot.Responder.Middleware; using Ocelot.Responder.Middleware;
using Ocelot.WebSockets.Middleware;
namespace Ocelot.Middleware.Pipeline namespace Ocelot.Middleware.Pipeline
{ {
public static class OcelotPipelineExtensions public static class OcelotPipelineExtensions
{ {
public static OcelotRequestDelegate BuildOcelotPipeline(this IOcelotPipelineBuilder builder, public static OcelotRequestDelegate BuildOcelotPipeline(this IOcelotPipelineBuilder builder,
OcelotPipelineConfiguration pipelineConfiguration = null) OcelotPipelineConfiguration pipelineConfiguration)
{ {
// This is registered to catch any global exceptions that are not handled // This is registered to catch any global exceptions that are not handled
// It also sets the Request Id if anything is set globally // It also sets the Request Id if anything is set globally
builder.UseExceptionHandlerMiddleware(); 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. // Allow the user to respond with absolutely anything they want.
builder.UseIfNotNull(pipelineConfiguration.PreErrorResponderMiddleware); builder.UseIfNotNull(pipelineConfiguration.PreErrorResponderMiddleware);

View File

@ -7,6 +7,7 @@ using Ocelot.Responses;
using System.Security.Claims; using System.Security.Claims;
using System.Net.Http; using System.Net.Http;
using System; using System;
using Ocelot.Request.Middleware;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using System.Text; using System.Text;
@ -21,9 +22,9 @@ namespace Ocelot.QueryStrings
_claimsParser = claimsParser; _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) foreach (var config in claimsToThings)
{ {
@ -46,11 +47,7 @@ namespace Ocelot.QueryStrings
} }
} }
var uriBuilder = new UriBuilder(downstreamRequest.RequestUri); downstreamRequest.Query = ConvertDictionaryToQueryString(queryDictionary);
uriBuilder.Query = ConvertDictionaryToQueryString(queryDictionary);
downstreamRequest.RequestUri = uriBuilder.Uri;
return new OkResponse(); return new OkResponse();
} }

View File

@ -4,11 +4,12 @@ using Ocelot.Configuration;
using Ocelot.Responses; using Ocelot.Responses;
using System.Net.Http; using System.Net.Http;
using System.Security.Claims; using System.Security.Claims;
using Ocelot.Request.Middleware;
namespace Ocelot.QueryStrings namespace Ocelot.QueryStrings
{ {
public interface IAddQueriesToRequest 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 namespace Ocelot.Request.Middleware
{ {
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamRouteFinder.Middleware;
@ -31,7 +32,7 @@ namespace Ocelot.Request.Middleware
return; return;
} }
context.DownstreamRequest = downstreamRequest.Data; context.DownstreamRequest = new DownstreamRequest(downstreamRequest.Data);
await _next.Invoke(context); await _next.Invoke(context);
} }

View File

@ -9,6 +9,7 @@ using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.Request.Middleware;
namespace Ocelot.RequestId.Middleware namespace Ocelot.RequestId.Middleware
{ {
@ -82,7 +83,7 @@ namespace Ocelot.RequestId.Middleware
return headers.TryGetValues(requestId.RequestIdKey, out value); 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); httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue);
} }

View File

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

View File

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

View File

@ -1,4 +1,4 @@
namespace Ocelot.ServiceDiscovery namespace Ocelot.ServiceDiscovery.Configuration
{ {
public class ConsulRegistryConfiguration public class ConsulRegistryConfiguration
{ {

View File

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

View File

@ -1,6 +1,6 @@
using Ocelot.Errors; using Ocelot.Errors;
namespace Ocelot.ServiceDiscovery namespace Ocelot.ServiceDiscovery.Errors
{ {
public class UnableToFindServiceDiscoveryProviderError : Error public class UnableToFindServiceDiscoveryProviderError : Error
{ {

View File

@ -1,4 +1,5 @@
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.ServiceDiscovery.Providers;
namespace Ocelot.ServiceDiscovery namespace Ocelot.ServiceDiscovery
{ {

View File

@ -2,7 +2,7 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Values; using Ocelot.Values;
namespace Ocelot.ServiceDiscovery namespace Ocelot.ServiceDiscovery.Providers
{ {
public class ConfigurationServiceProvider : IServiceDiscoveryProvider public class ConfigurationServiceProvider : IServiceDiscoveryProvider
{ {

View File

@ -5,9 +5,10 @@ using System.Threading.Tasks;
using Consul; using Consul;
using Ocelot.Infrastructure.Extensions; using Ocelot.Infrastructure.Extensions;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.ServiceDiscovery.Configuration;
using Ocelot.Values; using Ocelot.Values;
namespace Ocelot.ServiceDiscovery namespace Ocelot.ServiceDiscovery.Providers
{ {
public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider
{ {

View File

@ -2,7 +2,7 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Values; using Ocelot.Values;
namespace Ocelot.ServiceDiscovery namespace Ocelot.ServiceDiscovery.Providers
{ {
public interface IServiceDiscoveryProvider public interface IServiceDiscoveryProvider
{ {

View File

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

View File

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

View File

@ -7,6 +7,6 @@
Value = value; 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; Value = value;
} }
public string Value { get; private set; } public string Value { get; }
} }
} }

View File

@ -17,14 +17,14 @@ namespace Ocelot.Values
Tags = tags; 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; DownstreamPort = downstreamPort;
} }
public string DownstreamHost { get; private set; } public string DownstreamHost { get; }
public int DownstreamPort { get; private set; }
public int DownstreamPort { get; }
} }
} }

View File

@ -9,6 +9,7 @@ namespace Ocelot.Values
} }
public string Template { get; } public string Template { get; }
public int Priority { 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>();
}
}
}

View File

@ -1,8 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net;
using Consul;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -28,7 +26,7 @@ namespace Ocelot.AcceptanceTests
} }
[Fact] [Fact]
public void should_use_service_discovery_and_load_balance_request() public void should_load_balance_request()
{ {
var downstreamServiceOneUrl = "http://localhost:50881"; var downstreamServiceOneUrl = "http://localhost:50881";
var downstreamServiceTwoUrl = "http://localhost:50892"; var downstreamServiceTwoUrl = "http://localhost:50892";
@ -74,18 +72,6 @@ namespace Ocelot.AcceptanceTests
.BDDfy(); .BDDfy();
} }
private void ThenOnlyOneServiceHasBeenCalled()
{
_counterOne.ShouldBe(10);
_counterTwo.ShouldBe(0);
}
private void GivenIResetCounters()
{
_counterOne = 0;
_counterTwo = 0;
}
private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top) private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top)
{ {
_counterOne.ShouldBeInRange(bottom, top); _counterOne.ShouldBeInRange(bottom, top);
@ -121,7 +107,7 @@ namespace Ocelot.AcceptanceTests
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(response); await context.Response.WriteAsync(response);
} }
catch (System.Exception exception) catch (Exception exception)
{ {
await context.Response.WriteAsync(exception.StackTrace); await context.Response.WriteAsync(exception.StackTrace);
} }

View File

@ -40,12 +40,49 @@ namespace Ocelot.AcceptanceTests
public string RequestIdKey = "OcRequestId"; public string RequestIdKey = "OcRequestId";
private readonly Random _random; private readonly Random _random;
private IWebHostBuilder _webHostBuilder; private IWebHostBuilder _webHostBuilder;
private WebHostBuilder _ocelotBuilder;
private IWebHost _ocelotHost;
public Steps() public Steps()
{ {
_random = new Random(); _random = new Random();
} }
public async Task StartFakeOcelotWithWebSockets()
{
_ocelotBuilder = new WebHostBuilder();
_ocelotBuilder.ConfigureServices(s =>
{
s.AddSingleton(_ocelotBuilder);
s.AddOcelot();
});
_ocelotBuilder.UseKestrel()
.UseUrls("http://localhost:5000")
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
config.AddJsonFile("configuration.json");
config.AddEnvironmentVariables();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
})
.Configure(app =>
{
app.UseWebSockets();
app.UseOcelot().Wait();
})
.UseIISIntegration();
_ocelotHost = _ocelotBuilder.Build();
await _ocelotHost.StartAsync();
}
public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
{ {
var configurationPath = TestConfiguration.ConfigurationPath; var configurationPath = TestConfiguration.ConfigurationPath;
@ -698,6 +735,7 @@ namespace Ocelot.AcceptanceTests
{ {
_ocelotClient?.Dispose(); _ocelotClient?.Dispose();
_ocelotServer?.Dispose(); _ocelotServer?.Dispose();
_ocelotHost?.Dispose();
} }
public void ThenTheRequestIdIsReturned() public void ThenTheRequestIdIsReturned()

View File

@ -0,0 +1,487 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.AcceptanceTests
{
public class WebSocketTests : IDisposable
{
private IWebHost _firstDownstreamHost;
private IWebHost _secondDownstreamHost;
private readonly List<string> _secondRecieved;
private readonly List<string> _firstRecieved;
private readonly List<ServiceEntry> _serviceEntries;
private readonly Steps _steps;
private IWebHost _fakeConsulBuilder;
public WebSocketTests()
{
_steps = new Steps();
_firstRecieved = new List<string>();
_secondRecieved = new List<string>();
_serviceEntries = new List<ServiceEntry>();
}
[Fact]
public async Task should_proxy_websocket_input_to_downstream_service()
{
var downstreamPort = 5001;
var downstreamHost = "localhost";
var config = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/",
DownstreamPathTemplate = "/ws",
DownstreamScheme = "ws",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = downstreamHost,
Port = downstreamPort
}
}
}
}
};
this.Given(_ => _steps.GivenThereIsAConfiguration(config))
.And(_ => _steps.StartFakeOcelotWithWebSockets())
.And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws"))
.When(_ => StartClient("ws://localhost:5000/"))
.Then(_ => _firstRecieved.Count.ShouldBe(10))
.BDDfy();
}
[Fact]
public async Task should_proxy_websocket_input_to_downstream_service_and_use_load_balancer()
{
var downstreamPort = 5005;
var downstreamHost = "localhost";
var secondDownstreamPort = 5006;
var secondDownstreamHost = "localhost";
var config = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/",
DownstreamPathTemplate = "/ws",
DownstreamScheme = "ws",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = downstreamHost,
Port = downstreamPort
},
new FileHostAndPort
{
Host = secondDownstreamHost,
Port = secondDownstreamPort
}
},
LoadBalancer = "RoundRobin"
}
}
};
this.Given(_ => _steps.GivenThereIsAConfiguration(config))
.And(_ => _steps.StartFakeOcelotWithWebSockets())
.And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws"))
.And(_ => StartSecondFakeDownstreamService($"http://{secondDownstreamHost}:{secondDownstreamPort}","/ws"))
.When(_ => WhenIStartTheClients())
.Then(_ => ThenBothDownstreamServicesAreCalled())
.BDDfy();
}
[Fact]
public async Task should_proxy_websocket_input_to_downstream_service_and_use_service_discovery_and_load_balancer()
{
var downstreamPort = 5007;
var downstreamHost = "localhost";
var secondDownstreamPort = 5008;
var secondDownstreamHost = "localhost";
var serviceName = "websockets";
var consulPort = 8509;
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = downstreamHost,
Port = downstreamPort,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
var serviceEntryTwo = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = secondDownstreamHost,
Port = secondDownstreamPort,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
var config = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/",
DownstreamPathTemplate = "/ws",
DownstreamScheme = "ws",
LoadBalancer = "RoundRobin",
ServiceName = serviceName,
UseServiceDiscovery = true
}
},
GlobalConfiguration = new FileGlobalConfiguration
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Host = "localhost",
Port = consulPort,
Type = "consul"
}
}
};
this.Given(_ => _steps.GivenThereIsAConfiguration(config))
.And(_ => _steps.StartFakeOcelotWithWebSockets())
.And(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
.And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
.And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws"))
.And(_ => StartSecondFakeDownstreamService($"http://{secondDownstreamHost}:{secondDownstreamPort}", "/ws"))
.When(_ => WhenIStartTheClients())
.Then(_ => ThenBothDownstreamServicesAreCalled())
.BDDfy();
}
private void ThenBothDownstreamServicesAreCalled()
{
_firstRecieved.Count.ShouldBe(10);
_firstRecieved.ForEach(x =>
{
x.ShouldBe("test");
});
_secondRecieved.Count.ShouldBe(10);
_secondRecieved.ForEach(x =>
{
x.ShouldBe("chocolate");
});
}
private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries)
{
foreach (var serviceEntry in serviceEntries)
{
_serviceEntries.Add(serviceEntry);
}
}
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName)
{
_fakeConsulBuilder = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
if (context.Request.Path.Value == $"/v1/health/service/{serviceName}")
{
await context.Response.WriteJsonAsync(_serviceEntries);
}
});
})
.Build();
_fakeConsulBuilder.Start();
}
private async Task WhenIStartTheClients()
{
var firstClient = StartClient("ws://localhost:5000/");
var secondClient = StartSecondClient("ws://localhost:5000/");
await Task.WhenAll(firstClient, secondClient);
}
private async Task StartClient(string url)
{
var client = new ClientWebSocket();
await client.ConnectAsync(new Uri(url), CancellationToken.None);
var sending = Task.Run(async () =>
{
string line = "test";
for (int i = 0; i < 10; i++)
{
var bytes = Encoding.UTF8.GetBytes(line);
await client.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true,
CancellationToken.None);
await Task.Delay(10);
}
await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
});
var receiving = Task.Run(async () =>
{
var buffer = new byte[1024 * 4];
while (true)
{
var result = await client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Text)
{
_firstRecieved.Add(Encoding.UTF8.GetString(buffer, 0, result.Count));
}
else if (result.MessageType == WebSocketMessageType.Close)
{
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
break;
}
}
});
await Task.WhenAll(sending, receiving);
}
private async Task StartSecondClient(string url)
{
await Task.Delay(500);
var client = new ClientWebSocket();
await client.ConnectAsync(new Uri(url), CancellationToken.None);
var sending = Task.Run(async () =>
{
string line = "test";
for (int i = 0; i < 10; i++)
{
var bytes = Encoding.UTF8.GetBytes(line);
await client.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true,
CancellationToken.None);
await Task.Delay(10);
}
await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
});
var receiving = Task.Run(async () =>
{
var buffer = new byte[1024 * 4];
while (true)
{
var result = await client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Text)
{
_secondRecieved.Add(Encoding.UTF8.GetString(buffer, 0, result.Count));
}
else if (result.MessageType == WebSocketMessageType.Close)
{
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
break;
}
}
});
await Task.WhenAll(sending, receiving);
}
private async Task StartFakeDownstreamService(string url, string path)
{
_firstDownstreamHost = new WebHostBuilder()
.ConfigureServices(s => { }).UseKestrel()
.UseUrls(url)
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
config.AddEnvironmentVariables();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
})
.Configure(app =>
{
app.UseWebSockets();
app.Use(async (context, next) =>
{
if (context.Request.Path == path)
{
if (context.WebSockets.IsWebSocketRequest)
{
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
await Echo(webSocket);
}
else
{
context.Response.StatusCode = 400;
}
}
else
{
await next();
}
});
})
.UseIISIntegration().Build();
await _firstDownstreamHost.StartAsync();
}
private async Task StartSecondFakeDownstreamService(string url, string path)
{
_secondDownstreamHost = new WebHostBuilder()
.ConfigureServices(s => { }).UseKestrel()
.UseUrls(url)
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
config.AddEnvironmentVariables();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
})
.Configure(app =>
{
app.UseWebSockets();
app.Use(async (context, next) =>
{
if (context.Request.Path == path)
{
if (context.WebSockets.IsWebSocketRequest)
{
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
await Message(webSocket);
}
else
{
context.Response.StatusCode = 400;
}
}
else
{
await next();
}
});
})
.UseIISIntegration().Build();
await _secondDownstreamHost.StartAsync();
}
private async Task Echo(WebSocket webSocket)
{
try
{
var buffer = new byte[1024 * 4];
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private async Task Message(WebSocket webSocket)
{
try
{
var buffer = new byte[1024 * 4];
var bytes = Encoding.UTF8.GetBytes("chocolate");
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
await webSocket.SendAsync(new ArraySegment<byte>(bytes), result.MessageType, result.EndOfMessage, CancellationToken.None);
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
public void Dispose()
{
_steps.Dispose();
_firstDownstreamHost?.Dispose();
_secondDownstreamHost?.Dispose();
_fakeConsulBuilder?.Dispose();
}
}
}

View File

@ -43,7 +43,7 @@ namespace Ocelot.UnitTests.Cache
}); });
_cacheManager = new OcelotCacheManagerCache<CachedResponse>(cacheManagerOutputCache); _cacheManager = new OcelotCacheManagerCache<CachedResponse>(cacheManagerOutputCache);
_downstreamContext = new DownstreamContext(new DefaultHttpContext()); _downstreamContext = new DownstreamContext(new DefaultHttpContext());
_downstreamContext.DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"); _downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"));
_next = context => Task.CompletedTask; _next = context => Task.CompletedTask;
_middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager, _regionCreator); _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager, _regionCreator);
} }

View File

@ -40,7 +40,7 @@ namespace Ocelot.UnitTests.Cache
_logger = new Mock<IOcelotLogger>(); _logger = new Mock<IOcelotLogger>();
_loggerFactory.Setup(x => x.CreateLogger<OutputCacheMiddleware>()).Returns(_logger.Object); _loggerFactory.Setup(x => x.CreateLogger<OutputCacheMiddleware>()).Returns(_logger.Object);
_next = context => Task.CompletedTask; _next = context => Task.CompletedTask;
_downstreamContext.DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"); _downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"));
} }
[Fact] [Fact]

View File

@ -20,6 +20,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
using Xunit; using Xunit;
using Shouldly; using Shouldly;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Request.Middleware;
public class DownstreamUrlCreatorMiddlewareTests public class DownstreamUrlCreatorMiddlewareTests
{ {
@ -30,6 +31,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
private DownstreamUrlCreatorMiddleware _middleware; private DownstreamUrlCreatorMiddleware _middleware;
private DownstreamContext _downstreamContext; private DownstreamContext _downstreamContext;
private OcelotRequestDelegate _next; private OcelotRequestDelegate _next;
private HttpRequestMessage _request;
public DownstreamUrlCreatorMiddlewareTests() public DownstreamUrlCreatorMiddlewareTests()
{ {
@ -38,7 +40,8 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
_logger = new Mock<IOcelotLogger>(); _logger = new Mock<IOcelotLogger>();
_loggerFactory.Setup(x => x.CreateLogger<DownstreamUrlCreatorMiddleware>()).Returns(_logger.Object); _loggerFactory.Setup(x => x.CreateLogger<DownstreamUrlCreatorMiddleware>()).Returns(_logger.Object);
_downstreamUrlTemplateVariableReplacer = new Mock<IDownstreamPathPlaceholderReplacer>(); _downstreamUrlTemplateVariableReplacer = new Mock<IDownstreamPathPlaceholderReplacer>();
_downstreamContext.DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123"); _request = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123");
_downstreamContext.DownstreamRequest = new DownstreamRequest(_request);
_next = context => Task.CompletedTask; _next = context => Task.CompletedTask;
} }
@ -208,7 +211,9 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
private void GivenTheDownstreamRequestUriIs(string uri) private void GivenTheDownstreamRequestUriIs(string uri)
{ {
_downstreamContext.DownstreamRequest.RequestUri = new Uri(uri); _request.RequestUri = new Uri(uri);
//todo - not sure if needed
_downstreamContext.DownstreamRequest = new DownstreamRequest(_request);
} }
private void GivenTheUrlReplacerWillReturn(string path) private void GivenTheUrlReplacerWillReturn(string path)
@ -221,7 +226,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
private void ThenTheDownstreamRequestUriIs(string expectedUri) private void ThenTheDownstreamRequestUriIs(string expectedUri)
{ {
_downstreamContext.DownstreamRequest.RequestUri.OriginalString.ShouldBe(expectedUri); _downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri);
} }
} }
} }

View File

@ -11,6 +11,7 @@ using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using System.Net.Http; using System.Net.Http;
using Ocelot.Request.Middleware;
namespace Ocelot.UnitTests.Headers namespace Ocelot.UnitTests.Headers
{ {
@ -18,7 +19,7 @@ namespace Ocelot.UnitTests.Headers
{ {
private readonly AddHeadersToRequest _addHeadersToRequest; private readonly AddHeadersToRequest _addHeadersToRequest;
private readonly Mock<IClaimsParser> _parser; private readonly Mock<IClaimsParser> _parser;
private readonly HttpRequestMessage _downstreamRequest; private readonly DownstreamRequest _downstreamRequest;
private List<Claim> _claims; private List<Claim> _claims;
private List<ClaimToThing> _configuration; private List<ClaimToThing> _configuration;
private Response _result; private Response _result;
@ -28,7 +29,7 @@ namespace Ocelot.UnitTests.Headers
{ {
_parser = new Mock<IClaimsParser>(); _parser = new Mock<IClaimsParser>();
_addHeadersToRequest = new AddHeadersToRequest(_parser.Object); _addHeadersToRequest = new AddHeadersToRequest(_parser.Object);
_downstreamRequest = new HttpRequestMessage(); _downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"));
} }
[Fact] [Fact]

View File

@ -16,6 +16,7 @@ using Ocelot.Middleware;
namespace Ocelot.UnitTests.Headers namespace Ocelot.UnitTests.Headers
{ {
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Request.Middleware;
public class HttpHeadersTransformationMiddlewareTests public class HttpHeadersTransformationMiddlewareTests
{ {
@ -68,7 +69,7 @@ namespace Ocelot.UnitTests.Headers
private void GivenTheDownstreamRequestIs() private void GivenTheDownstreamRequestIs()
{ {
_downstreamContext.DownstreamRequest = new HttpRequestMessage(); _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"));
} }
private void GivenTheHttpResponseMessageIs() private void GivenTheHttpResponseMessageIs()
@ -97,7 +98,7 @@ namespace Ocelot.UnitTests.Headers
private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly() private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()
{ {
_postReplacer.Verify(x => x.Replace(It.IsAny<HttpResponseMessage>(), It.IsAny<List<HeaderFindAndReplace>>(), It.IsAny<HttpRequestMessage>()), Times.Once); _postReplacer.Verify(x => x.Replace(It.IsAny<HttpResponseMessage>(), It.IsAny<List<HeaderFindAndReplace>>(), It.IsAny<DownstreamRequest>()), Times.Once);
} }
private void GivenTheFollowingRequest() private void GivenTheFollowingRequest()

View File

@ -14,6 +14,7 @@ namespace Ocelot.UnitTests.Headers
using Ocelot.Headers; using Ocelot.Headers;
using Ocelot.Headers.Middleware; using Ocelot.Headers.Middleware;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Request.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
@ -37,7 +38,7 @@ namespace Ocelot.UnitTests.Headers
_loggerFactory.Setup(x => x.CreateLogger<HttpRequestHeadersBuilderMiddleware>()).Returns(_logger.Object); _loggerFactory.Setup(x => x.CreateLogger<HttpRequestHeadersBuilderMiddleware>()).Returns(_logger.Object);
_next = context => Task.CompletedTask; _next = context => Task.CompletedTask;
_middleware = new HttpRequestHeadersBuilderMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); _middleware = new HttpRequestHeadersBuilderMiddleware(_next, _loggerFactory.Object, _addHeaders.Object);
_downstreamContext.DownstreamRequest = new HttpRequestMessage(); _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"));
} }
[Fact] [Fact]
@ -81,7 +82,7 @@ namespace Ocelot.UnitTests.Headers
.Setup(x => x.SetHeadersOnDownstreamRequest( .Setup(x => x.SetHeadersOnDownstreamRequest(
It.IsAny<List<ClaimToThing>>(), It.IsAny<List<ClaimToThing>>(),
It.IsAny<IEnumerable<System.Security.Claims.Claim>>(), It.IsAny<IEnumerable<System.Security.Claims.Claim>>(),
It.IsAny<HttpRequestMessage>())) It.IsAny<DownstreamRequest>()))
.Returns(new OkResponse()); .Returns(new OkResponse());
} }

View File

@ -11,6 +11,7 @@ using Moq;
using Ocelot.Infrastructure; using Ocelot.Infrastructure;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Request.Middleware;
namespace Ocelot.UnitTests.Headers namespace Ocelot.UnitTests.Headers
{ {
@ -21,7 +22,7 @@ namespace Ocelot.UnitTests.Headers
private HttpResponseHeaderReplacer _replacer; private HttpResponseHeaderReplacer _replacer;
private List<HeaderFindAndReplace> _headerFindAndReplaces; private List<HeaderFindAndReplace> _headerFindAndReplaces;
private Response _result; private Response _result;
private HttpRequestMessage _request; private DownstreamRequest _request;
private Mock<IBaseUrlFinder> _finder; private Mock<IBaseUrlFinder> _finder;
private Mock<IRequestScopedDataRepository> _repo; private Mock<IRequestScopedDataRepository> _repo;
@ -69,7 +70,7 @@ namespace Ocelot.UnitTests.Headers
{ {
var downstreamUrl = "http://downstream.com/"; var downstreamUrl = "http://downstream.com/";
var request = new HttpRequestMessage(); var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com");
request.RequestUri = new System.Uri(downstreamUrl); request.RequestUri = new System.Uri(downstreamUrl);
var response = new HttpResponseMessage(); var response = new HttpResponseMessage();
@ -91,7 +92,7 @@ namespace Ocelot.UnitTests.Headers
{ {
var downstreamUrl = "http://downstream.com/"; var downstreamUrl = "http://downstream.com/";
var request = new HttpRequestMessage(); var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com");
request.RequestUri = new System.Uri(downstreamUrl); request.RequestUri = new System.Uri(downstreamUrl);
var response = new HttpResponseMessage(); var response = new HttpResponseMessage();
@ -113,7 +114,7 @@ namespace Ocelot.UnitTests.Headers
{ {
var downstreamUrl = "http://downstream.com/test/product"; var downstreamUrl = "http://downstream.com/test/product";
var request = new HttpRequestMessage(); var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com");
request.RequestUri = new System.Uri(downstreamUrl); request.RequestUri = new System.Uri(downstreamUrl);
var response = new HttpResponseMessage(); var response = new HttpResponseMessage();
@ -135,7 +136,7 @@ namespace Ocelot.UnitTests.Headers
{ {
var downstreamUrl = "http://downstream.com/test/product"; var downstreamUrl = "http://downstream.com/test/product";
var request = new HttpRequestMessage(); var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com");
request.RequestUri = new System.Uri(downstreamUrl); request.RequestUri = new System.Uri(downstreamUrl);
var response = new HttpResponseMessage(); var response = new HttpResponseMessage();
@ -157,7 +158,7 @@ namespace Ocelot.UnitTests.Headers
{ {
var downstreamUrl = "http://downstream.com:123/test/product"; var downstreamUrl = "http://downstream.com:123/test/product";
var request = new HttpRequestMessage(); var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com");
request.RequestUri = new System.Uri(downstreamUrl); request.RequestUri = new System.Uri(downstreamUrl);
var response = new HttpResponseMessage(); var response = new HttpResponseMessage();
@ -179,7 +180,7 @@ namespace Ocelot.UnitTests.Headers
{ {
var downstreamUrl = "http://downstream.com:123/test/product"; var downstreamUrl = "http://downstream.com:123/test/product";
var request = new HttpRequestMessage(); var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com");
request.RequestUri = new System.Uri(downstreamUrl); request.RequestUri = new System.Uri(downstreamUrl);
var response = new HttpResponseMessage(); var response = new HttpResponseMessage();
@ -198,7 +199,7 @@ namespace Ocelot.UnitTests.Headers
private void GivenTheRequestIs(HttpRequestMessage request) private void GivenTheRequestIs(HttpRequestMessage request)
{ {
_request = request; _request = new DownstreamRequest(request);
} }
private void ThenTheHeadersAreNotReplaced() private void ThenTheHeadersAreNotReplaced()

View File

@ -4,6 +4,7 @@ using Moq;
using Ocelot.Infrastructure; using Ocelot.Infrastructure;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Request.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
using Shouldly; using Shouldly;
using Xunit; using Xunit;
@ -43,8 +44,9 @@ namespace Ocelot.UnitTests.Infrastructure
[Fact] [Fact]
public void should_return_downstream_base_url_when_port_is_not_80_or_443() public void should_return_downstream_base_url_when_port_is_not_80_or_443()
{ {
var request = new HttpRequestMessage(); var httpRequest = new HttpRequestMessage();
request.RequestUri = new Uri("http://www.bbc.co.uk"); httpRequest.RequestUri = new Uri("http://www.bbc.co.uk");
var request = new DownstreamRequest(httpRequest);
var result = _placeholders.Get("{DownstreamBaseUrl}", request); var result = _placeholders.Get("{DownstreamBaseUrl}", request);
result.Data.ShouldBe("http://www.bbc.co.uk/"); result.Data.ShouldBe("http://www.bbc.co.uk/");
} }
@ -53,8 +55,9 @@ namespace Ocelot.UnitTests.Infrastructure
[Fact] [Fact]
public void should_return_downstream_base_url_when_port_is_80_or_443() public void should_return_downstream_base_url_when_port_is_80_or_443()
{ {
var request = new HttpRequestMessage(); var httpRequest = new HttpRequestMessage();
request.RequestUri = new Uri("http://www.bbc.co.uk:123"); httpRequest.RequestUri = new Uri("http://www.bbc.co.uk:123");
var request = new DownstreamRequest(httpRequest);
var result = _placeholders.Get("{DownstreamBaseUrl}", request); var result = _placeholders.Get("{DownstreamBaseUrl}", request);
result.Data.ShouldBe("http://www.bbc.co.uk:123/"); result.Data.ShouldBe("http://www.bbc.co.uk:123/");
} }
@ -62,7 +65,8 @@ namespace Ocelot.UnitTests.Infrastructure
[Fact] [Fact]
public void should_return_key_does_not_exist_for_http_request_message() public void should_return_key_does_not_exist_for_http_request_message()
{ {
var result = _placeholders.Get("{Test}", new System.Net.Http.HttpRequestMessage()); var request = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://west.com"));
var result = _placeholders.Get("{Test}", request);
result.IsError.ShouldBeTrue(); result.IsError.ShouldBeTrue();
result.Errors[0].Message.ShouldBe("Unable to find placeholder called {Test}"); result.Errors[0].Message.ShouldBe("Unable to find placeholder called {Test}");
} }

View File

@ -0,0 +1,29 @@
using Xunit;
using Ocelot.Infrastructure.Extensions;
using Shouldly;
namespace Ocelot.UnitTests.Infrastructure
{
public class StringExtensionsTests
{
[Fact]
public void should_trim_start()
{
var test = "/string";
test = test.TrimStart("/");
test.ShouldBe("string");
}
[Fact]
public void should_return_source()
{
var test = "string";
test = test.LastCharAsForwardSlash();
test.ShouldBe("string/");
}
}
}

View File

@ -5,6 +5,7 @@ using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
using Shouldly; using Shouldly;
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.ServiceDiscovery.Providers;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;

View File

@ -13,6 +13,7 @@ namespace Ocelot.UnitTests.LoadBalancer
using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.LoadBalancer.Middleware; using Ocelot.LoadBalancer.Middleware;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Request.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Values; using Ocelot.Values;
using Shouldly; using Shouldly;
@ -39,13 +40,13 @@ namespace Ocelot.UnitTests.LoadBalancer
_loadBalancerHouse = new Mock<ILoadBalancerHouse>(); _loadBalancerHouse = new Mock<ILoadBalancerHouse>();
_loadBalancer = new Mock<ILoadBalancer>(); _loadBalancer = new Mock<ILoadBalancer>();
_loadBalancerHouse = new Mock<ILoadBalancerHouse>(); _loadBalancerHouse = new Mock<ILoadBalancerHouse>();
_downstreamRequest = new HttpRequestMessage(HttpMethod.Get, ""); _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "http://test.com/");
_downstreamContext = new DownstreamContext(new DefaultHttpContext()); _downstreamContext = new DownstreamContext(new DefaultHttpContext());
_loggerFactory = new Mock<IOcelotLoggerFactory>(); _loggerFactory = new Mock<IOcelotLoggerFactory>();
_logger = new Mock<IOcelotLogger>(); _logger = new Mock<IOcelotLogger>();
_loggerFactory.Setup(x => x.CreateLogger<LoadBalancingMiddleware>()).Returns(_logger.Object); _loggerFactory.Setup(x => x.CreateLogger<LoadBalancingMiddleware>()).Returns(_logger.Object);
_next = context => Task.CompletedTask; _next = context => Task.CompletedTask;
_downstreamContext.DownstreamRequest = _downstreamRequest; _downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest);
} }
[Fact] [Fact]
@ -122,6 +123,7 @@ namespace Ocelot.UnitTests.LoadBalancer
private void GivenTheDownStreamUrlIs(string downstreamUrl) private void GivenTheDownStreamUrlIs(string downstreamUrl)
{ {
_downstreamRequest.RequestUri = new System.Uri(downstreamUrl); _downstreamRequest.RequestUri = new System.Uri(downstreamUrl);
_downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest);
} }
private void GivenTheLoadBalancerReturnsAnError() private void GivenTheLoadBalancerReturnsAnError()
@ -185,7 +187,7 @@ namespace Ocelot.UnitTests.LoadBalancer
private void ThenTheDownstreamUrlIsReplacedWith(string expectedUri) private void ThenTheDownstreamUrlIsReplacedWith(string expectedUri)
{ {
_downstreamContext.DownstreamRequest.RequestUri.OriginalString.ShouldBe(expectedUri); _downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri);
} }
} }
} }

View File

@ -9,6 +9,7 @@ using Ocelot.Configuration.Builder;
using Ocelot.Errors; using Ocelot.Errors;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer; using Ocelot.Middleware.Multiplexer;
using Ocelot.Request.Middleware;
using Ocelot.UnitTests.Responder; using Ocelot.UnitTests.Responder;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
@ -48,7 +49,7 @@ namespace Ocelot.UnitTests.Middleware
new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Bill says hi") }, new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Bill says hi") },
DownstreamReRoute = billDownstreamReRoute, DownstreamReRoute = billDownstreamReRoute,
Errors = new List<Error> { new AnyError() }, Errors = new List<Error> { new AnyError() },
DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.bbc.co.uk")), DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.bbc.co.uk"))),
}; };
var downstreamContexts = new List<DownstreamContext> { billDownstreamContext }; var downstreamContexts = new List<DownstreamContext> { billDownstreamContext };

View File

@ -12,24 +12,27 @@ using TestStack.BDDfy;
using Xunit; using Xunit;
using System.Net.Http; using System.Net.Http;
using System; using System;
using Ocelot.Request.Middleware;
namespace Ocelot.UnitTests.QueryStrings namespace Ocelot.UnitTests.QueryStrings
{ {
public class AddQueriesToRequestTests public class AddQueriesToRequestTests
{ {
private readonly AddQueriesToRequest _addQueriesToRequest; private readonly AddQueriesToRequest _addQueriesToRequest;
private HttpRequestMessage _downstreamRequest; private DownstreamRequest _downstreamRequest;
private readonly Mock<IClaimsParser> _parser; private readonly Mock<IClaimsParser> _parser;
private List<ClaimToThing> _configuration; private List<ClaimToThing> _configuration;
private List<Claim> _claims; private List<Claim> _claims;
private Response _result; private Response _result;
private Response<string> _claimValue; private Response<string> _claimValue;
private HttpRequestMessage _request;
public AddQueriesToRequestTests() public AddQueriesToRequestTests()
{ {
_request = new HttpRequestMessage(HttpMethod.Post, "http://my.url/abc?q=123");
_parser = new Mock<IClaimsParser>(); _parser = new Mock<IClaimsParser>();
_addQueriesToRequest = new AddQueriesToRequest(_parser.Object); _addQueriesToRequest = new AddQueriesToRequest(_parser.Object);
_downstreamRequest = new HttpRequestMessage(HttpMethod.Post, "http://my.url/abc?q=123"); _downstreamRequest = new DownstreamRequest(_request);
} }
[Fact] [Fact]
@ -78,7 +81,7 @@ namespace Ocelot.UnitTests.QueryStrings
private void TheTheQueryStringIs(string expected) private void TheTheQueryStringIs(string expected)
{ {
_downstreamRequest.RequestUri.Query.ShouldBe(expected); _downstreamRequest.Query.ShouldBe(expected);
} }
[Fact] [Fact]
@ -123,7 +126,7 @@ namespace Ocelot.UnitTests.QueryStrings
private void ThenTheQueryIsAdded() private void ThenTheQueryIsAdded()
{ {
var queries = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(_downstreamRequest.RequestUri.OriginalString); var queries = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(_downstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString);
var query = queries.First(x => x.Key == "query-key"); var query = queries.First(x => x.Key == "query-key");
query.Value.First().ShouldBe(_claimValue.Data); query.Value.First().ShouldBe(_claimValue.Data);
} }
@ -140,15 +143,18 @@ namespace Ocelot.UnitTests.QueryStrings
private void GivenTheDownstreamRequestHasQueryString(string queryString) private void GivenTheDownstreamRequestHasQueryString(string queryString)
{ {
_downstreamRequest = new HttpRequestMessage(HttpMethod.Post, $"http://my.url/abc{queryString}"); _request = new HttpRequestMessage(HttpMethod.Post, $"http://my.url/abc{queryString}");
_downstreamRequest = new DownstreamRequest(_request);
} }
private void GivenTheDownstreamRequestHasQueryString(string key, string value) private void GivenTheDownstreamRequestHasQueryString(string key, string value)
{ {
var newUri = Microsoft.AspNetCore.WebUtilities.QueryHelpers var newUri = Microsoft.AspNetCore.WebUtilities.QueryHelpers
.AddQueryString(_downstreamRequest.RequestUri.OriginalString, key, value); .AddQueryString(_downstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString, key, value);
_downstreamRequest.RequestUri = new Uri(newUri); _request.RequestUri = new Uri(newUri);
//todo - might not need to instanciate
_downstreamRequest = new DownstreamRequest(_request);
} }
private void GivenTheClaimParserReturns(Response<string> claimValue) private void GivenTheClaimParserReturns(Response<string> claimValue)

View File

@ -18,6 +18,7 @@ namespace Ocelot.UnitTests.QueryStrings
using System.Security.Claims; using System.Security.Claims;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Request.Middleware;
public class QueryStringBuilderMiddlewareTests public class QueryStringBuilderMiddlewareTests
{ {
@ -36,7 +37,7 @@ namespace Ocelot.UnitTests.QueryStrings
_loggerFactory.Setup(x => x.CreateLogger<QueryStringBuilderMiddleware>()).Returns(_logger.Object); _loggerFactory.Setup(x => x.CreateLogger<QueryStringBuilderMiddleware>()).Returns(_logger.Object);
_next = context => Task.CompletedTask; _next = context => Task.CompletedTask;
_addQueries = new Mock<IAddQueriesToRequest>(); _addQueries = new Mock<IAddQueriesToRequest>();
_downstreamContext.DownstreamRequest = new HttpRequestMessage(); _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"));
_middleware = new QueryStringBuilderMiddleware(_next, _loggerFactory.Object, _addQueries.Object); _middleware = new QueryStringBuilderMiddleware(_next, _loggerFactory.Object, _addQueries.Object);
} }
@ -74,7 +75,7 @@ namespace Ocelot.UnitTests.QueryStrings
.Setup(x => x.SetQueriesOnDownstreamRequest( .Setup(x => x.SetQueriesOnDownstreamRequest(
It.IsAny<List<ClaimToThing>>(), It.IsAny<List<ClaimToThing>>(),
It.IsAny<IEnumerable<Claim>>(), It.IsAny<IEnumerable<Claim>>(),
It.IsAny<HttpRequestMessage>())) It.IsAny<DownstreamRequest>()))
.Returns(new OkResponse()); .Returns(new OkResponse());
} }

View File

@ -18,6 +18,7 @@ namespace Ocelot.UnitTests.RateLimit
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Request.Middleware;
public class ClientRateLimitMiddlewareTests public class ClientRateLimitMiddlewareTests
{ {
@ -100,7 +101,7 @@ namespace Ocelot.UnitTests.RateLimit
{ {
var request = new HttpRequestMessage(new HttpMethod("GET"), _url); var request = new HttpRequestMessage(new HttpMethod("GET"), _url);
request.Headers.Add("ClientId", clientId); request.Headers.Add("ClientId", clientId);
_downstreamContext.DownstreamRequest = request; _downstreamContext.DownstreamRequest = new DownstreamRequest(request);
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
_responseStatusCode = (int)_downstreamContext.HttpContext.Response.StatusCode; _responseStatusCode = (int)_downstreamContext.HttpContext.Response.StatusCode;
@ -115,7 +116,7 @@ namespace Ocelot.UnitTests.RateLimit
{ {
var request = new HttpRequestMessage(new HttpMethod("GET"), _url); var request = new HttpRequestMessage(new HttpMethod("GET"), _url);
request.Headers.Add("ClientId", clientId); request.Headers.Add("ClientId", clientId);
_downstreamContext.DownstreamRequest = request; _downstreamContext.DownstreamRequest = new DownstreamRequest(request);
_downstreamContext.HttpContext.Request.Headers.TryAdd("ClientId", clientId); _downstreamContext.HttpContext.Request.Headers.TryAdd("ClientId", clientId);
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();

View File

@ -88,7 +88,7 @@ namespace Ocelot.UnitTests.Request
private void GivenTheMapperWillReturnAMappedRequest() private void GivenTheMapperWillReturnAMappedRequest()
{ {
_mappedRequest = new OkResponse<HttpRequestMessage>(new HttpRequestMessage()); _mappedRequest = new OkResponse<HttpRequestMessage>(new HttpRequestMessage(HttpMethod.Get, "http://www.bbc.co.uk"));
_requestMapper _requestMapper
.Setup(rm => rm.Map(It.IsAny<HttpRequest>())) .Setup(rm => rm.Map(It.IsAny<HttpRequest>()))

View File

@ -20,6 +20,7 @@ namespace Ocelot.UnitTests.RequestId
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using Ocelot.Request.Middleware;
public class ReRouteRequestIdMiddlewareTests public class ReRouteRequestIdMiddlewareTests
{ {
@ -35,7 +36,7 @@ namespace Ocelot.UnitTests.RequestId
public ReRouteRequestIdMiddlewareTests() public ReRouteRequestIdMiddlewareTests()
{ {
_downstreamRequest = new HttpRequestMessage(); _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "http://test.com");
_repo = new Mock<IRequestScopedDataRepository>(); _repo = new Mock<IRequestScopedDataRepository>();
_downstreamContext = new DownstreamContext(new DefaultHttpContext()); _downstreamContext = new DownstreamContext(new DefaultHttpContext());
_loggerFactory = new Mock<IOcelotLoggerFactory>(); _loggerFactory = new Mock<IOcelotLoggerFactory>();
@ -47,7 +48,7 @@ namespace Ocelot.UnitTests.RequestId
return Task.CompletedTask; return Task.CompletedTask;
}; };
_middleware = new ReRouteRequestIdMiddleware(_next, _loggerFactory.Object, _repo.Object); _middleware = new ReRouteRequestIdMiddleware(_next, _loggerFactory.Object, _repo.Object);
_downstreamContext.DownstreamRequest = _downstreamRequest; _downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest);
} }
[Fact] [Fact]

View File

@ -14,6 +14,7 @@ using Ocelot.Configuration;
using Ocelot.Configuration.Builder; using Ocelot.Configuration.Builder;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Request.Middleware;
using Ocelot.Requester; using Ocelot.Requester;
using Ocelot.Responses; using Ocelot.Responses;
using Shouldly; using Shouldly;
@ -170,7 +171,7 @@ namespace Ocelot.UnitTests.Requester
var context = new DownstreamContext(new DefaultHttpContext()) var context = new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamReRoute = downstream, DownstreamReRoute = downstream,
DownstreamRequest = new HttpRequestMessage() { RequestUri = new Uri("http://localhost:5003") }, DownstreamRequest = new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri("http://localhost:5003") }),
}; };
_context = context; _context = context;

View File

@ -12,13 +12,16 @@ using Ocelot.Middleware;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using Shouldly; using Shouldly;
using Ocelot.Request.Middleware;
using System.Threading.Tasks;
using System.Threading;
namespace Ocelot.UnitTests.Requester namespace Ocelot.UnitTests.Requester
{ {
public class HttpClientHttpRequesterTest public class HttpClientHttpRequesterTest
{ {
private readonly Mock<IHttpClientCache> _cacheHandlers; private readonly Mock<IHttpClientCache> _cacheHandlers;
private Mock<IDelegatingHandlerHandlerFactory> _house; private Mock<IDelegatingHandlerHandlerFactory> _factory;
private Response<HttpResponseMessage> _response; private Response<HttpResponseMessage> _response;
private readonly HttpClientHttpRequester _httpClientRequester; private readonly HttpClientHttpRequester _httpClientRequester;
private DownstreamContext _request; private DownstreamContext _request;
@ -27,8 +30,8 @@ namespace Ocelot.UnitTests.Requester
public HttpClientHttpRequesterTest() public HttpClientHttpRequesterTest()
{ {
_house = new Mock<IDelegatingHandlerHandlerFactory>(); _factory = new Mock<IDelegatingHandlerHandlerFactory>();
_house.Setup(x => x.Get(It.IsAny<DownstreamReRoute>())).Returns(new OkResponse<List<Func<DelegatingHandler>>>(new List<Func<DelegatingHandler>>())); _factory.Setup(x => x.Get(It.IsAny<DownstreamReRoute>())).Returns(new OkResponse<List<Func<DelegatingHandler>>>(new List<Func<DelegatingHandler>>()));
_logger = new Mock<IOcelotLogger>(); _logger = new Mock<IOcelotLogger>();
_loggerFactory = new Mock<IOcelotLoggerFactory>(); _loggerFactory = new Mock<IOcelotLoggerFactory>();
_loggerFactory _loggerFactory
@ -38,7 +41,7 @@ namespace Ocelot.UnitTests.Requester
_httpClientRequester = new HttpClientHttpRequester( _httpClientRequester = new HttpClientHttpRequester(
_loggerFactory.Object, _loggerFactory.Object,
_cacheHandlers.Object, _cacheHandlers.Object,
_house.Object); _factory.Object);
} }
[Fact] [Fact]
@ -50,10 +53,11 @@ namespace Ocelot.UnitTests.Requester
var context = new DownstreamContext(new DefaultHttpContext()) var context = new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamReRoute = reRoute, DownstreamReRoute = reRoute,
DownstreamRequest = new HttpRequestMessage() { RequestUri = new Uri("http://www.bbc.co.uk") }, DownstreamRequest = new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri("http://www.bbc.co.uk") }),
}; };
this.Given(x=>x.GivenTheRequestIs(context)) this.Given(x=>x.GivenTheRequestIs(context))
.And(x => GivenTheHouseReturnsOkHandler())
.When(x=>x.WhenIGetResponse()) .When(x=>x.WhenIGetResponse())
.Then(x => x.ThenTheResponseIsCalledCorrectly()) .Then(x => x.ThenTheResponseIsCalledCorrectly())
.BDDfy(); .BDDfy();
@ -68,7 +72,7 @@ namespace Ocelot.UnitTests.Requester
var context = new DownstreamContext(new DefaultHttpContext()) var context = new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamReRoute = reRoute, DownstreamReRoute = reRoute,
DownstreamRequest = new HttpRequestMessage() { RequestUri = new Uri("http://localhost:60080") }, DownstreamRequest = new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri("http://localhost:60080") }),
}; };
this.Given(x => x.GivenTheRequestIs(context)) this.Given(x => x.GivenTheRequestIs(context))
@ -96,5 +100,23 @@ namespace Ocelot.UnitTests.Requester
{ {
_response.IsError.ShouldBeTrue(); _response.IsError.ShouldBeTrue();
} }
private void GivenTheHouseReturnsOkHandler()
{
var handlers = new List<Func<DelegatingHandler>>
{
() => new OkDelegatingHandler()
};
_factory.Setup(x => x.Get(It.IsAny<DownstreamReRoute>())).Returns(new OkResponse<List<Func<DelegatingHandler>>>(handlers));
}
class OkDelegatingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(new HttpResponseMessage());
}
}
} }
} }

View File

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
using Ocelot.ServiceDiscovery.Providers;
using Ocelot.Values; using Ocelot.Values;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;

View File

@ -9,6 +9,8 @@ using Microsoft.AspNetCore.Http;
using Moq; using Moq;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
using Ocelot.ServiceDiscovery.Configuration;
using Ocelot.ServiceDiscovery.Providers;
using Ocelot.Values; using Ocelot.Values;
using Xunit; using Xunit;
using TestStack.BDDfy; using TestStack.BDDfy;

View File

@ -1,4 +1,7 @@
namespace Ocelot.UnitTests.ServiceDiscovery using Ocelot.ServiceDiscovery.Configuration;
using Ocelot.ServiceDiscovery.Providers;
namespace Ocelot.UnitTests.ServiceDiscovery
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;

View File

@ -5,6 +5,7 @@ using Ocelot.Configuration;
using Ocelot.Configuration.Builder; using Ocelot.Configuration.Builder;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
using Ocelot.ServiceDiscovery.Providers;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;

View File

@ -0,0 +1,239 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Websockets
{
public class WebSocketsProxyMiddlewareTests : IDisposable
{
private IWebHost _firstDownstreamHost;
private readonly List<string> _firstRecieved;
private WebHostBuilder _ocelotBuilder;
private IWebHost _ocelotHost;
public WebSocketsProxyMiddlewareTests()
{
_firstRecieved = new List<string>();
}
[Fact]
public async Task should_proxy_websocket_input_to_downstream_service()
{
var downstreamPort = 5001;
var downstreamHost = "localhost";
var config = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/",
DownstreamPathTemplate = "/ws",
DownstreamScheme = "ws",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = downstreamHost,
Port = downstreamPort
}
}
}
}
};
this.Given(_ => GivenThereIsAConfiguration(config))
.And(_ => StartFakeOcelotWithWebSockets())
.And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws"))
.When(_ => StartClient("ws://localhost:5000/"))
.Then(_ => _firstRecieved.Count.ShouldBe(10))
.BDDfy();
}
public void Dispose()
{
_firstDownstreamHost?.Dispose();
}
public async Task StartFakeOcelotWithWebSockets()
{
_ocelotBuilder = new WebHostBuilder();
_ocelotBuilder.ConfigureServices(s =>
{
s.AddSingleton(_ocelotBuilder);
s.AddOcelot();
});
_ocelotBuilder.UseKestrel()
.UseUrls("http://localhost:5000")
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
config.AddJsonFile("configuration.json");
config.AddEnvironmentVariables();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
})
.Configure(app =>
{
app.UseWebSockets();
app.UseOcelot().Wait();
})
.UseIISIntegration();
_ocelotHost = _ocelotBuilder.Build();
await _ocelotHost.StartAsync();
}
public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
{
var configurationPath = Path.Combine(AppContext.BaseDirectory, "configuration.json");
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
if (File.Exists(configurationPath))
{
File.Delete(configurationPath);
}
File.WriteAllText(configurationPath, jsonConfiguration);
}
private async Task StartFakeDownstreamService(string url, string path)
{
_firstDownstreamHost = new WebHostBuilder()
.ConfigureServices(s => { }).UseKestrel()
.UseUrls(url)
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
config.AddEnvironmentVariables();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
})
.Configure(app =>
{
app.UseWebSockets();
app.Use(async (context, next) =>
{
if (context.Request.Path == path)
{
if (context.WebSockets.IsWebSocketRequest)
{
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
await Echo(webSocket);
}
else
{
context.Response.StatusCode = 400;
}
}
else
{
await next();
}
});
})
.UseIISIntegration().Build();
await _firstDownstreamHost.StartAsync();
}
private async Task StartClient(string url)
{
var client = new ClientWebSocket();
await client.ConnectAsync(new Uri(url), CancellationToken.None);
var sending = Task.Run(async () =>
{
string line = "test";
for (int i = 0; i < 10; i++)
{
var bytes = Encoding.UTF8.GetBytes(line);
await client.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true,
CancellationToken.None);
await Task.Delay(10);
}
await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
});
var receiving = Task.Run(async () =>
{
var buffer = new byte[1024 * 4];
while (true)
{
var result = await client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Text)
{
_firstRecieved.Add(Encoding.UTF8.GetString(buffer, 0, result.Count));
}
else if (result.MessageType == WebSocketMessageType.Close)
{
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
break;
}
}
});
await Task.WhenAll(sending, receiving);
}
private async Task Echo(WebSocket webSocket)
{
try
{
var buffer = new byte[1024 * 4];
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
}