Merge branch 'develop' into feature/config_grow_when_merged

# Conflicts:
#	src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs
#	test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs
This commit is contained in:
Sergey Borodachev 2018-08-18 05:11:35 +04:00
commit 3f784d7949
174 changed files with 5237 additions and 11121 deletions

2
.gitignore vendored
View File

@ -6,7 +6,7 @@
*.user *.user
*.userosscache *.userosscache
*.sln.docstates *.sln.docstates
*.DS_Store
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs

View File

@ -2,8 +2,8 @@ We love to receive contributions from the community so please keep them coming :
Pull requests, issues and commentary welcome! Pull requests, issues and commentary welcome!
Please complete the relavent template for issues and PRs. Sometimes it's worth getting in touch with us to discuss changes Please complete the relevant template for issues and PRs. Sometimes it's worth getting in touch with us to discuss changes
before doing any work incase this is something we are already doing or it might not make sense. We can also give before doing any work incase this is something we are already doing or it might not make sense. We can also give
advice on the easiest way to do things :) advice on the easiest way to do things :)
Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contriute for the first time I suggest looking at a help wanted & small effort issue :) Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contribute for the first time I suggest looking at a help wanted & small effort issue :)

View File

@ -1,7 +1,7 @@
## Expected Behavior / New Feature ## Expected Behavior / New Feature
## Actual Behavior / Motivation for New Feautre ## Actual Behavior / Motivation for New Feature
## Steps to Reproduce the Problem ## Steps to Reproduce the Problem

View File

@ -81,11 +81,11 @@ We love to receive contributions from the community so please keep them coming :
Pull requests, issues and commentary welcome! Pull requests, issues and commentary welcome!
Please complete the relavent template for issues and PRs. Sometimes it's worth getting in touch with us to discuss changes Please complete the relevant template for issues and PRs. Sometimes it's worth getting in touch with us to discuss changes
before doing any work incase this is something we are already doing or it might not make sense. We can also give before doing any work incase this is something we are already doing or it might not make sense. We can also give
advice on the easiest way to do things :) advice on the easiest way to do things :)
Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contriute for the first time I suggest looking at a help wanted & small effort issue :) Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contribute for the first time I suggest looking at a help wanted & small effort issue :)
## Donate ## Donate

View File

@ -5,6 +5,12 @@ Ocelot supports changing configuration during runtime via an authenticated HTTP
internal IdentityServer (for authenticating requests to the administration API only) or hooking the administration API authentication into your own internal IdentityServer (for authenticating requests to the administration API only) or hooking the administration API authentication into your own
IdentityServer. IdentityServer.
The first thing you need to do if you want to use the administration API is bring in the relavent NuGet package..
``Install-Package Ocelot.Administration``
This will bring down everything needed by the admin API.
Providing your own IdentityServer Providing your own IdentityServer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -55,14 +61,34 @@ The secret is the client secret that Ocelot's internal IdentityServer will use t
.AddAdministration("/administration", "secret"); .AddAdministration("/administration", "secret");
} }
In order for the administration API to work, Ocelot / IdentityServer must be able to call itself for validation. This means that you need to add the base url of Ocelot
to global configuration if it is not default (http://localhost:5000). This can be done as follows..
If you want to run on a different host and port locally..
.. code-block:: json
"GlobalConfiguration": {
"BaseUrl": "http://localhost:55580"
}
or if Ocelot is exposed via dns
.. code-block:: json
"GlobalConfiguration": {
"BaseUrl": "http://mydns.com"
}
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.
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.
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 instances in a cluster then you need to use a certificate to sign the bearer tokens used to access the administration API.
In order to do this you need to add two more environmental variables for each Ocelot in the cluster. In order to do this you need to add two more environmental variables for each Ocelot in the cluster.
@ -71,7 +97,7 @@ In order to do this you need to add two more environmental variables for each Oc
``OCELOT_CERTIFICATE_PASSWORD`` ``OCELOT_CERTIFICATE_PASSWORD``
The password for the certificate. The password for the certificate.
Normally Ocelot just uses temporary signing credentials but if you set these environmental variables then it will use the certificate. If all the other Ocelots in the cluster have the same certificate then you are good! Normally Ocelot just uses temporary signing credentials but if you set these environmental variables then it will use the certificate. If all the other Ocelot instances in the cluster have the same certificate then you are good!
Administration API Administration API
@ -98,12 +124,14 @@ This gets the current Ocelot configuration. It is exactly the same JSON we use t
**POST {adminPath}/configuration** **POST {adminPath}/configuration**
This overrwrites the existing configuration (should probably be a put!). I reccomend getting your config from the GET endpoint, making any changes and posting it back...simples. This overrwrites the existing configuration (should probably be a put!). I reccomend getting your config from the GET endpoint, making any changes and posting it back...simples.
The body of the request is JSON and it is the same format as the FileConfiguration.cs that we use to set up The body of the request is JSON and it is the same format as the FileConfiguration.cs that we use to set up
Ocelot on a file system. Ocelot on a file system.
Please note that if you want to use this API then the process running Ocelot must have permission to write to the disk
where your ocelot.json or ocelot.{environment}.json is located. This is because Ocelot will overwrite them on save.
**DELETE {adminPath}/outputcache/{region}** **DELETE {adminPath}/outputcache/{region}**
This clears a region of the cache. If you are using a backplane it will clear all instances of the cache! Giving your the ability to run a cluster of Ocelots and cache over all of them in memory and clear them all at the same time / just use a distributed cache. This clears a region of the cache. If you are using a backplane it will clear all instances of the cache! Giving your the ability to run a cluster of Ocelots and cache over all of them in memory and clear them all at the same time / just use a distributed cache.

View File

@ -6,7 +6,15 @@ the `CacheManager <https://github.com/MichaCo/CacheManager>`_ project. This is a
that is solving a lot of caching problems. I would reccomend using this package to that is solving a lot of caching problems. I would reccomend using this package to
cache with Ocelot. cache with Ocelot.
The following example shows how to add CacheManager to Ocelot so that you can do output caching. The first thing you need to do is add the following to your ConfigureServices.. The following example shows how to add CacheManager to Ocelot so that you can do output caching.
First of all add the following NuGet package.
``Install-Package Ocelot.Cache.CacheManager``
This will give you access to the Ocelot cache manager extension methods.
The second thing you need to do something like the following to your ConfigureServices..
.. code-block:: csharp .. code-block:: csharp
@ -16,7 +24,7 @@ The following example shows how to add CacheManager to Ocelot so that you can do
x.WithDictionaryHandle(); x.WithDictionaryHandle();
}) })
In order to use caching on a route in your ReRoute configuration add this setting. Finally in order to use caching on a route in your ReRoute configuration add this setting.
.. code-block:: json .. code-block:: json
@ -24,11 +32,23 @@ In order to use caching on a route in your ReRoute configuration add this settin
In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds.
If you look at the example `here <https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/Program.cs>`_ you can see how the cache manager is setup and then passed into the Ocelot If you look at the example `here <https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/Program.cs>`_ you can see how the cache manager
AddOcelotOutputCaching configuration method. You can use any settings supported by is setup and then passed into the Ocelot AddCacheManager configuration method. You can use any settings supported by
the CacheManager package and just pass them in. the CacheManager package and just pass them in.
Anyway Ocelot currently supports caching on the URL of the downstream service Anyway Ocelot currently supports caching on the URL of the downstream service
and setting a TTL in seconds to expire the cache. You can also clear the cache for a region and setting a TTL in seconds to expire the cache. You can also clear the cache for a region
by calling Ocelot's administration API. by calling Ocelot's administration API.
Your own caching
^^^^^^^^^^^^^^^^
If you want to add your own caching method implement the following interfaces and register them in DI
e.g. ``services.AddSingleton<IOcelotCache<CachedResponse>, MyCache>()``
``IOcelotCache<CachedResponse>`` this is for output caching.
``IOcelotCache<FileConfiguration>`` this is for caching the file configuration if you are calling something remote to get your config such as Consul.
Please dig into the Ocelot source code to find more. I would really appreciate it if anyone wants to implement Redis, memcache etc..

View File

@ -7,13 +7,13 @@ parameters and other claims. This is only available once a user has been authent
After the user is authenticated we run the claims to claims transformation middleware. After the user is authenticated we run the claims to claims transformation middleware.
This allows the user to transform claims before the authorisation middleware is called. This allows the user to transform claims before the authorisation middleware is called.
After the user is authorised first we call the claims to headers middleware and Finally After the user is authorised first we call the claims to headers middleware and Finally
the claims to query strig parameters middleware. the claims to query string parameters middleware.
The syntax for performing the transforms is the same for each proces. In the ReRoute The syntax for performing the transforms is the same for each process. In the ReRoute
configuration a json dictionary is added with a specific name either AddClaimsToRequest, configuration a json dictionary is added with a specific name either AddClaimsToRequest,
AddHeadersToRequest, AddQueriesToRequest. AddHeadersToRequest, AddQueriesToRequest.
Note I'm not a hotshot programmer so have no idea if this syntax is good.. Note: I'm not a hotshot programmer so have no idea if this syntax is good...
Within this dictionary the entries specify how Ocelot should transform things! Within this dictionary the entries specify how Ocelot should transform things!
The key to the dictionary is going to become the key of either a claim, header The key to the dictionary is going to become the key of either a claim, header
@ -23,12 +23,12 @@ The value of the entry is parsed to logic that will perform the transform. First
all a dictionary accessor is specified e.g. Claims[CustomerId]. This means we want all a dictionary accessor is specified e.g. Claims[CustomerId]. This means we want
to access the claims and get the CustomerId claim type. Next is a greater than (>) to access the claims and get the CustomerId claim type. Next is a greater than (>)
symbol which is just used to split the string. The next entry is either value or value with symbol which is just used to split the string. The next entry is either value or value with
and indexer. If value is specifed Ocelot will just take the value and add it to the and indexer. If value is specified Ocelot will just take the value and add it to the
transform. If the value has an indexer Ocelot will look for a delimiter which is provided transform. If the value has an indexer Ocelot will look for a delimiter which is provided
after another greater than symbol. Ocelot will then split the value on the delimiter after another greater than symbol. Ocelot will then split the value on the delimiter
and add whatever was at the index requested to the transform. and add whatever was at the index requested to the transform.
Claims to Claims Tranformation Claims to Claims Transformation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Below is an example configuration that will transforms claims to claims Below is an example configuration that will transforms claims to claims
@ -54,10 +54,10 @@ Below is an example configuration that will transforms claims to headers
"CustomerId": "Claims[sub] > value[1] > |" "CustomerId": "Claims[sub] > value[1] > |"
} }
This shows a transform where Ocelot looks at the users sub claim and trasnforms it into a This shows a transform where Ocelot looks at the users sub claim and transforms it into a
CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue". CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue".
Claims to Query String Parameters Tranformation Claims to Query String Parameters Transformation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Below is an example configuration that will transforms claims to query string parameters Below is an example configuration that will transforms claims to query string parameters
@ -68,5 +68,5 @@ Below is an example configuration that will transforms claims to query string pa
"LocationId": "Claims[LocationId] > value", "LocationId": "Claims[LocationId] > value",
} }
This shows a transform where Ocelot looks at the users LocationId claim and add its as This shows a transform where Ocelot looks at the users LocationId claim and add it as
a query string parameter to be forwarded onto the downstream service. a query string parameter to be forwarded onto the downstream service.

View File

@ -121,13 +121,18 @@ At the moment there is no validation at this stage it only happens when Ocelot v
Store configuration in consul Store configuration in consul
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store. The first thing you need to do is install the NuGet package that provides Consul support in Ocelot.
``Install-Package Ocelot.Provider.Consul``
Then you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store.
.. code-block:: csharp .. code-block:: csharp
services services
.AddOcelot() .AddOcelot()
.AddStoreOcelotConfigurationInConsul(); .AddConsul()
.AddConfigStoredInConsul();
You also need to add the following to your ocelot.json. This is how Ocelot You also need to add the following to your ocelot.json. This is how Ocelot
finds your Consul agent and interacts to load and store the configuration from Consul. finds your Consul agent and interacts to load and store the configuration from Consul.
@ -146,6 +151,16 @@ I guess it means if you want to use Ocelot to its fullest you take on Consul as
This feature has a 3 second ttl cache before making a new request to your local consul agent. This feature has a 3 second ttl cache before making a new request to your local consul agent.
Reload JSON config on change
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Ocelot supports reloading the json configuration file on change. e.g. the following will recreate Ocelots internal configuration when the ocelot.json file is updated
manually.
.. code-block:: json
config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
Configuration Key Configuration Key
----------------- -----------------

View File

@ -4,8 +4,8 @@ Middleware Injection and Overrides
Warning use with caution. If you are seeing any exceptions or strange behavior in your middleware Warning use with caution. If you are seeing any exceptions or strange behavior in your middleware
pipeline and you are using any of the following. Remove them and try again! pipeline and you are using any of the following. Remove them and try again!
When setting up Ocelot in your Startup.cs you can provide some additonal middleware When setting up Ocelot in your Startup.cs you can provide some additional middleware
and override middleware. This is done as follos. and override middleware. This is done as follows.
.. code-block:: csharp .. code-block:: csharp
@ -20,7 +20,7 @@ and override middleware. This is done as follos.
app.UseOcelot(configuration); app.UseOcelot(configuration);
In the example above the provided function will run before the first piece of Ocelot middleware. In the example above the provided function will run before the first piece of Ocelot middleware.
This allows a user to supply any behaviours they want before and after the Ocelot pipeline has run. This allows a user to supply any behaviors they want before and after the Ocelot pipeline has run.
This means you can break everything so use at your own pleasure! This means you can break everything so use at your own pleasure!
The user can set functions against the following. The user can set functions against the following.
@ -35,7 +35,7 @@ The user can set functions against the following.
* AuthorisationMiddleware - This overrides Ocelots authorisation middleware. * AuthorisationMiddleware - This overrides Ocelots authorisation middleware.
* PreQueryStringBuilderMiddleware - This alows the user to manipulate the query string on the http request before it is passed to Ocelots request creator. * PreQueryStringBuilderMiddleware - This allows the user to manipulate the query string on the http request before it is passed to Ocelots request creator.
Obviously you can just add middleware as normal before the call to app.UseOcelot() It cannot be added Obviously you can just add middleware as normal before the call to app.UseOcelot() It cannot be added
after as Ocelot does not call the next middleware. after as Ocelot does not call the next middleware.

View File

@ -1,11 +1,15 @@
Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION) 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. Ocelot has recently 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). 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. To get Raft support you must first install the Ocelot Rafty package.
``Install-Package Ocelot.Provider.Rafty``
Then you must make the following changes to your Startup.cs / Program.cs.
.. code-block:: csharp .. code-block:: csharp

View File

@ -1,8 +1,8 @@
Routing Routing
======= =======
Ocelot's primary functionality is to take incomeing http requests and forward them on Ocelot's primary functionality is to take incoming http requests and forward them on
to a downstream service. At the moment in the form of another http request (in the future to a downstream service. Ocelot currently only supports this in the form of another http request (in the future
this could be any transport mechanism). this could be any transport mechanism).
Ocelot's describes the routing of one request to another as a ReRoute. In order to get Ocelot's describes the routing of one request to another as a ReRoute. In order to get
@ -15,8 +15,7 @@ anything working in Ocelot you need to set up a ReRoute in the configuration.
] ]
} }
In order to set up a ReRoute you need to add one to the json array called ReRoutes like To configure a ReRoute you need to add one to the ReRoutes json array.
the following.
.. code-block:: json .. code-block:: json
@ -33,16 +32,16 @@ the following.
"UpstreamHttpMethod": [ "Put", "Delete" ] "UpstreamHttpMethod": [ "Put", "Delete" ]
} }
The DownstreamPathTemplate, Scheme and DownstreamHostAndPorts make the URL that this request will be forwarded to. The DownstreamPathTemplate, DownstreamScheme and DownstreamHostAndPorts define the URL that a request will be forwarded to.
DownstreamHostAndPorts is an array that contains the host and port of any downstream services that you wish to forward requests to. Usually this will just contain one entry but sometimes you might want to load balance DownstreamHostAndPorts is a collection that defines the host and port of any downstream services that you wish to forward requests to.
requests to your downstream services and Ocelot let's you add more than one entry and then select a load balancer. Usually this will just contain a single entry but sometimes you might want to load balance requests to your downstream services and Ocelot allows you add more than one entry and then select a load balancer.
The UpstreamPathTemplate is the URL that Ocelot will use to identity which DownstreamPathTemplate to use for a given request. Finally the UpstreamHttpMethod is used so The UpstreamPathTemplate is the URL that Ocelot will use to identity which DownstreamPathTemplate to use for a given request.
Ocelot can distinguish between requests to the same URL and is obviously needed to work :) The UpstreamHttpMethod is used so Ocelot can distinguish between requests with different HTTP verbs to the same URL. You can set a specific list of HTTP Methods or set an empty list to allow any of them.
You can set a specific list of HTTP Methods or set an empty list to allow any of them. In Ocelot you can add placeholders for variables to your Templates in the form of {something}. In Ocelot you can add placeholders for variables to your Templates in the form of {something}.
The placeholder needs to be in both the DownstreamPathTemplate and UpstreamPathTemplate. If it is Ocelot will attempt to replace the placeholder with the correct variable value from the Upstream URL when the request comes in. The placeholder variable needs to be present in both the DownstreamPathTemplate and UpstreamPathTemplate properties. When it is Ocelot will attempt to substitute the value in the UpstreamPathTemplate placeholder into the DownstreamPathTemplate for each request Ocelot processes.
You can also do a catch all type of ReRoute e.g. You can also do a catch all type of ReRoute e.g.
@ -63,7 +62,9 @@ You can also do a catch all type of ReRoute e.g.
This will forward any path + query string combinations to the downstream service after the path /api. This will forward any path + query string combinations to the downstream service after the path /api.
At the moment without any configuration Ocelot will default to all ReRoutes being case insensitive.
The default ReRouting configuration is case insensitive!
In order to change this you can specify on a per ReRoute basis the following setting. In order to change this you can specify on a per ReRoute basis the following setting.
.. code-block:: json .. code-block:: json
@ -71,13 +72,14 @@ In order to change this you can specify on a per ReRoute basis the following set
"ReRouteIsCaseSensitive": true "ReRouteIsCaseSensitive": true
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.
the ReRoute to be case sensitive is my advice!
Catch All 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). 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, all requests will be proxied straight through. The placeholder {url} name is not significant, any name will work.
.. code-block:: json .. code-block:: json
@ -136,18 +138,15 @@ In order to use this feature please add the following to your config.
The ReRoute above will only be matched when the host header value is somedomain.com. The ReRoute above will only be matched when the host header value is somedomain.com.
If you do not set UpstreamHost on a ReRoue then any host header can match it. This is basically a catch all and If you do not set UpstreamHost on a ReRoute then any host header will match it. This means that if you have two ReRoutes that are the same, apart from the UpstreamHost, where one is null and the other set Ocelot will favour the one that has been set.
preservers existing functionality at the time of building the feature. This means that if you have two ReRoutes that are the same apart from the UpstreamHost where one is null and the other set. Ocelot will favour the one that has been set.
This feature was requested as part of `Issue 216 <https://github.com/TomPallister/Ocelot/pull/216>`_ . This feature was requested as part of `Issue 216 <https://github.com/TomPallister/Ocelot/pull/216>`_ .
Priority Priority
^^^^^^^^ ^^^^^^^^
In `Issue 270 <https://github.com/TomPallister/Ocelot/pull/270>`_ I finally decided to expose the ReRoute priority in You can define the order you want your ReRoutes to match the Upstream HttpRequest by including a "Priority" property in ocelot.json
ocelot.json. This means you can decide in what order you want your ReRoutes to match the Upstream HttpRequest. See `Issue 270 <https://github.com/TomPallister/Ocelot/pull/270>`_ for reference
In order to get this working add the following to a ReRoute in ocelot.json, 0 is just an example value here but will explain below.
.. code-block:: json .. code-block:: json
@ -182,14 +181,15 @@ matched /goods/{catchAll} (because this is the first ReRoute in the list!).
Dynamic Routing Dynamic Routing
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/issue/340>`_. The idea is to enable dynamic routing This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/issue/340>`_.
when using a service discovery provider so you don't have to provide the ReRoute config. See the docs :ref:`service-discovery` if
The idea is to enable dynamic routing when using a service discovery provider so you don't have to provide the ReRoute config. See the docs :ref:`service-discovery` if
this sounds interesting to you. this sounds interesting to you.
Query Strings Query Strings
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
Ocelot allow's you to specify a querystring as part of the DownstreamPathTemplate like the example below. Ocelot allows you to specify a querystring as part of the DownstreamPathTemplate like the example below.
.. code-block:: json .. code-block:: json
@ -217,7 +217,7 @@ Ocelot allow's you to specify a querystring as part of the DownstreamPathTemplat
In this example Ocelot will use the value from the {unitId} in the upstream path template and add it to the downstream request as a query string parameter called unitId! In this example Ocelot will use the value from the {unitId} in the upstream path template and add it to the downstream request as a query string parameter called unitId!
Ocelot will also allow you to put query string parametrs in the UpstreamPathTemplate so you can match certain queries to certain services. Ocelot will also allow you to put query string parameters in the UpstreamPathTemplate so you can match certain queries to certain services.
.. code-block:: json .. code-block:: json
@ -244,4 +244,4 @@ Ocelot will also allow you to put query string parametrs in the UpstreamPathTemp
} }
In this example Ocelot will only match requests that have a matching url path and the querystring starts with unitId=something. You can have other queries after this In this example Ocelot will only match requests that have a matching url path and the querystring starts with unitId=something. You can have other queries after this
but you must start with the matching parameter. Also in this example Ocelot will swap the unitId param from the query string and use it in the downstream request path. but you must start with the matching parameter. Also Ocelot will swap the {unitId} parameter from the query string and use it in the downstream request path.

View File

@ -11,6 +11,17 @@ you specify a ServiceName for at ReRoute level.
Consul Consul
^^^^^^ ^^^^^^
The first thing you need to do is install the NuGet package that provides Consul support in Ocelot.
``Install-Package Ocelot.Provider.Consul``
Then add the following to your ConfigureServices method.
.. code-block:: csharp
s.AddOcelot()
.AddConsul();
The following is required in the GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default The following is required in the GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default
will be used. will be used.
@ -59,6 +70,31 @@ The polling interval is in milliseconds and tells Ocelot how often to call Consu
Please note there are tradeoffs here. If you poll Consul it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volitile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling consul per request (as sidecar agent). If you are calling a remote consul agent then polling will be a good performance improvement. Please note there are tradeoffs here. If you poll Consul it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volitile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling consul per request (as sidecar agent). If you are calling a remote consul agent then polling will be a good performance improvement.
You services need to be added to Consul something like below (c# style but hopefully this make sense)...The only important thing to note
is not to add http or https to the Address field. I have been contacted before about not accepting scheme in Address and accepting scheme
in address. After reading `this <https://www.consul.io/docs/agent/services.html>`_ I don't think the scheme should be in there.
.. code-block: csharp
new AgentService()
{
Service = "some-service-name",
Address = "localhost",
Port = 8080,
ID = "some-id",
}
Or
.. code-block:: json
"Service": {
"ID": "some-id",
"Service": "some-service-name",
"Address": "localhost",
"Port": 8080
}
ACL Token ACL Token
--------- ---------
@ -81,7 +117,18 @@ This feature was requested as part of `Issue 262 <https://github.com/TomPalliste
Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe <https://steeltoe.io/>`_ which is something Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe <https://steeltoe.io/>`_ which is something
to do with `Pivotal <https://pivotal.io/platform>`_! Anyway enough of the background. to do with `Pivotal <https://pivotal.io/platform>`_! Anyway enough of the background.
In order to get this working add the following to ocelot.json.. The first thing you need to do is install the NuGet package that provides Eureka support in Ocelot.
``Install-Package Ocelot.Provider.Eureka``
Then add the following to your ConfigureServices method.
.. code-block:: csharp
s.AddOcelot()
.AddEureka();
Then in order to get this working add the following to ocelot.json..
.. code-block:: json .. code-block:: json
@ -111,20 +158,14 @@ is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them
Dynamic Routing Dynamic Routing
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/issue/340>`_. The idea is to enable dynamic routing when using This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/issue/340>`_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider.
a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segmentof the upstream path to lookup the
downstream service with the service discovery provider.
An example of this would be calling ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of An example of this would be calling ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of
the path which is product and use it as a key to look up the service in consul. If consul returns a service Ocelot will request it on whatever host and the path which is product and use it as a key to look up the service in consul. If consul returns a service Ocelot will request it on whatever host and port comes back from consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. Ocelot will apprend any query string to the downstream url as normal.
port comes back from consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products.
Ocelot will apprend any query string to the downstream url as normal.
In order to enable dynamic routing you need to have 0 ReRoutes in your config. At the moment you cannot mix dynamic and configuration ReRoutes. In addition to this you In order to enable dynamic routing you need to have 0 ReRoutes in your config. At the moment you cannot mix dynamic and configuration ReRoutes. In addition to this you need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme.
need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme.
In addition to that you can set RateLimitOptions, QoSOptions, LoadBalancerOptions and HttpHandlerOptions, DownstreamScheme (You might want to call Ocelot on https but In addition to that you can set RateLimitOptions, QoSOptions, LoadBalancerOptions and HttpHandlerOptions, DownstreamScheme (You might want to call Ocelot on https but talk to private services over http) that will be applied to all of the dynamic ReRoutes.
talk to private services over http) that will be applied to all of the dynamic ReRoutes.
The config might look something like The config might look something like
@ -169,4 +210,40 @@ The config might look something like
} }
} }
Ocelot also allows you to set DynamicReRoutes which lets you set rate limiting rules per downstream service. This is useful if you have for example a product and search service and you want to rate limit one more than the other. An example of this would be as follows.
.. code-block:: json
{
"DynamicReRoutes": [
{
"ServiceName": "product",
"RateLimitRule": {
"ClientWhitelist": [],
"EnableRateLimiting": true,
"Period": "1s",
"PeriodTimespan": 1000.0,
"Limit": 3
}
}
],
"GlobalConfiguration": {
"RequestIdKey": null,
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 8523,
},
"RateLimitOptions": {
"ClientIdHeader": "ClientId",
"QuotaExceededMessage": "",
"RateLimitCounterPrefix": "",
"DisableRateLimitHeaders": false,
"HttpStatusCode": 428
}
"DownstreamScheme": "http",
}
}
This configuration means that if you have a request come into Ocelot on /product/* then dynamic routing will kick in and ocelot will use the rate limiting set against the product service in the DynamicReRoutes section.
Please take a look through all of the docs to understand these options. Please take a look through all of the docs to understand these options.

View File

@ -1,19 +1,29 @@
Tracing Tracing
======= =======
Ocelot providers tracing functionality from the excellent `Butterfly <https://github.com/ButterflyAPM>`_ project. This page details how to perform distributed tracing with Ocelot. At the moment we only support Butterfly but other tracers might just work without
anything Ocelot specific.
Butterfly
^^^^^^^^^
Ocelot providers tracing functionality from the excellent `Butterfly <https://github.com/liuhaoyang/butterfly>`_ project. The code for the Ocelot integration
can be found `here <https://github.com/ThreeMammals/Ocelot.Tracing.Butterfly>`_.
In order to use the tracing please read the Butterfly documentation. In order to use the tracing please read the Butterfly documentation.
In ocelot you need to do the following if you wish to trace a ReRoute. In ocelot you need to do the following if you wish to trace a ReRoute.
``Install-Package Ocelot.Tracing.Butterfly``
In your ConfigureServices method In your ConfigureServices method
.. code-block:: csharp .. code-block:: csharp
services services
.AddOcelot() .AddOcelot()
.AddOpenTracing(option => // this comes from Ocelot.Tracing.Butterfly package
.AddButterfly(option =>
{ {
//this is the url that the butterfly collector server is running on... //this is the url that the butterfly collector server is running on...
option.CollectorUrl = "http://localhost:9618"; option.CollectorUrl = "http://localhost:9618";

View File

@ -35,6 +35,52 @@ With this configuration set Ocelot will match any websocket traffic that comes i
Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and
proxy these to the upstream client. proxy these to the upstream client.
SignalR
^^^^^^^
Ocelot supports proxying SignalR. This functionality was requested in `Issue 344 <https://github.com/ThreeMammals/Ocelot/issues/344>`_.
In order to get websocket proxying working with Ocelot you need to do the following.
Install Microsoft.AspNetCore.SignalR.Client 1.0.2 you can try other packages but this one is tested.
Do not run it in IISExpress or install the websockets feature in the IIS features
In your Configure method you need to tell your application to use SignalR.
.. code-block:: csharp
Configure(app =>
{
app.UseWebSockets();
app.UseOcelot().Wait();
})
Then in your ocelot.json add the following to proxy a ReRoute using SignalR. Note normal Ocelot routing rules apply the main thing is the scheme which is set to "ws".
.. code-block:: json
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/{catchAll}",
"DownstreamScheme": "ws",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 50000
}
],
"UpstreamPathTemplate": "/gateway/{catchAll}",
"UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE", "OPTIONS" ]
}
]
}
With this configuration set Ocelot will match any SignalR traffic that comes in on / and proxy it to localhost:5001/ws. To make this clearer
Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and
proxy these to the upstream client.
Supported Supported
^^^^^^^^^ ^^^^^^^^^

View File

@ -5,7 +5,7 @@ Ocelot does not support...
* Chunked Encoding - Ocelot will always get the body size and return Content-Length header. Sorry if this doesn't work for your use case! * Chunked Encoding - Ocelot will always get the body size and return Content-Length header. Sorry if this doesn't work for your use case!
* Fowarding a host header - The host header that you send to Ocelot will not be forwarded to the downstream service. Obviously this would break everything :( * Forwarding a host header - The host header that you send to Ocelot will not be forwarded to the downstream service. Obviously this would break everything :(
* Swagger - I have looked multiple times at building swagger.json out of the Ocelot ocelot.json but it doesnt fit into the vision * Swagger - I have looked multiple times at building swagger.json out of the Ocelot ocelot.json but it doesnt fit into the vision
I have for Ocelot. If you would like to have Swagger in Ocelot then you must roll your own swagger.json and do the following in your I have for Ocelot. If you would like to have Swagger in Ocelot then you must roll your own swagger.json and do the following in your

View File

@ -22,7 +22,7 @@
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true) .AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddJsonFile("ocelot.json") .AddJsonFile("ocelot.json", false, false)
.AddEnvironmentVariables(); .AddEnvironmentVariables();
}) })
.ConfigureServices(s => .ConfigureServices(s =>

View File

@ -106,7 +106,7 @@ namespace OcelotGraphQL
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true) .AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddJsonFile("ocelot.json") .AddJsonFile("ocelot.json", false, false)
.AddEnvironmentVariables(); .AddEnvironmentVariables();
}) })
.ConfigureServices(s => { .ConfigureServices(s => {

View File

@ -59,7 +59,6 @@ namespace OcelotApplicationApiGateway
{ {
this.webHost = new WebHostBuilder() this.webHost = new WebHostBuilder()
.UseKestrel() .UseKestrel()
//.UseStartup<Startup>()
.UseUrls(this.listeningAddress) .UseUrls(this.listeningAddress)
.ConfigureAppConfiguration((hostingContext, config) => .ConfigureAppConfiguration((hostingContext, config) =>
{ {
@ -67,7 +66,7 @@ namespace OcelotApplicationApiGateway
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true) .AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddJsonFile("ocelot.json") .AddJsonFile("ocelot.json", false, false)
.AddEnvironmentVariables(); .AddEnvironmentVariables();
}) })
.ConfigureLogging((hostingContext, logging) => .ConfigureLogging((hostingContext, logging) =>

View File

@ -1,5 +1,4 @@
using IdentityModel; using Ocelot.Responses;
using Ocelot.Responses;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Claims; using System.Security.Claims;
using System.Linq; using System.Linq;
@ -11,6 +10,7 @@ namespace Ocelot.Authorisation
public class ScopesAuthoriser : IScopesAuthoriser public class ScopesAuthoriser : IScopesAuthoriser
{ {
private readonly IClaimsParser _claimsParser; private readonly IClaimsParser _claimsParser;
private readonly string _scope = "scope";
public ScopesAuthoriser(IClaimsParser claimsParser) public ScopesAuthoriser(IClaimsParser claimsParser)
{ {
@ -24,7 +24,7 @@ namespace Ocelot.Authorisation
return new OkResponse<bool>(true); return new OkResponse<bool>(true);
} }
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, JwtClaimTypes.Scope); var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, _scope);
if (values.IsError) if (values.IsError)
{ {

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
namespace Ocelot.Cache namespace Ocelot.Cache
{ {
@ -10,4 +9,24 @@ namespace Ocelot.Cache
T Get(string key, string region); T Get(string key, string region);
void ClearRegion(string region); void ClearRegion(string region);
} }
public class NoCache<T> : IOcelotCache<T>
{
public void Add(string key, T value, TimeSpan ttl, string region)
{
}
public void AddAndDelete(string key, T value, TimeSpan ttl, string region)
{
}
public void ClearRegion(string region)
{
}
public T Get(string key, string region)
{
return default(T);
}
}
} }

View File

@ -1,29 +1,25 @@
using System; namespace Ocelot.Cache.Middleware
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Ocelot.Logging;
using Ocelot.Middleware;
using System.IO;
using Ocelot.Middleware.Multiplexer;
namespace Ocelot.Cache.Middleware
{ {
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Ocelot.Logging;
using Ocelot.Middleware;
using System.IO;
public class OutputCacheMiddleware : OcelotMiddleware public class OutputCacheMiddleware : OcelotMiddleware
{ {
private readonly OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private readonly IOcelotCache<CachedResponse> _outputCache; private readonly IOcelotCache<CachedResponse> _outputCache;
private readonly IRegionCreator _regionCreator;
public OutputCacheMiddleware(OcelotRequestDelegate next, public OutputCacheMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IOcelotCache<CachedResponse> outputCache, IOcelotCache<CachedResponse> outputCache)
IRegionCreator regionCreator)
:base(loggerFactory.CreateLogger<OutputCacheMiddleware>()) :base(loggerFactory.CreateLogger<OutputCacheMiddleware>())
{ {
_next = next; _next = next;
_outputCache = outputCache; _outputCache = outputCache;
_regionCreator = regionCreator;
} }
public async Task Invoke(DownstreamContext context) public async Task Invoke(DownstreamContext context)

View File

@ -1,44 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using CacheManager.Core;
namespace Ocelot.Cache
{
public class OcelotCacheManagerCache<T> : IOcelotCache<T>
{
private readonly ICacheManager<T> _cacheManager;
public OcelotCacheManagerCache(ICacheManager<T> cacheManager)
{
_cacheManager = cacheManager;
}
public void Add(string key, T value, TimeSpan ttl, string region)
{
_cacheManager.Add(new CacheItem<T>(key, region, value, ExpirationMode.Absolute, ttl));
}
public void AddAndDelete(string key, T value, TimeSpan ttl, string region)
{
var exists = _cacheManager.Get(key);
if (exists != null)
{
_cacheManager.Remove(key);
}
Add(key, value, ttl, region);
}
public T Get(string key, string region)
{
return _cacheManager.Get<T>(key, region);
}
public void ClearRegion(string region)
{
_cacheManager.ClearRegion(region);
}
}
}

View File

@ -1,7 +1,7 @@
using System.Collections.Generic;
namespace Ocelot.Cache namespace Ocelot.Cache
{ {
using System.Collections.Generic;
public class Regions public class Regions
{ {
public Regions(List<string> value) public Regions(List<string> value)
@ -9,6 +9,6 @@ namespace Ocelot.Cache
Value = value; Value = value;
} }
public List<string> Value {get;private set;} public List<string> Value { get; }
} }
} }

View File

@ -9,6 +9,7 @@
} }
public int TtlSeconds { get; private set; } public int TtlSeconds { get; private set; }
public string Region {get;private set;}
public string Region { get; private set; }
} }
} }

View File

@ -10,6 +10,7 @@ using Ocelot.Configuration.Validator;
using Ocelot.DependencyInjection; using Ocelot.DependencyInjection;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Responses; using Ocelot.Responses;
using Microsoft.Extensions.DependencyInjection;
namespace Ocelot.Configuration.Creator namespace Ocelot.Configuration.Creator
{ {
@ -49,14 +50,14 @@ namespace Ocelot.Configuration.Creator
IRateLimitOptionsCreator rateLimitOptionsCreator, IRateLimitOptionsCreator rateLimitOptionsCreator,
IRegionCreator regionCreator, IRegionCreator regionCreator,
IHttpHandlerOptionsCreator httpHandlerOptionsCreator, IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
IAdministrationPath adminPath, IServiceProvider serviceProvider,
IHeaderFindAndReplaceCreator headerFAndRCreator, IHeaderFindAndReplaceCreator headerFAndRCreator,
IDownstreamAddressesCreator downstreamAddressesCreator IDownstreamAddressesCreator downstreamAddressesCreator
) )
{ {
_downstreamAddressesCreator = downstreamAddressesCreator; _downstreamAddressesCreator = downstreamAddressesCreator;
_headerFAndRCreator = headerFAndRCreator; _headerFAndRCreator = headerFAndRCreator;
_adminPath = adminPath; _adminPath = serviceProvider.GetService<IAdministrationPath>();
_regionCreator = regionCreator; _regionCreator = regionCreator;
_rateLimitOptionsCreator = rateLimitOptionsCreator; _rateLimitOptionsCreator = rateLimitOptionsCreator;
_requestIdKeyCreator = requestIdKeyCreator; _requestIdKeyCreator = requestIdKeyCreator;
@ -103,6 +104,12 @@ namespace Ocelot.Configuration.Creator
reRoutes.Add(ocelotReRoute); reRoutes.Add(ocelotReRoute);
} }
foreach(var fileDynamicReRoute in fileConfiguration.DynamicReRoutes)
{
var reRoute = SetUpDynamicReRoute(fileDynamicReRoute, fileConfiguration.GlobalConfiguration);
reRoutes.Add(reRoute);
}
var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration); var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
var lbOptions = CreateLoadBalancerOptions(fileConfiguration.GlobalConfiguration.LoadBalancerOptions); var lbOptions = CreateLoadBalancerOptions(fileConfiguration.GlobalConfiguration.LoadBalancerOptions);
@ -111,8 +118,10 @@ namespace Ocelot.Configuration.Creator
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileConfiguration.GlobalConfiguration.HttpHandlerOptions); var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileConfiguration.GlobalConfiguration.HttpHandlerOptions);
var adminPath = _adminPath != null ? _adminPath.Path : null;
var config = new InternalConfiguration(reRoutes, var config = new InternalConfiguration(reRoutes,
_adminPath.Path, adminPath,
serviceProviderConfiguration, serviceProviderConfiguration,
fileConfiguration.GlobalConfiguration.RequestIdKey, fileConfiguration.GlobalConfiguration.RequestIdKey,
lbOptions, lbOptions,
@ -124,7 +133,24 @@ namespace Ocelot.Configuration.Creator
return new OkResponse<IInternalConfiguration>(config); return new OkResponse<IInternalConfiguration>(config);
} }
public ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) private ReRoute SetUpDynamicReRoute(FileDynamicReRoute fileDynamicReRoute, FileGlobalConfiguration globalConfiguration)
{
var rateLimitOption = _rateLimitOptionsCreator.Create(fileDynamicReRoute.RateLimitRule, globalConfiguration);
var downstreamReRoute = new DownstreamReRouteBuilder()
.WithEnableRateLimiting(true)
.WithRateLimitOptions(rateLimitOption)
.WithServiceName(fileDynamicReRoute.ServiceName)
.Build();
var reRoute = new ReRouteBuilder()
.WithDownstreamReRoute(downstreamReRoute)
.Build();
return reRoute;
}
private ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration)
{ {
var applicableReRoutes = reRoutes var applicableReRoutes = reRoutes
.SelectMany(x => x.DownstreamReRoute) .SelectMany(x => x.DownstreamReRoute)
@ -186,7 +212,7 @@ namespace Ocelot.Configuration.Creator
var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod.ToArray()); var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod.ToArray());
var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute.RateLimitOptions, globalConfiguration);
var region = _regionCreator.Create(fileReRoute); var region = _regionCreator.Create(fileReRoute);

View File

@ -1,21 +1,22 @@
using Butterfly.Client.Tracing; namespace Ocelot.Configuration.Creator
using Ocelot.Configuration.File;
using Ocelot.Requester;
namespace Ocelot.Configuration.Creator
{ {
using System;
using Logging;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration.File;
public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator
{ {
private readonly IServiceTracer _tracer; private readonly ITracer _tracer;
public HttpHandlerOptionsCreator(IServiceTracer tracer) public HttpHandlerOptionsCreator(IServiceProvider services)
{ {
_tracer = tracer; _tracer = services.GetService<ITracer>();
} }
public HttpHandlerOptions Create(FileHttpHandlerOptions options) public HttpHandlerOptions Create(FileHttpHandlerOptions options)
{ {
var useTracing = _tracer.GetType() != typeof(FakeServiceTracer) && options.UseTracing; var useTracing = _tracer!= null && options.UseTracing;
return new HttpHandlerOptions(options.AllowAutoRedirect, return new HttpHandlerOptions(options.AllowAutoRedirect,
options.UseCookieContainer, useTracing, options.UseProxy); options.UseCookieContainer, useTracing, options.UseProxy);

View File

@ -4,6 +4,6 @@ namespace Ocelot.Configuration.Creator
{ {
public interface IRateLimitOptionsCreator public interface IRateLimitOptionsCreator
{ {
RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting); RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration);
} }
} }

View File

@ -1,23 +0,0 @@
using System;
using System.Collections.Generic;
namespace Ocelot.Configuration.Creator
{
public static class IdentityServerConfigurationCreator
{
public static IdentityServerConfiguration GetIdentityServerConfiguration(string secret)
{
var credentialsSigningCertificateLocation = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE");
var credentialsSigningCertificatePassword = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD");
return new IdentityServerConfiguration(
"admin",
false,
secret,
new List<string> { "admin", "openid", "offline_access" },
credentialsSigningCertificateLocation,
credentialsSigningCertificatePassword
);
}
}
}

View File

@ -6,23 +6,23 @@ namespace Ocelot.Configuration.Creator
{ {
public class RateLimitOptionsCreator : IRateLimitOptionsCreator public class RateLimitOptionsCreator : IRateLimitOptionsCreator
{ {
public RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting) public RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration)
{ {
RateLimitOptions rateLimitOption = null; RateLimitOptions rateLimitOption = null;
if (enableRateLimiting) if (fileRateLimitRule != null && fileRateLimitRule.EnableRateLimiting)
{ {
rateLimitOption = new RateLimitOptionsBuilder() rateLimitOption = new RateLimitOptionsBuilder()
.WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader) .WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader)
.WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) .WithClientWhiteList(fileRateLimitRule.ClientWhitelist)
.WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders) .WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders)
.WithEnableRateLimiting(fileReRoute.RateLimitOptions.EnableRateLimiting) .WithEnableRateLimiting(fileRateLimitRule.EnableRateLimiting)
.WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode) .WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode)
.WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage) .WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage)
.WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix) .WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix)
.WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, .WithRateLimitRule(new RateLimitRule(fileRateLimitRule.Period,
fileReRoute.RateLimitOptions.PeriodTimespan, fileRateLimitRule.PeriodTimespan,
fileReRoute.RateLimitOptions.Limit)) fileRateLimitRule.Limit))
.Build(); .Build();
} }

View File

@ -9,9 +9,11 @@ namespace Ocelot.Configuration.File
ReRoutes = new List<FileReRoute>(); ReRoutes = new List<FileReRoute>();
GlobalConfiguration = new FileGlobalConfiguration(); GlobalConfiguration = new FileGlobalConfiguration();
Aggregates = new List<FileAggregateReRoute>(); Aggregates = new List<FileAggregateReRoute>();
DynamicReRoutes = new List<FileDynamicReRoute>();
} }
public List<FileReRoute> ReRoutes { get; set; } public List<FileReRoute> ReRoutes { get; set; }
public List<FileDynamicReRoute> DynamicReRoutes { get; set; }
// Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates // Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates
public List<FileAggregateReRoute> Aggregates { get;set; } public List<FileAggregateReRoute> Aggregates { get;set; }

View File

@ -0,0 +1,8 @@
namespace Ocelot.Configuration.File
{
public class FileDynamicReRoute
{
public string ServiceName { get; set; }
public FileRateLimitRule RateLimitRule { get; set; }
}
}

View File

@ -1,10 +0,0 @@
namespace Ocelot.Configuration.File
{
public class FileIdentityServerConfig
{
public string ProviderRootUrl { get; set; }
public string ApiName { get; set; }
public bool RequireHttps { get; set; }
public string ApiSecret { get; set; }
}
}

View File

@ -4,11 +4,9 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Configuration.Setter; using Ocelot.Configuration.Setter;
using Ocelot.Raft;
namespace Ocelot.Configuration namespace Ocelot.Configuration
{ {
using Rafty.Concensus.Node;
using Repository; using Repository;
[Authorize] [Authorize]
@ -44,20 +42,6 @@ namespace Ocelot.Configuration
{ {
try try
{ {
//todo - this code is a bit shit sort it out..
var test = _provider.GetService(typeof(INode));
if (test != null)
{
var node = (INode)test;
var result = await node.Accept(new UpdateFileConfiguration(fileConfiguration));
if (result.GetType() == typeof(Rafty.Infrastructure.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 _setter.Set(fileConfiguration); var response = await _setter.Set(fileConfiguration);
if (response.IsError) if (response.IsError)

View File

@ -1,14 +0,0 @@
namespace Ocelot.Configuration
{
using System.Collections.Generic;
public interface IIdentityServerConfiguration
{
string ApiName { get; }
string ApiSecret { get; }
bool RequireHttps { get; }
List<string> AllowedScopes { get; }
string CredentialsSigningCertificateLocation { get; }
string CredentialsSigningCertificatePassword { get; }
}
}

View File

@ -1,30 +0,0 @@
namespace Ocelot.Configuration
{
using System.Collections.Generic;
public class IdentityServerConfiguration : IIdentityServerConfiguration
{
public IdentityServerConfiguration(
string apiName,
bool requireHttps,
string apiSecret,
List<string> allowedScopes,
string credentialsSigningCertificateLocation,
string credentialsSigningCertificatePassword)
{
ApiName = apiName;
RequireHttps = requireHttps;
ApiSecret = apiSecret;
AllowedScopes = allowedScopes;
CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation;
CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword;
}
public string ApiName { get; }
public bool RequireHttps { get; }
public List<string> AllowedScopes { get; }
public string ApiSecret { get; }
public string CredentialsSigningCertificateLocation { get; }
public string CredentialsSigningCertificatePassword { get; }
}
}

View File

@ -1,97 +0,0 @@
namespace Ocelot.Configuration.Repository
{
using System;
using System.Text;
using System.Threading.Tasks;
using Consul;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Ocelot.Infrastructure.Consul;
using Ocelot.Logging;
using Ocelot.Responses;
using Ocelot.ServiceDiscovery.Configuration;
public class ConsulFileConfigurationRepository : IFileConfigurationRepository
{
private readonly IConsulClient _consul;
private readonly string _configurationKey;
private readonly Cache.IOcelotCache<FileConfiguration> _cache;
private readonly IOcelotLogger _logger;
public ConsulFileConfigurationRepository(
Cache.IOcelotCache<FileConfiguration> cache,
IInternalConfigurationRepository repo,
IConsulClientFactory factory,
IOcelotLoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<ConsulFileConfigurationRepository>();
_cache = cache;
var internalConfig = repo.Get();
_configurationKey = "InternalConfiguration";
string token = null;
if (!internalConfig.IsError)
{
token = internalConfig.Data.ServiceProviderConfiguration.Token;
_configurationKey = !string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey) ?
internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey : _configurationKey;
}
var config = new ConsulRegistryConfiguration(internalConfig.Data.ServiceProviderConfiguration.Host,
internalConfig.Data.ServiceProviderConfiguration.Port, _configurationKey, token);
_consul = factory.Get(config);
}
public async Task<Response<FileConfiguration>> Get()
{
var config = _cache.Get(_configurationKey, _configurationKey);
if (config != null)
{
return new OkResponse<FileConfiguration>(config);
}
var queryResult = await _consul.KV.Get(_configurationKey);
if (queryResult.Response == null)
{
return new OkResponse<FileConfiguration>(null);
}
var bytes = queryResult.Response.Value;
var json = Encoding.UTF8.GetString(bytes);
var consulConfig = JsonConvert.DeserializeObject<FileConfiguration>(json);
return new OkResponse<FileConfiguration>(consulConfig);
}
public async Task<Response> Set(FileConfiguration ocelotConfiguration)
{
var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented);
var bytes = Encoding.UTF8.GetBytes(json);
var kvPair = new KVPair(_configurationKey)
{
Value = bytes
};
var result = await _consul.KV.Put(kvPair);
if (result.Response)
{
_cache.AddAndDelete(_configurationKey, ocelotConfiguration, TimeSpan.FromSeconds(3), _configurationKey);
return new OkResponse();
}
return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}"));
}
}
}

View File

@ -9,15 +9,16 @@ namespace Ocelot.Configuration.Repository
{ {
public class DiskFileConfigurationRepository : IFileConfigurationRepository public class DiskFileConfigurationRepository : IFileConfigurationRepository
{ {
private readonly string _configFilePath; private readonly string _environmentFilePath;
private readonly string _ocelotFilePath;
private static readonly object _lock = new object(); private static readonly object _lock = new object();
private const string ConfigurationFileName = "ocelot"; private const string ConfigurationFileName = "ocelot";
public DiskFileConfigurationRepository(IHostingEnvironment hostingEnvironment) public DiskFileConfigurationRepository(IHostingEnvironment hostingEnvironment)
{ {
_configFilePath = $"{AppContext.BaseDirectory}/{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; _environmentFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json";
_ocelotFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}.json";
} }
public Task<Response<FileConfiguration>> Get() public Task<Response<FileConfiguration>> Get()
@ -26,7 +27,7 @@ namespace Ocelot.Configuration.Repository
lock(_lock) lock(_lock)
{ {
jsonConfiguration = System.IO.File.ReadAllText(_configFilePath); jsonConfiguration = System.IO.File.ReadAllText(_environmentFilePath);
} }
var fileConfiguration = JsonConvert.DeserializeObject<FileConfiguration>(jsonConfiguration); var fileConfiguration = JsonConvert.DeserializeObject<FileConfiguration>(jsonConfiguration);
@ -40,12 +41,19 @@ namespace Ocelot.Configuration.Repository
lock(_lock) lock(_lock)
{ {
if (System.IO.File.Exists(_configFilePath)) if (System.IO.File.Exists(_environmentFilePath))
{ {
System.IO.File.Delete(_configFilePath); System.IO.File.Delete(_environmentFilePath);
} }
System.IO.File.WriteAllText(_configFilePath, jsonConfiguration); System.IO.File.WriteAllText(_environmentFilePath, jsonConfiguration);
if (System.IO.File.Exists(_ocelotFilePath))
{
System.IO.File.Delete(_ocelotFilePath);
}
System.IO.File.WriteAllText(_ocelotFilePath, jsonConfiguration);
} }
return Task.FromResult<Response>(new OkResponse()); return Task.FromResult<Response>(new OkResponse());

View File

@ -2,34 +2,45 @@ using System;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Configuration.Setter; using Ocelot.Configuration.Setter;
using Ocelot.Logging; using Ocelot.Logging;
namespace Ocelot.Configuration.Repository namespace Ocelot.Configuration.Repository
{ {
public class ConsulFileConfigurationPoller : IDisposable public class FileConfigurationPoller : IHostedService, IDisposable
{ {
private readonly IOcelotLogger _logger; private readonly IOcelotLogger _logger;
private readonly IFileConfigurationRepository _repo; private readonly IFileConfigurationRepository _repo;
private readonly IFileConfigurationSetter _setter;
private string _previousAsJson; private string _previousAsJson;
private readonly Timer _timer; private Timer _timer;
private bool _polling; private bool _polling;
private readonly IConsulPollerConfiguration _config; private readonly IFileConfigurationPollerOptions _options;
private readonly IInternalConfigurationRepository _internalConfigRepo;
private readonly IInternalConfigurationCreator _internalConfigCreator;
public ConsulFileConfigurationPoller( public FileConfigurationPoller(
IOcelotLoggerFactory factory, IOcelotLoggerFactory factory,
IFileConfigurationRepository repo, IFileConfigurationRepository repo,
IFileConfigurationSetter setter, IFileConfigurationPollerOptions options,
IConsulPollerConfiguration config) IInternalConfigurationRepository internalConfigRepo,
IInternalConfigurationCreator internalConfigCreator)
{ {
_setter = setter; _internalConfigRepo = internalConfigRepo;
_config = config; _internalConfigCreator = internalConfigCreator;
_logger = factory.CreateLogger<ConsulFileConfigurationPoller>(); _options = options;
_logger = factory.CreateLogger<FileConfigurationPoller>();
_repo = repo; _repo = repo;
_previousAsJson = ""; _previousAsJson = "";
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation($"{nameof(FileConfigurationPoller)} is starting.");
_timer = new Timer(async x => _timer = new Timer(async x =>
{ {
if(_polling) if(_polling)
@ -40,12 +51,23 @@ namespace Ocelot.Configuration.Repository
_polling = true; _polling = true;
await Poll(); await Poll();
_polling = false; _polling = false;
}, null, _config.Delay, _config.Delay); }, null, _options.Delay, _options.Delay);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation($"{nameof(FileConfigurationPoller)} is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
} }
private async Task Poll() private async Task Poll()
{ {
_logger.LogInformation("Started polling consul"); _logger.LogInformation("Started polling");
var fileConfig = await _repo.Get(); var fileConfig = await _repo.Get();
@ -59,11 +81,17 @@ namespace Ocelot.Configuration.Repository
if(!fileConfig.IsError && asJson != _previousAsJson) if(!fileConfig.IsError && asJson != _previousAsJson)
{ {
await _setter.Set(fileConfig.Data); var config = await _internalConfigCreator.Create(fileConfig.Data);
if(!config.IsError)
{
_internalConfigRepo.AddOrReplace(config.Data);
}
_previousAsJson = asJson; _previousAsJson = asJson;
} }
_logger.LogInformation("Finished polling consul"); _logger.LogInformation("Finished polling");
} }
/// <summary> /// <summary>

View File

@ -1,6 +1,6 @@
namespace Ocelot.Configuration.Repository namespace Ocelot.Configuration.Repository
{ {
public interface IConsulPollerConfiguration public interface IFileConfigurationPollerOptions
{ {
int Delay { get; } int Delay { get; }
} }

View File

@ -1,6 +1,6 @@
namespace Ocelot.Configuration.Repository namespace Ocelot.Configuration.Repository
{ {
public class InMemoryConsulPollerConfiguration : IConsulPollerConfiguration public class InMemoryFileConfigurationPollerOptions : IFileConfigurationPollerOptions
{ {
public int Delay => 1000; public int Delay => 1000;
} }

View File

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

View File

@ -8,7 +8,7 @@ namespace Ocelot.Configuration.Setter
{ {
public class FileAndInternalConfigurationSetter : IFileConfigurationSetter public class FileAndInternalConfigurationSetter : IFileConfigurationSetter
{ {
private readonly IInternalConfigurationRepository _configRepo; private readonly IInternalConfigurationRepository internalConfigRepo;
private readonly IInternalConfigurationCreator _configCreator; private readonly IInternalConfigurationCreator _configCreator;
private readonly IFileConfigurationRepository _repo; private readonly IFileConfigurationRepository _repo;
@ -17,7 +17,7 @@ namespace Ocelot.Configuration.Setter
IInternalConfigurationCreator configCreator, IInternalConfigurationCreator configCreator,
IFileConfigurationRepository repo) IFileConfigurationRepository repo)
{ {
_configRepo = configRepo; internalConfigRepo = configRepo;
_configCreator = configCreator; _configCreator = configCreator;
_repo = repo; _repo = repo;
} }
@ -35,7 +35,7 @@ namespace Ocelot.Configuration.Setter
if(!config.IsError) if(!config.IsError)
{ {
_configRepo.AddOrReplace(config.Data); internalConfigRepo.AddOrReplace(config.Data);
} }
return new ErrorResponse(config.Errors); return new ErrorResponse(config.Errors);

View File

@ -30,6 +30,11 @@ namespace Ocelot.DependencyInjection
} }
public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, IHostingEnvironment env = null) public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, IHostingEnvironment env = null)
{
return builder.AddOcelot(".", env);
}
public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder, IHostingEnvironment env = null)
{ {
const string primaryConfigFile = "ocelot.json"; const string primaryConfigFile = "ocelot.json";
@ -41,7 +46,7 @@ namespace Ocelot.DependencyInjection
var reg = new Regex(subConfigPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline); var reg = new Regex(subConfigPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline);
var files = new DirectoryInfo(".") var files = new DirectoryInfo(folder)
.EnumerateFiles() .EnumerateFiles()
.Where(fi => reg.IsMatch(fi.Name) && (fi.Name != excludeConfigName)) .Where(fi => reg.IsMatch(fi.Name) && (fi.Name != excludeConfigName))
.ToList(); .ToList();
@ -72,7 +77,7 @@ namespace Ocelot.DependencyInjection
File.WriteAllText(primaryConfigFile, json); File.WriteAllText(primaryConfigFile, json);
builder.AddJsonFile(primaryConfigFile); builder.AddJsonFile(primaryConfigFile, false, false);
return builder; return builder;
} }

View File

@ -1,7 +1,11 @@
namespace Ocelot.DependencyInjection namespace Ocelot.DependencyInjection
{ {
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
public interface IOcelotAdministrationBuilder public interface IOcelotAdministrationBuilder
{ {
IOcelotAdministrationBuilder AddRafty(); IServiceCollection Services { get; }
IConfiguration ConfigurationRoot { get; }
} }
} }

View File

@ -1,29 +1,23 @@
using Butterfly.Client.AspNetCore;
using CacheManager.Core;
using System; using System;
using System.Net.Http; using System.Net.Http;
using IdentityServer4.AccessTokenValidation;
using Ocelot.Middleware.Multiplexer; using Ocelot.Middleware.Multiplexer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
namespace Ocelot.DependencyInjection namespace Ocelot.DependencyInjection
{ {
public interface IOcelotBuilder public interface IOcelotBuilder
{ {
IOcelotBuilder AddStoreOcelotConfigurationInConsul(); IServiceCollection Services { get; }
IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings); IConfiguration Configuration { get; }
IOcelotBuilder AddOpenTracing(Action<ButterflyOptions> settings);
IOcelotAdministrationBuilder AddAdministration(string path, string secret);
IOcelotAdministrationBuilder AddAdministration(string path, Action<IdentityServerAuthenticationOptions> configOptions);
IOcelotBuilder AddDelegatingHandler<T>(bool global = false) IOcelotBuilder AddDelegatingHandler<T>(bool global = false)
where T : DelegatingHandler; where T : DelegatingHandler;
IOcelotBuilder AddSingletonDefinedAggregator<T>() IOcelotBuilder AddSingletonDefinedAggregator<T>()
where T : class, IDefinedAggregator; where T : class, IDefinedAggregator;
IOcelotBuilder AddTransientDefinedAggregator<T>() IOcelotBuilder AddTransientDefinedAggregator<T>()
where T : class, IDefinedAggregator; where T : class, IDefinedAggregator;
} }

View File

@ -1,12 +0,0 @@
namespace Ocelot.DependencyInjection
{
public class NullAdministrationPath : IAdministrationPath
{
public NullAdministrationPath()
{
Path = null;
}
public string Path {get;private set;}
}
}

View File

@ -1,36 +1,17 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Raft;
using Rafty.Concensus;
using Rafty.FiniteStateMachine;
using Rafty.Infrastructure;
using Rafty.Log;
namespace Ocelot.DependencyInjection namespace Ocelot.DependencyInjection
{ {
using Rafty.Concensus.Node; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder
{ {
private readonly IServiceCollection _services; public IServiceCollection Services { get; }
private readonly IConfiguration _configurationRoot; public IConfiguration ConfigurationRoot { get; }
public OcelotAdministrationBuilder(IServiceCollection services, IConfiguration configurationRoot) public OcelotAdministrationBuilder(IServiceCollection services, IConfiguration configurationRoot)
{ {
_configurationRoot = configurationRoot; ConfigurationRoot = configurationRoot;
_services = services; Services = services;
}
public IOcelotAdministrationBuilder AddRafty()
{
var settings = new InMemorySettings(4000, 6000, 100, 10000);
_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;
} }
} }
} }

View File

@ -1,7 +1,5 @@
namespace Ocelot.DependencyInjection namespace Ocelot.DependencyInjection
{ {
using CacheManager.Core;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -32,183 +30,125 @@ namespace Ocelot.DependencyInjection
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using IdentityServer4.AccessTokenValidation;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Ocelot.Configuration; using Ocelot.Configuration;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Net.Http; using System.Net.Http;
using Butterfly.Client.AspNetCore;
using Ocelot.Infrastructure; using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Consul;
using Butterfly.Client.Tracing;
using Ocelot.Middleware.Multiplexer; using Ocelot.Middleware.Multiplexer;
using ServiceDiscovery.Providers; using ServiceDiscovery.Providers;
using Steeltoe.Common.Discovery;
using Pivotal.Discovery.Client;
using Ocelot.Request.Creator; using Ocelot.Request.Creator;
public class OcelotBuilder : IOcelotBuilder public class OcelotBuilder : IOcelotBuilder
{ {
private readonly IServiceCollection _services; public IServiceCollection Services { get; }
private readonly IConfiguration _configurationRoot; public IConfiguration Configuration { get; }
public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot) public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot)
{ {
_configurationRoot = configurationRoot; Configuration = configurationRoot;
_services = services; Services = services;
//add default cache settings... Services.Configure<FileConfiguration>(configurationRoot);
Action<ConfigurationBuilderCachePart> defaultCachingSettings = x =>
{
x.WithDictionaryHandle();
};
AddCacheManager(defaultCachingSettings); //default no caches...
Services.TryAddSingleton<IOcelotCache<FileConfiguration>, NoCache<FileConfiguration>>();
Services.TryAddSingleton<IOcelotCache<CachedResponse>, NoCache<CachedResponse>>();
//add ocelot services... Services.TryAddSingleton<IHttpResponseHeaderReplacer, HttpResponseHeaderReplacer>();
_services.Configure<FileConfiguration>(configurationRoot); Services.TryAddSingleton<IHttpContextRequestHeaderReplacer, HttpContextRequestHeaderReplacer>();
_services.TryAddSingleton<IHttpResponseHeaderReplacer, HttpResponseHeaderReplacer>(); Services.TryAddSingleton<IHeaderFindAndReplaceCreator, HeaderFindAndReplaceCreator>();
_services.TryAddSingleton<IHttpContextRequestHeaderReplacer, HttpContextRequestHeaderReplacer>(); Services.TryAddSingleton<IInternalConfigurationCreator, FileInternalConfigurationCreator>();
_services.TryAddSingleton<IHeaderFindAndReplaceCreator, HeaderFindAndReplaceCreator>(); Services.TryAddSingleton<IInternalConfigurationRepository, InMemoryInternalConfigurationRepository>();
_services.TryAddSingleton<IInternalConfigurationCreator, FileInternalConfigurationCreator>(); Services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>();
_services.TryAddSingleton<IInternalConfigurationRepository, InMemoryInternalConfigurationRepository>(); Services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();
_services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>(); Services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
_services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>(); Services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
_services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>(); Services.TryAddSingleton<IRequestIdKeyCreator, RequestIdKeyCreator>();
_services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>(); Services.TryAddSingleton<IServiceProviderConfigurationCreator,ServiceProviderConfigurationCreator>();
_services.TryAddSingleton<IRequestIdKeyCreator, RequestIdKeyCreator>(); Services.TryAddSingleton<IQoSOptionsCreator, QoSOptionsCreator>();
_services.TryAddSingleton<IServiceProviderConfigurationCreator,ServiceProviderConfigurationCreator>(); Services.TryAddSingleton<IReRouteOptionsCreator, ReRouteOptionsCreator>();
_services.TryAddSingleton<IQoSOptionsCreator, QoSOptionsCreator>(); Services.TryAddSingleton<IRateLimitOptionsCreator, RateLimitOptionsCreator>();
_services.TryAddSingleton<IReRouteOptionsCreator, ReRouteOptionsCreator>(); Services.TryAddSingleton<IBaseUrlFinder, BaseUrlFinder>();
_services.TryAddSingleton<IRateLimitOptionsCreator, RateLimitOptionsCreator>(); Services.TryAddSingleton<IRegionCreator, RegionCreator>();
_services.TryAddSingleton<IBaseUrlFinder, BaseUrlFinder>(); Services.TryAddSingleton<IFileConfigurationRepository, DiskFileConfigurationRepository>();
_services.TryAddSingleton<IRegionCreator, RegionCreator>(); Services.TryAddSingleton<IFileConfigurationSetter, FileAndInternalConfigurationSetter>();
_services.TryAddSingleton<IFileConfigurationRepository, DiskFileConfigurationRepository>(); Services.TryAddSingleton<IQosProviderHouse, QosProviderHouse>();
_services.TryAddSingleton<IFileConfigurationSetter, FileAndInternalConfigurationSetter>(); Services.TryAddSingleton<IQoSProviderFactory, QoSProviderFactory>();
_services.TryAddSingleton<IQosProviderHouse, QosProviderHouse>(); Services.TryAddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
_services.TryAddSingleton<IQoSProviderFactory, QoSProviderFactory>(); Services.TryAddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
_services.TryAddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>(); Services.TryAddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
_services.TryAddSingleton<ILoadBalancerFactory, LoadBalancerFactory>(); Services.TryAddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
_services.TryAddSingleton<ILoadBalancerHouse, LoadBalancerHouse>(); Services.TryAddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
_services.TryAddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>(); Services.TryAddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
_services.TryAddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>(); Services.TryAddSingleton<IClaimsAuthoriser, ClaimsAuthoriser>();
_services.TryAddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>(); Services.TryAddSingleton<IScopesAuthoriser, ScopesAuthoriser>();
_services.TryAddSingleton<IClaimsAuthoriser, ClaimsAuthoriser>(); Services.TryAddSingleton<IAddClaimsToRequest, AddClaimsToRequest>();
_services.TryAddSingleton<IScopesAuthoriser, ScopesAuthoriser>(); Services.TryAddSingleton<IAddHeadersToRequest, AddHeadersToRequest>();
_services.TryAddSingleton<IAddClaimsToRequest, AddClaimsToRequest>(); Services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>();
_services.TryAddSingleton<IAddHeadersToRequest, AddHeadersToRequest>(); Services.TryAddSingleton<IClaimsParser, ClaimsParser>();
_services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>(); Services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
_services.TryAddSingleton<IClaimsParser, ClaimsParser>(); Services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();
_services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>(); Services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>();
_services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>(); Services.AddSingleton<IDownstreamRouteProvider, DownstreamRouteFinder>();
_services.TryAddSingleton<IDownstreamPathPlaceholderReplacer, DownstreamTemplatePathPlaceholderReplacer>(); Services.AddSingleton<IDownstreamRouteProvider, DownstreamRouteCreator>();
_services.AddSingleton<IDownstreamRouteProvider, DownstreamRouteFinder>(); Services.TryAddSingleton<IDownstreamRouteProviderFactory, DownstreamRouteProviderFactory>();
_services.AddSingleton<IDownstreamRouteProvider, Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteCreator>(); Services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
_services.TryAddSingleton<IDownstreamRouteProviderFactory, Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteProviderFactory>(); Services.TryAddSingleton<IHttpResponder, HttpContextResponder>();
_services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>(); Services.TryAddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>();
_services.TryAddSingleton<IHttpResponder, HttpContextResponder>(); Services.TryAddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>();
_services.TryAddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>(); Services.TryAddSingleton<IHttpClientCache, MemoryHttpClientCache>();
_services.TryAddSingleton<IRateLimitCounterHandler, MemoryCacheRateLimitCounterHandler>(); Services.TryAddSingleton<IRequestMapper, RequestMapper>();
_services.TryAddSingleton<IHttpClientCache, MemoryHttpClientCache>(); Services.TryAddSingleton<IHttpHandlerOptionsCreator, HttpHandlerOptionsCreator>();
_services.TryAddSingleton<IRequestMapper, RequestMapper>(); Services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>();
_services.TryAddSingleton<IHttpHandlerOptionsCreator, HttpHandlerOptionsCreator>(); Services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>();
_services.TryAddSingleton<IDownstreamAddressesCreator, DownstreamAddressesCreator>(); Services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
_services.TryAddSingleton<IDelegatingHandlerHandlerFactory, DelegatingHandlerHandlerFactory>();
if (UsingEurekaServiceDiscoveryProvider(configurationRoot))
{
_services.AddDiscoveryClient(configurationRoot);
}
else
{
_services.TryAddSingleton<IDiscoveryClient, FakeEurekaDiscoveryClient>();
}
_services.TryAddSingleton<IHttpRequester, HttpClientHttpRequester>();
// 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.TryAddSingleton<IRequestScopedDataRepository, HttpDataRepository>(); Services.TryAddSingleton<IRequestScopedDataRepository, HttpDataRepository>();
_services.AddMemoryCache(); Services.AddMemoryCache();
_services.TryAddSingleton<OcelotDiagnosticListener>(); Services.TryAddSingleton<OcelotDiagnosticListener>();
//add asp.net services.. //add asp.net services..
var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly;
_services.AddMvcCore() Services.AddMvcCore()
.AddApplicationPart(assembly) .AddApplicationPart(assembly)
.AddControllersAsServices() .AddControllersAsServices()
.AddAuthorization() .AddAuthorization()
.AddJsonFormatters(); .AddJsonFormatters();
_services.AddLogging(); Services.AddLogging();
_services.AddMiddlewareAnalysis(); Services.AddMiddlewareAnalysis();
_services.AddWebEncoders(); Services.AddWebEncoders();
_services.AddSingleton<IAdministrationPath>(new NullAdministrationPath());
_services.TryAddSingleton<IMultiplexer, Multiplexer>(); Services.TryAddSingleton<IMultiplexer, Multiplexer>();
_services.TryAddSingleton<IResponseAggregator, SimpleJsonResponseAggregator>(); Services.TryAddSingleton<IResponseAggregator, SimpleJsonResponseAggregator>();
_services.AddSingleton<ITracingHandlerFactory, TracingHandlerFactory>(); Services.AddSingleton<ITracingHandlerFactory, TracingHandlerFactory>();
Services.TryAddSingleton<IFileConfigurationPollerOptions, InMemoryFileConfigurationPollerOptions>();
// We add this here so that we can always inject something into the factory for IoC.. Services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
_services.AddSingleton<IServiceTracer, FakeServiceTracer>(); Services.TryAddSingleton<IPlaceholders, Placeholders>();
_services.TryAddSingleton<IConsulPollerConfiguration, InMemoryConsulPollerConfiguration>(); Services.TryAddSingleton<IResponseAggregatorFactory, InMemoryResponseAggregatorFactory>();
_services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>(); Services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();
_services.TryAddSingleton<IPlaceholders, Placeholders>(); Services.TryAddSingleton<IDownstreamRequestCreator, DownstreamRequestCreator>();
_services.TryAddSingleton<IConsulClientFactory, ConsulClientFactory>(); Services.TryAddSingleton<IFrameworkDescription, FrameworkDescription>();
_services.TryAddSingleton<IResponseAggregatorFactory, InMemoryResponseAggregatorFactory>();
_services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();
_services.TryAddSingleton<IDownstreamRequestCreator, DownstreamRequestCreator>();
_services.TryAddSingleton<IFrameworkDescription, FrameworkDescription>();
}
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 IOcelotAdministrationBuilder AddAdministration(string path, Action<IdentityServerAuthenticationOptions> configureOptions)
{
var administrationPath = new AdministrationPath(path);
if (configureOptions != null)
{
AddIdentityServer(configureOptions);
}
//todo - hack because we add this earlier so it always exists for some reason...investigate..
var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath);
_services.Replace(descriptor);
return new OcelotAdministrationBuilder(_services, _configurationRoot);
} }
public IOcelotBuilder AddSingletonDefinedAggregator<T>() public IOcelotBuilder AddSingletonDefinedAggregator<T>()
where T : class, IDefinedAggregator where T : class, IDefinedAggregator
{ {
_services.AddSingleton<IDefinedAggregator, T>(); Services.AddSingleton<IDefinedAggregator, T>();
return this; return this;
} }
public IOcelotBuilder AddTransientDefinedAggregator<T>() public IOcelotBuilder AddTransientDefinedAggregator<T>()
where T : class, IDefinedAggregator where T : class, IDefinedAggregator
{ {
_services.AddTransient<IDefinedAggregator, T>(); Services.AddTransient<IDefinedAggregator, T>();
return this; return this;
} }
@ -217,142 +157,18 @@ namespace Ocelot.DependencyInjection
{ {
if(global) if(global)
{ {
_services.AddTransient<THandler>(); Services.AddTransient<THandler>();
_services.AddTransient<GlobalDelegatingHandler>(s => { Services.AddTransient<GlobalDelegatingHandler>(s => {
var service = s.GetService<THandler>(); var service = s.GetService<THandler>();
return new GlobalDelegatingHandler(service); return new GlobalDelegatingHandler(service);
}); });
} }
else else
{ {
_services.AddTransient<DelegatingHandler, THandler>(); Services.AddTransient<DelegatingHandler, THandler>();
} }
return this; return this;
} }
public IOcelotBuilder AddOpenTracing(Action<ButterflyOptions> settings)
{
// Earlier we add FakeServiceTracer and need to remove it here before we add butterfly
_services.RemoveAll<IServiceTracer>();
_services.AddButterfly(settings);
return this;
}
public IOcelotBuilder AddStoreOcelotConfigurationInConsul()
{
_services.AddSingleton<ConsulFileConfigurationPoller>();
_services.AddSingleton<IFileConfigurationRepository, ConsulFileConfigurationRepository>();
return this;
}
public IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings)
{
var cacheManagerOutputCache = CacheFactory.Build<CachedResponse>("OcelotOutputCache", settings);
var ocelotOutputCacheManager = new OcelotCacheManagerCache<CachedResponse>(cacheManagerOutputCache);
_services.RemoveAll(typeof(ICacheManager<CachedResponse>));
_services.RemoveAll(typeof(IOcelotCache<CachedResponse>));
_services.AddSingleton<ICacheManager<CachedResponse>>(cacheManagerOutputCache);
_services.AddSingleton<IOcelotCache<CachedResponse>>(ocelotOutputCacheManager);
var ocelotConfigCacheManagerOutputCache = CacheFactory.Build<IInternalConfiguration>("OcelotConfigurationCache", settings);
var ocelotConfigCacheManager = new OcelotCacheManagerCache<IInternalConfiguration>(ocelotConfigCacheManagerOutputCache);
_services.RemoveAll(typeof(ICacheManager<IInternalConfiguration>));
_services.RemoveAll(typeof(IOcelotCache<IInternalConfiguration>));
_services.AddSingleton<ICacheManager<IInternalConfiguration>>(ocelotConfigCacheManagerOutputCache);
_services.AddSingleton<IOcelotCache<IInternalConfiguration>>(ocelotConfigCacheManager);
var fileConfigCacheManagerOutputCache = CacheFactory.Build<FileConfiguration>("FileConfigurationCache", settings);
var fileConfigCacheManager = new OcelotCacheManagerCache<FileConfiguration>(fileConfigCacheManagerOutputCache);
_services.RemoveAll(typeof(ICacheManager<FileConfiguration>));
_services.RemoveAll(typeof(IOcelotCache<FileConfiguration>));
_services.AddSingleton<ICacheManager<FileConfiguration>>(fileConfigCacheManagerOutputCache);
_services.AddSingleton<IOcelotCache<FileConfiguration>>(fileConfigCacheManager);
return this;
}
private void AddIdentityServer(Action<IdentityServerAuthenticationOptions> configOptions)
{
_services
.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(configOptions);
}
private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath)
{
_services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
var identityServerBuilder = _services
.AddIdentityServer(o => {
o.IssuerUri = "Ocelot";
})
.AddInMemoryApiResources(Resources(identityServerConfiguration))
.AddInMemoryClients(Client(identityServerConfiguration));
var urlFinder = new BaseUrlFinder(_configurationRoot);
var baseSchemeUrlAndPort = urlFinder.Find();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
_services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(o =>
{
o.Authority = baseSchemeUrlAndPort + adminPath.Path;
o.ApiName = identityServerConfiguration.ApiName;
o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps;
o.SupportedTokens = SupportedTokens.Both;
o.ApiSecret = identityServerConfiguration.ApiSecret;
});
//todo - refactor naming..
if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword))
{
identityServerBuilder.AddDeveloperSigningCredential();
}
else
{
//todo - refactor so calls method?
var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword);
identityServerBuilder.AddSigningCredential(cert);
}
}
private List<ApiResource> Resources(IIdentityServerConfiguration identityServerConfiguration)
{
return new List<ApiResource>
{
new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName)
{
ApiSecrets = new List<Secret>
{
new Secret
{
Value = identityServerConfiguration.ApiSecret.Sha256()
}
}
},
};
}
private List<Client> Client(IIdentityServerConfiguration identityServerConfiguration)
{
return new List<Client>
{
new Client
{
ClientId = identityServerConfiguration.ApiName,
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = new List<Secret> {new Secret(identityServerConfiguration.ApiSecret.Sha256())},
AllowedScopes = { identityServerConfiguration.ApiName }
}
};
}
private static bool UsingEurekaServiceDiscoveryProvider(IConfiguration configurationRoot)
{
var type = configurationRoot.GetValue<string>("GlobalConfiguration:ServiceDiscoveryProvider:Type",
string.Empty);
return type.ToLower() == "eureka";
}
} }
} }

View File

@ -2,6 +2,7 @@
{ {
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Configuration; using Configuration;
using Configuration.Builder; using Configuration.Builder;
using Configuration.Creator; using Configuration.Creator;
@ -43,7 +44,7 @@
var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod }); var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod });
var downstreamReRoute = new DownstreamReRouteBuilder() var downstreamReRouteBuilder = new DownstreamReRouteBuilder()
.WithServiceName(serviceName) .WithServiceName(serviceName)
.WithLoadBalancerKey(loadBalancerKey) .WithLoadBalancerKey(loadBalancerKey)
.WithDownstreamPathTemplate(downstreamPath) .WithDownstreamPathTemplate(downstreamPath)
@ -51,8 +52,22 @@
.WithHttpHandlerOptions(configuration.HttpHandlerOptions) .WithHttpHandlerOptions(configuration.HttpHandlerOptions)
.WithQosOptions(qosOptions) .WithQosOptions(qosOptions)
.WithDownstreamScheme(configuration.DownstreamScheme) .WithDownstreamScheme(configuration.DownstreamScheme)
.WithLoadBalancerOptions(configuration.LoadBalancerOptions) .WithLoadBalancerOptions(configuration.LoadBalancerOptions);
.Build();
var rateLimitOptions = configuration.ReRoutes != null
? configuration.ReRoutes
.SelectMany(x => x.DownstreamReRoute)
.FirstOrDefault(x => x.ServiceName == serviceName)
: null;
if(rateLimitOptions != null)
{
downstreamReRouteBuilder
.WithRateLimitOptions(rateLimitOptions.RateLimitOptions)
.WithEnableRateLimiting(true);
}
var downstreamReRoute = downstreamReRouteBuilder.Build();
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithDownstreamReRoute(downstreamReRoute) .WithDownstreamReRoute(downstreamReRoute)

View File

@ -20,7 +20,9 @@
public IDownstreamRouteProvider Get(IInternalConfiguration config) public IDownstreamRouteProvider Get(IInternalConfiguration config)
{ {
if(!config.ReRoutes.Any() && IsServiceDiscovery(config.ServiceProviderConfiguration)) //todo - this is a bit hacky we are saying there are no reRoutes or there are reRoutes but none of them have
//an upstream path template which means they are dyanmic and service discovery is on...
if((!config.ReRoutes.Any() || config.ReRoutes.All(x => string.IsNullOrEmpty(x.UpstreamPathTemplate.Value))) && IsServiceDiscovery(config.ServiceProviderConfiguration))
{ {
_logger.LogInformation($"Selected {nameof(DownstreamRouteCreator)} as DownstreamRouteProvider for this request"); _logger.LogInformation($"Selected {nameof(DownstreamRouteCreator)} as DownstreamRouteProvider for this request");
return _providers[nameof(DownstreamRouteCreator)]; return _providers[nameof(DownstreamRouteCreator)];

View File

@ -52,6 +52,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
} }
var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value)); var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value));
Logger.LogDebug($"downstream templates are {downstreamPathTemplates}"); Logger.LogDebug($"downstream templates are {downstreamPathTemplates}");
context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues; context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues;

View File

@ -52,9 +52,17 @@ namespace Ocelot.DownstreamUrlCreator.Middleware
if(ContainsQueryString(dsPath)) if(ContainsQueryString(dsPath))
{ {
context.DownstreamRequest.AbsolutePath = GetPath(dsPath); context.DownstreamRequest.AbsolutePath = GetPath(dsPath);
if (string.IsNullOrEmpty(context.DownstreamRequest.Query))
{
context.DownstreamRequest.Query = GetQueryString(dsPath); context.DownstreamRequest.Query = GetQueryString(dsPath);
} }
else else
{
context.DownstreamRequest.Query += GetQueryString(dsPath).Replace('?', '&');
}
}
else
{ {
RemoveQueryStringParametersThatHaveBeenUsedInTemplate(context); RemoveQueryStringParametersThatHaveBeenUsedInTemplate(context);

View File

@ -1,15 +1,14 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Ocelot.Configuration.Repository;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging;
using Ocelot.Middleware;
namespace Ocelot.Errors.Middleware namespace Ocelot.Errors.Middleware
{ {
using Configuration; using Configuration;
using System;
using System.Linq;
using System.Threading.Tasks;
using Ocelot.Configuration.Repository;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging;
using Ocelot.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

View File

@ -29,7 +29,6 @@
UnableToFindLoadBalancerError, UnableToFindLoadBalancerError,
RequestTimedOutError, RequestTimedOutError,
UnableToFindQoSProviderError, UnableToFindQoSProviderError,
UnableToSetConfigInConsulError,
UnmappableRequestError, UnmappableRequestError,
RateLimitOptionsError, RateLimitOptionsError,
PathTemplateDoesntStartWithForwardSlash, PathTemplateDoesntStartWithForwardSlash,

View File

@ -1,22 +0,0 @@
using System;
using Consul;
using Ocelot.ServiceDiscovery.Configuration;
namespace Ocelot.Infrastructure.Consul
{
public class ConsulClientFactory : IConsulClientFactory
{
public IConsulClient Get(ConsulRegistryConfiguration config)
{
return new ConsulClient(c =>
{
c.Address = new Uri($"http://{config.Host}:{config.Port}");
if (!string.IsNullOrEmpty(config?.Token))
{
c.Token = config.Token;
}
});
}
}
}

View File

@ -1,10 +0,0 @@
using Consul;
using Ocelot.ServiceDiscovery.Configuration;
namespace Ocelot.Infrastructure.Consul
{
public interface IConsulClientFactory
{
IConsulClient Get(ConsulRegistryConfiguration config);
}
}

View File

@ -3,7 +3,7 @@ using System.Linq;
namespace Ocelot.Infrastructure.Extensions namespace Ocelot.Infrastructure.Extensions
{ {
internal static class StringValuesExtensions public static class StringValuesExtensions
{ {
public static string GetValue(this StringValues stringValues) public static string GetValue(this StringValues stringValues)
{ {

View File

@ -0,0 +1,19 @@
namespace Ocelot.Logging
{
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
public interface ITracer
{
void Event(HttpContext httpContext, string @event);
Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken,
Action<string> addTraceIdToRepo,
Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> baseSendAsync);
}
}

View File

@ -1,26 +1,21 @@
using System; using System;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DiagnosticAdapter; using Microsoft.Extensions.DiagnosticAdapter;
using Butterfly.Client.AspNetCore; using Microsoft.Extensions.DependencyInjection;
using Butterfly.OpenTracing;
using Ocelot.Middleware; using Ocelot.Middleware;
using Butterfly.Client.Tracing;
using System.Linq;
using System.Collections.Generic;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Requester;
namespace Ocelot.Logging namespace Ocelot.Logging
{ {
public class OcelotDiagnosticListener public class OcelotDiagnosticListener
{ {
private readonly IServiceTracer _tracer;
private readonly IOcelotLogger _logger; private readonly IOcelotLogger _logger;
private readonly ITracer _tracer;
public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceTracer tracer) public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceProvider serviceProvider)
{ {
_tracer = tracer;
_logger = factory.CreateLogger<OcelotDiagnosticListener>(); _logger = factory.CreateLogger<OcelotDiagnosticListener>();
_tracer = serviceProvider.GetService<ITracer>();
} }
[DiagnosticName("Ocelot.MiddlewareException")] [DiagnosticName("Ocelot.MiddlewareException")]
@ -66,31 +61,7 @@ namespace Ocelot.Logging
private void Event(HttpContext httpContext, string @event) private void Event(HttpContext httpContext, string @event)
{ {
// todo - if the user isnt using tracing the code gets here and will blow up on _tracer?.Event(httpContext, @event);
// _tracer.Tracer.TryExtract. We already use the fake tracer for another scenario
// so sticking it here as well..I guess we need a factory for this but cba to do it at
// the moment
if(_tracer.GetType() == typeof(FakeServiceTracer))
{
return;
}
var span = httpContext.GetSpan();
if(span == null)
{
var spanBuilder = new SpanBuilder($"server {httpContext.Request.Method} {httpContext.Request.Path}");
if (_tracer.Tracer.TryExtract(out var spanContext, httpContext.Request.Headers, (c, k) => c[k].GetValue(),
c => c.Select(x => new KeyValuePair<string, string>(x.Key, x.Value.GetValue())).GetEnumerator()))
{
spanBuilder.AsChildOf(spanContext);
}
span = _tracer.Start(spanBuilder);
httpContext.SetSpan(span);
}
span?.Log(LogField.CreateNew().Event(@event));
} }
} }
} }

View File

@ -0,0 +1,7 @@
namespace Ocelot.Middleware
{
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
public delegate Task OcelotMiddlewareConfigurationDelegate(IApplicationBuilder builder);
}

View File

@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Diagnostics; using System.Diagnostics;
using DependencyInjection;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
@ -14,39 +15,35 @@
using Ocelot.Configuration.Setter; using Ocelot.Configuration.Setter;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Logging; using Ocelot.Logging;
using Rafty.Concensus;
using Rafty.Infrastructure;
using Ocelot.Middleware.Pipeline; using Ocelot.Middleware.Pipeline;
using Pivotal.Discovery.Client; using Microsoft.Extensions.DependencyInjection;
using Rafty.Concensus.Node;
public static class OcelotMiddlewareExtensions public static class OcelotMiddlewareExtensions
{ {
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder) public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder)
{ {
await builder.UseOcelot(new OcelotPipelineConfiguration()); await builder.UseOcelot(new OcelotPipelineConfiguration());
return builder; return builder;
} }
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, Action<OcelotPipelineConfiguration> pipelineConfiguration)
{
var config = new OcelotPipelineConfiguration();
pipelineConfiguration?.Invoke(config);
return await builder.UseOcelot(config);
}
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
{ {
var configuration = await CreateConfiguration(builder); var configuration = await CreateConfiguration(builder);
CreateAdministrationArea(builder, configuration);
if(UsingRafty(builder))
{
SetUpRafty(builder);
}
if (UsingEurekaServiceDiscoveryProvider(configuration))
{
builder.UseDiscoveryClient();
}
ConfigureDiagnosticListener(builder); ConfigureDiagnosticListener(builder);
return CreateOcelotPipeline(builder, pipelineConfiguration);
}
private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
{
var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices);
pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration); pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration);
@ -70,90 +67,61 @@
return builder; return builder;
} }
private static bool UsingEurekaServiceDiscoveryProvider(IInternalConfiguration configuration)
{
return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "eureka";
}
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);
}
private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder) private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder)
{ {
// make configuration from file system? // make configuration from file system?
// earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this
var fileConfig = (IOptions<FileConfiguration>)builder.ApplicationServices.GetService(typeof(IOptions<FileConfiguration>)); var fileConfig = builder.ApplicationServices.GetService<IOptionsMonitor<FileConfiguration>>();
// now create the config // now create the config
var internalConfigCreator = (IInternalConfigurationCreator)builder.ApplicationServices.GetService(typeof(IInternalConfigurationCreator)); var internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>();
var internalConfig = await internalConfigCreator.Create(fileConfig.Value); var internalConfig = await internalConfigCreator.Create(fileConfig.CurrentValue);
//Configuration error, throw error message
if (internalConfig.IsError)
{
ThrowToStopOcelotStarting(internalConfig);
}
// now save it in memory // now save it in memory
var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository)); var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
internalConfigRepo.AddOrReplace(internalConfig.Data); internalConfigRepo.AddOrReplace(internalConfig.Data);
var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); fileConfig.OnChange(async (config) =>
var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository));
if (UsingConsul(fileConfigRepo))
{ {
await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo); var newInternalConfig = await internalConfigCreator.Create(config);
internalConfigRepo.AddOrReplace(newInternalConfig.Data);
});
var adminPath = builder.ApplicationServices.GetService<IAdministrationPath>();
var configurations = builder.ApplicationServices.GetServices<OcelotMiddlewareConfigurationDelegate>();
// Todo - this has just been added for consul so far...will there be an ordering problem in the future? Should refactor all config into this pattern?
foreach (var configuration in configurations)
{
await configuration(builder);
} }
else
if(AdministrationApiInUse(adminPath))
{ {
//We have to make sure the file config is set for the ocelot.env.json and ocelot.json so that if we pull it from the
//admin api it works...boy this is getting a spit spags boll.
var fileConfigSetter = builder.ApplicationServices.GetService<IFileConfigurationSetter>();
await SetFileConfig(fileConfigSetter, fileConfig); await SetFileConfig(fileConfigSetter, fileConfig);
} }
return GetOcelotConfigAndReturn(internalConfigRepo); return GetOcelotConfigAndReturn(internalConfigRepo);
} }
private static async Task SetFileConfigInConsul(IApplicationBuilder builder, private static bool AdministrationApiInUse(IAdministrationPath adminPath)
IFileConfigurationRepository fileConfigRepo, IOptions<FileConfiguration> fileConfig,
IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo)
{ {
// get the config from consul. return adminPath != null;
var fileConfigFromConsul = await fileConfigRepo.Get(); }
if (IsError(fileConfigFromConsul)) private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptionsMonitor<FileConfiguration> fileConfig)
{ {
ThrowToStopOcelotStarting(fileConfigFromConsul); var response = await fileConfigSetter.Set(fileConfig.CurrentValue);
}
else if (ConfigNotStoredInConsul(fileConfigFromConsul))
{
//there was no config in consul set the file in config in consul
await fileConfigRepo.Set(fileConfig.Value);
}
else
{
// create the internal config from consul data
var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data);
if (IsError(internalConfig))
{
ThrowToStopOcelotStarting(internalConfig);
}
else
{
// add the internal config to the internal repo
var response = internalConfigRepo.AddOrReplace(internalConfig.Data);
if (IsError(response)) if (IsError(response))
{ {
@ -161,32 +129,6 @@
} }
} }
if (IsError(internalConfig))
{
ThrowToStopOcelotStarting(internalConfig);
}
}
//todo - this starts the poller if it has been registered...please this is so bad.
var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller));
}
private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions<FileConfiguration> fileConfig)
{
Response response;
response = await fileConfigSetter.Set(fileConfig.Value);
if (IsError(response))
{
ThrowToStopOcelotStarting(response);
}
}
private static bool ConfigNotStoredInConsul(Responses.Response<FileConfiguration> fileConfigFromConsul)
{
return fileConfigFromConsul.Data == null;
}
private static bool IsError(Response response) private static bool IsError(Response response)
{ {
return response == null || response.IsError; return response == null || response.IsError;
@ -196,7 +138,7 @@
{ {
var ocelotConfiguration = provider.Get(); var ocelotConfiguration = provider.Get();
if(ocelotConfiguration?.Data == null || ocelotConfiguration.IsError) if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError)
{ {
ThrowToStopOcelotStarting(ocelotConfiguration); ThrowToStopOcelotStarting(ocelotConfiguration);
} }
@ -209,42 +151,12 @@
throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}"); throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}");
} }
private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo)
{
return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository);
}
private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration)
{
if(!string.IsNullOrEmpty(configuration.AdministrationPath))
{
builder.Map(configuration.AdministrationPath, app =>
{
//todo - hack so we know that we are using internal identity server
var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration));
if (identityServerConfiguration != null)
{
app.UseIdentityServer();
}
app.UseAuthentication();
app.UseMvc();
});
}
}
private static void ConfigureDiagnosticListener(IApplicationBuilder builder) private static void ConfigureDiagnosticListener(IApplicationBuilder builder)
{ {
var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment)); var env = builder.ApplicationServices.GetService<IHostingEnvironment>();
var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener)); var listener = builder.ApplicationServices.GetService<OcelotDiagnosticListener>();
var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener)); var diagnosticListener = builder.ApplicationServices.GetService<DiagnosticListener>();
diagnosticListener.SubscribeWithAdapter(listener); diagnosticListener.SubscribeWithAdapter(listener);
} }
private static void OnShutdown(IApplicationBuilder app)
{
var node = (INode)app.ApplicationServices.GetService(typeof(INode));
node.Stop();
}
} }
} }

View File

@ -1,6 +1,8 @@
namespace Ocelot.Middleware namespace Ocelot.Middleware
{ {
using Ocelot.Middleware.Pipeline;
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
public class OcelotPipelineConfiguration public class OcelotPipelineConfiguration
@ -38,5 +40,9 @@
/// This allows the user to implement there own query string manipulation logic /// This allows the user to implement there own query string manipulation logic
/// </summary> /// </summary>
public Func<DownstreamContext, Func<Task>, Task> PreQueryStringBuilderMiddleware { get; set; } public Func<DownstreamContext, Func<Task>, Task> PreQueryStringBuilderMiddleware { get; set; }
/// <summary>
/// This is an extension that will branch to different pipes
/// </summary>
public List<Func<IOcelotPipelineBuilder, Func<DownstreamContext, bool>>> MapWhenOcelotPipeline { get; } = new List<Func<IOcelotPipelineBuilder, Func<DownstreamContext, bool>>>();
} }
} }

View File

@ -80,13 +80,14 @@ namespace Ocelot.Middleware.Pipeline
var diagnosticListener = (DiagnosticListener)app.ApplicationServices.GetService(typeof(DiagnosticListener)); var diagnosticListener = (DiagnosticListener)app.ApplicationServices.GetService(typeof(DiagnosticListener));
var middlewareName = ocelotDelegate.Target.GetType().Name; var middlewareName = ocelotDelegate.Target.GetType().Name;
OcelotRequestDelegate wrapped = context => { OcelotRequestDelegate wrapped = context =>
{
try try
{ {
Write(diagnosticListener, "Ocelot.MiddlewareStarted", middlewareName, context); Write(diagnosticListener, "Ocelot.MiddlewareStarted", middlewareName, context);
return ocelotDelegate(context); return ocelotDelegate(context);
} }
catch(Exception ex) catch (Exception ex)
{ {
WriteException(diagnosticListener, ex, "Ocelot.MiddlewareException", middlewareName, context); WriteException(diagnosticListener, ex, "Ocelot.MiddlewareException", middlewareName, context);
throw ex; throw ex;
@ -117,7 +118,7 @@ namespace Ocelot.Middleware.Pipeline
private static void Write(DiagnosticListener diagnosticListener, string message, string middlewareName, DownstreamContext context) private static void Write(DiagnosticListener diagnosticListener, string message, string middlewareName, DownstreamContext context)
{ {
if(diagnosticListener != null) if (diagnosticListener != null)
{ {
diagnosticListener.Write(message, new { name = middlewareName, context = context }); diagnosticListener.Write(message, new { name = middlewareName, context = context });
} }
@ -125,7 +126,7 @@ namespace Ocelot.Middleware.Pipeline
private static void WriteException(DiagnosticListener diagnosticListener, Exception exception, string message, string middlewareName, DownstreamContext context) private static void WriteException(DiagnosticListener diagnosticListener, Exception exception, string message, string middlewareName, DownstreamContext context)
{ {
if(diagnosticListener != null) if (diagnosticListener != null)
{ {
diagnosticListener.Write(message, new { name = middlewareName, context = context, exception = exception }); diagnosticListener.Write(message, new { name = middlewareName, context = context, exception = exception });
} }
@ -160,6 +161,28 @@ namespace Ocelot.Middleware.Pipeline
return app.Use(next => new MapWhenMiddleware(next, options).Invoke); return app.Use(next => new MapWhenMiddleware(next, options).Invoke);
} }
public static IOcelotPipelineBuilder MapWhen(this IOcelotPipelineBuilder app, Func<IOcelotPipelineBuilder, Predicate> pipelineBuilderFunc)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (pipelineBuilderFunc == null)
{
throw new ArgumentNullException(nameof(pipelineBuilderFunc));
}
var branchBuilder = app.New();
var predicate = pipelineBuilderFunc.Invoke(branchBuilder);
var branch = branchBuilder.Build();
var options = new MapWhenOptions
{
Predicate = predicate,
Branch = branch
};
return app.Use(next => new MapWhenMiddleware(next, options).Invoke);
}
private static Func<T, DownstreamContext, IServiceProvider, Task> Compile<T>(MethodInfo methodinfo, ParameterInfo[] parameters) private static Func<T, DownstreamContext, IServiceProvider, Task> Compile<T>(MethodInfo methodinfo, ParameterInfo[] parameters)
{ {
var middleware = typeof(T); var middleware = typeof(T);

View File

@ -48,6 +48,15 @@ namespace Ocelot.Middleware.Pipeline
// Then we get the downstream route information // Then we get the downstream route information
builder.UseDownstreamRouteFinderMiddleware(); builder.UseDownstreamRouteFinderMiddleware();
//Expand other branch pipes
if (pipelineConfiguration.MapWhenOcelotPipeline != null)
{
foreach (var pipeline in pipelineConfiguration.MapWhenOcelotPipeline)
{
builder.MapWhen(pipeline);
}
}
// Now we have the ds route we can transform headers and stuff? // Now we have the ds route we can transform headers and stuff?
builder.UseHttpHeadersTransformationMiddleware(); builder.UseHttpHeadersTransformationMiddleware();

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion> <RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
@ -12,6 +12,7 @@
<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>
<PackageIconUrl>http://threemammals.com/images/ocelot_logo.png</PackageIconUrl>
<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>
@ -25,12 +26,7 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Butterfly.Client" Version="0.0.8" />
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8">
<NoWarn>NU1701</NoWarn>
</PackageReference>
<PackageReference Include="FluentValidation" Version="7.6.104" /> <PackageReference Include="FluentValidation" Version="7.6.104" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.1" /> <PackageReference Include="Microsoft.AspNetCore" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="2.1.1" /> <PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="2.1.1" />
@ -49,13 +45,6 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.0" /> <PackageReference Include="System.Text.RegularExpressions" Version="4.3.0" />
<PackageReference Include="CacheManager.Core" Version="1.1.2" />
<PackageReference Include="CacheManager.Microsoft.Extensions.Configuration" Version="1.1.2" />
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.1.2" />
<PackageReference Include="Consul" Version="0.7.2.5" />
<PackageReference Include="Polly" Version="6.0.1" /> <PackageReference Include="Polly" Version="6.0.1" />
<PackageReference Include="IdentityServer4" Version="2.2.0" />
<PackageReference Include="Pivotal.Discovery.ClientCore" Version="2.0.1" />
<PackageReference Include="Rafty" Version="0.4.4" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,17 +0,0 @@
using Newtonsoft.Json;
namespace Ocelot.Raft
{
[ExcludeFromCoverage]
internal 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; }
}
}

View File

@ -1,7 +0,0 @@
using System;
namespace Ocelot.Raft
{
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method|AttributeTargets.Property)]
public class ExcludeFromCoverageAttribute : Attribute{}
}

View File

@ -1,15 +0,0 @@
using Rafty.FiniteStateMachine;
namespace Ocelot.Raft
{
[ExcludeFromCoverage]
public class FakeCommand : ICommand
{
public FakeCommand(string value)
{
this.Value = value;
}
public string Value { get; private set; }
}
}

View File

@ -1,8 +0,0 @@
namespace Ocelot.Raft
{
[ExcludeFromCoverage]
public class FilePeer
{
public string HostAndPort { get; set; }
}
}

View File

@ -1,15 +0,0 @@
using System.Collections.Generic;
namespace Ocelot.Raft
{
[ExcludeFromCoverage]
public class FilePeers
{
public FilePeers()
{
Peers = new List<FilePeer>();
}
public List<FilePeer> Peers {get; set;}
}
}

View File

@ -1,47 +0,0 @@
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.Extensions.Options;
using Ocelot.Configuration;
using Ocelot.Configuration.Repository;
using Ocelot.Middleware;
using Rafty.Concensus;
using Rafty.Infrastructure;
namespace Ocelot.Raft
{
using Rafty.Concensus.Peers;
[ExcludeFromCoverage]
public class FilePeersProvider : IPeersProvider
{
private readonly IOptions<FilePeers> _options;
private readonly List<IPeer> _peers;
private IBaseUrlFinder _finder;
private IInternalConfigurationRepository _repo;
private IIdentityServerConfiguration _identityServerConfig;
public FilePeersProvider(IOptions<FilePeers> options, IBaseUrlFinder finder, IInternalConfigurationRepository repo, IIdentityServerConfiguration identityServerConfig)
{
_identityServerConfig = identityServerConfig;
_repo = repo;
_finder = finder;
_options = options;
_peers = new List<IPeer>();
var config = _repo.Get();
foreach (var item in _options.Value.Peers)
{
var httpClient = new HttpClient();
//todo what if this errors?
var httpPeer = new HttpPeer(item.HostAndPort, httpClient, _finder, config.Data, _identityServerConfig);
_peers.Add(httpPeer);
}
}
public List<IPeer> Get()
{
return _peers;
}
}
}

View File

@ -1,131 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Ocelot.Configuration;
using Ocelot.Middleware;
using Rafty.Concensus;
using Rafty.FiniteStateMachine;
namespace Ocelot.Raft
{
using Rafty.Concensus.Messages;
using Rafty.Concensus.Peers;
using Rafty.Infrastructure;
[ExcludeFromCoverage]
public class HttpPeer : IPeer
{
private readonly string _hostAndPort;
private readonly HttpClient _httpClient;
private readonly JsonSerializerSettings _jsonSerializerSettings;
private readonly string _baseSchemeUrlAndPort;
private BearerToken _token;
private readonly IInternalConfiguration _config;
private readonly IIdentityServerConfiguration _identityServerConfiguration;
public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IInternalConfiguration config, IIdentityServerConfiguration identityServerConfiguration)
{
_identityServerConfiguration = identityServerConfiguration;
_config = config;
Id = hostAndPort;
_hostAndPort = hostAndPort;
_httpClient = httpClient;
_jsonSerializerSettings = new JsonSerializerSettings() {
TypeNameHandling = TypeNameHandling.All
};
_baseSchemeUrlAndPort = finder.Find();
}
public string Id { get; }
public async Task<RequestVoteResponse> Request(RequestVote requestVote)
{
if(_token == null)
{
await 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 = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content);
if(response.IsSuccessStatusCode)
{
return JsonConvert.DeserializeObject<RequestVoteResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
}
return new RequestVoteResponse(false, requestVote.Term);
}
public async Task<AppendEntriesResponse> Request(AppendEntries appendEntries)
{
try
{
if(_token == null)
{
await 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 = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content);
if(response.IsSuccessStatusCode)
{
return JsonConvert.DeserializeObject<AppendEntriesResponse>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
}
return new AppendEntriesResponse(appendEntries.Term, false);
}
catch(Exception ex)
{
Console.WriteLine(ex);
return new AppendEntriesResponse(appendEntries.Term, false);
}
}
public async Task<Response<T>> Request<T>(T command)
where T : ICommand
{
Console.WriteLine("SENDING REQUEST....");
if(_token == null)
{
await 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 = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content);
if(response.IsSuccessStatusCode)
{
Console.WriteLine("REQUEST OK....");
var okResponse = JsonConvert.DeserializeObject<OkResponse<ICommand>>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
return new OkResponse<T>((T)okResponse.Command);
}
Console.WriteLine("REQUEST NOT OK....");
return new ErrorResponse<T>(await response.Content.ReadAsStringAsync(), command);
}
private async Task 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 = await _httpClient.PostAsync(tokenUrl, content);
var responseContent = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode();
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken);
}
}
}

View File

@ -1,26 +0,0 @@
using System.Threading.Tasks;
using Ocelot.Configuration.Setter;
using Rafty.FiniteStateMachine;
using Rafty.Log;
namespace Ocelot.Raft
{
[ExcludeFromCoverage]
public class OcelotFiniteStateMachine : IFiniteStateMachine
{
private readonly IFileConfigurationSetter _setter;
public OcelotFiniteStateMachine(IFileConfigurationSetter setter)
{
_setter = setter;
}
public async Task 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;
await _setter.Set(hack.Configuration);
}
}
}

View File

@ -1,101 +0,0 @@
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.Middleware;
using Ocelot.Raft;
using Rafty.Concensus;
using Rafty.FiniteStateMachine;
namespace Ocelot.Raft
{
using Rafty.Concensus.Messages;
using Rafty.Concensus.Node;
[ExcludeFromCoverage]
[Authorize]
[Route("raft")]
public class RaftController : Controller
{
private readonly INode _node;
private readonly IOcelotLogger _logger;
private readonly string _baseSchemeUrlAndPort;
private readonly JsonSerializerSettings _jsonSerialiserSettings;
public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IBaseUrlFinder finder)
{
_jsonSerialiserSettings = new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.All
};
_baseSchemeUrlAndPort = finder.Find();
_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 = await _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 = await _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 = await _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;
}
}
}
}

View File

@ -1,335 +0,0 @@
namespace Ocelot.Raft
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Rafty.Infrastructure;
using Rafty.Log;
[ExcludeFromCoverage]
public class SqlLiteLog : ILog
{
private readonly string _path;
private readonly SemaphoreSlim _sempaphore = new SemaphoreSlim(1, 1);
private readonly ILogger _logger;
private readonly NodeId _nodeId;
public SqlLiteLog(NodeId nodeId, ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<SqlLiteLog>();
_nodeId = nodeId;
_path = $"{nodeId.Id.Replace("/", "").Replace(":", "")}.db";
_sempaphore.Wait();
if (!File.Exists(_path))
{
var fs = File.Create(_path);
fs.Dispose();
using (var connection = new SqliteConnection($"Data Source={_path};"))
{
connection.Open();
const string sql = @"create table logs (
id integer primary key,
data text not null
)";
using (var command = new SqliteCommand(sql, connection))
{
var result = command.ExecuteNonQuery();
_logger.LogInformation(result == 0
? $"id: {_nodeId.Id} create database, result: {result}"
: $"id: {_nodeId.Id} did not create database., result: {result}");
}
}
}
_sempaphore.Release();
}
public async Task<int> LastLogIndex()
{
_sempaphore.Wait();
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(await command.ExecuteScalarAsync());
if (index > result)
{
result = index;
}
}
}
_sempaphore.Release();
return result;
}
public async Task<long> LastLogTerm()
{
_sempaphore.Wait();
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(await command.ExecuteScalarAsync());
var jsonSerializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
};
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
if (log != null && log.Term > result)
{
result = log.Term;
}
}
}
_sempaphore.Release();
return result;
}
public async Task<int> Count()
{
_sempaphore.Wait();
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(await command.ExecuteScalarAsync());
if (index > result)
{
result = index;
}
}
}
_sempaphore.Release();
return result;
}
public async Task<int> Apply(LogEntry log)
{
_sempaphore.Wait();
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}')";
_logger.LogInformation($"id: {_nodeId.Id}, sql: {sql}");
using (var command = new SqliteCommand(sql, connection))
{
var result = await command.ExecuteNonQueryAsync();
_logger.LogInformation($"id: {_nodeId.Id}, insert log result: {result}");
}
sql = "select last_insert_rowid()";
using (var command = new SqliteCommand(sql, connection))
{
var result = await command.ExecuteScalarAsync();
_logger.LogInformation($"id: {_nodeId.Id}, about to release semaphore");
_sempaphore.Release();
_logger.LogInformation($"id: {_nodeId.Id}, saved log to sqlite");
return Convert.ToInt32(result);
}
}
}
public async Task DeleteConflictsFromThisLog(int index, LogEntry logEntry)
{
_sempaphore.Wait();
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};";
_logger.LogInformation($"id: {_nodeId.Id} sql: {sql}");
using (var command = new SqliteCommand(sql, connection))
{
var data = Convert.ToString(await command.ExecuteScalarAsync());
var jsonSerializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
};
_logger.LogInformation($"id {_nodeId.Id} got log for index: {index}, data is {data} and new log term is {logEntry.Term}");
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};";
_logger.LogInformation($"id: {_nodeId.Id} sql: {deleteSql}");
using (var deleteCommand = new SqliteCommand(deleteSql, connection))
{
var result = await deleteCommand.ExecuteNonQueryAsync();
}
}
}
}
_sempaphore.Release();
}
public async Task<bool> IsDuplicate(int index, LogEntry logEntry)
{
_sempaphore.Wait();
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(await command.ExecuteScalarAsync());
var jsonSerializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
};
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
if (logEntry != null && log != null && logEntry.Term == log.Term)
{
_sempaphore.Release();
return true;
}
}
}
_sempaphore.Release();
return false;
}
public async Task<LogEntry> Get(int index)
{
_sempaphore.Wait();
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(await command.ExecuteScalarAsync());
var jsonSerializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
};
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
_sempaphore.Release();
return log;
}
}
}
public async Task<List<(int index, LogEntry logEntry)>> GetFrom(int index)
{
_sempaphore.Wait();
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 = await command.ExecuteReaderAsync())
{
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));
}
}
}
_sempaphore.Release();
return logsToReturn;
}
}
public async Task<long> GetTermAtIndex(int index)
{
_sempaphore.Wait();
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(await command.ExecuteScalarAsync());
var jsonSerializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
};
var log = JsonConvert.DeserializeObject<LogEntry>(data, jsonSerializerSettings);
if (log != null && log.Term > result)
{
result = log.Term;
}
}
}
_sempaphore.Release();
return result;
}
public async Task Remove(int indexOfCommand)
{
_sempaphore.Wait();
using (var connection = new SqliteConnection($"Data Source={_path};"))
{
connection.Open();
//todo - sql injection dont copy this..
var deleteSql = $"delete from logs where id >= {indexOfCommand};";
_logger.LogInformation($"id: {_nodeId.Id} Remove {deleteSql}");
using (var deleteCommand = new SqliteCommand(deleteSql, connection))
{
var result = await deleteCommand.ExecuteNonQueryAsync();
}
}
_sempaphore.Release();
}
}
}

View File

@ -1,15 +0,0 @@
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;}
}
}

View File

@ -38,15 +38,19 @@
private async Task<HttpContent> MapContent(HttpRequest request) private async Task<HttpContent> MapContent(HttpRequest request)
{ {
if (request.Body == null) if (request.Body == null || (request.Body.CanSeek && request.Body.Length <= 0))
{ {
return null; return null;
} }
// Never change this to StreamContent again, I forgot it doesnt work in #464.
var content = new ByteArrayContent(await ToByteArray(request.Body)); var content = new ByteArrayContent(await ToByteArray(request.Body));
if(!string.IsNullOrEmpty(request.ContentType))
{
content.Headers content.Headers
.TryAddWithoutValidation("Content-Type", new[] {request.ContentType}); .TryAddWithoutValidation("Content-Type", new[] {request.ContentType});
}
AddHeaderIfExistsOnRequest("Content-Language", content, request); AddHeaderIfExistsOnRequest("Content-Language", content, request);
AddHeaderIfExistsOnRequest("Content-Location", content, request); AddHeaderIfExistsOnRequest("Content-Location", content, request);
@ -88,9 +92,14 @@
} }
} }
private bool IsSupportedHeader(KeyValuePair<string, StringValues> header)
{
return !_unsupportedHeaders.Contains(header.Key.ToLower());
}
private async Task<byte[]> ToByteArray(Stream stream) private async Task<byte[]> ToByteArray(Stream stream)
{ {
using (stream) using(stream)
{ {
using (var memStream = new MemoryStream()) using (var memStream = new MemoryStream())
{ {
@ -99,10 +108,5 @@
} }
} }
} }
private bool IsSupportedHeader(KeyValuePair<string, StringValues> header)
{
return !_unsupportedHeaders.Contains(header.Key.ToLower());
}
} }
} }

View File

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using Butterfly.Client.Tracing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Logging; using Ocelot.Logging;

View File

@ -1,17 +0,0 @@
using Butterfly.Client.Tracing;
using Butterfly.OpenTracing;
namespace Ocelot.Requester
{
public class FakeServiceTracer : IServiceTracer
{
public ITracer Tracer { get; }
public string ServiceName { get; }
public string Environment { get; }
public string Identity { get; }
public ISpan Start(ISpanBuilder spanBuilder)
{
return null;
}
}
}

View File

@ -1,21 +1,19 @@
using System; namespace Ocelot.Requester
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Butterfly.Client.Tracing;
using Butterfly.OpenTracing;
using Ocelot.Infrastructure.RequestData;
namespace Ocelot.Requester
{ {
using Logging;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Ocelot.Infrastructure.RequestData;
public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler
{ {
private readonly IServiceTracer _tracer; private readonly ITracer _tracer;
private readonly IRequestScopedDataRepository _repo; private readonly IRequestScopedDataRepository _repo;
private const string PrefixSpanId = "ot-spanId";
public OcelotHttpTracingHandler( public OcelotHttpTracingHandler(
IServiceTracer tracer, ITracer tracer,
IRequestScopedDataRepository repo, IRequestScopedDataRepository repo,
HttpMessageHandler httpMessageHandler = null) HttpMessageHandler httpMessageHandler = null)
{ {
@ -28,46 +26,8 @@ namespace Ocelot.Requester
HttpRequestMessage request, HttpRequestMessage request,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
return _tracer.ChildTraceAsync($"httpclient {request.Method}", DateTimeOffset.UtcNow, span => TracingSendAsync(span, request, cancellationToken));
}
protected virtual async Task<HttpResponseMessage> TracingSendAsync( return _tracer.SendAsync(request, cancellationToken, x => _repo.Add("TraceId", x), (r,c) => base.SendAsync(r, c));
ISpan span,
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.Headers.Contains(PrefixSpanId))
{
request.Headers.Remove(PrefixSpanId);
request.Headers.TryAddWithoutValidation(PrefixSpanId, span.SpanContext.SpanId);
}
_repo.Add("TraceId", span.SpanContext.TraceId);
span.Tags.Client().Component("HttpClient")
.HttpMethod(request.Method.Method)
.HttpUrl(request.RequestUri.OriginalString)
.HttpHost(request.RequestUri.Host)
.HttpPath(request.RequestUri.PathAndQuery)
.PeerAddress(request.RequestUri.OriginalString)
.PeerHostName(request.RequestUri.Host)
.PeerPort(request.RequestUri.Port);
_tracer.Tracer.Inject(span.SpanContext, request.Headers, (c, k, v) =>
{
if (!c.Contains(k))
{
c.Add(k, v);
}
});
span.Log(LogField.CreateNew().ClientSend());
var responseMessage = await base.SendAsync(request, cancellationToken);
span.Log(LogField.CreateNew().ClientReceive());
return responseMessage;
} }
} }
} }

View File

@ -1,19 +1,21 @@
using Butterfly.Client.Tracing;
using Ocelot.Infrastructure.RequestData;
namespace Ocelot.Requester namespace Ocelot.Requester
{ {
using System;
using Logging;
using Ocelot.Infrastructure.RequestData;
using Microsoft.Extensions.DependencyInjection;
public class TracingHandlerFactory : ITracingHandlerFactory public class TracingHandlerFactory : ITracingHandlerFactory
{ {
private readonly IServiceTracer _tracer; private readonly ITracer _tracer;
private readonly IRequestScopedDataRepository _repo; private readonly IRequestScopedDataRepository _repo;
public TracingHandlerFactory( public TracingHandlerFactory(
IServiceTracer tracer, IServiceProvider services,
IRequestScopedDataRepository repo) IRequestScopedDataRepository repo)
{ {
_repo = repo; _repo = repo;
_tracer = tracer; _tracer = services.GetService<ITracer>();
} }
public ITracingHandler Get() public ITracingHandler Get()

View File

@ -36,7 +36,7 @@ namespace Ocelot.Responder
AddHeaderIfDoesntExist(context, new Header(httpResponseHeader.Key, httpResponseHeader.Value)); AddHeaderIfDoesntExist(context, new Header(httpResponseHeader.Key, httpResponseHeader.Value));
} }
var content = await response.Content.ReadAsByteArrayAsync(); var content = await response.Content.ReadAsStreamAsync();
AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ content.Length.ToString() }) ); AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ content.Length.ToString() }) );
@ -49,11 +49,11 @@ namespace Ocelot.Responder
return Task.CompletedTask; return Task.CompletedTask;
}, context); }, context);
using (Stream stream = new MemoryStream(content)) using(content)
{ {
if (response.StatusCode != HttpStatusCode.NotModified && context.Response.ContentLength != 0) if (response.StatusCode != HttpStatusCode.NotModified && context.Response.ContentLength != 0)
{ {
await stream.CopyToAsync(context.Response.Body); await content.CopyToAsync(context.Response.Body);
} }
} }
} }

View File

@ -1,18 +0,0 @@
namespace Ocelot.ServiceDiscovery.Configuration
{
public class ConsulRegistryConfiguration
{
public ConsulRegistryConfiguration(string host, int port, string keyOfServiceInConsul, string token)
{
Host = string.IsNullOrEmpty(host) ? "localhost" : host;
Port = port > 0 ? port : 8500;
KeyOfServiceInConsul = keyOfServiceInConsul;
Token = token;
}
public string KeyOfServiceInConsul { get; }
public string Host { get; }
public int Port { get; }
public string Token { get; }
}
}

View File

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

View File

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

View File

@ -1,34 +0,0 @@
namespace Ocelot.ServiceDiscovery.Providers
{
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Steeltoe.Common.Discovery;
using Values;
public class EurekaServiceDiscoveryProvider : IServiceDiscoveryProvider
{
private readonly IDiscoveryClient _client;
private readonly string _serviceName;
public EurekaServiceDiscoveryProvider(string serviceName, IDiscoveryClient client)
{
_client = client;
_serviceName = serviceName;
}
public Task<List<Service>> Get()
{
var services = new List<Service>();
var instances = _client.GetInstances(_serviceName);
if (instances != null && instances.Any())
{
services.AddRange(instances.Select(i => new Service(i.ServiceId, new ServiceHostAndPort(i.Host, i.Port), "", "", new List<string>())));
}
return Task.FromResult(services);
}
}
}

View File

@ -1,27 +0,0 @@
namespace Ocelot.ServiceDiscovery.Providers
{
using System.Collections.Generic;
using System.Threading.Tasks;
using Steeltoe.Common.Discovery;
public class FakeEurekaDiscoveryClient : IDiscoveryClient
{
public IServiceInstance GetLocalServiceInstance()
{
throw new System.NotImplementedException();
}
public IList<IServiceInstance> GetInstances(string serviceId)
{
throw new System.NotImplementedException();
}
public Task ShutdownAsync()
{
throw new System.NotImplementedException();
}
public string Description { get; }
public IList<string> Services { get; }
}
}

View File

@ -1,56 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Consul;
using Ocelot.Infrastructure.Consul;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Logging;
using Ocelot.ServiceDiscovery.Configuration;
using Ocelot.Values;
namespace Ocelot.ServiceDiscovery.Providers
{
public class PollingConsulServiceDiscoveryProvider : IServiceDiscoveryProvider
{
private readonly IOcelotLogger _logger;
private readonly IServiceDiscoveryProvider _consulServiceDiscoveryProvider;
private readonly Timer _timer;
private bool _polling;
private List<Service> _services;
private string _keyOfServiceInConsul;
public PollingConsulServiceDiscoveryProvider(int pollingInterval, string keyOfServiceInConsul, IOcelotLoggerFactory factory, IServiceDiscoveryProvider consulServiceDiscoveryProvider)
{;
_logger = factory.CreateLogger<PollingConsulServiceDiscoveryProvider>();
_keyOfServiceInConsul = keyOfServiceInConsul;
_consulServiceDiscoveryProvider = consulServiceDiscoveryProvider;
_services = new List<Service>();
_timer = new Timer(async x =>
{
if(_polling)
{
return;
}
_polling = true;
await Poll();
_polling = false;
}, null, pollingInterval, pollingInterval);
}
public Task<List<Service>> Get()
{
return Task.FromResult(_services);
}
private async Task Poll()
{
_services = await _consulServiceDiscoveryProvider.Get();
}
}
}

View File

@ -0,0 +1,8 @@
namespace Ocelot.ServiceDiscovery
{
using System;
using Ocelot.Configuration;
using Providers;
public delegate IServiceDiscoveryProvider ServiceDiscoveryFinderDelegate(IServiceProvider provider, ServiceProviderConfiguration config, string key);
}

View File

@ -1,26 +1,28 @@
using System.Collections.Generic;
using Ocelot.Configuration;
using Ocelot.Infrastructure.Consul;
using Ocelot.Logging;
using Ocelot.ServiceDiscovery.Configuration;
using Ocelot.ServiceDiscovery.Providers;
using Ocelot.Values;
namespace Ocelot.ServiceDiscovery namespace Ocelot.ServiceDiscovery
{ {
using Steeltoe.Common.Discovery; using System.Collections.Generic;
using Ocelot.Configuration;
using Ocelot.Logging;
using Ocelot.ServiceDiscovery.Configuration;
using Ocelot.ServiceDiscovery.Providers;
using Ocelot.Values;
using System;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory
{ {
private readonly IOcelotLoggerFactory _factory; private readonly IOcelotLoggerFactory _factory;
private readonly IConsulClientFactory _consulFactory; private readonly List<ServiceDiscoveryFinderDelegate> _delegates;
private readonly IDiscoveryClient _eurekaClient; private readonly IServiceProvider _provider;
public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IConsulClientFactory consulFactory, IDiscoveryClient eurekaClient) public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IServiceProvider provider)
{ {
_factory = factory; _factory = factory;
_consulFactory = consulFactory; _provider = provider;
_eurekaClient = eurekaClient; _delegates = provider
.GetServices<ServiceDiscoveryFinderDelegate>()
.ToList();
} }
public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute) public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute)
@ -42,29 +44,24 @@ namespace Ocelot.ServiceDiscovery
return new ConfigurationServiceProvider(services); return new ConfigurationServiceProvider(services);
} }
private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration serviceConfig, string serviceName) private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration config, string key)
{ {
if (serviceConfig.Type?.ToLower() == "servicefabric") if (config.Type?.ToLower() == "servicefabric")
{ {
var config = new ServiceFabricConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName); var sfConfig = new ServiceFabricConfiguration(config.Host, config.Port, key);
return new ServiceFabricServiceDiscoveryProvider(config); return new ServiceFabricServiceDiscoveryProvider(sfConfig);
} }
if (serviceConfig.Type?.ToLower() == "eureka") foreach (var serviceDiscoveryFinderDelegate in _delegates)
{ {
return new EurekaServiceDiscoveryProvider(serviceName, _eurekaClient); var provider = serviceDiscoveryFinderDelegate?.Invoke(_provider, config, key);
if (provider != null)
{
return provider;
}
} }
var consulRegistryConfiguration = new ConsulRegistryConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName, serviceConfig.Token); return null;
var consulServiceDiscoveryProvider = new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory, _consulFactory);
if (serviceConfig.Type?.ToLower() == "pollconsul")
{
return new PollingConsulServiceDiscoveryProvider(serviceConfig.PollingInterval, consulRegistryConfiguration.KeyOfServiceInConsul, _factory, consulServiceDiscoveryProvider);
}
return consulServiceDiscoveryProvider;
} }
} }
} }

View File

@ -14,7 +14,7 @@ namespace Ocelot.WebSockets.Middleware
public WebSocketsProxyMiddleware(OcelotRequestDelegate next, public WebSocketsProxyMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory) IOcelotLoggerFactory loggerFactory)
:base(loggerFactory.CreateLogger<WebSocketsProxyMiddleware>()) : base(loggerFactory.CreateLogger<WebSocketsProxyMiddleware>())
{ {
_next = next; _next = next;
} }
@ -29,6 +29,17 @@ namespace Ocelot.WebSockets.Middleware
var wsToUpstreamClient = await context.WebSockets.AcceptWebSocketAsync(); var wsToUpstreamClient = await context.WebSockets.AcceptWebSocketAsync();
var wsToDownstreamService = new ClientWebSocket(); var wsToDownstreamService = new ClientWebSocket();
foreach (var requestHeader in context.Request.Headers)
{
// Do not copy the Sec-Websocket headers because it is specified by the own connection it will fail when you copy this one.
if (requestHeader.Key.StartsWith("Sec-WebSocket"))
{
continue;
}
wsToDownstreamService.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
}
var uri = new Uri(serverEndpoint); var uri = new Uri(serverEndpoint);
await wsToDownstreamService.ConnectAsync(uri, CancellationToken.None); await wsToDownstreamService.ConnectAsync(uri, CancellationToken.None);

View File

@ -20,14 +20,14 @@ namespace Ocelot.AcceptanceTests
{ {
public class AggregateTests : IDisposable public class AggregateTests : IDisposable
{ {
private IWebHost _serviceOneBuilder;
private IWebHost _serviceTwoBuilder;
private readonly Steps _steps; private readonly Steps _steps;
private string _downstreamPathOne; private string _downstreamPathOne;
private string _downstreamPathTwo; private string _downstreamPathTwo;
private readonly ServiceHandler _serviceHandler;
public AggregateTests() public AggregateTests()
{ {
_serviceHandler = new ServiceHandler();
_steps = new Steps(); _steps = new Steps();
} }
@ -116,7 +116,7 @@ namespace Ocelot.AcceptanceTests
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51885, Port = 51875,
} }
}, },
UpstreamPathTemplate = "/laura", UpstreamPathTemplate = "/laura",
@ -157,7 +157,7 @@ namespace Ocelot.AcceptanceTests
var expected = "{\"Laura\":{Hello from Laura},\"Tom\":{Hello from Tom}}"; var expected = "{\"Laura\":{Hello from Laura},\"Tom\":{Hello from Tom}}";
this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51885", "/", 200, "{Hello from Laura}")) this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51875", "/", 200, "{Hello from Laura}"))
.Given(x => x.GivenServiceTwoIsRunning("http://localhost:51886", "/", 200, "{Hello from Tom}")) .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51886", "/", 200, "{Hello from Tom}"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
@ -370,19 +370,11 @@ namespace Ocelot.AcceptanceTests
private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody)
{ {
_serviceOneBuilder = new WebHostBuilder() _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.Configure(app =>
{
app.UsePathBase(basePath);
app.Run(async context =>
{ {
_downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; _downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
if(_downstreamPathOne != basePath) if (_downstreamPathOne != basePath)
{ {
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
await context.Response.WriteAsync("downstream path didnt match base path"); await context.Response.WriteAsync("downstream path didnt match base path");
@ -393,27 +385,15 @@ namespace Ocelot.AcceptanceTests
await context.Response.WriteAsync(responseBody); await context.Response.WriteAsync(responseBody);
} }
}); });
})
.Build();
_serviceOneBuilder.Start();
} }
private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody)
{ {
_serviceTwoBuilder = new WebHostBuilder() _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.Configure(app =>
{
app.UsePathBase(basePath);
app.Run(async context =>
{ {
_downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; _downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
if(_downstreamPathTwo != basePath) if (_downstreamPathTwo != basePath)
{ {
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
await context.Response.WriteAsync("downstream path didnt match base path"); await context.Response.WriteAsync("downstream path didnt match base path");
@ -424,10 +404,6 @@ namespace Ocelot.AcceptanceTests
await context.Response.WriteAsync(responseBody); await context.Response.WriteAsync(responseBody);
} }
}); });
})
.Build();
_serviceTwoBuilder.Start();
} }
internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath) internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath)
@ -438,8 +414,7 @@ namespace Ocelot.AcceptanceTests
public void Dispose() public void Dispose()
{ {
_serviceOneBuilder?.Dispose(); _serviceHandler.Dispose();
_serviceTwoBuilder?.Dispose();
_steps.Dispose(); _steps.Dispose();
} }
} }

View File

@ -1,37 +1,36 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Claims;
using IdentityServer4.AccessTokenValidation;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration.File;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.AcceptanceTests namespace Ocelot.AcceptanceTests
{ {
using IdentityServer4.Test; using IdentityServer4.Test;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Claims;
using IdentityServer4.AccessTokenValidation;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration.File;
using TestStack.BDDfy;
using Xunit;
public class AuthenticationTests : IDisposable public class AuthenticationTests : IDisposable
{ {
private IWebHost _servicebuilder;
private readonly Steps _steps; private readonly Steps _steps;
private IWebHost _identityServerBuilder; private IWebHost _identityServerBuilder;
private string _identityServerRootUrl = "http://localhost:51888"; private string _identityServerRootUrl = "http://localhost:51888";
private string _downstreamServicePath = "/"; private string _downstreamServicePath = "/";
private string _downstreamServiceHost = "localhost"; private string _downstreamServiceHost = "localhost";
private int _downstreamServicePort = 51876;
private string _downstreamServiceScheme = "http"; private string _downstreamServiceScheme = "http";
private string _downstreamServiceUrl = "http://localhost:51876"; private string _downstreamServiceUrl = "http://localhost:";
private readonly Action<IdentityServerAuthenticationOptions> _options; private readonly Action<IdentityServerAuthenticationOptions> _options;
private readonly ServiceHandler _serviceHandler;
public AuthenticationTests() public AuthenticationTests()
{ {
_serviceHandler = new ServiceHandler();
_steps = new Steps(); _steps = new Steps();
_options = o => _options = o =>
{ {
@ -46,6 +45,8 @@ namespace Ocelot.AcceptanceTests
[Fact] [Fact]
public void should_return_401_using_identity_server_access_token() public void should_return_401_using_identity_server_access_token()
{ {
int port = 54329;
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -58,7 +59,7 @@ namespace Ocelot.AcceptanceTests
new FileHostAndPort new FileHostAndPort
{ {
Host =_downstreamServiceHost, Host =_downstreamServiceHost,
Port = _downstreamServicePort, Port = port,
} }
}, },
DownstreamScheme = _downstreamServiceScheme, DownstreamScheme = _downstreamServiceScheme,
@ -73,7 +74,7 @@ namespace Ocelot.AcceptanceTests
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt))
.And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => x.GivenThereIsAServiceRunningOn($"{_downstreamServiceUrl}{port}", 201, string.Empty))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenOcelotIsRunning(_options, "Test"))
.And(x => _steps.GivenThePostHasContent("postContent")) .And(x => _steps.GivenThePostHasContent("postContent"))
@ -85,6 +86,8 @@ namespace Ocelot.AcceptanceTests
[Fact] [Fact]
public void should_return_response_200_using_identity_server() public void should_return_response_200_using_identity_server()
{ {
int port = 54099;
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -97,7 +100,7 @@ namespace Ocelot.AcceptanceTests
new FileHostAndPort new FileHostAndPort
{ {
Host =_downstreamServiceHost, Host =_downstreamServiceHost,
Port = _downstreamServicePort, Port = port,
} }
}, },
DownstreamScheme = _downstreamServiceScheme, DownstreamScheme = _downstreamServiceScheme,
@ -112,7 +115,7 @@ namespace Ocelot.AcceptanceTests
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt))
.And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) .And(x => x.GivenThereIsAServiceRunningOn($"{_downstreamServiceUrl}{port}", 200, "Hello from Laura"))
.And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenOcelotIsRunning(_options, "Test"))
@ -126,6 +129,8 @@ namespace Ocelot.AcceptanceTests
[Fact] [Fact]
public void should_return_response_401_using_identity_server_with_token_requested_for_other_api() public void should_return_response_401_using_identity_server_with_token_requested_for_other_api()
{ {
int port = 54196;
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -138,7 +143,7 @@ namespace Ocelot.AcceptanceTests
new FileHostAndPort new FileHostAndPort
{ {
Host =_downstreamServiceHost, Host =_downstreamServiceHost,
Port = _downstreamServicePort, Port = port,
} }
}, },
DownstreamScheme = _downstreamServiceScheme, DownstreamScheme = _downstreamServiceScheme,
@ -153,7 +158,7 @@ namespace Ocelot.AcceptanceTests
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt))
.And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) .And(x => x.GivenThereIsAServiceRunningOn($"{_downstreamServiceUrl}{port}", 200, "Hello from Laura"))
.And(x => _steps.GivenIHaveATokenForApi2(_identityServerRootUrl)) .And(x => _steps.GivenIHaveATokenForApi2(_identityServerRootUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenOcelotIsRunning(_options, "Test"))
@ -166,6 +171,8 @@ namespace Ocelot.AcceptanceTests
[Fact] [Fact]
public void should_return_201_using_identity_server_access_token() public void should_return_201_using_identity_server_access_token()
{ {
int port = 52226;
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -178,7 +185,7 @@ namespace Ocelot.AcceptanceTests
new FileHostAndPort new FileHostAndPort
{ {
Host =_downstreamServiceHost, Host =_downstreamServiceHost,
Port = _downstreamServicePort, Port = port,
} }
}, },
DownstreamScheme = _downstreamServiceScheme, DownstreamScheme = _downstreamServiceScheme,
@ -193,7 +200,7 @@ namespace Ocelot.AcceptanceTests
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt))
.And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => x.GivenThereIsAServiceRunningOn($"{_downstreamServiceUrl}{port}", 201, string.Empty))
.And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenOcelotIsRunning(_options, "Test"))
@ -207,6 +214,8 @@ namespace Ocelot.AcceptanceTests
[Fact] [Fact]
public void should_return_201_using_identity_server_reference_token() public void should_return_201_using_identity_server_reference_token()
{ {
int port = 52222;
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -219,7 +228,7 @@ namespace Ocelot.AcceptanceTests
new FileHostAndPort new FileHostAndPort
{ {
Host =_downstreamServiceHost, Host =_downstreamServiceHost,
Port = _downstreamServicePort, Port = port,
} }
}, },
DownstreamScheme = _downstreamServiceScheme, DownstreamScheme = _downstreamServiceScheme,
@ -234,7 +243,7 @@ namespace Ocelot.AcceptanceTests
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Reference)) this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Reference))
.And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => x.GivenThereIsAServiceRunningOn($"{_downstreamServiceUrl}{port}", 201, string.Empty))
.And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenOcelotIsRunning(_options, "Test"))
@ -247,23 +256,11 @@ namespace Ocelot.AcceptanceTests
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody)
{ {
_servicebuilder = new WebHostBuilder() _serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{ {
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody); await context.Response.WriteAsync(responseBody);
}); });
})
.Build();
_servicebuilder.Start();
} }
private void GivenThereIsAnIdentityServerOn(string url, string apiName, string api2Name, AccessTokenType tokenType) private void GivenThereIsAnIdentityServerOn(string url, string apiName, string api2Name, AccessTokenType tokenType)
@ -371,7 +368,7 @@ namespace Ocelot.AcceptanceTests
public void Dispose() public void Dispose()
{ {
_servicebuilder?.Dispose(); _serviceHandler.Dispose();
_steps.Dispose(); _steps.Dispose();
_identityServerBuilder?.Dispose(); _identityServerBuilder?.Dispose();
} }

View File

@ -1,33 +1,32 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Claims;
using IdentityServer4.AccessTokenValidation;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration.File;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.AcceptanceTests namespace Ocelot.AcceptanceTests
{ {
using IdentityServer4; using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Claims;
using IdentityServer4.AccessTokenValidation;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration.File;
using TestStack.BDDfy;
using Xunit;
using IdentityServer4.Test; using IdentityServer4.Test;
public class AuthorisationTests : IDisposable public class AuthorisationTests : IDisposable
{ {
private IWebHost _servicebuilder;
private IWebHost _identityServerBuilder; private IWebHost _identityServerBuilder;
private readonly Steps _steps; private readonly Steps _steps;
private Action<IdentityServerAuthenticationOptions> _options; private readonly Action<IdentityServerAuthenticationOptions> _options;
private string _identityServerRootUrl = "http://localhost:51888"; private string _identityServerRootUrl = "http://localhost:51888";
private readonly ServiceHandler _serviceHandler;
public AuthorisationTests() public AuthorisationTests()
{ {
_serviceHandler = new ServiceHandler();
_steps = new Steps(); _steps = new Steps();
_options = o => _options = o =>
{ {
@ -42,6 +41,8 @@ namespace Ocelot.AcceptanceTests
[Fact] [Fact]
public void should_return_response_200_authorising_route() public void should_return_response_200_authorising_route()
{ {
int port = 52875;
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -54,7 +55,7 @@ namespace Ocelot.AcceptanceTests
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51876, Port = port,
} }
}, },
DownstreamScheme = "http", DownstreamScheme = "http",
@ -86,7 +87,7 @@ namespace Ocelot.AcceptanceTests
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) .And(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", 200, "Hello from Laura"))
.And(x => _steps.GivenIHaveAToken("http://localhost:51888")) .And(x => _steps.GivenIHaveAToken("http://localhost:51888"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenOcelotIsRunning(_options, "Test"))
@ -100,6 +101,8 @@ namespace Ocelot.AcceptanceTests
[Fact] [Fact]
public void should_return_response_403_authorising_route() public void should_return_response_403_authorising_route()
{ {
int port = 59471;
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -112,7 +115,7 @@ namespace Ocelot.AcceptanceTests
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51876, Port = port,
} }
}, },
DownstreamScheme = "http", DownstreamScheme = "http",
@ -143,7 +146,7 @@ namespace Ocelot.AcceptanceTests
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) .And(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", 200, "Hello from Laura"))
.And(x => _steps.GivenIHaveAToken("http://localhost:51888")) .And(x => _steps.GivenIHaveAToken("http://localhost:51888"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenOcelotIsRunning(_options, "Test"))
@ -156,6 +159,8 @@ namespace Ocelot.AcceptanceTests
[Fact] [Fact]
public void should_return_response_200_using_identity_server_with_allowed_scope() public void should_return_response_200_using_identity_server_with_allowed_scope()
{ {
int port = 63471;
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -168,7 +173,7 @@ namespace Ocelot.AcceptanceTests
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51876, Port = port,
} }
}, },
DownstreamScheme = "http", DownstreamScheme = "http",
@ -184,7 +189,7 @@ namespace Ocelot.AcceptanceTests
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) .And(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", 200, "Hello from Laura"))
.And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888")) .And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenOcelotIsRunning(_options, "Test"))
@ -197,6 +202,8 @@ namespace Ocelot.AcceptanceTests
[Fact] [Fact]
public void should_return_response_403_using_identity_server_with_scope_not_allowed() public void should_return_response_403_using_identity_server_with_scope_not_allowed()
{ {
int port = 60571;
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -209,7 +216,7 @@ namespace Ocelot.AcceptanceTests
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51876, Port = port,
} }
}, },
DownstreamScheme = "http", DownstreamScheme = "http",
@ -225,7 +232,7 @@ namespace Ocelot.AcceptanceTests
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) .And(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", 200, "Hello from Laura"))
.And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888")) .And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenOcelotIsRunning(_options, "Test"))
@ -238,6 +245,8 @@ namespace Ocelot.AcceptanceTests
[Fact] [Fact]
public void should_fix_issue_240() public void should_fix_issue_240()
{ {
int port = 61071;
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -250,7 +259,7 @@ namespace Ocelot.AcceptanceTests
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51876, Port = port,
} }
}, },
DownstreamScheme = "http", DownstreamScheme = "http",
@ -284,7 +293,7 @@ namespace Ocelot.AcceptanceTests
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt, users)) this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt, users))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) .And(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", 200, "Hello from Laura"))
.And(x => _steps.GivenIHaveAToken("http://localhost:51888")) .And(x => _steps.GivenIHaveAToken("http://localhost:51888"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenOcelotIsRunning(_options, "Test"))
@ -297,23 +306,11 @@ namespace Ocelot.AcceptanceTests
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody)
{ {
_servicebuilder = new WebHostBuilder() _serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{ {
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody); await context.Response.WriteAsync(responseBody);
}); });
})
.Build();
_servicebuilder.Start();
} }
private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType) private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType)
@ -465,7 +462,7 @@ namespace Ocelot.AcceptanceTests
public void Dispose() public void Dispose()
{ {
_servicebuilder?.Dispose(); _serviceHandler?.Dispose();
_steps.Dispose(); _steps.Dispose();
_identityServerBuilder?.Dispose(); _identityServerBuilder?.Dispose();
} }

View File

@ -1,284 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
using Butterfly.Client.AspNetCore;
using static Rafty.Infrastructure.Wait;
namespace Ocelot.AcceptanceTests
{
using Xunit.Abstractions;
public class ButterflyTracingTests : IDisposable
{
private IWebHost _serviceOneBuilder;
private IWebHost _serviceTwoBuilder;
private IWebHost _fakeButterfly;
private readonly Steps _steps;
private string _downstreamPathOne;
private string _downstreamPathTwo;
private int _butterflyCalled;
private readonly ITestOutputHelper _output;
public ButterflyTracingTests(ITestOutputHelper output)
{
_output = output;
_steps = new Steps();
}
[Fact]
public void should_forward_tracing_information_from_ocelot_and_downstream_services()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/api/values",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51887,
}
},
UpstreamPathTemplate = "/api001/values",
UpstreamHttpMethod = new List<string> { "Get" },
HttpHandlerOptions = new FileHttpHandlerOptions
{
UseTracing = true
},
QoSOptions = new FileQoSOptions
{
ExceptionsAllowedBeforeBreaking = 3,
DurationOfBreak = 10,
TimeoutValue = 5000
}
},
new FileReRoute
{
DownstreamPathTemplate = "/api/values",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51388,
}
},
UpstreamPathTemplate = "/api002/values",
UpstreamHttpMethod = new List<string> { "Get" },
HttpHandlerOptions = new FileHttpHandlerOptions
{
UseTracing = true
},
QoSOptions = new FileQoSOptions
{
ExceptionsAllowedBeforeBreaking = 3,
DurationOfBreak = 10,
TimeoutValue = 5000
}
}
}
};
var butterflyUrl = "http://localhost:9618";
this.Given(x => GivenFakeButterfly(butterflyUrl))
.And(x => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl))
.And(x => GivenServiceTwoIsRunning("http://localhost:51388", "/api/values", 200, "Hello from Tom", butterflyUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api002/values"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom"))
.BDDfy();
var commandOnAllStateMachines = WaitFor(10000).Until(() => _butterflyCalled >= 4);
_output.WriteLine($"_butterflyCalled is {_butterflyCalled}");
commandOnAllStateMachines.ShouldBeTrue();
}
[Fact]
public void should_return_tracing_header()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/api/values",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51387,
}
},
UpstreamPathTemplate = "/api001/values",
UpstreamHttpMethod = new List<string> { "Get" },
HttpHandlerOptions = new FileHttpHandlerOptions
{
UseTracing = true
},
QoSOptions = new FileQoSOptions
{
ExceptionsAllowedBeforeBreaking = 3,
DurationOfBreak = 10,
TimeoutValue = 5000
},
DownstreamHeaderTransform = new Dictionary<string, string>()
{
{"Trace-Id", "{TraceId}"},
{"Tom", "Laura"}
}
}
}
};
var butterflyUrl = "http://localhost:9618";
this.Given(x => GivenFakeButterfly(butterflyUrl))
.And(x => GivenServiceOneIsRunning("http://localhost:51387", "/api/values", 200, "Hello from Laura", butterflyUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => _steps.ThenTheTraceHeaderIsSet("Trace-Id"))
.And(x => _steps.ThenTheResponseHeaderIs("Tom", "Laura"))
.BDDfy();
}
private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl)
{
_serviceOneBuilder = new WebHostBuilder()
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.ConfigureServices(services => {
services.AddButterfly(option =>
{
option.CollectorUrl = butterflyUrl;
option.Service = "Service One";
option.IgnoredRoutesRegexPatterns = new string[0];
});
})
.Configure(app =>
{
app.UsePathBase(basePath);
app.Run(async context =>
{
_downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
if(_downstreamPathOne != 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();
_serviceOneBuilder.Start();
}
private void GivenFakeButterfly(string baseUrl)
{
_fakeButterfly = new WebHostBuilder()
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.Configure(app =>
{
app.Run(async context =>
{
_butterflyCalled++;
await context.Response.WriteAsync("OK...");
});
})
.Build();
_fakeButterfly.Start();
}
private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl)
{
_serviceTwoBuilder = new WebHostBuilder()
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.ConfigureServices(services => {
services.AddButterfly(option =>
{
option.CollectorUrl = butterflyUrl;
option.Service = "Service Two";
option.IgnoredRoutesRegexPatterns = new string[0];
});
})
.Configure(app =>
{
app.UsePathBase(basePath);
app.Run(async context =>
{
_downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
if(_downstreamPathTwo != 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();
_serviceTwoBuilder.Start();
}
internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath)
{
_downstreamPathOne.ShouldBe(expectedDownstreamPathOne);
_downstreamPathTwo.ShouldBe(expectedDownstreamPath);
}
public void Dispose()
{
_serviceOneBuilder?.Dispose();
_serviceTwoBuilder?.Dispose();
_fakeButterfly?.Dispose();
_steps.Dispose();
}
}
}

View File

@ -1,137 +0,0 @@
using CacheManager.Core;
using CacheManager.Core.Internal;
using CacheManager.Core.Logging;
using System;
using System.Collections.Concurrent;
using System.Linq;
using static CacheManager.Core.Utility.Guard;
namespace Ocelot.AcceptanceTests.Caching
{
public class InMemoryJsonHandle<TCacheValue> : BaseCacheHandle<TCacheValue>
{
private readonly ICacheSerializer _serializer;
private readonly ConcurrentDictionary<string, Tuple<Type, byte[]>> _cache;
public InMemoryJsonHandle(
ICacheManagerConfiguration managerConfiguration,
CacheHandleConfiguration configuration,
ICacheSerializer serializer,
ILoggerFactory loggerFactory) : base(managerConfiguration, configuration)
{
_cache = new ConcurrentDictionary<string, Tuple<Type, byte[]>>();
_serializer = serializer;
Logger = loggerFactory.CreateLogger(this);
}
public override int Count => _cache.Count;
protected override ILogger Logger { get; }
public override void Clear() => _cache.Clear();
public override void ClearRegion(string region)
{
NotNullOrWhiteSpace(region, nameof(region));
var key = string.Concat(region, ":");
foreach (var item in _cache.Where(p => p.Key.StartsWith(key, StringComparison.OrdinalIgnoreCase)))
{
_cache.TryRemove(item.Key, out Tuple<Type, byte[]> val);
}
}
public override bool Exists(string key)
{
NotNullOrWhiteSpace(key, nameof(key));
return _cache.ContainsKey(key);
}
public override bool Exists(string key, string region)
{
NotNullOrWhiteSpace(region, nameof(region));
var fullKey = GetKey(key, region);
return _cache.ContainsKey(fullKey);
}
protected override bool AddInternalPrepared(CacheItem<TCacheValue> item)
{
NotNull(item, nameof(item));
var key = GetKey(item.Key, item.Region);
var serializedItem = _serializer.SerializeCacheItem(item);
return _cache.TryAdd(key, new Tuple<Type, byte[]>(item.Value.GetType(), serializedItem));
}
protected override CacheItem<TCacheValue> GetCacheItemInternal(string key) => GetCacheItemInternal(key, null);
protected override CacheItem<TCacheValue> GetCacheItemInternal(string key, string region)
{
var fullKey = GetKey(key, region);
CacheItem<TCacheValue> deserializedResult = null;
if (_cache.TryGetValue(fullKey, out Tuple<Type, byte[]> result))
{
deserializedResult = _serializer.DeserializeCacheItem<TCacheValue>(result.Item2, result.Item1);
if (deserializedResult.ExpirationMode != ExpirationMode.None && IsExpired(deserializedResult, DateTime.UtcNow))
{
_cache.TryRemove(fullKey, out Tuple<Type, byte[]> removeResult);
TriggerCacheSpecificRemove(key, region, CacheItemRemovedReason.Expired, deserializedResult.Value);
return null;
}
}
return deserializedResult;
}
protected override void PutInternalPrepared(CacheItem<TCacheValue> item)
{
NotNull(item, nameof(item));
var serializedItem = _serializer.SerializeCacheItem<TCacheValue>(item);
_cache[GetKey(item.Key, item.Region)] = new Tuple<Type, byte[]>(item.Value.GetType(), serializedItem);
}
protected override bool RemoveInternal(string key) => RemoveInternal(key, null);
protected override bool RemoveInternal(string key, string region)
{
var fullKey = GetKey(key, region);
return _cache.TryRemove(fullKey, out Tuple<Type, byte[]> val);
}
private static string GetKey(string key, string region)
{
NotNullOrWhiteSpace(key, nameof(key));
if (string.IsNullOrWhiteSpace(region))
{
return key;
}
return string.Concat(region, ":", key);
}
private static bool IsExpired(CacheItem<TCacheValue> item, DateTime now)
{
if (item.ExpirationMode == ExpirationMode.Absolute
&& item.CreatedUtc.Add(item.ExpirationTimeout) < now)
{
return true;
}
else if (item.ExpirationMode == ExpirationMode.Sliding
&& item.LastAccessedUtc.Add(item.ExpirationTimeout) < now)
{
return true;
}
return false;
}
}
}

View File

@ -1,239 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.AcceptanceTests
{
public class CachingTests : IDisposable
{
private IWebHost _builder;
private readonly Steps _steps;
public CachingTests()
{
_steps = new Steps();
}
[Fact]
public void should_return_cached_response()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51899,
}
},
DownstreamScheme = "http",
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 100
}
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura", null, null))
.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"))
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => _steps.ThenTheContentLengthIs(16))
.BDDfy();
}
[Fact]
public void should_return_cached_response_with_expires_header()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 52839,
}
},
DownstreamScheme = "http",
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 100
}
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:52839", 200, "Hello from Laura", "Expires", "-1"))
.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"))
.Given(x => x.GivenTheServiceNowReturns("http://localhost:52839", 200, "Hello from Tom"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(x => _steps.ThenTheContentLengthIs(16))
.And(x => _steps.ThenTheResponseBodyHeaderIs("Expires", "-1"))
.BDDfy();
}
[Fact]
public void should_return_cached_response_when_using_jsonserialized_cache()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51899,
}
},
DownstreamScheme = "http",
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 100
}
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura", null, null))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingJsonSerializedCache())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
[Fact]
public void should_not_return_cached_response_as_ttl_expires()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51899,
}
},
DownstreamScheme = "http",
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 1
}
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura", null, null))
.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"))
.Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 200, "Hello from Tom"))
.And(x => x.GivenTheCacheExpires())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom"))
.BDDfy();
}
private void GivenTheCacheExpires()
{
Thread.Sleep(1000);
}
private void GivenTheServiceNowReturns(string url, int statusCode, string responseBody)
{
_builder.Dispose();
GivenThereIsAServiceRunningOn(url, statusCode, responseBody, null, null);
}
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody, string key, string value)
{
_builder = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
if(!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(key))
{
context.Response.Headers.Add(key, value);
}
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody);
});
})
.Build();
_builder.Start();
}
public void Dispose()
{
_builder?.Dispose();
_steps.Dispose();
}
}
}

View File

@ -1,23 +1,21 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.AcceptanceTests namespace Ocelot.AcceptanceTests
{ {
using System;
using System.Collections.Generic;
using System.Net;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using TestStack.BDDfy;
using Xunit;
public class CaseSensitiveRoutingTests : IDisposable public class CaseSensitiveRoutingTests : IDisposable
{ {
private IWebHost _builder;
private readonly Steps _steps; private readonly Steps _steps;
private readonly ServiceHandler _serviceHandler;
public CaseSensitiveRoutingTests() public CaseSensitiveRoutingTests()
{ {
_serviceHandler = new ServiceHandler();
_steps = new Steps(); _steps = new Steps();
} }
@ -226,29 +224,16 @@ namespace Ocelot.AcceptanceTests
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody)
{ {
_builder = new WebHostBuilder() _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(baseUrl)
.Configure(app =>
{
app.UsePathBase(basePath);
app.Run(async context =>
{ {
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody); await context.Response.WriteAsync(responseBody);
}); });
})
.Build();
_builder.Start();
} }
public void Dispose() public void Dispose()
{ {
_builder?.Dispose(); _serviceHandler?.Dispose();
_steps.Dispose(); _steps.Dispose();
} }
} }

View File

@ -1,35 +1,35 @@
using System; using Xunit;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Claims;
using IdentityServer4.AccessTokenValidation;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration.File;
using TestStack.BDDfy;
using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)] [assembly: CollectionBehavior(DisableTestParallelization = true)]
namespace Ocelot.AcceptanceTests namespace Ocelot.AcceptanceTests
{ {
using IdentityServer4;
using IdentityServer4.Test; using IdentityServer4.Test;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Claims;
using IdentityServer4.AccessTokenValidation;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration.File;
using TestStack.BDDfy;
public class ClaimsToHeadersForwardingTests : IDisposable public class ClaimsToHeadersForwardingTests : IDisposable
{ {
private IWebHost _servicebuilder;
private IWebHost _identityServerBuilder; private IWebHost _identityServerBuilder;
private readonly Steps _steps; private readonly Steps _steps;
private Action<IdentityServerAuthenticationOptions> _options; private Action<IdentityServerAuthenticationOptions> _options;
private string _identityServerRootUrl = "http://localhost:52888"; private string _identityServerRootUrl = "http://localhost:52888";
private readonly ServiceHandler _serviceHandler;
public ClaimsToHeadersForwardingTests() public ClaimsToHeadersForwardingTests()
{ {
_serviceHandler = new ServiceHandler();
_steps = new Steps(); _steps = new Steps();
_options = o => _options = o =>
{ {
@ -107,15 +107,7 @@ namespace Ocelot.AcceptanceTests
private void GivenThereIsAServiceRunningOn(string url, int statusCode) private void GivenThereIsAServiceRunningOn(string url, int statusCode)
{ {
_servicebuilder = new WebHostBuilder() _serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{ {
var customerId = context.Request.Headers.First(x => x.Key == "CustomerId").Value.First(); var customerId = context.Request.Headers.First(x => x.Key == "CustomerId").Value.First();
var locationId = context.Request.Headers.First(x => x.Key == "LocationId").Value.First(); var locationId = context.Request.Headers.First(x => x.Key == "LocationId").Value.First();
@ -126,10 +118,6 @@ namespace Ocelot.AcceptanceTests
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody); await context.Response.WriteAsync(responseBody);
}); });
})
.Build();
_servicebuilder.Start();
} }
private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, TestUser user) private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, TestUser user)
@ -203,7 +191,7 @@ namespace Ocelot.AcceptanceTests
public void Dispose() public void Dispose()
{ {
_servicebuilder?.Dispose(); _serviceHandler?.Dispose();
_steps.Dispose(); _steps.Dispose();
_identityServerBuilder?.Dispose(); _identityServerBuilder?.Dispose();
} }

View File

@ -1,36 +1,25 @@
using Microsoft.AspNetCore.Builder; namespace Ocelot.AcceptanceTests
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using Shouldly;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.AcceptanceTests
{ {
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TestStack.BDDfy;
using Xunit;
public class ClientRateLimitTests : IDisposable public class ClientRateLimitTests : IDisposable
{ {
private IWebHost _builder;
private readonly Steps _steps; private readonly Steps _steps;
private int _counterOne; private int _counterOne;
private readonly ServiceHandler _serviceHandler;
public ClientRateLimitTests() public ClientRateLimitTests()
{ {
_serviceHandler = new ServiceHandler();
_steps = new Steps(); _steps = new Steps();
} }
public void Dispose()
{
_builder?.Dispose();
_steps.Dispose();
}
[Fact] [Fact]
public void should_call_withratelimiting() public void should_call_withratelimiting()
{ {
@ -53,7 +42,6 @@ namespace Ocelot.AcceptanceTests
UpstreamPathTemplate = "/api/ClientRateLimit", UpstreamPathTemplate = "/api/ClientRateLimit",
UpstreamHttpMethod = new List<string> { "Get" }, UpstreamHttpMethod = new List<string> { "Get" },
RequestIdKey = _steps.RequestIdKey, RequestIdKey = _steps.RequestIdKey,
RateLimitOptions = new FileRateLimitRule() RateLimitOptions = new FileRateLimitRule()
{ {
EnableRateLimiting = true, EnableRateLimiting = true,
@ -158,6 +146,8 @@ namespace Ocelot.AcceptanceTests
[Fact] [Fact]
public void should_call_middleware_withWhitelistClient() public void should_call_middleware_withWhitelistClient()
{ {
int port = 61876;
var configuration = new FileConfiguration var configuration = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
@ -170,7 +160,7 @@ namespace Ocelot.AcceptanceTests
new FileHostAndPort new FileHostAndPort
{ {
Host = "localhost", Host = "localhost",
Port = 51876, Port = port,
} }
}, },
DownstreamScheme = "http", DownstreamScheme = "http",
@ -201,7 +191,7 @@ namespace Ocelot.AcceptanceTests
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit")) this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/api/ClientRateLimit"))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4)) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4))
@ -211,26 +201,18 @@ namespace Ocelot.AcceptanceTests
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath) private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath)
{ {
_builder = new WebHostBuilder() _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, context =>
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(baseUrl)
.Configure(app =>
{
app.UsePathBase(basePath);
app.Run(context =>
{ {
_counterOne++; _counterOne++;
context.Response.StatusCode = 200; context.Response.StatusCode = 200;
context.Response.WriteAsync(_counterOne.ToString()); context.Response.WriteAsync(_counterOne.ToString());
return Task.CompletedTask; return Task.CompletedTask;
}); });
}) }
.Build();
_builder.Start(); public void Dispose()
{
_steps.Dispose();
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More