mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 06:22:50 +08:00
commit
e3db60bd49
@ -1 +1,2 @@
|
|||||||
./build.ps1 -target BuildAndReleaseUnstable
|
./build.ps1 -target BuildAndReleaseUnstable
|
||||||
|
exit $LASTEXITCODE
|
@ -1 +1,2 @@
|
|||||||
./build.ps1 -target RunTests
|
./build.ps1 -target RunTests
|
||||||
|
exit $LASTEXITCODE
|
17
build.cake
17
build.cake
@ -102,17 +102,10 @@ Task("Version")
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Task("Restore")
|
Task("Compile")
|
||||||
.IsDependentOn("Clean")
|
.IsDependentOn("Clean")
|
||||||
.IsDependentOn("Version")
|
.IsDependentOn("Version")
|
||||||
.Does(() =>
|
.Does(() =>
|
||||||
{
|
|
||||||
DotNetCoreRestore(slnFile);
|
|
||||||
});
|
|
||||||
|
|
||||||
Task("Compile")
|
|
||||||
.IsDependentOn("Restore")
|
|
||||||
.Does(() =>
|
|
||||||
{
|
{
|
||||||
var settings = new DotNetCoreBuildSettings
|
var settings = new DotNetCoreBuildSettings
|
||||||
{
|
{
|
||||||
@ -140,7 +133,7 @@ Task("RunUnitTests")
|
|||||||
new OpenCoverSettings()
|
new OpenCoverSettings()
|
||||||
{
|
{
|
||||||
Register="user",
|
Register="user",
|
||||||
ArgumentCustomization=args=>args.Append(@"-oldstyle -returntargetcode")
|
ArgumentCustomization=args=>args.Append(@"-oldstyle -returntargetcode -excludebyattribute:*.ExcludeFromCoverage*")
|
||||||
}
|
}
|
||||||
.WithFilter("+[Ocelot*]*")
|
.WithFilter("+[Ocelot*]*")
|
||||||
.WithFilter("-[xunit*]*")
|
.WithFilter("-[xunit*]*")
|
||||||
@ -199,6 +192,9 @@ Task("RunAcceptanceTests")
|
|||||||
var settings = new DotNetCoreTestSettings
|
var settings = new DotNetCoreTestSettings
|
||||||
{
|
{
|
||||||
Configuration = compileConfig,
|
Configuration = compileConfig,
|
||||||
|
ArgumentCustomization = args => args
|
||||||
|
.Append("--no-restore")
|
||||||
|
.Append("--no-build")
|
||||||
};
|
};
|
||||||
|
|
||||||
EnsureDirectoryExists(artifactsForAcceptanceTestsDir);
|
EnsureDirectoryExists(artifactsForAcceptanceTestsDir);
|
||||||
@ -212,6 +208,9 @@ Task("RunIntegrationTests")
|
|||||||
var settings = new DotNetCoreTestSettings
|
var settings = new DotNetCoreTestSettings
|
||||||
{
|
{
|
||||||
Configuration = compileConfig,
|
Configuration = compileConfig,
|
||||||
|
ArgumentCustomization = args => args
|
||||||
|
.Append("--no-restore")
|
||||||
|
.Append("--no-build")
|
||||||
};
|
};
|
||||||
|
|
||||||
EnsureDirectoryExists(artifactsForIntegrationTestsDir);
|
EnsureDirectoryExists(artifactsForIntegrationTestsDir);
|
||||||
|
@ -6,33 +6,24 @@ using bearer tokens that you request from Ocelot iteself. This is provided by th
|
|||||||
`Identity Server <https://github.com/IdentityServer/IdentityServer4>`_ project that I have been using for a few years now. Check them out.
|
`Identity Server <https://github.com/IdentityServer/IdentityServer4>`_ project that I have been using for a few years now. Check them out.
|
||||||
|
|
||||||
In order to enable the administration section you need to do a few things. First of all add this to your
|
In order to enable the administration section you need to do a few things. First of all add this to your
|
||||||
initial configuration.json. The value can be anything you want and it is obviously reccomended don't use
|
initial Startup.cs.
|
||||||
|
|
||||||
|
The path can be anything you want and it is obviously reccomended don't use
|
||||||
a url you would like to route through with Ocelot as this will not work. The administration uses the
|
a url you would like to route through with Ocelot as this will not work. The administration uses the
|
||||||
MapWhen functionality of asp.net core and all requests to {root}/administration will be sent there not
|
MapWhen functionality of asp.net core and all requests to {root}/administration will be sent there not
|
||||||
to the Ocelot middleware.
|
to the Ocelot middleware.
|
||||||
|
|
||||||
.. code-block:: json
|
The secret is the client secret that Ocelot's internal IdentityServer will use to authenticate requests to the administration API. This can be whatever you want it to be!
|
||||||
|
|
||||||
"GlobalConfiguration": {
|
.. code-block:: csharp
|
||||||
"AdministrationPath": "/administration"
|
|
||||||
|
public virtual void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services
|
||||||
|
.AddOcelot(Configuration)
|
||||||
|
.AddAdministration("/administration", "secret");
|
||||||
}
|
}
|
||||||
|
|
||||||
This will get the admin area set up but not the authentication.
|
|
||||||
Please note that this is a very basic approach to
|
|
||||||
this problem and if needed we can obviously improve on this!
|
|
||||||
|
|
||||||
You need to set 3 environmental variables.
|
|
||||||
|
|
||||||
``OCELOT_USERNAME``
|
|
||||||
|
|
||||||
This need to be the admin username you want to use with Ocelot.
|
|
||||||
``OCELOT_HASH``
|
|
||||||
``OCELOT_SALT``
|
|
||||||
The hash and salt of the password you want to use given hashing algorythm. When requesting bearer tokens for use with the administration api you will need to supply username and password. In order to create a hash and salt of your password please check out HashCreationTests.should_create_hash_and_salt() this technique is based on [this](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/consumer-apis/password-hashing)
|
|
||||||
using SHA256 rather than SHA1.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Now if you went with the configuration options above and want to access the API you can use the postman scripts
|
Now if you went with the configuration options above and want to access the API you can use the postman scripts
|
||||||
called ocelot.postman_collection.json in the solution to change the Ocelot configuration. Obviously these
|
called ocelot.postman_collection.json in the solution to change the Ocelot configuration. Obviously these
|
||||||
will need to be changed if you are running Ocelot on a different url to http://localhost:5000.
|
will need to be changed if you are running Ocelot on a different url to http://localhost:5000.
|
||||||
@ -40,7 +31,6 @@ will need to be changed if you are running Ocelot on a different url to http://l
|
|||||||
The scripts show you how to request a bearer token from ocelot and then use it to GET the existing configuration and POST
|
The scripts show you how to request a bearer token from ocelot and then use it to GET the existing configuration and POST
|
||||||
a configuration.
|
a configuration.
|
||||||
|
|
||||||
|
|
||||||
Administration running multiple Ocelot's
|
Administration running multiple Ocelot's
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
If you are running multiple Ocelot's in a cluster then you need to use a certificate to sign the bearer tokens used to access the administration API.
|
If you are running multiple Ocelot's in a cluster then you need to use a certificate to sign the bearer tokens used to access the administration API.
|
||||||
@ -59,21 +49,17 @@ Administration API
|
|||||||
|
|
||||||
**POST {adminPath}/connect/token**
|
**POST {adminPath}/connect/token**
|
||||||
|
|
||||||
This gets a token for use with the admin area using the username and password we talk about setting above. Under the hood this calls into an IdentityServer hosted within Ocelot.
|
This gets a token for use with the admin area using the client credentials we talk about setting above. Under the hood this calls into an IdentityServer hosted within Ocelot.
|
||||||
|
|
||||||
The body of the request is form-data as follows
|
The body of the request is form-data as follows
|
||||||
|
|
||||||
``client_id`` set as admin
|
``client_id`` set as admin
|
||||||
|
|
||||||
``client_secret`` set as secret
|
``client_secret`` set as whatever you used when setting up the administration services.
|
||||||
|
|
||||||
``scope`` set as admin
|
``scope`` set as admin
|
||||||
|
|
||||||
``username`` set as whatever you used
|
``grant_type`` set as client_credentials
|
||||||
|
|
||||||
``password`` set aswhatever you used
|
|
||||||
|
|
||||||
``grant_type`` set as password
|
|
||||||
|
|
||||||
**GET {adminPath}/configuration**
|
**GET {adminPath}/configuration**
|
||||||
|
|
||||||
|
@ -3,11 +3,11 @@ Logging
|
|||||||
|
|
||||||
Ocelot uses the standard logging interfaces ILoggerFactory / ILogger<T> at the moment.
|
Ocelot uses the standard logging interfaces ILoggerFactory / ILogger<T> at the moment.
|
||||||
This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation
|
This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation
|
||||||
for the standard asp.net core logging stuff at the moment.
|
for the standard asp.net core logging stuff at the moment. This is because Ocelot add's some extra info to the logs such as request id if it is configured.
|
||||||
|
|
||||||
There are a bunch of debugging logs in the ocelot middlewares however I think the
|
There is a global error handler that should catch any exceptions thrown and log them as errors.
|
||||||
system probably needs more logging in the code it calls into. Other than the debugging
|
|
||||||
there is a global error handler that should catch any errors thrown and log them as errors.
|
Finally if logging is set to trace level Ocelot will log starting, finishing and any middlewares that throw an exception which can be quite useful.
|
||||||
|
|
||||||
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
|
||||||
|
45
docs/features/raft.rst
Normal file
45
docs/features/raft.rst
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION)
|
||||||
|
============================================
|
||||||
|
|
||||||
|
Ocelot has recenely integrated `Rafty <https://github.com/TomPallister/Rafty>`_ which is an implementation of Raft that I have also been working on over the last year. This project is very experimental so please do not use this feature of Ocelot in production until I think it's OK.
|
||||||
|
|
||||||
|
Raft is a distributed concensus algorythm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server).
|
||||||
|
|
||||||
|
In order to enable Rafty in Ocelot you must make the following changes to your Startup.cs.
|
||||||
|
|
||||||
|
.. code-block:: csharp
|
||||||
|
|
||||||
|
public virtual void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services
|
||||||
|
.AddOcelot(Configuration)
|
||||||
|
.AddAdministration("/administration", "secret")
|
||||||
|
.AddRafty();
|
||||||
|
}
|
||||||
|
|
||||||
|
In addition to this you must add a file called peers.json to your main project and it will look as follows
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"Peers": [{
|
||||||
|
"HostAndPort": "http://localhost:5000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"HostAndPort": "http://localhost:5002"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"HostAndPort": "http://localhost:5003"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"HostAndPort": "http://localhost:5004"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"HostAndPort": "http://localhost:5001"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Each instance of Ocelot must have it's address in the array so that they can communicate using Rafty.
|
||||||
|
|
||||||
|
Once you have made these configuration changes you must deploy and start each instance of Ocelot using the addresses in the peers.json file. The servers should then start communicating with each other! You can test if everything is working by posting a configuration update and checking it has replicated to all servers by getting there configuration.
|
@ -4,25 +4,57 @@ Request Id / Correlation Id
|
|||||||
Ocelot supports a client sending a request id in the form of a header. If set Ocelot will
|
Ocelot supports a client sending a request id in the form of a header. If set Ocelot will
|
||||||
use the requestid for logging as soon as it becomes available in the middleware pipeline.
|
use the requestid for logging as soon as it becomes available in the middleware pipeline.
|
||||||
Ocelot will also forward the request id with the specified header to the downstream service.
|
Ocelot will also forward the request id with the specified header to the downstream service.
|
||||||
I'm not sure if have this spot on yet in terms of the pipeline order becasue there are a few logs
|
|
||||||
that don't get the users request id at the moment and ocelot just logs not set for request id
|
|
||||||
which sucks. You can still get the framework request id in the logs if you set
|
|
||||||
IncludeScopes true in your logging config. This can then be used to match up later logs that do
|
|
||||||
have an OcelotRequestId.
|
|
||||||
|
|
||||||
In order to use the requestid feature in your ReRoute configuration add this setting
|
You can still get the asp.net core request id in the logs if you set
|
||||||
|
IncludeScopes true in your logging config.
|
||||||
|
|
||||||
|
In order to use the reques tid feature you have two options.
|
||||||
|
|
||||||
|
*Global*
|
||||||
|
|
||||||
|
In your configuration.json set the following in the GlobalConfiguration section. This will be used for all requests into Ocelot.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
"GlobalConfiguration": {
|
||||||
|
"RequestIdKey": "OcRequestId"
|
||||||
|
}
|
||||||
|
|
||||||
|
I reccomend using the GlobalConfiguration unless you really need it to be ReRoute specific.
|
||||||
|
|
||||||
|
*ReRoute*
|
||||||
|
|
||||||
|
If you want to override this for a specific ReRoute add the following to configuration.json for the specific ReRoute.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"RequestIdKey": "OcRequestId"
|
"RequestIdKey": "OcRequestId"
|
||||||
|
|
||||||
In this example OcRequestId is the request header that contains the clients request id.
|
Once Ocelot has identified the incoming requests matching ReRoute object it will set the request id based on the ReRoute configuration.
|
||||||
|
|
||||||
There is also a setting in the GlobalConfiguration section which will override whatever has been
|
This can lead to a small gotcha. If you set a GlobalConfiguration it is possible to get one request id until the ReRoute is identified and then another after that because the request id key can change. This is by design and is the best solution I can think of at the moment. In this case the OcelotLogger will show the request id and previous request id in the logs.
|
||||||
set at ReRoute level for the request id. The setting is as fllows.
|
|
||||||
|
|
||||||
.. code-block:: json
|
Below is an example of the logging when set at Debug level for a normal request..
|
||||||
|
|
||||||
"RequestIdKey": "OcRequestId"
|
.. code-block:: bash
|
||||||
|
|
||||||
It behaves in exactly the same way as the ReRoute level RequestIdKey settings.
|
dbug: Ocelot.Errors.Middleware.ExceptionHandlerMiddleware[0]
|
||||||
|
requestId: asdf, previousRequestId: no previous request id, message: ocelot pipeline started,
|
||||||
|
dbug: Ocelot.DownstreamRouteFinder.Middleware.DownstreamRouteFinderMiddleware[0]
|
||||||
|
requestId: asdf, previousRequestId: no previous request id, message: upstream url path is {upstreamUrlPath},
|
||||||
|
dbug: Ocelot.DownstreamRouteFinder.Middleware.DownstreamRouteFinderMiddleware[0]
|
||||||
|
requestId: asdf, previousRequestId: no previous request id, message: downstream template is {downstreamRoute.Data.ReRoute.DownstreamPath},
|
||||||
|
dbug: Ocelot.RateLimit.Middleware.ClientRateLimitMiddleware[0]
|
||||||
|
requestId: asdf, previousRequestId: no previous request id, message: EndpointRateLimiting is not enabled for Ocelot.Values.PathTemplate,
|
||||||
|
dbug: Ocelot.Authorisation.Middleware.AuthorisationMiddleware[0]
|
||||||
|
requestId: 1234, previousRequestId: asdf, message: /posts/{postId} route does not require user to be authorised,
|
||||||
|
dbug: Ocelot.DownstreamUrlCreator.Middleware.DownstreamUrlCreatorMiddleware[0]
|
||||||
|
requestId: 1234, previousRequestId: asdf, message: downstream url is {downstreamUrl.Data.Value},
|
||||||
|
dbug: Ocelot.Request.Middleware.HttpRequestBuilderMiddleware[0]
|
||||||
|
requestId: 1234, previousRequestId: asdf, message: setting upstream request,
|
||||||
|
dbug: Ocelot.Requester.Middleware.HttpRequesterMiddleware[0]
|
||||||
|
requestId: 1234, previousRequestId: asdf, message: setting http response message,
|
||||||
|
dbug: Ocelot.Responder.Middleware.ResponderMiddleware[0]
|
||||||
|
requestId: 1234, previousRequestId: asdf, message: no pipeline errors, setting and returning completed response,
|
||||||
|
dbug: Ocelot.Errors.Middleware.ExceptionHandlerMiddleware[0]
|
||||||
|
requestId: 1234, previousRequestId: asdf, message: ocelot pipeline finished,
|
||||||
|
@ -63,3 +63,32 @@ In order to change this you can specify on a per ReRoute basis the following set
|
|||||||
This means that when Ocelot tries to match the incoming upstream url with an upstream template the
|
This means that when Ocelot tries to match the incoming upstream url with an upstream template the
|
||||||
evaluation will be case sensitive. This setting defaults to false so only set it if you want
|
evaluation will be case sensitive. This setting defaults to false so only set it if you want
|
||||||
the ReRoute to be case sensitive is my advice!
|
the ReRoute to be case sensitive is my advice!
|
||||||
|
|
||||||
|
Catch All
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
|
Ocelot's routing also supports a catch all style routing where the user can specify that they want to match all traffic if you set up your config like below the request will be proxied straight through (it doesnt have to be url any placeholder name will work).
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/{url}",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
|
"DownstreamPort": 80,
|
||||||
|
"DownstreamHost" "localhost",
|
||||||
|
"UpstreamPathTemplate": "/{url}",
|
||||||
|
"UpstreamHttpMethod": [ "Get" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
The catch all has a lower priority than any other ReRoute. If you also have the ReRoute below in your config then Ocelot would match it before the catch all.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"DownstreamPathTemplate": "/",
|
||||||
|
"DownstreamScheme": "https",
|
||||||
|
"DownstreamPort": 80,
|
||||||
|
"DownstreamHost" "10.0.10.1",
|
||||||
|
"UpstreamPathTemplate": "/",
|
||||||
|
"UpstreamHttpMethod": [ "Get" ]
|
||||||
|
}
|
BIN
docs/images/OcelotBasic.jpg
Normal file
BIN
docs/images/OcelotBasic.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
BIN
docs/images/OcelotIndentityServer.jpg
Normal file
BIN
docs/images/OcelotIndentityServer.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
BIN
docs/images/OcelotMultipleInstances.jpg
Normal file
BIN
docs/images/OcelotMultipleInstances.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
BIN
docs/images/OcelotMultipleInstancesConsul.jpg
Normal file
BIN
docs/images/OcelotMultipleInstancesConsul.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
@ -1,29 +1,7 @@
|
|||||||
Welcome to Ocelot
|
Welcome to Ocelot
|
||||||
=================
|
=================
|
||||||
|
|
||||||
This project is aimed at people using .NET running
|
Thanks for taking a look at the Ocelot documentation. Please use the left hand nav to get around. I would suggest taking a look at introduction first.
|
||||||
a micro services / service orientated architecture
|
|
||||||
that need a unified point of entry into their system.
|
|
||||||
|
|
||||||
In particular I want easy integration with
|
|
||||||
IdentityServer reference and bearer tokens.
|
|
||||||
|
|
||||||
We have been unable to find this in my current workplace
|
|
||||||
without having to write our own Javascript middlewares
|
|
||||||
to handle the IdentityServer reference tokens. We would
|
|
||||||
rather use the IdentityServer code that already exists
|
|
||||||
to do this.
|
|
||||||
|
|
||||||
Ocelot is a bunch of middlewares in a specific order.
|
|
||||||
|
|
||||||
Ocelot manipulates the HttpRequest object into a state specified by its configuration until
|
|
||||||
it reaches a request builder middleware where it creates a HttpRequestMessage object which is
|
|
||||||
used to make a request to a downstream service. The middleware that makes the request is
|
|
||||||
the last thing in the Ocelot pipeline. It does not call the next middleware.
|
|
||||||
The response from the downstream service is stored in a per request scoped repository
|
|
||||||
and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware
|
|
||||||
that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client.
|
|
||||||
That is basically it with a bunch of other features.
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
@ -46,6 +24,7 @@ That is basically it with a bunch of other features.
|
|||||||
features/authentication
|
features/authentication
|
||||||
features/authorisation
|
features/authorisation
|
||||||
features/administration
|
features/administration
|
||||||
|
features/raft
|
||||||
features/caching
|
features/caching
|
||||||
features/qualityofservice
|
features/qualityofservice
|
||||||
features/claimstransformation
|
features/claimstransformation
|
||||||
|
@ -1,4 +1,38 @@
|
|||||||
Big Picture
|
Big Picture
|
||||||
===========
|
===========
|
||||||
|
|
||||||
Coming soon...
|
Ocleot is aimed at people using .NET running
|
||||||
|
a micro services / service orientated architecture
|
||||||
|
that need a unified point of entry into their system.
|
||||||
|
|
||||||
|
In particular I want easy integration with
|
||||||
|
IdentityServer reference and bearer tokens.
|
||||||
|
|
||||||
|
Ocelot is a bunch of middlewares in a specific order.
|
||||||
|
|
||||||
|
Ocelot manipulates the HttpRequest object into a state specified by its configuration until
|
||||||
|
it reaches a request builder middleware where it creates a HttpRequestMessage object which is
|
||||||
|
used to make a request to a downstream service. The middleware that makes the request is
|
||||||
|
the last thing in the Ocelot pipeline. It does not call the next middleware.
|
||||||
|
The response from the downstream service is stored in a per request scoped repository
|
||||||
|
and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware
|
||||||
|
that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client.
|
||||||
|
That is basically it with a bunch of other features.
|
||||||
|
|
||||||
|
The following are configuration that you use when deploying Ocelot.
|
||||||
|
|
||||||
|
Basic Implementation
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
.. image:: ../images/OcelotBasic.jpg
|
||||||
|
|
||||||
|
With IdentityServer
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
.. image:: ../images/OcelotIndentityServer.jpg
|
||||||
|
|
||||||
|
Multiple Instances
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
.. image:: ../images/OcelotMultipleInstances.jpg
|
||||||
|
|
||||||
|
With Consul
|
||||||
|
^^^^^^^^^^^
|
||||||
|
.. image:: ../images/OcelotMultipleInstancesConsul.jpg
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
Getting Started
|
Getting Started
|
||||||
===============
|
===============
|
||||||
|
|
||||||
Ocelot is designed to work with ASP.NET core only and is currently
|
Ocelot is designed to work with .NET Core only and is currently
|
||||||
built to netcoreapp2.0 `this <https://docs.microsoft.com/en-us/dotnet/articles/standard/library>`_ documentation may prove helpful when working out if Ocelot would be suitable for you.
|
built to netcoreapp2.0 `this <https://docs.microsoft.com/en-us/dotnet/articles/standard/library>`_ documentation may prove helpful when working out if Ocelot would be suitable for you.
|
||||||
|
|
||||||
|
.NET Core 2.0
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
**Install NuGet package**
|
**Install NuGet package**
|
||||||
|
|
||||||
@ -30,6 +32,86 @@ The following is a very basic configuration.json. It won't do anything but shoul
|
|||||||
Then in your Program.cs you will want to have the following. This can be changed if you
|
Then in your Program.cs you will want to have the following. This can be changed if you
|
||||||
don't wan't to use the default url e.g. UseUrls(someUrls) and should work as long as you keep the WebHostBuilder registration.
|
don't wan't to use the default url e.g. UseUrls(someUrls) and should work as long as you keep the WebHostBuilder registration.
|
||||||
|
|
||||||
|
.. code-block:: csharp
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
IWebHostBuilder builder = new WebHostBuilder();
|
||||||
|
builder.ConfigureServices(s => {
|
||||||
|
s.AddSingleton(builder);
|
||||||
|
});
|
||||||
|
builder.UseKestrel()
|
||||||
|
.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();
|
||||||
|
})
|
||||||
|
.UseIISIntegration()
|
||||||
|
.UseStartup<ManualTestStartup>();
|
||||||
|
var host = builder.Build();
|
||||||
|
host.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sadly we need to inject the IWebHostBuilder interface to get the applications scheme, url and port later. I cannot find a better way of doing this at the moment without setting this in a static or some kind of config.
|
||||||
|
|
||||||
|
**Startup**
|
||||||
|
|
||||||
|
An example startup using a json file for configuration can be seen below. This is the most basic startup and Ocelot has quite a few more options. Detailed in the rest of these docs! If you get a stuck a good place to look is at the ManualTests project in the source code.
|
||||||
|
|
||||||
|
.. code-block:: csharp
|
||||||
|
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddOcelot();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.UseOcelot().Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.NET Core 1.0
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
**Install NuGet package**
|
||||||
|
|
||||||
|
Install Ocelot and it's dependecies using nuget. You will need to create a netcoreapp1.0+ projct and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections
|
||||||
|
to get up and running. Please note you will need to choose one of the Ocelot packages from the NuGet feed.
|
||||||
|
|
||||||
|
All versions can be found `here <https://www.nuget.org/packages/Ocelot/>`_.
|
||||||
|
|
||||||
|
**Configuration**
|
||||||
|
|
||||||
|
The following is a very basic configuration.json. It won't do anything but should get Ocelot starting.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"ReRoutes": [],
|
||||||
|
"GlobalConfiguration": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
**Program**
|
||||||
|
|
||||||
|
Then in your Program.cs you will want to have the following. This can be changed if you
|
||||||
|
don't wan't to use the default url e.g. UseUrls(someUrls) and should work as long as you keep the WebHostBuilder registration.
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
public class Program
|
public class Program
|
||||||
@ -57,7 +139,6 @@ Sadly we need to inject the IWebHostBuilder interface to get the applications sc
|
|||||||
**Startup**
|
**Startup**
|
||||||
|
|
||||||
An example startup using a json file for configuration can be seen below.
|
An example startup using a json file for configuration can be seen below.
|
||||||
Currently this is the only way to get configuration into Ocelot.
|
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
@ -79,22 +160,13 @@ Currently this is the only way to get configuration into Ocelot.
|
|||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddOcelot(Configuration)
|
services.AddOcelot(Configuration);
|
||||||
.AddCacheManager(x => {
|
}
|
||||||
x.WithMicrosoftLogging(log =>
|
|
||||||
{
|
|
||||||
log.AddConsole(LogLevel.Debug);
|
|
||||||
})
|
|
||||||
.WithDictionaryHandle();
|
|
||||||
});;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
public void Configure(IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
|
||||||
|
|
||||||
app.UseOcelot().Wait();
|
app.UseOcelot().Wait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
This is pretty much all you need to get going.......more to come!
|
This is pretty much all you need to get going.
|
File diff suppressed because one or more lines are too long
@ -1 +1,2 @@
|
|||||||
./build.ps1 -target Release
|
./build.ps1 -target Release
|
||||||
|
exit $LASTEXITCODE
|
@ -1 +1,2 @@
|
|||||||
./build -target RunAcceptanceTests
|
./build -target RunAcceptanceTests
|
||||||
|
exit $LASTEXITCODE
|
@ -1 +1,2 @@
|
|||||||
./build.ps1 -target RunBenchmarkTests
|
./build.ps1 -target RunBenchmarkTests
|
||||||
|
exit $LASTEXITCODE
|
@ -1 +1,2 @@
|
|||||||
./build.ps1 -target RunUnitTests
|
./build.ps1 -target RunUnitTests
|
||||||
|
exit $LASTEXITCODE
|
16
src/Ocelot/Authentication/BearerToken.cs
Normal file
16
src/Ocelot/Authentication/BearerToken.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Ocelot.Authentication
|
||||||
|
{
|
||||||
|
class BearerToken
|
||||||
|
{
|
||||||
|
[JsonProperty("access_token")]
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("expires_in")]
|
||||||
|
public int ExpiresIn { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("token_type")]
|
||||||
|
public string TokenType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Ocelot.Cache;
|
using Ocelot.Cache;
|
||||||
using Ocelot.Configuration.Provider;
|
using Ocelot.Configuration.Provider;
|
||||||
|
|
||||||
namespace Ocelot.Controllers
|
namespace Ocelot.Cache
|
||||||
{
|
{
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("outputcache")]
|
[Route("outputcache")]
|
@ -1,53 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using IdentityServer4.Models;
|
|
||||||
using IdentityServer4.Validation;
|
|
||||||
using Ocelot.Configuration.Provider;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Authentication
|
|
||||||
{
|
|
||||||
public class OcelotResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
|
|
||||||
{
|
|
||||||
private readonly IHashMatcher _matcher;
|
|
||||||
private readonly IIdentityServerConfiguration _identityServerConfiguration;
|
|
||||||
|
|
||||||
public OcelotResourceOwnerPasswordValidator(IHashMatcher matcher, IIdentityServerConfiguration identityServerConfiguration)
|
|
||||||
{
|
|
||||||
_identityServerConfiguration = identityServerConfiguration;
|
|
||||||
_matcher = matcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var user = _identityServerConfiguration.Users.FirstOrDefault(u => u.UserName == context.UserName);
|
|
||||||
|
|
||||||
if(user == null)
|
|
||||||
{
|
|
||||||
context.Result = new GrantValidationResult(
|
|
||||||
TokenRequestErrors.InvalidGrant,
|
|
||||||
"invalid custom credential");
|
|
||||||
}
|
|
||||||
else if(_matcher.Match(context.Password, user.Salt, user.Hash))
|
|
||||||
{
|
|
||||||
context.Result = new GrantValidationResult(
|
|
||||||
subject: "admin",
|
|
||||||
authenticationMethod: "custom");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Result = new GrantValidationResult(
|
|
||||||
TokenRequestErrors.InvalidGrant,
|
|
||||||
"invalid custom credential");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,6 +2,7 @@
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Ocelot.Configuration.Creator;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Builder
|
namespace Ocelot.Configuration.Builder
|
||||||
{
|
{
|
||||||
@ -11,7 +12,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
private string _loadBalancerKey;
|
private string _loadBalancerKey;
|
||||||
private string _downstreamPathTemplate;
|
private string _downstreamPathTemplate;
|
||||||
private string _upstreamTemplate;
|
private string _upstreamTemplate;
|
||||||
private string _upstreamTemplatePattern;
|
private UpstreamPathTemplate _upstreamTemplatePattern;
|
||||||
private List<HttpMethod> _upstreamHttpMethod;
|
private List<HttpMethod> _upstreamHttpMethod;
|
||||||
private bool _isAuthenticated;
|
private bool _isAuthenticated;
|
||||||
private List<ClaimToThing> _configHeaderExtractorProperties;
|
private List<ClaimToThing> _configHeaderExtractorProperties;
|
||||||
@ -65,7 +66,7 @@ namespace Ocelot.Configuration.Builder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReRouteBuilder WithUpstreamTemplatePattern(string input)
|
public ReRouteBuilder WithUpstreamTemplatePattern(UpstreamPathTemplate input)
|
||||||
{
|
{
|
||||||
_upstreamTemplatePattern = input;
|
_upstreamTemplatePattern = input;
|
||||||
return this;
|
return this;
|
||||||
|
@ -9,6 +9,7 @@ using Ocelot.Configuration.Builder;
|
|||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Configuration.Parser;
|
using Ocelot.Configuration.Parser;
|
||||||
using Ocelot.Configuration.Validator;
|
using Ocelot.Configuration.Validator;
|
||||||
|
using Ocelot.DependencyInjection;
|
||||||
using Ocelot.LoadBalancer;
|
using Ocelot.LoadBalancer;
|
||||||
using Ocelot.LoadBalancer.LoadBalancers;
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
@ -35,6 +36,8 @@ namespace Ocelot.Configuration.Creator
|
|||||||
private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator;
|
private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator;
|
||||||
private readonly IRegionCreator _regionCreator;
|
private readonly IRegionCreator _regionCreator;
|
||||||
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
|
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
|
||||||
|
private readonly IAdministrationPath _adminPath;
|
||||||
|
|
||||||
|
|
||||||
public FileOcelotConfigurationCreator(
|
public FileOcelotConfigurationCreator(
|
||||||
IOptions<FileConfiguration> options,
|
IOptions<FileConfiguration> options,
|
||||||
@ -49,9 +52,11 @@ namespace Ocelot.Configuration.Creator
|
|||||||
IReRouteOptionsCreator fileReRouteOptionsCreator,
|
IReRouteOptionsCreator fileReRouteOptionsCreator,
|
||||||
IRateLimitOptionsCreator rateLimitOptionsCreator,
|
IRateLimitOptionsCreator rateLimitOptionsCreator,
|
||||||
IRegionCreator regionCreator,
|
IRegionCreator regionCreator,
|
||||||
IHttpHandlerOptionsCreator httpHandlerOptionsCreator
|
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
|
||||||
|
IAdministrationPath adminPath
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
_adminPath = adminPath;
|
||||||
_regionCreator = regionCreator;
|
_regionCreator = regionCreator;
|
||||||
_rateLimitOptionsCreator = rateLimitOptionsCreator;
|
_rateLimitOptionsCreator = rateLimitOptionsCreator;
|
||||||
_requestIdKeyCreator = requestIdKeyCreator;
|
_requestIdKeyCreator = requestIdKeyCreator;
|
||||||
@ -92,7 +97,7 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
|
var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
|
||||||
|
|
||||||
var config = new OcelotConfiguration(reRoutes, fileConfiguration.GlobalConfiguration.AdministrationPath, serviceProviderConfiguration);
|
var config = new OcelotConfiguration(reRoutes, _adminPath.Path, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey);
|
||||||
|
|
||||||
return new OkResponse<IOcelotConfiguration>(config);
|
return new OkResponse<IOcelotConfiguration>(config);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Creator
|
namespace Ocelot.Configuration.Creator
|
||||||
{
|
{
|
||||||
public interface IUpstreamTemplatePatternCreator
|
public interface IUpstreamTemplatePatternCreator
|
||||||
{
|
{
|
||||||
string Create(FileReRoute reRoute);
|
UpstreamPathTemplate Create(FileReRoute reRoute);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,29 +8,16 @@ namespace Ocelot.Configuration.Creator
|
|||||||
{
|
{
|
||||||
public static class IdentityServerConfigurationCreator
|
public static class IdentityServerConfigurationCreator
|
||||||
{
|
{
|
||||||
public static IdentityServerConfiguration GetIdentityServerConfiguration()
|
public static IdentityServerConfiguration GetIdentityServerConfiguration(string secret)
|
||||||
{
|
{
|
||||||
var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME");
|
|
||||||
var hash = Environment.GetEnvironmentVariable("OCELOT_HASH");
|
|
||||||
var salt = Environment.GetEnvironmentVariable("OCELOT_SALT");
|
|
||||||
var credentialsSigningCertificateLocation = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE");
|
var credentialsSigningCertificateLocation = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE");
|
||||||
var credentialsSigningCertificatePassword = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD");
|
var credentialsSigningCertificatePassword = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD");
|
||||||
|
|
||||||
return new IdentityServerConfiguration(
|
return new IdentityServerConfiguration(
|
||||||
"admin",
|
"admin",
|
||||||
false,
|
false,
|
||||||
SupportedTokens.Both,
|
secret,
|
||||||
"secret",
|
|
||||||
new List<string> { "admin", "openid", "offline_access" },
|
new List<string> { "admin", "openid", "offline_access" },
|
||||||
"Ocelot Administration",
|
|
||||||
true,
|
|
||||||
GrantTypes.ResourceOwnerPassword,
|
|
||||||
AccessTokenType.Jwt,
|
|
||||||
false,
|
|
||||||
new List<User>
|
|
||||||
{
|
|
||||||
new User("admin", username, hash, salt)
|
|
||||||
},
|
|
||||||
credentialsSigningCertificateLocation,
|
credentialsSigningCertificateLocation,
|
||||||
credentialsSigningCertificatePassword
|
credentialsSigningCertificatePassword
|
||||||
);
|
);
|
||||||
|
@ -6,11 +6,11 @@ namespace Ocelot.Configuration.Creator
|
|||||||
{
|
{
|
||||||
public string Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
|
public string Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
|
||||||
{
|
{
|
||||||
var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey);
|
var reRouteId = !string.IsNullOrEmpty(fileReRoute.RequestIdKey);
|
||||||
|
|
||||||
var requestIdKey = globalRequestIdConfiguration
|
var requestIdKey = reRouteId
|
||||||
? globalConfiguration.RequestIdKey
|
? fileReRoute.RequestIdKey
|
||||||
: fileReRoute.RequestIdKey;
|
: globalConfiguration.RequestIdKey;
|
||||||
|
|
||||||
return requestIdKey;
|
return requestIdKey;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Creator
|
namespace Ocelot.Configuration.Creator
|
||||||
{
|
{
|
||||||
@ -9,8 +10,9 @@ namespace Ocelot.Configuration.Creator
|
|||||||
private const string RegExMatchEndString = "$";
|
private const string RegExMatchEndString = "$";
|
||||||
private const string RegExIgnoreCase = "(?i)";
|
private const string RegExIgnoreCase = "(?i)";
|
||||||
private const string RegExForwardSlashOnly = "^/$";
|
private const string RegExForwardSlashOnly = "^/$";
|
||||||
|
private const string RegExForwardSlashAndOnePlaceHolder = "^/.*";
|
||||||
|
|
||||||
public string Create(FileReRoute reRoute)
|
public UpstreamPathTemplate Create(FileReRoute reRoute)
|
||||||
{
|
{
|
||||||
var upstreamTemplate = reRoute.UpstreamPathTemplate;
|
var upstreamTemplate = reRoute.UpstreamPathTemplate;
|
||||||
|
|
||||||
@ -22,8 +24,14 @@ namespace Ocelot.Configuration.Creator
|
|||||||
{
|
{
|
||||||
var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i);
|
var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i);
|
||||||
var difference = postitionOfPlaceHolderClosingBracket - i + 1;
|
var difference = postitionOfPlaceHolderClosingBracket - i + 1;
|
||||||
var variableName = upstreamTemplate.Substring(i, difference);
|
var placeHolderName = upstreamTemplate.Substring(i, difference);
|
||||||
placeholders.Add(variableName);
|
placeholders.Add(placeHolderName);
|
||||||
|
|
||||||
|
//hack to handle /{url} case
|
||||||
|
if(ForwardSlashAndOnePlaceHolder(upstreamTemplate, placeholders, postitionOfPlaceHolderClosingBracket))
|
||||||
|
{
|
||||||
|
return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +42,7 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
if (upstreamTemplate == "/")
|
if (upstreamTemplate == "/")
|
||||||
{
|
{
|
||||||
return RegExForwardSlashOnly;
|
return new UpstreamPathTemplate(RegExForwardSlashOnly, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(upstreamTemplate.EndsWith("/"))
|
if(upstreamTemplate.EndsWith("/"))
|
||||||
@ -46,7 +54,17 @@ namespace Ocelot.Configuration.Creator
|
|||||||
? $"^{upstreamTemplate}{RegExMatchEndString}"
|
? $"^{upstreamTemplate}{RegExMatchEndString}"
|
||||||
: $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
|
: $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
|
||||||
|
|
||||||
return route;
|
return new UpstreamPathTemplate(route, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List<string> placeholders, int postitionOfPlaceHolderClosingBracket)
|
||||||
|
{
|
||||||
|
if(upstreamTemplate.Substring(0, 2) == "/{" && placeholders.Count == 1 && upstreamTemplate.Length == postitionOfPlaceHolderClosingBracket + 1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.File
|
namespace Ocelot.Configuration.File
|
||||||
{
|
{
|
||||||
@ -11,5 +12,14 @@ namespace Ocelot.Configuration.File
|
|||||||
|
|
||||||
public string AuthenticationProviderKey {get; set;}
|
public string AuthenticationProviderKey {get; set;}
|
||||||
public List<string> AllowedScopes { get; set; }
|
public List<string> AllowedScopes { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append($"{nameof(AuthenticationProviderKey)}:{AuthenticationProviderKey},{nameof(AllowedScopes)}:[");
|
||||||
|
sb.AppendJoin(',', AllowedScopes);
|
||||||
|
sb.Append("]");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ namespace Ocelot.Configuration.File
|
|||||||
public string RequestIdKey { get; set; }
|
public string RequestIdKey { get; set; }
|
||||||
|
|
||||||
public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;}
|
public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;}
|
||||||
public string AdministrationPath {get;set;}
|
|
||||||
|
|
||||||
public FileRateLimitOptions RateLimitOptions { get; set; }
|
public FileRateLimitOptions RateLimitOptions { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.File
|
namespace Ocelot.Configuration.File
|
||||||
@ -30,5 +31,20 @@ namespace Ocelot.Configuration.File
|
|||||||
/// Maximum number of requests that a client can make in a defined period
|
/// Maximum number of requests that a client can make in a defined period
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long Limit { get; set; }
|
public long Limit { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
if (!EnableRateLimiting)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append(
|
||||||
|
$"{nameof(Period)}:{Period},{nameof(PeriodTimespan)}:{PeriodTimespan:F},{nameof(Limit)}:{Limit},{nameof(ClientWhitelist)}:[");
|
||||||
|
|
||||||
|
sb.AppendJoin(',', ClientWhitelist);
|
||||||
|
sb.Append(']');
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Configuration.Provider;
|
using Ocelot.Configuration.Provider;
|
||||||
using Ocelot.Configuration.Setter;
|
using Ocelot.Configuration.Setter;
|
||||||
|
using Ocelot.Raft;
|
||||||
|
using Rafty.Concensus;
|
||||||
|
|
||||||
namespace Ocelot.Controllers
|
namespace Ocelot.Configuration
|
||||||
{
|
{
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("configuration")]
|
[Route("configuration")]
|
||||||
@ -13,11 +17,13 @@ namespace Ocelot.Controllers
|
|||||||
{
|
{
|
||||||
private readonly IFileConfigurationProvider _configGetter;
|
private readonly IFileConfigurationProvider _configGetter;
|
||||||
private readonly IFileConfigurationSetter _configSetter;
|
private readonly IFileConfigurationSetter _configSetter;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
public FileConfigurationController(IFileConfigurationProvider getFileConfig, IFileConfigurationSetter configSetter)
|
public FileConfigurationController(IFileConfigurationProvider getFileConfig, IFileConfigurationSetter configSetter, IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_configGetter = getFileConfig;
|
_configGetter = getFileConfig;
|
||||||
_configSetter = configSetter;
|
_configSetter = configSetter;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -36,9 +42,23 @@ namespace Ocelot.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Post([FromBody]FileConfiguration fileConfiguration)
|
public async Task<IActionResult> Post([FromBody]FileConfiguration fileConfiguration)
|
||||||
{
|
{
|
||||||
|
//todo - this code is a bit shit sort it out..
|
||||||
|
var test = _serviceProvider.GetService(typeof(INode));
|
||||||
|
if (test != null)
|
||||||
|
{
|
||||||
|
var node = (INode)test;
|
||||||
|
var result = node.Accept(new UpdateFileConfiguration(fileConfiguration));
|
||||||
|
if (result.GetType() == typeof(Rafty.Concensus.ErrorResponse<UpdateFileConfiguration>))
|
||||||
|
{
|
||||||
|
return new BadRequestObjectResult("There was a problem. This error message sucks raise an issue in GitHub.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkObjectResult(result.Command.Configuration);
|
||||||
|
}
|
||||||
|
|
||||||
var response = await _configSetter.Set(fileConfiguration);
|
var response = await _configSetter.Set(fileConfiguration);
|
||||||
|
|
||||||
if(response.IsError)
|
if (response.IsError)
|
||||||
{
|
{
|
||||||
return new BadRequestObjectResult(response.Errors);
|
return new BadRequestObjectResult(response.Errors);
|
||||||
}
|
}
|
@ -7,5 +7,6 @@ namespace Ocelot.Configuration
|
|||||||
List<ReRoute> ReRoutes { get; }
|
List<ReRoute> ReRoutes { get; }
|
||||||
string AdministrationPath {get;}
|
string AdministrationPath {get;}
|
||||||
ServiceProviderConfiguration ServiceProviderConfiguration {get;}
|
ServiceProviderConfiguration ServiceProviderConfiguration {get;}
|
||||||
|
string RequestId {get;}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,15 +4,17 @@ namespace Ocelot.Configuration
|
|||||||
{
|
{
|
||||||
public class OcelotConfiguration : IOcelotConfiguration
|
public class OcelotConfiguration : IOcelotConfiguration
|
||||||
{
|
{
|
||||||
public OcelotConfiguration(List<ReRoute> reRoutes, string administrationPath, ServiceProviderConfiguration serviceProviderConfiguration)
|
public OcelotConfiguration(List<ReRoute> reRoutes, string administrationPath, ServiceProviderConfiguration serviceProviderConfiguration, string requestId)
|
||||||
{
|
{
|
||||||
ReRoutes = reRoutes;
|
ReRoutes = reRoutes;
|
||||||
AdministrationPath = administrationPath;
|
AdministrationPath = administrationPath;
|
||||||
ServiceProviderConfiguration = serviceProviderConfiguration;
|
ServiceProviderConfiguration = serviceProviderConfiguration;
|
||||||
|
RequestId = requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ReRoute> ReRoutes { get; }
|
public List<ReRoute> ReRoutes { get; }
|
||||||
public string AdministrationPath {get;}
|
public string AdministrationPath {get;}
|
||||||
public ServiceProviderConfiguration ServiceProviderConfiguration {get;}
|
public ServiceProviderConfiguration ServiceProviderConfiguration {get;}
|
||||||
|
public string RequestId {get;}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,16 +7,9 @@ namespace Ocelot.Configuration.Provider
|
|||||||
public interface IIdentityServerConfiguration
|
public interface IIdentityServerConfiguration
|
||||||
{
|
{
|
||||||
string ApiName { get; }
|
string ApiName { get; }
|
||||||
|
string ApiSecret { get; }
|
||||||
bool RequireHttps { get; }
|
bool RequireHttps { get; }
|
||||||
List<string> AllowedScopes { get; }
|
List<string> AllowedScopes { get; }
|
||||||
SupportedTokens SupportedTokens { get; }
|
|
||||||
string ApiSecret { get; }
|
|
||||||
string Description {get;}
|
|
||||||
bool Enabled {get;}
|
|
||||||
IEnumerable<string> AllowedGrantTypes {get;}
|
|
||||||
AccessTokenType AccessTokenType {get;}
|
|
||||||
bool RequireClientSecret {get;}
|
|
||||||
List<User> Users {get;}
|
|
||||||
string CredentialsSigningCertificateLocation { get; }
|
string CredentialsSigningCertificateLocation { get; }
|
||||||
string CredentialsSigningCertificatePassword { get; }
|
string CredentialsSigningCertificatePassword { get; }
|
||||||
}
|
}
|
||||||
|
@ -9,27 +9,15 @@ namespace Ocelot.Configuration.Provider
|
|||||||
public IdentityServerConfiguration(
|
public IdentityServerConfiguration(
|
||||||
string apiName,
|
string apiName,
|
||||||
bool requireHttps,
|
bool requireHttps,
|
||||||
SupportedTokens supportedTokens,
|
|
||||||
string apiSecret,
|
string apiSecret,
|
||||||
List<string> allowedScopes,
|
List<string> allowedScopes,
|
||||||
string description,
|
string credentialsSigningCertificateLocation,
|
||||||
bool enabled,
|
string credentialsSigningCertificatePassword)
|
||||||
IEnumerable<string> grantType,
|
|
||||||
AccessTokenType accessTokenType,
|
|
||||||
bool requireClientSecret,
|
|
||||||
List<User> users, string credentialsSigningCertificateLocation, string credentialsSigningCertificatePassword)
|
|
||||||
{
|
{
|
||||||
ApiName = apiName;
|
ApiName = apiName;
|
||||||
RequireHttps = requireHttps;
|
RequireHttps = requireHttps;
|
||||||
SupportedTokens = supportedTokens;
|
|
||||||
ApiSecret = apiSecret;
|
ApiSecret = apiSecret;
|
||||||
AllowedScopes = allowedScopes;
|
AllowedScopes = allowedScopes;
|
||||||
Description = description;
|
|
||||||
Enabled = enabled;
|
|
||||||
AllowedGrantTypes = grantType;
|
|
||||||
AccessTokenType = accessTokenType;
|
|
||||||
RequireClientSecret = requireClientSecret;
|
|
||||||
Users = users;
|
|
||||||
CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation;
|
CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation;
|
||||||
CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword;
|
CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword;
|
||||||
}
|
}
|
||||||
@ -37,14 +25,7 @@ namespace Ocelot.Configuration.Provider
|
|||||||
public string ApiName { get; private set; }
|
public string ApiName { get; private set; }
|
||||||
public bool RequireHttps { get; private set; }
|
public bool RequireHttps { get; private set; }
|
||||||
public List<string> AllowedScopes { get; private set; }
|
public List<string> AllowedScopes { get; private set; }
|
||||||
public SupportedTokens SupportedTokens { get; private set; }
|
|
||||||
public string ApiSecret { get; private set; }
|
public string ApiSecret { get; private set; }
|
||||||
public string Description {get;private set;}
|
|
||||||
public bool Enabled {get;private set;}
|
|
||||||
public IEnumerable<string> AllowedGrantTypes {get;private set;}
|
|
||||||
public AccessTokenType AccessTokenType {get;private set;}
|
|
||||||
public bool RequireClientSecret {get;private set;}
|
|
||||||
public List<User> Users {get;private set;}
|
|
||||||
public string CredentialsSigningCertificateLocation { get; private set; }
|
public string CredentialsSigningCertificateLocation { get; private set; }
|
||||||
public string CredentialsSigningCertificatePassword { get; private set; }
|
public string CredentialsSigningCertificatePassword { get; private set; }
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
namespace Ocelot.Configuration.Provider
|
|
||||||
{
|
|
||||||
public class User
|
|
||||||
{
|
|
||||||
public User(string subject, string userName, string hash, string salt)
|
|
||||||
{
|
|
||||||
Subject = subject;
|
|
||||||
UserName = userName;
|
|
||||||
Hash = hash;
|
|
||||||
Salt = salt;
|
|
||||||
}
|
|
||||||
public string Subject { get; private set; }
|
|
||||||
public string UserName { get; private set; }
|
|
||||||
public string Hash { get; private set; }
|
|
||||||
public string Salt { get; private set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using Ocelot.Configuration.Creator;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.Configuration
|
namespace Ocelot.Configuration
|
||||||
@ -9,7 +10,7 @@ namespace Ocelot.Configuration
|
|||||||
public ReRoute(PathTemplate downstreamPathTemplate,
|
public ReRoute(PathTemplate downstreamPathTemplate,
|
||||||
PathTemplate upstreamPathTemplate,
|
PathTemplate upstreamPathTemplate,
|
||||||
List<HttpMethod> upstreamHttpMethod,
|
List<HttpMethod> upstreamHttpMethod,
|
||||||
string upstreamTemplatePattern,
|
UpstreamPathTemplate upstreamTemplatePattern,
|
||||||
bool isAuthenticated,
|
bool isAuthenticated,
|
||||||
AuthenticationOptions authenticationOptions,
|
AuthenticationOptions authenticationOptions,
|
||||||
List<ClaimToThing> claimsToHeaders,
|
List<ClaimToThing> claimsToHeaders,
|
||||||
@ -67,7 +68,7 @@ namespace Ocelot.Configuration
|
|||||||
public string ReRouteKey {get;private set;}
|
public string ReRouteKey {get;private set;}
|
||||||
public PathTemplate DownstreamPathTemplate { get; private set; }
|
public PathTemplate DownstreamPathTemplate { get; private set; }
|
||||||
public PathTemplate UpstreamPathTemplate { get; private set; }
|
public PathTemplate UpstreamPathTemplate { get; private set; }
|
||||||
public string UpstreamTemplatePattern { get; private set; }
|
public UpstreamPathTemplate UpstreamTemplatePattern { get; private set; }
|
||||||
public List<HttpMethod> UpstreamHttpMethod { get; private set; }
|
public List<HttpMethod> UpstreamHttpMethod { get; private set; }
|
||||||
public bool IsAuthenticated { get; private set; }
|
public bool IsAuthenticated { get; private set; }
|
||||||
public bool IsAuthorised { get; private set; }
|
public bool IsAuthorised { get; private set; }
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
using Ocelot.Errors;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Validator
|
|
||||||
{
|
|
||||||
public class DownstreamPathTemplateAlreadyUsedError : Error
|
|
||||||
{
|
|
||||||
public DownstreamPathTemplateAlreadyUsedError(string message) : base(message, OcelotErrorCode.DownstreampathTemplateAlreadyUsedError)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
using Ocelot.Errors;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Validator
|
|
||||||
{
|
|
||||||
public class DownstreamPathTemplateContainsSchemeError : Error
|
|
||||||
{
|
|
||||||
public DownstreamPathTemplateContainsSchemeError(string message)
|
|
||||||
: base(message, OcelotErrorCode.DownstreamPathTemplateContainsSchemeError)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
using Ocelot.Errors;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Validator
|
|
||||||
{
|
|
||||||
public class PathTemplateDoesntStartWithForwardSlash : Error
|
|
||||||
{
|
|
||||||
public PathTemplateDoesntStartWithForwardSlash(string message)
|
|
||||||
: base(message, OcelotErrorCode.PathTemplateDoesntStartWithForwardSlash)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,65 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Errors;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Validator
|
||||||
|
{
|
||||||
|
public class FileConfigurationFluentValidator : AbstractValidator<FileConfiguration>, IConfigurationValidator
|
||||||
|
{
|
||||||
|
public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider)
|
||||||
|
{
|
||||||
|
RuleFor(configuration => configuration.ReRoutes)
|
||||||
|
.SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider));
|
||||||
|
|
||||||
|
RuleForEach(configuration => configuration.ReRoutes)
|
||||||
|
.Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.ReRoutes))
|
||||||
|
.WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<ConfigurationValidationResult>> IsValid(FileConfiguration configuration)
|
||||||
|
{
|
||||||
|
var validateResult = await ValidateAsync(configuration);
|
||||||
|
|
||||||
|
if (validateResult.IsValid)
|
||||||
|
{
|
||||||
|
return new OkResponse<ConfigurationValidationResult>(new ConfigurationValidationResult(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
var errors = validateResult.Errors.Select(failure => new FileValidationFailedError(failure.ErrorMessage));
|
||||||
|
|
||||||
|
var result = new ConfigurationValidationResult(true, errors.Cast<Error>().ToList());
|
||||||
|
|
||||||
|
return new OkResponse<ConfigurationValidationResult>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsNotDuplicateIn(FileReRoute reRoute, List<FileReRoute> reRoutes)
|
||||||
|
{
|
||||||
|
var matchingReRoutes = reRoutes.Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate).ToList();
|
||||||
|
|
||||||
|
if(matchingReRoutes.Count == 1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowAllVerbs = matchingReRoutes.Any(x => x.UpstreamHttpMethod.Count == 0);
|
||||||
|
|
||||||
|
var duplicateAllowAllVerbs = matchingReRoutes.Count(x => x.UpstreamHttpMethod.Count == 0) > 1;
|
||||||
|
|
||||||
|
var specificVerbs = matchingReRoutes.Any(x => x.UpstreamHttpMethod.Count != 0);
|
||||||
|
|
||||||
|
var duplicateSpecificVerbs = matchingReRoutes.SelectMany(x => x.UpstreamHttpMethod).GroupBy(x => x.ToLower()).SelectMany(x => x.Skip(1)).Any();
|
||||||
|
|
||||||
|
if (duplicateAllowAllVerbs || duplicateSpecificVerbs || (allowAllVerbs && specificVerbs))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,223 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authentication;
|
|
||||||
using Ocelot.Configuration.File;
|
|
||||||
using Ocelot.Errors;
|
|
||||||
using Ocelot.Responses;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Validator
|
|
||||||
{
|
|
||||||
public class FileConfigurationValidator : IConfigurationValidator
|
|
||||||
{
|
|
||||||
private readonly IAuthenticationSchemeProvider _provider;
|
|
||||||
|
|
||||||
public FileConfigurationValidator(IAuthenticationSchemeProvider provider)
|
|
||||||
{
|
|
||||||
_provider = provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Response<ConfigurationValidationResult>> IsValid(FileConfiguration configuration)
|
|
||||||
{
|
|
||||||
var result = CheckForDuplicateReRoutes(configuration);
|
|
||||||
|
|
||||||
if (result.IsError)
|
|
||||||
{
|
|
||||||
return new OkResponse<ConfigurationValidationResult>(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
result = CheckDownstreamTemplatePathBeingsWithForwardSlash(configuration);
|
|
||||||
|
|
||||||
if (result.IsError)
|
|
||||||
{
|
|
||||||
return new OkResponse<ConfigurationValidationResult>(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
result = CheckUpstreamTemplatePathBeingsWithForwardSlash(configuration);
|
|
||||||
|
|
||||||
if (result.IsError)
|
|
||||||
{
|
|
||||||
return new OkResponse<ConfigurationValidationResult>(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
result = await CheckForUnsupportedAuthenticationProviders(configuration);
|
|
||||||
|
|
||||||
if (result.IsError)
|
|
||||||
{
|
|
||||||
return new OkResponse<ConfigurationValidationResult>(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
result = CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(configuration);
|
|
||||||
|
|
||||||
if (result.IsError)
|
|
||||||
{
|
|
||||||
return new OkResponse<ConfigurationValidationResult>(result);
|
|
||||||
}
|
|
||||||
result = CheckForReRoutesRateLimitOptions(configuration);
|
|
||||||
|
|
||||||
if (result.IsError)
|
|
||||||
{
|
|
||||||
return new OkResponse<ConfigurationValidationResult>(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new OkResponse<ConfigurationValidationResult>(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConfigurationValidationResult CheckDownstreamTemplatePathBeingsWithForwardSlash(FileConfiguration configuration)
|
|
||||||
{
|
|
||||||
var errors = new List<Error>();
|
|
||||||
|
|
||||||
foreach(var reRoute in configuration.ReRoutes)
|
|
||||||
{
|
|
||||||
if(!reRoute.DownstreamPathTemplate.StartsWith("/"))
|
|
||||||
{
|
|
||||||
errors.Add(new PathTemplateDoesntStartWithForwardSlash($"{reRoute.DownstreamPathTemplate} doesnt start with forward slash"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(errors.Any())
|
|
||||||
{
|
|
||||||
return new ConfigurationValidationResult(true, errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ConfigurationValidationResult(false, errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConfigurationValidationResult CheckUpstreamTemplatePathBeingsWithForwardSlash(FileConfiguration configuration)
|
|
||||||
{
|
|
||||||
var errors = new List<Error>();
|
|
||||||
|
|
||||||
foreach(var reRoute in configuration.ReRoutes)
|
|
||||||
{
|
|
||||||
if(!reRoute.UpstreamPathTemplate.StartsWith("/"))
|
|
||||||
{
|
|
||||||
errors.Add(new PathTemplateDoesntStartWithForwardSlash($"{reRoute.DownstreamPathTemplate} doesnt start with forward slash"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(errors.Any())
|
|
||||||
{
|
|
||||||
return new ConfigurationValidationResult(true, errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ConfigurationValidationResult(false, errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<ConfigurationValidationResult> CheckForUnsupportedAuthenticationProviders(FileConfiguration configuration)
|
|
||||||
{
|
|
||||||
var errors = new List<Error>();
|
|
||||||
|
|
||||||
foreach (var reRoute in configuration.ReRoutes)
|
|
||||||
{
|
|
||||||
var isAuthenticated = !string.IsNullOrEmpty(reRoute.AuthenticationOptions.AuthenticationProviderKey);
|
|
||||||
|
|
||||||
if (!isAuthenticated)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = await _provider.GetAllSchemesAsync();
|
|
||||||
var schemes = data.ToList();
|
|
||||||
if (schemes.Any(x => x.Name == reRoute.AuthenticationOptions.AuthenticationProviderKey))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var error = new UnsupportedAuthenticationProviderError($"{reRoute.AuthenticationOptions.AuthenticationProviderKey} is unsupported authentication provider, upstream template is {reRoute.UpstreamPathTemplate}, upstream method is {reRoute.UpstreamHttpMethod}");
|
|
||||||
errors.Add(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.Count > 0
|
|
||||||
? new ConfigurationValidationResult(true, errors)
|
|
||||||
: new ConfigurationValidationResult(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConfigurationValidationResult CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(FileConfiguration configuration)
|
|
||||||
{
|
|
||||||
var errors = new List<Error>();
|
|
||||||
|
|
||||||
foreach(var reRoute in configuration.ReRoutes)
|
|
||||||
{
|
|
||||||
if(reRoute.DownstreamPathTemplate.Contains("https://")
|
|
||||||
|| reRoute.DownstreamPathTemplate.Contains("http://"))
|
|
||||||
{
|
|
||||||
errors.Add(new DownstreamPathTemplateContainsSchemeError($"{reRoute.DownstreamPathTemplate} contains scheme"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(errors.Any())
|
|
||||||
{
|
|
||||||
return new ConfigurationValidationResult(true, errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ConfigurationValidationResult(false, errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConfigurationValidationResult CheckForDuplicateReRoutes(FileConfiguration configuration)
|
|
||||||
{
|
|
||||||
var duplicatedUpstreamPathTemplates = new List<string>();
|
|
||||||
|
|
||||||
var distinctUpstreamPathTemplates = configuration.ReRoutes.Select(x => x.UpstreamPathTemplate).Distinct();
|
|
||||||
|
|
||||||
foreach (string upstreamPathTemplate in distinctUpstreamPathTemplates)
|
|
||||||
{
|
|
||||||
var reRoutesWithUpstreamPathTemplate = configuration.ReRoutes.Where(x => x.UpstreamPathTemplate == upstreamPathTemplate);
|
|
||||||
|
|
||||||
var hasEmptyListToAllowAllHttpVerbs = reRoutesWithUpstreamPathTemplate.Where(x => x.UpstreamHttpMethod.Count() == 0).Any();
|
|
||||||
var hasDuplicateEmptyListToAllowAllHttpVerbs = reRoutesWithUpstreamPathTemplate.Where(x => x.UpstreamHttpMethod.Count() == 0).Count() > 1;
|
|
||||||
var hasSpecificHttpVerbs = reRoutesWithUpstreamPathTemplate.Where(x => x.UpstreamHttpMethod.Count() > 0).Any();
|
|
||||||
var hasDuplicateSpecificHttpVerbs = reRoutesWithUpstreamPathTemplate.SelectMany(x => x.UpstreamHttpMethod).GroupBy(x => x.ToLower()).SelectMany(x => x.Skip(1)).Any();
|
|
||||||
|
|
||||||
if (hasDuplicateEmptyListToAllowAllHttpVerbs || hasDuplicateSpecificHttpVerbs || (hasEmptyListToAllowAllHttpVerbs && hasSpecificHttpVerbs))
|
|
||||||
{
|
|
||||||
duplicatedUpstreamPathTemplates.Add(upstreamPathTemplate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duplicatedUpstreamPathTemplates.Count() == 0)
|
|
||||||
{
|
|
||||||
return new ConfigurationValidationResult(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var errors = duplicatedUpstreamPathTemplates
|
|
||||||
.Select(d => new DownstreamPathTemplateAlreadyUsedError(string.Format("Duplicate DownstreamPath: {0}", d)))
|
|
||||||
.Cast<Error>()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return new ConfigurationValidationResult(true, errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConfigurationValidationResult CheckForReRoutesRateLimitOptions(FileConfiguration configuration)
|
|
||||||
{
|
|
||||||
var errors = new List<Error>();
|
|
||||||
|
|
||||||
foreach (var reRoute in configuration.ReRoutes)
|
|
||||||
{
|
|
||||||
if (reRoute.RateLimitOptions.EnableRateLimiting)
|
|
||||||
{
|
|
||||||
if (!IsValidPeriod(reRoute))
|
|
||||||
{
|
|
||||||
errors.Add(new RateLimitOptionsValidationError($"{reRoute.RateLimitOptions.Period} not contains scheme"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors.Any())
|
|
||||||
{
|
|
||||||
return new ConfigurationValidationResult(true, errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ConfigurationValidationResult(false, errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsValidPeriod(FileReRoute reRoute)
|
|
||||||
{
|
|
||||||
string period = reRoute.RateLimitOptions.Period;
|
|
||||||
|
|
||||||
return period.Contains("s") || period.Contains("m") || period.Contains("h") || period.Contains("d");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Validator
|
||||||
|
{
|
||||||
|
public class FileValidationFailedError : Error
|
||||||
|
{
|
||||||
|
public FileValidationFailedError(string message) : base(message, OcelotErrorCode.FileValidationFailedError)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +0,0 @@
|
|||||||
using Ocelot.Errors;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Validator
|
|
||||||
{
|
|
||||||
public class RateLimitOptionsValidationError : Error
|
|
||||||
{
|
|
||||||
public RateLimitOptionsValidationError(string message)
|
|
||||||
: base(message, OcelotErrorCode.RateLimitOptionsError)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
71
src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs
Normal file
71
src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Validator
|
||||||
|
{
|
||||||
|
public class ReRouteFluentValidator : AbstractValidator<FileReRoute>
|
||||||
|
{
|
||||||
|
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
|
||||||
|
|
||||||
|
public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider)
|
||||||
|
{
|
||||||
|
_authenticationSchemeProvider = authenticationSchemeProvider;
|
||||||
|
|
||||||
|
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
||||||
|
.Must(path => path.StartsWith("/"))
|
||||||
|
.WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash");
|
||||||
|
|
||||||
|
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
||||||
|
.Must(path => path.StartsWith("/"))
|
||||||
|
.WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash");
|
||||||
|
|
||||||
|
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
||||||
|
.Must(path => !path.Contains("https://") && !path.Contains("http://"))
|
||||||
|
.WithMessage("{PropertyName} {PropertyValue} contains scheme");
|
||||||
|
|
||||||
|
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
||||||
|
.Must(path => !path.Contains("https://") && !path.Contains("http://"))
|
||||||
|
.WithMessage("{PropertyName} {PropertyValue} contains scheme");
|
||||||
|
|
||||||
|
RuleFor(reRoute => reRoute.RateLimitOptions)
|
||||||
|
.Must(IsValidPeriod)
|
||||||
|
.WithMessage("RateLimitOptions.Period does not contains (s,m,h,d)");
|
||||||
|
|
||||||
|
RuleFor(reRoute => reRoute.AuthenticationOptions)
|
||||||
|
.MustAsync(IsSupportedAuthenticationProviders)
|
||||||
|
.WithMessage("{PropertyValue} is unsupported authentication provider");
|
||||||
|
|
||||||
|
When(reRoute => reRoute.UseServiceDiscovery, () => {
|
||||||
|
RuleFor(r => r.ServiceName).NotEmpty().WithMessage("ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!");
|
||||||
|
});
|
||||||
|
|
||||||
|
When(reRoute => !reRoute.UseServiceDiscovery, () => {
|
||||||
|
RuleFor(r => r.DownstreamHost).NotEmpty().WithMessage("When not using service discover DownstreamHost must be set or Ocelot cannot find your service!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> IsSupportedAuthenticationProviders(FileAuthenticationOptions authenticationOptions, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(authenticationOptions.AuthenticationProviderKey))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var schemes = await _authenticationSchemeProvider.GetAllSchemesAsync();
|
||||||
|
|
||||||
|
var supportedSchemes = schemes.Select(scheme => scheme.Name).ToList();
|
||||||
|
|
||||||
|
return supportedSchemes.Contains(authenticationOptions.AuthenticationProviderKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidPeriod(FileRateLimitRule rateLimitOptions)
|
||||||
|
{
|
||||||
|
string period = rateLimitOptions.Period;
|
||||||
|
|
||||||
|
return !rateLimitOptions.EnableRateLimiting || period.Contains("s") || period.Contains("m") || period.Contains("h") || period.Contains("d");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
using Ocelot.Errors;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Validator
|
|
||||||
{
|
|
||||||
public class UnsupportedAuthenticationProviderError : Error
|
|
||||||
{
|
|
||||||
public UnsupportedAuthenticationProviderError(string message)
|
|
||||||
: base(message, OcelotErrorCode.UnsupportedAuthenticationProviderError)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,5 +7,6 @@ namespace Ocelot.DependencyInjection
|
|||||||
{
|
{
|
||||||
IOcelotBuilder AddStoreOcelotConfigurationInConsul();
|
IOcelotBuilder AddStoreOcelotConfigurationInConsul();
|
||||||
IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings);
|
IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings);
|
||||||
|
IOcelotAdministrationBuilder AddAdministration(string path, string secret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ using Ocelot.Configuration.Provider;
|
|||||||
using Ocelot.Configuration.Repository;
|
using Ocelot.Configuration.Repository;
|
||||||
using Ocelot.Configuration.Setter;
|
using Ocelot.Configuration.Setter;
|
||||||
using Ocelot.Configuration.Validator;
|
using Ocelot.Configuration.Validator;
|
||||||
using Ocelot.Controllers;
|
|
||||||
using Ocelot.DownstreamRouteFinder.Finder;
|
using Ocelot.DownstreamRouteFinder.Finder;
|
||||||
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
using Ocelot.DownstreamRouteFinder.UrlMatcher;
|
||||||
using Ocelot.DownstreamUrlCreator;
|
using Ocelot.DownstreamUrlCreator;
|
||||||
@ -47,15 +46,21 @@ using Ocelot.Configuration.Builder;
|
|||||||
using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider;
|
using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Ocelot.Raft;
|
||||||
|
using Rafty.Concensus;
|
||||||
|
using Rafty.FiniteStateMachine;
|
||||||
|
using Rafty.Infrastructure;
|
||||||
|
using Rafty.Log;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Ocelot.DependencyInjection
|
namespace Ocelot.DependencyInjection
|
||||||
{
|
{
|
||||||
public class OcelotBuilder : IOcelotBuilder
|
public class OcelotBuilder : IOcelotBuilder
|
||||||
{
|
{
|
||||||
private IServiceCollection _services;
|
private IServiceCollection _services;
|
||||||
private IConfigurationRoot _configurationRoot;
|
private IConfiguration _configurationRoot;
|
||||||
|
|
||||||
public OcelotBuilder(IServiceCollection services, IConfigurationRoot configurationRoot)
|
public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot)
|
||||||
{
|
{
|
||||||
_configurationRoot = configurationRoot;
|
_configurationRoot = configurationRoot;
|
||||||
_services = services;
|
_services = services;
|
||||||
@ -72,7 +77,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
_services.Configure<FileConfiguration>(configurationRoot);
|
_services.Configure<FileConfiguration>(configurationRoot);
|
||||||
_services.TryAddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
|
_services.TryAddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
|
||||||
_services.TryAddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
|
_services.TryAddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
|
||||||
_services.TryAddSingleton<IConfigurationValidator, FileConfigurationValidator>();
|
_services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>();
|
||||||
_services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();
|
_services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();
|
||||||
_services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
|
_services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
|
||||||
_services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
|
_services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
|
||||||
@ -103,7 +108,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
_services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>();
|
_services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>();
|
||||||
_services.TryAddSingleton<IClaimsParser, ClaimsParser>();
|
_services.TryAddSingleton<IClaimsParser, ClaimsParser>();
|
||||||
_services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
|
_services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
|
||||||
_services.TryAddSingleton<IUrlPathPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
|
_services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
|
||||||
_services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>();
|
_services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>();
|
||||||
_services.TryAddSingleton<IDownstreamRouteFinder, DownstreamRouteFinder.Finder.DownstreamRouteFinder>();
|
_services.TryAddSingleton<IDownstreamRouteFinder, DownstreamRouteFinder.Finder.DownstreamRouteFinder>();
|
||||||
_services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
_services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
||||||
@ -117,18 +122,10 @@ namespace Ocelot.DependencyInjection
|
|||||||
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc
|
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc
|
||||||
// could maybe use a scoped data repository
|
// could maybe use a scoped data repository
|
||||||
_services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
_services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||||
_services.TryAddScoped<IRequestScopedDataRepository, HttpDataRepository>();
|
_services.TryAddSingleton<IRequestScopedDataRepository, HttpDataRepository>();
|
||||||
_services.AddMemoryCache();
|
_services.AddMemoryCache();
|
||||||
_services.TryAddSingleton<OcelotDiagnosticListener>();
|
_services.TryAddSingleton<OcelotDiagnosticListener>();
|
||||||
|
|
||||||
//add identity server for admin area
|
|
||||||
var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration();
|
|
||||||
|
|
||||||
if (identityServerConfiguration != null)
|
|
||||||
{
|
|
||||||
AddIdentityServer(identityServerConfiguration);
|
|
||||||
}
|
|
||||||
|
|
||||||
//add asp.net services..
|
//add asp.net services..
|
||||||
var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly;
|
var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly;
|
||||||
|
|
||||||
@ -141,6 +138,24 @@ namespace Ocelot.DependencyInjection
|
|||||||
_services.AddLogging();
|
_services.AddLogging();
|
||||||
_services.AddMiddlewareAnalysis();
|
_services.AddMiddlewareAnalysis();
|
||||||
_services.AddWebEncoders();
|
_services.AddWebEncoders();
|
||||||
|
_services.AddSingleton<IAdministrationPath>(new NullAdministrationPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
|
||||||
|
{
|
||||||
|
var administrationPath = new AdministrationPath(path);
|
||||||
|
|
||||||
|
//add identity server for admin area
|
||||||
|
var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(secret);
|
||||||
|
|
||||||
|
if (identityServerConfiguration != null)
|
||||||
|
{
|
||||||
|
AddIdentityServer(identityServerConfiguration, administrationPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath);
|
||||||
|
_services.Replace(descriptor);
|
||||||
|
return new OcelotAdministrationBuilder(_services, _configurationRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IOcelotBuilder AddStoreOcelotConfigurationInConsul()
|
public IOcelotBuilder AddStoreOcelotConfigurationInConsul()
|
||||||
@ -185,7 +200,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration)
|
private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath)
|
||||||
{
|
{
|
||||||
_services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
|
_services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
|
||||||
_services.TryAddSingleton<IHashMatcher, HashMatcher>();
|
_services.TryAddSingleton<IHashMatcher, HashMatcher>();
|
||||||
@ -194,8 +209,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
o.IssuerUri = "Ocelot";
|
o.IssuerUri = "Ocelot";
|
||||||
})
|
})
|
||||||
.AddInMemoryApiResources(Resources(identityServerConfiguration))
|
.AddInMemoryApiResources(Resources(identityServerConfiguration))
|
||||||
.AddInMemoryClients(Client(identityServerConfiguration))
|
.AddInMemoryClients(Client(identityServerConfiguration));
|
||||||
.AddResourceOwnerValidator<OcelotResourceOwnerPasswordValidator>();
|
|
||||||
|
|
||||||
//todo - refactor a method so we know why this is happening
|
//todo - refactor a method so we know why this is happening
|
||||||
var whb = _services.First(x => x.ServiceType == typeof(IWebHostBuilder));
|
var whb = _services.First(x => x.ServiceType == typeof(IWebHostBuilder));
|
||||||
@ -206,8 +220,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
_services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
|
_services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
|
||||||
.AddIdentityServerAuthentication(o =>
|
.AddIdentityServerAuthentication(o =>
|
||||||
{
|
{
|
||||||
var adminPath = _configurationRoot.GetValue("GlobalConfiguration:AdministrationPath", string.Empty);
|
o.Authority = baseSchemeUrlAndPort + adminPath.Path;
|
||||||
o.Authority = baseSchemeUrlAndPort + adminPath;
|
|
||||||
o.ApiName = identityServerConfiguration.ApiName;
|
o.ApiName = identityServerConfiguration.ApiName;
|
||||||
o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps;
|
o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps;
|
||||||
o.SupportedTokens = SupportedTokens.Both;
|
o.SupportedTokens = SupportedTokens.Both;
|
||||||
@ -240,7 +253,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
Value = identityServerConfiguration.ApiSecret.Sha256()
|
Value = identityServerConfiguration.ApiSecret.Sha256()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,12 +264,65 @@ namespace Ocelot.DependencyInjection
|
|||||||
new Client
|
new Client
|
||||||
{
|
{
|
||||||
ClientId = identityServerConfiguration.ApiName,
|
ClientId = identityServerConfiguration.ApiName,
|
||||||
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
|
AllowedGrantTypes = GrantTypes.ClientCredentials,
|
||||||
ClientSecrets = new List<Secret> {new Secret(identityServerConfiguration.ApiSecret.Sha256())},
|
ClientSecrets = new List<Secret> {new Secret(identityServerConfiguration.ApiSecret.Sha256())},
|
||||||
AllowedScopes = { identityServerConfiguration.ApiName }
|
AllowedScopes = { identityServerConfiguration.ApiName }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IOcelotAdministrationBuilder
|
||||||
|
{
|
||||||
|
IOcelotAdministrationBuilder AddRafty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder
|
||||||
|
{
|
||||||
|
private IServiceCollection _services;
|
||||||
|
private IConfiguration _configurationRoot;
|
||||||
|
|
||||||
|
public OcelotAdministrationBuilder(IServiceCollection services, IConfiguration configurationRoot)
|
||||||
|
{
|
||||||
|
_configurationRoot = configurationRoot;
|
||||||
|
_services = services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IOcelotAdministrationBuilder AddRafty()
|
||||||
|
{
|
||||||
|
var settings = new InMemorySettings(4000, 5000, 100, 5000);
|
||||||
|
_services.AddSingleton<ILog, SqlLiteLog>();
|
||||||
|
_services.AddSingleton<IFiniteStateMachine, OcelotFiniteStateMachine>();
|
||||||
|
_services.AddSingleton<ISettings>(settings);
|
||||||
|
_services.AddSingleton<IPeersProvider, FilePeersProvider>();
|
||||||
|
_services.AddSingleton<INode, Node>();
|
||||||
|
_services.Configure<FilePeers>(_configurationRoot);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAdministrationPath
|
||||||
|
{
|
||||||
|
string Path {get;}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NullAdministrationPath : IAdministrationPath
|
||||||
|
{
|
||||||
|
public NullAdministrationPath()
|
||||||
|
{
|
||||||
|
Path = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Path {get;private set;}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AdministrationPath : IAdministrationPath
|
||||||
|
{
|
||||||
|
public AdministrationPath(string path)
|
||||||
|
{
|
||||||
|
Path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Path {get;private set;}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,16 @@ namespace Ocelot.DependencyInjection
|
|||||||
{
|
{
|
||||||
public static class ServiceCollectionExtensions
|
public static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
public static IOcelotBuilder AddOcelot(this IServiceCollection services,
|
public static IOcelotBuilder AddOcelot(this IServiceCollection services)
|
||||||
IConfigurationRoot configurationRoot)
|
|
||||||
{
|
{
|
||||||
return new OcelotBuilder(services, configurationRoot);
|
var service = services.First(x => x.ServiceType == typeof(IConfiguration));
|
||||||
|
var configuration = (IConfiguration)service.ImplementationInstance;
|
||||||
|
return new OcelotBuilder(services, configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IOcelotBuilder AddOcelot(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
return new OcelotBuilder(services, configuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,12 @@ namespace Ocelot.DownstreamRouteFinder
|
|||||||
{
|
{
|
||||||
public class DownstreamRoute
|
public class DownstreamRoute
|
||||||
{
|
{
|
||||||
public DownstreamRoute(List<UrlPathPlaceholderNameAndValue> templatePlaceholderNameAndValues, ReRoute reRoute)
|
public DownstreamRoute(List<PlaceholderNameAndValue> templatePlaceholderNameAndValues, ReRoute reRoute)
|
||||||
{
|
{
|
||||||
TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues;
|
TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues;
|
||||||
ReRoute = reRoute;
|
ReRoute = reRoute;
|
||||||
}
|
}
|
||||||
public List<UrlPathPlaceholderNameAndValue> TemplatePlaceholderNameAndValues { get; private set; }
|
public List<PlaceholderNameAndValue> TemplatePlaceholderNameAndValues { get; private set; }
|
||||||
public ReRoute ReRoute { get; private set; }
|
public ReRoute ReRoute { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,34 +13,30 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
|||||||
public class DownstreamRouteFinder : IDownstreamRouteFinder
|
public class DownstreamRouteFinder : IDownstreamRouteFinder
|
||||||
{
|
{
|
||||||
private readonly IUrlPathToUrlTemplateMatcher _urlMatcher;
|
private readonly IUrlPathToUrlTemplateMatcher _urlMatcher;
|
||||||
private readonly IUrlPathPlaceholderNameAndValueFinder _urlPathPlaceholderNameAndValueFinder;
|
private readonly IPlaceholderNameAndValueFinder __placeholderNameAndValueFinder;
|
||||||
|
|
||||||
public DownstreamRouteFinder(IUrlPathToUrlTemplateMatcher urlMatcher, IUrlPathPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder)
|
public DownstreamRouteFinder(IUrlPathToUrlTemplateMatcher urlMatcher, IPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder)
|
||||||
{
|
{
|
||||||
_urlMatcher = urlMatcher;
|
_urlMatcher = urlMatcher;
|
||||||
_urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder;
|
__placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response<DownstreamRoute> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod, IOcelotConfiguration configuration)
|
public Response<DownstreamRoute> FindDownstreamRoute(string path, string httpMethod, IOcelotConfiguration configuration)
|
||||||
{
|
{
|
||||||
var applicableReRoutes = configuration.ReRoutes.Where(r => r.UpstreamHttpMethod.Count == 0 || r.UpstreamHttpMethod.Select(x => x.Method.ToLower()).Contains(upstreamHttpMethod.ToLower()));
|
var applicableReRoutes = configuration.ReRoutes.Where(r => r.UpstreamHttpMethod.Count == 0 || r.UpstreamHttpMethod.Select(x => x.Method.ToLower()).Contains(httpMethod.ToLower())).OrderByDescending(x => x.UpstreamTemplatePattern.Priority);
|
||||||
|
|
||||||
foreach (var reRoute in applicableReRoutes)
|
foreach (var reRoute in applicableReRoutes)
|
||||||
{
|
{
|
||||||
if (upstreamUrlPath == reRoute.UpstreamTemplatePattern)
|
if (path == reRoute.UpstreamTemplatePattern.Template)
|
||||||
{
|
{
|
||||||
var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value);
|
return GetPlaceholderNamesAndValues(path, reRoute);
|
||||||
|
|
||||||
return new OkResponse<DownstreamRoute>(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var urlMatch = _urlMatcher.Match(upstreamUrlPath, reRoute.UpstreamTemplatePattern);
|
var urlMatch = _urlMatcher.Match(path, reRoute.UpstreamTemplatePattern.Template);
|
||||||
|
|
||||||
if (urlMatch.Data.Match)
|
if (urlMatch.Data.Match)
|
||||||
{
|
{
|
||||||
var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value);
|
return GetPlaceholderNamesAndValues(path, reRoute);
|
||||||
|
|
||||||
return new OkResponse<DownstreamRoute>(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,5 +45,12 @@ namespace Ocelot.DownstreamRouteFinder.Finder
|
|||||||
new UnableToFindDownstreamRouteError()
|
new UnableToFindDownstreamRouteError()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private OkResponse<DownstreamRoute> GetPlaceholderNamesAndValues(string path, ReRoute reRoute)
|
||||||
|
{
|
||||||
|
var templatePlaceholderNameAndValues = __placeholderNameAndValueFinder.Find(path, reRoute.UpstreamPathTemplate.Value);
|
||||||
|
|
||||||
|
return new OkResponse<DownstreamRoute>(new DownstreamRoute(templatePlaceholderNameAndValues.Data, reRoute));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -36,8 +36,8 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
|
|||||||
{
|
{
|
||||||
var upstreamUrlPath = context.Request.Path.ToString();
|
var upstreamUrlPath = context.Request.Path.ToString();
|
||||||
|
|
||||||
//todo make this getting config its own middleware one day?
|
|
||||||
var configuration = await _configProvider.Get();
|
var configuration = await _configProvider.Get();
|
||||||
|
|
||||||
if(configuration.IsError)
|
if(configuration.IsError)
|
||||||
{
|
{
|
||||||
_logger.LogError($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}");
|
_logger.LogError($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}");
|
||||||
|
@ -3,8 +3,8 @@ using Ocelot.Responses;
|
|||||||
|
|
||||||
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||||
{
|
{
|
||||||
public interface IUrlPathPlaceholderNameAndValueFinder
|
public interface IPlaceholderNameAndValueFinder
|
||||||
{
|
{
|
||||||
Response<List<UrlPathPlaceholderNameAndValue>> Find(string upstreamUrlPath, string upstreamUrlPathTemplate);
|
Response<List<PlaceholderNameAndValue>> Find(string path, string pathTemplate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||||
{
|
{
|
||||||
public class UrlPathPlaceholderNameAndValue
|
public class PlaceholderNameAndValue
|
||||||
{
|
{
|
||||||
public UrlPathPlaceholderNameAndValue(string templateVariableName, string templateVariableValue)
|
public PlaceholderNameAndValue(string name, string value)
|
||||||
{
|
{
|
||||||
TemplateVariableName = templateVariableName;
|
Name = name;
|
||||||
TemplateVariableValue = templateVariableValue;
|
Value = value;
|
||||||
}
|
}
|
||||||
public string TemplateVariableName {get;private set;}
|
public string Name {get;private set;}
|
||||||
public string TemplateVariableValue {get;private set;}
|
public string Value {get;private set;}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,44 +3,72 @@ using Ocelot.Responses;
|
|||||||
|
|
||||||
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
||||||
{
|
{
|
||||||
public class UrlPathPlaceholderNameAndValueFinder : IUrlPathPlaceholderNameAndValueFinder
|
public class UrlPathPlaceholderNameAndValueFinder : IPlaceholderNameAndValueFinder
|
||||||
{
|
{
|
||||||
public Response<List<UrlPathPlaceholderNameAndValue>> Find(string upstreamUrlPath, string upstreamUrlPathTemplate)
|
public Response<List<PlaceholderNameAndValue>> Find(string path, string pathTemplate)
|
||||||
{
|
{
|
||||||
var templateKeysAndValues = new List<UrlPathPlaceholderNameAndValue>();
|
var placeHolderNameAndValues = new List<PlaceholderNameAndValue>();
|
||||||
|
|
||||||
int counterForUrl = 0;
|
int counterForPath = 0;
|
||||||
|
|
||||||
for (int counterForTemplate = 0; counterForTemplate < upstreamUrlPathTemplate.Length; counterForTemplate++)
|
for (int counterForTemplate = 0; counterForTemplate < pathTemplate.Length; counterForTemplate++)
|
||||||
{
|
{
|
||||||
if ((upstreamUrlPath.Length > counterForUrl) && CharactersDontMatch(upstreamUrlPathTemplate[counterForTemplate], upstreamUrlPath[counterForUrl]) && ContinueScanningUrl(counterForUrl,upstreamUrlPath.Length))
|
if ((path.Length > counterForPath) && CharactersDontMatch(pathTemplate[counterForTemplate], path[counterForPath]) && ContinueScanningUrl(counterForPath,path.Length))
|
||||||
{
|
{
|
||||||
if (IsPlaceholder(upstreamUrlPathTemplate[counterForTemplate]))
|
if (IsPlaceholder(pathTemplate[counterForTemplate]))
|
||||||
{
|
{
|
||||||
var variableName = GetPlaceholderVariableName(upstreamUrlPathTemplate, counterForTemplate);
|
var placeholderName = GetPlaceholderName(pathTemplate, counterForTemplate);
|
||||||
|
|
||||||
var variableValue = GetPlaceholderVariableValue(upstreamUrlPathTemplate, variableName, upstreamUrlPath, counterForUrl);
|
var placeholderValue = GetPlaceholderValue(pathTemplate, placeholderName, path, counterForPath);
|
||||||
|
|
||||||
var templateVariableNameAndValue = new UrlPathPlaceholderNameAndValue(variableName, variableValue);
|
placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue));
|
||||||
|
|
||||||
templateKeysAndValues.Add(templateVariableNameAndValue);
|
counterForTemplate = GetNextCounterPosition(pathTemplate, counterForTemplate, '}');
|
||||||
|
|
||||||
counterForTemplate = GetNextCounterPosition(upstreamUrlPathTemplate, counterForTemplate, '}');
|
counterForPath = GetNextCounterPosition(path, counterForPath, '/');
|
||||||
|
|
||||||
counterForUrl = GetNextCounterPosition(upstreamUrlPath, counterForUrl, '/');
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OkResponse<List<UrlPathPlaceholderNameAndValue>>(templateKeysAndValues);
|
return new OkResponse<List<PlaceholderNameAndValue>>(placeHolderNameAndValues);
|
||||||
}
|
}
|
||||||
counterForUrl++;
|
else if(IsCatchAll(path, counterForPath, pathTemplate))
|
||||||
|
{
|
||||||
|
var endOfPlaceholder = GetNextCounterPosition(pathTemplate, counterForTemplate, '}');
|
||||||
|
|
||||||
|
var placeholderName = GetPlaceholderName(pathTemplate, 1);
|
||||||
|
|
||||||
|
if(NothingAfterFirstForwardSlash(path))
|
||||||
|
{
|
||||||
|
placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, ""));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var placeholderValue = GetPlaceholderValue(pathTemplate, placeholderName, path, counterForPath + 1);
|
||||||
|
placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
counterForTemplate = endOfPlaceholder;
|
||||||
|
}
|
||||||
|
counterForPath++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OkResponse<List<UrlPathPlaceholderNameAndValue>>(templateKeysAndValues);
|
return new OkResponse<List<PlaceholderNameAndValue>>(placeHolderNameAndValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetPlaceholderVariableValue(string urlPathTemplate, string variableName, string urlPath, int counterForUrl)
|
private bool IsCatchAll(string path, int counterForPath, string pathTemplate)
|
||||||
|
{
|
||||||
|
return string.IsNullOrEmpty(path) || (path.Length > counterForPath && path[counterForPath] == '/') && pathTemplate.Length > 1
|
||||||
|
&& pathTemplate.Substring(0, 2) == "/{"
|
||||||
|
&& pathTemplate.IndexOf('}') == pathTemplate.Length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool NothingAfterFirstForwardSlash(string path)
|
||||||
|
{
|
||||||
|
return path.Length == 1 || path.Length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetPlaceholderValue(string urlPathTemplate, string variableName, string urlPath, int counterForUrl)
|
||||||
{
|
{
|
||||||
var positionOfNextSlash = urlPath.IndexOf('/', counterForUrl);
|
var positionOfNextSlash = urlPath.IndexOf('/', counterForUrl);
|
||||||
|
|
||||||
@ -54,7 +82,7 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher
|
|||||||
return variableValue;
|
return variableValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetPlaceholderVariableName(string urlPathTemplate, int counterForTemplate)
|
private string GetPlaceholderName(string urlPathTemplate, int counterForTemplate)
|
||||||
{
|
{
|
||||||
var postitionOfPlaceHolderClosingBracket = urlPathTemplate.IndexOf('}', counterForTemplate) + 1;
|
var postitionOfPlaceHolderClosingBracket = urlPathTemplate.IndexOf('}', counterForTemplate) + 1;
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
|
|||||||
{
|
{
|
||||||
public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer
|
public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer
|
||||||
{
|
{
|
||||||
public Response<DownstreamPath> Replace(PathTemplate downstreamPathTemplate, List<UrlPathPlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
|
public Response<DownstreamPath> Replace(PathTemplate downstreamPathTemplate, List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
|
||||||
{
|
{
|
||||||
var downstreamPath = new StringBuilder();
|
var downstreamPath = new StringBuilder();
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
|
|||||||
|
|
||||||
foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues)
|
foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues)
|
||||||
{
|
{
|
||||||
downstreamPath.Replace(placeholderVariableAndValue.TemplateVariableName, placeholderVariableAndValue.TemplateVariableValue);
|
downstreamPath.Replace(placeholderVariableAndValue.Name, placeholderVariableAndValue.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OkResponse<DownstreamPath>(new DownstreamPath(downstreamPath.ToString()));
|
return new OkResponse<DownstreamPath>(new DownstreamPath(downstreamPath.ToString()));
|
||||||
|
@ -7,6 +7,6 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer
|
|||||||
{
|
{
|
||||||
public interface IDownstreamPathPlaceholderReplacer
|
public interface IDownstreamPathPlaceholderReplacer
|
||||||
{
|
{
|
||||||
Response<DownstreamPath> Replace(PathTemplate downstreamPathTemplate, List<UrlPathPlaceholderNameAndValue> urlPathPlaceholderNameAndValues);
|
Response<DownstreamPath> Replace(PathTemplate downstreamPathTemplate, List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,24 +1,34 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Ocelot.Configuration.Provider;
|
||||||
|
using Ocelot.Infrastructure.Extensions;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
namespace Ocelot.Errors.Middleware
|
namespace Ocelot.Errors.Middleware
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Catches all unhandled exceptions thrown by middleware, logs and returns a 500
|
/// Catches all unhandled exceptions thrown by middleware, logs and returns a 500
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ExceptionHandlerMiddleware
|
public class ExceptionHandlerMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly RequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly IOcelotLogger _logger;
|
private readonly IOcelotLogger _logger;
|
||||||
private readonly IRequestScopedDataRepository _requestScopedDataRepository;
|
private readonly IRequestScopedDataRepository _requestScopedDataRepository;
|
||||||
|
private readonly IOcelotConfigurationProvider _configProvider;
|
||||||
|
|
||||||
|
|
||||||
public ExceptionHandlerMiddleware(RequestDelegate next,
|
public ExceptionHandlerMiddleware(RequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
IRequestScopedDataRepository requestScopedDataRepository)
|
IRequestScopedDataRepository requestScopedDataRepository,
|
||||||
|
IOcelotConfigurationProvider configProvider)
|
||||||
|
:base(requestScopedDataRepository)
|
||||||
{
|
{
|
||||||
|
_configProvider = configProvider;
|
||||||
_next = next;
|
_next = next;
|
||||||
_requestScopedDataRepository = requestScopedDataRepository;
|
_requestScopedDataRepository = requestScopedDataRepository;
|
||||||
_logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleware>();
|
_logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleware>();
|
||||||
@ -28,26 +38,51 @@ namespace Ocelot.Errors.Middleware
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogDebug("ocelot pipeline started");
|
await TrySetGlobalRequestId(context);
|
||||||
|
|
||||||
_logger.LogDebug("calling next middleware");
|
_logger.LogDebug("ocelot pipeline started");
|
||||||
|
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(context);
|
||||||
|
|
||||||
_logger.LogDebug("succesfully called middleware");
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("error calling middleware");
|
_logger.LogDebug("error calling middleware");
|
||||||
|
|
||||||
var message = CreateMessage(context, e);
|
var message = CreateMessage(context, e);
|
||||||
|
|
||||||
_logger.LogError(message, e);
|
_logger.LogError(message, e);
|
||||||
|
|
||||||
SetInternalServerErrorOnResponse(context);
|
SetInternalServerErrorOnResponse(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("ocelot pipeline finished");
|
_logger.LogDebug("ocelot pipeline finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task TrySetGlobalRequestId(HttpContext context)
|
||||||
|
{
|
||||||
|
//try and get the global request id and set it for logs...
|
||||||
|
//shoudl this basically be immutable per request...i guess it should!
|
||||||
|
//first thing is get config
|
||||||
|
var configuration = await _configProvider.Get();
|
||||||
|
|
||||||
|
//if error throw to catch below..
|
||||||
|
if(configuration.IsError)
|
||||||
|
{
|
||||||
|
throw new Exception($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
//else set the request id?
|
||||||
|
var key = configuration.Data.RequestId;
|
||||||
|
|
||||||
|
StringValues upstreamRequestIds;
|
||||||
|
if (!string.IsNullOrEmpty(key) && context.Request.Headers.TryGetValue(key, out upstreamRequestIds))
|
||||||
|
{
|
||||||
|
context.TraceIdentifier = upstreamRequestIds.First();
|
||||||
|
_requestScopedDataRepository.Add<string>("RequestId", context.TraceIdentifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void SetInternalServerErrorOnResponse(HttpContext context)
|
private void SetInternalServerErrorOnResponse(HttpContext context)
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = 500;
|
context.Response.StatusCode = 500;
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
UnableToSetConfigInConsulError,
|
UnableToSetConfigInConsulError,
|
||||||
UnmappableRequestError,
|
UnmappableRequestError,
|
||||||
RateLimitOptionsError,
|
RateLimitOptionsError,
|
||||||
PathTemplateDoesntStartWithForwardSlash
|
PathTemplateDoesntStartWithForwardSlash,
|
||||||
|
FileValidationFailedError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,22 @@ namespace Ocelot.Infrastructure.RequestData
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Response Update<T>(string key, T value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_httpContextAccessor.HttpContext.Items[key] = value;
|
||||||
|
return new OkResponse();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
return new ErrorResponse(new List<Error>
|
||||||
|
{
|
||||||
|
new CannotAddDataError(string.Format($"Unable to update data for key: {key}, exception: {exception.Message}"))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Response<T> Get<T>(string key)
|
public Response<T> Get<T>(string key)
|
||||||
{
|
{
|
||||||
object obj;
|
object obj;
|
||||||
|
@ -5,6 +5,7 @@ namespace Ocelot.Infrastructure.RequestData
|
|||||||
public interface IRequestScopedDataRepository
|
public interface IRequestScopedDataRepository
|
||||||
{
|
{
|
||||||
Response Add<T>(string key, T value);
|
Response Add<T>(string key, T value);
|
||||||
|
Response Update<T>(string key, T value);
|
||||||
Response<T> Get<T>(string key);
|
Response<T> Get<T>(string key);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -20,33 +20,68 @@ namespace Ocelot.Logging
|
|||||||
|
|
||||||
public void LogTrace(string message, params object[] args)
|
public void LogTrace(string message, params object[] args)
|
||||||
{
|
{
|
||||||
_logger.LogTrace(GetMessageWithOcelotRequestId(message), args);
|
var requestId = GetOcelotRequestId();
|
||||||
|
var previousRequestId = GetOcelotPreviousRequestId();
|
||||||
|
_logger.LogTrace("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message},", requestId, previousRequestId, message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LogDebug(string message, params object[] args)
|
public void LogDebug(string message, params object[] args)
|
||||||
{
|
{
|
||||||
_logger.LogDebug(GetMessageWithOcelotRequestId(message), args);
|
var requestId = GetOcelotRequestId();
|
||||||
|
var previousRequestId = GetOcelotPreviousRequestId();
|
||||||
|
_logger.LogDebug("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message},", requestId, previousRequestId, message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void LogInformation(string message, params object[] args)
|
||||||
|
{
|
||||||
|
var requestId = GetOcelotRequestId();
|
||||||
|
var previousRequestId = GetOcelotPreviousRequestId();
|
||||||
|
_logger.LogInformation("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message},", requestId, previousRequestId, message, args);
|
||||||
|
}
|
||||||
|
|
||||||
public void LogError(string message, Exception exception)
|
public void LogError(string message, Exception exception)
|
||||||
{
|
{
|
||||||
_logger.LogError(GetMessageWithOcelotRequestId(message), exception);
|
var requestId = GetOcelotRequestId();
|
||||||
|
var previousRequestId = GetOcelotPreviousRequestId();
|
||||||
|
_logger.LogError("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}, exception: {exception}", requestId, previousRequestId, message, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LogError(string message, params object[] args)
|
public void LogError(string message, params object[] args)
|
||||||
{
|
{
|
||||||
_logger.LogError(GetMessageWithOcelotRequestId(message), args);
|
var requestId = GetOcelotRequestId();
|
||||||
|
var previousRequestId = GetOcelotPreviousRequestId();
|
||||||
|
_logger.LogError("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}", requestId, previousRequestId, message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetMessageWithOcelotRequestId(string message)
|
public void LogCritical(string message, Exception exception)
|
||||||
|
{
|
||||||
|
var requestId = GetOcelotRequestId();
|
||||||
|
var previousRequestId = GetOcelotPreviousRequestId();
|
||||||
|
_logger.LogError("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}", requestId, previousRequestId, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetOcelotRequestId()
|
||||||
{
|
{
|
||||||
var requestId = _scopedDataRepository.Get<string>("RequestId");
|
var requestId = _scopedDataRepository.Get<string>("RequestId");
|
||||||
|
|
||||||
if (requestId == null || requestId.IsError)
|
if (requestId == null || requestId.IsError)
|
||||||
{
|
{
|
||||||
return $"{message} : OcelotRequestId - not set";
|
return $"no request id";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{message} : OcelotRequestId - {requestId.Data}";
|
return requestId.Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetOcelotPreviousRequestId()
|
||||||
|
{
|
||||||
|
var requestId = _scopedDataRepository.Get<string>("PreviousRequestId");
|
||||||
|
|
||||||
|
if (requestId == null || requestId.IsError)
|
||||||
|
{
|
||||||
|
return $"no previous request id";
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestId.Data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,8 +13,10 @@ namespace Ocelot.Logging
|
|||||||
{
|
{
|
||||||
void LogTrace(string message, params object[] args);
|
void LogTrace(string message, params object[] args);
|
||||||
void LogDebug(string message, params object[] args);
|
void LogDebug(string message, params object[] args);
|
||||||
|
void LogInformation(string message, params object[] args);
|
||||||
void LogError(string message, Exception exception);
|
void LogError(string message, Exception exception);
|
||||||
void LogError(string message, params object[] args);
|
void LogError(string message, params object[] args);
|
||||||
|
void LogCritical(string message, Exception exception);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of the type the logger has been built for.
|
/// The name of the type the logger has been built for.
|
||||||
|
@ -7,7 +7,6 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
using Ocelot.Authentication.Middleware;
|
using Ocelot.Authentication.Middleware;
|
||||||
using Ocelot.Cache.Middleware;
|
using Ocelot.Cache.Middleware;
|
||||||
using Ocelot.Claims.Middleware;
|
using Ocelot.Claims.Middleware;
|
||||||
using Ocelot.Controllers;
|
|
||||||
using Ocelot.DownstreamRouteFinder.Middleware;
|
using Ocelot.DownstreamRouteFinder.Middleware;
|
||||||
using Ocelot.DownstreamUrlCreator.Middleware;
|
using Ocelot.DownstreamUrlCreator.Middleware;
|
||||||
using Ocelot.Errors.Middleware;
|
using Ocelot.Errors.Middleware;
|
||||||
@ -23,12 +22,15 @@ using Ocelot.RateLimit.Middleware;
|
|||||||
namespace Ocelot.Middleware
|
namespace Ocelot.Middleware
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Authorisation.Middleware;
|
using Authorisation.Middleware;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Configuration.Creator;
|
using Ocelot.Configuration.Creator;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
@ -36,7 +38,10 @@ namespace Ocelot.Middleware
|
|||||||
using Ocelot.Configuration.Repository;
|
using Ocelot.Configuration.Repository;
|
||||||
using Ocelot.Configuration.Setter;
|
using Ocelot.Configuration.Setter;
|
||||||
using Ocelot.LoadBalancer.Middleware;
|
using Ocelot.LoadBalancer.Middleware;
|
||||||
|
using Ocelot.Raft;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
using Rafty.Concensus;
|
||||||
|
using Rafty.Infrastructure;
|
||||||
|
|
||||||
public static class OcelotMiddlewareExtensions
|
public static class OcelotMiddlewareExtensions
|
||||||
{
|
{
|
||||||
@ -64,9 +69,15 @@ namespace Ocelot.Middleware
|
|||||||
|
|
||||||
await CreateAdministrationArea(builder, configuration);
|
await CreateAdministrationArea(builder, configuration);
|
||||||
|
|
||||||
|
if(UsingRafty(builder))
|
||||||
|
{
|
||||||
|
SetUpRafty(builder);
|
||||||
|
}
|
||||||
|
|
||||||
ConfigureDiagnosticListener(builder);
|
ConfigureDiagnosticListener(builder);
|
||||||
|
|
||||||
// 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
|
||||||
builder.UseExceptionHandlerMiddleware();
|
builder.UseExceptionHandlerMiddleware();
|
||||||
|
|
||||||
// Allow the user to respond with absolutely anything they want.
|
// Allow the user to respond with absolutely anything they want.
|
||||||
@ -84,7 +95,9 @@ namespace Ocelot.Middleware
|
|||||||
// We check whether the request is ratelimit, and if there is no continue processing
|
// We check whether the request is ratelimit, and if there is no continue processing
|
||||||
builder.UseRateLimiting();
|
builder.UseRateLimiting();
|
||||||
|
|
||||||
// Now we can look for the requestId
|
// This adds or updates the request id (initally we try and set this based on global config in the error handling middleware)
|
||||||
|
// If anything was set at global level and we have a different setting at re route level the global stuff will be overwritten
|
||||||
|
// This means you can get a scenario where you have a different request id from the first piece of middleware to the request id middleware.
|
||||||
builder.UseRequestIdMiddleware();
|
builder.UseRequestIdMiddleware();
|
||||||
|
|
||||||
// Allow pre authentication logic. The idea being people might want to run something custom before what is built in.
|
// Allow pre authentication logic. The idea being people might want to run something custom before what is built in.
|
||||||
@ -149,6 +162,26 @@ namespace Ocelot.Middleware
|
|||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool UsingRafty(IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode;
|
||||||
|
if(possible != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetUpRafty(IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
var applicationLifetime = (IApplicationLifetime)builder.ApplicationServices.GetService(typeof(IApplicationLifetime));
|
||||||
|
applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder));
|
||||||
|
var node = (INode)builder.ApplicationServices.GetService(typeof(INode));
|
||||||
|
var nodeId = (NodeId)builder.ApplicationServices.GetService(typeof(NodeId));
|
||||||
|
node.Start(nodeId.Id);
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task<IOcelotConfiguration> CreateConfiguration(IApplicationBuilder builder)
|
private static async Task<IOcelotConfiguration> CreateConfiguration(IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
var deps = GetDependencies(builder);
|
var deps = GetDependencies(builder);
|
||||||
@ -183,7 +216,7 @@ namespace Ocelot.Middleware
|
|||||||
return response == null || response.IsError;
|
return response == null || response.IsError;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ConfigurationNotSetUp(Response<IOcelotConfiguration> ocelotConfiguration)
|
private static bool ConfigurationNotSetUp(Ocelot.Responses.Response<IOcelotConfiguration> ocelotConfiguration)
|
||||||
{
|
{
|
||||||
return ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError;
|
return ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError;
|
||||||
}
|
}
|
||||||
@ -247,6 +280,7 @@ namespace Ocelot.Middleware
|
|||||||
return new ErrorResponse(ocelotConfig.Errors);
|
return new ErrorResponse(ocelotConfig.Errors);
|
||||||
}
|
}
|
||||||
config = await ocelotConfigurationRepository.AddOrReplace(ocelotConfig.Data);
|
config = await ocelotConfigurationRepository.AddOrReplace(ocelotConfig.Data);
|
||||||
|
//todo - this starts the poller if it has been registered...please this is so bad.
|
||||||
var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller));
|
var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,15 +316,16 @@ namespace Ocelot.Middleware
|
|||||||
/// <param name="builder"></param>
|
/// <param name="builder"></param>
|
||||||
private static void ConfigureDiagnosticListener(IApplicationBuilder builder)
|
private static void ConfigureDiagnosticListener(IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment));
|
var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment));
|
||||||
|
var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener));
|
||||||
//https://github.com/TomPallister/Ocelot/pull/87 not sure why only for dev envs and marc disapeered so just merging and maybe change one day?
|
var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener));
|
||||||
if (!env.IsProduction())
|
diagnosticListener.SubscribeWithAdapter(listener);
|
||||||
{
|
|
||||||
var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener));
|
|
||||||
var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener));
|
|
||||||
diagnosticListener.SubscribeWithAdapter(listener);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void OnShutdown(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
var node = (INode)app.ApplicationServices.GetService(typeof(INode));
|
||||||
|
node.Stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
@ -11,38 +10,37 @@
|
|||||||
<PackageId>Ocelot</PackageId>
|
<PackageId>Ocelot</PackageId>
|
||||||
<PackageTags>API Gateway;.NET core</PackageTags>
|
<PackageTags>API Gateway;.NET core</PackageTags>
|
||||||
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
|
||||||
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/TomPallister/Ocelot</PackageProjectUrl>
|
||||||
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
|
||||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||||
<Authors>Tom Pallister</Authors>
|
<Authors>Tom Pallister</Authors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<DebugType>full</DebugType>
|
<DebugType>full</DebugType>
|
||||||
<DebugSymbols>True</DebugSymbols>
|
<DebugSymbols>True</DebugSymbols>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.1.0" />
|
<PackageReference Include="FluentValidation" Version="7.2.1"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
|
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.1.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0"/>
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.0" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0"/>
|
||||||
<PackageReference Include="CacheManager.Core" Version="1.1.1" />
|
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.0"/>
|
||||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.1" />
|
<PackageReference Include="CacheManager.Core" Version="1.1.1"/>
|
||||||
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.1" />
|
<PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.1"/>
|
||||||
<PackageReference Include="Consul" Version="0.7.2.3" />
|
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.1"/>
|
||||||
<PackageReference Include="Polly" Version="5.3.1" />
|
<PackageReference Include="Consul" Version="0.7.2.3"/>
|
||||||
<PackageReference Include="IdentityServer4" Version="2.0.2" />
|
<PackageReference Include="Polly" Version="5.3.1"/>
|
||||||
|
<PackageReference Include="IdentityServer4" Version="2.0.2"/>
|
||||||
|
<PackageReference Include="Rafty" Version="0.4.2"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
7
src/Ocelot/Raft/ExcludeFromCoverage.cs
Normal file
7
src/Ocelot/Raft/ExcludeFromCoverage.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ocelot.Raft
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method|AttributeTargets.Property)]
|
||||||
|
public class ExcludeFromCoverageAttribute : Attribute{}
|
||||||
|
}
|
15
src/Ocelot/Raft/FakeCommand.cs
Normal file
15
src/Ocelot/Raft/FakeCommand.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Rafty.FiniteStateMachine;
|
||||||
|
|
||||||
|
namespace Ocelot.Raft
|
||||||
|
{
|
||||||
|
[ExcludeFromCoverage]
|
||||||
|
public class FakeCommand : ICommand
|
||||||
|
{
|
||||||
|
public FakeCommand(string value)
|
||||||
|
{
|
||||||
|
this.Value = value;
|
||||||
|
|
||||||
|
}
|
||||||
|
public string Value { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
33
src/Ocelot/Raft/FileFsm.cs
Normal file
33
src/Ocelot/Raft/FileFsm.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Rafty.FiniteStateMachine;
|
||||||
|
using Rafty.Infrastructure;
|
||||||
|
using Rafty.Log;
|
||||||
|
|
||||||
|
namespace Ocelot.Raft
|
||||||
|
{
|
||||||
|
[ExcludeFromCoverage]
|
||||||
|
public class FileFsm : IFiniteStateMachine
|
||||||
|
{
|
||||||
|
private string _id;
|
||||||
|
|
||||||
|
public FileFsm(NodeId nodeId)
|
||||||
|
{
|
||||||
|
_id = nodeId.Id.Replace("/","").Replace(":","");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(LogEntry log)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = JsonConvert.SerializeObject(log.CommandData);
|
||||||
|
File.AppendAllText(_id, json);
|
||||||
|
}
|
||||||
|
catch(Exception exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/Ocelot/Raft/FilePeer.cs
Normal file
8
src/Ocelot/Raft/FilePeer.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace Ocelot.Raft
|
||||||
|
{
|
||||||
|
[ExcludeFromCoverage]
|
||||||
|
public class FilePeer
|
||||||
|
{
|
||||||
|
public string HostAndPort { get; set; }
|
||||||
|
}
|
||||||
|
}
|
15
src/Ocelot/Raft/FilePeers.cs
Normal file
15
src/Ocelot/Raft/FilePeers.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocelot.Raft
|
||||||
|
{
|
||||||
|
[ExcludeFromCoverage]
|
||||||
|
public class FilePeers
|
||||||
|
{
|
||||||
|
public FilePeers()
|
||||||
|
{
|
||||||
|
Peers = new List<FilePeer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FilePeer> Peers {get; set;}
|
||||||
|
}
|
||||||
|
}
|
44
src/Ocelot/Raft/FilePeersProvider.cs
Normal file
44
src/Ocelot/Raft/FilePeersProvider.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Configuration.Provider;
|
||||||
|
using Rafty.Concensus;
|
||||||
|
using Rafty.Infrastructure;
|
||||||
|
|
||||||
|
namespace Ocelot.Raft
|
||||||
|
{
|
||||||
|
[ExcludeFromCoverage]
|
||||||
|
public class FilePeersProvider : IPeersProvider
|
||||||
|
{
|
||||||
|
private readonly IOptions<FilePeers> _options;
|
||||||
|
private List<IPeer> _peers;
|
||||||
|
private IWebHostBuilder _builder;
|
||||||
|
private IOcelotConfigurationProvider _provider;
|
||||||
|
private IIdentityServerConfiguration _identityServerConfig;
|
||||||
|
|
||||||
|
public FilePeersProvider(IOptions<FilePeers> options, IWebHostBuilder builder, IOcelotConfigurationProvider provider, IIdentityServerConfiguration identityServerConfig)
|
||||||
|
{
|
||||||
|
_identityServerConfig = identityServerConfig;
|
||||||
|
_provider = provider;
|
||||||
|
_builder = builder;
|
||||||
|
_options = options;
|
||||||
|
_peers = new List<IPeer>();
|
||||||
|
//todo - sort out async nonsense..
|
||||||
|
var config = _provider.Get().GetAwaiter().GetResult();
|
||||||
|
foreach (var item in _options.Value.Peers)
|
||||||
|
{
|
||||||
|
var httpClient = new HttpClient();
|
||||||
|
//todo what if this errors?
|
||||||
|
var httpPeer = new HttpPeer(item.HostAndPort, httpClient, _builder, config.Data, _identityServerConfig);
|
||||||
|
_peers.Add(httpPeer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public List<IPeer> Get()
|
||||||
|
{
|
||||||
|
return _peers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
128
src/Ocelot/Raft/HttpPeer.cs
Normal file
128
src/Ocelot/Raft/HttpPeer.cs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Ocelot.Authentication;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Configuration.Provider;
|
||||||
|
using Rafty.Concensus;
|
||||||
|
using Rafty.FiniteStateMachine;
|
||||||
|
|
||||||
|
namespace Ocelot.Raft
|
||||||
|
{
|
||||||
|
[ExcludeFromCoverage]
|
||||||
|
public class HttpPeer : IPeer
|
||||||
|
{
|
||||||
|
private string _hostAndPort;
|
||||||
|
private HttpClient _httpClient;
|
||||||
|
private JsonSerializerSettings _jsonSerializerSettings;
|
||||||
|
private string _baseSchemeUrlAndPort;
|
||||||
|
private BearerToken _token;
|
||||||
|
private IOcelotConfiguration _config;
|
||||||
|
private IIdentityServerConfiguration _identityServerConfiguration;
|
||||||
|
|
||||||
|
public HttpPeer(string hostAndPort, HttpClient httpClient, IWebHostBuilder builder, IOcelotConfiguration config, IIdentityServerConfiguration identityServerConfiguration)
|
||||||
|
{
|
||||||
|
_identityServerConfiguration = identityServerConfiguration;
|
||||||
|
_config = config;
|
||||||
|
Id = hostAndPort;
|
||||||
|
_hostAndPort = hostAndPort;
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_jsonSerializerSettings = new JsonSerializerSettings() {
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
_baseSchemeUrlAndPort = builder.GetSetting(WebHostDefaults.ServerUrlsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Id {get; private set;}
|
||||||
|
|
||||||
|
public RequestVoteResponse Request(RequestVote requestVote)
|
||||||
|
{
|
||||||
|
if(_token == null)
|
||||||
|
{
|
||||||
|
SetToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = JsonConvert.SerializeObject(requestVote, _jsonSerializerSettings);
|
||||||
|
var content = new StringContent(json);
|
||||||
|
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||||
|
var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content).GetAwaiter().GetResult();
|
||||||
|
if(response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return JsonConvert.DeserializeObject<RequestVoteResponse>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new RequestVoteResponse(false, requestVote.Term);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppendEntriesResponse Request(AppendEntries appendEntries)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(_token == null)
|
||||||
|
{
|
||||||
|
SetToken();
|
||||||
|
}
|
||||||
|
var json = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings);
|
||||||
|
var content = new StringContent(json);
|
||||||
|
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||||
|
var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content).GetAwaiter().GetResult();
|
||||||
|
if(response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return JsonConvert.DeserializeObject<AppendEntriesResponse>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(),_jsonSerializerSettings);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new AppendEntriesResponse(appendEntries.Term, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(ex);
|
||||||
|
return new AppendEntriesResponse(appendEntries.Term, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response<T> Request<T>(T command) where T : ICommand
|
||||||
|
{
|
||||||
|
if(_token == null)
|
||||||
|
{
|
||||||
|
SetToken();
|
||||||
|
}
|
||||||
|
var json = JsonConvert.SerializeObject(command, _jsonSerializerSettings);
|
||||||
|
var content = new StringContent(json);
|
||||||
|
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||||
|
var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content).GetAwaiter().GetResult();
|
||||||
|
if(response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return JsonConvert.DeserializeObject<OkResponse<T>>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new ErrorResponse<T>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetToken()
|
||||||
|
{
|
||||||
|
var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token";
|
||||||
|
var formData = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("client_id", _identityServerConfiguration.ApiName),
|
||||||
|
new KeyValuePair<string, string>("client_secret", _identityServerConfiguration.ApiSecret),
|
||||||
|
new KeyValuePair<string, string>("scope", _identityServerConfiguration.ApiName),
|
||||||
|
new KeyValuePair<string, string>("grant_type", "client_credentials")
|
||||||
|
};
|
||||||
|
var content = new FormUrlEncodedContent(formData);
|
||||||
|
var response = _httpClient.PostAsync(tokenUrl, content).GetAwaiter().GetResult();
|
||||||
|
var responseContent = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
|
||||||
|
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
src/Ocelot/Raft/OcelotFiniteStateMachine.cs
Normal file
25
src/Ocelot/Raft/OcelotFiniteStateMachine.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using Ocelot.Configuration.Setter;
|
||||||
|
using Rafty.FiniteStateMachine;
|
||||||
|
using Rafty.Log;
|
||||||
|
|
||||||
|
namespace Ocelot.Raft
|
||||||
|
{
|
||||||
|
[ExcludeFromCoverage]
|
||||||
|
public class OcelotFiniteStateMachine : IFiniteStateMachine
|
||||||
|
{
|
||||||
|
private IFileConfigurationSetter _setter;
|
||||||
|
|
||||||
|
public OcelotFiniteStateMachine(IFileConfigurationSetter setter)
|
||||||
|
{
|
||||||
|
_setter = setter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(LogEntry log)
|
||||||
|
{
|
||||||
|
//todo - handle an error
|
||||||
|
//hack it to just cast as at the moment we know this is the only command :P
|
||||||
|
var hack = (UpdateFileConfiguration)log.CommandData;
|
||||||
|
_setter.Set(hack.Configuration).GetAwaiter().GetResult();;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
src/Ocelot/Raft/RaftController.cs
Normal file
84
src/Ocelot/Raft/RaftController.cs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Raft;
|
||||||
|
using Rafty.Concensus;
|
||||||
|
using Rafty.FiniteStateMachine;
|
||||||
|
|
||||||
|
namespace Ocelot.Raft
|
||||||
|
{
|
||||||
|
[ExcludeFromCoverage]
|
||||||
|
[Authorize]
|
||||||
|
[Route("raft")]
|
||||||
|
public class RaftController : Controller
|
||||||
|
{
|
||||||
|
private readonly INode _node;
|
||||||
|
private IOcelotLogger _logger;
|
||||||
|
private string _baseSchemeUrlAndPort;
|
||||||
|
private JsonSerializerSettings _jsonSerialiserSettings;
|
||||||
|
|
||||||
|
public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IWebHostBuilder builder)
|
||||||
|
{
|
||||||
|
_jsonSerialiserSettings = new JsonSerializerSettings {
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
_baseSchemeUrlAndPort = builder.GetSetting(WebHostDefaults.ServerUrlsKey);
|
||||||
|
_logger = loggerFactory.CreateLogger<RaftController>();
|
||||||
|
_node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("appendentries")]
|
||||||
|
public async Task<IActionResult> AppendEntries()
|
||||||
|
{
|
||||||
|
using(var reader = new StreamReader(HttpContext.Request.Body))
|
||||||
|
{
|
||||||
|
var json = await reader.ReadToEndAsync();
|
||||||
|
var appendEntries = JsonConvert.DeserializeObject<AppendEntries>(json, _jsonSerialiserSettings);
|
||||||
|
_logger.LogDebug($"{_baseSchemeUrlAndPort}/appendentries called, my state is {_node.State.GetType().FullName}");
|
||||||
|
var appendEntriesResponse = _node.Handle(appendEntries);
|
||||||
|
return new OkObjectResult(appendEntriesResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("requestvote")]
|
||||||
|
public async Task<IActionResult> RequestVote()
|
||||||
|
{
|
||||||
|
using(var reader = new StreamReader(HttpContext.Request.Body))
|
||||||
|
{
|
||||||
|
var json = await reader.ReadToEndAsync();
|
||||||
|
var requestVote = JsonConvert.DeserializeObject<RequestVote>(json, _jsonSerialiserSettings);
|
||||||
|
_logger.LogDebug($"{_baseSchemeUrlAndPort}/requestvote called, my state is {_node.State.GetType().FullName}");
|
||||||
|
var requestVoteResponse = _node.Handle(requestVote);
|
||||||
|
return new OkObjectResult(requestVoteResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("command")]
|
||||||
|
public async Task<IActionResult> Command()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using(var reader = new StreamReader(HttpContext.Request.Body))
|
||||||
|
{
|
||||||
|
var json = await reader.ReadToEndAsync();
|
||||||
|
var command = JsonConvert.DeserializeObject<ICommand>(json, _jsonSerialiserSettings);
|
||||||
|
_logger.LogDebug($"{_baseSchemeUrlAndPort}/command called, my state is {_node.State.GetType().FullName}");
|
||||||
|
var commandResponse = _node.Accept(command);
|
||||||
|
json = JsonConvert.SerializeObject(commandResponse, _jsonSerialiserSettings);
|
||||||
|
return StatusCode(200, json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError($"THERE WAS A PROBLEM ON NODE {_node.State.CurrentState.Id}", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
279
src/Ocelot/Raft/SqlLiteLog.cs
Normal file
279
src/Ocelot/Raft/SqlLiteLog.cs
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
using System.IO;
|
||||||
|
using Rafty.Log;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using Rafty.Infrastructure;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocelot.Raft
|
||||||
|
{
|
||||||
|
[ExcludeFromCoverage]
|
||||||
|
public class SqlLiteLog : ILog
|
||||||
|
{
|
||||||
|
private string _path;
|
||||||
|
private readonly object _lock = new object();
|
||||||
|
|
||||||
|
public SqlLiteLog(NodeId nodeId)
|
||||||
|
{
|
||||||
|
_path = $"{nodeId.Id.Replace("/","").Replace(":","")}.db";
|
||||||
|
if(!File.Exists(_path))
|
||||||
|
{
|
||||||
|
lock(_lock)
|
||||||
|
{
|
||||||
|
FileStream fs = File.Create(_path);
|
||||||
|
fs.Dispose();
|
||||||
|
}
|
||||||
|
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
var sql = @"create table logs (
|
||||||
|
id integer primary key,
|
||||||
|
data text not null
|
||||||
|
)";
|
||||||
|
using(var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var result = command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int LastLogIndex
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock(_lock)
|
||||||
|
{
|
||||||
|
var result = 1;
|
||||||
|
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
var sql = @"select id from logs order by id desc limit 1";
|
||||||
|
using(var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var index = Convert.ToInt32(command.ExecuteScalar());
|
||||||
|
if(index > result)
|
||||||
|
{
|
||||||
|
result = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long LastLogTerm
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock(_lock)
|
||||||
|
{
|
||||||
|
long result = 0;
|
||||||
|
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
var sql = @"select data from logs order by id desc limit 1";
|
||||||
|
using(var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var data = Convert.ToString(command.ExecuteScalar());
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings() {
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
|
if(log != null && log.Term > result)
|
||||||
|
{
|
||||||
|
result = log.Term;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock(_lock)
|
||||||
|
{
|
||||||
|
var result = 0;
|
||||||
|
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
var sql = @"select count(id) from logs";
|
||||||
|
using(var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var index = Convert.ToInt32(command.ExecuteScalar());
|
||||||
|
if(index > result)
|
||||||
|
{
|
||||||
|
result = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Apply(LogEntry log)
|
||||||
|
{
|
||||||
|
lock(_lock)
|
||||||
|
{
|
||||||
|
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings() {
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
var data = JsonConvert.SerializeObject(log, jsonSerializerSettings);
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var sql = $"insert into logs (data) values ('{data}')";
|
||||||
|
using(var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var result = command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = "select last_insert_rowid()";
|
||||||
|
using(var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var result = command.ExecuteScalar();
|
||||||
|
return Convert.ToInt32(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteConflictsFromThisLog(int index, LogEntry logEntry)
|
||||||
|
{
|
||||||
|
lock(_lock)
|
||||||
|
{
|
||||||
|
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var sql = $"select data from logs where id = {index};";
|
||||||
|
using(var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var data = Convert.ToString(command.ExecuteScalar());
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings() {
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
|
if(logEntry != null && log != null && logEntry.Term != log.Term)
|
||||||
|
{
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var deleteSql = $"delete from logs where id >= {index};";
|
||||||
|
using(var deleteCommand = new SqliteCommand(deleteSql, connection))
|
||||||
|
{
|
||||||
|
var result = deleteCommand.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogEntry Get(int index)
|
||||||
|
{
|
||||||
|
lock(_lock)
|
||||||
|
{
|
||||||
|
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var sql = $"select data from logs where id = {index}";
|
||||||
|
using(var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var data = Convert.ToString(command.ExecuteScalar());
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings() {
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public System.Collections.Generic.List<(int index, LogEntry logEntry)> GetFrom(int index)
|
||||||
|
{
|
||||||
|
lock(_lock)
|
||||||
|
{
|
||||||
|
var logsToReturn = new List<(int, LogEntry)>();
|
||||||
|
|
||||||
|
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var sql = $"select id, data from logs where id >= {index}";
|
||||||
|
using(var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
using(var reader = command.ExecuteReader())
|
||||||
|
{
|
||||||
|
while(reader.Read())
|
||||||
|
{
|
||||||
|
var id = Convert.ToInt32(reader[0]);
|
||||||
|
var data = (string)reader[1];
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings() {
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
|
logsToReturn.Add((id, log));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return logsToReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public long GetTermAtIndex(int index)
|
||||||
|
{
|
||||||
|
lock(_lock)
|
||||||
|
{
|
||||||
|
long result = 0;
|
||||||
|
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var sql = $"select data from logs where id = {index}";
|
||||||
|
using(var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var data = Convert.ToString(command.ExecuteScalar());
|
||||||
|
var jsonSerializerSettings = new JsonSerializerSettings() {
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
};
|
||||||
|
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
|
||||||
|
if(log != null && log.Term > result)
|
||||||
|
{
|
||||||
|
result = log.Term;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void Remove(int indexOfCommand)
|
||||||
|
{
|
||||||
|
lock(_lock)
|
||||||
|
{
|
||||||
|
using(var connection = new SqliteConnection($"Data Source={_path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
//todo - sql injection dont copy this..
|
||||||
|
var deleteSql = $"delete from logs where id >= {indexOfCommand};";
|
||||||
|
using(var deleteCommand = new SqliteCommand(deleteSql, connection))
|
||||||
|
{
|
||||||
|
var result = deleteCommand.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/Ocelot/Raft/UpdateFileConfiguration.cs
Normal file
15
src/Ocelot/Raft/UpdateFileConfiguration.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Rafty.FiniteStateMachine;
|
||||||
|
|
||||||
|
namespace Ocelot.Raft
|
||||||
|
{
|
||||||
|
public class UpdateFileConfiguration : ICommand
|
||||||
|
{
|
||||||
|
public UpdateFileConfiguration(FileConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileConfiguration Configuration {get;private set;}
|
||||||
|
}
|
||||||
|
}
|
@ -26,8 +26,6 @@ namespace Ocelot.Request.Middleware
|
|||||||
|
|
||||||
public async Task Invoke(HttpContext context)
|
public async Task Invoke(HttpContext context)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("started calling request builder middleware");
|
|
||||||
|
|
||||||
var downstreamRequest = await _requestMapper.Map(context.Request);
|
var downstreamRequest = await _requestMapper.Map(context.Request);
|
||||||
if (downstreamRequest.IsError)
|
if (downstreamRequest.IsError)
|
||||||
{
|
{
|
||||||
@ -37,11 +35,7 @@ namespace Ocelot.Request.Middleware
|
|||||||
|
|
||||||
SetDownstreamRequest(downstreamRequest.Data);
|
SetDownstreamRequest(downstreamRequest.Data);
|
||||||
|
|
||||||
_logger.LogDebug("calling next middleware");
|
|
||||||
|
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(context);
|
||||||
|
|
||||||
_logger.LogDebug("succesfully called next middleware");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -30,8 +30,6 @@ namespace Ocelot.Request.Middleware
|
|||||||
|
|
||||||
public async Task Invoke(HttpContext context)
|
public async Task Invoke(HttpContext context)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("started calling request builder middleware");
|
|
||||||
|
|
||||||
var qosProvider = _qosProviderHouse.Get(DownstreamRoute.ReRoute);
|
var qosProvider = _qosProviderHouse.Get(DownstreamRoute.ReRoute);
|
||||||
|
|
||||||
if (qosProvider.IsError)
|
if (qosProvider.IsError)
|
||||||
@ -62,11 +60,7 @@ namespace Ocelot.Request.Middleware
|
|||||||
|
|
||||||
SetUpstreamRequestForThisRequest(buildResult.Data);
|
SetUpstreamRequestForThisRequest(buildResult.Data);
|
||||||
|
|
||||||
_logger.LogDebug("calling next middleware");
|
|
||||||
|
|
||||||
await _next.Invoke(context);
|
await _next.Invoke(context);
|
||||||
|
|
||||||
_logger.LogDebug("succesfully called next middleware");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,19 +11,19 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace Ocelot.RequestId.Middleware
|
namespace Ocelot.RequestId.Middleware
|
||||||
{
|
{
|
||||||
public class RequestIdMiddleware : OcelotMiddleware
|
public class ReRouteRequestIdMiddleware : OcelotMiddleware
|
||||||
{
|
{
|
||||||
private readonly RequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly IOcelotLogger _logger;
|
private readonly IOcelotLogger _logger;
|
||||||
private readonly IRequestScopedDataRepository _requestScopedDataRepository;
|
private readonly IRequestScopedDataRepository _requestScopedDataRepository;
|
||||||
|
|
||||||
public RequestIdMiddleware(RequestDelegate next,
|
public ReRouteRequestIdMiddleware(RequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
IRequestScopedDataRepository requestScopedDataRepository)
|
IRequestScopedDataRepository requestScopedDataRepository)
|
||||||
: base(requestScopedDataRepository)
|
: base(requestScopedDataRepository)
|
||||||
{
|
{
|
||||||
_next = next;
|
_next = next;
|
||||||
_logger = loggerFactory.CreateLogger<RequestIdMiddleware>();
|
_logger = loggerFactory.CreateLogger<ReRouteRequestIdMiddleware>();
|
||||||
_requestScopedDataRepository = requestScopedDataRepository;
|
_requestScopedDataRepository = requestScopedDataRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +41,22 @@ namespace Ocelot.RequestId.Middleware
|
|||||||
StringValues upstreamRequestIds;
|
StringValues upstreamRequestIds;
|
||||||
if (context.Request.Headers.TryGetValue(key, out upstreamRequestIds))
|
if (context.Request.Headers.TryGetValue(key, out upstreamRequestIds))
|
||||||
{
|
{
|
||||||
|
//set the traceidentifier
|
||||||
context.TraceIdentifier = upstreamRequestIds.First();
|
context.TraceIdentifier = upstreamRequestIds.First();
|
||||||
|
|
||||||
|
//check if we have previous id
|
||||||
|
var previousRequestId = _requestScopedDataRepository.Get<string>("RequestId");
|
||||||
|
if(!previousRequestId.IsError && !string.IsNullOrEmpty(previousRequestId.Data))
|
||||||
|
{
|
||||||
|
//we have a previous request id lets store it and update request id
|
||||||
|
_requestScopedDataRepository.Add<string>("PreviousRequestId", previousRequestId.Data);
|
||||||
|
_requestScopedDataRepository.Update<string>("RequestId", context.TraceIdentifier);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//else just add request id
|
||||||
|
_requestScopedDataRepository.Add<string>("RequestId", context.TraceIdentifier);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set request ID on downstream request, if required
|
// set request ID on downstream request, if required
|
@ -6,7 +6,7 @@ namespace Ocelot.RequestId.Middleware
|
|||||||
{
|
{
|
||||||
public static IApplicationBuilder UseRequestIdMiddleware(this IApplicationBuilder builder)
|
public static IApplicationBuilder UseRequestIdMiddleware(this IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
return builder.UseMiddleware<RequestIdMiddleware>();
|
return builder.UseMiddleware<ReRouteRequestIdMiddleware>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,8 +26,6 @@ namespace Ocelot.Requester.Middleware
|
|||||||
|
|
||||||
public async Task Invoke(HttpContext context)
|
public async Task Invoke(HttpContext context)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("started calling requester middleware");
|
|
||||||
|
|
||||||
var response = await _requester.GetResponse(Request);
|
var response = await _requester.GetResponse(Request);
|
||||||
|
|
||||||
if (response.IsError)
|
if (response.IsError)
|
||||||
@ -41,8 +39,6 @@ namespace Ocelot.Requester.Middleware
|
|||||||
_logger.LogDebug("setting http response message");
|
_logger.LogDebug("setting http response message");
|
||||||
|
|
||||||
SetHttpResponseMessageThisRequest(response.Data);
|
SetHttpResponseMessageThisRequest(response.Data);
|
||||||
|
|
||||||
_logger.LogDebug("returning to calling middleware");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
14
src/Ocelot/Values/UpstreamPathTemplate.cs
Normal file
14
src/Ocelot/Values/UpstreamPathTemplate.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
namespace Ocelot.Values
|
||||||
|
{
|
||||||
|
public class UpstreamPathTemplate
|
||||||
|
{
|
||||||
|
public UpstreamPathTemplate(string template, int priority)
|
||||||
|
{
|
||||||
|
Template = template;
|
||||||
|
Priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Template {get;}
|
||||||
|
public int Priority {get;}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using CacheManager.Core;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
@ -9,11 +8,11 @@ using Ocelot.DependencyInjection;
|
|||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
|
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
|
||||||
|
|
||||||
namespace Ocelot.ManualTest
|
namespace Ocelot.AcceptanceTests
|
||||||
{
|
{
|
||||||
public class Startup
|
public class AcceptanceTestsStartup
|
||||||
{
|
{
|
||||||
public Startup(IHostingEnvironment env)
|
public AcceptanceTestsStartup(IHostingEnvironment env)
|
||||||
{
|
{
|
||||||
var builder = new ConfigurationBuilder()
|
var builder = new ConfigurationBuilder()
|
||||||
.SetBasePath(env.ContentRootPath)
|
.SetBasePath(env.ContentRootPath)
|
||||||
@ -25,33 +24,15 @@ namespace Ocelot.ManualTest
|
|||||||
Configuration = builder.Build();
|
Configuration = builder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IConfigurationRoot Configuration { get; }
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public virtual void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
Action<ConfigurationBuilderCachePart> settings = (x) =>
|
|
||||||
{
|
|
||||||
x.WithMicrosoftLogging(log =>
|
|
||||||
{
|
|
||||||
log.AddConsole(LogLevel.Debug);
|
|
||||||
})
|
|
||||||
.WithDictionaryHandle();
|
|
||||||
};
|
|
||||||
|
|
||||||
services.AddAuthentication()
|
|
||||||
.AddJwtBearer("TestKey", x =>
|
|
||||||
{
|
|
||||||
x.Authority = "test";
|
|
||||||
x.Audience = "test";
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddOcelot(Configuration);
|
services.AddOcelot(Configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
|
||||||
|
|
||||||
app.UseOcelot().Wait();
|
app.UseOcelot().Wait();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -22,25 +22,18 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.AddJsonFile("configuration.json")
|
.AddJsonFile("configuration.json")
|
||||||
.AddEnvironmentVariables();
|
.AddEnvironmentVariables();
|
||||||
|
|
||||||
Configuration = builder.Build();
|
Config = builder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IConfigurationRoot Configuration { get; }
|
public static IConfiguration Config { get; private set; }
|
||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
Action<ConfigurationBuilderCachePart> settings = (x) =>
|
services.AddOcelot(Config).AddStoreOcelotConfigurationInConsul();
|
||||||
{
|
|
||||||
x.WithDictionaryHandle();
|
|
||||||
};
|
|
||||||
|
|
||||||
services.AddOcelot(Configuration).AddStoreOcelotConfigurationInConsul();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
public void Configure(IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
|
||||||
|
|
||||||
app.UseOcelot().Wait();
|
app.UseOcelot().Wait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,173 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_response_200_with_forward_slash_and_placeholder_only()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/{url}",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHost = "localhost",
|
||||||
|
DownstreamPort = 51879,
|
||||||
|
UpstreamPathTemplate = "/{url}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/", "/", 200, "Hello from Laura"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_response_200_favouring_forward_slash_with_path_route()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/{url}",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHost = "localhost",
|
||||||
|
DownstreamPort = 51880,
|
||||||
|
UpstreamPathTemplate = "/{url}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
},
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHost = "localhost",
|
||||||
|
DownstreamPort = 51879,
|
||||||
|
UpstreamPathTemplate = "/",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", "/test", 200, "Hello from Laura"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/test"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_response_200_favouring_forward_slash()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/{url}",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHost = "localhost",
|
||||||
|
DownstreamPort = 51880,
|
||||||
|
UpstreamPathTemplate = "/{url}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
},
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHost = "localhost",
|
||||||
|
DownstreamPort = 51879,
|
||||||
|
UpstreamPathTemplate = "/",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/", "/", 200, "Hello from Laura"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_response_200_favouring_forward_slash_route_because_it_is_first()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHost = "localhost",
|
||||||
|
DownstreamPort = 51880,
|
||||||
|
UpstreamPathTemplate = "/",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
},
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/{url}",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHost = "localhost",
|
||||||
|
DownstreamPort = 51879,
|
||||||
|
UpstreamPathTemplate = "/{url}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", "/", 200, "Hello from Laura"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_response_200_with_nothing_and_placeholder_only()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/{url}",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHost = "localhost",
|
||||||
|
DownstreamPort = 51879,
|
||||||
|
UpstreamPathTemplate = "/{url}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway(""))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_response_200_with_simple_url()
|
public void should_return_response_200_with_simple_url()
|
||||||
{
|
{
|
||||||
@ -52,7 +219,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 200, "Hello from Laura"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
@ -275,6 +442,35 @@ namespace Ocelot.AcceptanceTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_response_200_with_complex_url_that_starts_with_placeholder()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/api/{variantId}/products/{productId}",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHost = "localhost",
|
||||||
|
DownstreamPort = 51879,
|
||||||
|
UpstreamPathTemplate = "/{variantId}/products/{productId}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/23/products/1", 200, "Some Product"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("23/products/1"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Some Product"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_not_add_trailing_slash_to_downstream_url()
|
public void should_not_add_trailing_slash_to_downstream_url()
|
||||||
{
|
{
|
||||||
@ -321,7 +517,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 201, string.Empty))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 201, string.Empty))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.And(x => _steps.GivenThePostHasContent("postContent"))
|
.And(x => _steps.GivenThePostHasContent("postContent"))
|
||||||
@ -349,7 +545,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 200, "Hello from Laura"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/newThing", 200, "Hello from Laura"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/newThing?DeviceType=IphoneApp&Browser=moonpigIphone&BrowserString=-&CountryCode=123&DeviceName=iPhone 5 (GSM+CDMA)&OperatingSystem=iPhone OS 7.1.2&BrowserVersion=3708AdHoc&ipAddress=-"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/newThing?DeviceType=IphoneApp&Browser=moonpigIphone&BrowserString=-&CountryCode=123&DeviceName=iPhone 5 (GSM+CDMA)&OperatingSystem=iPhone OS 7.1.2&BrowserVersion=3708AdHoc&ipAddress=-"))
|
||||||
@ -377,7 +573,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/myApp1Name/api/products/1", 200, "Some Product"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/myApp1Name/api/products/1"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/myApp1Name/api/products/1"))
|
||||||
@ -433,7 +629,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 200, "Hello from Laura"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
@ -507,7 +703,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", "", 200, "Hello from Laura"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", "/api/swagger/lib/backbone-min.js", 200, "Hello from Laura"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/platform/swagger/lib/backbone-min.js"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/platform/swagger/lib/backbone-min.js"))
|
||||||
@ -530,8 +726,17 @@ namespace Ocelot.AcceptanceTests
|
|||||||
app.Run(async context =>
|
app.Run(async context =>
|
||||||
{
|
{
|
||||||
_downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
|
_downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
|
||||||
context.Response.StatusCode = statusCode;
|
|
||||||
await context.Response.WriteAsync(responseBody);
|
if(_downstreamPath != basePath)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync("downstream path didnt match base path");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync(responseBody);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
using System;
|
|
||||||
using CacheManager.Core;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Ocelot.DependencyInjection;
|
|
||||||
using Ocelot.Middleware;
|
|
||||||
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
|
|
||||||
using Ocelot.AcceptanceTests.Caching;
|
|
||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
|
||||||
{
|
|
||||||
public class Startup
|
|
||||||
{
|
|
||||||
public Startup(IHostingEnvironment env)
|
|
||||||
{
|
|
||||||
var builder = new ConfigurationBuilder()
|
|
||||||
.SetBasePath(env.ContentRootPath)
|
|
||||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
|
||||||
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
|
|
||||||
.AddJsonFile("configuration.json")
|
|
||||||
.AddEnvironmentVariables();
|
|
||||||
|
|
||||||
Configuration = builder.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IConfigurationRoot Configuration { get; }
|
|
||||||
|
|
||||||
public virtual void ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddOcelot(Configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
|
||||||
{
|
|
||||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
|
||||||
|
|
||||||
app.UseOcelot().Wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Startup_WithCustomCacheHandle : Startup
|
|
||||||
{
|
|
||||||
public Startup_WithCustomCacheHandle(IHostingEnvironment env) : base(env) { }
|
|
||||||
|
|
||||||
public override void ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddOcelot(Configuration)
|
|
||||||
.AddCacheManager((x) =>
|
|
||||||
{
|
|
||||||
x.WithMicrosoftLogging(log =>
|
|
||||||
{
|
|
||||||
log.AddConsole(LogLevel.Debug);
|
|
||||||
})
|
|
||||||
.WithJsonSerializer()
|
|
||||||
.WithHandle(typeof(InMemoryJsonHandle<>));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Startup_WithConsul_And_CustomCacheHandle : Startup
|
|
||||||
{
|
|
||||||
public Startup_WithConsul_And_CustomCacheHandle(IHostingEnvironment env) : base(env) { }
|
|
||||||
|
|
||||||
public override void ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddOcelot(Configuration)
|
|
||||||
.AddCacheManager((x) =>
|
|
||||||
{
|
|
||||||
x.WithMicrosoftLogging(log =>
|
|
||||||
{
|
|
||||||
log.AddConsole(LogLevel.Debug);
|
|
||||||
})
|
|
||||||
.WithJsonSerializer()
|
|
||||||
.WithHandle(typeof(InMemoryJsonHandle<>));
|
|
||||||
})
|
|
||||||
.AddStoreOcelotConfigurationInConsul();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,29 @@
|
|||||||
|
using CacheManager.Core;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ocelot.DependencyInjection;
|
||||||
|
using Ocelot.AcceptanceTests.Caching;
|
||||||
|
|
||||||
|
namespace Ocelot.AcceptanceTests
|
||||||
|
{
|
||||||
|
public class StartupWithConsulAndCustomCacheHandle : AcceptanceTestsStartup
|
||||||
|
{
|
||||||
|
public StartupWithConsulAndCustomCacheHandle(IHostingEnvironment env) : base(env) { }
|
||||||
|
|
||||||
|
public override void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddOcelot(Configuration)
|
||||||
|
.AddCacheManager((x) =>
|
||||||
|
{
|
||||||
|
x.WithMicrosoftLogging(log =>
|
||||||
|
{
|
||||||
|
log.AddConsole(LogLevel.Debug);
|
||||||
|
})
|
||||||
|
.WithJsonSerializer()
|
||||||
|
.WithHandle(typeof(InMemoryJsonHandle<>));
|
||||||
|
})
|
||||||
|
.AddStoreOcelotConfigurationInConsul();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
test/Ocelot.AcceptanceTests/StartupWithCustomCacheHandle.cs
Normal file
28
test/Ocelot.AcceptanceTests/StartupWithCustomCacheHandle.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using CacheManager.Core;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ocelot.DependencyInjection;
|
||||||
|
using Ocelot.AcceptanceTests.Caching;
|
||||||
|
|
||||||
|
namespace Ocelot.AcceptanceTests
|
||||||
|
{
|
||||||
|
public class StartupWithCustomCacheHandle : AcceptanceTestsStartup
|
||||||
|
{
|
||||||
|
public StartupWithCustomCacheHandle(IHostingEnvironment env) : base(env) { }
|
||||||
|
|
||||||
|
public override void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddOcelot(Configuration)
|
||||||
|
.AddCacheManager((x) =>
|
||||||
|
{
|
||||||
|
x.WithMicrosoftLogging(log =>
|
||||||
|
{
|
||||||
|
log.AddConsole(LogLevel.Debug);
|
||||||
|
})
|
||||||
|
.WithJsonSerializer()
|
||||||
|
.WithHandle(typeof(InMemoryJsonHandle<>));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -83,7 +83,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
});
|
});
|
||||||
|
|
||||||
_ocelotServer = new TestServer(_webHostBuilder
|
_ocelotServer = new TestServer(_webHostBuilder
|
||||||
.UseStartup<Startup>());
|
.UseStartup<AcceptanceTestsStartup>());
|
||||||
|
|
||||||
_ocelotClient = _ocelotServer.CreateClient();
|
_ocelotClient = _ocelotServer.CreateClient();
|
||||||
}
|
}
|
||||||
@ -103,7 +103,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
});
|
});
|
||||||
|
|
||||||
_ocelotServer = new TestServer(_webHostBuilder
|
_ocelotServer = new TestServer(_webHostBuilder
|
||||||
.UseStartup<Startup>());
|
.UseStartup<AcceptanceTestsStartup>());
|
||||||
|
|
||||||
_ocelotClient = _ocelotServer.CreateClient();
|
_ocelotClient = _ocelotServer.CreateClient();
|
||||||
}
|
}
|
||||||
@ -118,7 +118,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
});
|
});
|
||||||
|
|
||||||
_ocelotServer = new TestServer(_webHostBuilder
|
_ocelotServer = new TestServer(_webHostBuilder
|
||||||
.UseStartup<Startup_WithCustomCacheHandle>());
|
.UseStartup<StartupWithCustomCacheHandle>());
|
||||||
|
|
||||||
_ocelotClient = _ocelotServer.CreateClient();
|
_ocelotClient = _ocelotServer.CreateClient();
|
||||||
}
|
}
|
||||||
@ -148,7 +148,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
});
|
});
|
||||||
|
|
||||||
_ocelotServer = new TestServer(_webHostBuilder
|
_ocelotServer = new TestServer(_webHostBuilder
|
||||||
.UseStartup<Startup_WithConsul_And_CustomCacheHandle>());
|
.UseStartup<StartupWithConsulAndCustomCacheHandle>());
|
||||||
|
|
||||||
_ocelotClient = _ocelotServer.CreateClient();
|
_ocelotClient = _ocelotServer.CreateClient();
|
||||||
}
|
}
|
||||||
@ -157,7 +157,6 @@ namespace Ocelot.AcceptanceTests
|
|||||||
{
|
{
|
||||||
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
|
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
|
||||||
|
|
||||||
response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath);
|
|
||||||
response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey);
|
response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey);
|
||||||
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host);
|
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host);
|
||||||
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port);
|
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port);
|
||||||
|
@ -39,13 +39,7 @@ namespace Ocelot.IntegrationTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_response_401_with_call_re_routes_controller()
|
public void should_return_response_401_with_call_re_routes_controller()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration();
|
||||||
{
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration
|
|
||||||
{
|
|
||||||
AdministrationPath = "/administration"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => GivenThereIsAConfiguration(configuration))
|
this.Given(x => GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => GivenOcelotIsRunning())
|
.And(x => GivenOcelotIsRunning())
|
||||||
@ -57,13 +51,7 @@ namespace Ocelot.IntegrationTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_response_200_with_call_re_routes_controller()
|
public void should_return_response_200_with_call_re_routes_controller()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration();
|
||||||
{
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration
|
|
||||||
{
|
|
||||||
AdministrationPath = "/administration"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => GivenThereIsAConfiguration(configuration))
|
this.Given(x => GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => GivenOcelotIsRunning())
|
.And(x => GivenOcelotIsRunning())
|
||||||
@ -77,13 +65,7 @@ namespace Ocelot.IntegrationTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_be_able_to_use_token_from_ocelot_a_on_ocelot_b()
|
public void should_be_able_to_use_token_from_ocelot_a_on_ocelot_b()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration();
|
||||||
{
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration
|
|
||||||
{
|
|
||||||
AdministrationPath = "/administration"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => GivenThereIsAConfiguration(configuration))
|
this.Given(x => GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => GivenIdentityServerSigningEnvironmentalVariablesAreSet())
|
.And(x => GivenIdentityServerSigningEnvironmentalVariablesAreSet())
|
||||||
@ -102,7 +84,6 @@ namespace Ocelot.IntegrationTests
|
|||||||
{
|
{
|
||||||
GlobalConfiguration = new FileGlobalConfiguration
|
GlobalConfiguration = new FileGlobalConfiguration
|
||||||
{
|
{
|
||||||
AdministrationPath = "/administration",
|
|
||||||
RequestIdKey = "RequestId",
|
RequestIdKey = "RequestId",
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
|
||||||
{
|
{
|
||||||
@ -160,7 +141,6 @@ namespace Ocelot.IntegrationTests
|
|||||||
{
|
{
|
||||||
GlobalConfiguration = new FileGlobalConfiguration
|
GlobalConfiguration = new FileGlobalConfiguration
|
||||||
{
|
{
|
||||||
AdministrationPath = "/administration"
|
|
||||||
},
|
},
|
||||||
ReRoutes = new List<FileReRoute>()
|
ReRoutes = new List<FileReRoute>()
|
||||||
{
|
{
|
||||||
@ -189,7 +169,6 @@ namespace Ocelot.IntegrationTests
|
|||||||
{
|
{
|
||||||
GlobalConfiguration = new FileGlobalConfiguration
|
GlobalConfiguration = new FileGlobalConfiguration
|
||||||
{
|
{
|
||||||
AdministrationPath = "/administration"
|
|
||||||
},
|
},
|
||||||
ReRoutes = new List<FileReRoute>()
|
ReRoutes = new List<FileReRoute>()
|
||||||
{
|
{
|
||||||
@ -234,7 +213,6 @@ namespace Ocelot.IntegrationTests
|
|||||||
{
|
{
|
||||||
GlobalConfiguration = new FileGlobalConfiguration
|
GlobalConfiguration = new FileGlobalConfiguration
|
||||||
{
|
{
|
||||||
AdministrationPath = "/administration"
|
|
||||||
},
|
},
|
||||||
ReRoutes = new List<FileReRoute>()
|
ReRoutes = new List<FileReRoute>()
|
||||||
{
|
{
|
||||||
@ -289,7 +267,7 @@ namespace Ocelot.IntegrationTests
|
|||||||
.ConfigureServices(x => {
|
.ConfigureServices(x => {
|
||||||
x.AddSingleton(_webHostBuilderTwo);
|
x.AddSingleton(_webHostBuilderTwo);
|
||||||
})
|
})
|
||||||
.UseStartup<Startup>();
|
.UseStartup<IntegrationTestsStartup>();
|
||||||
|
|
||||||
_builderTwo = _webHostBuilderTwo.Build();
|
_builderTwo = _webHostBuilderTwo.Build();
|
||||||
|
|
||||||
@ -327,7 +305,6 @@ namespace Ocelot.IntegrationTests
|
|||||||
{
|
{
|
||||||
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
|
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
|
||||||
|
|
||||||
response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath);
|
|
||||||
response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey);
|
response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey);
|
||||||
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host);
|
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host);
|
||||||
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port);
|
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port);
|
||||||
@ -356,9 +333,7 @@ namespace Ocelot.IntegrationTests
|
|||||||
new KeyValuePair<string, string>("client_id", "admin"),
|
new KeyValuePair<string, string>("client_id", "admin"),
|
||||||
new KeyValuePair<string, string>("client_secret", "secret"),
|
new KeyValuePair<string, string>("client_secret", "secret"),
|
||||||
new KeyValuePair<string, string>("scope", "admin"),
|
new KeyValuePair<string, string>("scope", "admin"),
|
||||||
new KeyValuePair<string, string>("username", "admin"),
|
new KeyValuePair<string, string>("grant_type", "client_credentials")
|
||||||
new KeyValuePair<string, string>("password", "secret"),
|
|
||||||
new KeyValuePair<string, string>("grant_type", "password")
|
|
||||||
};
|
};
|
||||||
var content = new FormUrlEncodedContent(formData);
|
var content = new FormUrlEncodedContent(formData);
|
||||||
|
|
||||||
@ -380,7 +355,7 @@ namespace Ocelot.IntegrationTests
|
|||||||
.ConfigureServices(x => {
|
.ConfigureServices(x => {
|
||||||
x.AddSingleton(_webHostBuilder);
|
x.AddSingleton(_webHostBuilder);
|
||||||
})
|
})
|
||||||
.UseStartup<Startup>();
|
.UseStartup<IntegrationTestsStartup>();
|
||||||
|
|
||||||
_builder = _webHostBuilder.Build();
|
_builder = _webHostBuilder.Build();
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@ using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBui
|
|||||||
|
|
||||||
namespace Ocelot.IntegrationTests
|
namespace Ocelot.IntegrationTests
|
||||||
{
|
{
|
||||||
public class Startup
|
public class IntegrationTestsStartup
|
||||||
{
|
{
|
||||||
public Startup(IHostingEnvironment env)
|
public IntegrationTestsStartup(IHostingEnvironment env)
|
||||||
{
|
{
|
||||||
var builder = new ConfigurationBuilder()
|
var builder = new ConfigurationBuilder()
|
||||||
.SetBasePath(env.ContentRootPath)
|
.SetBasePath(env.ContentRootPath)
|
||||||
@ -25,7 +25,7 @@ namespace Ocelot.IntegrationTests
|
|||||||
Configuration = builder.Build();
|
Configuration = builder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IConfigurationRoot Configuration { get; }
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
@ -38,13 +38,13 @@ namespace Ocelot.IntegrationTests
|
|||||||
.WithDictionaryHandle();
|
.WithDictionaryHandle();
|
||||||
};
|
};
|
||||||
|
|
||||||
services.AddOcelot(Configuration);
|
services.AddOcelot(Configuration)
|
||||||
|
.AddCacheManager(settings)
|
||||||
|
.AddAdministration("/administration", "secret");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
|
||||||
|
|
||||||
app.UseOcelot().Wait();
|
app.UseOcelot().Wait();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
@ -13,39 +12,35 @@
|
|||||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="configuration.json;appsettings.json;idsrv3test.pfx">
|
<None Update="peers.json;configuration.json;appsettings.json;idsrv3test.pfx">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
|
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj"/>
|
||||||
<ProjectReference Include="..\Ocelot.ManualTest\Ocelot.ManualTest.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20171031-01" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20171031-01"/>
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0"/>
|
||||||
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
|
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177"/>
|
||||||
<PackageReference Include="xunit" Version="2.3.1" />
|
<PackageReference Include="xunit" Version="2.3.1"/>
|
||||||
<PackageReference Include="IdentityServer4" Version="2.0.2" />
|
<PackageReference Include="IdentityServer4" Version="2.0.2"/>
|
||||||
<PackageReference Include="Shouldly" Version="3.0.0-beta0003" />
|
<PackageReference Include="Shouldly" Version="3.0.0-beta0003"/>
|
||||||
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
|
<PackageReference Include="TestStack.BDDfy" Version="4.3.2"/>
|
||||||
<PackageReference Include="Consul" Version="0.7.2.3" />
|
<PackageReference Include="Consul" Version="0.7.2.3"/>
|
||||||
|
<PackageReference Include="Rafty" Version="0.4.2"/>
|
||||||
|
<PackageReference Include="Microsoft.Data.SQLite" Version="2.0.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
52
test/Ocelot.IntegrationTests/RaftStartup.cs
Normal file
52
test/Ocelot.IntegrationTests/RaftStartup.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Ocelot.DependencyInjection;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.Raft;
|
||||||
|
using Rafty.Concensus;
|
||||||
|
using Rafty.FiniteStateMachine;
|
||||||
|
using Rafty.Infrastructure;
|
||||||
|
using Rafty.Log;
|
||||||
|
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
|
||||||
|
|
||||||
|
namespace Ocelot.IntegrationTests
|
||||||
|
{
|
||||||
|
public class RaftStartup
|
||||||
|
{
|
||||||
|
public RaftStartup(IHostingEnvironment env)
|
||||||
|
{
|
||||||
|
var builder = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(env.ContentRootPath)
|
||||||
|
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
||||||
|
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
|
||||||
|
.AddJsonFile("peers.json", optional: true, reloadOnChange: true)
|
||||||
|
.AddJsonFile("configuration.json")
|
||||||
|
.AddEnvironmentVariables();
|
||||||
|
|
||||||
|
Configuration = builder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
public virtual void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services
|
||||||
|
.AddOcelot(Configuration)
|
||||||
|
.AddAdministration("/administration", "secret")
|
||||||
|
.AddRafty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
app.UseOcelot().Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
431
test/Ocelot.IntegrationTests/RaftTests.cs
Normal file
431
test/Ocelot.IntegrationTests/RaftTests.cs
Normal file
@ -0,0 +1,431 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Raft;
|
||||||
|
using Rafty.Concensus;
|
||||||
|
using Rafty.FiniteStateMachine;
|
||||||
|
using Rafty.Infrastructure;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using static Rafty.Infrastructure.Wait;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
|
||||||
|
namespace Ocelot.IntegrationTests
|
||||||
|
{
|
||||||
|
public class RaftTests : IDisposable
|
||||||
|
{
|
||||||
|
private List<IWebHost> _builders;
|
||||||
|
private List<IWebHostBuilder> _webHostBuilders;
|
||||||
|
private List<Thread> _threads;
|
||||||
|
private FilePeers _peers;
|
||||||
|
private HttpClient _httpClient;
|
||||||
|
private HttpClient _httpClientForAssertions;
|
||||||
|
private string _ocelotBaseUrl;
|
||||||
|
private BearerToken _token;
|
||||||
|
private HttpResponseMessage _response;
|
||||||
|
private static object _lock = new object();
|
||||||
|
|
||||||
|
public RaftTests()
|
||||||
|
{
|
||||||
|
_httpClientForAssertions = new HttpClient();
|
||||||
|
_httpClient = new HttpClient();
|
||||||
|
_ocelotBaseUrl = "http://localhost:5000";
|
||||||
|
_httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
|
||||||
|
_webHostBuilders = new List<IWebHostBuilder>();
|
||||||
|
_builders = new List<IWebHost>();
|
||||||
|
_threads = new List<Thread>();
|
||||||
|
}
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var builder in _builders)
|
||||||
|
{
|
||||||
|
builder?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var peer in _peers.Peers)
|
||||||
|
{
|
||||||
|
File.Delete(peer.HostAndPort.Replace("/","").Replace(":",""));
|
||||||
|
File.Delete($"{peer.HostAndPort.Replace("/","").Replace(":","")}.db");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_persist_command_to_five_servers()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
GlobalConfiguration = new FileGlobalConfiguration
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var updatedConfiguration = new FileConfiguration
|
||||||
|
{
|
||||||
|
GlobalConfiguration = new FileGlobalConfiguration
|
||||||
|
{
|
||||||
|
},
|
||||||
|
ReRoutes = new List<FileReRoute>()
|
||||||
|
{
|
||||||
|
new FileReRoute()
|
||||||
|
{
|
||||||
|
DownstreamHost = "127.0.0.1",
|
||||||
|
DownstreamPort = 80,
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamPathTemplate = "/geoffrey",
|
||||||
|
UpstreamHttpMethod = new List<string> { "get" },
|
||||||
|
UpstreamPathTemplate = "/"
|
||||||
|
},
|
||||||
|
new FileReRoute()
|
||||||
|
{
|
||||||
|
DownstreamHost = "123.123.123",
|
||||||
|
DownstreamPort = 443,
|
||||||
|
DownstreamScheme = "https",
|
||||||
|
DownstreamPathTemplate = "/blooper/{productId}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "post" },
|
||||||
|
UpstreamPathTemplate = "/test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var command = new UpdateFileConfiguration(updatedConfiguration);
|
||||||
|
GivenThereIsAConfiguration(configuration);
|
||||||
|
GivenFiveServersAreRunning();
|
||||||
|
GivenALeaderIsElected();
|
||||||
|
GivenIHaveAnOcelotToken("/administration");
|
||||||
|
WhenISendACommandIntoTheCluster(command);
|
||||||
|
ThenTheCommandIsReplicatedToAllStateMachines(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_persist_command_to_five_servers_when_using_administration_api()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
var updatedConfiguration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>()
|
||||||
|
{
|
||||||
|
new FileReRoute()
|
||||||
|
{
|
||||||
|
DownstreamHost = "127.0.0.1",
|
||||||
|
DownstreamPort = 80,
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamPathTemplate = "/geoffrey",
|
||||||
|
UpstreamHttpMethod = new List<string> { "get" },
|
||||||
|
UpstreamPathTemplate = "/"
|
||||||
|
},
|
||||||
|
new FileReRoute()
|
||||||
|
{
|
||||||
|
DownstreamHost = "123.123.123",
|
||||||
|
DownstreamPort = 443,
|
||||||
|
DownstreamScheme = "https",
|
||||||
|
DownstreamPathTemplate = "/blooper/{productId}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "post" },
|
||||||
|
UpstreamPathTemplate = "/test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var command = new UpdateFileConfiguration(updatedConfiguration);
|
||||||
|
GivenThereIsAConfiguration(configuration);
|
||||||
|
GivenFiveServersAreRunning();
|
||||||
|
GivenALeaderIsElected();
|
||||||
|
GivenIHaveAnOcelotToken("/administration");
|
||||||
|
GivenIHaveAddedATokenToMyRequest();
|
||||||
|
WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration);
|
||||||
|
ThenTheCommandIsReplicatedToAllStateMachines(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenISendACommandIntoTheCluster(UpdateFileConfiguration command)
|
||||||
|
{
|
||||||
|
var p = _peers.Peers.First();
|
||||||
|
var json = JsonConvert.SerializeObject(command,new JsonSerializerSettings() {
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
});
|
||||||
|
var httpContent = new StringContent(json);
|
||||||
|
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||||
|
using(var httpClient = new HttpClient())
|
||||||
|
{
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
||||||
|
var response = httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent).GetAwaiter().GetResult();
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
var result = JsonConvert.DeserializeObject<OkResponse<UpdateFileConfiguration>>(content);
|
||||||
|
result.Command.Configuration.ReRoutes.Count.ShouldBe(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
//dirty sleep to make sure command replicated...
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
while(stopwatch.ElapsedMilliseconds < 10000)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expected)
|
||||||
|
{
|
||||||
|
//dirty sleep to give a chance to replicate...
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
while(stopwatch.ElapsedMilliseconds < 2000)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommandCalledOnAllStateMachines()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var passed = 0;
|
||||||
|
foreach (var peer in _peers.Peers)
|
||||||
|
{
|
||||||
|
var path = $"{peer.HostAndPort.Replace("/","").Replace(":","")}.db";
|
||||||
|
using(var connection = new SqliteConnection($"Data Source={path};"))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
var sql = @"select count(id) from logs";
|
||||||
|
using(var command = new SqliteCommand(sql, connection))
|
||||||
|
{
|
||||||
|
var index = Convert.ToInt32(command.ExecuteScalar());
|
||||||
|
index.ShouldBe(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_httpClientForAssertions.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
||||||
|
var result = _httpClientForAssertions.GetAsync($"{peer.HostAndPort}/administration/configuration").Result;
|
||||||
|
var json = result.Content.ReadAsStringAsync().Result;
|
||||||
|
var response = JsonConvert.DeserializeObject<FileConfiguration>(json, new JsonSerializerSettings{TypeNameHandling = TypeNameHandling.All});
|
||||||
|
response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.Configuration.GlobalConfiguration.RequestIdKey);
|
||||||
|
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Host);
|
||||||
|
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Port);
|
||||||
|
|
||||||
|
for (var i = 0; i < response.ReRoutes.Count; i++)
|
||||||
|
{
|
||||||
|
response.ReRoutes[i].DownstreamHost.ShouldBe(expected.Configuration.ReRoutes[i].DownstreamHost);
|
||||||
|
response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.Configuration.ReRoutes[i].DownstreamPathTemplate);
|
||||||
|
response.ReRoutes[i].DownstreamPort.ShouldBe(expected.Configuration.ReRoutes[i].DownstreamPort);
|
||||||
|
response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.Configuration.ReRoutes[i].DownstreamScheme);
|
||||||
|
response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.Configuration.ReRoutes[i].UpstreamPathTemplate);
|
||||||
|
response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.Configuration.ReRoutes[i].UpstreamHttpMethod);
|
||||||
|
}
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return passed == 5;
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandOnAllStateMachines = WaitFor(20000).Until(() => CommandCalledOnAllStateMachines());
|
||||||
|
commandOnAllStateMachines.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheResponseShouldBe(FileConfiguration expected)
|
||||||
|
{
|
||||||
|
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
|
||||||
|
|
||||||
|
response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey);
|
||||||
|
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host);
|
||||||
|
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port);
|
||||||
|
|
||||||
|
for (var i = 0; i < response.ReRoutes.Count; i++)
|
||||||
|
{
|
||||||
|
response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost);
|
||||||
|
response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate);
|
||||||
|
response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort);
|
||||||
|
response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme);
|
||||||
|
response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate);
|
||||||
|
response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenIGetUrlOnTheApiGateway(string url)
|
||||||
|
{
|
||||||
|
_response = _httpClient.GetAsync(url).Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration)
|
||||||
|
{
|
||||||
|
var json = JsonConvert.SerializeObject(updatedConfiguration);
|
||||||
|
var content = new StringContent(json);
|
||||||
|
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||||
|
_response = _httpClient.PostAsync(url, content).Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenIHaveAddedATokenToMyRequest()
|
||||||
|
{
|
||||||
|
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenIHaveAnOcelotToken(string adminPath)
|
||||||
|
{
|
||||||
|
var tokenUrl = $"{adminPath}/connect/token";
|
||||||
|
var formData = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("client_id", "admin"),
|
||||||
|
new KeyValuePair<string, string>("client_secret", "secret"),
|
||||||
|
new KeyValuePair<string, string>("scope", "admin"),
|
||||||
|
new KeyValuePair<string, string>("grant_type", "client_credentials")
|
||||||
|
};
|
||||||
|
var content = new FormUrlEncodedContent(formData);
|
||||||
|
|
||||||
|
var response = _httpClient.PostAsync(tokenUrl, content).Result;
|
||||||
|
var responseContent = response.Content.ReadAsStringAsync().Result;
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
|
||||||
|
var configPath = $"{adminPath}/.well-known/openid-configuration";
|
||||||
|
response = _httpClient.GetAsync(configPath).Result;
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration)
|
||||||
|
{
|
||||||
|
var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json";
|
||||||
|
|
||||||
|
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration);
|
||||||
|
|
||||||
|
if (File.Exists(configurationPath))
|
||||||
|
{
|
||||||
|
File.Delete(configurationPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(configurationPath, jsonConfiguration);
|
||||||
|
|
||||||
|
var text = File.ReadAllText(configurationPath);
|
||||||
|
|
||||||
|
configurationPath = $"{AppContext.BaseDirectory}/configuration.json";
|
||||||
|
|
||||||
|
if (File.Exists(configurationPath))
|
||||||
|
{
|
||||||
|
File.Delete(configurationPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(configurationPath, jsonConfiguration);
|
||||||
|
|
||||||
|
text = File.ReadAllText(configurationPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenAServerIsRunning(string url)
|
||||||
|
{
|
||||||
|
lock(_lock)
|
||||||
|
{
|
||||||
|
IWebHostBuilder webHostBuilder = new WebHostBuilder();
|
||||||
|
webHostBuilder.UseUrls(url)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.ConfigureServices(x =>
|
||||||
|
{
|
||||||
|
x.AddSingleton(webHostBuilder);
|
||||||
|
x.AddSingleton(new NodeId(url));
|
||||||
|
})
|
||||||
|
.UseStartup<RaftStartup>();
|
||||||
|
|
||||||
|
var builder = webHostBuilder.Build();
|
||||||
|
builder.Start();
|
||||||
|
|
||||||
|
_webHostBuilders.Add(webHostBuilder);
|
||||||
|
_builders.Add(builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenFiveServersAreRunning()
|
||||||
|
{
|
||||||
|
var bytes = File.ReadAllText("peers.json");
|
||||||
|
_peers = JsonConvert.DeserializeObject<FilePeers>(bytes);
|
||||||
|
|
||||||
|
foreach (var peer in _peers.Peers)
|
||||||
|
{
|
||||||
|
var thread = new Thread(() => GivenAServerIsRunning(peer.HostAndPort));
|
||||||
|
thread.Start();
|
||||||
|
_threads.Add(thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenALeaderIsElected()
|
||||||
|
{
|
||||||
|
//dirty sleep to make sure we have a leader
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
while(stopwatch.ElapsedMilliseconds < 20000)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenISendACommandIntoTheCluster(FakeCommand command)
|
||||||
|
{
|
||||||
|
var p = _peers.Peers.First();
|
||||||
|
var json = JsonConvert.SerializeObject(command,new JsonSerializerSettings() {
|
||||||
|
TypeNameHandling = TypeNameHandling.All
|
||||||
|
});
|
||||||
|
var httpContent = new StringContent(json);
|
||||||
|
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
|
||||||
|
using(var httpClient = new HttpClient())
|
||||||
|
{
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
|
||||||
|
var response = httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent).GetAwaiter().GetResult();
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||||
|
var result = JsonConvert.DeserializeObject<OkResponse<FakeCommand>>(content);
|
||||||
|
result.Command.Value.ShouldBe(command.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//dirty sleep to make sure command replicated...
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
while(stopwatch.ElapsedMilliseconds < 10000)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheCommandIsReplicatedToAllStateMachines(FakeCommand command)
|
||||||
|
{
|
||||||
|
//dirty sleep to give a chance to replicate...
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
while(stopwatch.ElapsedMilliseconds < 2000)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommandCalledOnAllStateMachines()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var passed = 0;
|
||||||
|
foreach (var peer in _peers.Peers)
|
||||||
|
{
|
||||||
|
string fsmData;
|
||||||
|
fsmData = File.ReadAllText(peer.HostAndPort.Replace("/","").Replace(":",""));
|
||||||
|
fsmData.ShouldNotBeNullOrEmpty();
|
||||||
|
var fakeCommand = JsonConvert.DeserializeObject<FakeCommand>(fsmData);
|
||||||
|
fakeCommand.Value.ShouldBe(command.Value);
|
||||||
|
passed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return passed == 5;
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandOnAllStateMachines = WaitFor(20000).Until(() => CommandCalledOnAllStateMachines());
|
||||||
|
commandOnAllStateMachines.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -95,7 +95,7 @@ namespace Ocelot.IntegrationTests
|
|||||||
{
|
{
|
||||||
x.AddSingleton(_webHostBuilder);
|
x.AddSingleton(_webHostBuilder);
|
||||||
})
|
})
|
||||||
.UseStartup<Startup>();
|
.UseStartup<IntegrationTestsStartup>();
|
||||||
|
|
||||||
_builder = _webHostBuilder.Build();
|
_builder = _webHostBuilder.Build();
|
||||||
|
|
||||||
|
18
test/Ocelot.IntegrationTests/peers.json
Normal file
18
test/Ocelot.IntegrationTests/peers.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"Peers": [{
|
||||||
|
"HostAndPort": "http://localhost:5000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"HostAndPort": "http://localhost:5002"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"HostAndPort": "http://localhost:5003"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"HostAndPort": "http://localhost:5004"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"HostAndPort": "http://localhost:5001"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
40
test/Ocelot.ManualTest/ManualTestStartup.cs
Normal file
40
test/Ocelot.ManualTest/ManualTestStartup.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using CacheManager.Core;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ocelot.DependencyInjection;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
|
||||||
|
|
||||||
|
namespace Ocelot.ManualTest
|
||||||
|
{
|
||||||
|
public class ManualTestStartup
|
||||||
|
{
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
Action<ConfigurationBuilderCachePart> settings = (x) =>
|
||||||
|
{
|
||||||
|
x.WithDictionaryHandle();
|
||||||
|
};
|
||||||
|
|
||||||
|
services.AddAuthentication()
|
||||||
|
.AddJwtBearer("TestKey", x =>
|
||||||
|
{
|
||||||
|
x.Authority = "test";
|
||||||
|
x.Audience = "test";
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddOcelot()
|
||||||
|
.AddCacheManager(settings)
|
||||||
|
.AddAdministration("/administration", "secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.UseOcelot().Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user