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)
[![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
@ -41,6 +41,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio
* Request Aggregation
* Service Discovery with Consul
* Service Fabric
* WebSockets
* Authentication
* Authorisation
* 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
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.
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/authentication
features/authorisation
features/websockets
features/administration
features/ratelimiting
features/caching

View File

@ -1,7 +1,7 @@
Big Picture
===========
Ocleot is aimed at people using .NET running
Ocelot is aimed at people using .NET running
a micro services / service orientated architecture
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 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.
``Install-Package Ocelot``

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,14 +17,14 @@ namespace Ocelot.Values
Tags = tags;
}
public string Id { get; private set; }
public string Id { get; }
public string Name { get; private set; }
public string Name { get; }
public string Version { get; private set; }
public string Version { get; }
public IEnumerable<string> Tags { get; private set; }
public IEnumerable<string> Tags { get; }
public ServiceHostAndPort HostAndPort { get; private set; }
public ServiceHostAndPort HostAndPort { get; }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -40,12 +40,49 @@ namespace Ocelot.AcceptanceTests
public string RequestIdKey = "OcRequestId";
private readonly Random _random;
private IWebHostBuilder _webHostBuilder;
private WebHostBuilder _ocelotBuilder;
private IWebHost _ocelotHost;
public Steps()
{
_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)
{
var configurationPath = TestConfiguration.ConfigurationPath;
@ -698,6 +735,7 @@ namespace Ocelot.AcceptanceTests
{
_ocelotClient?.Dispose();
_ocelotServer?.Dispose();
_ocelotHost?.Dispose();
}
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);
_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;
_middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager, _regionCreator);
}

View File

@ -40,7 +40,7 @@ namespace Ocelot.UnitTests.Cache
_logger = new Mock<IOcelotLogger>();
_loggerFactory.Setup(x => x.CreateLogger<OutputCacheMiddleware>()).Returns(_logger.Object);
_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]

View File

@ -20,6 +20,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
using Xunit;
using Shouldly;
using Microsoft.AspNetCore.Http;
using Ocelot.Request.Middleware;
public class DownstreamUrlCreatorMiddlewareTests
{
@ -30,6 +31,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
private DownstreamUrlCreatorMiddleware _middleware;
private DownstreamContext _downstreamContext;
private OcelotRequestDelegate _next;
private HttpRequestMessage _request;
public DownstreamUrlCreatorMiddlewareTests()
{
@ -38,7 +40,8 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
_logger = new Mock<IOcelotLogger>();
_loggerFactory.Setup(x => x.CreateLogger<DownstreamUrlCreatorMiddleware>()).Returns(_logger.Object);
_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;
}
@ -208,7 +211,9 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
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)
@ -221,7 +226,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
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 Xunit;
using System.Net.Http;
using Ocelot.Request.Middleware;
namespace Ocelot.UnitTests.Headers
{
@ -18,7 +19,7 @@ namespace Ocelot.UnitTests.Headers
{
private readonly AddHeadersToRequest _addHeadersToRequest;
private readonly Mock<IClaimsParser> _parser;
private readonly HttpRequestMessage _downstreamRequest;
private readonly DownstreamRequest _downstreamRequest;
private List<Claim> _claims;
private List<ClaimToThing> _configuration;
private Response _result;
@ -28,7 +29,7 @@ namespace Ocelot.UnitTests.Headers
{
_parser = new Mock<IClaimsParser>();
_addHeadersToRequest = new AddHeadersToRequest(_parser.Object);
_downstreamRequest = new HttpRequestMessage();
_downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"));
}
[Fact]

View File

@ -16,6 +16,7 @@ using Ocelot.Middleware;
namespace Ocelot.UnitTests.Headers
{
using System.Threading.Tasks;
using Ocelot.Request.Middleware;
public class HttpHeadersTransformationMiddlewareTests
{
@ -68,7 +69,7 @@ namespace Ocelot.UnitTests.Headers
private void GivenTheDownstreamRequestIs()
{
_downstreamContext.DownstreamRequest = new HttpRequestMessage();
_downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"));
}
private void GivenTheHttpResponseMessageIs()
@ -97,7 +98,7 @@ namespace Ocelot.UnitTests.Headers
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()

View File

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

View File

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

View File

@ -4,6 +4,7 @@ using Moq;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware;
using Ocelot.Request.Middleware;
using Ocelot.Responses;
using Shouldly;
using Xunit;
@ -43,8 +44,9 @@ namespace Ocelot.UnitTests.Infrastructure
[Fact]
public void should_return_downstream_base_url_when_port_is_not_80_or_443()
{
var request = new HttpRequestMessage();
request.RequestUri = new Uri("http://www.bbc.co.uk");
var httpRequest = new HttpRequestMessage();
httpRequest.RequestUri = new Uri("http://www.bbc.co.uk");
var request = new DownstreamRequest(httpRequest);
var result = _placeholders.Get("{DownstreamBaseUrl}", request);
result.Data.ShouldBe("http://www.bbc.co.uk/");
}
@ -53,8 +55,9 @@ namespace Ocelot.UnitTests.Infrastructure
[Fact]
public void should_return_downstream_base_url_when_port_is_80_or_443()
{
var request = new HttpRequestMessage();
request.RequestUri = new Uri("http://www.bbc.co.uk:123");
var httpRequest = new HttpRequestMessage();
httpRequest.RequestUri = new Uri("http://www.bbc.co.uk:123");
var request = new DownstreamRequest(httpRequest);
var result = _placeholders.Get("{DownstreamBaseUrl}", request);
result.Data.ShouldBe("http://www.bbc.co.uk:123/");
}
@ -62,7 +65,8 @@ namespace Ocelot.UnitTests.Infrastructure
[Fact]
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.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 Shouldly;
using System.Collections.Generic;
using Ocelot.ServiceDiscovery.Providers;
using TestStack.BDDfy;
using Xunit;

View File

@ -13,6 +13,7 @@ namespace Ocelot.UnitTests.LoadBalancer
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.LoadBalancer.Middleware;
using Ocelot.Logging;
using Ocelot.Request.Middleware;
using Ocelot.Responses;
using Ocelot.Values;
using Shouldly;
@ -39,13 +40,13 @@ namespace Ocelot.UnitTests.LoadBalancer
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
_loadBalancer = new Mock<ILoadBalancer>();
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
_downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "");
_downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "http://test.com/");
_downstreamContext = new DownstreamContext(new DefaultHttpContext());
_loggerFactory = new Mock<IOcelotLoggerFactory>();
_logger = new Mock<IOcelotLogger>();
_loggerFactory.Setup(x => x.CreateLogger<LoadBalancingMiddleware>()).Returns(_logger.Object);
_next = context => Task.CompletedTask;
_downstreamContext.DownstreamRequest = _downstreamRequest;
_downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest);
}
[Fact]
@ -122,6 +123,7 @@ namespace Ocelot.UnitTests.LoadBalancer
private void GivenTheDownStreamUrlIs(string downstreamUrl)
{
_downstreamRequest.RequestUri = new System.Uri(downstreamUrl);
_downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest);
}
private void GivenTheLoadBalancerReturnsAnError()
@ -185,7 +187,7 @@ namespace Ocelot.UnitTests.LoadBalancer
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.Middleware;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Request.Middleware;
using Ocelot.UnitTests.Responder;
using Shouldly;
using TestStack.BDDfy;
@ -48,7 +49,7 @@ namespace Ocelot.UnitTests.Middleware
new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Bill says hi") },
DownstreamReRoute = billDownstreamReRoute,
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 };

View File

@ -12,24 +12,27 @@ using TestStack.BDDfy;
using Xunit;
using System.Net.Http;
using System;
using Ocelot.Request.Middleware;
namespace Ocelot.UnitTests.QueryStrings
{
public class AddQueriesToRequestTests
{
private readonly AddQueriesToRequest _addQueriesToRequest;
private HttpRequestMessage _downstreamRequest;
private DownstreamRequest _downstreamRequest;
private readonly Mock<IClaimsParser> _parser;
private List<ClaimToThing> _configuration;
private List<Claim> _claims;
private Response _result;
private Response<string> _claimValue;
private HttpRequestMessage _request;
public AddQueriesToRequestTests()
{
_request = new HttpRequestMessage(HttpMethod.Post, "http://my.url/abc?q=123");
_parser = new Mock<IClaimsParser>();
_addQueriesToRequest = new AddQueriesToRequest(_parser.Object);
_downstreamRequest = new HttpRequestMessage(HttpMethod.Post, "http://my.url/abc?q=123");
_downstreamRequest = new DownstreamRequest(_request);
}
[Fact]
@ -78,7 +81,7 @@ namespace Ocelot.UnitTests.QueryStrings
private void TheTheQueryStringIs(string expected)
{
_downstreamRequest.RequestUri.Query.ShouldBe(expected);
_downstreamRequest.Query.ShouldBe(expected);
}
[Fact]
@ -123,7 +126,7 @@ namespace Ocelot.UnitTests.QueryStrings
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");
query.Value.First().ShouldBe(_claimValue.Data);
}
@ -140,15 +143,18 @@ namespace Ocelot.UnitTests.QueryStrings
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)
{
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)

View File

@ -18,6 +18,7 @@ namespace Ocelot.UnitTests.QueryStrings
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
using Ocelot.Request.Middleware;
public class QueryStringBuilderMiddlewareTests
{
@ -36,7 +37,7 @@ namespace Ocelot.UnitTests.QueryStrings
_loggerFactory.Setup(x => x.CreateLogger<QueryStringBuilderMiddleware>()).Returns(_logger.Object);
_next = context => Task.CompletedTask;
_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);
}
@ -74,7 +75,7 @@ namespace Ocelot.UnitTests.QueryStrings
.Setup(x => x.SetQueriesOnDownstreamRequest(
It.IsAny<List<ClaimToThing>>(),
It.IsAny<IEnumerable<Claim>>(),
It.IsAny<HttpRequestMessage>()))
It.IsAny<DownstreamRequest>()))
.Returns(new OkResponse());
}

View File

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

View File

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

View File

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

View File

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

View File

@ -12,13 +12,16 @@ using Ocelot.Middleware;
using TestStack.BDDfy;
using Xunit;
using Shouldly;
using Ocelot.Request.Middleware;
using System.Threading.Tasks;
using System.Threading;
namespace Ocelot.UnitTests.Requester
{
public class HttpClientHttpRequesterTest
{
private readonly Mock<IHttpClientCache> _cacheHandlers;
private Mock<IDelegatingHandlerHandlerFactory> _house;
private Mock<IDelegatingHandlerHandlerFactory> _factory;
private Response<HttpResponseMessage> _response;
private readonly HttpClientHttpRequester _httpClientRequester;
private DownstreamContext _request;
@ -27,8 +30,8 @@ namespace Ocelot.UnitTests.Requester
public HttpClientHttpRequesterTest()
{
_house = new Mock<IDelegatingHandlerHandlerFactory>();
_house.Setup(x => x.Get(It.IsAny<DownstreamReRoute>())).Returns(new OkResponse<List<Func<DelegatingHandler>>>(new List<Func<DelegatingHandler>>()));
_factory = new Mock<IDelegatingHandlerHandlerFactory>();
_factory.Setup(x => x.Get(It.IsAny<DownstreamReRoute>())).Returns(new OkResponse<List<Func<DelegatingHandler>>>(new List<Func<DelegatingHandler>>()));
_logger = new Mock<IOcelotLogger>();
_loggerFactory = new Mock<IOcelotLoggerFactory>();
_loggerFactory
@ -38,7 +41,7 @@ namespace Ocelot.UnitTests.Requester
_httpClientRequester = new HttpClientHttpRequester(
_loggerFactory.Object,
_cacheHandlers.Object,
_house.Object);
_factory.Object);
}
[Fact]
@ -50,10 +53,11 @@ namespace Ocelot.UnitTests.Requester
var context = new DownstreamContext(new DefaultHttpContext())
{
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))
.And(x => GivenTheHouseReturnsOkHandler())
.When(x=>x.WhenIGetResponse())
.Then(x => x.ThenTheResponseIsCalledCorrectly())
.BDDfy();
@ -68,7 +72,7 @@ namespace Ocelot.UnitTests.Requester
var context = new DownstreamContext(new DefaultHttpContext())
{
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))
@ -96,5 +100,23 @@ namespace Ocelot.UnitTests.Requester
{
_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 Ocelot.ServiceDiscovery;
using Ocelot.ServiceDiscovery.Providers;
using Ocelot.Values;
using Shouldly;
using TestStack.BDDfy;

View File

@ -9,6 +9,8 @@ using Microsoft.AspNetCore.Http;
using Moq;
using Ocelot.Logging;
using Ocelot.ServiceDiscovery;
using Ocelot.ServiceDiscovery.Configuration;
using Ocelot.ServiceDiscovery.Providers;
using Ocelot.Values;
using Xunit;
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.Collections.Generic;

View File

@ -5,6 +5,7 @@ using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Logging;
using Ocelot.ServiceDiscovery;
using Ocelot.ServiceDiscovery.Providers;
using Shouldly;
using TestStack.BDDfy;
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);
}
}
}
}